installed = mAppWidgetManager.getInstalledProviders();
+ putAppWidgetItems(installed, null, items, categoryFilter, false);
+ }
+}
diff --git a/src/com/android/settings/AppWidgetPickActivity.java b/src/com/android/settings/AppWidgetPickActivity.java
new file mode 100644
index 0000000..2bd62c0
--- /dev/null
+++ b/src/com/android/settings/AppWidgetPickActivity.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.util.Log;
+
+import com.android.settings.ActivityPicker.PickAdapter;
+
+import java.util.List;
+
+/**
+ * Displays a list of {@link AppWidgetProviderInfo} widgets, along with any
+ * injected special widgets specified through
+ * {@link AppWidgetManager#EXTRA_CUSTOM_INFO} and
+ * {@link AppWidgetManager#EXTRA_CUSTOM_EXTRAS}.
+ *
+ * When an installed {@link AppWidgetProviderInfo} is selected, this activity
+ * will bind it to the given {@link AppWidgetManager#EXTRA_APPWIDGET_ID},
+ * otherwise it will return the requested extras.
+ */
+public class AppWidgetPickActivity extends ActivityPicker
+ implements AppWidgetLoader.ItemConstructor{
+ private static final String TAG = "AppWidgetPickActivity";
+ static final boolean LOGD = false;
+
+ List mItems;
+
+ /**
+ * The allocated {@link AppWidgetManager#EXTRA_APPWIDGET_ID} that this
+ * activity is binding.
+ */
+ private int mAppWidgetId;
+ private AppWidgetLoader mAppWidgetLoader;
+ private AppWidgetManager mAppWidgetManager;
+ private PackageManager mPackageManager;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ mPackageManager = getPackageManager();
+ mAppWidgetManager = AppWidgetManager.getInstance(this);
+ mAppWidgetLoader = new AppWidgetLoader
+ (this, mAppWidgetManager, this);
+
+ super.onCreate(icicle);
+
+ // Set default return data
+ setResultData(RESULT_CANCELED, null);
+
+ // Read the appWidgetId passed our direction, otherwise bail if not found
+ final Intent intent = getIntent();
+ if (intent.hasExtra(AppWidgetManager.EXTRA_APPWIDGET_ID)) {
+ mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
+ AppWidgetManager.INVALID_APPWIDGET_ID);
+ } else {
+ finish();
+ }
+ }
+
+ /**
+ * Build and return list of items to be shown in dialog. This will mix both
+ * installed {@link AppWidgetProviderInfo} and those provided through
+ * {@link AppWidgetManager#EXTRA_CUSTOM_INFO}, sorting them alphabetically.
+ */
+ @Override
+ protected List getItems() {
+ mItems = mAppWidgetLoader.getItems(getIntent());
+ return mItems;
+ }
+
+ @Override
+ public PickAdapter.Item createItem(Context context, AppWidgetProviderInfo info, Bundle extras) {
+ CharSequence label = info.label;
+ Drawable icon = null;
+
+ if (info.icon != 0) {
+ try {
+ final Resources res = context.getResources();
+ final int density = res.getDisplayMetrics().densityDpi;
+ int iconDensity;
+ switch (density) {
+ case DisplayMetrics.DENSITY_MEDIUM:
+ iconDensity = DisplayMetrics.DENSITY_LOW;
+ case DisplayMetrics.DENSITY_TV:
+ iconDensity = DisplayMetrics.DENSITY_MEDIUM;
+ case DisplayMetrics.DENSITY_HIGH:
+ iconDensity = DisplayMetrics.DENSITY_MEDIUM;
+ case DisplayMetrics.DENSITY_XHIGH:
+ iconDensity = DisplayMetrics.DENSITY_HIGH;
+ case DisplayMetrics.DENSITY_XXHIGH:
+ iconDensity = DisplayMetrics.DENSITY_XHIGH;
+ default:
+ // The density is some abnormal value. Return some other
+ // abnormal value that is a reasonable scaling of it.
+ iconDensity = (int)((density*0.75f)+.5f);
+ }
+ Resources packageResources = mPackageManager.
+ getResourcesForApplication(info.provider.getPackageName());
+ icon = packageResources.getDrawableForDensity(info.icon, iconDensity);
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Can't load icon drawable 0x" + Integer.toHexString(info.icon)
+ + " for provider: " + info.provider);
+ }
+ if (icon == null) {
+ Log.w(TAG, "Can't load icon drawable 0x" + Integer.toHexString(info.icon)
+ + " for provider: " + info.provider);
+ }
+ }
+
+ PickAdapter.Item item = new PickAdapter.Item(context, label, icon);
+ item.packageName = info.provider.getPackageName();
+ item.className = info.provider.getClassName();
+ item.extras = extras;
+ return item;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Intent intent = getIntentForPosition(which);
+ PickAdapter.Item item = mItems.get(which);
+
+ int result;
+ if (item.extras != null) {
+ // If these extras are present it's because this entry is custom.
+ // Don't try to bind it, just pass it back to the app.
+ setResultData(RESULT_OK, intent);
+ } else {
+ try {
+ Bundle options = null;
+ if (intent.getExtras() != null) {
+ options = intent.getExtras().getBundle(
+ AppWidgetManager.EXTRA_APPWIDGET_OPTIONS);
+ }
+ mAppWidgetManager.bindAppWidgetId(mAppWidgetId, intent.getComponent(), options);
+ result = RESULT_OK;
+ } catch (IllegalArgumentException e) {
+ // This is thrown if they're already bound, or otherwise somehow
+ // bogus. Set the result to canceled, and exit. The app *should*
+ // clean up at this point. We could pass the error along, but
+ // it's not clear that that's useful -- the widget will simply not
+ // appear.
+ result = RESULT_CANCELED;
+ }
+ setResultData(result, null);
+ }
+
+ finish();
+ }
+
+
+ /**
+ * Convenience method for setting the result code and intent. This method
+ * correctly injects the {@link AppWidgetManager#EXTRA_APPWIDGET_ID} that
+ * most hosts expect returned.
+ */
+ void setResultData(int code, Intent intent) {
+ Intent result = intent != null ? intent : new Intent();
+ result.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
+ setResult(code, result);
+ }
+}
diff --git a/src/com/android/settings/ApplicationSettings.java b/src/com/android/settings/ApplicationSettings.java
new file mode 100644
index 0000000..6a17049
--- /dev/null
+++ b/src/com/android/settings/ApplicationSettings.java
@@ -0,0 +1,137 @@
+/*
+ * 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.android.settings;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.PreferenceScreen;
+import android.provider.Settings;
+
+public class ApplicationSettings extends SettingsPreferenceFragment {
+
+ private static final String KEY_TOGGLE_ADVANCED_SETTINGS = "toggle_advanced_settings";
+ private static final String KEY_APP_INSTALL_LOCATION = "app_install_location";
+
+ // App installation location. Default is ask the user.
+ private static final int APP_INSTALL_AUTO = 0;
+ private static final int APP_INSTALL_DEVICE = 1;
+ private static final int APP_INSTALL_SDCARD = 2;
+
+ private static final String APP_INSTALL_DEVICE_ID = "device";
+ private static final String APP_INSTALL_SDCARD_ID = "sdcard";
+ private static final String APP_INSTALL_AUTO_ID = "auto";
+
+ private CheckBoxPreference mToggleAdvancedSettings;
+ private ListPreference mInstallLocation;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ addPreferencesFromResource(R.xml.application_settings);
+
+ mToggleAdvancedSettings = (CheckBoxPreference)findPreference(
+ KEY_TOGGLE_ADVANCED_SETTINGS);
+ mToggleAdvancedSettings.setChecked(isAdvancedSettingsEnabled());
+ getPreferenceScreen().removePreference(mToggleAdvancedSettings);
+
+ // not ready for prime time yet
+ if (false) {
+ getPreferenceScreen().removePreference(mInstallLocation);
+ }
+
+ mInstallLocation = (ListPreference) findPreference(KEY_APP_INSTALL_LOCATION);
+ // Is app default install location set?
+ boolean userSetInstLocation = (Settings.Global.getInt(getContentResolver(),
+ Settings.Global.SET_INSTALL_LOCATION, 0) != 0);
+ if (!userSetInstLocation) {
+ getPreferenceScreen().removePreference(mInstallLocation);
+ } else {
+ mInstallLocation.setValue(getAppInstallLocation());
+ mInstallLocation.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ String value = (String) newValue;
+ handleUpdateAppInstallLocation(value);
+ return false;
+ }
+ });
+ }
+ }
+
+ protected void handleUpdateAppInstallLocation(final String value) {
+ if(APP_INSTALL_DEVICE_ID.equals(value)) {
+ Settings.Global.putInt(getContentResolver(),
+ Settings.Global.DEFAULT_INSTALL_LOCATION, APP_INSTALL_DEVICE);
+ } else if (APP_INSTALL_SDCARD_ID.equals(value)) {
+ Settings.Global.putInt(getContentResolver(),
+ Settings.Global.DEFAULT_INSTALL_LOCATION, APP_INSTALL_SDCARD);
+ } else if (APP_INSTALL_AUTO_ID.equals(value)) {
+ Settings.Global.putInt(getContentResolver(),
+ Settings.Global.DEFAULT_INSTALL_LOCATION, APP_INSTALL_AUTO);
+ } else {
+ // Should not happen, default to prompt...
+ Settings.Global.putInt(getContentResolver(),
+ Settings.Global.DEFAULT_INSTALL_LOCATION, APP_INSTALL_AUTO);
+ }
+ mInstallLocation.setValue(value);
+ }
+
+ @Override
+ public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
+ if (preference == mToggleAdvancedSettings) {
+ boolean value = mToggleAdvancedSettings.isChecked();
+ setAdvancedSettingsEnabled(value);
+ }
+
+ return super.onPreferenceTreeClick(preferenceScreen, preference);
+ }
+
+ private boolean isAdvancedSettingsEnabled() {
+ return Settings.System.getInt(getContentResolver(),
+ Settings.System.ADVANCED_SETTINGS,
+ Settings.System.ADVANCED_SETTINGS_DEFAULT) > 0;
+ }
+
+ private void setAdvancedSettingsEnabled(boolean enabled) {
+ int value = enabled ? 1 : 0;
+ // Change the system setting
+ Settings.Secure.putInt(getContentResolver(), Settings.System.ADVANCED_SETTINGS, value);
+ // TODO: the settings thing should broadcast this for thread safety purposes.
+ Intent intent = new Intent(Intent.ACTION_ADVANCED_SETTINGS_CHANGED);
+ intent.putExtra("state", value);
+ getActivity().sendBroadcast(intent);
+ }
+
+ private String getAppInstallLocation() {
+ int selectedLocation = Settings.Global.getInt(getContentResolver(),
+ Settings.Global.DEFAULT_INSTALL_LOCATION, APP_INSTALL_AUTO);
+ if (selectedLocation == APP_INSTALL_DEVICE) {
+ return APP_INSTALL_DEVICE_ID;
+ } else if (selectedLocation == APP_INSTALL_SDCARD) {
+ return APP_INSTALL_SDCARD_ID;
+ } else if (selectedLocation == APP_INSTALL_AUTO) {
+ return APP_INSTALL_AUTO_ID;
+ } else {
+ // Default value, should not happen.
+ return APP_INSTALL_AUTO_ID;
+ }
+ }
+}
diff --git a/src/com/android/settings/AudioChannelsSelect.java b/src/com/android/settings/AudioChannelsSelect.java
new file mode 100644
index 0000000..34744d3
--- /dev/null
+++ b/src/com/android/settings/AudioChannelsSelect.java
@@ -0,0 +1,198 @@
+package com.android.settings;
+
+import java.util.ArrayList;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnCancelListener;
+import android.content.DialogInterface.OnDismissListener;
+
+import android.media.AudioManager;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
+import android.widget.ListView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+import android.provider.Settings;
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.view.KeyEvent;
+
+public class AudioChannelsSelect implements OnItemClickListener,OnDismissListener,OnCancelListener{
+ private Context mContext;
+ private Dialog mDialog;
+ private ListView mList;
+ private TableRow mTable;
+ private ArrayList mChannels;
+ private AudioManager mAudioManager;
+ private BroadcastReceiver mReceiver;
+ private static final String AUDIO_STATE = "audioState";
+ private static final String AUDIO_NAME = "audioName";
+ private static final String AUDIO_TYPE = "audioType";
+ public AudioChannelsSelect(Context context){
+ mContext = context;
+ mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ mReceiver = new BroadcastReceiver(){
+ @Override
+ public void onReceive(Context context,Intent intent){
+ if(mDialog == null || !mDialog.isShowing()){
+ return;
+ }
+ Bundle bundle = intent.getExtras();
+ final int state = bundle.getInt(AUDIO_STATE);
+ final String name = bundle.getString(AUDIO_NAME);
+ final int type = bundle.getInt(AUDIO_TYPE);
+ try {
+ Thread.currentThread().sleep(500);
+ }catch(Exception e) {};
+
+ mChannels = mAudioManager.getActiveAudioDevices(AudioManager.AUDIO_OUTPUT_ACTIVE);
+ boolean hasHdmi = audioHasHdmi();
+ mTable.clear();
+ ArrayList list = mAudioManager.getAudioDevices(AudioManager.AUDIO_OUTPUT_TYPE);
+ for(String st:list){
+ if(!hasHdmi){
+ if(st.contains("HDMI")){
+ continue;
+ }
+ }
+ mTable.add(st);
+ }
+
+ }
+ };
+ }
+
+ public void registerUSBAudioReceiver(){
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_AUDIO_PLUG_IN_OUT);
+ mContext.registerReceiver(mReceiver, filter);
+ }
+
+ public void unregisterUSBAudioReceiver(){
+ if(mReceiver != null){
+ mContext.unregisterReceiver(mReceiver);
+ }
+ }
+
+ private boolean audioHasHdmi(){
+ /*
+ String databaseValue = Settings.System.getString(mContext.getContentResolver(),Settings.System.DISPLY_OUTPUT_FORMAT);
+ if(databaseValue != null && databaseValue.startsWith("HDMI")){
+ return true;
+ }else{
+ return false;
+ }
+ */
+ return true;
+ }
+
+ public void showChannelsSelectDialog(){
+ if(mDialog == null){
+ mDialog = new Dialog(mContext);
+ mList = new ListView(mContext);
+ mTable = new TableRow(mContext);
+ mList.setAdapter(mTable);
+ mList.setOnItemClickListener(this);
+ mTable.setNotifyOnChange(true);
+ mDialog.setContentView(mList);
+ mDialog.setCanceledOnTouchOutside(true);
+ mDialog.setOnCancelListener(this);
+ mDialog.setOnDismissListener(this);
+ mDialog.setTitle(R.string.audio_output_mode_title);
+ mChannels = new ArrayList();
+ }
+ mChannels = mAudioManager.getActiveAudioDevices(AudioManager.AUDIO_OUTPUT_ACTIVE);
+ mTable.clear();
+ boolean hasHdmi = audioHasHdmi();
+ ArrayList list = mAudioManager.getAudioDevices(AudioManager.AUDIO_OUTPUT_TYPE);
+ for(String st:list){
+ if(!hasHdmi){
+ if(st.contains("HDMI")){
+ continue;
+ }
+ }
+ mTable.add(st);
+ }
+ mDialog.show();
+ Window w = mDialog.getWindow();
+ w.setLayout(400,300);
+ }
+
+ public void dismissDialog(){
+ if(mDialog != null) mDialog.dismiss();
+
+ }
+
+ private class TableRow extends ArrayAdapter{
+ private Context context = null;
+ public TableRow(Context context) {
+ super(context, R.layout.audio_channels_select);
+ this.context = context;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent){
+ if(convertView == null){
+ LayoutInflater inflater = LayoutInflater.from(context);
+ RelativeLayout ly = (RelativeLayout) inflater.inflate(R.layout.audio_channels_select, null);
+ convertView = ly;
+ }
+ TextView text = (TextView)convertView.findViewById(R.id.audio_channel_name);
+ CheckBox check = (CheckBox)convertView.findViewById(R.id.check_audio_selected);
+ text.setText(this.getItem(position));
+ if(mChannels.contains(this.getItem(position))){
+ check.setChecked(true);
+ }else{
+ check.setChecked(false);
+ }
+ return convertView;
+ }
+ }
+
+ @Override
+ public void onItemClick(AdapterView> arg0, View arg1, int arg2, long arg3) {
+ CheckBox check = (CheckBox)arg1.findViewById(R.id.check_audio_selected);
+ if(check.isChecked()){
+ check.setChecked(false);
+ mChannels.remove(mTable.getItem(arg2));
+ }else{
+ check.setChecked(true);
+ mChannels.clear(); //注释掉这句话就会实现多选
+ mChannels.add(mTable.getItem(arg2));
+ }
+ mTable.notifyDataSetChanged();
+ }
+
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ Log.d("chen","onCancel() save audio output channels");
+ mAudioManager.setAudioDeviceActive(mChannels, AudioManager.AUDIO_OUTPUT_ACTIVE);
+ String st = null;
+ for(int i = 0; i < mChannels.size(); i++){
+ if(st == null){
+ st = mChannels.get(i);
+ }
+ else{
+ st = st + "," + mChannels.get(i);
+ }
+ }
+ Settings.System.putString(mContext.getContentResolver(), Settings.System.AUDIO_OUTPUT_CHANNEL, st);
+ }
+
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ Log.d("chen","onDismiss() save audio output channels");
+ mAudioManager.setAudioDeviceActive(mChannels, AudioManager.AUDIO_OUTPUT_ACTIVE);
+ }
+}
diff --git a/src/com/android/settings/BandMode.java b/src/com/android/settings/BandMode.java
new file mode 100644
index 0000000..0a0f77f
--- /dev/null
+++ b/src/com/android/settings/BandMode.java
@@ -0,0 +1,219 @@
+package com.android.settings;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.Handler;
+import android.os.AsyncResult;
+import android.util.Log;
+import android.content.DialogInterface;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.Window;
+import android.widget.ListView;
+import android.widget.ArrayAdapter;
+import android.widget.AdapterView;
+
+
+/**
+ * Radio Band Mode Selection Class
+ *
+ * It will query baseband about all available band modes and display them
+ * in screen. It will display all six band modes if the query failed.
+ *
+ * After user select one band, it will send the selection to baseband.
+ *
+ * It will alter user the result of select operation and exit, no matter success
+ * or not.
+ *
+ */
+public class BandMode extends Activity {
+ private static final String LOG_TAG = "phone";
+ private static final boolean DBG = false;
+
+ private static final int EVENT_BAND_SCAN_COMPLETED = 100;
+ private static final int EVENT_BAND_SELECTION_DONE = 200;
+
+ private static final String[] BAND_NAMES = new String[] {
+ "Automatic",
+ "EURO Band",
+ "USA Band",
+ "JAPAN Band",
+ "AUS Band",
+ "AUS2 Band"
+ };
+
+ private ListView mBandList;
+ private ArrayAdapter mBandListAdapter;
+ private BandListItem mTargetBand = null;
+ private DialogInterface mProgressPanel;
+
+ private Phone mPhone = null;
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+
+ setContentView(R.layout.band_mode);
+
+ setTitle(getString(R.string.band_mode_title));
+ getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.WRAP_CONTENT);
+
+ mPhone = PhoneFactory.getDefaultPhone();
+
+ mBandList = (ListView) findViewById(R.id.band);
+ mBandListAdapter = new ArrayAdapter(this,
+ android.R.layout.simple_list_item_1);
+ mBandList.setAdapter(mBandListAdapter);
+ mBandList.setOnItemClickListener(mBandSelectionHandler);
+
+
+
+ loadBandList();
+ }
+
+ private AdapterView.OnItemClickListener mBandSelectionHandler =
+ new AdapterView.OnItemClickListener () {
+ public void onItemClick(AdapterView parent, View v,
+ int position, long id) {
+
+ getWindow().setFeatureInt(
+ Window.FEATURE_INDETERMINATE_PROGRESS,
+ Window.PROGRESS_VISIBILITY_ON);
+
+ mTargetBand = (BandListItem) parent.getAdapter().getItem(position);
+
+ if (DBG) log("Select band : " + mTargetBand.toString());
+
+ Message msg =
+ mHandler.obtainMessage(EVENT_BAND_SELECTION_DONE);
+ mPhone.setBandMode(mTargetBand.getBand(), msg);
+ }
+ };
+
+ static private class BandListItem {
+ private int mBandMode = Phone.BM_UNSPECIFIED;
+
+ public BandListItem(int bm) {
+ mBandMode = bm;
+ }
+
+ public int getBand() {
+ return mBandMode;
+ }
+
+ public String toString() {
+ return BAND_NAMES[mBandMode];
+ }
+ }
+
+ private void loadBandList() {
+ String str = getString(R.string.band_mode_loading);
+
+ if (DBG) log(str);
+
+
+ //ProgressDialog.show(this, null, str, true, true, null);
+ mProgressPanel = new AlertDialog.Builder(this)
+ .setMessage(str)
+ .show();
+
+ Message msg = mHandler.obtainMessage(EVENT_BAND_SCAN_COMPLETED);
+ mPhone.queryAvailableBandMode(msg);
+
+ }
+
+ private void bandListLoaded(AsyncResult result) {
+ if (DBG) log("network list loaded");
+
+ if (mProgressPanel != null) mProgressPanel.dismiss();
+
+ clearList();
+
+ boolean addBandSuccess = false;
+ BandListItem item;
+
+ if (result.result != null) {
+ int bands[] = (int[])result.result;
+ int size = bands[0];
+
+ if (size > 0) {
+ for (int i=1; i 0) {
+ mBandListAdapter.remove(
+ mBandListAdapter.getItem(0));
+ }
+ }
+
+ private void log(String msg) {
+ Log.d(LOG_TAG, "[BandsList] " + msg);
+ }
+
+ private Handler mHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ AsyncResult ar;
+ switch (msg.what) {
+ case EVENT_BAND_SCAN_COMPLETED:
+ ar = (AsyncResult) msg.obj;
+
+ bandListLoaded(ar);
+ break;
+
+ case EVENT_BAND_SELECTION_DONE:
+ ar = (AsyncResult) msg.obj;
+
+ getWindow().setFeatureInt(
+ Window.FEATURE_INDETERMINATE_PROGRESS,
+ Window.PROGRESS_VISIBILITY_OFF);
+
+ if (!isFinishing()) {
+ displayBandSelectionResult(ar.exception);
+ }
+ break;
+ }
+ }
+ };
+
+
+}
diff --git a/src/com/android/settings/BatteryInfo.java b/src/com/android/settings/BatteryInfo.java
new file mode 100644
index 0000000..ccad236
--- /dev/null
+++ b/src/com/android/settings/BatteryInfo.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.BatteryManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IPowerManager;
+import android.os.Message;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.text.format.DateUtils;
+import android.widget.TextView;
+
+import com.android.internal.app.IBatteryStats;
+
+public class BatteryInfo extends Activity {
+ private TextView mStatus;
+ private TextView mPower;
+ private TextView mLevel;
+ private TextView mScale;
+ private TextView mHealth;
+ private TextView mVoltage;
+ private TextView mTemperature;
+ private TextView mTechnology;
+ private TextView mUptime;
+ private IBatteryStats mBatteryStats;
+ private IPowerManager mScreenStats;
+
+ private static final int EVENT_TICK = 1;
+
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_TICK:
+ updateBatteryStats();
+ sendEmptyMessageDelayed(EVENT_TICK, 1000);
+
+ break;
+ }
+ }
+ };
+
+ /**
+ * Format a number of tenths-units as a decimal string without using a
+ * conversion to float. E.g. 347 -> "34.7"
+ */
+ private final String tenthsToFixedString(int x) {
+ int tens = x / 10;
+ return Integer.toString(tens) + "." + (x - 10 * tens);
+ }
+
+ /**
+ *Listens for intent broadcasts
+ */
+ private IntentFilter mIntentFilter;
+
+ private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
+ int plugType = intent.getIntExtra("plugged", 0);
+
+ mLevel.setText("" + intent.getIntExtra("level", 0));
+ mScale.setText("" + intent.getIntExtra("scale", 0));
+ mVoltage.setText("" + intent.getIntExtra("voltage", 0) + " "
+ + getString(R.string.battery_info_voltage_units));
+ mTemperature.setText("" + tenthsToFixedString(intent.getIntExtra("temperature", 0))
+ + getString(R.string.battery_info_temperature_units));
+ mTechnology.setText("" + intent.getStringExtra("technology"));
+
+ mStatus.setText(Utils.getBatteryStatus(getResources(), intent));
+
+ switch (plugType) {
+ case 0:
+ mPower.setText(getString(R.string.battery_info_power_unplugged));
+ break;
+ case BatteryManager.BATTERY_PLUGGED_AC:
+ mPower.setText(getString(R.string.battery_info_power_ac));
+ break;
+ case BatteryManager.BATTERY_PLUGGED_USB:
+ mPower.setText(getString(R.string.battery_info_power_usb));
+ break;
+ case BatteryManager.BATTERY_PLUGGED_WIRELESS:
+ mPower.setText(getString(R.string.battery_info_power_wireless));
+ break;
+ case (BatteryManager.BATTERY_PLUGGED_AC|BatteryManager.BATTERY_PLUGGED_USB):
+ mPower.setText(getString(R.string.battery_info_power_ac_usb));
+ break;
+ default:
+ mPower.setText(getString(R.string.battery_info_power_unknown));
+ break;
+ }
+
+ int health = intent.getIntExtra("health", BatteryManager.BATTERY_HEALTH_UNKNOWN);
+ String healthString;
+ if (health == BatteryManager.BATTERY_HEALTH_GOOD) {
+ healthString = getString(R.string.battery_info_health_good);
+ } else if (health == BatteryManager.BATTERY_HEALTH_OVERHEAT) {
+ healthString = getString(R.string.battery_info_health_overheat);
+ } else if (health == BatteryManager.BATTERY_HEALTH_DEAD) {
+ healthString = getString(R.string.battery_info_health_dead);
+ } else if (health == BatteryManager.BATTERY_HEALTH_OVER_VOLTAGE) {
+ healthString = getString(R.string.battery_info_health_over_voltage);
+ } else if (health == BatteryManager.BATTERY_HEALTH_UNSPECIFIED_FAILURE) {
+ healthString = getString(R.string.battery_info_health_unspecified_failure);
+ } else if (health == BatteryManager.BATTERY_HEALTH_COLD) {
+ healthString = getString(R.string.battery_info_health_cold);
+ } else {
+ healthString = getString(R.string.battery_info_health_unknown);
+ }
+ mHealth.setText(healthString);
+ }
+ }
+ };
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ setContentView(R.layout.battery_info);
+
+ // create the IntentFilter that will be used to listen
+ // to battery status broadcasts
+ mIntentFilter = new IntentFilter();
+ mIntentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ mStatus = (TextView)findViewById(R.id.status);
+ mPower = (TextView)findViewById(R.id.power);
+ mLevel = (TextView)findViewById(R.id.level);
+ mScale = (TextView)findViewById(R.id.scale);
+ mHealth = (TextView)findViewById(R.id.health);
+ mTechnology = (TextView)findViewById(R.id.technology);
+ mVoltage = (TextView)findViewById(R.id.voltage);
+ mTemperature = (TextView)findViewById(R.id.temperature);
+ mUptime = (TextView) findViewById(R.id.uptime);
+
+ // Get awake time plugged in and on battery
+ mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo"));
+ mScreenStats = IPowerManager.Stub.asInterface(ServiceManager.getService(POWER_SERVICE));
+ mHandler.sendEmptyMessageDelayed(EVENT_TICK, 1000);
+
+ registerReceiver(mIntentReceiver, mIntentFilter);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ mHandler.removeMessages(EVENT_TICK);
+
+ // we are no longer on the screen stop the observers
+ unregisterReceiver(mIntentReceiver);
+ }
+
+ private void updateBatteryStats() {
+ long uptime = SystemClock.elapsedRealtime();
+ mUptime.setText(DateUtils.formatElapsedTime(uptime / 1000));
+ }
+
+}
diff --git a/src/com/android/settings/BootUpReceiver.java b/src/com/android/settings/BootUpReceiver.java
new file mode 100644
index 0000000..57bcb54
--- /dev/null
+++ b/src/com/android/settings/BootUpReceiver.java
@@ -0,0 +1,36 @@
+
+package com.android.settings;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.provider.Settings;
+import android.util.Log;
+
+public class BootUpReceiver extends BroadcastReceiver {
+ private Handler mHandler = new Handler();
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final Context mContext = context;
+ if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
+ Log.d("boot", "boot start");
+ Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AUTO_TIME, 0);
+ Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AUTO_TIME_ZONE, 0);
+ mHandler.postDelayed(new Runnable() {
+
+ @Override
+ public void run() {
+ Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AUTO_TIME,
+ 1);
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.AUTO_TIME_ZONE, 1);
+ }
+ }, 5000);
+ }
+
+ }
+
+}
diff --git a/src/com/android/settings/BrightnessPreference.java b/src/com/android/settings/BrightnessPreference.java
new file mode 100644
index 0000000..77b3fba
--- /dev/null
+++ b/src/com/android/settings/BrightnessPreference.java
@@ -0,0 +1,350 @@
+/*
+ * 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.android.settings;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IPowerManager;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.preference.SeekBarDialogPreference;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.SeekBar;
+
+public class BrightnessPreference extends SeekBarDialogPreference implements
+ SeekBar.OnSeekBarChangeListener, CheckBox.OnCheckedChangeListener {
+ // If true, enables the use of the screen auto-brightness adjustment setting.
+ private static final boolean USE_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT =
+ PowerManager.useScreenAutoBrightnessAdjustmentFeature();
+
+ private final int mScreenBrightnessMinimum;
+ private final int mScreenBrightnessMaximum;
+
+ private SeekBar mSeekBar;
+ //private CheckBox mCheckBox;
+
+ private int mOldBrightness;
+ private int mOldAutomatic;
+
+ private boolean mAutomaticAvailable;
+ private boolean mAutomaticMode;
+
+ private int mCurBrightness = -1;
+
+ private boolean mRestoredOldState;
+
+ private static final int SEEK_BAR_RANGE = 10000;
+
+ private ContentObserver mBrightnessObserver = new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ mCurBrightness = -1;
+ onBrightnessChanged();
+ }
+ };
+
+ private ContentObserver mBrightnessModeObserver = new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ onBrightnessModeChanged();
+ }
+ };
+
+ public BrightnessPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+ mScreenBrightnessMinimum = pm.getMinimumScreenBrightnessSetting();
+ mScreenBrightnessMaximum = pm.getMaximumScreenBrightnessSetting();
+
+ mAutomaticAvailable = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_automatic_brightness_available);
+
+ setDialogLayoutResource(R.layout.preference_dialog_brightness);
+ setDialogIcon(R.drawable.ic_settings_display);
+ }
+
+ @Override
+ protected void showDialog(Bundle state) {
+ super.showDialog(state);
+
+ getContext().getContentResolver().registerContentObserver(
+ Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS), true,
+ mBrightnessObserver);
+
+ getContext().getContentResolver().registerContentObserver(
+ Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE), true,
+ mBrightnessModeObserver);
+ mRestoredOldState = false;
+ }
+
+ @Override
+ protected void onBindDialogView(View view) {
+ super.onBindDialogView(view);
+
+ mSeekBar = getSeekBar(view);
+ mSeekBar.setMax(SEEK_BAR_RANGE);
+ mOldBrightness = getBrightness();
+ mSeekBar.setProgress(mOldBrightness);
+
+ /*
+ mCheckBox = (CheckBox)view.findViewById(R.id.automatic_mode);
+ if (mAutomaticAvailable) {
+ mCheckBox.setOnCheckedChangeListener(this);
+ mOldAutomatic = getBrightnessMode(0);
+ mAutomaticMode = mOldAutomatic == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
+ mCheckBox.setChecked(mAutomaticMode);
+ mSeekBar.setEnabled(!mAutomaticMode || USE_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT);
+ } else {
+ mSeekBar.setEnabled(true);
+ }
+
+ */
+
+ mSeekBar.setEnabled(true);
+
+ mSeekBar.setOnSeekBarChangeListener(this);
+ }
+
+ public void onProgressChanged(SeekBar seekBar, int progress,
+ boolean fromTouch) {
+ setBrightness(progress, false);
+ }
+
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ // NA
+ }
+
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ // NA
+ }
+
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ setMode(isChecked ? Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC
+ : Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+ mSeekBar.setProgress(getBrightness());
+ mSeekBar.setEnabled(!mAutomaticMode || USE_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT);
+ setBrightness(mSeekBar.getProgress(), false);
+ }
+
+ private int getBrightness() {
+ int mode = getBrightnessMode(0);
+ float brightness = 0;
+ if (USE_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT
+ && mode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) {
+ brightness = Settings.System.getFloat(getContext().getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0);
+ brightness = (brightness+1)/2;
+ } else {
+ if (mCurBrightness < 0) {
+ brightness = Settings.System.getInt(getContext().getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS, 100);
+ } else {
+ brightness = mCurBrightness;
+ }
+ brightness = (brightness - mScreenBrightnessMinimum)
+ / (mScreenBrightnessMaximum - mScreenBrightnessMinimum);
+ }
+ return (int)(brightness*SEEK_BAR_RANGE);
+ }
+
+ private int getBrightnessMode(int defaultValue) {
+ int brightnessMode = defaultValue;
+ try {
+ brightnessMode = Settings.System.getInt(getContext().getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE);
+ } catch (SettingNotFoundException snfe) {
+ }
+ return brightnessMode;
+ }
+
+ private void onBrightnessChanged() {
+ mSeekBar.setProgress(getBrightness());
+ }
+
+ private void onBrightnessModeChanged() {
+ boolean checked = getBrightnessMode(0)
+ == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
+ //mCheckBox.setChecked(checked);
+ mSeekBar.setProgress(getBrightness());
+ mSeekBar.setEnabled(!checked || USE_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT);
+ }
+
+ @Override
+ protected void onDialogClosed(boolean positiveResult) {
+ super.onDialogClosed(positiveResult);
+
+ final ContentResolver resolver = getContext().getContentResolver();
+
+ if (positiveResult) {
+ setBrightness(mSeekBar.getProgress(), true);
+ } else {
+ restoreOldState();
+ }
+
+ resolver.unregisterContentObserver(mBrightnessObserver);
+ resolver.unregisterContentObserver(mBrightnessModeObserver);
+ }
+
+ private void restoreOldState() {
+ if (mRestoredOldState) return;
+
+ if (mAutomaticAvailable) {
+ setMode(mOldAutomatic);
+ }
+ setBrightness(mOldBrightness, false);
+ mRestoredOldState = true;
+ mCurBrightness = -1;
+ }
+
+ private void setBrightness(int brightness, boolean write) {
+ if (mAutomaticMode) {
+ if (USE_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT) {
+ float valf = (((float)brightness*2)/SEEK_BAR_RANGE) - 1.0f;
+ try {
+ IPowerManager power = IPowerManager.Stub.asInterface(
+ ServiceManager.getService("power"));
+ if (power != null) {
+ power.setTemporaryScreenAutoBrightnessAdjustmentSettingOverride(valf);
+ }
+ if (write) {
+ final ContentResolver resolver = getContext().getContentResolver();
+ Settings.System.putFloat(resolver,
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, valf);
+ }
+ } catch (RemoteException doe) {
+ }
+ }
+ } else {
+ int range = (mScreenBrightnessMaximum - mScreenBrightnessMinimum);
+ brightness = (brightness * range)/SEEK_BAR_RANGE + mScreenBrightnessMinimum;
+ try {
+ IPowerManager power = IPowerManager.Stub.asInterface(
+ ServiceManager.getService("power"));
+ if (power != null) {
+ power.setTemporaryScreenBrightnessSettingOverride(brightness);
+ }
+ if (write) {
+ mCurBrightness = -1;
+ final ContentResolver resolver = getContext().getContentResolver();
+ Settings.System.putInt(resolver,
+ Settings.System.SCREEN_BRIGHTNESS, brightness);
+ } else {
+ mCurBrightness = brightness;
+ }
+ } catch (RemoteException doe) {
+ }
+ }
+ }
+
+ private void setMode(int mode) {
+ mAutomaticMode = mode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
+ Settings.System.putInt(getContext().getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE, mode);
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ final Parcelable superState = super.onSaveInstanceState();
+ if (getDialog() == null || !getDialog().isShowing()) return superState;
+
+ // Save the dialog state
+ final SavedState myState = new SavedState(superState);
+ //myState.automatic = mCheckBox.isChecked();
+ myState.progress = mSeekBar.getProgress();
+ myState.oldAutomatic = mOldAutomatic == 1;
+ myState.oldProgress = mOldBrightness;
+ myState.curBrightness = mCurBrightness;
+
+ // Restore the old state when the activity or dialog is being paused
+ restoreOldState();
+ return myState;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ if (state == null || !state.getClass().equals(SavedState.class)) {
+ // Didn't save state for us in onSaveInstanceState
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
+ SavedState myState = (SavedState) state;
+ super.onRestoreInstanceState(myState.getSuperState());
+ mOldBrightness = myState.oldProgress;
+ mOldAutomatic = myState.oldAutomatic ? 1 : 0;
+ setMode(myState.automatic ? 1 : 0);
+ setBrightness(myState.progress, false);
+ mCurBrightness = myState.curBrightness;
+ }
+
+ private static class SavedState extends BaseSavedState {
+
+ boolean automatic;
+ boolean oldAutomatic;
+ int progress;
+ int oldProgress;
+ int curBrightness;
+
+ public SavedState(Parcel source) {
+ super(source);
+ automatic = source.readInt() == 1;
+ progress = source.readInt();
+ oldAutomatic = source.readInt() == 1;
+ oldProgress = source.readInt();
+ curBrightness = source.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(automatic ? 1 : 0);
+ dest.writeInt(progress);
+ dest.writeInt(oldAutomatic ? 1 : 0);
+ dest.writeInt(oldProgress);
+ dest.writeInt(curBrightness);
+ }
+
+ public SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ public static final Parcelable.Creator CREATOR =
+ new Parcelable.Creator() {
+
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+}
+
diff --git a/src/com/android/settings/BugreportPreference.java b/src/com/android/settings/BugreportPreference.java
new file mode 100644
index 0000000..ba58ef4
--- /dev/null
+++ b/src/com/android/settings/BugreportPreference.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import android.app.AlertDialog.Builder;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.os.SystemProperties;
+import android.preference.DialogPreference;
+import android.util.AttributeSet;
+import android.view.View;
+
+public class BugreportPreference extends DialogPreference {
+ public BugreportPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onPrepareDialogBuilder(Builder builder) {
+ super.onPrepareDialogBuilder(builder);
+ builder.setPositiveButton(com.android.internal.R.string.report, this);
+ builder.setMessage(com.android.internal.R.string.bugreport_message);
+ }
+
+ @Override
+ protected void showDialog(Bundle state) {
+ super.showDialog(state);
+ }
+
+ @Override
+ protected void onBindDialogView(View view) {
+ super.onBindDialogView(view);
+ }
+
+ @Override
+ protected void onDialogClosed(boolean positiveResult) {
+ super.onDialogClosed(positiveResult);
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ SystemProperties.set("ctl.start", "bugreport");
+ }
+ }
+}
diff --git a/src/com/android/settings/ButtonBarHandler.java b/src/com/android/settings/ButtonBarHandler.java
new file mode 100644
index 0000000..d61da13
--- /dev/null
+++ b/src/com/android/settings/ButtonBarHandler.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings;
+
+import android.widget.Button;
+
+/**
+ * Interface letting {@link SettingsPreferenceFragment} access to bottom bar inside
+ * {@link android.preference.PreferenceActivity}.
+ */
+public interface ButtonBarHandler {
+ public boolean hasNextButton();
+ public Button getNextButton();
+}
\ No newline at end of file
diff --git a/src/com/android/settings/ChooseLockGeneric.java b/src/com/android/settings/ChooseLockGeneric.java
new file mode 100644
index 0000000..6854305
--- /dev/null
+++ b/src/com/android/settings/ChooseLockGeneric.java
@@ -0,0 +1,423 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.os.Bundle;
+import android.os.UserManager;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
+import android.security.KeyStore;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ListView;
+
+import com.android.internal.widget.LockPatternUtils;
+
+import java.util.List;
+
+import libcore.util.MutableBoolean;
+
+public class ChooseLockGeneric extends PreferenceActivity {
+
+ @Override
+ public Intent getIntent() {
+ Intent modIntent = new Intent(super.getIntent());
+ modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ChooseLockGenericFragment.class.getName());
+ modIntent.putExtra(EXTRA_NO_HEADERS, true);
+ return modIntent;
+ }
+
+ public static class ChooseLockGenericFragment extends SettingsPreferenceFragment {
+ private static final int MIN_PASSWORD_LENGTH = 4;
+ private static final String KEY_UNLOCK_BACKUP_INFO = "unlock_backup_info";
+ private static final String KEY_UNLOCK_SET_OFF = "unlock_set_off";
+ private static final String KEY_UNLOCK_SET_NONE = "unlock_set_none";
+ private static final String KEY_UNLOCK_SET_BIOMETRIC_WEAK = "unlock_set_biometric_weak";
+ private static final String KEY_UNLOCK_SET_PIN = "unlock_set_pin";
+ private static final String KEY_UNLOCK_SET_PASSWORD = "unlock_set_password";
+ private static final String KEY_UNLOCK_SET_PATTERN = "unlock_set_pattern";
+ private static final int CONFIRM_EXISTING_REQUEST = 100;
+ private static final int FALLBACK_REQUEST = 101;
+ private static final String PASSWORD_CONFIRMED = "password_confirmed";
+ private static final String CONFIRM_CREDENTIALS = "confirm_credentials";
+ private static final String WAITING_FOR_CONFIRMATION = "waiting_for_confirmation";
+ private static final String FINISH_PENDING = "finish_pending";
+ public static final String MINIMUM_QUALITY_KEY = "minimum_quality";
+
+ private static final boolean ALWAY_SHOW_TUTORIAL = true;
+
+ private ChooseLockSettingsHelper mChooseLockSettingsHelper;
+ private DevicePolicyManager mDPM;
+ private KeyStore mKeyStore;
+ private boolean mPasswordConfirmed = false;
+ private boolean mWaitingForConfirmation = false;
+ private boolean mFinishPending = false;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mDPM = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
+ mKeyStore = KeyStore.getInstance();
+ mChooseLockSettingsHelper = new ChooseLockSettingsHelper(this.getActivity());
+
+ // Defaults to needing to confirm credentials
+ final boolean confirmCredentials = getActivity().getIntent()
+ .getBooleanExtra(CONFIRM_CREDENTIALS, true);
+ mPasswordConfirmed = !confirmCredentials;
+
+ if (savedInstanceState != null) {
+ mPasswordConfirmed = savedInstanceState.getBoolean(PASSWORD_CONFIRMED);
+ mWaitingForConfirmation = savedInstanceState.getBoolean(WAITING_FOR_CONFIRMATION);
+ mFinishPending = savedInstanceState.getBoolean(FINISH_PENDING);
+ }
+
+ if (mPasswordConfirmed) {
+ updatePreferencesOrFinish();
+ } else if (!mWaitingForConfirmation) {
+ ChooseLockSettingsHelper helper =
+ new ChooseLockSettingsHelper(this.getActivity(), this);
+ if (!helper.launchConfirmationActivity(CONFIRM_EXISTING_REQUEST, null, null)) {
+ mPasswordConfirmed = true; // no password set, so no need to confirm
+ updatePreferencesOrFinish();
+ } else {
+ mWaitingForConfirmation = true;
+ }
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (mFinishPending) {
+ mFinishPending = false;
+ finish();
+ }
+ }
+
+ @Override
+ public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
+ Preference preference) {
+ final String key = preference.getKey();
+ boolean handled = true;
+ if (KEY_UNLOCK_SET_OFF.equals(key)) {
+ updateUnlockMethodAndFinish(
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, true);
+ } else if (KEY_UNLOCK_SET_NONE.equals(key)) {
+ updateUnlockMethodAndFinish(
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, false);
+ } else if (KEY_UNLOCK_SET_BIOMETRIC_WEAK.equals(key)) {
+ updateUnlockMethodAndFinish(
+ DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK, false);
+ }else if (KEY_UNLOCK_SET_PATTERN.equals(key)) {
+ updateUnlockMethodAndFinish(
+ DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, false);
+ } else if (KEY_UNLOCK_SET_PIN.equals(key)) {
+ updateUnlockMethodAndFinish(
+ DevicePolicyManager.PASSWORD_QUALITY_NUMERIC, false);
+ } else if (KEY_UNLOCK_SET_PASSWORD.equals(key)) {
+ updateUnlockMethodAndFinish(
+ DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, false);
+ } else {
+ handled = false;
+ }
+ return handled;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View v = super.onCreateView(inflater, container, savedInstanceState);
+ final boolean onlyShowFallback = getActivity().getIntent()
+ .getBooleanExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, false);
+ if (onlyShowFallback) {
+ View header = v.inflate(getActivity(),
+ R.layout.weak_biometric_fallback_header, null);
+ ((ListView) v.findViewById(android.R.id.list)).addHeaderView(header, null, false);
+ }
+
+ return v;
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ mWaitingForConfirmation = false;
+ if (requestCode == CONFIRM_EXISTING_REQUEST && resultCode == Activity.RESULT_OK) {
+ mPasswordConfirmed = true;
+ updatePreferencesOrFinish();
+ } else if(requestCode == FALLBACK_REQUEST) {
+ mChooseLockSettingsHelper.utils().deleteTempGallery();
+ getActivity().setResult(resultCode);
+ finish();
+ } else {
+ getActivity().setResult(Activity.RESULT_CANCELED);
+ finish();
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ // Saved so we don't force user to re-enter their password if configuration changes
+ outState.putBoolean(PASSWORD_CONFIRMED, mPasswordConfirmed);
+ outState.putBoolean(WAITING_FOR_CONFIRMATION, mWaitingForConfirmation);
+ outState.putBoolean(FINISH_PENDING, mFinishPending);
+ }
+
+ private void updatePreferencesOrFinish() {
+ Intent intent = getActivity().getIntent();
+ int quality = intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY, -1);
+ if (quality == -1) {
+ // If caller didn't specify password quality, show UI and allow the user to choose.
+ quality = intent.getIntExtra(MINIMUM_QUALITY_KEY, -1);
+ MutableBoolean allowBiometric = new MutableBoolean(false);
+ quality = upgradeQuality(quality, allowBiometric);
+ final PreferenceScreen prefScreen = getPreferenceScreen();
+ if (prefScreen != null) {
+ prefScreen.removeAll();
+ }
+ addPreferencesFromResource(R.xml.security_settings_picker);
+ disableUnusablePreferences(quality, allowBiometric);
+ } else {
+ updateUnlockMethodAndFinish(quality, false);
+ }
+ }
+
+ /** increases the quality if necessary, and returns whether biometric is allowed */
+ private int upgradeQuality(int quality, MutableBoolean allowBiometric) {
+ quality = upgradeQualityForDPM(quality);
+ quality = upgradeQualityForKeyStore(quality);
+ int encryptionQuality = upgradeQualityForEncryption(quality);
+ if (encryptionQuality > quality) {
+ //The first case checks whether biometric is allowed, prior to the user making
+ //their selection from the list
+ if (allowBiometric != null) {
+ allowBiometric.value = quality <=
+ DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK;
+ } else if (quality == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK) {
+ //When the user has selected biometric we shouldn't change that due to
+ //encryption
+ return quality;
+ }
+ }
+ return encryptionQuality;
+ }
+
+ private int upgradeQualityForDPM(int quality) {
+ // Compare min allowed password quality
+ int minQuality = mDPM.getPasswordQuality(null);
+ if (quality < minQuality) {
+ quality = minQuality;
+ }
+ return quality;
+ }
+
+ /**
+ * Mix in "encryption minimums" to any given quality value. This prevents users
+ * from downgrading the pattern/pin/password to a level below the minimums.
+ *
+ * ASSUMPTION: Setting quality is sufficient (e.g. minimum lengths will be set
+ * appropriately.)
+ */
+ private int upgradeQualityForEncryption(int quality) {
+ int encryptionStatus = mDPM.getStorageEncryptionStatus();
+ boolean encrypted = (encryptionStatus == DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE)
+ || (encryptionStatus == DevicePolicyManager.ENCRYPTION_STATUS_ACTIVATING);
+ if (encrypted) {
+ if (quality < CryptKeeperSettings.MIN_PASSWORD_QUALITY) {
+ quality = CryptKeeperSettings.MIN_PASSWORD_QUALITY;
+ }
+ }
+ return quality;
+ }
+
+ private int upgradeQualityForKeyStore(int quality) {
+ if (!mKeyStore.isEmpty()) {
+ if (quality < CredentialStorage.MIN_PASSWORD_QUALITY) {
+ quality = CredentialStorage.MIN_PASSWORD_QUALITY;
+ }
+ }
+ return quality;
+ }
+
+ /***
+ * Disables preferences that are less secure than required quality.
+ *
+ * @param quality the requested quality.
+ */
+ private void disableUnusablePreferences(final int quality, MutableBoolean allowBiometric) {
+ final PreferenceScreen entries = getPreferenceScreen();
+ final boolean onlyShowFallback = getActivity().getIntent()
+ .getBooleanExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, false);
+ final boolean weakBiometricAvailable =
+ mChooseLockSettingsHelper.utils().isBiometricWeakInstalled();
+
+ // if there are multiple users, disable "None" setting
+ UserManager mUm = (UserManager) getSystemService(Context.USER_SERVICE);
+ List users = mUm.getUsers(true);
+ final boolean singleUser = users.size() == 1;
+
+ for (int i = entries.getPreferenceCount() - 1; i >= 0; --i) {
+ Preference pref = entries.getPreference(i);
+ if (pref instanceof PreferenceScreen) {
+ final String key = ((PreferenceScreen) pref).getKey();
+ boolean enabled = true;
+ boolean visible = true;
+ if (KEY_UNLOCK_SET_OFF.equals(key)) {
+ enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+ visible = singleUser; // don't show when there's more than 1 user
+ } else if (KEY_UNLOCK_SET_NONE.equals(key)) {
+ enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+ } else if (KEY_UNLOCK_SET_BIOMETRIC_WEAK.equals(key)) {
+ enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK ||
+ allowBiometric.value;
+ visible = weakBiometricAvailable; // If not available, then don't show it.
+ } else if (KEY_UNLOCK_SET_PATTERN.equals(key)) {
+ enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+ } else if (KEY_UNLOCK_SET_PIN.equals(key)) {
+ enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+ } else if (KEY_UNLOCK_SET_PASSWORD.equals(key)) {
+ enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+ }
+ if (!visible || (onlyShowFallback && !allowedForFallback(key))) {
+ entries.removePreference(pref);
+ } else if (!enabled) {
+ pref.setSummary(R.string.unlock_set_unlock_disabled_summary);
+ pref.setEnabled(false);
+ }
+ }
+ }
+ }
+
+ /**
+ * Check whether the key is allowed for fallback (e.g. bio sensor). Returns true if it's
+ * supported as a backup.
+ *
+ * @param key
+ * @return true if allowed
+ */
+ private boolean allowedForFallback(String key) {
+ return KEY_UNLOCK_BACKUP_INFO.equals(key) ||
+ KEY_UNLOCK_SET_PATTERN.equals(key) || KEY_UNLOCK_SET_PIN.equals(key);
+ }
+
+ private Intent getBiometricSensorIntent() {
+ Intent fallBackIntent = new Intent().setClass(getActivity(), ChooseLockGeneric.class);
+ fallBackIntent.putExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, true);
+ fallBackIntent.putExtra(CONFIRM_CREDENTIALS, false);
+ fallBackIntent.putExtra(EXTRA_SHOW_FRAGMENT_TITLE,
+ R.string.backup_lock_settings_picker_title);
+
+ boolean showTutorial = ALWAY_SHOW_TUTORIAL ||
+ !mChooseLockSettingsHelper.utils().isBiometricWeakEverChosen();
+ Intent intent = new Intent();
+ intent.setClassName("com.android.facelock", "com.android.facelock.SetupIntro");
+ intent.putExtra("showTutorial", showTutorial);
+ PendingIntent pending = PendingIntent.getActivity(getActivity(), 0, fallBackIntent, 0);
+ intent.putExtra("PendingIntent", pending);
+ return intent;
+ }
+
+ /**
+ * Invokes an activity to change the user's pattern, password or PIN based on given quality
+ * and minimum quality specified by DevicePolicyManager. If quality is
+ * {@link DevicePolicyManager#PASSWORD_QUALITY_UNSPECIFIED}, password is cleared.
+ *
+ * @param quality the desired quality. Ignored if DevicePolicyManager requires more security
+ * @param disabled whether or not to show LockScreen at all. Only meaningful when quality is
+ * {@link DevicePolicyManager#PASSWORD_QUALITY_UNSPECIFIED}
+ */
+ void updateUnlockMethodAndFinish(int quality, boolean disabled) {
+ // Sanity check. We should never get here without confirming user's existing password.
+ if (!mPasswordConfirmed) {
+ throw new IllegalStateException("Tried to update password without confirming it");
+ }
+
+ final boolean isFallback = getActivity().getIntent()
+ .getBooleanExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, false);
+
+ quality = upgradeQuality(quality, null);
+
+ if (quality >= DevicePolicyManager.PASSWORD_QUALITY_NUMERIC) {
+ int minLength = mDPM.getPasswordMinimumLength(null);
+ if (minLength < MIN_PASSWORD_LENGTH) {
+ minLength = MIN_PASSWORD_LENGTH;
+ }
+ final int maxLength = mDPM.getPasswordMaximumLength(quality);
+ Intent intent = new Intent().setClass(getActivity(), ChooseLockPassword.class);
+ intent.putExtra(LockPatternUtils.PASSWORD_TYPE_KEY, quality);
+ intent.putExtra(ChooseLockPassword.PASSWORD_MIN_KEY, minLength);
+ intent.putExtra(ChooseLockPassword.PASSWORD_MAX_KEY, maxLength);
+ intent.putExtra(CONFIRM_CREDENTIALS, false);
+ intent.putExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK,
+ isFallback);
+ if (isFallback) {
+ startActivityForResult(intent, FALLBACK_REQUEST);
+ return;
+ } else {
+ mFinishPending = true;
+ intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
+ startActivity(intent);
+ }
+ } else if (quality == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING) {
+ boolean showTutorial = !mChooseLockSettingsHelper.utils().isPatternEverChosen();
+ Intent intent = new Intent();
+ intent.setClass(getActivity(), showTutorial
+ ? ChooseLockPatternTutorial.class
+ : ChooseLockPattern.class);
+ intent.putExtra("key_lock_method", "pattern");
+ intent.putExtra(CONFIRM_CREDENTIALS, false);
+ intent.putExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK,
+ isFallback);
+ if (isFallback) {
+ startActivityForResult(intent, FALLBACK_REQUEST);
+ return;
+ } else {
+ mFinishPending = true;
+ intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
+ startActivity(intent);
+ }
+ } else if (quality == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK) {
+ Intent intent = getBiometricSensorIntent();
+ mFinishPending = true;
+ startActivity(intent);
+ } else if (quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
+ mChooseLockSettingsHelper.utils().clearLock(false);
+ mChooseLockSettingsHelper.utils().setLockScreenDisabled(disabled);
+ getActivity().setResult(Activity.RESULT_OK);
+ finish();
+ } else {
+ finish();
+ }
+ }
+
+ @Override
+ protected int getHelpResource() {
+ return R.string.help_url_choose_lockscreen;
+ }
+
+ }
+}
diff --git a/src/com/android/settings/ChooseLockPassword.java b/src/com/android/settings/ChooseLockPassword.java
new file mode 100644
index 0000000..aab4ba6
--- /dev/null
+++ b/src/com/android/settings/ChooseLockPassword.java
@@ -0,0 +1,488 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.PasswordEntryKeyboardHelper;
+import com.android.internal.widget.PasswordEntryKeyboardView;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.admin.DevicePolicyManager;
+import android.content.Intent;
+import android.inputmethodservice.KeyboardView;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.preference.PreferenceActivity;
+import android.text.Editable;
+import android.text.InputType;
+import android.text.Selection;
+import android.text.Spannable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.inputmethod.EditorInfo;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+public class ChooseLockPassword extends PreferenceActivity {
+ public static final String PASSWORD_MIN_KEY = "lockscreen.password_min";
+ public static final String PASSWORD_MAX_KEY = "lockscreen.password_max";
+ public static final String PASSWORD_MIN_LETTERS_KEY = "lockscreen.password_min_letters";
+ public static final String PASSWORD_MIN_LOWERCASE_KEY = "lockscreen.password_min_lowercase";
+ public static final String PASSWORD_MIN_UPPERCASE_KEY = "lockscreen.password_min_uppercase";
+ public static final String PASSWORD_MIN_NUMERIC_KEY = "lockscreen.password_min_numeric";
+ public static final String PASSWORD_MIN_SYMBOLS_KEY = "lockscreen.password_min_symbols";
+ public static final String PASSWORD_MIN_NONLETTER_KEY = "lockscreen.password_min_nonletter";
+
+ @Override
+ public Intent getIntent() {
+ Intent modIntent = new Intent(super.getIntent());
+ modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ChooseLockPasswordFragment.class.getName());
+ modIntent.putExtra(EXTRA_NO_HEADERS, true);
+ return modIntent;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ // TODO: Fix on phones
+ // Disable IME on our window since we provide our own keyboard
+ //getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
+ //WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+ super.onCreate(savedInstanceState);
+ CharSequence msg = getText(R.string.lockpassword_choose_your_password_header);
+ showBreadCrumbs(msg, msg);
+ }
+
+ public static class ChooseLockPasswordFragment extends Fragment
+ implements OnClickListener, OnEditorActionListener, TextWatcher {
+ private static final String KEY_FIRST_PIN = "first_pin";
+ private static final String KEY_UI_STAGE = "ui_stage";
+ private TextView mPasswordEntry;
+ private int mPasswordMinLength = 4;
+ private int mPasswordMaxLength = 16;
+ private int mPasswordMinLetters = 0;
+ private int mPasswordMinUpperCase = 0;
+ private int mPasswordMinLowerCase = 0;
+ private int mPasswordMinSymbols = 0;
+ private int mPasswordMinNumeric = 0;
+ private int mPasswordMinNonLetter = 0;
+ private LockPatternUtils mLockPatternUtils;
+ private int mRequestedQuality = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+ private ChooseLockSettingsHelper mChooseLockSettingsHelper;
+ private Stage mUiStage = Stage.Introduction;
+ private TextView mHeaderText;
+ private String mFirstPin;
+ private KeyboardView mKeyboardView;
+ private PasswordEntryKeyboardHelper mKeyboardHelper;
+ private boolean mIsAlphaMode;
+ private Button mCancelButton;
+ private Button mNextButton;
+ private static final int CONFIRM_EXISTING_REQUEST = 58;
+ static final int RESULT_FINISHED = RESULT_FIRST_USER;
+ private static final long ERROR_MESSAGE_TIMEOUT = 3000;
+ private static final int MSG_SHOW_ERROR = 1;
+
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_SHOW_ERROR) {
+ updateStage((Stage) msg.obj);
+ }
+ }
+ };
+
+ /**
+ * Keep track internally of where the user is in choosing a pattern.
+ */
+ protected enum Stage {
+
+ Introduction(R.string.lockpassword_choose_your_password_header,
+ R.string.lockpassword_choose_your_pin_header,
+ R.string.lockpassword_continue_label),
+
+ NeedToConfirm(R.string.lockpassword_confirm_your_password_header,
+ R.string.lockpassword_confirm_your_pin_header,
+ R.string.lockpassword_ok_label),
+
+ ConfirmWrong(R.string.lockpassword_confirm_passwords_dont_match,
+ R.string.lockpassword_confirm_pins_dont_match,
+ R.string.lockpassword_continue_label);
+
+ /**
+ * @param headerMessage The message displayed at the top.
+ */
+ Stage(int hintInAlpha, int hintInNumeric, int nextButtonText) {
+ this.alphaHint = hintInAlpha;
+ this.numericHint = hintInNumeric;
+ this.buttonText = nextButtonText;
+ }
+
+ public final int alphaHint;
+ public final int numericHint;
+ public final int buttonText;
+ }
+
+ // required constructor for fragments
+ public ChooseLockPasswordFragment() {
+
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mLockPatternUtils = new LockPatternUtils(getActivity());
+ Intent intent = getActivity().getIntent();
+ mRequestedQuality = Math.max(intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY,
+ mRequestedQuality), mLockPatternUtils.getRequestedPasswordQuality());
+ mPasswordMinLength = Math.max(
+ intent.getIntExtra(PASSWORD_MIN_KEY, mPasswordMinLength), mLockPatternUtils
+ .getRequestedMinimumPasswordLength());
+ mPasswordMaxLength = intent.getIntExtra(PASSWORD_MAX_KEY, mPasswordMaxLength);
+ mPasswordMinLetters = Math.max(intent.getIntExtra(PASSWORD_MIN_LETTERS_KEY,
+ mPasswordMinLetters), mLockPatternUtils.getRequestedPasswordMinimumLetters());
+ mPasswordMinUpperCase = Math.max(intent.getIntExtra(PASSWORD_MIN_UPPERCASE_KEY,
+ mPasswordMinUpperCase), mLockPatternUtils.getRequestedPasswordMinimumUpperCase());
+ mPasswordMinLowerCase = Math.max(intent.getIntExtra(PASSWORD_MIN_LOWERCASE_KEY,
+ mPasswordMinLowerCase), mLockPatternUtils.getRequestedPasswordMinimumLowerCase());
+ mPasswordMinNumeric = Math.max(intent.getIntExtra(PASSWORD_MIN_NUMERIC_KEY,
+ mPasswordMinNumeric), mLockPatternUtils.getRequestedPasswordMinimumNumeric());
+ mPasswordMinSymbols = Math.max(intent.getIntExtra(PASSWORD_MIN_SYMBOLS_KEY,
+ mPasswordMinSymbols), mLockPatternUtils.getRequestedPasswordMinimumSymbols());
+ mPasswordMinNonLetter = Math.max(intent.getIntExtra(PASSWORD_MIN_NONLETTER_KEY,
+ mPasswordMinNonLetter), mLockPatternUtils.getRequestedPasswordMinimumNonLetter());
+
+ mChooseLockSettingsHelper = new ChooseLockSettingsHelper(getActivity());
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+
+ View view = inflater.inflate(R.layout.choose_lock_password, null);
+
+ mCancelButton = (Button) view.findViewById(R.id.cancel_button);
+ mCancelButton.setOnClickListener(this);
+ mNextButton = (Button) view.findViewById(R.id.next_button);
+ mNextButton.setOnClickListener(this);
+
+ mIsAlphaMode = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == mRequestedQuality
+ || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == mRequestedQuality
+ || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == mRequestedQuality;
+ mKeyboardView = (PasswordEntryKeyboardView) view.findViewById(R.id.keyboard);
+ mPasswordEntry = (TextView) view.findViewById(R.id.password_entry);
+ mPasswordEntry.setOnEditorActionListener(this);
+ mPasswordEntry.addTextChangedListener(this);
+
+ final Activity activity = getActivity();
+ mKeyboardHelper = new PasswordEntryKeyboardHelper(activity,
+ mKeyboardView, mPasswordEntry);
+ mKeyboardHelper.setKeyboardMode(mIsAlphaMode ?
+ PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA
+ : PasswordEntryKeyboardHelper.KEYBOARD_MODE_NUMERIC);
+
+ mHeaderText = (TextView) view.findViewById(R.id.headerText);
+ mKeyboardView.requestFocus();
+
+ int currentType = mPasswordEntry.getInputType();
+ mPasswordEntry.setInputType(mIsAlphaMode ? currentType
+ : (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD));
+
+ Intent intent = getActivity().getIntent();
+ final boolean confirmCredentials = intent.getBooleanExtra("confirm_credentials", true);
+ if (savedInstanceState == null) {
+ updateStage(Stage.Introduction);
+ if (confirmCredentials) {
+ mChooseLockSettingsHelper.launchConfirmationActivity(CONFIRM_EXISTING_REQUEST,
+ null, null);
+ }
+ } else {
+ mFirstPin = savedInstanceState.getString(KEY_FIRST_PIN);
+ final String state = savedInstanceState.getString(KEY_UI_STAGE);
+ if (state != null) {
+ mUiStage = Stage.valueOf(state);
+ updateStage(mUiStage);
+ }
+ }
+ // Update the breadcrumb (title) if this is embedded in a PreferenceActivity
+ if (activity instanceof PreferenceActivity) {
+ final PreferenceActivity preferenceActivity = (PreferenceActivity) activity;
+ int id = mIsAlphaMode ? R.string.lockpassword_choose_your_password_header
+ : R.string.lockpassword_choose_your_pin_header;
+ CharSequence title = getText(id);
+ preferenceActivity.showBreadCrumbs(title, title);
+ }
+
+ return view;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ updateStage(mUiStage);
+ mKeyboardView.requestFocus();
+ }
+
+ @Override
+ public void onPause() {
+ mHandler.removeMessages(MSG_SHOW_ERROR);
+
+ super.onPause();
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putString(KEY_UI_STAGE, mUiStage.name());
+ outState.putString(KEY_FIRST_PIN, mFirstPin);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode,
+ Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ switch (requestCode) {
+ case CONFIRM_EXISTING_REQUEST:
+ if (resultCode != Activity.RESULT_OK) {
+ getActivity().setResult(RESULT_FINISHED);
+ getActivity().finish();
+ }
+ break;
+ }
+ }
+
+ protected void updateStage(Stage stage) {
+ final Stage previousStage = mUiStage;
+ mUiStage = stage;
+ updateUi();
+
+ // If the stage changed, announce the header for accessibility. This
+ // is a no-op when accessibility is disabled.
+ if (previousStage != stage) {
+ mHeaderText.announceForAccessibility(mHeaderText.getText());
+ }
+ }
+
+ /**
+ * Validates PIN and returns a message to display if PIN fails test.
+ * @param password the raw password the user typed in
+ * @return error message to show to user or null if password is OK
+ */
+ private String validatePassword(String password) {
+ if (password.length() < mPasswordMinLength) {
+ return getString(mIsAlphaMode ?
+ R.string.lockpassword_password_too_short
+ : R.string.lockpassword_pin_too_short, mPasswordMinLength);
+ }
+ if (password.length() > mPasswordMaxLength) {
+ return getString(mIsAlphaMode ?
+ R.string.lockpassword_password_too_long
+ : R.string.lockpassword_pin_too_long, mPasswordMaxLength + 1);
+ }
+ int letters = 0;
+ int numbers = 0;
+ int lowercase = 0;
+ int symbols = 0;
+ int uppercase = 0;
+ int nonletter = 0;
+ for (int i = 0; i < password.length(); i++) {
+ char c = password.charAt(i);
+ // allow non control Latin-1 characters only
+ if (c < 32 || c > 127) {
+ return getString(R.string.lockpassword_illegal_character);
+ }
+ if (c >= '0' && c <= '9') {
+ numbers++;
+ nonletter++;
+ } else if (c >= 'A' && c <= 'Z') {
+ letters++;
+ uppercase++;
+ } else if (c >= 'a' && c <= 'z') {
+ letters++;
+ lowercase++;
+ } else {
+ symbols++;
+ nonletter++;
+ }
+ }
+ if (DevicePolicyManager.PASSWORD_QUALITY_NUMERIC == mRequestedQuality
+ && (letters > 0 || symbols > 0)) {
+ // This shouldn't be possible unless user finds some way to bring up
+ // soft keyboard
+ return getString(R.string.lockpassword_pin_contains_non_digits);
+ } else if (DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == mRequestedQuality) {
+ if (letters < mPasswordMinLetters) {
+ return String.format(getResources().getQuantityString(
+ R.plurals.lockpassword_password_requires_letters, mPasswordMinLetters),
+ mPasswordMinLetters);
+ } else if (numbers < mPasswordMinNumeric) {
+ return String.format(getResources().getQuantityString(
+ R.plurals.lockpassword_password_requires_numeric, mPasswordMinNumeric),
+ mPasswordMinNumeric);
+ } else if (lowercase < mPasswordMinLowerCase) {
+ return String.format(getResources().getQuantityString(
+ R.plurals.lockpassword_password_requires_lowercase, mPasswordMinLowerCase),
+ mPasswordMinLowerCase);
+ } else if (uppercase < mPasswordMinUpperCase) {
+ return String.format(getResources().getQuantityString(
+ R.plurals.lockpassword_password_requires_uppercase, mPasswordMinUpperCase),
+ mPasswordMinUpperCase);
+ } else if (symbols < mPasswordMinSymbols) {
+ return String.format(getResources().getQuantityString(
+ R.plurals.lockpassword_password_requires_symbols, mPasswordMinSymbols),
+ mPasswordMinSymbols);
+ } else if (nonletter < mPasswordMinNonLetter) {
+ return String.format(getResources().getQuantityString(
+ R.plurals.lockpassword_password_requires_nonletter, mPasswordMinNonLetter),
+ mPasswordMinNonLetter);
+ }
+ } else {
+ final boolean alphabetic = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC
+ == mRequestedQuality;
+ final boolean alphanumeric = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
+ == mRequestedQuality;
+ if ((alphabetic || alphanumeric) && letters == 0) {
+ return getString(R.string.lockpassword_password_requires_alpha);
+ }
+ if (alphanumeric && numbers == 0) {
+ return getString(R.string.lockpassword_password_requires_digit);
+ }
+ }
+ if(mLockPatternUtils.checkPasswordHistory(password)) {
+ return getString(mIsAlphaMode ? R.string.lockpassword_password_recently_used
+ : R.string.lockpassword_pin_recently_used);
+ }
+ return null;
+ }
+
+ private void handleNext() {
+ final String pin = mPasswordEntry.getText().toString();
+ if (TextUtils.isEmpty(pin)) {
+ return;
+ }
+ String errorMsg = null;
+ if (mUiStage == Stage.Introduction) {
+ errorMsg = validatePassword(pin);
+ if (errorMsg == null) {
+ mFirstPin = pin;
+ mPasswordEntry.setText("");
+ updateStage(Stage.NeedToConfirm);
+ }
+ } else if (mUiStage == Stage.NeedToConfirm) {
+ if (mFirstPin.equals(pin)) {
+ final boolean isFallback = getActivity().getIntent().getBooleanExtra(
+ LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, false);
+ mLockPatternUtils.clearLock(isFallback);
+ mLockPatternUtils.saveLockPassword(pin, mRequestedQuality, isFallback);
+ getActivity().finish();
+ } else {
+ CharSequence tmp = mPasswordEntry.getText();
+ if (tmp != null) {
+ Selection.setSelection((Spannable) tmp, 0, tmp.length());
+ }
+ updateStage(Stage.ConfirmWrong);
+ }
+ }
+ if (errorMsg != null) {
+ showError(errorMsg, mUiStage);
+ }
+ }
+
+ public void onClick(View v) {
+ switch (v.getId()) {
+ case R.id.next_button:
+ handleNext();
+ break;
+
+ case R.id.cancel_button:
+ getActivity().finish();
+ break;
+ }
+ }
+
+ private void showError(String msg, final Stage next) {
+ mHeaderText.setText(msg);
+ mHeaderText.announceForAccessibility(mHeaderText.getText());
+ Message mesg = mHandler.obtainMessage(MSG_SHOW_ERROR, next);
+ mHandler.removeMessages(MSG_SHOW_ERROR);
+ mHandler.sendMessageDelayed(mesg, ERROR_MESSAGE_TIMEOUT);
+ }
+
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ // Check if this was the result of hitting the enter or "done" key
+ if (actionId == EditorInfo.IME_NULL
+ || actionId == EditorInfo.IME_ACTION_DONE
+ || actionId == EditorInfo.IME_ACTION_NEXT) {
+ handleNext();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Update the hint based on current Stage and length of password entry
+ */
+ private void updateUi() {
+ String password = mPasswordEntry.getText().toString();
+ final int length = password.length();
+ if (mUiStage == Stage.Introduction && length > 0) {
+ if (length < mPasswordMinLength) {
+ String msg = getString(mIsAlphaMode ? R.string.lockpassword_password_too_short
+ : R.string.lockpassword_pin_too_short, mPasswordMinLength);
+ mHeaderText.setText(msg);
+ mNextButton.setEnabled(false);
+ } else {
+ String error = validatePassword(password);
+ if (error != null) {
+ mHeaderText.setText(error);
+ mNextButton.setEnabled(false);
+ } else {
+ mHeaderText.setText(R.string.lockpassword_press_continue);
+ mNextButton.setEnabled(true);
+ }
+ }
+ } else {
+ mHeaderText.setText(mIsAlphaMode ? mUiStage.alphaHint : mUiStage.numericHint);
+ mNextButton.setEnabled(length > 0);
+ }
+ mNextButton.setText(mUiStage.buttonText);
+ }
+
+ public void afterTextChanged(Editable s) {
+ // Changing the text while error displayed resets to NeedToConfirm state
+ if (mUiStage == Stage.ConfirmWrong) {
+ mUiStage = Stage.NeedToConfirm;
+ }
+ updateUi();
+ }
+
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+
+ }
+
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+
+ }
+ }
+}
diff --git a/src/com/android/settings/ChooseLockPattern.java b/src/com/android/settings/ChooseLockPattern.java
new file mode 100644
index 0000000..f9c01b6
--- /dev/null
+++ b/src/com/android/settings/ChooseLockPattern.java
@@ -0,0 +1,530 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import com.google.android.collect.Lists;
+
+import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternView;
+import com.android.internal.widget.LockPatternView.Cell;
+
+import static com.android.internal.widget.LockPatternView.DisplayMode;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.content.Intent;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * If the user has a lock pattern set already, makes them confirm the existing one.
+ *
+ * Then, prompts the user to choose a lock pattern:
+ * - prompts for initial pattern
+ * - asks for confirmation / restart
+ * - saves chosen password when confirmed
+ */
+public class ChooseLockPattern extends PreferenceActivity {
+ /**
+ * Used by the choose lock pattern wizard to indicate the wizard is
+ * finished, and each activity in the wizard should finish.
+ *
+ * Previously, each activity in the wizard would finish itself after
+ * starting the next activity. However, this leads to broken 'Back'
+ * behavior. So, now an activity does not finish itself until it gets this
+ * result.
+ */
+ static final int RESULT_FINISHED = RESULT_FIRST_USER;
+
+ @Override
+ public Intent getIntent() {
+ Intent modIntent = new Intent(super.getIntent());
+ modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ChooseLockPatternFragment.class.getName());
+ modIntent.putExtra(EXTRA_NO_HEADERS, true);
+ return modIntent;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ // requestWindowFeature(Window.FEATURE_NO_TITLE);
+ super.onCreate(savedInstanceState);
+ CharSequence msg = getText(R.string.lockpassword_choose_your_pattern_header);
+ showBreadCrumbs(msg, msg);
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ // *** TODO ***
+ // chooseLockPatternFragment.onKeyDown(keyCode, event);
+ return super.onKeyDown(keyCode, event);
+ }
+
+ public static class ChooseLockPatternFragment extends Fragment
+ implements View.OnClickListener {
+
+ public static final int CONFIRM_EXISTING_REQUEST = 55;
+
+ // how long after a confirmation message is shown before moving on
+ static final int INFORMATION_MSG_TIMEOUT_MS = 3000;
+
+ // how long we wait to clear a wrong pattern
+ private static final int WRONG_PATTERN_CLEAR_TIMEOUT_MS = 2000;
+
+ private static final int ID_EMPTY_MESSAGE = -1;
+
+ protected TextView mHeaderText;
+ protected LockPatternView mLockPatternView;
+ protected TextView mFooterText;
+ private TextView mFooterLeftButton;
+ private TextView mFooterRightButton;
+ protected List mChosenPattern = null;
+
+ /**
+ * The patten used during the help screen to show how to draw a pattern.
+ */
+ private final List mAnimatePattern =
+ Collections.unmodifiableList(Lists.newArrayList(
+ LockPatternView.Cell.of(0, 0),
+ LockPatternView.Cell.of(0, 1),
+ LockPatternView.Cell.of(1, 1),
+ LockPatternView.Cell.of(2, 1)
+ ));
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode,
+ Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ switch (requestCode) {
+ case CONFIRM_EXISTING_REQUEST:
+ if (resultCode != Activity.RESULT_OK) {
+ getActivity().setResult(RESULT_FINISHED);
+ getActivity().finish();
+ }
+ updateStage(Stage.Introduction);
+ break;
+ }
+ }
+
+ /**
+ * The pattern listener that responds according to a user choosing a new
+ * lock pattern.
+ */
+ protected LockPatternView.OnPatternListener mChooseNewLockPatternListener =
+ new LockPatternView.OnPatternListener() {
+
+ public void onPatternStart() {
+ mLockPatternView.removeCallbacks(mClearPatternRunnable);
+ patternInProgress();
+ }
+
+ public void onPatternCleared() {
+ mLockPatternView.removeCallbacks(mClearPatternRunnable);
+ }
+
+ public void onPatternDetected(List pattern) {
+ if (mUiStage == Stage.NeedToConfirm || mUiStage == Stage.ConfirmWrong) {
+ if (mChosenPattern == null) throw new IllegalStateException(
+ "null chosen pattern in stage 'need to confirm");
+ if (mChosenPattern.equals(pattern)) {
+ updateStage(Stage.ChoiceConfirmed);
+ } else {
+ updateStage(Stage.ConfirmWrong);
+ }
+ } else if (mUiStage == Stage.Introduction || mUiStage == Stage.ChoiceTooShort){
+ if (pattern.size() < LockPatternUtils.MIN_LOCK_PATTERN_SIZE) {
+ updateStage(Stage.ChoiceTooShort);
+ } else {
+ mChosenPattern = new ArrayList(pattern);
+ updateStage(Stage.FirstChoiceValid);
+ }
+ } else {
+ throw new IllegalStateException("Unexpected stage " + mUiStage + " when "
+ + "entering the pattern.");
+ }
+ }
+
+ public void onPatternCellAdded(List pattern) {
+
+ }
+
+ private void patternInProgress() {
+ mHeaderText.setText(R.string.lockpattern_recording_inprogress);
+ mFooterText.setText("");
+ mFooterLeftButton.setEnabled(false);
+ mFooterRightButton.setEnabled(false);
+ }
+ };
+
+
+ /**
+ * The states of the left footer button.
+ */
+ enum LeftButtonMode {
+ Cancel(R.string.cancel, true),
+ CancelDisabled(R.string.cancel, false),
+ Retry(R.string.lockpattern_retry_button_text, true),
+ RetryDisabled(R.string.lockpattern_retry_button_text, false),
+ Gone(ID_EMPTY_MESSAGE, false);
+
+
+ /**
+ * @param text The displayed text for this mode.
+ * @param enabled Whether the button should be enabled.
+ */
+ LeftButtonMode(int text, boolean enabled) {
+ this.text = text;
+ this.enabled = enabled;
+ }
+
+ final int text;
+ final boolean enabled;
+ }
+
+ /**
+ * The states of the right button.
+ */
+ enum RightButtonMode {
+ Continue(R.string.lockpattern_continue_button_text, true),
+ ContinueDisabled(R.string.lockpattern_continue_button_text, false),
+ Confirm(R.string.lockpattern_confirm_button_text, true),
+ ConfirmDisabled(R.string.lockpattern_confirm_button_text, false),
+ Ok(android.R.string.ok, true);
+
+ /**
+ * @param text The displayed text for this mode.
+ * @param enabled Whether the button should be enabled.
+ */
+ RightButtonMode(int text, boolean enabled) {
+ this.text = text;
+ this.enabled = enabled;
+ }
+
+ final int text;
+ final boolean enabled;
+ }
+
+ /**
+ * Keep track internally of where the user is in choosing a pattern.
+ */
+ protected enum Stage {
+
+ Introduction(
+ R.string.lockpattern_recording_intro_header,
+ LeftButtonMode.Cancel, RightButtonMode.ContinueDisabled,
+ ID_EMPTY_MESSAGE, true),
+ HelpScreen(
+ R.string.lockpattern_settings_help_how_to_record,
+ LeftButtonMode.Gone, RightButtonMode.Ok, ID_EMPTY_MESSAGE, false),
+ ChoiceTooShort(
+ R.string.lockpattern_recording_incorrect_too_short,
+ LeftButtonMode.Retry, RightButtonMode.ContinueDisabled,
+ ID_EMPTY_MESSAGE, true),
+ FirstChoiceValid(
+ R.string.lockpattern_pattern_entered_header,
+ LeftButtonMode.Retry, RightButtonMode.Continue, ID_EMPTY_MESSAGE, false),
+ NeedToConfirm(
+ R.string.lockpattern_need_to_confirm,
+ LeftButtonMode.Cancel, RightButtonMode.ConfirmDisabled,
+ ID_EMPTY_MESSAGE, true),
+ ConfirmWrong(
+ R.string.lockpattern_need_to_unlock_wrong,
+ LeftButtonMode.Cancel, RightButtonMode.ConfirmDisabled,
+ ID_EMPTY_MESSAGE, true),
+ ChoiceConfirmed(
+ R.string.lockpattern_pattern_confirmed_header,
+ LeftButtonMode.Cancel, RightButtonMode.Confirm, ID_EMPTY_MESSAGE, false);
+
+
+ /**
+ * @param headerMessage The message displayed at the top.
+ * @param leftMode The mode of the left button.
+ * @param rightMode The mode of the right button.
+ * @param footerMessage The footer message.
+ * @param patternEnabled Whether the pattern widget is enabled.
+ */
+ Stage(int headerMessage,
+ LeftButtonMode leftMode,
+ RightButtonMode rightMode,
+ int footerMessage, boolean patternEnabled) {
+ this.headerMessage = headerMessage;
+ this.leftMode = leftMode;
+ this.rightMode = rightMode;
+ this.footerMessage = footerMessage;
+ this.patternEnabled = patternEnabled;
+ }
+
+ final int headerMessage;
+ final LeftButtonMode leftMode;
+ final RightButtonMode rightMode;
+ final int footerMessage;
+ final boolean patternEnabled;
+ }
+
+ private Stage mUiStage = Stage.Introduction;
+
+ private Runnable mClearPatternRunnable = new Runnable() {
+ public void run() {
+ mLockPatternView.clearPattern();
+ }
+ };
+
+ private ChooseLockSettingsHelper mChooseLockSettingsHelper;
+
+ private static final String KEY_UI_STAGE = "uiStage";
+ private static final String KEY_PATTERN_CHOICE = "chosenPattern";
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mChooseLockSettingsHelper = new ChooseLockSettingsHelper(getActivity());
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+
+ // setupViews()
+ View view = inflater.inflate(R.layout.choose_lock_pattern, null);
+ mHeaderText = (TextView) view.findViewById(R.id.headerText);
+ mLockPatternView = (LockPatternView) view.findViewById(R.id.lockPattern);
+ mLockPatternView.setOnPatternListener(mChooseNewLockPatternListener);
+ mLockPatternView.setTactileFeedbackEnabled(
+ mChooseLockSettingsHelper.utils().isTactileFeedbackEnabled());
+
+ mFooterText = (TextView) view.findViewById(R.id.footerText);
+
+ mFooterLeftButton = (TextView) view.findViewById(R.id.footerLeftButton);
+ mFooterRightButton = (TextView) view.findViewById(R.id.footerRightButton);
+
+ mFooterLeftButton.setOnClickListener(this);
+ mFooterRightButton.setOnClickListener(this);
+
+ // make it so unhandled touch events within the unlock screen go to the
+ // lock pattern view.
+ final LinearLayoutWithDefaultTouchRecepient topLayout
+ = (LinearLayoutWithDefaultTouchRecepient) view.findViewById(
+ R.id.topLayout);
+ topLayout.setDefaultTouchRecepient(mLockPatternView);
+
+ final boolean confirmCredentials = getActivity().getIntent()
+ .getBooleanExtra("confirm_credentials", false);
+
+ if (savedInstanceState == null) {
+ if (confirmCredentials) {
+ // first launch. As a security measure, we're in NeedToConfirm mode until we
+ // know there isn't an existing password or the user confirms their password.
+ updateStage(Stage.NeedToConfirm);
+ boolean launchedConfirmationActivity =
+ mChooseLockSettingsHelper.launchConfirmationActivity(
+ CONFIRM_EXISTING_REQUEST, null, null);
+ if (!launchedConfirmationActivity) {
+ updateStage(Stage.Introduction);
+ }
+ } else {
+ updateStage(Stage.Introduction);
+ }
+ } else {
+ // restore from previous state
+ final String patternString = savedInstanceState.getString(KEY_PATTERN_CHOICE);
+ if (patternString != null) {
+ mChosenPattern = LockPatternUtils.stringToPattern(patternString);
+ }
+ updateStage(Stage.values()[savedInstanceState.getInt(KEY_UI_STAGE)]);
+ }
+ return view;
+ }
+
+ public void onClick(View v) {
+ if (v == mFooterLeftButton) {
+ if (mUiStage.leftMode == LeftButtonMode.Retry) {
+ mChosenPattern = null;
+ mLockPatternView.clearPattern();
+ updateStage(Stage.Introduction);
+ } else if (mUiStage.leftMode == LeftButtonMode.Cancel) {
+ // They are canceling the entire wizard
+ getActivity().setResult(RESULT_FINISHED);
+ getActivity().finish();
+ } else {
+ throw new IllegalStateException("left footer button pressed, but stage of " +
+ mUiStage + " doesn't make sense");
+ }
+ } else if (v == mFooterRightButton) {
+
+ if (mUiStage.rightMode == RightButtonMode.Continue) {
+ if (mUiStage != Stage.FirstChoiceValid) {
+ throw new IllegalStateException("expected ui stage " + Stage.FirstChoiceValid
+ + " when button is " + RightButtonMode.Continue);
+ }
+ updateStage(Stage.NeedToConfirm);
+ } else if (mUiStage.rightMode == RightButtonMode.Confirm) {
+ if (mUiStage != Stage.ChoiceConfirmed) {
+ throw new IllegalStateException("expected ui stage " + Stage.ChoiceConfirmed
+ + " when button is " + RightButtonMode.Confirm);
+ }
+ saveChosenPatternAndFinish();
+ } else if (mUiStage.rightMode == RightButtonMode.Ok) {
+ if (mUiStage != Stage.HelpScreen) {
+ throw new IllegalStateException("Help screen is only mode with ok button, but " +
+ "stage is " + mUiStage);
+ }
+ mLockPatternView.clearPattern();
+ mLockPatternView.setDisplayMode(DisplayMode.Correct);
+ updateStage(Stage.Introduction);
+ }
+ }
+ }
+
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
+ if (mUiStage == Stage.HelpScreen) {
+ updateStage(Stage.Introduction);
+ return true;
+ }
+ }
+ if (keyCode == KeyEvent.KEYCODE_MENU && mUiStage == Stage.Introduction) {
+ updateStage(Stage.HelpScreen);
+ return true;
+ }
+ return false;
+ }
+
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ outState.putInt(KEY_UI_STAGE, mUiStage.ordinal());
+ if (mChosenPattern != null) {
+ outState.putString(KEY_PATTERN_CHOICE,
+ LockPatternUtils.patternToString(mChosenPattern));
+ }
+ }
+
+ /**
+ * Updates the messages and buttons appropriate to what stage the user
+ * is at in choosing a view. This doesn't handle clearing out the pattern;
+ * the pattern is expected to be in the right state.
+ * @param stage
+ */
+ protected void updateStage(Stage stage) {
+ final Stage previousStage = mUiStage;
+
+ mUiStage = stage;
+
+ // header text, footer text, visibility and
+ // enabled state all known from the stage
+ if (stage == Stage.ChoiceTooShort) {
+ mHeaderText.setText(
+ getResources().getString(
+ stage.headerMessage,
+ LockPatternUtils.MIN_LOCK_PATTERN_SIZE));
+ } else {
+ mHeaderText.setText(stage.headerMessage);
+ }
+ if (stage.footerMessage == ID_EMPTY_MESSAGE) {
+ mFooterText.setText("");
+ } else {
+ mFooterText.setText(stage.footerMessage);
+ }
+
+ if (stage.leftMode == LeftButtonMode.Gone) {
+ mFooterLeftButton.setVisibility(View.GONE);
+ } else {
+ mFooterLeftButton.setVisibility(View.VISIBLE);
+ mFooterLeftButton.setText(stage.leftMode.text);
+ mFooterLeftButton.setEnabled(stage.leftMode.enabled);
+ }
+
+ mFooterRightButton.setText(stage.rightMode.text);
+ mFooterRightButton.setEnabled(stage.rightMode.enabled);
+
+ // same for whether the patten is enabled
+ if (stage.patternEnabled) {
+ mLockPatternView.enableInput();
+ } else {
+ mLockPatternView.disableInput();
+ }
+
+ // the rest of the stuff varies enough that it is easier just to handle
+ // on a case by case basis.
+ mLockPatternView.setDisplayMode(DisplayMode.Correct);
+
+ switch (mUiStage) {
+ case Introduction:
+ mLockPatternView.clearPattern();
+ break;
+ case HelpScreen:
+ mLockPatternView.setPattern(DisplayMode.Animate, mAnimatePattern);
+ break;
+ case ChoiceTooShort:
+ mLockPatternView.setDisplayMode(DisplayMode.Wrong);
+ postClearPatternRunnable();
+ break;
+ case FirstChoiceValid:
+ break;
+ case NeedToConfirm:
+ mLockPatternView.clearPattern();
+ break;
+ case ConfirmWrong:
+ mLockPatternView.setDisplayMode(DisplayMode.Wrong);
+ postClearPatternRunnable();
+ break;
+ case ChoiceConfirmed:
+ break;
+ }
+
+ // If the stage changed, announce the header for accessibility. This
+ // is a no-op when accessibility is disabled.
+ if (previousStage != stage) {
+ mHeaderText.announceForAccessibility(mHeaderText.getText());
+ }
+ }
+
+
+ // clear the wrong pattern unless they have started a new one
+ // already
+ private void postClearPatternRunnable() {
+ mLockPatternView.removeCallbacks(mClearPatternRunnable);
+ mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS);
+ }
+
+ private void saveChosenPatternAndFinish() {
+ LockPatternUtils utils = mChooseLockSettingsHelper.utils();
+ final boolean lockVirgin = !utils.isPatternEverChosen();
+
+ final boolean isFallback = getActivity().getIntent()
+ .getBooleanExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, false);
+ utils.saveLockPattern(mChosenPattern, isFallback);
+ utils.setLockPatternEnabled(true);
+
+ if (lockVirgin) {
+ utils.setVisiblePatternEnabled(true);
+ }
+
+ getActivity().setResult(RESULT_FINISHED);
+ getActivity().finish();
+ }
+ }
+}
diff --git a/src/com/android/settings/ChooseLockPatternTutorial.java b/src/com/android/settings/ChooseLockPatternTutorial.java
new file mode 100644
index 0000000..5dbd616
--- /dev/null
+++ b/src/com/android/settings/ChooseLockPatternTutorial.java
@@ -0,0 +1,118 @@
+/*
+ * 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.android.settings;
+
+import java.util.ArrayList;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternView;
+
+import android.app.Fragment;
+import android.content.Intent;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class ChooseLockPatternTutorial extends PreferenceActivity {
+
+ // required constructor for fragments
+ public ChooseLockPatternTutorial() {
+
+ }
+
+ @Override
+ public Intent getIntent() {
+ Intent modIntent = new Intent(super.getIntent());
+ modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ChooseLockPatternTutorialFragment.class.getName());
+ modIntent.putExtra(EXTRA_NO_HEADERS, true);
+ return modIntent;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ CharSequence msg = getText(R.string.lockpassword_choose_your_pattern_header);
+ showBreadCrumbs(msg, msg);
+ }
+
+ public static class ChooseLockPatternTutorialFragment extends Fragment
+ implements View.OnClickListener {
+ private View mNextButton;
+ private View mSkipButton;
+ private LockPatternView mPatternView;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ // Don't show the tutorial if the user has seen it before.
+ LockPatternUtils lockPatternUtils = new LockPatternUtils(getActivity());
+ if (savedInstanceState == null && lockPatternUtils.isPatternEverChosen()) {
+ Intent intent = new Intent(getActivity(), ChooseLockPattern.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
+ intent.putExtra("confirm_credentials", false);
+ final boolean isFallback = getActivity().getIntent()
+ .getBooleanExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, false);
+ intent.putExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK,
+ isFallback);
+ startActivity(intent);
+ getActivity().finish();
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.choose_lock_pattern_tutorial, null);
+ mNextButton = view.findViewById(R.id.next_button);
+ mNextButton.setOnClickListener(this);
+ mSkipButton = view.findViewById(R.id.skip_button);
+ mSkipButton.setOnClickListener(this);
+
+ // Set up LockPatternView to be a non-interactive demo animation
+ mPatternView = (LockPatternView) view.findViewById(R.id.lockPattern);
+ ArrayList demoPattern = new ArrayList();
+ demoPattern.add(LockPatternView.Cell.of(0,0));
+ demoPattern.add(LockPatternView.Cell.of(0,1));
+ demoPattern.add(LockPatternView.Cell.of(1,1));
+ demoPattern.add(LockPatternView.Cell.of(2,1));
+ mPatternView.setPattern(LockPatternView.DisplayMode.Animate, demoPattern);
+ mPatternView.disableInput();
+
+ return view;
+ }
+
+ public void onClick(View v) {
+ if (v == mSkipButton) {
+ // Canceling, so finish all
+ getActivity().setResult(ChooseLockPattern.RESULT_FINISHED);
+ getActivity().finish();
+ } else if (v == mNextButton) {
+ final boolean isFallback = getActivity().getIntent()
+ .getBooleanExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, false);
+ Intent intent = new Intent(getActivity(), ChooseLockPattern.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
+ intent.putExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK,
+ isFallback);
+ startActivity(intent);
+ getActivity().overridePendingTransition(0, 0); // no animation
+ getActivity().finish();
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/ChooseLockSettingsHelper.java b/src/com/android/settings/ChooseLockSettingsHelper.java
new file mode 100644
index 0000000..a069712
--- /dev/null
+++ b/src/com/android/settings/ChooseLockSettingsHelper.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import com.android.internal.widget.LockPatternUtils;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.admin.DevicePolicyManager;
+import android.content.Intent;
+
+public final class ChooseLockSettingsHelper {
+
+ static final String EXTRA_KEY_PASSWORD = "password";
+
+ private LockPatternUtils mLockPatternUtils;
+ private Activity mActivity;
+ private Fragment mFragment;
+
+ public ChooseLockSettingsHelper(Activity activity) {
+ mActivity = activity;
+ mLockPatternUtils = new LockPatternUtils(activity);
+ }
+
+ public ChooseLockSettingsHelper(Activity activity, Fragment fragment) {
+ this(activity);
+ mFragment = fragment;
+ }
+
+ public LockPatternUtils utils() {
+ return mLockPatternUtils;
+ }
+
+ /**
+ * If a pattern, password or PIN exists, prompt the user before allowing them to change it.
+ * @param message optional message to display about the action about to be done
+ * @param details optional detail message to display
+ * @return true if one exists and we launched an activity to confirm it
+ * @see #onActivityResult(int, int, android.content.Intent)
+ */
+ boolean launchConfirmationActivity(int request, CharSequence message, CharSequence details) {
+ boolean launched = false;
+ switch (mLockPatternUtils.getKeyguardStoredPasswordQuality()) {
+ case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
+ launched = confirmPattern(request, message, details);
+ break;
+ case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
+ case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
+ case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
+ case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
+ // TODO: update UI layout for ConfirmPassword to show message and details
+ launched = confirmPassword(request);
+ break;
+ }
+ return launched;
+ }
+
+ /**
+ * Launch screen to confirm the existing lock pattern.
+ * @param message shown in header of ConfirmLockPattern if not null
+ * @param details shown in footer of ConfirmLockPattern if not null
+ * @see #onActivityResult(int, int, android.content.Intent)
+ * @return true if we launched an activity to confirm pattern
+ */
+ private boolean confirmPattern(int request, CharSequence message, CharSequence details) {
+ if (!mLockPatternUtils.isLockPatternEnabled() || !mLockPatternUtils.savedPatternExists()) {
+ return false;
+ }
+ final Intent intent = new Intent();
+ // supply header and footer text in the intent
+ intent.putExtra(ConfirmLockPattern.HEADER_TEXT, message);
+ intent.putExtra(ConfirmLockPattern.FOOTER_TEXT, details);
+ intent.setClassName("com.android.settings", "com.android.settings.ConfirmLockPattern");
+ if (mFragment != null) {
+ mFragment.startActivityForResult(intent, request);
+ } else {
+ mActivity.startActivityForResult(intent, request);
+ }
+ return true;
+ }
+
+ /**
+ * Launch screen to confirm the existing lock password.
+ * @see #onActivityResult(int, int, android.content.Intent)
+ * @return true if we launched an activity to confirm password
+ */
+ private boolean confirmPassword(int request) {
+ if (!mLockPatternUtils.isLockPasswordEnabled()) return false;
+ final Intent intent = new Intent();
+ intent.setClassName("com.android.settings", "com.android.settings.ConfirmLockPassword");
+ if (mFragment != null) {
+ mFragment.startActivityForResult(intent, request);
+ } else {
+ mActivity.startActivityForResult(intent, request);
+ }
+ return true;
+ }
+
+
+}
diff --git a/src/com/android/settings/ConfirmLockPassword.java b/src/com/android/settings/ConfirmLockPassword.java
new file mode 100644
index 0000000..96839ba
--- /dev/null
+++ b/src/com/android/settings/ConfirmLockPassword.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.PasswordEntryKeyboardHelper;
+import com.android.internal.widget.PasswordEntryKeyboardView;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.admin.DevicePolicyManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.preference.PreferenceActivity;
+import android.text.Editable;
+import android.text.InputType;
+import android.text.TextWatcher;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.inputmethod.EditorInfo;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+public class ConfirmLockPassword extends PreferenceActivity {
+
+ @Override
+ public Intent getIntent() {
+ Intent modIntent = new Intent(super.getIntent());
+ modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ConfirmLockPasswordFragment.class.getName());
+ modIntent.putExtra(EXTRA_NO_HEADERS, true);
+ return modIntent;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ // Disable IME on our window since we provide our own keyboard
+ //getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
+ //WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+ super.onCreate(savedInstanceState);
+ CharSequence msg = getText(R.string.lockpassword_confirm_your_password_header);
+ showBreadCrumbs(msg, msg);
+ }
+
+ public static class ConfirmLockPasswordFragment extends Fragment implements OnClickListener,
+ OnEditorActionListener, TextWatcher {
+ private static final long ERROR_MESSAGE_TIMEOUT = 3000;
+ private TextView mPasswordEntry;
+ private LockPatternUtils mLockPatternUtils;
+ private TextView mHeaderText;
+ private Handler mHandler = new Handler();
+ private PasswordEntryKeyboardHelper mKeyboardHelper;
+ private PasswordEntryKeyboardView mKeyboardView;
+ private Button mContinueButton;
+
+
+ // required constructor for fragments
+ public ConfirmLockPasswordFragment() {
+
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mLockPatternUtils = new LockPatternUtils(getActivity());
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ final int storedQuality = mLockPatternUtils.getKeyguardStoredPasswordQuality();
+ View view = inflater.inflate(R.layout.confirm_lock_password, null);
+ // Disable IME on our window since we provide our own keyboard
+
+ view.findViewById(R.id.cancel_button).setOnClickListener(this);
+ mContinueButton = (Button) view.findViewById(R.id.next_button);
+ mContinueButton.setOnClickListener(this);
+ mContinueButton.setEnabled(false); // disable until the user enters at least one char
+
+ mPasswordEntry = (TextView) view.findViewById(R.id.password_entry);
+ mPasswordEntry.setOnEditorActionListener(this);
+ mPasswordEntry.addTextChangedListener(this);
+
+ mKeyboardView = (PasswordEntryKeyboardView) view.findViewById(R.id.keyboard);
+ mHeaderText = (TextView) view.findViewById(R.id.headerText);
+ final boolean isAlpha = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == storedQuality
+ || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == storedQuality
+ || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == storedQuality;
+ mHeaderText.setText(isAlpha ? R.string.lockpassword_confirm_your_password_header
+ : R.string.lockpassword_confirm_your_pin_header);
+
+ final Activity activity = getActivity();
+ mKeyboardHelper = new PasswordEntryKeyboardHelper(activity,
+ mKeyboardView, mPasswordEntry);
+ mKeyboardHelper.setKeyboardMode(isAlpha ?
+ PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA
+ : PasswordEntryKeyboardHelper.KEYBOARD_MODE_NUMERIC);
+ mKeyboardView.requestFocus();
+
+ int currentType = mPasswordEntry.getInputType();
+ mPasswordEntry.setInputType(isAlpha ? currentType
+ : (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD));
+
+ // Update the breadcrumb (title) if this is embedded in a PreferenceActivity
+ if (activity instanceof PreferenceActivity) {
+ final PreferenceActivity preferenceActivity = (PreferenceActivity) activity;
+ int id = isAlpha ? R.string.lockpassword_confirm_your_password_header
+ : R.string.lockpassword_confirm_your_pin_header;
+ CharSequence title = getText(id);
+ preferenceActivity.showBreadCrumbs(title, title);
+ }
+
+ return view;
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ mKeyboardView.requestFocus();
+ }
+
+ @Override
+ public void onResume() {
+ // TODO Auto-generated method stub
+ super.onResume();
+ mKeyboardView.requestFocus();
+ }
+
+ private void handleNext() {
+ final String pin = mPasswordEntry.getText().toString();
+ if (mLockPatternUtils.checkPassword(pin)) {
+
+ Intent intent = new Intent();
+ intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, pin);
+
+ getActivity().setResult(RESULT_OK, intent);
+ getActivity().finish();
+ } else {
+ showError(R.string.lockpattern_need_to_unlock_wrong);
+ }
+ }
+
+ public void onClick(View v) {
+ switch (v.getId()) {
+ case R.id.next_button:
+ handleNext();
+ break;
+
+ case R.id.cancel_button:
+ getActivity().setResult(RESULT_CANCELED);
+ getActivity().finish();
+ break;
+ }
+ }
+
+ private void showError(int msg) {
+ mHeaderText.setText(msg);
+ mHeaderText.announceForAccessibility(mHeaderText.getText());
+ mPasswordEntry.setText(null);
+ mHandler.postDelayed(new Runnable() {
+ public void run() {
+ mHeaderText.setText(R.string.lockpassword_confirm_your_password_header);
+ }
+ }, ERROR_MESSAGE_TIMEOUT);
+ }
+
+ // {@link OnEditorActionListener} methods.
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ // Check if this was the result of hitting the enter or "done" key
+ if (actionId == EditorInfo.IME_NULL
+ || actionId == EditorInfo.IME_ACTION_DONE
+ || actionId == EditorInfo.IME_ACTION_NEXT) {
+ handleNext();
+ return true;
+ }
+ return false;
+ }
+
+ // {@link TextWatcher} methods.
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+
+ public void afterTextChanged(Editable s) {
+ mContinueButton.setEnabled(mPasswordEntry.getText().length() > 0);
+ }
+ }
+}
diff --git a/src/com/android/settings/ConfirmLockPattern.java b/src/com/android/settings/ConfirmLockPattern.java
new file mode 100644
index 0000000..dd375cc
--- /dev/null
+++ b/src/com/android/settings/ConfirmLockPattern.java
@@ -0,0 +1,308 @@
+/*
+ * 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.android.settings;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternView;
+import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient;
+import com.android.internal.widget.LockPatternView.Cell;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.content.Intent;
+import android.os.CountDownTimer;
+import android.os.SystemClock;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+import android.widget.TextView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.List;
+
+/**
+ * Launch this when you want the user to confirm their lock pattern.
+ *
+ * Sets an activity result of {@link Activity#RESULT_OK} when the user
+ * successfully confirmed their pattern.
+ */
+public class ConfirmLockPattern extends PreferenceActivity {
+
+ /**
+ * Names of {@link CharSequence} fields within the originating {@link Intent}
+ * that are used to configure the keyguard confirmation view's labeling.
+ * The view will use the system-defined resource strings for any labels that
+ * the caller does not supply.
+ */
+ public static final String PACKAGE = "com.android.settings";
+ public static final String HEADER_TEXT = PACKAGE + ".ConfirmLockPattern.header";
+ public static final String FOOTER_TEXT = PACKAGE + ".ConfirmLockPattern.footer";
+ public static final String HEADER_WRONG_TEXT = PACKAGE + ".ConfirmLockPattern.header_wrong";
+ public static final String FOOTER_WRONG_TEXT = PACKAGE + ".ConfirmLockPattern.footer_wrong";
+
+ private enum Stage {
+ NeedToUnlock,
+ NeedToUnlockWrong,
+ LockedOut
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ CharSequence msg = getText(R.string.lockpassword_confirm_your_pattern_header);
+ showBreadCrumbs(msg, msg);
+ }
+
+ @Override
+ public Intent getIntent() {
+ Intent modIntent = new Intent(super.getIntent());
+ modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ConfirmLockPatternFragment.class.getName());
+ modIntent.putExtra(EXTRA_NO_HEADERS, true);
+ return modIntent;
+ }
+
+ public static class ConfirmLockPatternFragment extends Fragment {
+
+ // how long we wait to clear a wrong pattern
+ private static final int WRONG_PATTERN_CLEAR_TIMEOUT_MS = 2000;
+
+ private static final String KEY_NUM_WRONG_ATTEMPTS = "num_wrong_attempts";
+
+ private LockPatternView mLockPatternView;
+ private LockPatternUtils mLockPatternUtils;
+ private int mNumWrongConfirmAttempts;
+ private CountDownTimer mCountdownTimer;
+
+ private TextView mHeaderTextView;
+ private TextView mFooterTextView;
+
+ // caller-supplied text for various prompts
+ private CharSequence mHeaderText;
+ private CharSequence mFooterText;
+ private CharSequence mHeaderWrongText;
+ private CharSequence mFooterWrongText;
+
+ // required constructor for fragments
+ public ConfirmLockPatternFragment() {
+
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mLockPatternUtils = new LockPatternUtils(getActivity());
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.confirm_lock_pattern, null);
+ mHeaderTextView = (TextView) view.findViewById(R.id.headerText);
+ mLockPatternView = (LockPatternView) view.findViewById(R.id.lockPattern);
+ mFooterTextView = (TextView) view.findViewById(R.id.footerText);
+
+ // make it so unhandled touch events within the unlock screen go to the
+ // lock pattern view.
+ final LinearLayoutWithDefaultTouchRecepient topLayout
+ = (LinearLayoutWithDefaultTouchRecepient) view.findViewById(R.id.topLayout);
+ topLayout.setDefaultTouchRecepient(mLockPatternView);
+
+ Intent intent = getActivity().getIntent();
+ if (intent != null) {
+ mHeaderText = intent.getCharSequenceExtra(HEADER_TEXT);
+ mFooterText = intent.getCharSequenceExtra(FOOTER_TEXT);
+ mHeaderWrongText = intent.getCharSequenceExtra(HEADER_WRONG_TEXT);
+ mFooterWrongText = intent.getCharSequenceExtra(FOOTER_WRONG_TEXT);
+ }
+
+ mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled());
+ mLockPatternView.setOnPatternListener(mConfirmExistingLockPatternListener);
+ updateStage(Stage.NeedToUnlock);
+
+ if (savedInstanceState != null) {
+ mNumWrongConfirmAttempts = savedInstanceState.getInt(KEY_NUM_WRONG_ATTEMPTS);
+ } else {
+ // on first launch, if no lock pattern is set, then finish with
+ // success (don't want user to get stuck confirming something that
+ // doesn't exist).
+ if (!mLockPatternUtils.savedPatternExists()) {
+ getActivity().setResult(Activity.RESULT_OK);
+ getActivity().finish();
+ }
+ }
+ return view;
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ // deliberately not calling super since we are managing this in full
+ outState.putInt(KEY_NUM_WRONG_ATTEMPTS, mNumWrongConfirmAttempts);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+
+ if (mCountdownTimer != null) {
+ mCountdownTimer.cancel();
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ // if the user is currently locked out, enforce it.
+ long deadline = mLockPatternUtils.getLockoutAttemptDeadline();
+ if (deadline != 0) {
+ handleAttemptLockout(deadline);
+ } else if (!mLockPatternView.isEnabled()) {
+ // The deadline has passed, but the timer was cancelled...
+ // Need to clean up.
+ mNumWrongConfirmAttempts = 0;
+ updateStage(Stage.NeedToUnlock);
+ }
+ }
+
+ private void updateStage(Stage stage) {
+ switch (stage) {
+ case NeedToUnlock:
+ if (mHeaderText != null) {
+ mHeaderTextView.setText(mHeaderText);
+ } else {
+ mHeaderTextView.setText(R.string.lockpattern_need_to_unlock);
+ }
+ if (mFooterText != null) {
+ mFooterTextView.setText(mFooterText);
+ } else {
+ mFooterTextView.setText(R.string.lockpattern_need_to_unlock_footer);
+ }
+
+ mLockPatternView.setEnabled(true);
+ mLockPatternView.enableInput();
+ break;
+ case NeedToUnlockWrong:
+ if (mHeaderWrongText != null) {
+ mHeaderTextView.setText(mHeaderWrongText);
+ } else {
+ mHeaderTextView.setText(R.string.lockpattern_need_to_unlock_wrong);
+ }
+ if (mFooterWrongText != null) {
+ mFooterTextView.setText(mFooterWrongText);
+ } else {
+ mFooterTextView.setText(R.string.lockpattern_need_to_unlock_wrong_footer);
+ }
+
+ mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
+ mLockPatternView.setEnabled(true);
+ mLockPatternView.enableInput();
+ break;
+ case LockedOut:
+ mLockPatternView.clearPattern();
+ // enabled = false means: disable input, and have the
+ // appearance of being disabled.
+ mLockPatternView.setEnabled(false); // appearance of being disabled
+ break;
+ }
+
+ // Always announce the header for accessibility. This is a no-op
+ // when accessibility is disabled.
+ mHeaderTextView.announceForAccessibility(mHeaderTextView.getText());
+ }
+
+ private Runnable mClearPatternRunnable = new Runnable() {
+ public void run() {
+ mLockPatternView.clearPattern();
+ }
+ };
+
+ // clear the wrong pattern unless they have started a new one
+ // already
+ private void postClearPatternRunnable() {
+ mLockPatternView.removeCallbacks(mClearPatternRunnable);
+ mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS);
+ }
+
+ /**
+ * The pattern listener that responds according to a user confirming
+ * an existing lock pattern.
+ */
+ private LockPatternView.OnPatternListener mConfirmExistingLockPatternListener
+ = new LockPatternView.OnPatternListener() {
+
+ public void onPatternStart() {
+ mLockPatternView.removeCallbacks(mClearPatternRunnable);
+ }
+
+ public void onPatternCleared() {
+ mLockPatternView.removeCallbacks(mClearPatternRunnable);
+ }
+
+ public void onPatternCellAdded(List pattern) {
+
+ }
+
+ public void onPatternDetected(List pattern) {
+ if (mLockPatternUtils.checkPattern(pattern)) {
+
+ Intent intent = new Intent();
+ intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD,
+ LockPatternUtils.patternToString(pattern));
+
+ getActivity().setResult(Activity.RESULT_OK, intent);
+ getActivity().finish();
+ } else {
+ if (pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL &&
+ ++mNumWrongConfirmAttempts
+ >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) {
+ long deadline = mLockPatternUtils.setLockoutAttemptDeadline();
+ handleAttemptLockout(deadline);
+ } else {
+ updateStage(Stage.NeedToUnlockWrong);
+ postClearPatternRunnable();
+ }
+ }
+ }
+ };
+
+
+ private void handleAttemptLockout(long elapsedRealtimeDeadline) {
+ updateStage(Stage.LockedOut);
+ long elapsedRealtime = SystemClock.elapsedRealtime();
+ mCountdownTimer = new CountDownTimer(
+ elapsedRealtimeDeadline - elapsedRealtime,
+ LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) {
+
+ @Override
+ public void onTick(long millisUntilFinished) {
+ mHeaderTextView.setText(R.string.lockpattern_too_many_failed_confirmation_attempts_header);
+ final int secondsCountdown = (int) (millisUntilFinished / 1000);
+ mFooterTextView.setText(getString(
+ R.string.lockpattern_too_many_failed_confirmation_attempts_footer,
+ secondsCountdown));
+ }
+
+ @Override
+ public void onFinish() {
+ mNumWrongConfirmAttempts = 0;
+ updateStage(Stage.NeedToUnlock);
+ }
+ }.start();
+ }
+ }
+}
diff --git a/src/com/android/settings/ContrastPreference.java b/src/com/android/settings/ContrastPreference.java
new file mode 100644
index 0000000..f8e4ccf
--- /dev/null
+++ b/src/com/android/settings/ContrastPreference.java
@@ -0,0 +1,176 @@
+package com.android.settings;
+/*
+ ************************************************************************************
+ * Android Settings
+
+ * (c) Copyright 2006-2010, huanglong Allwinner
+ * All Rights Reserved
+ *
+ * File : ContrastPreference.java
+ * By : huanglong
+ * Version : v1.0
+ * Date : 2011-9-5 14:27:00
+ * Description: Add the Contrast settings to Display.
+ * Update : date author version notes
+ *
+ ************************************************************************************
+ */
+
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.AlertDialog.Builder;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.IPowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.preference.SeekBarDialogPreference;
+import android.preference.SeekBarPreference;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.DisplayManagerAw;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.SeekBar;
+
+public class ContrastPreference extends SeekBarDialogPreference implements
+ SeekBar.OnSeekBarChangeListener{
+
+ private SeekBar mSeekBar;
+
+ private int mOldContrastValue;
+ private DisplayManagerAw mDisplayManagerAw;
+
+ private int MAXIMUM_VALUE = 80;
+ private int MINIMUM_VALUE = 20;
+ private static boolean mIsPositiveResult;
+
+ public ContrastPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mDisplayManagerAw = (DisplayManagerAw) context.getSystemService(Context.DISPLAY_SERVICE_AW);
+ /* createActionButtons();
+ setDialogLayoutResource(R.layout.preference_dialog_contrast);
+ setDialogIcon(R.drawable.ic_settings_contrast);
+ setDialogIcon(null);
+ setDialogTitle(null);
+ setPositiveButtonText(null);
+ setNegativeButtonText(null);*/
+ }
+
+ @Override
+ public void createActionButtons() {
+
+ }
+
+ @Override
+ protected View onCreateDialogView() {
+ LayoutInflater inflater = LayoutInflater.from(getContext());
+ return inflater.inflate(R.layout.preference_dialog_contrast, null);
+ }
+
+ @Override
+ protected void onBindDialogView(View view){
+ super.onBindDialogView(view);
+
+ mSeekBar = getSeekBar(view);
+ mSeekBar.setMax(MAXIMUM_VALUE-MINIMUM_VALUE);
+ try{
+ mOldContrastValue = getSysInt();
+ }catch(SettingNotFoundException snfe){
+ mOldContrastValue = MAXIMUM_VALUE;
+ }
+ Log.d("staturation","" + mOldContrastValue);
+ mSeekBar.setProgress(mOldContrastValue - MINIMUM_VALUE);
+ mSeekBar.setOnSeekBarChangeListener(this);
+ }
+
+ public void onProgressChanged(SeekBar seekBar, int progress,
+ boolean fromTouch){
+ setContrast(progress + MINIMUM_VALUE);
+ }
+ @Override
+ protected void onDialogClosed(boolean positiveResult){
+ super.onDialogClosed(mIsPositiveResult);
+ if(mIsPositiveResult){
+ putSysInt(mSeekBar.getProgress() + MINIMUM_VALUE);
+ }else{
+ setContrast(mOldContrastValue);
+ }
+ }
+
+ private int getSysInt() throws SettingNotFoundException
+ {
+ return Settings.System.getInt(getContext().getContentResolver(),
+ Settings.System.COLOR_CONTRAST,MINIMUM_VALUE);
+ }
+ private boolean putSysInt(int value)
+ {
+ return Settings.System.putInt(getContext().getContentResolver(),
+ Settings.System.COLOR_CONTRAST,value);
+ }
+ private void setContrast(int Contrasrt) {
+ mDisplayManagerAw.setDisplayContrast(0,Contrasrt);
+ }
+
+ /*implements method in SeekBar.OnSeekBarChangeListener*/
+ @Override
+ public void onStartTrackingTouch(SeekBar arg0) {
+ // NA
+
+ }
+ /*implements method in SeekBar.OnSeekBarChangeListener*/
+ @Override
+ public void onStopTrackingTouch(SeekBar arg0) {
+ // NA
+
+ }
+
+
+ @Override
+ protected void showDialog(Bundle state) {
+
+ Builder mBuilder = new AlertDialog.Builder(getContext());
+
+ View contentView = onCreateDialogView();
+
+ if (contentView != null) {
+ onBindDialogView(contentView);
+ mBuilder.setView(contentView);
+ }
+ onPrepareDialogBuilder(mBuilder);
+ // Create the dialog
+ final Dialog dialog = mBuilder.create();
+ if (state != null) {
+ dialog.onRestoreInstanceState(state);
+ }
+ contentView.findViewById(R.id.cancel_btn).setOnClickListener(
+ new View.OnClickListener() {
+
+ @Override
+ public void onClick(View arg0) {
+ mIsPositiveResult = false;
+ dialog.dismiss();
+
+ }
+ });
+ contentView.findViewById(R.id.ok_btn).setOnClickListener(
+ new View.OnClickListener() {
+
+ @Override
+ public void onClick(View arg0) {
+ mIsPositiveResult = true;
+ dialog.dismiss();
+ }
+ });
+ dialog.setOnDismissListener(this);
+ dialog.show();
+
+ dialog.getWindow().setGravity(Gravity.CENTER);
+ dialog.getWindow().setLayout(600, 300);
+
+ }
+}
diff --git a/src/com/android/settings/CreateShortcut.java b/src/com/android/settings/CreateShortcut.java
new file mode 100644
index 0000000..0312a4b
--- /dev/null
+++ b/src/com/android/settings/CreateShortcut.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import android.app.LauncherActivity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.ListView;
+
+public class CreateShortcut extends LauncherActivity {
+
+ @Override
+ protected Intent getTargetIntent() {
+ Intent targetIntent = new Intent(Intent.ACTION_MAIN, null);
+ targetIntent.addCategory("com.android.settings.SHORTCUT");
+ targetIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return targetIntent;
+ }
+
+ @Override
+ protected void onListItemClick(ListView l, View v, int position, long id) {
+ Intent shortcutIntent = intentForPosition(position);
+ shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ Intent intent = new Intent();
+ intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
+ Intent.ShortcutIconResource.fromContext(this, R.mipmap.ic_launcher_settings));
+ intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
+ intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, itemForPosition(position).label);
+ setResult(RESULT_OK, intent);
+ finish();
+ }
+
+ @Override
+ protected boolean onEvaluateShowIcons() {
+ return false;
+ }
+}
diff --git a/src/com/android/settings/CredentialStorage.java b/src/com/android/settings/CredentialStorage.java
new file mode 100644
index 0000000..c737c7d
--- /dev/null
+++ b/src/com/android/settings/CredentialStorage.java
@@ -0,0 +1,468 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.admin.DevicePolicyManager;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.security.Credentials;
+import android.security.KeyChain.KeyChainConnection;
+import android.security.KeyChain;
+import android.security.KeyStore;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.Toast;
+import com.android.internal.widget.LockPatternUtils;
+
+/**
+ * CredentialStorage handles KeyStore reset, unlock, and install.
+ *
+ * CredentialStorage has a pretty convoluted state machine to migrate
+ * from the old style separate keystore password to a new key guard
+ * based password, as well as to deal with setting up the key guard if
+ * necessary.
+ *
+ * KeyStore: UNINITALIZED
+ * KeyGuard: OFF
+ * Action: set up key guard
+ * Notes: factory state
+ *
+ * KeyStore: UNINITALIZED
+ * KeyGuard: ON
+ * Action: confirm key guard
+ * Notes: user had key guard but no keystore and upgraded from pre-ICS
+ * OR user had key guard and pre-ICS keystore password which was then reset
+ *
+ * KeyStore: LOCKED
+ * KeyGuard: OFF/ON
+ * Action: old unlock dialog
+ * Notes: assume old password, need to use it to unlock.
+ * if unlock, ensure key guard before install.
+ * if reset, treat as UNINITALIZED/OFF
+ *
+ * KeyStore: UNLOCKED
+ * KeyGuard: OFF
+ * Action: set up key guard
+ * Notes: ensure key guard, then proceed
+ *
+ * KeyStore: UNLOCKED
+ * keyguard: ON
+ * Action: normal unlock/install
+ * Notes: this is the common case
+ */
+public final class CredentialStorage extends Activity {
+
+ private static final String TAG = "CredentialStorage";
+
+ public static final String ACTION_UNLOCK = "com.android.credentials.UNLOCK";
+ public static final String ACTION_INSTALL = "com.android.credentials.INSTALL";
+ public static final String ACTION_RESET = "com.android.credentials.RESET";
+
+ // This is the minimum acceptable password quality. If the current password quality is
+ // lower than this, keystore should not be activated.
+ static final int MIN_PASSWORD_QUALITY = DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+
+ private static final int CONFIRM_KEY_GUARD_REQUEST = 1;
+
+ private final KeyStore mKeyStore = KeyStore.getInstance();
+
+ /**
+ * When non-null, the bundle containing credentials to install.
+ */
+ private Bundle mInstallBundle;
+
+ /**
+ * After unsuccessful KeyStore.unlock, the number of unlock
+ * attempts remaining before the KeyStore will reset itself.
+ *
+ * Reset to -1 on successful unlock or reset.
+ */
+ private int mRetriesRemaining = -1;
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ if (UserHandle.myUserId() != UserHandle.USER_OWNER) {
+ Log.i(TAG, "Cannot install to CredentialStorage as non-primary user");
+ finish();
+ return;
+ }
+
+ Intent intent = getIntent();
+ String action = intent.getAction();
+
+ if (ACTION_RESET.equals(action)) {
+ new ResetDialog();
+ } else {
+ if (ACTION_INSTALL.equals(action)
+ && "com.android.certinstaller".equals(getCallingPackage())) {
+ mInstallBundle = intent.getExtras();
+ }
+ // ACTION_UNLOCK also handled here in addition to ACTION_INSTALL
+ handleUnlockOrInstall();
+ }
+ }
+
+ /**
+ * Based on the current state of the KeyStore and key guard, try to
+ * make progress on unlocking or installing to the keystore.
+ */
+ private void handleUnlockOrInstall() {
+ // something already decided we are done, do not proceed
+ if (isFinishing()) {
+ return;
+ }
+ switch (mKeyStore.state()) {
+ case UNINITIALIZED: {
+ ensureKeyGuard();
+ return;
+ }
+ case LOCKED: {
+ new UnlockDialog();
+ return;
+ }
+ case UNLOCKED: {
+ if (!checkKeyGuardQuality()) {
+ new ConfigureKeyGuardDialog();
+ return;
+ }
+ installIfAvailable();
+ finish();
+ return;
+ }
+ }
+ }
+
+ /**
+ * Make sure the user enters the key guard to set or change the
+ * keystore password. This can be used in UNINITIALIZED to set the
+ * keystore password or UNLOCKED to change the password (as is the
+ * case after unlocking with an old-style password).
+ */
+ private void ensureKeyGuard() {
+ if (!checkKeyGuardQuality()) {
+ // key guard not setup, doing so will initialize keystore
+ new ConfigureKeyGuardDialog();
+ // will return to onResume after Activity
+ return;
+ }
+ // force key guard confirmation
+ if (confirmKeyGuard()) {
+ // will return password value via onActivityResult
+ return;
+ }
+ finish();
+ }
+
+ /**
+ * Returns true if the currently set key guard matches our minimum quality requirements.
+ */
+ private boolean checkKeyGuardQuality() {
+ int quality = new LockPatternUtils(this).getActivePasswordQuality();
+ return (quality >= MIN_PASSWORD_QUALITY);
+ }
+
+ /**
+ * Install credentials if available, otherwise do nothing.
+ */
+ private void installIfAvailable() {
+ if (mInstallBundle != null && !mInstallBundle.isEmpty()) {
+ Bundle bundle = mInstallBundle;
+ mInstallBundle = null;
+
+ if (bundle.containsKey(Credentials.EXTRA_USER_PRIVATE_KEY_NAME)) {
+ String key = bundle.getString(Credentials.EXTRA_USER_PRIVATE_KEY_NAME);
+ byte[] value = bundle.getByteArray(Credentials.EXTRA_USER_PRIVATE_KEY_DATA);
+
+ if (!mKeyStore.importKey(key, value)) {
+ Log.e(TAG, "Failed to install " + key);
+ return;
+ }
+ }
+
+ if (bundle.containsKey(Credentials.EXTRA_USER_CERTIFICATE_NAME)) {
+ String certName = bundle.getString(Credentials.EXTRA_USER_CERTIFICATE_NAME);
+ byte[] certData = bundle.getByteArray(Credentials.EXTRA_USER_CERTIFICATE_DATA);
+
+ if (!mKeyStore.put(certName, certData)) {
+ Log.e(TAG, "Failed to install " + certName);
+ return;
+ }
+ }
+
+ if (bundle.containsKey(Credentials.EXTRA_CA_CERTIFICATES_NAME)) {
+ String caListName = bundle.getString(Credentials.EXTRA_CA_CERTIFICATES_NAME);
+ byte[] caListData = bundle.getByteArray(Credentials.EXTRA_CA_CERTIFICATES_DATA);
+
+ if (!mKeyStore.put(caListName, caListData)) {
+ Log.e(TAG, "Failed to install " + caListName);
+ return;
+ }
+
+ }
+
+ setResult(RESULT_OK);
+ }
+ }
+
+ /**
+ * Prompt for reset confirmation, resetting on confirmation, finishing otherwise.
+ */
+ private class ResetDialog
+ implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener
+ {
+ private boolean mResetConfirmed;
+
+ private ResetDialog() {
+ AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this)
+ .setTitle(android.R.string.dialog_alert_title)
+ .setIconAttribute(android.R.attr.alertDialogIcon)
+ .setMessage(R.string.credentials_reset_hint)
+ .setPositiveButton(android.R.string.ok, this)
+ .setNegativeButton(android.R.string.cancel, this)
+ .create();
+ dialog.setOnDismissListener(this);
+ dialog.show();
+ }
+
+ @Override public void onClick(DialogInterface dialog, int button) {
+ mResetConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
+ }
+
+ @Override public void onDismiss(DialogInterface dialog) {
+ if (mResetConfirmed) {
+ mResetConfirmed = false;
+ new ResetKeyStoreAndKeyChain().execute();
+ return;
+ }
+ finish();
+ }
+ }
+
+ /**
+ * Background task to handle reset of both keystore and user installed CAs.
+ */
+ private class ResetKeyStoreAndKeyChain extends AsyncTask {
+
+ @Override protected Boolean doInBackground(Void... unused) {
+
+ mKeyStore.reset();
+
+ try {
+ KeyChainConnection keyChainConnection = KeyChain.bind(CredentialStorage.this);
+ try {
+ return keyChainConnection.getService().reset();
+ } catch (RemoteException e) {
+ return false;
+ } finally {
+ keyChainConnection.close();
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return false;
+ }
+ }
+
+ @Override protected void onPostExecute(Boolean success) {
+ if (success) {
+ Toast.makeText(CredentialStorage.this,
+ R.string.credentials_erased, Toast.LENGTH_SHORT).show();
+ } else {
+ Toast.makeText(CredentialStorage.this,
+ R.string.credentials_not_erased, Toast.LENGTH_SHORT).show();
+ }
+ finish();
+ }
+ }
+
+ /**
+ * Prompt for key guard configuration confirmation.
+ */
+ private class ConfigureKeyGuardDialog
+ implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener
+ {
+ private boolean mConfigureConfirmed;
+
+ private ConfigureKeyGuardDialog() {
+ AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this)
+ .setTitle(android.R.string.dialog_alert_title)
+ .setIconAttribute(android.R.attr.alertDialogIcon)
+ .setMessage(R.string.credentials_configure_lock_screen_hint)
+ .setPositiveButton(android.R.string.ok, this)
+ .setNegativeButton(android.R.string.cancel, this)
+ .create();
+ dialog.setOnDismissListener(this);
+ dialog.show();
+ }
+
+ @Override public void onClick(DialogInterface dialog, int button) {
+ mConfigureConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
+ }
+
+ @Override public void onDismiss(DialogInterface dialog) {
+ if (mConfigureConfirmed) {
+ mConfigureConfirmed = false;
+ Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
+ intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY,
+ MIN_PASSWORD_QUALITY);
+ startActivity(intent);
+ return;
+ }
+ finish();
+ }
+ }
+
+ /**
+ * Confirm existing key guard, returning password via onActivityResult.
+ */
+ private boolean confirmKeyGuard() {
+ Resources res = getResources();
+ boolean launched = new ChooseLockSettingsHelper(this)
+ .launchConfirmationActivity(CONFIRM_KEY_GUARD_REQUEST,
+ res.getText(R.string.credentials_install_gesture_prompt),
+ res.getText(R.string.credentials_install_gesture_explanation));
+ return launched;
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ /**
+ * Receive key guard password initiated by confirmKeyGuard.
+ */
+ if (requestCode == CONFIRM_KEY_GUARD_REQUEST) {
+ if (resultCode == Activity.RESULT_OK) {
+ String password = data.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
+ if (!TextUtils.isEmpty(password)) {
+ // success
+ mKeyStore.password(password);
+ // return to onResume
+ return;
+ }
+ }
+ // failed confirmation, bail
+ finish();
+ }
+ }
+
+ /**
+ * Prompt for unlock with old-style password.
+ *
+ * On successful unlock, ensure migration to key guard before continuing.
+ * On unsuccessful unlock, retry by calling handleUnlockOrInstall.
+ */
+ private class UnlockDialog implements TextWatcher,
+ DialogInterface.OnClickListener, DialogInterface.OnDismissListener
+ {
+ private boolean mUnlockConfirmed;
+
+ private final Button mButton;
+ private final TextView mOldPassword;
+ private final TextView mError;
+
+ private UnlockDialog() {
+ View view = View.inflate(CredentialStorage.this, R.layout.credentials_dialog, null);
+
+ CharSequence text;
+ if (mRetriesRemaining == -1) {
+ text = getResources().getText(R.string.credentials_unlock_hint);
+ } else if (mRetriesRemaining > 3) {
+ text = getResources().getText(R.string.credentials_wrong_password);
+ } else if (mRetriesRemaining == 1) {
+ text = getResources().getText(R.string.credentials_reset_warning);
+ } else {
+ text = getString(R.string.credentials_reset_warning_plural, mRetriesRemaining);
+ }
+
+ ((TextView) view.findViewById(R.id.hint)).setText(text);
+ mOldPassword = (TextView) view.findViewById(R.id.old_password);
+ mOldPassword.setVisibility(View.VISIBLE);
+ mOldPassword.addTextChangedListener(this);
+ mError = (TextView) view.findViewById(R.id.error);
+
+ AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this)
+ .setView(view)
+ .setTitle(R.string.credentials_unlock)
+ .setPositiveButton(android.R.string.ok, this)
+ .setNegativeButton(android.R.string.cancel, this)
+ .create();
+ dialog.setOnDismissListener(this);
+ dialog.show();
+ mButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
+ mButton.setEnabled(false);
+ }
+
+ @Override public void afterTextChanged(Editable editable) {
+ mButton.setEnabled(mOldPassword == null || mOldPassword.getText().length() > 0);
+ }
+
+ @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override public void onTextChanged(CharSequence s,int start, int before, int count) {
+ }
+
+ @Override public void onClick(DialogInterface dialog, int button) {
+ mUnlockConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
+ }
+
+ @Override public void onDismiss(DialogInterface dialog) {
+ if (mUnlockConfirmed) {
+ mUnlockConfirmed = false;
+ mError.setVisibility(View.VISIBLE);
+ mKeyStore.unlock(mOldPassword.getText().toString());
+ int error = mKeyStore.getLastError();
+ if (error == KeyStore.NO_ERROR) {
+ mRetriesRemaining = -1;
+ Toast.makeText(CredentialStorage.this,
+ R.string.credentials_enabled,
+ Toast.LENGTH_SHORT).show();
+ // aha, now we are unlocked, switch to key guard.
+ // we'll end up back in onResume to install
+ ensureKeyGuard();
+ } else if (error == KeyStore.UNINITIALIZED) {
+ mRetriesRemaining = -1;
+ Toast.makeText(CredentialStorage.this,
+ R.string.credentials_erased,
+ Toast.LENGTH_SHORT).show();
+ // we are reset, we can now set new password with key guard
+ handleUnlockOrInstall();
+ } else if (error >= KeyStore.WRONG_PASSWORD) {
+ // we need to try again
+ mRetriesRemaining = error - KeyStore.WRONG_PASSWORD + 1;
+ handleUnlockOrInstall();
+ }
+ return;
+ }
+ finish();
+ }
+ }
+}
diff --git a/src/com/android/settings/CryptKeeper.java b/src/com/android/settings/CryptKeeper.java
new file mode 100644
index 0000000..f07d6fa
--- /dev/null
+++ b/src/com/android/settings/CryptKeeper.java
@@ -0,0 +1,754 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import android.app.Activity;
+import android.app.StatusBarManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.media.AudioManager;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.storage.IMountService;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnKeyListener;
+import android.view.View.OnTouchListener;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import com.android.internal.telephony.ITelephony;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+
+import java.util.List;
+
+/**
+ * Settings screens to show the UI flows for encrypting/decrypting the device.
+ *
+ * This may be started via adb for debugging the UI layout, without having to go through
+ * encryption flows everytime. It should be noted that starting the activity in this manner
+ * is only useful for verifying UI-correctness - the behavior will not be identical.
+ *
+ * $ adb shell pm enable com.android.settings/.CryptKeeper
+ * $ adb shell am start \
+ * -e "com.android.settings.CryptKeeper.DEBUG_FORCE_VIEW" "progress" \
+ * -n com.android.settings/.CryptKeeper
+ *
+ */
+public class CryptKeeper extends Activity implements TextView.OnEditorActionListener,
+ OnKeyListener, OnTouchListener, TextWatcher {
+ private static final String TAG = "CryptKeeper";
+
+ private static final String DECRYPT_STATE = "trigger_restart_framework";
+ /** Message sent to us to indicate encryption update progress. */
+ private static final int MESSAGE_UPDATE_PROGRESS = 1;
+ /** Message sent to us to cool-down (waste user's time between password attempts) */
+ private static final int MESSAGE_COOLDOWN = 2;
+ /** Message sent to us to indicate alerting the user that we are waiting for password entry */
+ private static final int MESSAGE_NOTIFY = 3;
+
+ // Constants used to control policy.
+ private static final int MAX_FAILED_ATTEMPTS = 30;
+ private static final int COOL_DOWN_ATTEMPTS = 10;
+ private static final int COOL_DOWN_INTERVAL = 30; // 30 seconds
+
+ // Intent action for launching the Emergency Dialer activity.
+ static final String ACTION_EMERGENCY_DIAL = "com.android.phone.EmergencyDialer.DIAL";
+
+ // Debug Intent extras so that this Activity may be started via adb for debugging UI layouts
+ private static final String EXTRA_FORCE_VIEW =
+ "com.android.settings.CryptKeeper.DEBUG_FORCE_VIEW";
+ private static final String FORCE_VIEW_PROGRESS = "progress";
+ private static final String FORCE_VIEW_ERROR = "error";
+ private static final String FORCE_VIEW_PASSWORD = "password";
+
+ /** When encryption is detected, this flag indicates whether or not we've checked for errors. */
+ private boolean mValidationComplete;
+ private boolean mValidationRequested;
+ /** A flag to indicate that the volume is in a bad state (e.g. partially encrypted). */
+ private boolean mEncryptionGoneBad;
+ /** A flag to indicate when the back event should be ignored */
+ private boolean mIgnoreBack = false;
+ private int mCooldown;
+ PowerManager.WakeLock mWakeLock;
+ private EditText mPasswordEntry;
+ /** Number of calls to {@link #notifyUser()} to ignore before notifying. */
+ private int mNotificationCountdown = 0;
+
+ /**
+ * Used to propagate state through configuration changes (e.g. screen rotation)
+ */
+ private static class NonConfigurationInstanceState {
+ final PowerManager.WakeLock wakelock;
+
+ NonConfigurationInstanceState(PowerManager.WakeLock _wakelock) {
+ wakelock = _wakelock;
+ }
+ }
+
+ /**
+ * Activity used to fade the screen to black after the password is entered.
+ */
+ public static class FadeToBlack extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.crypt_keeper_blank);
+ }
+ /** Ignore all back events. */
+ @Override
+ public void onBackPressed() {
+ return;
+ }
+ }
+
+ private class DecryptTask extends AsyncTask {
+ @Override
+ protected Integer doInBackground(String... params) {
+ final IMountService service = getMountService();
+ try {
+ return service.decryptStorage(params[0]);
+ } catch (Exception e) {
+ Log.e(TAG, "Error while decrypting...", e);
+ return -1;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Integer failedAttempts) {
+ if (failedAttempts == 0) {
+ // The password was entered successfully. Start the Blank activity
+ // so this activity animates to black before the devices starts. Note
+ // It has 1 second to complete the animation or it will be frozen
+ // until the boot animation comes back up.
+ Intent intent = new Intent(CryptKeeper.this, FadeToBlack.class);
+ finish();
+ startActivity(intent);
+ } else if (failedAttempts == MAX_FAILED_ATTEMPTS) {
+ // Factory reset the device.
+ sendBroadcast(new Intent("android.intent.action.MASTER_CLEAR"));
+ } else if ((failedAttempts % COOL_DOWN_ATTEMPTS) == 0) {
+ mCooldown = COOL_DOWN_INTERVAL;
+ cooldown();
+ } else {
+ final TextView status = (TextView) findViewById(R.id.status);
+ status.setText(R.string.try_again);
+ // Reenable the password entry
+ mPasswordEntry.setEnabled(true);
+ }
+ }
+ }
+
+ private class ValidationTask extends AsyncTask {
+ @Override
+ protected Boolean doInBackground(Void... params) {
+ final IMountService service = getMountService();
+ try {
+ Log.d(TAG, "Validating encryption state.");
+ int state = service.getEncryptionState();
+ if (state == IMountService.ENCRYPTION_STATE_NONE) {
+ Log.w(TAG, "Unexpectedly in CryptKeeper even though there is no encryption.");
+ return true; // Unexpected, but fine, I guess...
+ }
+ return state == IMountService.ENCRYPTION_STATE_OK;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to get encryption state properly");
+ return true;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ mValidationComplete = true;
+ if (Boolean.FALSE.equals(result)) {
+ Log.w(TAG, "Incomplete, or corrupted encryption detected. Prompting user to wipe.");
+ mEncryptionGoneBad = true;
+ } else {
+ Log.d(TAG, "Encryption state validated. Proceeding to configure UI");
+ }
+ setupUi();
+ }
+ }
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_UPDATE_PROGRESS:
+ updateProgress();
+ break;
+
+ case MESSAGE_COOLDOWN:
+ cooldown();
+ break;
+
+ case MESSAGE_NOTIFY:
+ notifyUser();
+ break;
+ }
+ }
+ };
+
+ private AudioManager mAudioManager;
+
+ /** @return whether or not this Activity was started for debugging the UI only. */
+ private boolean isDebugView() {
+ return getIntent().hasExtra(EXTRA_FORCE_VIEW);
+ }
+
+ /** @return whether or not this Activity was started for debugging the specific UI view only. */
+ private boolean isDebugView(String viewType /* non-nullable */) {
+ return viewType.equals(getIntent().getStringExtra(EXTRA_FORCE_VIEW));
+ }
+
+ /**
+ * Notify the user that we are awaiting input. Currently this sends an audio alert.
+ */
+ private void notifyUser() {
+ if (mNotificationCountdown > 0) {
+ --mNotificationCountdown;
+ } else if (mAudioManager != null) {
+ try {
+ // Play the standard keypress sound at full volume. This should be available on
+ // every device. We cannot play a ringtone here because media services aren't
+ // available yet. A DTMF-style tone is too soft to be noticed, and might not exist
+ // on tablet devices. The idea is to alert the user that something is needed: this
+ // does not have to be pleasing.
+ mAudioManager.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD, 100);
+ } catch (Exception e) {
+ Log.w(TAG, "notifyUser: Exception while playing sound: " + e);
+ }
+ }
+ // Notify the user again in 5 seconds.
+ mHandler.removeMessages(MESSAGE_NOTIFY);
+ mHandler.sendEmptyMessageDelayed(MESSAGE_NOTIFY, 5 * 1000);
+ }
+
+ /**
+ * Ignore back events after the user has entered the decrypt screen and while the device is
+ * encrypting.
+ */
+ @Override
+ public void onBackPressed() {
+ if (mIgnoreBack)
+ return;
+ super.onBackPressed();
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // If we are not encrypted or encrypting, get out quickly.
+ final String state = SystemProperties.get("vold.decrypt");
+ if (!isDebugView() && ("".equals(state) || DECRYPT_STATE.equals(state))) {
+ // Disable the crypt keeper.
+ PackageManager pm = getPackageManager();
+ ComponentName name = new ComponentName(this, CryptKeeper.class);
+ pm.setComponentEnabledSetting(name, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP);
+ // Typically CryptKeeper is launched as the home app. We didn't
+ // want to be running, so need to finish this activity. We can count
+ // on the activity manager re-launching the new home app upon finishing
+ // this one, since this will leave the activity stack empty.
+ // NOTE: This is really grungy. I think it would be better for the
+ // activity manager to explicitly launch the crypt keeper instead of
+ // home in the situation where we need to decrypt the device
+ finish();
+ return;
+ }
+
+ // Disable the status bar, but do NOT disable back because the user needs a way to go
+ // from keyboard settings and back to the password screen.
+ StatusBarManager sbm = (StatusBarManager) getSystemService(Context.STATUS_BAR_SERVICE);
+ sbm.disable(StatusBarManager.DISABLE_EXPAND
+ | StatusBarManager.DISABLE_NOTIFICATION_ICONS
+ | StatusBarManager.DISABLE_NOTIFICATION_ALERTS
+ | StatusBarManager.DISABLE_SYSTEM_INFO
+ | StatusBarManager.DISABLE_HOME
+ | StatusBarManager.DISABLE_RECENT);
+
+ setAirplaneModeIfNecessary();
+ mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ // Check for (and recover) retained instance data
+ final Object lastInstance = getLastNonConfigurationInstance();
+ if (lastInstance instanceof NonConfigurationInstanceState) {
+ NonConfigurationInstanceState retained = (NonConfigurationInstanceState) lastInstance;
+ mWakeLock = retained.wakelock;
+ Log.d(TAG, "Restoring wakelock from NonConfigurationInstanceState");
+ }
+ }
+
+ /**
+ * Note, we defer the state check and screen setup to onStart() because this will be
+ * re-run if the user clicks the power button (sleeping/waking the screen), and this is
+ * especially important if we were to lose the wakelock for any reason.
+ */
+ @Override
+ public void onStart() {
+ super.onStart();
+ setupUi();
+ }
+
+ /**
+ * Initializes the UI based on the current state of encryption.
+ * This is idempotent - calling repeatedly will simply re-initialize the UI.
+ */
+ private void setupUi() {
+ if (mEncryptionGoneBad || isDebugView(FORCE_VIEW_ERROR)) {
+ setContentView(R.layout.crypt_keeper_progress);
+ showFactoryReset();
+ return;
+ }
+
+ final String progress = SystemProperties.get("vold.encrypt_progress");
+ if (!"".equals(progress) || isDebugView(FORCE_VIEW_PROGRESS)) {
+ setContentView(R.layout.crypt_keeper_progress);
+ encryptionProgressInit();
+ } else if (mValidationComplete || isDebugView(FORCE_VIEW_PASSWORD)) {
+ setContentView(R.layout.crypt_keeper_password_entry);
+ passwordEntryInit();
+ } else if (!mValidationRequested) {
+ // We're supposed to be encrypted, but no validation has been done.
+ new ValidationTask().execute((Void[]) null);
+ mValidationRequested = true;
+ }
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ mHandler.removeMessages(MESSAGE_COOLDOWN);
+ mHandler.removeMessages(MESSAGE_UPDATE_PROGRESS);
+ mHandler.removeMessages(MESSAGE_NOTIFY);
+ }
+
+ /**
+ * Reconfiguring, so propagate the wakelock to the next instance. This runs between onStop()
+ * and onDestroy() and only if we are changing configuration (e.g. rotation). Also clears
+ * mWakeLock so the subsequent call to onDestroy does not release it.
+ */
+ @Override
+ public Object onRetainNonConfigurationInstance() {
+ NonConfigurationInstanceState state = new NonConfigurationInstanceState(mWakeLock);
+ Log.d(TAG, "Handing wakelock off to NonConfigurationInstanceState");
+ mWakeLock = null;
+ return state;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+
+ if (mWakeLock != null) {
+ Log.d(TAG, "Releasing and destroying wakelock");
+ mWakeLock.release();
+ mWakeLock = null;
+ }
+ }
+
+ /**
+ * Start encrypting the device.
+ */
+ private void encryptionProgressInit() {
+ // Accquire a partial wakelock to prevent the device from sleeping. Note
+ // we never release this wakelock as we will be restarted after the device
+ // is encrypted.
+ Log.d(TAG, "Encryption progress screen initializing.");
+ if (mWakeLock == null) {
+ Log.d(TAG, "Acquiring wakelock.");
+ PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
+ mWakeLock.acquire();
+ }
+
+ ((ProgressBar) findViewById(R.id.progress_bar)).setIndeterminate(true);
+ // Ignore all back presses from now, both hard and soft keys.
+ mIgnoreBack = true;
+ // Start the first run of progress manually. This method sets up messages to occur at
+ // repeated intervals.
+ updateProgress();
+ }
+
+ private void showFactoryReset() {
+ // Hide the encryption-bot to make room for the "factory reset" button
+ findViewById(R.id.encroid).setVisibility(View.GONE);
+
+ // Show the reset button, failure text, and a divider
+ final Button button = (Button) findViewById(R.id.factory_reset);
+ button.setVisibility(View.VISIBLE);
+ button.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Factory reset the device.
+ sendBroadcast(new Intent("android.intent.action.MASTER_CLEAR"));
+ }
+ });
+
+ // Alert the user of the failure.
+ ((TextView) findViewById(R.id.title)).setText(R.string.crypt_keeper_failed_title);
+ ((TextView) findViewById(R.id.status)).setText(R.string.crypt_keeper_failed_summary);
+
+ final View view = findViewById(R.id.bottom_divider);
+ // TODO(viki): Why would the bottom divider be missing in certain layouts? Investigate.
+ if (view != null) {
+ view.setVisibility(View.VISIBLE);
+ }
+ }
+
+ private void updateProgress() {
+ final String state = SystemProperties.get("vold.encrypt_progress");
+
+ if ("error_partially_encrypted".equals(state)) {
+ showFactoryReset();
+ return;
+ }
+
+ int progress = 0;
+ try {
+ // Force a 50% progress state when debugging the view.
+ progress = isDebugView() ? 50 : Integer.parseInt(state);
+ } catch (Exception e) {
+ Log.w(TAG, "Error parsing progress: " + e.toString());
+ }
+
+ final CharSequence status = getText(R.string.crypt_keeper_setup_description);
+ Log.v(TAG, "Encryption progress: " + progress);
+ final TextView tv = (TextView) findViewById(R.id.status);
+ if (tv != null) {
+ tv.setText(TextUtils.expandTemplate(status, Integer.toString(progress)));
+ }
+ // Check the progress every 5 seconds
+ mHandler.removeMessages(MESSAGE_UPDATE_PROGRESS);
+ mHandler.sendEmptyMessageDelayed(MESSAGE_UPDATE_PROGRESS, 5000);
+ }
+
+ /** Disable password input for a while to force the user to waste time between retries */
+ private void cooldown() {
+ final TextView status = (TextView) findViewById(R.id.status);
+
+ if (mCooldown <= 0) {
+ // Re-enable the password entry and back presses.
+ mPasswordEntry.setEnabled(true);
+ mIgnoreBack = false;
+ status.setText(R.string.enter_password);
+ } else {
+ CharSequence template = getText(R.string.crypt_keeper_cooldown);
+ status.setText(TextUtils.expandTemplate(template, Integer.toString(mCooldown)));
+
+ mCooldown--;
+ mHandler.removeMessages(MESSAGE_COOLDOWN);
+ mHandler.sendEmptyMessageDelayed(MESSAGE_COOLDOWN, 1000); // Tick every second
+ }
+ }
+
+ private void passwordEntryInit() {
+ mPasswordEntry = (EditText) findViewById(R.id.passwordEntry);
+ mPasswordEntry.setOnEditorActionListener(this);
+ mPasswordEntry.requestFocus();
+ // Become quiet when the user interacts with the Edit text screen.
+ mPasswordEntry.setOnKeyListener(this);
+ mPasswordEntry.setOnTouchListener(this);
+ mPasswordEntry.addTextChangedListener(this);
+
+ // Disable the Emergency call button if the device has no voice telephone capability
+ final TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
+ if (!tm.isVoiceCapable()) {
+ final View emergencyCall = findViewById(R.id.emergencyCallButton);
+ if (emergencyCall != null) {
+ Log.d(TAG, "Removing the emergency Call button");
+ emergencyCall.setVisibility(View.GONE);
+ }
+ }
+
+ final View imeSwitcher = findViewById(R.id.switch_ime_button);
+ final InputMethodManager imm = (InputMethodManager) getSystemService(
+ Context.INPUT_METHOD_SERVICE);
+ if (imeSwitcher != null && hasMultipleEnabledIMEsOrSubtypes(imm, false)) {
+ imeSwitcher.setVisibility(View.VISIBLE);
+ imeSwitcher.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ imm.showInputMethodPicker();
+ }
+ });
+ }
+
+ // We want to keep the screen on while waiting for input. In minimal boot mode, the device
+ // is completely non-functional, and we want the user to notice the device and enter a
+ // password.
+ if (mWakeLock == null) {
+ Log.d(TAG, "Acquiring wakelock.");
+ final PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+ if (pm != null) {
+ mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
+ mWakeLock.acquire();
+ }
+ }
+ // Asynchronously throw up the IME, since there are issues with requesting it to be shown
+ // immediately.
+ mHandler.postDelayed(new Runnable() {
+ @Override public void run() {
+ imm.showSoftInputUnchecked(0, null);
+ }
+ }, 0);
+
+ updateEmergencyCallButtonState();
+ // Notify the user in 120 seconds that we are waiting for him to enter the password.
+ mHandler.removeMessages(MESSAGE_NOTIFY);
+ mHandler.sendEmptyMessageDelayed(MESSAGE_NOTIFY, 120 * 1000);
+ }
+
+ /**
+ * Method adapted from com.android.inputmethod.latin.Utils
+ *
+ * @param imm The input method manager
+ * @param shouldIncludeAuxiliarySubtypes
+ * @return true if we have multiple IMEs to choose from
+ */
+ private boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm,
+ final boolean shouldIncludeAuxiliarySubtypes) {
+ final List enabledImis = imm.getEnabledInputMethodList();
+
+ // Number of the filtered IMEs
+ int filteredImisCount = 0;
+
+ for (InputMethodInfo imi : enabledImis) {
+ // We can return true immediately after we find two or more filtered IMEs.
+ if (filteredImisCount > 1) return true;
+ final List subtypes =
+ imm.getEnabledInputMethodSubtypeList(imi, true);
+ // IMEs that have no subtypes should be counted.
+ if (subtypes.isEmpty()) {
+ ++filteredImisCount;
+ continue;
+ }
+
+ int auxCount = 0;
+ for (InputMethodSubtype subtype : subtypes) {
+ if (subtype.isAuxiliary()) {
+ ++auxCount;
+ }
+ }
+ final int nonAuxCount = subtypes.size() - auxCount;
+
+ // IMEs that have one or more non-auxiliary subtypes should be counted.
+ // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary
+ // subtypes should be counted as well.
+ if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) {
+ ++filteredImisCount;
+ continue;
+ }
+ }
+
+ return filteredImisCount > 1
+ // imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's enabled
+ // input method subtype (The current IME should be LatinIME.)
+ || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1;
+ }
+
+ private IMountService getMountService() {
+ final IBinder service = ServiceManager.getService("mount");
+ if (service != null) {
+ return IMountService.Stub.asInterface(service);
+ }
+ return null;
+ }
+
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if (actionId == EditorInfo.IME_NULL || actionId == EditorInfo.IME_ACTION_DONE) {
+ // Get the password
+ final String password = v.getText().toString();
+
+ if (TextUtils.isEmpty(password)) {
+ return true;
+ }
+
+ // Now that we have the password clear the password field.
+ v.setText(null);
+
+ // Disable the password entry and back keypress while checking the password. These
+ // we either be re-enabled if the password was wrong or after the cooldown period.
+ mPasswordEntry.setEnabled(false);
+ mIgnoreBack = true;
+
+ Log.d(TAG, "Attempting to send command to decrypt");
+ new DecryptTask().execute(password);
+
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Set airplane mode on the device if it isn't an LTE device.
+ * Full story: In minimal boot mode, we cannot save any state. In particular, we cannot save
+ * any incoming SMS's. So SMSs that are received here will be silently dropped to the floor.
+ * That is bad. Also, we cannot receive any telephone calls in this state. So to avoid
+ * both these problems, we turn the radio off. However, on certain networks turning on and
+ * off the radio takes a long time. In such cases, we are better off leaving the radio
+ * running so the latency of an E911 call is short.
+ * The behavior after this is:
+ * 1. Emergency dialing: the emergency dialer has logic to force the device out of
+ * airplane mode and restart the radio.
+ * 2. Full boot: we read the persistent settings from the previous boot and restore the
+ * radio to whatever it was before it restarted. This also happens when rebooting a
+ * phone that has no encryption.
+ */
+ private final void setAirplaneModeIfNecessary() {
+ final boolean isLteDevice =
+ TelephonyManager.getDefault().getLteOnCdmaMode() == PhoneConstants.LTE_ON_CDMA_TRUE;
+ if (!isLteDevice) {
+ Log.d(TAG, "Going into airplane mode.");
+ Settings.Global.putInt(getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
+ final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ intent.putExtra("state", true);
+ sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
+ }
+
+ /**
+ * Code to update the state of, and handle clicks from, the "Emergency call" button.
+ *
+ * This code is mostly duplicated from the corresponding code in
+ * LockPatternUtils and LockPatternKeyguardView under frameworks/base.
+ */
+ private void updateEmergencyCallButtonState() {
+ final Button emergencyCall = (Button) findViewById(R.id.emergencyCallButton);
+ // The button isn't present at all in some configurations.
+ if (emergencyCall == null)
+ return;
+
+ if (isEmergencyCallCapable()) {
+ emergencyCall.setVisibility(View.VISIBLE);
+ emergencyCall.setOnClickListener(new View.OnClickListener() {
+ @Override
+
+ public void onClick(View v) {
+ takeEmergencyCallAction();
+ }
+ });
+ } else {
+ emergencyCall.setVisibility(View.GONE);
+ return;
+ }
+
+ final int newState = TelephonyManager.getDefault().getCallState();
+ int textId;
+ if (newState == TelephonyManager.CALL_STATE_OFFHOOK) {
+ // Show "return to call" text and show phone icon
+ textId = R.string.cryptkeeper_return_to_call;
+ final int phoneCallIcon = R.drawable.stat_sys_phone_call;
+ emergencyCall.setCompoundDrawablesWithIntrinsicBounds(phoneCallIcon, 0, 0, 0);
+ } else {
+ textId = R.string.cryptkeeper_emergency_call;
+ final int emergencyIcon = R.drawable.ic_emergency;
+ emergencyCall.setCompoundDrawablesWithIntrinsicBounds(emergencyIcon, 0, 0, 0);
+ }
+ emergencyCall.setText(textId);
+ }
+
+ private boolean isEmergencyCallCapable() {
+ return getResources().getBoolean(com.android.internal.R.bool.config_voice_capable);
+ }
+
+ private void takeEmergencyCallAction() {
+ if (TelephonyManager.getDefault().getCallState() == TelephonyManager.CALL_STATE_OFFHOOK) {
+ resumeCall();
+ } else {
+ launchEmergencyDialer();
+ }
+ }
+
+ private void resumeCall() {
+ final ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
+ if (phone != null) {
+ try {
+ phone.showCallScreen();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony service: " + e);
+ }
+ }
+ }
+
+ private void launchEmergencyDialer() {
+ final Intent intent = new Intent(ACTION_EMERGENCY_DIAL);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ startActivity(intent);
+ }
+
+ /**
+ * Listen to key events so we can disable sounds when we get a keyinput in EditText.
+ */
+ private void delayAudioNotification() {
+ mNotificationCountdown = 20;
+ }
+
+ @Override
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ delayAudioNotification();
+ return false;
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ delayAudioNotification();
+ return false;
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ return;
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ delayAudioNotification();
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ return;
+ }
+}
diff --git a/src/com/android/settings/CryptKeeperConfirm.java b/src/com/android/settings/CryptKeeperConfirm.java
new file mode 100644
index 0000000..9a7c21b
--- /dev/null
+++ b/src/com/android/settings/CryptKeeperConfirm.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.StatusBarManager;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.ServiceManager;
+import android.os.storage.IMountService;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+
+public class CryptKeeperConfirm extends Fragment {
+
+ public static class Blank extends Activity {
+ private Handler mHandler = new Handler();
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.crypt_keeper_blank);
+
+ if (Utils.isMonkeyRunning()) {
+ finish();
+ }
+
+ StatusBarManager sbm = (StatusBarManager) getSystemService(Context.STATUS_BAR_SERVICE);
+ sbm.disable(StatusBarManager.DISABLE_EXPAND
+ | StatusBarManager.DISABLE_NOTIFICATION_ICONS
+ | StatusBarManager.DISABLE_NOTIFICATION_ALERTS
+ | StatusBarManager.DISABLE_SYSTEM_INFO
+ | StatusBarManager.DISABLE_HOME
+ | StatusBarManager.DISABLE_RECENT
+ | StatusBarManager.DISABLE_BACK);
+
+ // Post a delayed message in 700 milliseconds to enable encryption.
+ // NOTE: The animation on this activity is set for 500 milliseconds
+ // I am giving it a little extra time to complete.
+ mHandler.postDelayed(new Runnable() {
+ public void run() {
+ IBinder service = ServiceManager.getService("mount");
+ if (service == null) {
+ Log.e("CryptKeeper", "Failed to find the mount service");
+ finish();
+ return;
+ }
+
+ IMountService mountService = IMountService.Stub.asInterface(service);
+ try {
+ Bundle args = getIntent().getExtras();
+ mountService.encryptStorage(args.getString("password"));
+ } catch (Exception e) {
+ Log.e("CryptKeeper", "Error while encrypting...", e);
+ }
+ }
+ }, 700);
+ }
+ }
+
+ private View mContentView;
+ private Button mFinalButton;
+ private Button.OnClickListener mFinalClickListener = new Button.OnClickListener() {
+
+ public void onClick(View v) {
+ if (Utils.isMonkeyRunning()) {
+ return;
+ }
+
+ Intent intent = new Intent(getActivity(), Blank.class);
+ intent.putExtras(getArguments());
+
+ startActivity(intent);
+ }
+ };
+
+ private void establishFinalConfirmationState() {
+ mFinalButton = (Button) mContentView.findViewById(R.id.execute_encrypt);
+ mFinalButton.setOnClickListener(mFinalClickListener);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ mContentView = inflater.inflate(R.layout.crypt_keeper_confirm, null);
+ establishFinalConfirmationState();
+ return mContentView;
+ }
+}
diff --git a/src/com/android/settings/CryptKeeperSettings.java b/src/com/android/settings/CryptKeeperSettings.java
new file mode 100644
index 0000000..58d97a8
--- /dev/null
+++ b/src/com/android/settings/CryptKeeperSettings.java
@@ -0,0 +1,206 @@
+/*
+ * 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.android.settings;
+
+import com.android.internal.widget.LockPatternUtils;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Fragment;
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.os.BatteryManager;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+
+public class CryptKeeperSettings extends Fragment {
+ private static final String TAG = "CryptKeeper";
+
+ private static final int KEYGUARD_REQUEST = 55;
+
+ // This is the minimum acceptable password quality. If the current password quality is
+ // lower than this, encryption should not be activated.
+ static final int MIN_PASSWORD_QUALITY = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+
+ // Minimum battery charge level (in percent) to launch encryption. If the battery charge is
+ // lower than this, encryption should not be activated.
+ private static final int MIN_BATTERY_LEVEL = 80;
+
+ private View mContentView;
+ private Button mInitiateButton;
+ private View mPowerWarning;
+ private View mBatteryWarning;
+ private IntentFilter mIntentFilter;
+
+ private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
+ final int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
+ final int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
+ final int invalidCharger = intent.getIntExtra(
+ BatteryManager.EXTRA_INVALID_CHARGER, 0);
+
+ final boolean levelOk = level >= MIN_BATTERY_LEVEL;
+ final boolean pluggedOk =
+ ((plugged & BatteryManager.BATTERY_PLUGGED_ANY) != 0) &&
+ invalidCharger == 0;
+
+ // Update UI elements based on power/battery status
+ mInitiateButton.setEnabled(levelOk && pluggedOk);
+ mPowerWarning.setVisibility(pluggedOk ? View.GONE : View.VISIBLE );
+ mBatteryWarning.setVisibility(levelOk ? View.GONE : View.VISIBLE);
+ }
+ }
+ };
+
+ /**
+ * If the user clicks to begin the reset sequence, we next require a
+ * keyguard confirmation if the user has currently enabled one. If there
+ * is no keyguard available, we prompt the user to set a password.
+ */
+ private Button.OnClickListener mInitiateListener = new Button.OnClickListener() {
+
+ public void onClick(View v) {
+ if (!runKeyguardConfirmation(KEYGUARD_REQUEST)) {
+ // TODO replace (or follow) this dialog with an explicit launch into password UI
+ new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.crypt_keeper_dialog_need_password_title)
+ .setIconAttribute(android.R.attr.alertDialogIcon)
+ .setMessage(R.string.crypt_keeper_dialog_need_password_message)
+ .setPositiveButton(android.R.string.ok, null)
+ .create()
+ .show();
+ }
+ }
+ };
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
+ mContentView = inflater.inflate(R.layout.crypt_keeper_settings, null);
+
+ mIntentFilter = new IntentFilter();
+ mIntentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
+
+ mInitiateButton = (Button) mContentView.findViewById(R.id.initiate_encrypt);
+ mInitiateButton.setOnClickListener(mInitiateListener);
+ mInitiateButton.setEnabled(false);
+
+ mPowerWarning = mContentView.findViewById(R.id.warning_unplugged);
+ mBatteryWarning = mContentView.findViewById(R.id.warning_low_charge);
+
+ return mContentView;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ getActivity().registerReceiver(mIntentReceiver, mIntentFilter);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ getActivity().unregisterReceiver(mIntentReceiver);
+ }
+
+ /**
+ * If encryption is already started, and this launched via a "start encryption" intent,
+ * then exit immediately - it's already up and running, so there's no point in "starting" it.
+ */
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ Activity activity = getActivity();
+ Intent intent = activity.getIntent();
+ if (DevicePolicyManager.ACTION_START_ENCRYPTION.equals(intent.getAction())) {
+ DevicePolicyManager dpm = (DevicePolicyManager)
+ activity.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ if (dpm != null) {
+ int status = dpm.getStorageEncryptionStatus();
+ if (status != DevicePolicyManager.ENCRYPTION_STATUS_INACTIVE) {
+ // There is nothing to do here, so simply finish() (which returns to caller)
+ activity.finish();
+ }
+ }
+ }
+ }
+
+ /**
+ * Keyguard validation is run using the standard {@link ConfirmLockPattern}
+ * component as a subactivity
+ * @param request the request code to be returned once confirmation finishes
+ * @return true if confirmation launched
+ */
+ private boolean runKeyguardConfirmation(int request) {
+ // 1. Confirm that we have a sufficient PIN/Password to continue
+ LockPatternUtils lockPatternUtils = new LockPatternUtils(getActivity());
+ int quality = lockPatternUtils.getActivePasswordQuality();
+ if (quality == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK
+ && lockPatternUtils.isLockPasswordEnabled()) {
+ // Use the alternate as the quality. We expect this to be
+ // PASSWORD_QUALITY_SOMETHING(pattern) or PASSWORD_QUALITY_NUMERIC(PIN).
+ quality = lockPatternUtils.getKeyguardStoredPasswordQuality();
+ }
+ if (quality < MIN_PASSWORD_QUALITY) {
+ return false;
+ }
+ // 2. Ask the user to confirm the current PIN/Password
+ Resources res = getActivity().getResources();
+ return new ChooseLockSettingsHelper(getActivity(), this)
+ .launchConfirmationActivity(request,
+ res.getText(R.string.master_clear_gesture_prompt),
+ res.getText(R.string.master_clear_gesture_explanation));
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ if (requestCode != KEYGUARD_REQUEST) {
+ return;
+ }
+
+ // If the user entered a valid keyguard trace, present the final
+ // confirmation prompt; otherwise, go back to the initial state.
+ if (resultCode == Activity.RESULT_OK && data != null) {
+ String password = data.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
+ if (!TextUtils.isEmpty(password)) {
+ showFinalConfirmation(password);
+ }
+ }
+ }
+
+ private void showFinalConfirmation(String password) {
+ Preference preference = new Preference(getActivity());
+ preference.setFragment(CryptKeeperConfirm.class.getName());
+ preference.setTitle(R.string.crypt_keeper_confirm_title);
+ preference.getExtras().putString("password", password);
+ ((PreferenceActivity) getActivity()).onPreferenceStartFragment(null, preference);
+ }
+}
diff --git a/src/com/android/settings/DataUsageSummary.java b/src/com/android/settings/DataUsageSummary.java
new file mode 100644
index 0000000..8dc3d53
--- /dev/null
+++ b/src/com/android/settings/DataUsageSummary.java
@@ -0,0 +1,2387 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import static android.net.ConnectivityManager.TYPE_ETHERNET;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.ConnectivityManager.TYPE_WIMAX;
+import static android.net.NetworkPolicy.LIMIT_DISABLED;
+import static android.net.NetworkPolicy.WARNING_DISABLED;
+import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
+import static android.net.NetworkPolicyManager.POLICY_NONE;
+import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
+import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
+import static android.net.NetworkPolicyManager.computeNextCycleBoundary;
+import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
+import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
+import static android.net.NetworkTemplate.MATCH_MOBILE_ALL;
+import static android.net.NetworkTemplate.MATCH_WIFI;
+import static android.net.NetworkTemplate.buildTemplateEthernet;
+import static android.net.NetworkTemplate.buildTemplateMobile3gLower;
+import static android.net.NetworkTemplate.buildTemplateMobile4g;
+import static android.net.NetworkTemplate.buildTemplateMobileAll;
+import static android.net.NetworkTemplate.buildTemplateWifiWildcard;
+import static android.net.TrafficStats.GB_IN_BYTES;
+import static android.net.TrafficStats.MB_IN_BYTES;
+import static android.net.TrafficStats.UID_REMOVED;
+import static android.net.TrafficStats.UID_TETHERING;
+import static android.telephony.TelephonyManager.SIM_STATE_READY;
+import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
+import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static com.android.internal.util.Preconditions.checkNotNull;
+import static com.android.settings.Utils.prepareCustomPreferencesList;
+
+import android.animation.LayoutTransition;
+import android.app.ActivityManager;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.Loader;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.ConnectivityManager;
+import android.net.INetworkPolicyManager;
+import android.net.INetworkStatsService;
+import android.net.INetworkStatsSession;
+import android.net.NetworkPolicy;
+import android.net.NetworkPolicyManager;
+import android.net.NetworkStats;
+import android.net.NetworkStatsHistory;
+import android.net.NetworkTemplate;
+import android.net.TrafficStats;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.INetworkManagementService;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.text.format.Formatter;
+import android.text.format.Time;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
+import android.widget.BaseAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.NumberPicker;
+import android.widget.ProgressBar;
+import android.widget.Spinner;
+import android.widget.Switch;
+import android.widget.TabHost;
+import android.widget.TabHost.OnTabChangeListener;
+import android.widget.TabHost.TabContentFactory;
+import android.widget.TabHost.TabSpec;
+import android.widget.TabWidget;
+import android.widget.TextView;
+
+import com.android.internal.telephony.PhoneConstants;
+import com.android.settings.drawable.InsetBoundsDrawable;
+import com.android.settings.net.ChartData;
+import com.android.settings.net.ChartDataLoader;
+import com.android.settings.net.DataUsageMeteredSettings;
+import com.android.settings.net.NetworkPolicyEditor;
+import com.android.settings.net.SummaryForAllUidLoader;
+import com.android.settings.net.UidDetail;
+import com.android.settings.net.UidDetailProvider;
+import com.android.settings.widget.ChartDataUsageView;
+import com.android.settings.widget.ChartDataUsageView.DataUsageChartListener;
+import com.android.settings.widget.PieChartView;
+import com.google.android.collect.Lists;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import libcore.util.Objects;
+
+/**
+ * Panel showing data usage history across various networks, including options
+ * to inspect based on usage cycle and control through {@link NetworkPolicy}.
+ */
+public class DataUsageSummary extends Fragment {
+ private static final String TAG = "DataUsage";
+ private static final boolean LOGD = false;
+
+ // TODO: remove this testing code
+ private static final boolean TEST_ANIM = false;
+ private static final boolean TEST_RADIOS = false;
+
+ private static final String TEST_RADIOS_PROP = "test.radios";
+ private static final String TEST_SUBSCRIBER_PROP = "test.subscriberid";
+
+ private static final String TAB_3G = "3g";
+ private static final String TAB_4G = "4g";
+ private static final String TAB_MOBILE = "mobile";
+ private static final String TAB_WIFI = "wifi";
+ private static final String TAB_ETHERNET = "ethernet";
+
+ private static final String TAG_CONFIRM_DATA_DISABLE = "confirmDataDisable";
+ private static final String TAG_CONFIRM_DATA_ROAMING = "confirmDataRoaming";
+ private static final String TAG_CONFIRM_LIMIT = "confirmLimit";
+ private static final String TAG_CYCLE_EDITOR = "cycleEditor";
+ private static final String TAG_WARNING_EDITOR = "warningEditor";
+ private static final String TAG_LIMIT_EDITOR = "limitEditor";
+ private static final String TAG_CONFIRM_RESTRICT = "confirmRestrict";
+ private static final String TAG_DENIED_RESTRICT = "deniedRestrict";
+ private static final String TAG_CONFIRM_APP_RESTRICT = "confirmAppRestrict";
+ private static final String TAG_CONFIRM_AUTO_SYNC_CHANGE = "confirmAutoSyncChange";
+ private static final String TAG_APP_DETAILS = "appDetails";
+
+ private static final int LOADER_CHART_DATA = 2;
+ private static final int LOADER_SUMMARY = 3;
+
+ private INetworkManagementService mNetworkService;
+ private INetworkStatsService mStatsService;
+ private NetworkPolicyManager mPolicyManager;
+ private ConnectivityManager mConnService;
+
+ private INetworkStatsSession mStatsSession;
+
+ private static final String PREF_FILE = "data_usage";
+ private static final String PREF_SHOW_WIFI = "show_wifi";
+ private static final String PREF_SHOW_ETHERNET = "show_ethernet";
+
+ private SharedPreferences mPrefs;
+
+ private TabHost mTabHost;
+ private ViewGroup mTabsContainer;
+ private TabWidget mTabWidget;
+ private ListView mListView;
+ private DataUsageAdapter mAdapter;
+
+ /** Distance to inset content from sides, when needed. */
+ private int mInsetSide = 0;
+
+ private ViewGroup mHeader;
+
+ private ViewGroup mNetworkSwitchesContainer;
+ private LinearLayout mNetworkSwitches;
+ private Switch mDataEnabled;
+ private View mDataEnabledView;
+ private CheckBox mDisableAtLimit;
+ private View mDisableAtLimitView;
+
+ private View mCycleView;
+ private Spinner mCycleSpinner;
+ private CycleAdapter mCycleAdapter;
+
+ private ChartDataUsageView mChart;
+ private TextView mUsageSummary;
+ private TextView mEmpty;
+
+ private View mAppDetail;
+ private ImageView mAppIcon;
+ private ViewGroup mAppTitles;
+ private PieChartView mAppPieChart;
+ private TextView mAppForeground;
+ private TextView mAppBackground;
+ private Button mAppSettings;
+
+ private LinearLayout mAppSwitches;
+ private CheckBox mAppRestrict;
+ private View mAppRestrictView;
+
+ private boolean mShowWifi = false;
+ private boolean mShowEthernet = false;
+
+ private NetworkTemplate mTemplate;
+ private ChartData mChartData;
+
+ private AppItem mCurrentApp = null;
+
+ private Intent mAppSettingsIntent;
+
+ private NetworkPolicyEditor mPolicyEditor;
+
+ private String mCurrentTab = null;
+ private String mIntentTab = null;
+
+ private MenuItem mMenuDataRoaming;
+ private MenuItem mMenuRestrictBackground;
+ private MenuItem mMenuAutoSync;
+
+ /** Flag used to ignore listeners during binding. */
+ private boolean mBinding;
+
+ private UidDetailProvider mUidDetailProvider;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ final Context context = getActivity();
+
+ mNetworkService = INetworkManagementService.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
+ mStatsService = INetworkStatsService.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
+ mPolicyManager = NetworkPolicyManager.from(context);
+ mConnService = ConnectivityManager.from(context);
+
+ mPrefs = getActivity().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
+
+ mPolicyEditor = new NetworkPolicyEditor(mPolicyManager);
+ mPolicyEditor.read();
+
+ mShowWifi = mPrefs.getBoolean(PREF_SHOW_WIFI, false);
+ mShowEthernet = mPrefs.getBoolean(PREF_SHOW_ETHERNET, false);
+
+ // override preferences when no mobile radio
+ if (!hasReadyMobileRadio(context)) {
+ mShowWifi = hasWifiRadio(context);
+ mShowEthernet = hasEthernet(context);
+ }
+
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+
+ final Context context = inflater.getContext();
+ final View view = inflater.inflate(R.layout.data_usage_summary, container, false);
+
+ mUidDetailProvider = new UidDetailProvider(context);
+
+ try {
+ mStatsSession = mStatsService.openSession();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+
+ mTabHost = (TabHost) view.findViewById(android.R.id.tabhost);
+ mTabsContainer = (ViewGroup) view.findViewById(R.id.tabs_container);
+ mTabWidget = (TabWidget) view.findViewById(android.R.id.tabs);
+ mListView = (ListView) view.findViewById(android.R.id.list);
+
+ // decide if we need to manually inset our content, or if we should rely
+ // on parent container for inset.
+ final boolean shouldInset = mListView.getScrollBarStyle()
+ == View.SCROLLBARS_OUTSIDE_OVERLAY;
+ if (shouldInset) {
+ mInsetSide = view.getResources().getDimensionPixelOffset(
+ com.android.internal.R.dimen.preference_fragment_padding_side);
+ } else {
+ mInsetSide = 0;
+ }
+
+ // adjust padding around tabwidget as needed
+ prepareCustomPreferencesList(container, view, mListView, true);
+
+ mTabHost.setup();
+ mTabHost.setOnTabChangedListener(mTabListener);
+
+ mHeader = (ViewGroup) inflater.inflate(R.layout.data_usage_header, mListView, false);
+ mHeader.setClickable(true);
+
+ mListView.addHeaderView(mHeader, null, true);
+ mListView.setItemsCanFocus(true);
+
+ if (mInsetSide > 0) {
+ // inset selector and divider drawables
+ insetListViewDrawables(mListView, mInsetSide);
+ mHeader.setPadding(mInsetSide, 0, mInsetSide, 0);
+ }
+
+ {
+ // bind network switches
+ mNetworkSwitchesContainer = (ViewGroup) mHeader.findViewById(
+ R.id.network_switches_container);
+ mNetworkSwitches = (LinearLayout) mHeader.findViewById(R.id.network_switches);
+
+ mDataEnabled = new Switch(inflater.getContext());
+ mDataEnabledView = inflatePreference(inflater, mNetworkSwitches, mDataEnabled);
+ mDataEnabled.setOnCheckedChangeListener(mDataEnabledListener);
+ mNetworkSwitches.addView(mDataEnabledView);
+
+ mDisableAtLimit = new CheckBox(inflater.getContext());
+ mDisableAtLimit.setClickable(false);
+ mDisableAtLimit.setFocusable(false);
+ mDisableAtLimitView = inflatePreference(inflater, mNetworkSwitches, mDisableAtLimit);
+ mDisableAtLimitView.setClickable(true);
+ mDisableAtLimitView.setFocusable(true);
+ mDisableAtLimitView.setOnClickListener(mDisableAtLimitListener);
+ mNetworkSwitches.addView(mDisableAtLimitView);
+ }
+
+ // bind cycle dropdown
+ mCycleView = mHeader.findViewById(R.id.cycles);
+ mCycleSpinner = (Spinner) mCycleView.findViewById(R.id.cycles_spinner);
+ mCycleAdapter = new CycleAdapter(context);
+ mCycleSpinner.setAdapter(mCycleAdapter);
+ mCycleSpinner.setOnItemSelectedListener(mCycleListener);
+
+ mChart = (ChartDataUsageView) mHeader.findViewById(R.id.chart);
+ mChart.setListener(mChartListener);
+ mChart.bindNetworkPolicy(null);
+
+ {
+ // bind app detail controls
+ mAppDetail = mHeader.findViewById(R.id.app_detail);
+ mAppIcon = (ImageView) mAppDetail.findViewById(R.id.app_icon);
+ mAppTitles = (ViewGroup) mAppDetail.findViewById(R.id.app_titles);
+ mAppPieChart = (PieChartView) mAppDetail.findViewById(R.id.app_pie_chart);
+ mAppForeground = (TextView) mAppDetail.findViewById(R.id.app_foreground);
+ mAppBackground = (TextView) mAppDetail.findViewById(R.id.app_background);
+ mAppSwitches = (LinearLayout) mAppDetail.findViewById(R.id.app_switches);
+
+ mAppSettings = (Button) mAppDetail.findViewById(R.id.app_settings);
+ mAppSettings.setOnClickListener(mAppSettingsListener);
+
+ mAppRestrict = new CheckBox(inflater.getContext());
+ mAppRestrict.setClickable(false);
+ mAppRestrict.setFocusable(false);
+ mAppRestrictView = inflatePreference(inflater, mAppSwitches, mAppRestrict);
+ mAppRestrictView.setClickable(true);
+ mAppRestrictView.setFocusable(true);
+ mAppRestrictView.setOnClickListener(mAppRestrictListener);
+ mAppSwitches.addView(mAppRestrictView);
+ }
+
+ mUsageSummary = (TextView) mHeader.findViewById(R.id.usage_summary);
+ mEmpty = (TextView) mHeader.findViewById(android.R.id.empty);
+
+ mAdapter = new DataUsageAdapter(mUidDetailProvider, mInsetSide);
+ mListView.setOnItemClickListener(mListListener);
+ mListView.setAdapter(mAdapter);
+
+ return view;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ // pick default tab based on incoming intent
+ final Intent intent = getActivity().getIntent();
+ mIntentTab = computeTabFromIntent(intent);
+
+ // this kicks off chain reaction which creates tabs, binds the body to
+ // selected network, and binds chart, cycles and detail list.
+ updateTabs();
+
+ // kick off background task to update stats
+ new AsyncTask() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ try {
+ // wait a few seconds before kicking off
+ Thread.sleep(2 * DateUtils.SECOND_IN_MILLIS);
+ mStatsService.forceUpdate();
+ } catch (InterruptedException e) {
+ } catch (RemoteException e) {
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ if (isAdded()) {
+ updateBody();
+ }
+ }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ inflater.inflate(R.menu.data_usage, menu);
+ }
+
+ @Override
+ public void onPrepareOptionsMenu(Menu menu) {
+ final Context context = getActivity();
+ final boolean appDetailMode = isAppDetailMode();
+ final boolean isOwner = ActivityManager.getCurrentUser() == UserHandle.USER_OWNER;
+
+ mMenuDataRoaming = menu.findItem(R.id.data_usage_menu_roaming);
+ mMenuDataRoaming.setVisible(hasReadyMobileRadio(context) && !appDetailMode);
+ mMenuDataRoaming.setChecked(getDataRoaming());
+
+ mMenuRestrictBackground = menu.findItem(R.id.data_usage_menu_restrict_background);
+ mMenuRestrictBackground.setVisible(hasReadyMobileRadio(context) && !appDetailMode);
+ mMenuRestrictBackground.setChecked(mPolicyManager.getRestrictBackground());
+ mMenuRestrictBackground.setVisible(isOwner);
+
+ mMenuAutoSync = menu.findItem(R.id.data_usage_menu_auto_sync);
+ mMenuAutoSync.setChecked(ContentResolver.getMasterSyncAutomatically());
+ mMenuAutoSync.setVisible(!appDetailMode);
+
+ final MenuItem split4g = menu.findItem(R.id.data_usage_menu_split_4g);
+ split4g.setVisible(hasReadyMobile4gRadio(context) && isOwner && !appDetailMode);
+ split4g.setChecked(isMobilePolicySplit());
+
+ final MenuItem showWifi = menu.findItem(R.id.data_usage_menu_show_wifi);
+ if (hasWifiRadio(context) && hasReadyMobileRadio(context)) {
+ showWifi.setVisible(!appDetailMode);
+ showWifi.setChecked(mShowWifi);
+ } else {
+ showWifi.setVisible(false);
+ }
+
+ final MenuItem showEthernet = menu.findItem(R.id.data_usage_menu_show_ethernet);
+ if (hasEthernet(context) && hasReadyMobileRadio(context)) {
+ showEthernet.setVisible(!appDetailMode);
+ showEthernet.setChecked(mShowEthernet);
+ } else {
+ showEthernet.setVisible(false);
+ }
+
+ final MenuItem metered = menu.findItem(R.id.data_usage_menu_metered);
+ if (hasReadyMobileRadio(context) || hasWifiRadio(context)) {
+ metered.setVisible(!appDetailMode);
+ } else {
+ metered.setVisible(false);
+ }
+
+ final MenuItem help = menu.findItem(R.id.data_usage_menu_help);
+ String helpUrl;
+ if (!TextUtils.isEmpty(helpUrl = getResources().getString(R.string.help_url_data_usage))) {
+ Intent helpIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(helpUrl));
+ helpIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ help.setIntent(helpIntent);
+ help.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+ } else {
+ help.setVisible(false);
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.data_usage_menu_roaming: {
+ final boolean dataRoaming = !item.isChecked();
+ if (dataRoaming) {
+ ConfirmDataRoamingFragment.show(this);
+ } else {
+ // no confirmation to disable roaming
+ setDataRoaming(false);
+ }
+ return true;
+ }
+ case R.id.data_usage_menu_restrict_background: {
+ final boolean restrictBackground = !item.isChecked();
+ if (restrictBackground) {
+ ConfirmRestrictFragment.show(this);
+ } else {
+ // no confirmation to drop restriction
+ setRestrictBackground(false);
+ }
+ return true;
+ }
+ case R.id.data_usage_menu_split_4g: {
+ final boolean mobileSplit = !item.isChecked();
+ setMobilePolicySplit(mobileSplit);
+ item.setChecked(isMobilePolicySplit());
+ updateTabs();
+ return true;
+ }
+ case R.id.data_usage_menu_show_wifi: {
+ mShowWifi = !item.isChecked();
+ mPrefs.edit().putBoolean(PREF_SHOW_WIFI, mShowWifi).apply();
+ item.setChecked(mShowWifi);
+ updateTabs();
+ return true;
+ }
+ case R.id.data_usage_menu_show_ethernet: {
+ mShowEthernet = !item.isChecked();
+ mPrefs.edit().putBoolean(PREF_SHOW_ETHERNET, mShowEthernet).apply();
+ item.setChecked(mShowEthernet);
+ updateTabs();
+ return true;
+ }
+ case R.id.data_usage_menu_metered: {
+ final PreferenceActivity activity = (PreferenceActivity) getActivity();
+ activity.startPreferencePanel(DataUsageMeteredSettings.class.getCanonicalName(), null,
+ R.string.data_usage_metered_title, null, this, 0);
+ return true;
+ }
+ case R.id.data_usage_menu_auto_sync: {
+ if (ActivityManager.isUserAMonkey()) {
+ Log.d("SyncState", "ignoring monkey's attempt to flip global sync state");
+ } else {
+ ConfirmAutoSyncChangeFragment.show(this, !item.isChecked());
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void onDestroy() {
+ mDataEnabledView = null;
+ mDisableAtLimitView = null;
+
+ mUidDetailProvider.clearCache();
+ mUidDetailProvider = null;
+
+ TrafficStats.closeQuietly(mStatsSession);
+
+ if (this.isRemoving()) {
+ getFragmentManager()
+ .popBackStack(TAG_APP_DETAILS, FragmentManager.POP_BACK_STACK_INCLUSIVE);
+ }
+
+ super.onDestroy();
+ }
+
+ /**
+ * Build and assign {@link LayoutTransition} to various containers. Should
+ * only be assigned after initial layout is complete.
+ */
+ private void ensureLayoutTransitions() {
+ // skip when already setup
+ if (mChart.getLayoutTransition() != null) return;
+
+ mTabsContainer.setLayoutTransition(buildLayoutTransition());
+ mHeader.setLayoutTransition(buildLayoutTransition());
+ mNetworkSwitchesContainer.setLayoutTransition(buildLayoutTransition());
+
+ final LayoutTransition chartTransition = buildLayoutTransition();
+ chartTransition.disableTransitionType(LayoutTransition.APPEARING);
+ chartTransition.disableTransitionType(LayoutTransition.DISAPPEARING);
+ mChart.setLayoutTransition(chartTransition);
+ }
+
+ private static LayoutTransition buildLayoutTransition() {
+ final LayoutTransition transition = new LayoutTransition();
+ if (TEST_ANIM) {
+ transition.setDuration(1500);
+ }
+ transition.setAnimateParentHierarchy(false);
+ return transition;
+ }
+
+ /**
+ * Rebuild all tabs based on {@link NetworkPolicyEditor} and
+ * {@link #mShowWifi}, hiding the tabs entirely when applicable. Selects
+ * first tab, and kicks off a full rebind of body contents.
+ */
+ private void updateTabs() {
+ final Context context = getActivity();
+ mTabHost.clearAllTabs();
+
+ final boolean mobileSplit = isMobilePolicySplit();
+ if (mobileSplit && hasReadyMobile4gRadio(context)) {
+ mTabHost.addTab(buildTabSpec(TAB_3G, R.string.data_usage_tab_3g));
+ mTabHost.addTab(buildTabSpec(TAB_4G, R.string.data_usage_tab_4g));
+ } else if (hasReadyMobileRadio(context)) {
+ mTabHost.addTab(buildTabSpec(TAB_MOBILE, R.string.data_usage_tab_mobile));
+ }
+ if (mShowWifi && hasWifiRadio(context)) {
+ mTabHost.addTab(buildTabSpec(TAB_WIFI, R.string.data_usage_tab_wifi));
+ }
+ if (mShowEthernet && hasEthernet(context)) {
+ mTabHost.addTab(buildTabSpec(TAB_ETHERNET, R.string.data_usage_tab_ethernet));
+ }
+
+ final boolean noTabs = mTabWidget.getTabCount() == 0;
+ final boolean multipleTabs = mTabWidget.getTabCount() > 1;
+ mTabWidget.setVisibility(multipleTabs ? View.VISIBLE : View.GONE);
+ if (mIntentTab != null) {
+ if (Objects.equal(mIntentTab, mTabHost.getCurrentTabTag())) {
+ // already hit updateBody() when added; ignore
+ updateBody();
+ } else {
+ mTabHost.setCurrentTabByTag(mIntentTab);
+ }
+ mIntentTab = null;
+ } else if (noTabs) {
+ // no usable tabs, so hide body
+ updateBody();
+ } else {
+ // already hit updateBody() when added; ignore
+ }
+ }
+
+ /**
+ * Factory that provide empty {@link View} to make {@link TabHost} happy.
+ */
+ private TabContentFactory mEmptyTabContent = new TabContentFactory() {
+ @Override
+ public View createTabContent(String tag) {
+ return new View(mTabHost.getContext());
+ }
+ };
+
+ /**
+ * Build {@link TabSpec} with thin indicator, and empty content.
+ */
+ private TabSpec buildTabSpec(String tag, int titleRes) {
+ return mTabHost.newTabSpec(tag).setIndicator(getText(titleRes)).setContent(
+ mEmptyTabContent);
+ }
+
+ private OnTabChangeListener mTabListener = new OnTabChangeListener() {
+ @Override
+ public void onTabChanged(String tabId) {
+ // user changed tab; update body
+ updateBody();
+ }
+ };
+
+ /**
+ * Update body content based on current tab. Loads
+ * {@link NetworkStatsHistory} and {@link NetworkPolicy} from system, and
+ * binds them to visible controls.
+ */
+ private void updateBody() {
+ mBinding = true;
+ if (!isAdded()) return;
+
+ final Context context = getActivity();
+ final String currentTab = mTabHost.getCurrentTabTag();
+ final boolean isOwner = ActivityManager.getCurrentUser() == UserHandle.USER_OWNER;
+
+ if (currentTab == null) {
+ Log.w(TAG, "no tab selected; hiding body");
+ mListView.setVisibility(View.GONE);
+ return;
+ } else {
+ mListView.setVisibility(View.VISIBLE);
+ }
+
+ final boolean tabChanged = !currentTab.equals(mCurrentTab);
+ mCurrentTab = currentTab;
+
+ if (LOGD) Log.d(TAG, "updateBody() with currentTab=" + currentTab);
+
+ mDataEnabledView.setVisibility(isOwner ? View.VISIBLE : View.GONE);
+
+ // TODO: remove mobile tabs when SIM isn't ready
+ final TelephonyManager tele = TelephonyManager.from(context);
+
+ if (TAB_MOBILE.equals(currentTab)) {
+ setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_mobile);
+ setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_mobile_limit);
+ mTemplate = buildTemplateMobileAll(getActiveSubscriberId(context));
+
+ } else if (TAB_3G.equals(currentTab)) {
+ setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_3g);
+ setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_3g_limit);
+ // TODO: bind mDataEnabled to 3G radio state
+ mTemplate = buildTemplateMobile3gLower(getActiveSubscriberId(context));
+
+ } else if (TAB_4G.equals(currentTab)) {
+ setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_4g);
+ setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_4g_limit);
+ // TODO: bind mDataEnabled to 4G radio state
+ mTemplate = buildTemplateMobile4g(getActiveSubscriberId(context));
+
+ } else if (TAB_WIFI.equals(currentTab)) {
+ // wifi doesn't have any controls
+ mDataEnabledView.setVisibility(View.GONE);
+ mDisableAtLimitView.setVisibility(View.GONE);
+ mTemplate = buildTemplateWifiWildcard();
+
+ } else if (TAB_ETHERNET.equals(currentTab)) {
+ // ethernet doesn't have any controls
+ mDataEnabledView.setVisibility(View.GONE);
+ mDisableAtLimitView.setVisibility(View.GONE);
+ mTemplate = buildTemplateEthernet();
+
+ } else {
+ throw new IllegalStateException("unknown tab: " + currentTab);
+ }
+
+ // kick off loader for network history
+ // TODO: consider chaining two loaders together instead of reloading
+ // network history when showing app detail.
+ getLoaderManager().restartLoader(LOADER_CHART_DATA,
+ ChartDataLoader.buildArgs(mTemplate, mCurrentApp), mChartDataCallbacks);
+
+ // detail mode can change visible menus, invalidate
+ getActivity().invalidateOptionsMenu();
+
+ mBinding = false;
+ }
+
+ private boolean isAppDetailMode() {
+ return mCurrentApp != null;
+ }
+
+ /**
+ * Update UID details panels to match {@link #mCurrentApp}, showing or
+ * hiding them depending on {@link #isAppDetailMode()}.
+ */
+ private void updateAppDetail() {
+ final Context context = getActivity();
+ final PackageManager pm = context.getPackageManager();
+ final LayoutInflater inflater = getActivity().getLayoutInflater();
+
+ if (isAppDetailMode()) {
+ mAppDetail.setVisibility(View.VISIBLE);
+ mCycleAdapter.setChangeVisible(false);
+ } else {
+ mAppDetail.setVisibility(View.GONE);
+ mCycleAdapter.setChangeVisible(true);
+
+ // hide detail stats when not in detail mode
+ mChart.bindDetailNetworkStats(null);
+ return;
+ }
+
+ // remove warning/limit sweeps while in detail mode
+ mChart.bindNetworkPolicy(null);
+
+ // show icon and all labels appearing under this app
+ final int uid = mCurrentApp.key;
+ final UidDetail detail = mUidDetailProvider.getUidDetail(uid, true);
+ mAppIcon.setImageDrawable(detail.icon);
+
+ mAppTitles.removeAllViews();
+ if (detail.detailLabels != null) {
+ for (CharSequence label : detail.detailLabels) {
+ mAppTitles.addView(inflateAppTitle(inflater, mAppTitles, label));
+ }
+ } else {
+ mAppTitles.addView(inflateAppTitle(inflater, mAppTitles, detail.label));
+ }
+
+ // enable settings button when package provides it
+ final String[] packageNames = pm.getPackagesForUid(uid);
+ if (packageNames != null && packageNames.length > 0) {
+ mAppSettingsIntent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE);
+ mAppSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT);
+
+ // Search for match across all packages
+ boolean matchFound = false;
+ for (String packageName : packageNames) {
+ mAppSettingsIntent.setPackage(packageName);
+ if (pm.resolveActivity(mAppSettingsIntent, 0) != null) {
+ matchFound = true;
+ break;
+ }
+ }
+
+ mAppSettings.setEnabled(matchFound);
+ mAppSettings.setVisibility(View.VISIBLE);
+
+ } else {
+ mAppSettingsIntent = null;
+ mAppSettings.setVisibility(View.GONE);
+ }
+
+ updateDetailData();
+
+ if (UserHandle.isApp(uid) && !mPolicyManager.getRestrictBackground()
+ && isBandwidthControlEnabled() && hasReadyMobileRadio(context)) {
+ setPreferenceTitle(mAppRestrictView, R.string.data_usage_app_restrict_background);
+ setPreferenceSummary(mAppRestrictView,
+ getString(R.string.data_usage_app_restrict_background_summary));
+
+ mAppRestrictView.setVisibility(View.VISIBLE);
+ mAppRestrict.setChecked(getAppRestrictBackground());
+
+ } else {
+ mAppRestrictView.setVisibility(View.GONE);
+ }
+ }
+
+ private void setPolicyWarningBytes(long warningBytes) {
+ if (LOGD) Log.d(TAG, "setPolicyWarningBytes()");
+ mPolicyEditor.setPolicyWarningBytes(mTemplate, warningBytes);
+ updatePolicy(false);
+ }
+
+ private void setPolicyLimitBytes(long limitBytes) {
+ if (LOGD) Log.d(TAG, "setPolicyLimitBytes()");
+ mPolicyEditor.setPolicyLimitBytes(mTemplate, limitBytes);
+ updatePolicy(false);
+ }
+
+ /**
+ * Local cache of value, used to work around delay when
+ * {@link ConnectivityManager#setMobileDataEnabled(boolean)} is async.
+ */
+ private Boolean mMobileDataEnabled;
+
+ private boolean isMobileDataEnabled() {
+ if (mMobileDataEnabled != null) {
+ // TODO: deprecate and remove this once enabled flag is on policy
+ return mMobileDataEnabled;
+ } else {
+ return mConnService.getMobileDataEnabled();
+ }
+ }
+
+ private void setMobileDataEnabled(boolean enabled) {
+ if (LOGD) Log.d(TAG, "setMobileDataEnabled()");
+ mConnService.setMobileDataEnabled(enabled);
+ mMobileDataEnabled = enabled;
+ updatePolicy(false);
+ }
+
+ private boolean isNetworkPolicyModifiable(NetworkPolicy policy) {
+ return policy != null && isBandwidthControlEnabled() && mDataEnabled.isChecked()
+ && ActivityManager.getCurrentUser() == UserHandle.USER_OWNER;
+ }
+
+ private boolean isBandwidthControlEnabled() {
+ try {
+ return mNetworkService.isBandwidthControlEnabled();
+ } catch (RemoteException e) {
+ Log.w(TAG, "problem talking with INetworkManagementService: " + e);
+ return false;
+ }
+ }
+
+ private boolean getDataRoaming() {
+ final ContentResolver resolver = getActivity().getContentResolver();
+ return Settings.Global.getInt(resolver, Settings.Global.DATA_ROAMING, 0) != 0;
+ }
+
+ private void setDataRoaming(boolean enabled) {
+ // TODO: teach telephony DataConnectionTracker to watch and apply
+ // updates when changed.
+ final ContentResolver resolver = getActivity().getContentResolver();
+ Settings.Global.putInt(resolver, Settings.Global.DATA_ROAMING, enabled ? 1 : 0);
+ mMenuDataRoaming.setChecked(enabled);
+ }
+
+ public void setRestrictBackground(boolean restrictBackground) {
+ mPolicyManager.setRestrictBackground(restrictBackground);
+ mMenuRestrictBackground.setChecked(restrictBackground);
+ }
+
+ private boolean getAppRestrictBackground() {
+ final int uid = mCurrentApp.key;
+ final int uidPolicy = mPolicyManager.getUidPolicy(uid);
+ return (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0;
+ }
+
+ private void setAppRestrictBackground(boolean restrictBackground) {
+ if (LOGD) Log.d(TAG, "setAppRestrictBackground()");
+ final int uid = mCurrentApp.key;
+ mPolicyManager.setUidPolicy(
+ uid, restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE);
+ mAppRestrict.setChecked(restrictBackground);
+ }
+
+ /**
+ * Update chart sweeps and cycle list to reflect {@link NetworkPolicy} for
+ * current {@link #mTemplate}.
+ */
+ private void updatePolicy(boolean refreshCycle) {
+ if (isAppDetailMode()) {
+ mNetworkSwitches.setVisibility(View.GONE);
+ } else {
+ mNetworkSwitches.setVisibility(View.VISIBLE);
+ }
+
+ // TODO: move enabled state directly into policy
+ if (TAB_MOBILE.equals(mCurrentTab)) {
+ mBinding = true;
+ mDataEnabled.setChecked(isMobileDataEnabled());
+ mBinding = false;
+ }
+
+ final NetworkPolicy policy = mPolicyEditor.getPolicy(mTemplate);
+ if (isNetworkPolicyModifiable(policy)) {
+ mDisableAtLimitView.setVisibility(View.VISIBLE);
+ mDisableAtLimit.setChecked(policy != null && policy.limitBytes != LIMIT_DISABLED);
+ if (!isAppDetailMode()) {
+ mChart.bindNetworkPolicy(policy);
+ }
+
+ } else {
+ // controls are disabled; don't bind warning/limit sweeps
+ mDisableAtLimitView.setVisibility(View.GONE);
+ mChart.bindNetworkPolicy(null);
+ }
+
+ if (refreshCycle) {
+ // generate cycle list based on policy and available history
+ updateCycleList(policy);
+ }
+ }
+
+ /**
+ * Rebuild {@link #mCycleAdapter} based on {@link NetworkPolicy#cycleDay}
+ * and available {@link NetworkStatsHistory} data. Always selects the newest
+ * item, updating the inspection range on {@link #mChart}.
+ */
+ private void updateCycleList(NetworkPolicy policy) {
+ // stash away currently selected cycle to try restoring below
+ final CycleItem previousItem = (CycleItem) mCycleSpinner.getSelectedItem();
+ mCycleAdapter.clear();
+
+ final Context context = mCycleSpinner.getContext();
+
+ long historyStart = Long.MAX_VALUE;
+ long historyEnd = Long.MIN_VALUE;
+ if (mChartData != null) {
+ historyStart = mChartData.network.getStart();
+ historyEnd = mChartData.network.getEnd();
+ }
+
+ final long now = System.currentTimeMillis();
+ if (historyStart == Long.MAX_VALUE) historyStart = now;
+ if (historyEnd == Long.MIN_VALUE) historyEnd = now + 1;
+
+ boolean hasCycles = false;
+ if (policy != null) {
+ // find the next cycle boundary
+ long cycleEnd = computeNextCycleBoundary(historyEnd, policy);
+
+ // walk backwards, generating all valid cycle ranges
+ while (cycleEnd > historyStart) {
+ final long cycleStart = computeLastCycleBoundary(cycleEnd, policy);
+ Log.d(TAG, "generating cs=" + cycleStart + " to ce=" + cycleEnd + " waiting for hs="
+ + historyStart);
+ mCycleAdapter.add(new CycleItem(context, cycleStart, cycleEnd));
+ cycleEnd = cycleStart;
+ hasCycles = true;
+ }
+
+ // one last cycle entry to modify policy cycle day
+ mCycleAdapter.setChangePossible(isNetworkPolicyModifiable(policy));
+ }
+
+ if (!hasCycles) {
+ // no policy defined cycles; show entry for each four-week period
+ long cycleEnd = historyEnd;
+ while (cycleEnd > historyStart) {
+ final long cycleStart = cycleEnd - (DateUtils.WEEK_IN_MILLIS * 4);
+ mCycleAdapter.add(new CycleItem(context, cycleStart, cycleEnd));
+ cycleEnd = cycleStart;
+ }
+
+ mCycleAdapter.setChangePossible(false);
+ }
+
+ // force pick the current cycle (first item)
+ if (mCycleAdapter.getCount() > 0) {
+ final int position = mCycleAdapter.findNearestPosition(previousItem);
+ mCycleSpinner.setSelection(position);
+
+ // only force-update cycle when changed; skipping preserves any
+ // user-defined inspection region.
+ final CycleItem selectedItem = mCycleAdapter.getItem(position);
+ if (!Objects.equal(selectedItem, previousItem)) {
+ mCycleListener.onItemSelected(mCycleSpinner, null, position, 0);
+ } else {
+ // but still kick off loader for detailed list
+ updateDetailData();
+ }
+ } else {
+ updateDetailData();
+ }
+ }
+
+ private OnCheckedChangeListener mDataEnabledListener = new OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ if (mBinding) return;
+
+ final boolean dataEnabled = isChecked;
+ final String currentTab = mCurrentTab;
+ if (TAB_MOBILE.equals(currentTab)) {
+ if (dataEnabled) {
+ setMobileDataEnabled(true);
+ } else {
+ // disabling data; show confirmation dialog which eventually
+ // calls setMobileDataEnabled() once user confirms.
+ ConfirmDataDisableFragment.show(DataUsageSummary.this);
+ }
+ }
+
+ updatePolicy(false);
+ }
+ };
+
+ private View.OnClickListener mDisableAtLimitListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final boolean disableAtLimit = !mDisableAtLimit.isChecked();
+ if (disableAtLimit) {
+ // enabling limit; show confirmation dialog which eventually
+ // calls setPolicyLimitBytes() once user confirms.
+ ConfirmLimitFragment.show(DataUsageSummary.this);
+ } else {
+ setPolicyLimitBytes(LIMIT_DISABLED);
+ }
+ }
+ };
+
+ private View.OnClickListener mAppRestrictListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final boolean restrictBackground = !mAppRestrict.isChecked();
+
+ if (restrictBackground) {
+ // enabling restriction; show confirmation dialog which
+ // eventually calls setRestrictBackground() once user
+ // confirms.
+ ConfirmAppRestrictFragment.show(DataUsageSummary.this);
+ } else {
+ setAppRestrictBackground(false);
+ }
+ }
+ };
+
+ private OnClickListener mAppSettingsListener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (!isAdded()) return;
+
+ // TODO: target torwards entire UID instead of just first package
+ startActivity(mAppSettingsIntent);
+ }
+ };
+
+ private OnItemClickListener mListListener = new OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ final Context context = view.getContext();
+ final AppItem app = (AppItem) parent.getItemAtPosition(position);
+
+ // TODO: sigh, remove this hack once we understand 6450986
+ if (mUidDetailProvider == null || app == null) return;
+
+ final UidDetail detail = mUidDetailProvider.getUidDetail(app.key, true);
+ AppDetailsFragment.show(DataUsageSummary.this, app, detail.label);
+ }
+ };
+
+ private OnItemSelectedListener mCycleListener = new OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView> parent, View view, int position, long id) {
+ final CycleItem cycle = (CycleItem) parent.getItemAtPosition(position);
+ if (cycle instanceof CycleChangeItem) {
+ // show cycle editor; will eventually call setPolicyCycleDay()
+ // when user finishes editing.
+ CycleEditorFragment.show(DataUsageSummary.this);
+
+ // reset spinner to something other than "change cycle..."
+ mCycleSpinner.setSelection(0);
+
+ } else {
+ if (LOGD) {
+ Log.d(TAG, "showing cycle " + cycle + ", start=" + cycle.start + ", end="
+ + cycle.end + "]");
+ }
+
+ // update chart to show selected cycle, and update detail data
+ // to match updated sweep bounds.
+ mChart.setVisibleRange(cycle.start, cycle.end);
+
+ updateDetailData();
+ }
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView> parent) {
+ // ignored
+ }
+ };
+
+ /**
+ * Update details based on {@link #mChart} inspection range depending on
+ * current mode. In network mode, updates {@link #mAdapter} with sorted list
+ * of applications data usage, and when {@link #isAppDetailMode()} update
+ * app details.
+ */
+ private void updateDetailData() {
+ if (LOGD) Log.d(TAG, "updateDetailData()");
+
+ final long start = mChart.getInspectStart();
+ final long end = mChart.getInspectEnd();
+ final long now = System.currentTimeMillis();
+
+ final Context context = getActivity();
+
+ NetworkStatsHistory.Entry entry = null;
+ if (isAppDetailMode() && mChartData != null && mChartData.detail != null) {
+ // bind foreground/background to piechart and labels
+ entry = mChartData.detailDefault.getValues(start, end, now, entry);
+ final long defaultBytes = entry.rxBytes + entry.txBytes;
+ entry = mChartData.detailForeground.getValues(start, end, now, entry);
+ final long foregroundBytes = entry.rxBytes + entry.txBytes;
+
+ mAppPieChart.setOriginAngle(175);
+
+ mAppPieChart.removeAllSlices();
+ mAppPieChart.addSlice(foregroundBytes, Color.parseColor("#d88d3a"));
+ mAppPieChart.addSlice(defaultBytes, Color.parseColor("#666666"));
+
+ mAppPieChart.generatePath();
+
+ mAppBackground.setText(Formatter.formatFileSize(context, defaultBytes));
+ mAppForeground.setText(Formatter.formatFileSize(context, foregroundBytes));
+
+ // and finally leave with summary data for label below
+ entry = mChartData.detail.getValues(start, end, now, null);
+
+ getLoaderManager().destroyLoader(LOADER_SUMMARY);
+
+ } else {
+ if (mChartData != null) {
+ entry = mChartData.network.getValues(start, end, now, null);
+ }
+
+ // kick off loader for detailed stats
+ getLoaderManager().restartLoader(LOADER_SUMMARY,
+ SummaryForAllUidLoader.buildArgs(mTemplate, start, end), mSummaryCallbacks);
+ }
+
+ final long totalBytes = entry != null ? entry.rxBytes + entry.txBytes : 0;
+ final String totalPhrase = Formatter.formatFileSize(context, totalBytes);
+ final String rangePhrase = formatDateRange(context, start, end);
+
+ final int summaryRes;
+ if (TAB_MOBILE.equals(mCurrentTab) || TAB_3G.equals(mCurrentApp)
+ || TAB_4G.equals(mCurrentApp)) {
+ summaryRes = R.string.data_usage_total_during_range_mobile;
+ } else {
+ summaryRes = R.string.data_usage_total_during_range;
+ }
+
+ mUsageSummary.setText(getString(summaryRes, totalPhrase, rangePhrase));
+
+ // initial layout is finished above, ensure we have transitions
+ ensureLayoutTransitions();
+ }
+
+ private final LoaderCallbacks mChartDataCallbacks = new LoaderCallbacks<
+ ChartData>() {
+ @Override
+ public Loader onCreateLoader(int id, Bundle args) {
+ return new ChartDataLoader(getActivity(), mStatsSession, args);
+ }
+
+ @Override
+ public void onLoadFinished(Loader loader, ChartData data) {
+ mChartData = data;
+ mChart.bindNetworkStats(mChartData.network);
+ mChart.bindDetailNetworkStats(mChartData.detail);
+
+ // calcuate policy cycles based on available data
+ updatePolicy(true);
+ updateAppDetail();
+
+ // force scroll to top of body when showing detail
+ if (mChartData.detail != null) {
+ mListView.smoothScrollToPosition(0);
+ }
+ }
+
+ @Override
+ public void onLoaderReset(Loader loader) {
+ mChartData = null;
+ mChart.bindNetworkStats(null);
+ mChart.bindDetailNetworkStats(null);
+ }
+ };
+
+ private final LoaderCallbacks mSummaryCallbacks = new LoaderCallbacks<
+ NetworkStats>() {
+ @Override
+ public Loader onCreateLoader(int id, Bundle args) {
+ return new SummaryForAllUidLoader(getActivity(), mStatsSession, args);
+ }
+
+ @Override
+ public void onLoadFinished(Loader loader, NetworkStats data) {
+ final int[] restrictedUids = mPolicyManager.getUidsWithPolicy(
+ POLICY_REJECT_METERED_BACKGROUND);
+ mAdapter.bindStats(data, restrictedUids);
+ updateEmptyVisible();
+ }
+
+ @Override
+ public void onLoaderReset(Loader loader) {
+ mAdapter.bindStats(null, new int[0]);
+ updateEmptyVisible();
+ }
+
+ private void updateEmptyVisible() {
+ final boolean isEmpty = mAdapter.isEmpty() && !isAppDetailMode();
+ mEmpty.setVisibility(isEmpty ? View.VISIBLE : View.GONE);
+ }
+ };
+
+ @Deprecated
+ private boolean isMobilePolicySplit() {
+ final Context context = getActivity();
+ if (hasReadyMobileRadio(context)) {
+ final TelephonyManager tele = TelephonyManager.from(context);
+ return mPolicyEditor.isMobilePolicySplit(getActiveSubscriberId(context));
+ } else {
+ return false;
+ }
+ }
+
+ @Deprecated
+ private void setMobilePolicySplit(boolean split) {
+ final Context context = getActivity();
+ if (hasReadyMobileRadio(context)) {
+ final TelephonyManager tele = TelephonyManager.from(context);
+ mPolicyEditor.setMobilePolicySplit(getActiveSubscriberId(context), split);
+ }
+ }
+
+ private static String getActiveSubscriberId(Context context) {
+ final TelephonyManager tele = TelephonyManager.from(context);
+ final String actualSubscriberId = tele.getSubscriberId();
+ return SystemProperties.get(TEST_SUBSCRIBER_PROP, actualSubscriberId);
+ }
+
+ private DataUsageChartListener mChartListener = new DataUsageChartListener() {
+ @Override
+ public void onInspectRangeChanged() {
+ if (LOGD) Log.d(TAG, "onInspectRangeChanged()");
+ updateDetailData();
+ }
+
+ @Override
+ public void onWarningChanged() {
+ setPolicyWarningBytes(mChart.getWarningBytes());
+ }
+
+ @Override
+ public void onLimitChanged() {
+ setPolicyLimitBytes(mChart.getLimitBytes());
+ }
+
+ @Override
+ public void requestWarningEdit() {
+ WarningEditorFragment.show(DataUsageSummary.this);
+ }
+
+ @Override
+ public void requestLimitEdit() {
+ LimitEditorFragment.show(DataUsageSummary.this);
+ }
+ };
+
+ /**
+ * List item that reflects a specific data usage cycle.
+ */
+ public static class CycleItem implements Comparable {
+ public CharSequence label;
+ public long start;
+ public long end;
+
+ CycleItem(CharSequence label) {
+ this.label = label;
+ }
+
+ public CycleItem(Context context, long start, long end) {
+ this.label = formatDateRange(context, start, end);
+ this.start = start;
+ this.end = end;
+ }
+
+ @Override
+ public String toString() {
+ return label.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof CycleItem) {
+ final CycleItem another = (CycleItem) o;
+ return start == another.start && end == another.end;
+ }
+ return false;
+ }
+
+ @Override
+ public int compareTo(CycleItem another) {
+ return Long.compare(start, another.start);
+ }
+ }
+
+ private static final StringBuilder sBuilder = new StringBuilder(50);
+ private static final java.util.Formatter sFormatter = new java.util.Formatter(
+ sBuilder, Locale.getDefault());
+
+ public static String formatDateRange(Context context, long start, long end) {
+ final int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_MONTH;
+
+ synchronized (sBuilder) {
+ sBuilder.setLength(0);
+ return DateUtils.formatDateRange(context, sFormatter, start, end, flags, null)
+ .toString();
+ }
+ }
+
+ /**
+ * Special-case data usage cycle that triggers dialog to change
+ * {@link NetworkPolicy#cycleDay}.
+ */
+ public static class CycleChangeItem extends CycleItem {
+ public CycleChangeItem(Context context) {
+ super(context.getString(R.string.data_usage_change_cycle));
+ }
+ }
+
+ public static class CycleAdapter extends ArrayAdapter {
+ private boolean mChangePossible = false;
+ private boolean mChangeVisible = false;
+
+ private final CycleChangeItem mChangeItem;
+
+ public CycleAdapter(Context context) {
+ super(context, android.R.layout.simple_spinner_item);
+ setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mChangeItem = new CycleChangeItem(context);
+ }
+
+ public void setChangePossible(boolean possible) {
+ mChangePossible = possible;
+ updateChange();
+ }
+
+ public void setChangeVisible(boolean visible) {
+ mChangeVisible = visible;
+ updateChange();
+ }
+
+ private void updateChange() {
+ remove(mChangeItem);
+ if (mChangePossible && mChangeVisible) {
+ add(mChangeItem);
+ }
+ }
+
+ /**
+ * Find position of {@link CycleItem} in this adapter which is nearest
+ * the given {@link CycleItem}.
+ */
+ public int findNearestPosition(CycleItem target) {
+ if (target != null) {
+ final int count = getCount();
+ for (int i = count - 1; i >= 0; i--) {
+ final CycleItem item = getItem(i);
+ if (item instanceof CycleChangeItem) {
+ continue;
+ } else if (item.compareTo(target) >= 0) {
+ return i;
+ }
+ }
+ }
+ return 0;
+ }
+ }
+
+ public static class AppItem implements Comparable, Parcelable {
+ public final int key;
+ public boolean restricted;
+ public SparseBooleanArray uids = new SparseBooleanArray();
+ public long total;
+
+ public AppItem(int key) {
+ this.key = key;
+ }
+
+ public AppItem(Parcel parcel) {
+ key = parcel.readInt();
+ uids = parcel.readSparseBooleanArray();
+ total = parcel.readLong();
+ }
+
+ public void addUid(int uid) {
+ uids.put(uid, true);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(key);
+ dest.writeSparseBooleanArray(uids);
+ dest.writeLong(total);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public int compareTo(AppItem another) {
+ return Long.compare(another.total, total);
+ }
+
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public AppItem createFromParcel(Parcel in) {
+ return new AppItem(in);
+ }
+
+ @Override
+ public AppItem[] newArray(int size) {
+ return new AppItem[size];
+ }
+ };
+ }
+
+ /**
+ * Adapter of applications, sorted by total usage descending.
+ */
+ public static class DataUsageAdapter extends BaseAdapter {
+ private final UidDetailProvider mProvider;
+ private final int mInsetSide;
+
+ private ArrayList mItems = Lists.newArrayList();
+ private long mLargest;
+
+ public DataUsageAdapter(UidDetailProvider provider, int insetSide) {
+ mProvider = checkNotNull(provider);
+ mInsetSide = insetSide;
+ }
+
+ /**
+ * Bind the given {@link NetworkStats}, or {@code null} to clear list.
+ */
+ public void bindStats(NetworkStats stats, int[] restrictedUids) {
+ mItems.clear();
+
+ final int currentUserId = ActivityManager.getCurrentUser();
+ final SparseArray knownItems = new SparseArray();
+
+ NetworkStats.Entry entry = null;
+ final int size = stats != null ? stats.size() : 0;
+ for (int i = 0; i < size; i++) {
+ entry = stats.getValues(i, entry);
+
+ // Decide how to collapse items together
+ final int uid = entry.uid;
+ final int collapseKey;
+ if (UserHandle.isApp(uid)) {
+ if (UserHandle.getUserId(uid) == currentUserId) {
+ collapseKey = uid;
+ } else {
+ collapseKey = UidDetailProvider.buildKeyForUser(UserHandle.getUserId(uid));
+ }
+ } else if (uid == UID_REMOVED || uid == UID_TETHERING) {
+ collapseKey = uid;
+ } else {
+ collapseKey = android.os.Process.SYSTEM_UID;
+ }
+
+ AppItem item = knownItems.get(collapseKey);
+ if (item == null) {
+ item = new AppItem(collapseKey);
+ mItems.add(item);
+ knownItems.put(item.key, item);
+ }
+ item.addUid(uid);
+ item.total += entry.rxBytes + entry.txBytes;
+ }
+
+ for (int uid : restrictedUids) {
+ // Only splice in restricted state for current user
+ if (UserHandle.getUserId(uid) != currentUserId) continue;
+
+ AppItem item = knownItems.get(uid);
+ if (item == null) {
+ item = new AppItem(uid);
+ item.total = -1;
+ mItems.add(item);
+ knownItems.put(item.key, item);
+ }
+ item.restricted = true;
+ }
+
+ Collections.sort(mItems);
+ mLargest = (mItems.size() > 0) ? mItems.get(0).total : 0;
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public int getCount() {
+ return mItems.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mItems.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return mItems.get(position).key;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = LayoutInflater.from(parent.getContext()).inflate(
+ R.layout.data_usage_item, parent, false);
+
+ if (mInsetSide > 0) {
+ convertView.setPadding(mInsetSide, 0, mInsetSide, 0);
+ }
+ }
+
+ final Context context = parent.getContext();
+
+ final TextView text1 = (TextView) convertView.findViewById(android.R.id.text1);
+ final ProgressBar progress = (ProgressBar) convertView.findViewById(
+ android.R.id.progress);
+
+ // kick off async load of app details
+ final AppItem item = mItems.get(position);
+ UidDetailTask.bindView(mProvider, item, convertView);
+
+ if (item.restricted && item.total <= 0) {
+ text1.setText(R.string.data_usage_app_restricted);
+ progress.setVisibility(View.GONE);
+ } else {
+ text1.setText(Formatter.formatFileSize(context, item.total));
+ progress.setVisibility(View.VISIBLE);
+ }
+
+ final int percentTotal = mLargest != 0 ? (int) (item.total * 100 / mLargest) : 0;
+ progress.setProgress(percentTotal);
+
+ return convertView;
+ }
+ }
+
+ /**
+ * Empty {@link Fragment} that controls display of UID details in
+ * {@link DataUsageSummary}.
+ */
+ public static class AppDetailsFragment extends Fragment {
+ private static final String EXTRA_APP = "app";
+
+ public static void show(DataUsageSummary parent, AppItem app, CharSequence label) {
+ if (!parent.isAdded()) return;
+
+ final Bundle args = new Bundle();
+ args.putParcelable(EXTRA_APP, app);
+
+ final AppDetailsFragment fragment = new AppDetailsFragment();
+ fragment.setArguments(args);
+ fragment.setTargetFragment(parent, 0);
+ final FragmentTransaction ft = parent.getFragmentManager().beginTransaction();
+ ft.add(fragment, TAG_APP_DETAILS);
+ ft.addToBackStack(TAG_APP_DETAILS);
+ ft.setBreadCrumbTitle(label);
+ ft.commitAllowingStateLoss();
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
+ target.mCurrentApp = getArguments().getParcelable(EXTRA_APP);
+ target.updateBody();
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
+ target.mCurrentApp = null;
+ target.updateBody();
+ }
+ }
+
+ /**
+ * Dialog to request user confirmation before setting
+ * {@link NetworkPolicy#limitBytes}.
+ */
+ public static class ConfirmLimitFragment extends DialogFragment {
+ private static final String EXTRA_MESSAGE = "message";
+ private static final String EXTRA_LIMIT_BYTES = "limitBytes";
+
+ public static void show(DataUsageSummary parent) {
+ if (!parent.isAdded()) return;
+
+ final Resources res = parent.getResources();
+ final CharSequence message;
+ final long minLimitBytes = (long) (
+ parent.mPolicyEditor.getPolicy(parent.mTemplate).warningBytes * 1.2f);
+ final long limitBytes;
+
+ // TODO: customize default limits based on network template
+ final String currentTab = parent.mCurrentTab;
+ if (TAB_3G.equals(currentTab)) {
+ message = res.getString(R.string.data_usage_limit_dialog_mobile);
+ limitBytes = Math.max(5 * GB_IN_BYTES, minLimitBytes);
+ } else if (TAB_4G.equals(currentTab)) {
+ message = res.getString(R.string.data_usage_limit_dialog_mobile);
+ limitBytes = Math.max(5 * GB_IN_BYTES, minLimitBytes);
+ } else if (TAB_MOBILE.equals(currentTab)) {
+ message = res.getString(R.string.data_usage_limit_dialog_mobile);
+ limitBytes = Math.max(5 * GB_IN_BYTES, minLimitBytes);
+ } else {
+ throw new IllegalArgumentException("unknown current tab: " + currentTab);
+ }
+
+ final Bundle args = new Bundle();
+ args.putCharSequence(EXTRA_MESSAGE, message);
+ args.putLong(EXTRA_LIMIT_BYTES, limitBytes);
+
+ final ConfirmLimitFragment dialog = new ConfirmLimitFragment();
+ dialog.setArguments(args);
+ dialog.setTargetFragment(parent, 0);
+ dialog.show(parent.getFragmentManager(), TAG_CONFIRM_LIMIT);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Context context = getActivity();
+
+ final CharSequence message = getArguments().getCharSequence(EXTRA_MESSAGE);
+ final long limitBytes = getArguments().getLong(EXTRA_LIMIT_BYTES);
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.data_usage_limit_dialog_title);
+ builder.setMessage(message);
+
+ builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
+ if (target != null) {
+ target.setPolicyLimitBytes(limitBytes);
+ }
+ }
+ });
+
+ return builder.create();
+ }
+ }
+
+ /**
+ * Dialog to edit {@link NetworkPolicy#cycleDay}.
+ */
+ public static class CycleEditorFragment extends DialogFragment {
+ private static final String EXTRA_TEMPLATE = "template";
+
+ public static void show(DataUsageSummary parent) {
+ if (!parent.isAdded()) return;
+
+ final Bundle args = new Bundle();
+ args.putParcelable(EXTRA_TEMPLATE, parent.mTemplate);
+
+ final CycleEditorFragment dialog = new CycleEditorFragment();
+ dialog.setArguments(args);
+ dialog.setTargetFragment(parent, 0);
+ dialog.show(parent.getFragmentManager(), TAG_CYCLE_EDITOR);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Context context = getActivity();
+ final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
+ final NetworkPolicyEditor editor = target.mPolicyEditor;
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
+
+ final View view = dialogInflater.inflate(R.layout.data_usage_cycle_editor, null, false);
+ final NumberPicker cycleDayPicker = (NumberPicker) view.findViewById(R.id.cycle_day);
+
+ final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
+ final int cycleDay = editor.getPolicyCycleDay(template);
+
+ cycleDayPicker.setMinValue(1);
+ cycleDayPicker.setMaxValue(31);
+ cycleDayPicker.setValue(cycleDay);
+ cycleDayPicker.setWrapSelectorWheel(true);
+
+ builder.setTitle(R.string.data_usage_cycle_editor_title);
+ builder.setView(view);
+
+ builder.setPositiveButton(R.string.data_usage_cycle_editor_positive,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ final int cycleDay = cycleDayPicker.getValue();
+ final String cycleTimezone = new Time().timezone;
+ editor.setPolicyCycleDay(template, cycleDay, cycleTimezone);
+ target.updatePolicy(true);
+ }
+ });
+
+ return builder.create();
+ }
+ }
+
+ /**
+ * Dialog to edit {@link NetworkPolicy#warningBytes}.
+ */
+ public static class WarningEditorFragment extends DialogFragment {
+ private static final String EXTRA_TEMPLATE = "template";
+
+ public static void show(DataUsageSummary parent) {
+ if (!parent.isAdded()) return;
+
+ final Bundle args = new Bundle();
+ args.putParcelable(EXTRA_TEMPLATE, parent.mTemplate);
+
+ final WarningEditorFragment dialog = new WarningEditorFragment();
+ dialog.setArguments(args);
+ dialog.setTargetFragment(parent, 0);
+ dialog.show(parent.getFragmentManager(), TAG_WARNING_EDITOR);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Context context = getActivity();
+ final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
+ final NetworkPolicyEditor editor = target.mPolicyEditor;
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
+
+ final View view = dialogInflater.inflate(R.layout.data_usage_bytes_editor, null, false);
+ final NumberPicker bytesPicker = (NumberPicker) view.findViewById(R.id.bytes);
+
+ final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
+ final long warningBytes = editor.getPolicyWarningBytes(template);
+ final long limitBytes = editor.getPolicyLimitBytes(template);
+
+ bytesPicker.setMinValue(0);
+ if (limitBytes != LIMIT_DISABLED) {
+ bytesPicker.setMaxValue((int) (limitBytes / MB_IN_BYTES) - 1);
+ } else {
+ bytesPicker.setMaxValue(Integer.MAX_VALUE);
+ }
+ bytesPicker.setValue((int) (warningBytes / MB_IN_BYTES));
+ bytesPicker.setWrapSelectorWheel(false);
+
+ builder.setTitle(R.string.data_usage_warning_editor_title);
+ builder.setView(view);
+
+ builder.setPositiveButton(R.string.data_usage_cycle_editor_positive,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ // clear focus to finish pending text edits
+ bytesPicker.clearFocus();
+
+ final long bytes = bytesPicker.getValue() * MB_IN_BYTES;
+ editor.setPolicyWarningBytes(template, bytes);
+ target.updatePolicy(false);
+ }
+ });
+
+ return builder.create();
+ }
+ }
+
+ /**
+ * Dialog to edit {@link NetworkPolicy#limitBytes}.
+ */
+ public static class LimitEditorFragment extends DialogFragment {
+ private static final String EXTRA_TEMPLATE = "template";
+
+ public static void show(DataUsageSummary parent) {
+ if (!parent.isAdded()) return;
+
+ final Bundle args = new Bundle();
+ args.putParcelable(EXTRA_TEMPLATE, parent.mTemplate);
+
+ final LimitEditorFragment dialog = new LimitEditorFragment();
+ dialog.setArguments(args);
+ dialog.setTargetFragment(parent, 0);
+ dialog.show(parent.getFragmentManager(), TAG_LIMIT_EDITOR);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Context context = getActivity();
+ final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
+ final NetworkPolicyEditor editor = target.mPolicyEditor;
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
+
+ final View view = dialogInflater.inflate(R.layout.data_usage_bytes_editor, null, false);
+ final NumberPicker bytesPicker = (NumberPicker) view.findViewById(R.id.bytes);
+
+ final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE);
+ final long warningBytes = editor.getPolicyWarningBytes(template);
+ final long limitBytes = editor.getPolicyLimitBytes(template);
+
+ bytesPicker.setMaxValue(Integer.MAX_VALUE);
+ if (warningBytes != WARNING_DISABLED && limitBytes > 0) {
+ bytesPicker.setMinValue((int) (warningBytes / MB_IN_BYTES) + 1);
+ } else {
+ bytesPicker.setMinValue(0);
+ }
+ bytesPicker.setValue((int) (limitBytes / MB_IN_BYTES));
+ bytesPicker.setWrapSelectorWheel(false);
+
+ builder.setTitle(R.string.data_usage_limit_editor_title);
+ builder.setView(view);
+
+ builder.setPositiveButton(R.string.data_usage_cycle_editor_positive,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ // clear focus to finish pending text edits
+ bytesPicker.clearFocus();
+
+ final long bytes = bytesPicker.getValue() * MB_IN_BYTES;
+ editor.setPolicyLimitBytes(template, bytes);
+ target.updatePolicy(false);
+ }
+ });
+
+ return builder.create();
+ }
+ }
+ /**
+ * Dialog to request user confirmation before disabling data.
+ */
+ public static class ConfirmDataDisableFragment extends DialogFragment {
+ public static void show(DataUsageSummary parent) {
+ if (!parent.isAdded()) return;
+
+ final ConfirmDataDisableFragment dialog = new ConfirmDataDisableFragment();
+ dialog.setTargetFragment(parent, 0);
+ dialog.show(parent.getFragmentManager(), TAG_CONFIRM_DATA_DISABLE);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Context context = getActivity();
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setMessage(R.string.data_usage_disable_mobile);
+
+ builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
+ if (target != null) {
+ // TODO: extend to modify policy enabled flag.
+ target.setMobileDataEnabled(false);
+ }
+ }
+ });
+ builder.setNegativeButton(android.R.string.cancel, null);
+
+ return builder.create();
+ }
+ }
+
+ /**
+ * Dialog to request user confirmation before setting
+ * {@link android.provider.Settings.Global#DATA_ROAMING}.
+ */
+ public static class ConfirmDataRoamingFragment extends DialogFragment {
+ public static void show(DataUsageSummary parent) {
+ if (!parent.isAdded()) return;
+
+ final ConfirmDataRoamingFragment dialog = new ConfirmDataRoamingFragment();
+ dialog.setTargetFragment(parent, 0);
+ dialog.show(parent.getFragmentManager(), TAG_CONFIRM_DATA_ROAMING);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Context context = getActivity();
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.roaming_reenable_title);
+ if (Utils.hasMultipleUsers(context)) {
+ builder.setMessage(R.string.roaming_warning_multiuser);
+ } else {
+ builder.setMessage(R.string.roaming_warning);
+ }
+
+ builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
+ if (target != null) {
+ target.setDataRoaming(true);
+ }
+ }
+ });
+ builder.setNegativeButton(android.R.string.cancel, null);
+
+ return builder.create();
+ }
+ }
+
+ /**
+ * Dialog to request user confirmation before setting
+ * {@link INetworkPolicyManager#setRestrictBackground(boolean)}.
+ */
+ public static class ConfirmRestrictFragment extends DialogFragment {
+ public static void show(DataUsageSummary parent) {
+ if (!parent.isAdded()) return;
+
+ final ConfirmRestrictFragment dialog = new ConfirmRestrictFragment();
+ dialog.setTargetFragment(parent, 0);
+ dialog.show(parent.getFragmentManager(), TAG_CONFIRM_RESTRICT);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Context context = getActivity();
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.data_usage_restrict_background_title);
+ if (Utils.hasMultipleUsers(context)) {
+ builder.setMessage(R.string.data_usage_restrict_background_multiuser);
+ } else {
+ builder.setMessage(R.string.data_usage_restrict_background);
+ }
+
+ builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
+ if (target != null) {
+ target.setRestrictBackground(true);
+ }
+ }
+ });
+ builder.setNegativeButton(android.R.string.cancel, null);
+
+ return builder.create();
+ }
+ }
+
+ /**
+ * Dialog to inform user that {@link #POLICY_REJECT_METERED_BACKGROUND}
+ * change has been denied, usually based on
+ * {@link DataUsageSummary#hasLimitedNetworks()}.
+ */
+ public static class DeniedRestrictFragment extends DialogFragment {
+ public static void show(DataUsageSummary parent) {
+ if (!parent.isAdded()) return;
+
+ final DeniedRestrictFragment dialog = new DeniedRestrictFragment();
+ dialog.setTargetFragment(parent, 0);
+ dialog.show(parent.getFragmentManager(), TAG_DENIED_RESTRICT);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Context context = getActivity();
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.data_usage_app_restrict_background);
+ builder.setMessage(R.string.data_usage_restrict_denied_dialog);
+ builder.setPositiveButton(android.R.string.ok, null);
+
+ return builder.create();
+ }
+ }
+
+ /**
+ * Dialog to request user confirmation before setting
+ * {@link #POLICY_REJECT_METERED_BACKGROUND}.
+ */
+ public static class ConfirmAppRestrictFragment extends DialogFragment {
+ public static void show(DataUsageSummary parent) {
+ if (!parent.isAdded()) return;
+
+ final ConfirmAppRestrictFragment dialog = new ConfirmAppRestrictFragment();
+ dialog.setTargetFragment(parent, 0);
+ dialog.show(parent.getFragmentManager(), TAG_CONFIRM_APP_RESTRICT);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Context context = getActivity();
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.data_usage_app_restrict_dialog_title);
+ builder.setMessage(R.string.data_usage_app_restrict_dialog);
+
+ builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
+ if (target != null) {
+ target.setAppRestrictBackground(true);
+ }
+ }
+ });
+ builder.setNegativeButton(android.R.string.cancel, null);
+
+ return builder.create();
+ }
+ }
+
+ /**
+ * Dialog to inform user about changing auto-sync setting
+ */
+ public static class ConfirmAutoSyncChangeFragment extends DialogFragment {
+ private static final String SAVE_ENABLING = "enabling";
+ private boolean mEnabling;
+
+ public static void show(DataUsageSummary parent, boolean enabling) {
+ if (!parent.isAdded()) return;
+
+ final ConfirmAutoSyncChangeFragment dialog = new ConfirmAutoSyncChangeFragment();
+ dialog.mEnabling = enabling;
+ dialog.setTargetFragment(parent, 0);
+ dialog.show(parent.getFragmentManager(), TAG_CONFIRM_AUTO_SYNC_CHANGE);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Context context = getActivity();
+ if (savedInstanceState != null) {
+ mEnabling = savedInstanceState.getBoolean(SAVE_ENABLING);
+ }
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ if (!mEnabling) {
+ builder.setTitle(R.string.data_usage_auto_sync_off_dialog_title);
+ builder.setMessage(R.string.data_usage_auto_sync_off_dialog);
+ } else {
+ builder.setTitle(R.string.data_usage_auto_sync_on_dialog_title);
+ builder.setMessage(R.string.data_usage_auto_sync_on_dialog);
+ }
+
+ builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ ContentResolver.setMasterSyncAutomatically(mEnabling);
+ }
+ });
+ builder.setNegativeButton(android.R.string.cancel, null);
+
+ return builder.create();
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putBoolean(SAVE_ENABLING, mEnabling);
+ }
+ }
+
+ /**
+ * Compute default tab that should be selected, based on
+ * {@link NetworkPolicyManager#EXTRA_NETWORK_TEMPLATE} extra.
+ */
+ private static String computeTabFromIntent(Intent intent) {
+ final NetworkTemplate template = intent.getParcelableExtra(EXTRA_NETWORK_TEMPLATE);
+ if (template == null) return null;
+
+ switch (template.getMatchRule()) {
+ case MATCH_MOBILE_3G_LOWER:
+ return TAB_3G;
+ case MATCH_MOBILE_4G:
+ return TAB_4G;
+ case MATCH_MOBILE_ALL:
+ return TAB_MOBILE;
+ case MATCH_WIFI:
+ return TAB_WIFI;
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Background task that loads {@link UidDetail}, binding to
+ * {@link DataUsageAdapter} row item when finished.
+ */
+ private static class UidDetailTask extends AsyncTask {
+ private final UidDetailProvider mProvider;
+ private final AppItem mItem;
+ private final View mTarget;
+
+ private UidDetailTask(UidDetailProvider provider, AppItem item, View target) {
+ mProvider = checkNotNull(provider);
+ mItem = checkNotNull(item);
+ mTarget = checkNotNull(target);
+ }
+
+ public static void bindView(
+ UidDetailProvider provider, AppItem item, View target) {
+ final UidDetailTask existing = (UidDetailTask) target.getTag();
+ if (existing != null) {
+ existing.cancel(false);
+ }
+
+ final UidDetail cachedDetail = provider.getUidDetail(item.key, false);
+ if (cachedDetail != null) {
+ bindView(cachedDetail, target);
+ } else {
+ target.setTag(new UidDetailTask(provider, item, target).executeOnExecutor(
+ AsyncTask.THREAD_POOL_EXECUTOR));
+ }
+ }
+
+ private static void bindView(UidDetail detail, View target) {
+ final ImageView icon = (ImageView) target.findViewById(android.R.id.icon);
+ final TextView title = (TextView) target.findViewById(android.R.id.title);
+
+ if (detail != null) {
+ icon.setImageDrawable(detail.icon);
+ title.setText(detail.label);
+ } else {
+ icon.setImageDrawable(null);
+ title.setText(null);
+ }
+ }
+
+ @Override
+ protected void onPreExecute() {
+ bindView(null, mTarget);
+ }
+
+ @Override
+ protected UidDetail doInBackground(Void... params) {
+ return mProvider.getUidDetail(mItem.key, true);
+ }
+
+ @Override
+ protected void onPostExecute(UidDetail result) {
+ bindView(result, mTarget);
+ }
+ }
+
+ /**
+ * Test if device has a mobile data radio with SIM in ready state.
+ */
+ public static boolean hasReadyMobileRadio(Context context) {
+ if (TEST_RADIOS) {
+ return SystemProperties.get(TEST_RADIOS_PROP).contains("mobile");
+ }
+
+ final ConnectivityManager conn = ConnectivityManager.from(context);
+ final TelephonyManager tele = TelephonyManager.from(context);
+
+ // require both supported network and ready SIM
+ return conn.isNetworkSupported(TYPE_MOBILE) && tele.getSimState() == SIM_STATE_READY;
+ }
+
+ /**
+ * Test if device has a mobile 4G data radio.
+ */
+ public static boolean hasReadyMobile4gRadio(Context context) {
+ if (!NetworkPolicyEditor.ENABLE_SPLIT_POLICIES) {
+ return false;
+ }
+ if (TEST_RADIOS) {
+ return SystemProperties.get(TEST_RADIOS_PROP).contains("4g");
+ }
+
+ final ConnectivityManager conn = ConnectivityManager.from(context);
+ final TelephonyManager tele = TelephonyManager.from(context);
+
+ final boolean hasWimax = conn.isNetworkSupported(TYPE_WIMAX);
+ final boolean hasLte = (tele.getLteOnCdmaMode() == PhoneConstants.LTE_ON_CDMA_TRUE)
+ && hasReadyMobileRadio(context);
+ return hasWimax || hasLte;
+ }
+
+ /**
+ * Test if device has a Wi-Fi data radio.
+ */
+ public static boolean hasWifiRadio(Context context) {
+ if (TEST_RADIOS) {
+ return SystemProperties.get(TEST_RADIOS_PROP).contains("wifi");
+ }
+
+ final ConnectivityManager conn = ConnectivityManager.from(context);
+ return conn.isNetworkSupported(TYPE_WIFI);
+ }
+
+ /**
+ * Test if device has an ethernet network connection.
+ */
+ public boolean hasEthernet(Context context) {
+ if (TEST_RADIOS) {
+ return SystemProperties.get(TEST_RADIOS_PROP).contains("ethernet");
+ }
+
+ final ConnectivityManager conn = ConnectivityManager.from(context);
+ final boolean hasEthernet = conn.isNetworkSupported(TYPE_ETHERNET);
+
+ final long ethernetBytes;
+ if (mStatsSession != null) {
+ try {
+ ethernetBytes = mStatsSession.getSummaryForNetwork(
+ NetworkTemplate.buildTemplateEthernet(), Long.MIN_VALUE, Long.MAX_VALUE)
+ .getTotalBytes();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ ethernetBytes = 0;
+ }
+
+ // only show ethernet when both hardware present and traffic has occurred
+ return hasEthernet && ethernetBytes > 0;
+ }
+
+ /**
+ * Inflate a {@link Preference} style layout, adding the given {@link View}
+ * widget into {@link android.R.id#widget_frame}.
+ */
+ private static View inflatePreference(LayoutInflater inflater, ViewGroup root, View widget) {
+ final View view = inflater.inflate(R.layout.preference, root, false);
+ final LinearLayout widgetFrame = (LinearLayout) view.findViewById(
+ android.R.id.widget_frame);
+ widgetFrame.addView(widget, new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+ return view;
+ }
+
+ private static View inflateAppTitle(
+ LayoutInflater inflater, ViewGroup root, CharSequence label) {
+ final TextView view = (TextView) inflater.inflate(
+ R.layout.data_usage_app_title, root, false);
+ view.setText(label);
+ return view;
+ }
+
+ /**
+ * Test if any networks are currently limited.
+ */
+ private boolean hasLimitedNetworks() {
+ return !buildLimitedNetworksList().isEmpty();
+ }
+
+ /**
+ * Build string describing currently limited networks, which defines when
+ * background data is restricted.
+ */
+ @Deprecated
+ private CharSequence buildLimitedNetworksString() {
+ final List limited = buildLimitedNetworksList();
+
+ // handle case where no networks limited
+ if (limited.isEmpty()) {
+ limited.add(getText(R.string.data_usage_list_none));
+ }
+
+ return TextUtils.join(limited);
+ }
+
+ /**
+ * Build list of currently limited networks, which defines when background
+ * data is restricted.
+ */
+ @Deprecated
+ private List buildLimitedNetworksList() {
+ final Context context = getActivity();
+
+ // build combined list of all limited networks
+ final ArrayList limited = Lists.newArrayList();
+
+ final TelephonyManager tele = TelephonyManager.from(context);
+ if (tele.getSimState() == SIM_STATE_READY) {
+ final String subscriberId = getActiveSubscriberId(context);
+ if (mPolicyEditor.hasLimitedPolicy(buildTemplateMobileAll(subscriberId))) {
+ limited.add(getText(R.string.data_usage_list_mobile));
+ }
+ if (mPolicyEditor.hasLimitedPolicy(buildTemplateMobile3gLower(subscriberId))) {
+ limited.add(getText(R.string.data_usage_tab_3g));
+ }
+ if (mPolicyEditor.hasLimitedPolicy(buildTemplateMobile4g(subscriberId))) {
+ limited.add(getText(R.string.data_usage_tab_4g));
+ }
+ }
+
+ if (mPolicyEditor.hasLimitedPolicy(buildTemplateWifiWildcard())) {
+ limited.add(getText(R.string.data_usage_tab_wifi));
+ }
+ if (mPolicyEditor.hasLimitedPolicy(buildTemplateEthernet())) {
+ limited.add(getText(R.string.data_usage_tab_ethernet));
+ }
+
+ return limited;
+ }
+
+ /**
+ * Inset both selector and divider {@link Drawable} on the given
+ * {@link ListView} by the requested dimensions.
+ */
+ private static void insetListViewDrawables(ListView view, int insetSide) {
+ final Drawable selector = view.getSelector();
+ final Drawable divider = view.getDivider();
+
+ // fully unregister these drawables so callbacks can be maintained after
+ // wrapping below.
+ final Drawable stub = new ColorDrawable(Color.TRANSPARENT);
+ view.setSelector(stub);
+ view.setDivider(stub);
+
+ view.setSelector(new InsetBoundsDrawable(selector, insetSide));
+ view.setDivider(new InsetBoundsDrawable(divider, insetSide));
+ }
+
+ /**
+ * Set {@link android.R.id#title} for a preference view inflated with
+ * {@link #inflatePreference(LayoutInflater, ViewGroup, View)}.
+ */
+ private static void setPreferenceTitle(View parent, int resId) {
+ final TextView title = (TextView) parent.findViewById(android.R.id.title);
+ title.setText(resId);
+ }
+
+ /**
+ * Set {@link android.R.id#summary} for a preference view inflated with
+ * {@link #inflatePreference(LayoutInflater, ViewGroup, View)}.
+ */
+ private static void setPreferenceSummary(View parent, CharSequence string) {
+ final TextView summary = (TextView) parent.findViewById(android.R.id.summary);
+ summary.setVisibility(View.VISIBLE);
+ summary.setText(string);
+ }
+}
diff --git a/src/com/android/settings/DateTimeSettings.java b/src/com/android/settings/DateTimeSettings.java
new file mode 100644
index 0000000..9cad4e8
--- /dev/null
+++ b/src/com/android/settings/DateTimeSettings.java
@@ -0,0 +1,414 @@
+/*
+ * 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.android.settings;
+
+import android.app.Activity;
+import android.app.DatePickerDialog;
+import android.app.Dialog;
+import android.app.TimePickerDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.preference.CheckBoxPreference;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.PreferenceScreen;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.text.format.DateFormat;
+import android.widget.DatePicker;
+import android.widget.TimePicker;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+
+public class DateTimeSettings extends SettingsPreferenceFragment implements
+ OnSharedPreferenceChangeListener, TimePickerDialog.OnTimeSetListener,
+ DatePickerDialog.OnDateSetListener {
+
+ private static final String HOURS_12 = "12";
+ private static final String HOURS_24 = "24";
+
+ // Used for showing the current date format, which looks like "12/31/2010",
+ // "2010/12/13", etc.
+ // The date value is dummy (independent of actual date).
+ private Calendar mDummyDate;
+
+ private static final String KEY_DATE_FORMAT = "date_format";
+ private static final String KEY_AUTO_TIME = "auto_time";
+ private static final String KEY_AUTO_TIME_ZONE = "auto_zone";
+
+ private static final int DIALOG_DATEPICKER = 0;
+ private static final int DIALOG_TIMEPICKER = 1;
+
+ // have we been launched from the setup wizard?
+ protected static final String EXTRA_IS_FIRST_RUN = "firstRun";
+
+ private CheckBoxPreference mAutoTimePref;
+ private Preference mTimePref;
+ private Preference mTime24Pref;
+ private CheckBoxPreference mAutoTimeZonePref;
+ private Preference mTimeZone;
+ private Preference mCity;
+ private Preference mDatePref;
+ private ListPreference mDateFormat;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ addPreferencesFromResource(R.xml.date_time_prefs);
+
+ initUI();
+ }
+
+ private void initUI() {
+ boolean autoTimeEnabled = getAutoState(Settings.Global.AUTO_TIME);
+ boolean autoTimeZoneEnabled = getAutoState(Settings.Global.AUTO_TIME_ZONE);
+
+ Intent intent = getActivity().getIntent();
+ boolean isFirstRun = intent.getBooleanExtra(EXTRA_IS_FIRST_RUN, false);
+
+ mDummyDate = Calendar.getInstance();
+
+ mAutoTimePref = (CheckBoxPreference) findPreference(KEY_AUTO_TIME);
+ mAutoTimePref.setChecked(autoTimeEnabled);
+ mAutoTimeZonePref = (CheckBoxPreference) findPreference(KEY_AUTO_TIME_ZONE);
+ // Override auto-timezone if it's a wifi-only device or if we're still
+ // in setup wizard.
+ // TODO: Remove the wifiOnly test when auto-timezone is implemented
+ // based on wifi-location.
+ if (Utils.isWifiOnly(getActivity()) || isFirstRun) {
+ getPreferenceScreen().removePreference(mAutoTimeZonePref);
+ autoTimeZoneEnabled = false;
+ }
+ mAutoTimeZonePref.setChecked(autoTimeZoneEnabled);
+
+ mTimePref = findPreference("time");
+ mTime24Pref = findPreference("24 hour");
+ mTimeZone = findPreference("timezone");
+ mCity = findPreference("city");
+ mDatePref = findPreference("date");
+ mDateFormat = (ListPreference) findPreference(KEY_DATE_FORMAT);
+ if (isFirstRun) {
+ getPreferenceScreen().removePreference(mTime24Pref);
+ getPreferenceScreen().removePreference(mDateFormat);
+ }
+
+ String[] dateFormats = getResources().getStringArray(
+ R.array.date_format_values);
+ String[] formattedDates = new String[dateFormats.length];
+ String currentFormat = getDateFormat();
+ // Initialize if DATE_FORMAT is not set in the system settings
+ // This can happen after a factory reset (or data wipe)
+ if (currentFormat == null) {
+ currentFormat = "";
+ }
+ for (int i = 0; i < formattedDates.length; i++) {
+ String formatted = DateFormat.getDateFormatForSetting(
+ getActivity(), dateFormats[i]).format(mDummyDate.getTime());
+
+ if (dateFormats[i].length() == 0) {
+ formattedDates[i] = getResources().getString(
+ R.string.normal_date_format, formatted);
+ } else {
+ formattedDates[i] = formatted;
+ }
+ }
+
+ mDateFormat.setEntries(formattedDates);
+ mDateFormat.setEntryValues(R.array.date_format_values);
+ mDateFormat.setValue(currentFormat);
+
+ mTimePref.setEnabled(!autoTimeEnabled);
+ mDatePref.setEnabled(!autoTimeEnabled);
+ mTimeZone.setEnabled(!autoTimeZoneEnabled);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ getPreferenceScreen().getSharedPreferences()
+ .registerOnSharedPreferenceChangeListener(this);
+
+ ((CheckBoxPreference) mTime24Pref).setChecked(is24Hour());
+
+ // 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);
+ getPreferenceScreen().getSharedPreferences()
+ .unregisterOnSharedPreferenceChangeListener(this);
+ }
+
+ public void updateTimeAndDateDisplay(Context context) {
+ java.text.DateFormat shortDateFormat = DateFormat.getDateFormat(context);
+ final Calendar now = Calendar.getInstance();
+ mDummyDate.setTimeZone(now.getTimeZone());
+ // We use December 31st because it's unambiguous when demonstrating the date format.
+ // We use 13:00 so we can demonstrate the 12/24 hour options.
+ mDummyDate.set(now.get(Calendar.YEAR), 11, 31, 13, 0, 0);
+ Date dummyDate = mDummyDate.getTime();
+ mTimePref.setSummary(DateFormat.getTimeFormat(getActivity()).format(now.getTime()));
+ mTimeZone.setSummary(getTimeZoneText(now.getTimeZone()));
+ mDatePref.setSummary(shortDateFormat.format(now.getTime()));
+ mDateFormat.setSummary(shortDateFormat.format(dummyDate));
+ mTime24Pref.setSummary(DateFormat.getTimeFormat(getActivity()).format(dummyDate));
+ mCity.setSummary(getCity());
+ }
+
+ @Override
+ public void onDateSet(DatePicker view, int year, int month, int day) {
+ setDate(year, month, day);
+ final Activity activity = getActivity();
+ if (activity != null) {
+ updateTimeAndDateDisplay(activity);
+ }
+ }
+
+ @Override
+ public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
+ setTime(hourOfDay, minute);
+ final Activity activity = getActivity();
+ if (activity != null) {
+ updateTimeAndDateDisplay(activity);
+ }
+
+ // We don't need to call timeUpdated() here because the TIME_CHANGED
+ // broadcast is sent by the AlarmManager as a side effect of setting the
+ // SystemClock time.
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences preferences,
+ String key) {
+ if (key.equals(KEY_DATE_FORMAT)) {
+ String format = preferences.getString(key, getResources()
+ .getString(R.string.default_date_format));
+ Settings.System.putString(getContentResolver(),
+ Settings.System.DATE_FORMAT, format);
+ updateTimeAndDateDisplay(getActivity());
+ } else if (key.equals(KEY_AUTO_TIME)) {
+ boolean autoEnabled = preferences.getBoolean(key, true);
+ Settings.Global.putInt(getContentResolver(),
+ Settings.Global.AUTO_TIME, autoEnabled ? 1 : 0);
+ mTimePref.setEnabled(!autoEnabled);
+ mDatePref.setEnabled(!autoEnabled);
+ } else if (key.equals(KEY_AUTO_TIME_ZONE)) {
+ boolean autoZoneEnabled = preferences.getBoolean(key, true);
+ Settings.Global.putInt(getContentResolver(),
+ Settings.Global.AUTO_TIME_ZONE, autoZoneEnabled ? 1 : 0);
+ mTimeZone.setEnabled(!autoZoneEnabled);
+ }
+ }
+
+ @Override
+ public Dialog onCreateDialog(int id) {
+ Dialog d;
+
+ switch (id) {
+ case DIALOG_DATEPICKER: {
+ final Calendar calendar = Calendar.getInstance();
+ d = new DatePickerDialog(getActivity(), this,
+ calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH),
+ calendar.get(Calendar.DAY_OF_MONTH));
+ break;
+ }
+ case DIALOG_TIMEPICKER: {
+ final Calendar calendar = Calendar.getInstance();
+ d = new TimePickerDialog(getActivity(), this,
+ calendar.get(Calendar.HOUR_OF_DAY),
+ calendar.get(Calendar.MINUTE),
+ DateFormat.is24HourFormat(getActivity()));
+ break;
+ }
+ default:
+ d = null;
+ break;
+ }
+
+ return d;
+ }
+
+ /*
+ * @Override public void onPrepareDialog(int id, Dialog d) { switch (id) {
+ * case DIALOG_DATEPICKER: { DatePickerDialog datePicker =
+ * (DatePickerDialog)d; final Calendar calendar = Calendar.getInstance();
+ * datePicker.updateDate( calendar.get(Calendar.YEAR),
+ * calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH));
+ * break; } case DIALOG_TIMEPICKER: { TimePickerDialog timePicker =
+ * (TimePickerDialog)d; final Calendar calendar = Calendar.getInstance();
+ * timePicker.updateTime( calendar.get(Calendar.HOUR_OF_DAY),
+ * calendar.get(Calendar.MINUTE)); break; } default: break; } }
+ */
+ @Override
+ public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
+ Preference preference) {
+ if (preference == mDatePref) {
+ showDialog(DIALOG_DATEPICKER);
+ } else if (preference == mTimePref) {
+ // The 24-hour mode may have changed, so recreate the dialog
+ removeDialog(DIALOG_TIMEPICKER);
+ showDialog(DIALOG_TIMEPICKER);
+ } else if (preference == mTime24Pref) {
+ set24Hour(((CheckBoxPreference) mTime24Pref).isChecked());
+ updateTimeAndDateDisplay(getActivity());
+ timeUpdated();
+ }
+ return super.onPreferenceTreeClick(preferenceScreen, preference);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ updateTimeAndDateDisplay(getActivity());
+ }
+
+ private void timeUpdated() {
+ Intent timeChanged = new Intent(Intent.ACTION_TIME_CHANGED);
+ getActivity().sendBroadcast(timeChanged);
+ }
+
+ /* Get & Set values from the system settings */
+
+ private boolean is24Hour() {
+ return DateFormat.is24HourFormat(getActivity());
+ }
+
+ private void set24Hour(boolean is24Hour) {
+ Settings.System.putString(getContentResolver(),
+ Settings.System.TIME_12_24, is24Hour ? HOURS_24 : HOURS_12);
+ }
+
+ private String getDateFormat() {
+ return Settings.System.getString(getContentResolver(),
+ Settings.System.DATE_FORMAT);
+ }
+
+ private boolean getAutoState(String name) {
+ try {
+ return Settings.Global.getInt(getContentResolver(), name) > 0;
+ } catch (SettingNotFoundException snfe) {
+ return false;
+ }
+ }
+
+ /* package */static void setDate(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) {
+ SystemClock.setCurrentTimeMillis(when);
+ }
+ }
+
+ /* package */static void setTime(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) {
+ SystemClock.setCurrentTimeMillis(when);
+ }
+ }
+
+ /* Helper routines to format timezone */
+
+ /* package */static String getTimeZoneText(TimeZone tz) {
+ // Similar to new SimpleDateFormat("'GMT'Z, zzzz").format(new Date()),
+ // but
+ // we want "GMT-03:00" rather than "GMT-0300".
+ Date now = new Date();
+ return formatOffset(new StringBuilder(), tz, now)
+ .append(", ")
+ .append(tz.getDisplayName(tz.inDaylightTime(now), TimeZone.LONG))
+ .toString();
+ }
+
+ private static StringBuilder formatOffset(StringBuilder sb, TimeZone tz,
+ Date d) {
+ int off = tz.getOffset(d.getTime()) / 1000 / 60;
+
+ sb.append("GMT");
+ if (off < 0) {
+ sb.append('-');
+ off = -off;
+ } else {
+ sb.append('+');
+ }
+
+ int hours = off / 60;
+ int minutes = off % 60;
+
+ sb.append((char) ('0' + hours / 10));
+ sb.append((char) ('0' + hours % 10));
+
+ sb.append(':');
+
+ sb.append((char) ('0' + minutes / 10));
+ sb.append((char) ('0' + minutes % 10));
+
+ return sb;
+ }
+
+ private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final Activity activity = getActivity();
+ if (activity != null) {
+ updateTimeAndDateDisplay(activity);
+ }
+ }
+ };
+
+ private String getCity() {
+ String city = getString(R.string.default_city);
+ try {
+ city = Settings.System.getString(
+ getActivity().getContentResolver(), "city");
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ city = getString(R.string.default_city);
+ }
+ return city;
+ }
+}
diff --git a/src/com/android/settings/DateTimeSettingsSetupWizard.java b/src/com/android/settings/DateTimeSettingsSetupWizard.java
new file mode 100644
index 0000000..af0e96a
--- /dev/null
+++ b/src/com/android/settings/DateTimeSettingsSetupWizard.java
@@ -0,0 +1,332 @@
+/*
+ * 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.android.settings;
+
+import android.app.Activity;
+import android.app.AlarmManager;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceFragment;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.Window;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.Button;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.DatePicker;
+import android.widget.LinearLayout;
+import android.widget.ListPopupWindow;
+import android.widget.SimpleAdapter;
+import android.widget.TextView;
+import android.widget.TimePicker;
+
+import java.util.Calendar;
+import java.util.TimeZone;
+
+public class DateTimeSettingsSetupWizard extends Activity{
+ private static final String TAG = DateTimeSettingsSetupWizard.class.getSimpleName();
+
+ // force the first status of auto datetime flag.
+ private static final String EXTRA_INITIAL_AUTO_DATETIME_VALUE =
+ "extra_initial_auto_datetime_value";
+
+ // If we have enough screen real estate, we use a radically different layout with
+ // big date and time pickers right on the screen, which requires very different handling.
+ // Otherwise, we use the standard date time settings fragment.
+ private boolean mUsingXLargeLayout;
+
+ /* Available only in XL */
+ private CompoundButton mAutoDateTimeButton;
+ // private CompoundButton mAutoTimeZoneButton;
+
+ private Button mTimeZoneButton;
+ private ListPopupWindow mTimeZonePopup;
+ private SimpleAdapter mTimeZoneAdapter;
+ private TimeZone mSelectedTimeZone;
+
+ private TimePicker mTimePicker;
+ private DatePicker mDatePicker;
+ private InputMethodManager mInputMethodManager;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.date_time_settings_setupwizard);
+
+ /* // we know we've loaded the special xlarge layout because it has controls
+ // not present in the standard layout
+ mUsingXLargeLayout = findViewById(R.id.time_zone_button) != null;
+ if (mUsingXLargeLayout) {
+ initUiForXl();
+ } else {
+ findViewById(R.id.next_button).setOnClickListener(this);
+ }
+ mTimeZoneAdapter = ZonePicker.constructTimezoneAdapter(this, false,
+ R.layout.date_time_setup_custom_list_item_2);
+
+ // For the normal view, disable Back since changes stick immediately
+ // and can't be canceled, and we already have a Next button. For xLarge,
+ // though, we save up our changes and set them upon Next, so Back can
+ // cancel. And also, in xlarge, we need the keyboard dismiss button
+ // to be available.
+ if (!mUsingXLargeLayout) {
+ final View layoutRoot = findViewById(R.id.layout_root);
+ layoutRoot.setSystemUiVisibility(View.STATUS_BAR_DISABLE_BACK);
+ }
+ }
+
+ public void initUiForXl() {
+ // Currently just comment out codes related to auto timezone.
+ // TODO: Remove them when we are sure they are unnecessary.
+
+ final boolean autoTimeZoneEnabled = isAutoTimeZoneEnabled();
+ mAutoTimeZoneButton = (CompoundButton)findViewById(R.id.time_zone_auto);
+ mAutoTimeZoneButton.setChecked(autoTimeZoneEnabled);
+ mAutoTimeZoneButton.setOnCheckedChangeListener(this);
+ mAutoTimeZoneButton.setText(autoTimeZoneEnabled ? R.string.zone_auto_summaryOn :
+ R.string.zone_auto_summaryOff);
+
+ final TimeZone tz = TimeZone.getDefault();
+ mSelectedTimeZone = tz;
+ mTimeZoneButton = (Button)findViewById(R.id.time_zone_button);
+ mTimeZoneButton.setText(tz.getDisplayName());
+ // mTimeZoneButton.setText(DateTimeSettings.getTimeZoneText(tz));
+ mTimeZoneButton.setOnClickListener(this);
+
+ final boolean autoDateTimeEnabled;
+ final Intent intent = getIntent();
+ if (intent.hasExtra(EXTRA_INITIAL_AUTO_DATETIME_VALUE)) {
+ autoDateTimeEnabled = intent.getBooleanExtra(EXTRA_INITIAL_AUTO_DATETIME_VALUE, false);
+ } else {
+ autoDateTimeEnabled = isAutoDateTimeEnabled();
+ }
+
+ mAutoDateTimeButton = (CompoundButton)findViewById(R.id.date_time_auto_button);
+ mAutoDateTimeButton.setChecked(autoDateTimeEnabled);
+ mAutoDateTimeButton.setOnCheckedChangeListener(this);
+
+ mTimePicker = (TimePicker)findViewById(R.id.time_picker);
+ mTimePicker.setEnabled(!autoDateTimeEnabled);
+ mDatePicker = (DatePicker)findViewById(R.id.date_picker);
+ mDatePicker.setEnabled(!autoDateTimeEnabled);
+ mDatePicker.setCalendarViewShown(false);
+
+ mInputMethodManager = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
+
+ ((Button)findViewById(R.id.next_button)).setOnClickListener(this);
+ final Button skipButton = (Button)findViewById(R.id.skip_button);
+ if (skipButton != null) {
+ skipButton.setOnClickListener(this);
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_TIME_TICK);
+ filter.addAction(Intent.ACTION_TIME_CHANGED);
+ filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+ registerReceiver(mIntentReceiver, filter, null, null);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ unregisterReceiver(mIntentReceiver);
+ }
+
+ @Override
+ public void onClick(View view) {
+ switch (view.getId()) {
+ case R.id.time_zone_button: {
+ showTimezonePicker(R.id.time_zone_button);
+ break;
+ }
+ case R.id.next_button: {
+ if (mSelectedTimeZone != null) {
+ final TimeZone systemTimeZone = TimeZone.getDefault();
+ if (!systemTimeZone.equals(mSelectedTimeZone)) {
+ Log.i(TAG, "Another TimeZone is selected by a user. Changing system TimeZone.");
+ final AlarmManager alarm = (AlarmManager)
+ getSystemService(Context.ALARM_SERVICE);
+ alarm.setTimeZone(mSelectedTimeZone.getID());
+ }
+ }
+ if (mAutoDateTimeButton != null) {
+ Settings.Global.putInt(getContentResolver(), Settings.Global.AUTO_TIME,
+ mAutoDateTimeButton.isChecked() ? 1 : 0);
+ if (!mAutoDateTimeButton.isChecked()) {
+ DateTimeSettings.setDate(mDatePicker.getYear(), mDatePicker.getMonth(),
+ mDatePicker.getDayOfMonth());
+ DateTimeSettings.setTime(
+ mTimePicker.getCurrentHour(), mTimePicker.getCurrentMinute());
+ }
+ }
+ } // $FALL-THROUGH$
+ case R.id.skip_button: {
+ setResult(RESULT_OK);
+ finish();
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ final boolean autoEnabled = isChecked; // just for readibility.
+ if (buttonView == mAutoTimeZoneButton) {
+ // In XL screen, we save all the state only when the next button is pressed.
+ if (!mUsingXLargeLayout) {
+ Settings.Global.putInt(getContentResolver(),
+ Settings.Global.AUTO_TIME_ZONE,
+ isChecked ? 1 : 0);
+ }
+ mTimeZone.setEnabled(!autoEnabled);
+ if (isChecked) {
+ findViewById(R.id.current_time_zone).setVisibility(View.VISIBLE);
+ findViewById(R.id.zone_picker).setVisibility(View.GONE);
+ }
+ } else
+ if (buttonView == mAutoDateTimeButton) {
+ Settings.Global.putInt(getContentResolver(),
+ Settings.Global.AUTO_TIME,
+ isChecked ? 1 : 0);
+ mTimePicker.setEnabled(!autoEnabled);
+ mDatePicker.setEnabled(!autoEnabled);
+ }
+ if (autoEnabled) {
+ final View focusedView = getCurrentFocus();
+ if (focusedView != null) {
+ mInputMethodManager.hideSoftInputFromWindow(focusedView.getWindowToken(), 0);
+ focusedView.clearFocus();
+ }
+ }
+ }
+
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ final TimeZone tz = ZonePicker.obtainTimeZoneFromItem(parent.getItemAtPosition(position));
+ if (mUsingXLargeLayout) {
+ mSelectedTimeZone = tz;
+ final Calendar now = Calendar.getInstance(tz);
+ if (mTimeZoneButton != null) {
+ mTimeZoneButton.setText(tz.getDisplayName());
+ }
+ // mTimeZoneButton.setText(DateTimeSettings.getTimeZoneText(tz));
+ mDatePicker.updateDate(now.get(Calendar.YEAR), now.get(Calendar.MONTH),
+ now.get(Calendar.DAY_OF_MONTH));
+ mTimePicker.setCurrentHour(now.get(Calendar.HOUR_OF_DAY));
+ mTimePicker.setCurrentMinute(now.get(Calendar.MINUTE));
+ } else {
+ // in prefs mode, we actually change the setting right now, as opposed to waiting
+ // until Next is pressed in xLarge mode
+ final AlarmManager alarm = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
+ alarm.setTimeZone(tz.getID());
+ DateTimeSettings settingsFragment = (DateTimeSettings) getFragmentManager().
+ findFragmentById(R.id.date_time_settings_fragment);
+ settingsFragment.updateTimeAndDateDisplay(this);
+ }
+ mTimeZonePopup.dismiss();
+ }
+
+ *//**
+ * If this is called, that means we're in prefs style portrait mode for a large display
+ * and the user has tapped on the time zone preference. If we were a PreferenceActivity,
+ * we'd then launch the timezone fragment in a new activity, but we aren't, and here
+ * on a tablet display, we really want more of a popup picker look' like the one we use
+ * for the xlarge version of this activity. So we just take this opportunity to launch that.
+ *
+ * TODO: For phones, we might want to change this to do the "normal" opening
+ * of the zonepicker fragment in its own activity. Or we might end up just
+ * creating a separate DateTimeSettingsSetupWizardPhone activity that subclasses
+ * PreferenceActivity in the first place to handle all that automatically.
+ *//*
+ @Override
+ public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
+ showTimezonePicker(R.id.timezone_dropdown_anchor);
+ return true;
+ }
+
+ private void showTimezonePicker(int anchorViewId) {
+ View anchorView = findViewById(anchorViewId);
+ if (anchorView == null) {
+ Log.e(TAG, "Unable to find zone picker anchor view " + anchorViewId);
+ return;
+ }
+ mTimeZonePopup = new ListPopupWindow(this, null);
+ mTimeZonePopup.setWidth(anchorView.getWidth());
+ mTimeZonePopup.setAnchorView(anchorView);
+ mTimeZonePopup.setAdapter(mTimeZoneAdapter);
+ mTimeZonePopup.setOnItemClickListener(this);
+ mTimeZonePopup.setModal(true);
+ mTimeZonePopup.show();
+ }
+
+ private boolean isAutoDateTimeEnabled() {
+ try {
+ return Settings.Global.getInt(getContentResolver(), Settings.Global.AUTO_TIME) > 0;
+ } catch (SettingNotFoundException e) {
+ return true;
+ }
+ }
+
+
+ private boolean isAutoTimeZoneEnabled() {
+ try {
+ return Settings.Global.getInt(getContentResolver(),
+ Settings.Global.AUTO_TIME_ZONE) > 0;
+ } catch (SettingNotFoundException e) {
+ return true;
+ }
+ }
+
+ private void updateTimeAndDateDisplay() {
+ if (!mUsingXLargeLayout) {
+ return;
+ }
+ final Calendar now = Calendar.getInstance();
+ mTimeZoneButton.setText(now.getTimeZone().getDisplayName());
+ mDatePicker.updateDate(now.get(Calendar.YEAR), now.get(Calendar.MONTH),
+ now.get(Calendar.DAY_OF_MONTH));
+ mTimePicker.setCurrentHour(now.get(Calendar.HOUR_OF_DAY));
+ mTimePicker.setCurrentMinute(now.get(Calendar.MINUTE));
+ }
+
+ private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ updateTimeAndDateDisplay();
+ }
+ };*/
+}
+}
diff --git a/src/com/android/settings/DebugIntentSender.java b/src/com/android/settings/DebugIntentSender.java
new file mode 100644
index 0000000..9fed947
--- /dev/null
+++ b/src/com/android/settings/DebugIntentSender.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import android.app.Activity;
+import android.widget.EditText;
+import android.widget.Button;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.text.TextUtils;
+import android.text.Spannable;
+import android.text.Selection;
+import android.net.Uri;
+
+/**
+ * A simple activity that provides a UI for sending intents
+ */
+public class DebugIntentSender extends Activity {
+ private EditText mIntentField;
+ private EditText mDataField;
+ private EditText mAccountField;
+ private EditText mResourceField;
+ private Button mSendBroadcastButton;
+ private Button mStartActivityButton;
+ private View.OnClickListener mClicked = new View.OnClickListener() {
+ public void onClick(View v) {
+ if ((v == mSendBroadcastButton) ||
+ (v == mStartActivityButton)) {
+ String intentAction = mIntentField.getText().toString();
+ String intentData = mDataField.getText().toString();
+ String account = mAccountField.getText().toString();
+ String resource = mResourceField.getText().toString();
+
+ Intent intent = new Intent(intentAction);
+ if (!TextUtils.isEmpty(intentData)) {
+ intent.setData(Uri.parse(intentData));
+ }
+ intent.putExtra("account", account);
+ intent.putExtra("resource", resource);
+ if (v == mSendBroadcastButton) {
+ sendBroadcast(intent);
+ } else {
+ startActivity(intent);
+ }
+
+ setResult(RESULT_OK);
+ finish();
+ }
+ }
+ };
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.intent_sender);
+
+ mIntentField = (EditText) findViewById(R.id.intent);
+ mIntentField.setText(Intent.ACTION_SYNC);
+ Selection.selectAll((Spannable) mIntentField.getText());
+
+ mDataField = (EditText) findViewById(R.id.data);
+ mDataField.setBackgroundResource(android.R.drawable.editbox_background);
+
+ mAccountField = (EditText) findViewById(R.id.account);
+ mResourceField = (EditText) findViewById(R.id.resource);
+
+ mSendBroadcastButton = (Button) findViewById(R.id.sendbroadcast);
+ mSendBroadcastButton.setOnClickListener(mClicked);
+
+ mStartActivityButton = (Button) findViewById(R.id.startactivity);
+ mStartActivityButton.setOnClickListener(mClicked);
+ }
+}
diff --git a/src/com/android/settings/DefaultRingtonePreference.java b/src/com/android/settings/DefaultRingtonePreference.java
new file mode 100644
index 0000000..0801b1f
--- /dev/null
+++ b/src/com/android/settings/DefaultRingtonePreference.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+
+import android.content.Context;
+import android.content.Intent;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.preference.RingtonePreference;
+import android.util.AttributeSet;
+import android.util.Log;
+
+public class DefaultRingtonePreference extends RingtonePreference {
+ private static final String TAG = "DefaultRingtonePreference";
+
+ public DefaultRingtonePreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onPrepareRingtonePickerIntent(Intent ringtonePickerIntent) {
+ super.onPrepareRingtonePickerIntent(ringtonePickerIntent);
+
+ /*
+ * Since this preference is for choosing the default ringtone, it
+ * doesn't make sense to show a 'Default' item.
+ */
+ ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, false);
+ }
+
+ @Override
+ protected void onSaveRingtone(Uri ringtoneUri) {
+ RingtoneManager.setActualDefaultRingtoneUri(getContext(), getRingtoneType(), ringtoneUri);
+ }
+
+ @Override
+ protected Uri onRestoreRingtone() {
+ return RingtoneManager.getActualDefaultRingtoneUri(getContext(), getRingtoneType());
+ }
+
+}
diff --git a/src/com/android/settings/DevelopmentSettings.java b/src/com/android/settings/DevelopmentSettings.java
new file mode 100644
index 0000000..dc08b1c
--- /dev/null
+++ b/src/com/android/settings/DevelopmentSettings.java
@@ -0,0 +1,1264 @@
+/*
+ * 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.android.settings;
+
+import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
+
+import android.app.ActionBar;
+import android.app.Activity;
+import android.app.ActivityManagerNative;
+import android.app.ActivityThread;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.admin.DevicePolicyManager;
+import android.app.backup.IBackupManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.AsyncTask;
+import android.os.BatteryManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.StrictMode;
+import android.os.SystemProperties;
+import android.os.Trace;
+import android.preference.CheckBoxPreference;
+import android.preference.ListPreference;
+import android.preference.MultiCheckPreference;
+import android.preference.Preference;
+import android.preference.PreferenceCategory;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceGroup;
+import android.preference.PreferenceScreen;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.HardwareRenderer;
+import android.view.IWindowManager;
+import android.view.View;
+import android.widget.CompoundButton;
+import android.widget.Switch;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+/*
+ * Displays preferences for application developers.
+ */
+public class DevelopmentSettings extends PreferenceFragment
+ implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener,
+ OnPreferenceChangeListener, CompoundButton.OnCheckedChangeListener {
+
+ /**
+ * Preference file were development settings prefs are stored.
+ */
+ public static final String PREF_FILE = "development";
+
+ /**
+ * Whether to show the development settings to the user. Default is false.
+ */
+ public static final String PREF_SHOW = "show";
+
+ private static final String ENABLE_ADB = "enable_adb";
+ private static final String KEEP_SCREEN_ON = "keep_screen_on";
+ private static final String ALLOW_MOCK_LOCATION = "allow_mock_location";
+ private static final String HDCP_CHECKING_KEY = "hdcp_checking";
+ private static final String HDCP_CHECKING_PROPERTY = "persist.sys.hdcp_checking";
+ private static final String ENFORCE_READ_EXTERNAL = "enforce_read_external";
+ private static final String LOCAL_BACKUP_PASSWORD = "local_backup_password";
+ private static final String HARDWARE_UI_PROPERTY = "persist.sys.ui.hw";
+ private static final String MSAA_PROPERTY = "debug.egl.force_msaa";
+ private static final String BUGREPORT = "bugreport";
+ private static final String BUGREPORT_IN_POWER_KEY = "bugreport_in_power";
+ private static final String OPENGL_TRACES_PROPERTY = "debug.egl.trace";
+
+ private static final String DEBUG_APP_KEY = "debug_app";
+ private static final String WAIT_FOR_DEBUGGER_KEY = "wait_for_debugger";
+ private static final String VERIFY_APPS_OVER_USB_KEY = "verify_apps_over_usb";
+ private static final String STRICT_MODE_KEY = "strict_mode";
+ private static final String POINTER_LOCATION_KEY = "pointer_location";
+ private static final String SHOW_TOUCHES_KEY = "show_touches";
+ private static final String SHOW_SCREEN_UPDATES_KEY = "show_screen_updates";
+ private static final String DISABLE_OVERLAYS_KEY = "disable_overlays";
+ private static final String SHOW_CPU_USAGE_KEY = "show_cpu_usage";
+ private static final String FORCE_HARDWARE_UI_KEY = "force_hw_ui";
+ private static final String FORCE_MSAA_KEY = "force_msaa";
+ private static final String TRACK_FRAME_TIME_KEY = "track_frame_time";
+ private static final String SHOW_HW_SCREEN_UPDATES_KEY = "show_hw_screen_udpates";
+ private static final String SHOW_HW_LAYERS_UPDATES_KEY = "show_hw_layers_udpates";
+ private static final String SHOW_HW_OVERDRAW_KEY = "show_hw_overdraw";
+ private static final String DEBUG_LAYOUT_KEY = "debug_layout";
+ private static final String WINDOW_ANIMATION_SCALE_KEY = "window_animation_scale";
+ private static final String TRANSITION_ANIMATION_SCALE_KEY = "transition_animation_scale";
+ private static final String ANIMATOR_DURATION_SCALE_KEY = "animator_duration_scale";
+ private static final String OVERLAY_DISPLAY_DEVICES_KEY = "overlay_display_devices";
+ private static final String DEBUG_DEBUGGING_CATEGORY_KEY = "debug_debugging_category";
+ private static final String OPENGL_TRACES_KEY = "enable_opengl_traces";
+
+ private static final String ENABLE_TRACES_KEY = "enable_traces";
+
+ private static final String IMMEDIATELY_DESTROY_ACTIVITIES_KEY
+ = "immediately_destroy_activities";
+ private static final String APP_PROCESS_LIMIT_KEY = "app_process_limit";
+
+ private static final String SHOW_ALL_ANRS_KEY = "show_all_anrs";
+
+ private static final String TAG_CONFIRM_ENFORCE = "confirm_enforce";
+
+ private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
+
+ private static final int RESULT_DEBUG_APP = 1000;
+
+ private IWindowManager mWindowManager;
+ private IBackupManager mBackupManager;
+ private DevicePolicyManager mDpm;
+
+ private Switch mEnabledSwitch;
+ private boolean mLastEnabledState;
+ private boolean mHaveDebugSettings;
+ private boolean mDontPokeProperties;
+
+ private CheckBoxPreference mEnableAdb;
+ private Preference mBugreport;
+ private CheckBoxPreference mBugreportInPower;
+ private CheckBoxPreference mKeepScreenOn;
+ private CheckBoxPreference mEnforceReadExternal;
+ private CheckBoxPreference mAllowMockLocation;
+ private PreferenceScreen mPassword;
+
+ private String mDebugApp;
+ private Preference mDebugAppPref;
+ private CheckBoxPreference mWaitForDebugger;
+ private CheckBoxPreference mVerifyAppsOverUsb;
+
+ private CheckBoxPreference mStrictMode;
+ private CheckBoxPreference mPointerLocation;
+ private CheckBoxPreference mShowTouches;
+ private CheckBoxPreference mShowScreenUpdates;
+ private CheckBoxPreference mDisableOverlays;
+ private CheckBoxPreference mShowCpuUsage;
+ private CheckBoxPreference mForceHardwareUi;
+ private CheckBoxPreference mForceMsaa;
+ private CheckBoxPreference mTrackFrameTime;
+ private CheckBoxPreference mShowHwScreenUpdates;
+ private CheckBoxPreference mShowHwLayersUpdates;
+ private CheckBoxPreference mShowHwOverdraw;
+ private CheckBoxPreference mDebugLayout;
+ private ListPreference mWindowAnimationScale;
+ private ListPreference mTransitionAnimationScale;
+ private ListPreference mAnimatorDurationScale;
+ private ListPreference mOverlayDisplayDevices;
+ private ListPreference mOpenGLTraces;
+ private MultiCheckPreference mEnableTracesPref;
+
+ private CheckBoxPreference mImmediatelyDestroyActivities;
+ private ListPreference mAppProcessLimit;
+
+ private CheckBoxPreference mShowAllANRs;
+
+ private final ArrayList mAllPrefs = new ArrayList();
+ private final ArrayList mResetCbPrefs
+ = new ArrayList();
+
+ private final HashSet mDisabledPrefs = new HashSet();
+
+ // To track whether a confirmation dialog was clicked.
+ private boolean mDialogClicked;
+ private Dialog mEnableDialog;
+ private Dialog mAdbDialog;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ mWindowManager = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
+ mBackupManager = IBackupManager.Stub.asInterface(
+ ServiceManager.getService(Context.BACKUP_SERVICE));
+ mDpm = (DevicePolicyManager)getActivity().getSystemService(Context.DEVICE_POLICY_SERVICE);
+
+ addPreferencesFromResource(R.xml.development_prefs);
+
+ mEnableAdb = findAndInitCheckboxPref(ENABLE_ADB);
+ mBugreport = findPreference(BUGREPORT);
+ mBugreportInPower = findAndInitCheckboxPref(BUGREPORT_IN_POWER_KEY);
+ mKeepScreenOn = findAndInitCheckboxPref(KEEP_SCREEN_ON);
+ mEnforceReadExternal = findAndInitCheckboxPref(ENFORCE_READ_EXTERNAL);
+ mAllowMockLocation = findAndInitCheckboxPref(ALLOW_MOCK_LOCATION);
+ mPassword = (PreferenceScreen) findPreference(LOCAL_BACKUP_PASSWORD);
+ mAllPrefs.add(mPassword);
+
+ mDebugAppPref = findPreference(DEBUG_APP_KEY);
+ mAllPrefs.add(mDebugAppPref);
+ mWaitForDebugger = findAndInitCheckboxPref(WAIT_FOR_DEBUGGER_KEY);
+ mVerifyAppsOverUsb = findAndInitCheckboxPref(VERIFY_APPS_OVER_USB_KEY);
+ if (!showVerifierSetting()) {
+ PreferenceGroup debugDebuggingCategory = (PreferenceGroup)
+ findPreference(DEBUG_DEBUGGING_CATEGORY_KEY);
+ if (debugDebuggingCategory != null) {
+ debugDebuggingCategory.removePreference(mVerifyAppsOverUsb);
+ } else {
+ mVerifyAppsOverUsb.setEnabled(false);
+ }
+ }
+ mStrictMode = findAndInitCheckboxPref(STRICT_MODE_KEY);
+ mPointerLocation = findAndInitCheckboxPref(POINTER_LOCATION_KEY);
+ mShowTouches = findAndInitCheckboxPref(SHOW_TOUCHES_KEY);
+ mShowScreenUpdates = findAndInitCheckboxPref(SHOW_SCREEN_UPDATES_KEY);
+ mDisableOverlays = findAndInitCheckboxPref(DISABLE_OVERLAYS_KEY);
+ mShowCpuUsage = findAndInitCheckboxPref(SHOW_CPU_USAGE_KEY);
+ mForceHardwareUi = findAndInitCheckboxPref(FORCE_HARDWARE_UI_KEY);
+ mForceMsaa = findAndInitCheckboxPref(FORCE_MSAA_KEY);
+ mTrackFrameTime = findAndInitCheckboxPref(TRACK_FRAME_TIME_KEY);
+ mShowHwScreenUpdates = findAndInitCheckboxPref(SHOW_HW_SCREEN_UPDATES_KEY);
+ mShowHwLayersUpdates = findAndInitCheckboxPref(SHOW_HW_LAYERS_UPDATES_KEY);
+ mShowHwOverdraw = findAndInitCheckboxPref(SHOW_HW_OVERDRAW_KEY);
+ mDebugLayout = findAndInitCheckboxPref(DEBUG_LAYOUT_KEY);
+ mWindowAnimationScale = (ListPreference) findPreference(WINDOW_ANIMATION_SCALE_KEY);
+ mAllPrefs.add(mWindowAnimationScale);
+ mWindowAnimationScale.setOnPreferenceChangeListener(this);
+ mTransitionAnimationScale = (ListPreference) findPreference(TRANSITION_ANIMATION_SCALE_KEY);
+ mAllPrefs.add(mTransitionAnimationScale);
+ mTransitionAnimationScale.setOnPreferenceChangeListener(this);
+ mAnimatorDurationScale = (ListPreference) findPreference(ANIMATOR_DURATION_SCALE_KEY);
+ mAllPrefs.add(mAnimatorDurationScale);
+ mAnimatorDurationScale.setOnPreferenceChangeListener(this);
+ mOverlayDisplayDevices = (ListPreference) findPreference(OVERLAY_DISPLAY_DEVICES_KEY);
+ mAllPrefs.add(mOverlayDisplayDevices);
+ mOverlayDisplayDevices.setOnPreferenceChangeListener(this);
+ mOpenGLTraces = (ListPreference) findPreference(OPENGL_TRACES_KEY);
+ mAllPrefs.add(mOpenGLTraces);
+ mOpenGLTraces.setOnPreferenceChangeListener(this);
+ mEnableTracesPref = (MultiCheckPreference)findPreference(ENABLE_TRACES_KEY);
+ String[] traceValues = new String[Trace.TRACE_TAGS.length];
+ for (int i=Trace.TRACE_FLAGS_START_BIT; i 0) {
+ // A DeviceAdmin has specified a maximum time until the device
+ // will lock... in this case we can't allow the user to turn
+ // on "stay awake when plugged in" because that would defeat the
+ // restriction.
+ mDisabledPrefs.add(mKeepScreenOn);
+ } else {
+ mDisabledPrefs.remove(mKeepScreenOn);
+ }
+
+ final ContentResolver cr = getActivity().getContentResolver();
+ mLastEnabledState = Settings.Global.getInt(cr,
+ Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
+ mEnabledSwitch.setChecked(mLastEnabledState);
+ setPrefsEnabledState(mLastEnabledState);
+
+ if (mHaveDebugSettings && !mLastEnabledState) {
+ // Overall debugging is disabled, but there are some debug
+ // settings that are enabled. This is an invalid state. Switch
+ // to debug settings being enabled, so the user knows there is
+ // stuff enabled and can turn it all off if they want.
+ Settings.Global.putInt(getActivity().getContentResolver(),
+ Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1);
+ mLastEnabledState = true;
+ mEnabledSwitch.setChecked(mLastEnabledState);
+ setPrefsEnabledState(mLastEnabledState);
+ }
+ }
+
+ void updateCheckBox(CheckBoxPreference checkBox, boolean value) {
+ checkBox.setChecked(value);
+ mHaveDebugSettings |= value;
+ }
+
+ private void updateAllOptions() {
+ final Context context = getActivity();
+ final ContentResolver cr = context.getContentResolver();
+ mHaveDebugSettings = false;
+ updateCheckBox(mEnableAdb, Settings.Global.getInt(cr,
+ Settings.Global.ADB_ENABLED, 0) != 0);
+ updateCheckBox(mBugreportInPower, Settings.Secure.getInt(cr,
+ Settings.Secure.BUGREPORT_IN_POWER_MENU, 0) != 0);
+ updateCheckBox(mKeepScreenOn, Settings.Global.getInt(cr,
+ Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0) != 0);
+ updateCheckBox(mEnforceReadExternal, isPermissionEnforced(READ_EXTERNAL_STORAGE));
+ updateCheckBox(mAllowMockLocation, Settings.Secure.getInt(cr,
+ Settings.Secure.ALLOW_MOCK_LOCATION, 0) != 0);
+ updateHdcpValues();
+ updatePasswordSummary();
+ updateDebuggerOptions();
+ updateStrictModeVisualOptions();
+ updatePointerLocationOptions();
+ updateShowTouchesOptions();
+ updateFlingerOptions();
+ updateCpuUsageOptions();
+ updateHardwareUiOptions();
+ updateMsaaOptions();
+ updateTrackFrameTimeOptions();
+ updateShowHwScreenUpdatesOptions();
+ updateShowHwLayersUpdatesOptions();
+ updateShowHwOverdrawOptions();
+ updateDebugLayoutOptions();
+ updateAnimationScaleOptions();
+ updateOverlayDisplayDevicesOptions();
+ updateOpenGLTracesOptions();
+ updateEnableTracesOptions();
+ updateImmediatelyDestroyActivitiesOptions();
+ updateAppProcessLimitOptions();
+ updateShowAllANRsOptions();
+ updateVerifyAppsOverUsbOptions();
+ updateBugreportOptions();
+ }
+
+ private void resetDangerousOptions() {
+ mDontPokeProperties = true;
+ for (int i=0; i 0) {
+ String label;
+ try {
+ ApplicationInfo ai = getActivity().getPackageManager().getApplicationInfo(mDebugApp,
+ PackageManager.GET_DISABLED_COMPONENTS);
+ CharSequence lab = getActivity().getPackageManager().getApplicationLabel(ai);
+ label = lab != null ? lab.toString() : mDebugApp;
+ } catch (PackageManager.NameNotFoundException e) {
+ label = mDebugApp;
+ }
+ mDebugAppPref.setSummary(getResources().getString(R.string.debug_app_set, label));
+ mWaitForDebugger.setEnabled(true);
+ mHaveDebugSettings = true;
+ } else {
+ mDebugAppPref.setSummary(getResources().getString(R.string.debug_app_not_set));
+ mWaitForDebugger.setEnabled(false);
+ }
+ }
+
+ private void updateVerifyAppsOverUsbOptions() {
+ updateCheckBox(mVerifyAppsOverUsb, Settings.Global.getInt(getActivity().getContentResolver(),
+ Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, 1) != 0);
+ mVerifyAppsOverUsb.setEnabled(enableVerifierSetting());
+ }
+
+ private void writeVerifyAppsOverUsbOptions() {
+ Settings.Global.putInt(getActivity().getContentResolver(),
+ Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, mVerifyAppsOverUsb.isChecked() ? 1 : 0);
+ }
+
+ private boolean enableVerifierSetting() {
+ final ContentResolver cr = getActivity().getContentResolver();
+ if (Settings.Global.getInt(cr, Settings.Global.ADB_ENABLED, 0) == 0) {
+ return false;
+ }
+ if (Settings.Global.getInt(cr, Settings.Global.PACKAGE_VERIFIER_ENABLE, 1) == 0) {
+ return false;
+ } else {
+ final PackageManager pm = getActivity().getPackageManager();
+ final Intent verification = new Intent(Intent.ACTION_PACKAGE_NEEDS_VERIFICATION);
+ verification.setType(PACKAGE_MIME_TYPE);
+ verification.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ final List receivers = pm.queryBroadcastReceivers(verification, 0);
+ if (receivers.size() == 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean showVerifierSetting() {
+ return Settings.Global.getInt(getActivity().getContentResolver(),
+ Settings.Global.PACKAGE_VERIFIER_SETTING_VISIBLE, 1) > 0;
+ }
+
+ private void updateBugreportOptions() {
+ if ("user".equals(Build.TYPE)) {
+ final ContentResolver resolver = getActivity().getContentResolver();
+ final boolean adbEnabled = Settings.Global.getInt(
+ resolver, Settings.Global.ADB_ENABLED, 0) != 0;
+ if (adbEnabled) {
+ mBugreport.setEnabled(true);
+ mBugreportInPower.setEnabled(true);
+ } else {
+ mBugreport.setEnabled(false);
+ mBugreportInPower.setEnabled(false);
+ mBugreportInPower.setChecked(false);
+ Settings.Secure.putInt(resolver, Settings.Secure.BUGREPORT_IN_POWER_MENU, 0);
+ }
+ } else {
+ mBugreportInPower.setEnabled(true);
+ }
+ }
+
+ // Returns the current state of the system property that controls
+ // strictmode flashes. One of:
+ // 0: not explicitly set one way or another
+ // 1: on
+ // 2: off
+ private static int currentStrictModeActiveIndex() {
+ if (TextUtils.isEmpty(SystemProperties.get(StrictMode.VISUAL_PROPERTY))) {
+ return 0;
+ }
+ boolean enabled = SystemProperties.getBoolean(StrictMode.VISUAL_PROPERTY, false);
+ return enabled ? 1 : 2;
+ }
+
+ private void writeStrictModeVisualOptions() {
+ try {
+ mWindowManager.setStrictModeVisualIndicatorPreference(mStrictMode.isChecked()
+ ? "1" : "");
+ } catch (RemoteException e) {
+ }
+ }
+
+ private void updateStrictModeVisualOptions() {
+ updateCheckBox(mStrictMode, currentStrictModeActiveIndex() == 1);
+ }
+
+ private void writePointerLocationOptions() {
+ Settings.System.putInt(getActivity().getContentResolver(),
+ Settings.System.POINTER_LOCATION, mPointerLocation.isChecked() ? 1 : 0);
+ }
+
+ private void updatePointerLocationOptions() {
+ updateCheckBox(mPointerLocation, Settings.System.getInt(getActivity().getContentResolver(),
+ Settings.System.POINTER_LOCATION, 0) != 0);
+ }
+
+ private void writeShowTouchesOptions() {
+ Settings.System.putInt(getActivity().getContentResolver(),
+ Settings.System.SHOW_TOUCHES, mShowTouches.isChecked() ? 1 : 0);
+ }
+
+ private void updateShowTouchesOptions() {
+ updateCheckBox(mShowTouches, Settings.System.getInt(getActivity().getContentResolver(),
+ Settings.System.SHOW_TOUCHES, 0) != 0);
+ }
+
+ private void updateFlingerOptions() {
+ // magic communication with surface flinger.
+ try {
+ IBinder flinger = ServiceManager.getService("SurfaceFlinger");
+ if (flinger != null) {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken("android.ui.ISurfaceComposer");
+ flinger.transact(1010, data, reply, 0);
+ @SuppressWarnings("unused")
+ int showCpu = reply.readInt();
+ @SuppressWarnings("unused")
+ int enableGL = reply.readInt();
+ int showUpdates = reply.readInt();
+ updateCheckBox(mShowScreenUpdates, showUpdates != 0);
+ @SuppressWarnings("unused")
+ int showBackground = reply.readInt();
+ int disableOverlays = reply.readInt();
+ updateCheckBox(mDisableOverlays, disableOverlays != 0);
+ reply.recycle();
+ data.recycle();
+ }
+ } catch (RemoteException ex) {
+ }
+ }
+
+ private void writeShowUpdatesOption() {
+ try {
+ IBinder flinger = ServiceManager.getService("SurfaceFlinger");
+ if (flinger != null) {
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken("android.ui.ISurfaceComposer");
+ final int showUpdates = mShowScreenUpdates.isChecked() ? 1 : 0;
+ data.writeInt(showUpdates);
+ flinger.transact(1002, data, null, 0);
+ data.recycle();
+
+ updateFlingerOptions();
+ }
+ } catch (RemoteException ex) {
+ }
+ }
+
+ private void writeDisableOverlaysOption() {
+ try {
+ IBinder flinger = ServiceManager.getService("SurfaceFlinger");
+ if (flinger != null) {
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken("android.ui.ISurfaceComposer");
+ final int disableOverlays = mDisableOverlays.isChecked() ? 1 : 0;
+ data.writeInt(disableOverlays);
+ flinger.transact(1008, data, null, 0);
+ data.recycle();
+
+ updateFlingerOptions();
+ }
+ } catch (RemoteException ex) {
+ }
+ }
+
+ private void updateHardwareUiOptions() {
+ updateCheckBox(mForceHardwareUi, SystemProperties.getBoolean(HARDWARE_UI_PROPERTY, false));
+ }
+
+ private void writeHardwareUiOptions() {
+ SystemProperties.set(HARDWARE_UI_PROPERTY, mForceHardwareUi.isChecked() ? "true" : "false");
+ pokeSystemProperties();
+ }
+
+ private void updateMsaaOptions() {
+ updateCheckBox(mForceMsaa, SystemProperties.getBoolean(MSAA_PROPERTY, false));
+ }
+
+ private void writeMsaaOptions() {
+ SystemProperties.set(MSAA_PROPERTY, mForceMsaa.isChecked() ? "true" : "false");
+ pokeSystemProperties();
+ }
+
+ private void updateTrackFrameTimeOptions() {
+ updateCheckBox(mTrackFrameTime,
+ SystemProperties.getBoolean(HardwareRenderer.PROFILE_PROPERTY, false));
+ }
+
+ private void writeTrackFrameTimeOptions() {
+ SystemProperties.set(HardwareRenderer.PROFILE_PROPERTY,
+ mTrackFrameTime.isChecked() ? "true" : "false");
+ pokeSystemProperties();
+ }
+
+ private void updateShowHwScreenUpdatesOptions() {
+ updateCheckBox(mShowHwScreenUpdates,
+ SystemProperties.getBoolean(HardwareRenderer.DEBUG_DIRTY_REGIONS_PROPERTY, false));
+ }
+
+ private void writeShowHwScreenUpdatesOptions() {
+ SystemProperties.set(HardwareRenderer.DEBUG_DIRTY_REGIONS_PROPERTY,
+ mShowHwScreenUpdates.isChecked() ? "true" : null);
+ pokeSystemProperties();
+ }
+
+ private void updateShowHwLayersUpdatesOptions() {
+ updateCheckBox(mShowHwLayersUpdates, SystemProperties.getBoolean(
+ HardwareRenderer.DEBUG_SHOW_LAYERS_UPDATES_PROPERTY, false));
+ }
+
+ private void writeShowHwLayersUpdatesOptions() {
+ SystemProperties.set(HardwareRenderer.DEBUG_SHOW_LAYERS_UPDATES_PROPERTY,
+ mShowHwLayersUpdates.isChecked() ? "true" : null);
+ pokeSystemProperties();
+ }
+
+ private void updateShowHwOverdrawOptions() {
+ updateCheckBox(mShowHwOverdraw, SystemProperties.getBoolean(
+ HardwareRenderer.DEBUG_SHOW_OVERDRAW_PROPERTY, false));
+ }
+
+ private void writeShowHwOverdrawOptions() {
+ SystemProperties.set(HardwareRenderer.DEBUG_SHOW_OVERDRAW_PROPERTY,
+ mShowHwOverdraw.isChecked() ? "true" : null);
+ pokeSystemProperties();
+ }
+
+ private void updateDebugLayoutOptions() {
+ updateCheckBox(mDebugLayout,
+ SystemProperties.getBoolean(View.DEBUG_LAYOUT_PROPERTY, false));
+ }
+
+ private void writeDebugLayoutOptions() {
+ SystemProperties.set(View.DEBUG_LAYOUT_PROPERTY,
+ mDebugLayout.isChecked() ? "true" : "false");
+ pokeSystemProperties();
+ }
+
+ private void updateCpuUsageOptions() {
+ updateCheckBox(mShowCpuUsage, Settings.Global.getInt(getActivity().getContentResolver(),
+ Settings.Global.SHOW_PROCESSES, 0) != 0);
+ }
+
+ private void writeCpuUsageOptions() {
+ boolean value = mShowCpuUsage.isChecked();
+ Settings.Global.putInt(getActivity().getContentResolver(),
+ Settings.Global.SHOW_PROCESSES, value ? 1 : 0);
+ Intent service = (new Intent())
+ .setClassName("com.android.systemui", "com.android.systemui.LoadAverageService");
+ if (value) {
+ getActivity().startService(service);
+ } else {
+ getActivity().stopService(service);
+ }
+ }
+
+ private void writeImmediatelyDestroyActivitiesOptions() {
+ try {
+ ActivityManagerNative.getDefault().setAlwaysFinish(
+ mImmediatelyDestroyActivities.isChecked());
+ } catch (RemoteException ex) {
+ }
+ }
+
+ private void updateImmediatelyDestroyActivitiesOptions() {
+ updateCheckBox(mImmediatelyDestroyActivities, Settings.Global.getInt(
+ getActivity().getContentResolver(), Settings.Global.ALWAYS_FINISH_ACTIVITIES, 0) != 0);
+ }
+
+ private void updateAnimationScaleValue(int which, ListPreference pref) {
+ try {
+ float scale = mWindowManager.getAnimationScale(which);
+ if (scale != 1) {
+ mHaveDebugSettings = true;
+ }
+ CharSequence[] values = pref.getEntryValues();
+ for (int i=0; i= limit) {
+ if (i != 0) {
+ mHaveDebugSettings = true;
+ }
+ mAppProcessLimit.setValueIndex(i);
+ mAppProcessLimit.setSummary(mAppProcessLimit.getEntries()[i]);
+ return;
+ }
+ }
+ mAppProcessLimit.setValueIndex(0);
+ mAppProcessLimit.setSummary(mAppProcessLimit.getEntries()[0]);
+ } catch (RemoteException e) {
+ }
+ }
+
+ private void writeAppProcessLimitOptions(Object newValue) {
+ try {
+ int limit = newValue != null ? Integer.parseInt(newValue.toString()) : -1;
+ ActivityManagerNative.getDefault().setProcessLimit(limit);
+ updateAppProcessLimitOptions();
+ } catch (RemoteException e) {
+ }
+ }
+
+ private void writeShowAllANRsOptions() {
+ Settings.Secure.putInt(getActivity().getContentResolver(),
+ Settings.Secure.ANR_SHOW_BACKGROUND,
+ mShowAllANRs.isChecked() ? 1 : 0);
+ }
+
+ private void updateShowAllANRsOptions() {
+ updateCheckBox(mShowAllANRs, Settings.Secure.getInt(
+ getActivity().getContentResolver(), Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0);
+ }
+
+ private void updateEnableTracesOptions() {
+ long flags = SystemProperties.getLong(Trace.PROPERTY_TRACE_TAG_ENABLEFLAGS, 0);
+ String[] values = mEnableTracesPref.getEntryValues();
+ int numSet = 0;
+ for (int i=Trace.TRACE_FLAGS_START_BIT; i {
+ @Override
+ protected Void doInBackground(Void... params) {
+ String[] services;
+ try {
+ services = ServiceManager.listServices();
+ } catch (RemoteException e) {
+ return null;
+ }
+ for (String service : services) {
+ IBinder obj = ServiceManager.checkService(service);
+ if (obj != null) {
+ Parcel data = Parcel.obtain();
+ try {
+ obj.transact(IBinder.SYSPROPS_TRANSACTION, data, null, 0);
+ } catch (RemoteException e) {
+ } catch (Exception e) {
+ Log.i("DevSettings", "Somone wrote a bad service '" + service
+ + "' that doesn't like to be poked: " + e);
+ }
+ data.recycle();
+ }
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Dialog to confirm enforcement of {@link android.Manifest.permission#READ_EXTERNAL_STORAGE}.
+ */
+ public static class ConfirmEnforceFragment extends DialogFragment {
+ public static void show(DevelopmentSettings parent) {
+ final ConfirmEnforceFragment dialog = new ConfirmEnforceFragment();
+ dialog.setTargetFragment(parent, 0);
+ dialog.show(parent.getFragmentManager(), TAG_CONFIRM_ENFORCE);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Context context = getActivity();
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.enforce_read_external_confirm_title);
+ builder.setMessage(R.string.enforce_read_external_confirm_message);
+
+ builder.setPositiveButton(android.R.string.ok, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ setPermissionEnforced(context, READ_EXTERNAL_STORAGE, true);
+ ((DevelopmentSettings) getTargetFragment()).updateAllOptions();
+ }
+ });
+ builder.setNegativeButton(android.R.string.cancel, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ ((DevelopmentSettings) getTargetFragment()).updateAllOptions();
+ }
+ });
+
+ return builder.create();
+ }
+ }
+
+ private static boolean isPermissionEnforced(String permission) {
+ try {
+ return ActivityThread.getPackageManager().isPermissionEnforced(permission);
+ } catch (RemoteException e) {
+ throw new RuntimeException("Problem talking with PackageManager", e);
+ }
+ }
+
+ private static void setPermissionEnforced(
+ Context context, String permission, boolean enforced) {
+ try {
+ // TODO: offload to background thread
+ ActivityThread.getPackageManager()
+ .setPermissionEnforced(READ_EXTERNAL_STORAGE, enforced);
+ } catch (RemoteException e) {
+ throw new RuntimeException("Problem talking with PackageManager", e);
+ }
+ }
+}
diff --git a/src/com/android/settings/DeviceAdminAdd.java b/src/com/android/settings/DeviceAdminAdd.java
new file mode 100644
index 0000000..b2145b0
--- /dev/null
+++ b/src/com/android/settings/DeviceAdminAdd.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.app.Activity;
+import android.app.ActivityManagerNative;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.admin.DeviceAdminInfo;
+import android.app.admin.DeviceAdminReceiver;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.text.TextUtils.TruncateAt;
+import android.util.Log;
+import android.view.Display;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.AppSecurityPermissions;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+public class DeviceAdminAdd extends Activity {
+ static final String TAG = "DeviceAdminAdd";
+
+ static final int DIALOG_WARNING = 1;
+
+ private static final int MAX_ADD_MSG_LINES_PORTRAIT = 5;
+ private static final int MAX_ADD_MSG_LINES_LANDSCAPE = 2;
+ private static final int MAX_ADD_MSG_LINES = 15;
+
+ Handler mHandler;
+
+ DevicePolicyManager mDPM;
+ DeviceAdminInfo mDeviceAdmin;
+ CharSequence mAddMsgText;
+
+ ImageView mAdminIcon;
+ TextView mAdminName;
+ TextView mAdminDescription;
+ TextView mAddMsg;
+ ImageView mAddMsgExpander;
+ boolean mAddMsgEllipsized = true;
+ TextView mAdminWarning;
+ ViewGroup mAdminPolicies;
+ Button mActionButton;
+ Button mCancelButton;
+
+ final ArrayList mAddingPolicies = new ArrayList();
+ final ArrayList mActivePolicies = new ArrayList();
+
+ boolean mAdding;
+ boolean mRefreshing;
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ mHandler = new Handler(getMainLooper());
+
+ mDPM = (DevicePolicyManager)getSystemService(Context.DEVICE_POLICY_SERVICE);
+
+ if ((getIntent().getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
+ Log.w(TAG, "Cannot start ADD_DEVICE_ADMIN as a new task");
+ finish();
+ return;
+ }
+
+ ComponentName cn = (ComponentName)getIntent().getParcelableExtra(
+ DevicePolicyManager.EXTRA_DEVICE_ADMIN);
+ if (cn == null) {
+ Log.w(TAG, "No component specified in " + getIntent().getAction());
+ finish();
+ return;
+ }
+
+ ActivityInfo ai;
+ try {
+ ai = getPackageManager().getReceiverInfo(cn, PackageManager.GET_META_DATA);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Unable to retrieve device policy " + cn, e);
+ finish();
+ return;
+ }
+
+ ResolveInfo ri = new ResolveInfo();
+ ri.activityInfo = ai;
+ try {
+ mDeviceAdmin = new DeviceAdminInfo(this, ri);
+ } catch (XmlPullParserException e) {
+ Log.w(TAG, "Unable to retrieve device policy " + cn, e);
+ finish();
+ return;
+ } catch (IOException e) {
+ Log.w(TAG, "Unable to retrieve device policy " + cn, e);
+ finish();
+ return;
+ }
+
+ // This admin already exists, an we have two options at this point. If new policy
+ // bits are set, show the user the new list. If nothing has changed, simply return
+ // "OK" immediately.
+ if (DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN.equals(getIntent().getAction())) {
+ mRefreshing = false;
+ if (mDPM.isAdminActive(cn)) {
+ ArrayList newPolicies = mDeviceAdmin.getUsedPolicies();
+ for (int i = 0; i < newPolicies.size(); i++) {
+ DeviceAdminInfo.PolicyInfo pi = newPolicies.get(i);
+ if (!mDPM.hasGrantedPolicy(cn, pi.ident)) {
+ mRefreshing = true;
+ break;
+ }
+ }
+ if (!mRefreshing) {
+ // Nothing changed (or policies were removed) - return immediately
+ setResult(Activity.RESULT_OK);
+ finish();
+ return;
+ }
+ }
+ }
+ mAddMsgText = getIntent().getCharSequenceExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION);
+
+ setContentView(R.layout.device_admin_add);
+
+ mAdminIcon = (ImageView)findViewById(R.id.admin_icon);
+ mAdminName = (TextView)findViewById(R.id.admin_name);
+ mAdminDescription = (TextView)findViewById(R.id.admin_description);
+
+ mAddMsg = (TextView)findViewById(R.id.add_msg);
+ mAddMsgExpander = (ImageView) findViewById(R.id.add_msg_expander);
+ mAddMsg.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ toggleMessageEllipsis(v);
+ }
+ });
+
+ // toggleMessageEllipsis also handles initial layout:
+ toggleMessageEllipsis(mAddMsg);
+
+ mAdminWarning = (TextView) findViewById(R.id.admin_warning);
+ mAdminPolicies = (ViewGroup) findViewById(R.id.admin_policies);
+ mCancelButton = (Button) findViewById(R.id.cancel_button);
+ mCancelButton.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ finish();
+ }
+ });
+ mActionButton = (Button) findViewById(R.id.action_button);
+ mActionButton.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ if (mAdding) {
+ try {
+ mDPM.setActiveAdmin(mDeviceAdmin.getComponent(), mRefreshing);
+ setResult(Activity.RESULT_OK);
+ } catch (RuntimeException e) {
+ // Something bad happened... could be that it was
+ // already set, though.
+ Log.w(TAG, "Exception trying to activate admin "
+ + mDeviceAdmin.getComponent(), e);
+ if (mDPM.isAdminActive(mDeviceAdmin.getComponent())) {
+ setResult(Activity.RESULT_OK);
+ }
+ }
+ finish();
+ } else {
+ try {
+ // Don't allow the admin to put a dialog up in front
+ // of us while we interact with the user.
+ ActivityManagerNative.getDefault().stopAppSwitches();
+ } catch (RemoteException e) {
+ }
+ mDPM.getRemoveWarning(mDeviceAdmin.getComponent(),
+ new RemoteCallback(mHandler) {
+ @Override
+ protected void onResult(Bundle bundle) {
+ CharSequence msg = bundle != null
+ ? bundle.getCharSequence(
+ DeviceAdminReceiver.EXTRA_DISABLE_WARNING)
+ : null;
+ if (msg == null) {
+ try {
+ ActivityManagerNative.getDefault().resumeAppSwitches();
+ } catch (RemoteException e) {
+ }
+ mDPM.removeActiveAdmin(mDeviceAdmin.getComponent());
+ finish();
+ } else {
+ Bundle args = new Bundle();
+ args.putCharSequence(
+ DeviceAdminReceiver.EXTRA_DISABLE_WARNING, msg);
+ showDialog(DIALOG_WARNING, args);
+ }
+ }
+ });
+ }
+ }
+ });
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ updateInterface();
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id, Bundle args) {
+ switch (id) {
+ case DIALOG_WARNING: {
+ CharSequence msg = args.getCharSequence(DeviceAdminReceiver.EXTRA_DISABLE_WARNING);
+ AlertDialog.Builder builder = new AlertDialog.Builder(
+ DeviceAdminAdd.this);
+ builder.setMessage(msg);
+ builder.setPositiveButton(R.string.dlg_ok,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ mDPM.removeActiveAdmin(mDeviceAdmin.getComponent());
+ finish();
+ }
+ });
+ builder.setNegativeButton(R.string.dlg_cancel, null);
+ return builder.create();
+ }
+ default:
+ return super.onCreateDialog(id, args);
+
+ }
+ }
+
+ static void setViewVisibility(ArrayList views, int visibility) {
+ final int N = views.size();
+ for (int i=0; i policies = mDeviceAdmin.getUsedPolicies();
+ for (int i=0; i policies = mDeviceAdmin.getUsedPolicies();
+ for (int i=0; i d.getWidth() ?
+ MAX_ADD_MSG_LINES_PORTRAIT : MAX_ADD_MSG_LINES_LANDSCAPE;
+ }
+
+}
diff --git a/src/com/android/settings/DeviceAdminSettings.java b/src/com/android/settings/DeviceAdminSettings.java
new file mode 100644
index 0000000..310aae7
--- /dev/null
+++ b/src/com/android/settings/DeviceAdminSettings.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.app.Activity;
+import android.app.ListFragment;
+import android.app.admin.DeviceAdminInfo;
+import android.app.admin.DeviceAdminReceiver;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.CheckBox;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+public class DeviceAdminSettings extends ListFragment {
+ static final String TAG = "DeviceAdminSettings";
+
+ static final int DIALOG_WARNING = 1;
+
+ DevicePolicyManager mDPM;
+ final HashSet mActiveAdmins = new HashSet();
+ final ArrayList mAvailableAdmins = new ArrayList();
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ mDPM = (DevicePolicyManager) getActivity().getSystemService(Context.DEVICE_POLICY_SERVICE);
+ return inflater.inflate(R.layout.device_admin_settings, container, false);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ updateList();
+ }
+
+ void updateList() {
+ mActiveAdmins.clear();
+ List cur = mDPM.getActiveAdmins();
+ if (cur != null) {
+ for (int i=0; i avail = getActivity().getPackageManager().queryBroadcastReceivers(
+ new Intent(DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED),
+ PackageManager.GET_META_DATA);
+ int count = avail == null ? 0 : avail.size();
+ for (int i=0; i= (SystemClock.uptimeMillis()-500)) {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setClassName("android",
+ com.android.internal.app.PlatLogoActivity.class.getName());
+ try {
+ startActivity(intent);
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Unable to start activity " + intent.toString());
+ }
+ }
+ } else if (preference.getKey().equals(KEY_BUILD_NUMBER)) {
+ if (mDevHitCountdown > 0) {
+ mDevHitCountdown--;
+ if (mDevHitCountdown == 0) {
+ getActivity().getSharedPreferences(DevelopmentSettings.PREF_FILE,
+ Context.MODE_PRIVATE).edit().putBoolean(
+ DevelopmentSettings.PREF_SHOW, true).apply();
+ if (mDevHitToast != null) {
+ mDevHitToast.cancel();
+ }
+ mDevHitToast = Toast.makeText(getActivity(), R.string.show_dev_on,
+ Toast.LENGTH_LONG);
+ mDevHitToast.show();
+ } else if (mDevHitCountdown > 0
+ && mDevHitCountdown < (TAPS_TO_BE_A_DEVELOPER-2)) {
+ if (mDevHitToast != null) {
+ mDevHitToast.cancel();
+ }
+ mDevHitToast = Toast.makeText(getActivity(), getResources().getString(
+ R.string.show_dev_countdown, mDevHitCountdown),
+ Toast.LENGTH_SHORT);
+ mDevHitToast.show();
+ }
+ } else if (mDevHitCountdown < 0) {
+ if (mDevHitToast != null) {
+ mDevHitToast.cancel();
+ }
+ mDevHitToast = Toast.makeText(getActivity(), R.string.show_dev_already,
+ Toast.LENGTH_LONG);
+ mDevHitToast.show();
+ }
+ }
+ return super.onPreferenceTreeClick(preferenceScreen, preference);
+ }
+
+ private void removePreferenceIfPropertyMissing(PreferenceGroup preferenceGroup,
+ String preference, String property ) {
+ if (SystemProperties.get(property).equals("")) {
+ // Property is missing so remove preference from group
+ try {
+ preferenceGroup.removePreference(findPreference(preference));
+ } catch (RuntimeException e) {
+ Log.d(LOG_TAG, "Property '" + property + "' missing and no '"
+ + preference + "' preference");
+ }
+ }
+ }
+
+ private void removePreferenceIfBoolFalse(String preference, int resId) {
+ if (!getResources().getBoolean(resId)) {
+ Preference pref = findPreference(preference);
+ if (pref != null) {
+ getPreferenceScreen().removePreference(pref);
+ }
+ }
+ }
+
+ private void setStringSummary(String preference, String value) {
+ try {
+ findPreference(preference).setSummary(value);
+ } catch (RuntimeException e) {
+ findPreference(preference).setSummary(
+ getResources().getString(R.string.device_info_default));
+ }
+ }
+
+ private void setValueSummary(String preference, String property) {
+ try {
+ findPreference(preference).setSummary(
+ SystemProperties.get(property,
+ getResources().getString(R.string.device_info_default)));
+ } catch (RuntimeException e) {
+ // No recovery
+ }
+ }
+
+ /**
+ * Reads a line from the specified file.
+ * @param filename the file to read from
+ * @return the first line, if any.
+ * @throws IOException if the file couldn't be read
+ */
+ private static String readLine(String filename) throws IOException {
+ BufferedReader reader = new BufferedReader(new FileReader(filename), 256);
+ try {
+ return reader.readLine();
+ } finally {
+ reader.close();
+ }
+ }
+
+ public static String getFormattedKernelVersion() {
+ try {
+ return formatKernelVersion(readLine(FILENAME_PROC_VERSION));
+
+ } catch (IOException e) {
+ Log.e(LOG_TAG,
+ "IO Exception when getting kernel version for Device Info screen",
+ e);
+
+ return "Unavailable";
+ }
+ }
+
+ public static String formatKernelVersion(String rawKernelVersion) {
+ // Example (see tests for more):
+ // Linux version 3.0.31-g6fb96c9 (android-build@xxx.xxx.xxx.xxx.com) \
+ // (gcc version 4.6.x-xxx 20120106 (prerelease) (GCC) ) #1 SMP PREEMPT \
+ // Thu Jun 28 11:02:39 PDT 2012
+
+ final String PROC_VERSION_REGEX =
+ "Linux version (\\S+) " + /* group 1: "3.0.31-g6fb96c9" */
+ "\\((\\S+?)\\) " + /* group 2: "x@y.com" (kernel builder) */
+ "(?:\\(gcc.+? \\)) " + /* ignore: GCC version information */
+ "(#\\d+) " + /* group 3: "#1" */
+ "(?:.*?)?" + /* ignore: optional SMP, PREEMPT, and any CONFIG_FLAGS */
+ "((Sun|Mon|Tue|Wed|Thu|Fri|Sat).+)"; /* group 4: "Thu Jun 28 11:02:39 PDT 2012" */
+
+ Matcher m = Pattern.compile(PROC_VERSION_REGEX).matcher(rawKernelVersion);
+ if (!m.matches()) {
+ Log.e(LOG_TAG, "Regex did not match on /proc/version: " + rawKernelVersion);
+ return "Unavailable";
+ } else if (m.groupCount() < 4) {
+ Log.e(LOG_TAG, "Regex match on /proc/version only returned " + m.groupCount()
+ + " groups");
+ return "Unavailable";
+ }
+ return m.group(1) + "\n" + // 3.0.31-g6fb96c9
+ //m.group(2)
+ "luntech@luntech.com"+ " " + m.group(3) + "\n" + // x@y.com #1
+ m.group(4); // Thu Jun 28 11:02:39 PDT 2012
+ }
+
+ /**
+ * Returns " (ENGINEERING)" if the msv file has a zero value, else returns "".
+ * @return a string to append to the model number description.
+ */
+ private String getMsvSuffix() {
+ // Production devices should have a non-zero value. If we can't read it, assume it's a
+ // production device so that we don't accidentally show that it's an ENGINEERING device.
+ try {
+ String msv = readLine(FILENAME_MSV);
+ // Parse as a hex number. If it evaluates to a zero, then it's an engineering build.
+ if (Long.parseLong(msv, 16) == 0) {
+ return " (ENGINEERING)";
+ }
+ } catch (IOException ioe) {
+ // Fail quietly, as the file may not exist on some devices.
+ } catch (NumberFormatException nfe) {
+ // Fail quietly, returning empty string should be sufficient
+ }
+ return "";
+ }
+}
diff --git a/src/com/android/settings/DialogCreatable.java b/src/com/android/settings/DialogCreatable.java
new file mode 100644
index 0000000..1d10be7
--- /dev/null
+++ b/src/com/android/settings/DialogCreatable.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import android.app.Dialog;
+
+/**
+ * Letting the class, assumed to be Fragment, create a Dialog on it. Should be useful
+ * you want to utilize some capability in {@link SettingsPreferenceFragment} but don't want
+ * the class inherit the class itself (See {@link ProxySelector} for example).
+ */
+public interface DialogCreatable {
+
+ public Dialog onCreateDialog(int dialogId);
+}
diff --git a/src/com/android/settings/Display.java b/src/com/android/settings/Display.java
new file mode 100644
index 0000000..fa29318
--- /dev/null
+++ b/src/com/android/settings/Display.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import android.app.Activity;
+import android.app.ActivityManagerNative;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+
+public class Display extends Activity implements View.OnClickListener {
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ setContentView(R.layout.display);
+
+ mFontSize = (Spinner) findViewById(R.id.fontSize);
+ mFontSize.setOnItemSelectedListener(mFontSizeChanged);
+ String[] states = new String[3];
+ Resources r = getResources();
+ states[0] = r.getString(R.string.small_font);
+ states[1] = r.getString(R.string.medium_font);
+ states[2] = r.getString(R.string.large_font);
+ ArrayAdapter adapter = new ArrayAdapter(this,
+ android.R.layout.simple_spinner_item, states);
+ adapter.setDropDownViewResource(
+ android.R.layout.simple_spinner_dropdown_item);
+ mFontSize.setAdapter(adapter);
+
+ mPreview = (TextView) findViewById(R.id.preview);
+ mPreview.setText(r.getText(R.string.font_size_preview_text));
+
+ Button save = (Button) findViewById(R.id.save);
+ save.setText(r.getText(R.string.font_size_save));
+ save.setOnClickListener(this);
+
+ mTextSizeTyped = new TypedValue();
+ TypedArray styledAttributes =
+ obtainStyledAttributes(android.R.styleable.TextView);
+ styledAttributes.getValue(android.R.styleable.TextView_textSize,
+ mTextSizeTyped);
+
+ DisplayMetrics metrics = getResources().getDisplayMetrics();
+ mDisplayMetrics = new DisplayMetrics();
+ mDisplayMetrics.density = metrics.density;
+ mDisplayMetrics.heightPixels = metrics.heightPixels;
+ mDisplayMetrics.scaledDensity = metrics.scaledDensity;
+ mDisplayMetrics.widthPixels = metrics.widthPixels;
+ mDisplayMetrics.xdpi = metrics.xdpi;
+ mDisplayMetrics.ydpi = metrics.ydpi;
+
+ styledAttributes.recycle();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ try {
+ mCurConfig.updateFrom(
+ ActivityManagerNative.getDefault().getConfiguration());
+ } catch (RemoteException e) {
+ }
+ if (mCurConfig.fontScale < 1) {
+ mFontSize.setSelection(0);
+ } else if (mCurConfig.fontScale > 1) {
+ mFontSize.setSelection(2);
+ } else {
+ mFontSize.setSelection(1);
+ }
+ updateFontScale();
+ }
+
+ private void updateFontScale() {
+ mDisplayMetrics.scaledDensity = mDisplayMetrics.density *
+ mCurConfig.fontScale;
+
+ float size = mTextSizeTyped.getDimension(mDisplayMetrics);
+ mPreview.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
+ }
+
+ public void onClick(View v) {
+ try {
+ ActivityManagerNative.getDefault().updatePersistentConfiguration(mCurConfig);
+ } catch (RemoteException e) {
+ }
+ finish();
+ }
+
+ private Spinner.OnItemSelectedListener mFontSizeChanged
+ = new Spinner.OnItemSelectedListener() {
+ public void onItemSelected(android.widget.AdapterView av, View v,
+ int position, long id) {
+ if (position == 0) {
+ mCurConfig.fontScale = .75f;
+ } else if (position == 2) {
+ mCurConfig.fontScale = 1.25f;
+ } else {
+ mCurConfig.fontScale = 1.0f;
+ }
+
+ updateFontScale();
+ }
+
+ public void onNothingSelected(android.widget.AdapterView av) {
+ }
+ };
+
+ private Spinner mFontSize;
+ private TextView mPreview;
+ private TypedValue mTextSizeTyped;
+ private DisplayMetrics mDisplayMetrics;
+ private Configuration mCurConfig = new Configuration();
+}
diff --git a/src/com/android/settings/DisplaySettings.java b/src/com/android/settings/DisplaySettings.java
new file mode 100644
index 0000000..b1f4c5c
--- /dev/null
+++ b/src/com/android/settings/DisplaySettings.java
@@ -0,0 +1,727 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT;
+
+import android.app.ActivityManagerNative;
+import android.app.Dialog;
+import android.app.AlertDialog;
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.media.AudioManager;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.WifiDisplay;
+import android.hardware.display.WifiDisplayStatus;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.preference.CheckBoxPreference;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceClickListener;
+import android.preference.PreferenceScreen;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.util.AttributeSet;
+import android.util.Log;
+
+import com.android.internal.view.RotationPolicy;
+import com.android.settings.DreamSettings;
+import android.os.PowerManager;
+import android.view.DisplayManagerAw;
+import android.view.DispList;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class DisplaySettings extends SettingsPreferenceFragment implements
+ Preference.OnPreferenceChangeListener, OnPreferenceClickListener {
+ private static final String TAG = "DisplaySettings";
+
+ /** If there is no setting in the provider, use this. */
+ private static final int FALLBACK_SCREEN_TIMEOUT_VALUE = 30000;
+ private static final int FALLBACK_DISPLAY_MODE_TIMEOUT = 10;
+ private static final String DISPLAY_MODE_AUTO_KEY = "display_mode_auto";
+
+ private static final String KEY_SCREEN_TIMEOUT = "screen_timeout";
+ private static final String KEY_ACCELEROMETER = "accelerometer";
+ private static final String KEY_FONT_SIZE = "font_size";
+ private static final String KEY_NOTIFICATION_PULSE = "notification_pulse";
+ private static final String KEY_SCREEN_SAVER = "screensaver";
+ private static final String KEY_ACCELEROMETER_COORDINATE = "accelerometer_coornadite";
+ private static final String KEY_SMART_BRIGHTNESS = "smart_brightness";
+ private static final String KEY_SMART_BRIGHTNESS_PREVIEW = "key_smart_brightness_preview";
+ private static final String KEY_WIFI_DISPLAY = "wifi_display";
+ private static final String KEY_TV_OUTPUT_MODE = "display_output_mode";
+ private static final int DLG_GLOBAL_CHANGE_WARNING = 1;
+
+ private DisplayManager mDisplayManager;
+
+ private CheckBoxPreference mAccelerometer;
+ private WarnedListPreference mFontSizePref;
+ private CheckBoxPreference mNotificationPulse;
+ /*显示输出模式*/
+ private ListPreference mOutputMode;
+ private ArrayList mOutputModeItems;
+
+ private final Configuration mCurConfig = new Configuration();
+
+ private ListPreference mScreenTimeoutPreference;
+ private Preference mScreenSaverPreference;
+
+ private ListPreference mAccelerometerCoordinate;
+
+ private CheckBoxPreference mSmartBrightness;
+ private CheckBoxPreference mSmartBrightnessPreview;
+
+ private WifiDisplayStatus mWifiDisplayStatus;
+ private Preference mWifiDisplayPreference;
+
+ private final RotationPolicy.RotationPolicyListener mRotationPolicyListener =
+ new RotationPolicy.RotationPolicyListener() {
+ @Override
+ public void onChange() {
+ updateAccelerometerRotationCheckbox();
+ }
+ };
+
+
+ public static DisplaySettings newInstance(){
+ DisplaySettings displaySettings = new DisplaySettings();
+ return displaySettings;
+ }
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ ContentResolver resolver = getActivity().getContentResolver();
+
+ addPreferencesFromResource(R.xml.display_settings);
+
+ mAccelerometer = (CheckBoxPreference) findPreference(KEY_ACCELEROMETER);
+ mAccelerometer.setPersistent(false);
+ if (RotationPolicy.isRotationLockToggleSupported(getActivity())) {
+ // If rotation lock is supported, then we do not provide this option in
+ // Display settings. However, is still available in Accessibility settings.
+ getPreferenceScreen().removePreference(mAccelerometer);
+ }
+
+ mScreenSaverPreference = findPreference(KEY_SCREEN_SAVER);
+ if (mScreenSaverPreference != null
+ && getResources().getBoolean(
+ com.android.internal.R.bool.config_dreamsSupported) == false) {
+ getPreferenceScreen().removePreference(mScreenSaverPreference);
+ }
+
+ mScreenTimeoutPreference = (ListPreference) findPreference(KEY_SCREEN_TIMEOUT);
+ final long currentTimeout = Settings.System.getLong(resolver, SCREEN_OFF_TIMEOUT,
+ FALLBACK_SCREEN_TIMEOUT_VALUE);
+
+ mScreenTimeoutPreference.setValue(String.valueOf(currentTimeout));
+ mScreenTimeoutPreference.setOnPreferenceChangeListener(this);
+ disableUnusableTimeouts(mScreenTimeoutPreference);
+ updateTimeoutPreferenceDescription(currentTimeout);
+
+ mFontSizePref = (WarnedListPreference) findPreference(KEY_FONT_SIZE);
+ mFontSizePref.setOnPreferenceChangeListener(this);
+ mFontSizePref.setOnPreferenceClickListener(this);
+ mNotificationPulse = (CheckBoxPreference) findPreference(KEY_NOTIFICATION_PULSE);
+
+ if (mNotificationPulse != null
+ && getResources().getBoolean(
+ com.android.internal.R.bool.config_intrusiveNotificationLed) == false) {
+ getPreferenceScreen().removePreference(mNotificationPulse);
+ } else {
+ try {
+ mNotificationPulse.setChecked(Settings.System.getInt(resolver,
+ Settings.System.NOTIFICATION_LIGHT_PULSE) == 1);
+ mNotificationPulse.setOnPreferenceChangeListener(this);
+ } catch (SettingNotFoundException snfe) {
+ Log.e(TAG, Settings.System.NOTIFICATION_LIGHT_PULSE + " not found");
+ }
+ }
+
+ mAccelerometerCoordinate = (ListPreference) findPreference(KEY_ACCELEROMETER_COORDINATE);
+ if(mAccelerometerCoordinate != null)
+ {
+ mAccelerometerCoordinate.setOnPreferenceChangeListener(this);
+ String value = Settings.System.getString(getContentResolver(),
+ Settings.System.ACCELEROMETER_COORDINATE);
+ if ( value == null )
+ {
+ value = "default";
+ }
+
+ mAccelerometerCoordinate.setValue(value);
+ updateAccelerometerCoordinateSummary(value);
+ }
+
+
+ mSmartBrightnessPreview = new CheckBoxPreference(this.getActivity());
+ mSmartBrightnessPreview.setTitle(R.string.smart_brightness_preview);
+ mSmartBrightnessPreview.setKey(KEY_SMART_BRIGHTNESS_PREVIEW);
+ mSmartBrightness = (CheckBoxPreference)findPreference(KEY_SMART_BRIGHTNESS);
+ mSmartBrightness.setOnPreferenceChangeListener(this);
+ if(!getResources().getBoolean(R.bool.has_smart_brightness))
+ {
+ getPreferenceScreen().removePreference(mSmartBrightness);
+ }
+ else
+ {
+ boolean enable = Settings.System.getInt(getContentResolver(),
+ Settings.System.SMART_BRIGHTNESS_ENABLE,0) != 0 ? true : false;
+ mSmartBrightness.setChecked(enable);
+ mSmartBrightnessPreview.setOnPreferenceChangeListener(this);
+ if(enable)
+ {
+ getPreferenceScreen().addPreference(mSmartBrightnessPreview);
+ }
+ }
+
+
+ mDisplayManager = (DisplayManager)getActivity().getSystemService(
+ Context.DISPLAY_SERVICE);
+ mWifiDisplayStatus = mDisplayManager.getWifiDisplayStatus();
+ mWifiDisplayPreference = (Preference)findPreference(KEY_WIFI_DISPLAY);
+ if (mWifiDisplayStatus.getFeatureState()
+ == WifiDisplayStatus.FEATURE_STATE_UNAVAILABLE) {
+ getPreferenceScreen().removePreference(mWifiDisplayPreference);
+ mWifiDisplayPreference = null;
+ }
+ /* homlet 和 dongle上无休眠 */
+ getPreferenceScreen().removePreference(mScreenTimeoutPreference);
+ mOutputMode = (ListPreference) findPreference(KEY_TV_OUTPUT_MODE);
+ if (mOutputMode != null) {
+ mOutputMode.setOnPreferenceChangeListener(this);
+ setOutputMode(mOutputMode);
+ }
+ }
+
+ private void updateTimeoutPreferenceDescription(long currentTimeout) {
+ ListPreference preference = mScreenTimeoutPreference;
+ String summary;
+ if (currentTimeout < 0) {
+ // Unsupported value
+ summary = "";
+ } else {
+ final CharSequence[] entries = preference.getEntries();
+ final CharSequence[] values = preference.getEntryValues();
+ if (entries == null || entries.length == 0) {
+ summary = "";
+ } else {
+ int best = 0;
+ for (int i = 0; i < values.length; i++) {
+ long timeout = Long.parseLong(values[i].toString());
+ if (currentTimeout >= timeout) {
+ best = i;
+ }
+ }
+ summary = preference.getContext().getString(R.string.screen_timeout_summary,
+ entries[best]);
+ }
+ }
+ preference.setSummary(summary);
+ }
+
+ private void disableUnusableTimeouts(ListPreference screenTimeoutPreference) {
+ final DevicePolicyManager dpm =
+ (DevicePolicyManager) getActivity().getSystemService(
+ Context.DEVICE_POLICY_SERVICE);
+ final long maxTimeout = dpm != null ? dpm.getMaximumTimeToLock(null) : 0;
+ if (maxTimeout == 0) {
+ return; // policy not enforced
+ }
+ final CharSequence[] entries = screenTimeoutPreference.getEntries();
+ final CharSequence[] values = screenTimeoutPreference.getEntryValues();
+ ArrayList revisedEntries = new ArrayList();
+ ArrayList revisedValues = new ArrayList();
+ for (int i = 0; i < values.length; i++) {
+ long timeout = Long.parseLong(values[i].toString());
+ if (timeout <= maxTimeout) {
+ revisedEntries.add(entries[i]);
+ revisedValues.add(values[i]);
+ }
+ }
+ if (revisedEntries.size() != entries.length || revisedValues.size() != values.length) {
+ screenTimeoutPreference.setEntries(
+ revisedEntries.toArray(new CharSequence[revisedEntries.size()]));
+ screenTimeoutPreference.setEntryValues(
+ revisedValues.toArray(new CharSequence[revisedValues.size()]));
+ final int userPreference = Integer.parseInt(screenTimeoutPreference.getValue());
+ if (userPreference <= maxTimeout) {
+ screenTimeoutPreference.setValue(String.valueOf(userPreference));
+ } else {
+ // There will be no highlighted selection since nothing in the list matches
+ // maxTimeout. The user can still select anything less than maxTimeout.
+ // TODO: maybe append maxTimeout to the list and mark selected.
+ }
+ }
+ screenTimeoutPreference.setEnabled(revisedEntries.size() > 0);
+ }
+
+ int floatToIndex(float val) {
+ String[] indices = getResources().getStringArray(R.array.entryvalues_font_size);
+ float lastVal = Float.parseFloat(indices[0]);
+ for (int i=1; i mOutputModeItems.size()) {
+ return false;
+ } else if (value == -1) {
+ DisplayManagerAw displayManager = (DisplayManagerAw) getSystemService(Context.DISPLAY_SERVICE_AW);
+ ArrayList items = DispList.getDispList();
+ for (int i = 0; i < items.size(); i++) {
+ DispList.DispFormat finalFormat = items.get(i);
+ boolean isSupport = (displayManager
+ .isSupportHdmiMode(finalFormat.mFormat) != 0);
+ if (isSupport) {
+ switchDispMode(finalFormat);
+ }
+ }
+ Settings.System.putInt(getContentResolver(), DISPLAY_MODE_AUTO_KEY, 1);
+ mOutputMode.setValue(String.valueOf(-1));
+ return false;
+ } else {
+ Settings.System.putInt(getContentResolver(), DISPLAY_MODE_AUTO_KEY, 0);
+ }
+
+ //save the old value
+ final int oldValue = Integer.parseInt(mOutputMode.getValue());
+
+ DispList.DispFormat item = null;
+ item = mOutputModeItems.get(value);
+ switchDispMode(item);
+ String databaseValue = DispList.ItemCode2Name(item);
+ Log.d(TAG,"out put mode save value = " + databaseValue);
+ Settings.System.putString(getContentResolver(),
+ Settings.System.DISPLY_OUTPUT_FORMAT, databaseValue);
+
+
+ OnClickListener listener = new OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int btn) {
+ if (btn == AlertDialog.BUTTON_POSITIVE) {
+
+ } else if (btn == AlertDialog.BUTTON_NEGATIVE) {
+ DispList.DispFormat item = null;
+ if (oldValue == -1) {
+ DisplayManagerAw displayManager = (DisplayManagerAw) getSystemService(Context.DISPLAY_SERVICE_AW);
+ ArrayList items = DispList
+ .getDispList();
+ for (int i = 0; i < items.size(); i++) {
+ DispList.DispFormat finalFormat = items.get(i);
+ boolean isSupport = (displayManager
+ .isSupportHdmiMode(finalFormat.mFormat) != 0);
+ if (isSupport) {
+ switchDispMode(finalFormat);
+ }
+ }
+ Settings.System.putInt(getContentResolver(),
+ DISPLAY_MODE_AUTO_KEY, 1);
+ mOutputMode.setValue(String.valueOf(-1));
+ } else {
+ item = mOutputModeItems.get(oldValue);
+ switchDispMode(item);
+ String databaseValue = DispList.ItemCode2Name(item);
+ Log.d(TAG, "out put mode save value = "
+ + databaseValue);
+ Settings.System.putString(getContentResolver(),
+ Settings.System.DISPLY_OUTPUT_FORMAT,
+ databaseValue);
+ mOutputMode.setValue(Integer.toString(oldValue));
+ }
+ }
+ }
+ };
+
+ String str = getString(com.android.settings.R.string.display_mode_time_out_desc);
+ final AlertDialog dialog = new AlertDialog.Builder(this.getActivity())
+ .setTitle(com.android.settings.R.string.display_mode_time_out_title)
+ .setMessage(String.format(str, Integer.toString(FALLBACK_DISPLAY_MODE_TIMEOUT)))
+ .setPositiveButton(com.android.internal.R.string.ok, listener)
+ .setNegativeButton(com.android.internal.R.string.cancel, listener)
+ .create();
+ dialog.show();
+ new AsyncTask(){
+ @Override
+ protected Object doInBackground(Object... arg0) {
+ int time = FALLBACK_DISPLAY_MODE_TIMEOUT;
+ while(time >= 0 && dialog.isShowing()){
+ publishProgress(time);
+ try{
+ Thread.sleep(1000);
+ }catch(Exception e){}
+ time--;
+ }
+ return null;
+ }
+ @Override
+ protected void onPostExecute(Object result) {
+ super.onPostExecute(result);
+ if (dialog.isShowing()) {
+ DispList.DispFormat item = null;
+ if (oldValue == -1) {
+ DisplayManagerAw displayManager = (DisplayManagerAw) getSystemService(Context.DISPLAY_SERVICE_AW);
+ ArrayList items = DispList
+ .getDispList();
+ for (int i = 0; i < items.size(); i++) {
+ DispList.DispFormat finalFormat = items.get(i);
+ boolean isSupport = (displayManager
+ .isSupportHdmiMode(finalFormat.mFormat) != 0);
+ if (isSupport) {
+ switchDispMode(finalFormat);
+ }
+ }
+ Settings.System.putInt(getContentResolver(),
+ DISPLAY_MODE_AUTO_KEY, 1);
+ mOutputMode.setValue(String.valueOf(-1));
+ } else {
+ item = mOutputModeItems.get(oldValue);
+ switchDispMode(item);
+ String databaseValue = DispList.ItemCode2Name(item);
+ Log.d(TAG, "out put mode save value = "
+ + databaseValue);
+ Settings.System.putString(getContentResolver(),
+ Settings.System.DISPLY_OUTPUT_FORMAT,
+ databaseValue);
+ mOutputMode.setValue(Integer.toString(oldValue));
+ }
+ dialog.dismiss();
+ }
+ }
+ @Override
+ protected void onProgressUpdate(Object... values) {
+ super.onProgressUpdate(values);
+ int time = (Integer)values[0];
+ String str = getString(com.android.settings.R.string.display_mode_time_out_desc);
+ dialog.setMessage(String.format(str, Integer.toString(time)));
+ }
+
+ }.execute();
+
+ }
+ return true;
+ }
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED)) {
+ mWifiDisplayStatus = (WifiDisplayStatus)intent.getParcelableExtra(
+ DisplayManager.EXTRA_WIFI_DISPLAY_STATUS);
+ updateWifiDisplaySummary();
+ }
+ }
+ };
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ if (preference == mFontSizePref) {
+ if (Utils.hasMultipleUsers(getActivity())) {
+ showDialog(DLG_GLOBAL_CHANGE_WARNING);
+ return true;
+ } else {
+ mFontSizePref.click();
+ }
+ }
+ return false;
+ }
+ private void setOutputMode(ListPreference preference){
+ ArrayList items = DispList.getDispList();
+ mOutputModeItems = items;
+ HashMap strMap = DispList.getItemStringIdList();
+ String databaseValue = Settings.System.getString(getContentResolver(),
+ Settings.System.DISPLY_OUTPUT_FORMAT);
+ int autotag = 0;
+ int size = items.size() + autotag;
+ CharSequence[] entries = new CharSequence[size];
+ CharSequence[] entryValues = new CharSequence[size];
+ for (int i = 0; i < size; i++) {
+ if (i == 0 && autotag == 1) {
+ entries[i] = getResources().getText(
+ R.string.display_mode_auto_detect);
+ entryValues[i] = String.valueOf(-1);
+ continue;
+ }
+ entries[i] = getResources().getString(strMap.get(items.get(i - autotag)));
+ entryValues[i] = String.valueOf(i - autotag);
+ if (DispList.ItemName2Code(databaseValue).equals(items.get(i - autotag))) {
+ preference.setValue(String.valueOf(i - autotag));
+ }
+ }
+
+ preference.setEntries(entries);
+ preference.setEntryValues(entryValues);
+ int autoDetect = Settings.System.getInt(getContentResolver(), DISPLAY_MODE_AUTO_KEY,0);
+ if(autoDetect == 1){
+ preference.setValue(String.valueOf(-1));
+ }
+ }
+
+ /**
+ * 根据指定输出模式,设置显示输出模式
+ *
+ * @param item
+ * 指定的显示输出模式对象
+ */
+ private void switchDispMode(DispList.DispFormat item) {
+ DisplayManagerAw displayManager = (DisplayManagerAw) getSystemService(Context.DISPLAY_SERVICE_AW);
+ if (item == null) {
+ return;
+ }
+ if (displayManager.getDisplayOutputType(0) != item.mOutputType
+ || displayManager.getDisplayOutputFormat(0) != item.mFormat) {
+ // displayManager.setDisplayOutputType(0, item.mOutputType,
+ // item.mFormat);
+ displayManager.setDisplayParameter(0, item.mOutputType,
+ item.mFormat);
+ displayManager
+ .setDisplayMode(DisplayManagerAw.DISPLAY_MODE_SINGLE_FB_GPU);
+ // 设置音频输出模式
+ AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ if (audioManager == null) {
+ Log.w(TAG, "audioManager is null");
+ return;
+ }
+ ArrayList audioOutputChannels = audioManager
+ .getActiveAudioDevices(AudioManager.AUDIO_OUTPUT_ACTIVE);
+ if (item.mOutputType == DisplayManagerAw.DISPLAY_OUTPUT_TYPE_HDMI
+ && !audioOutputChannels.contains(AudioManager.AUDIO_NAME_HDMI)) {
+ audioOutputChannels.clear();
+ audioOutputChannels.add(AudioManager.AUDIO_NAME_HDMI);
+ audioManager.setAudioDeviceActive(audioOutputChannels,AudioManager.AUDIO_OUTPUT_ACTIVE);
+ } else if ((item.mOutputType == DisplayManagerAw.DISPLAY_OUTPUT_TYPE_TV || item.mOutputType == DisplayManagerAw.DISPLAY_OUTPUT_TYPE_VGA)
+ && !audioOutputChannels.contains(AudioManager.AUDIO_NAME_CODEC)) {
+ audioOutputChannels.clear();
+ audioOutputChannels.add(AudioManager.AUDIO_NAME_CODEC);
+ audioManager.setAudioDeviceActive(audioOutputChannels,AudioManager.AUDIO_OUTPUT_ACTIVE);
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/DisplayTrimmingPreference.java b/src/com/android/settings/DisplayTrimmingPreference.java
new file mode 100644
index 0000000..76dd3a4
--- /dev/null
+++ b/src/com/android/settings/DisplayTrimmingPreference.java
@@ -0,0 +1,193 @@
+package com.android.settings;
+/*
+ ************************************************************************************
+ * Android Settings
+
+ * (c) Copyright 2006-2010, huanglong Allwinner
+ * All Rights Reserved
+ *
+ * File : SaturationPreference.java
+ * By : huanglong
+ * Version : v1.0
+ * Date : 2011-9-5 16:20:00
+ * Description: Add the Saturation settings to Display.
+ * Update : date author version notes
+ *
+ ************************************************************************************
+ */
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.AlertDialog.Builder;
+import android.content.Context;
+import android.preference.SeekBarDialogPreference;
+import android.preference.SeekBarPreference;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.DisplayManagerAw;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.SeekBar;
+import java.lang.Integer;
+
+import android.os.Bundle;
+import android.os.SystemProperties;
+import com.softwinner.SecureFile;
+import java.lang.Integer;
+import java.lang.String;
+import java.lang.Exception;
+public class DisplayTrimmingPreference extends SeekBarDialogPreference implements
+ SeekBar.OnSeekBarChangeListener{
+
+ private SeekBar mSeekBar;
+
+ private int OldValue;
+
+ private int MAXIMUM_VALUE = 100;
+ private int MINIMUM_VALUE = 90;
+ private String DISPLAY_AREA_RADIO = "display.area_radio";
+ private DisplayManagerAw mDisplayManagerAw;
+ private static boolean mIsPositiveResult;
+
+ public DisplayTrimmingPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mDisplayManagerAw = (DisplayManagerAw) context.getSystemService(Context.DISPLAY_SERVICE_AW);
+ }
+
+ @Override
+ public void createActionButtons() {
+
+ }
+
+
+ @Override
+ protected View onCreateDialogView() {
+ LayoutInflater inflater = LayoutInflater.from(getContext());
+ return inflater.inflate(R.layout.preference_dialog_screen_trimming, null);
+ }
+
+ @Override
+ protected void showDialog(Bundle state) {
+
+ Builder mBuilder = new AlertDialog.Builder(getContext());
+
+ View contentView = onCreateDialogView();
+
+ if (contentView != null) {
+ onBindDialogView(contentView);
+ mBuilder.setView(contentView);
+ }
+ onPrepareDialogBuilder(mBuilder);
+ // Create the dialog
+ final Dialog dialog = mBuilder.create();
+ if (state != null) {
+ dialog.onRestoreInstanceState(state);
+ }
+ contentView.findViewById(R.id.cancel_btn).setOnClickListener(
+ new View.OnClickListener() {
+
+ @Override
+ public void onClick(View arg0) {
+ mIsPositiveResult = false;
+ dialog.dismiss();
+
+ }
+ });
+ contentView.findViewById(R.id.ok_btn).setOnClickListener(
+ new View.OnClickListener() {
+
+ @Override
+ public void onClick(View arg0) {
+ mIsPositiveResult = true;
+ dialog.dismiss();
+ }
+ });
+ dialog.setOnDismissListener(this);
+ dialog.show();
+
+ dialog.getWindow().setGravity(Gravity.CENTER);
+ dialog.getWindow().setLayout(600, 300);
+ }
+
+
+ protected void onBindDialogView(View view){
+ super.onBindDialogView(view);
+
+
+ mSeekBar = getSeekBar(view);
+ mSeekBar.setMax(MAXIMUM_VALUE-MINIMUM_VALUE);
+ OldValue = getSysInt();
+ Log.d("staturation","" + OldValue);
+ mSeekBar.setProgress(OldValue - MINIMUM_VALUE);
+ mSeekBar.setOnSeekBarChangeListener(this);
+ }
+
+ public void onProgressChanged(SeekBar seekBar, int progress,
+ boolean fromTouch){
+ setDisplayPercent(progress + MINIMUM_VALUE);
+ }
+ @Override
+ protected void onDialogClosed(boolean positiveResult){
+ if(mIsPositiveResult){
+ putSysInt(mSeekBar.getProgress() + MINIMUM_VALUE);
+ }else{
+ setDisplayPercent(OldValue);
+ }
+ super.onDialogClosed(mIsPositiveResult);
+ }
+
+ private int getSysInt()
+ {
+ SecureFile file = new SecureFile(DISPLAY_AREA_RADIO);
+ if(!file.exists())
+ {
+ putSysInt(95);
+ }
+ byte[] ret = new byte[255];
+ file.read(ret);
+ String st = new String(ret).substring(0,2);
+ int retInt = 95;
+ try{
+ retInt = Integer.valueOf(st).intValue();
+ if(retInt == 10) retInt = MAXIMUM_VALUE;
+ }catch(Exception e){
+ Log.d("chen","file is broken,rebuild it again");
+ file.delete();
+ putSysInt(retInt);
+ }
+ Log.d("chen","save " + st + " in /private,value is " + retInt);
+ return retInt;
+ }
+ private boolean putSysInt(int value)
+ {
+ SecureFile file = new SecureFile(DISPLAY_AREA_RADIO);
+ if(!file.exists()) file.createFile();
+ byte[] bt = String.valueOf(value).getBytes();
+ boolean ret = file.write(bt,false);
+ String st = new String(bt);
+ Log.d("chen","file content is " + st + " value is " + value);
+
+ Log.d("chen","write value to " + file.getPath() + " is " + getSysInt());
+ return ret;
+ }
+ private void setDisplayPercent(int value) {
+ mDisplayManagerAw.setDisplayAreaPercent(0,value);
+ }
+
+ /*implements method in SeekBar.OnSeekBarChangeListener*/
+ @Override
+ public void onStartTrackingTouch(SeekBar arg0) {
+ // NA
+
+ }
+ /*implements method in SeekBar.OnSeekBarChangeListener*/
+ @Override
+ public void onStopTrackingTouch(SeekBar arg0) {
+ // NA
+
+ }
+
+}
diff --git a/src/com/android/settings/DreamBackend.java b/src/com/android/settings/DreamBackend.java
new file mode 100644
index 0000000..70124ef
--- /dev/null
+++ b/src/com/android/settings/DreamBackend.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import static android.provider.Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK;
+import static android.provider.Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP;
+import static android.provider.Settings.Secure.SCREENSAVER_ENABLED;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.provider.Settings;
+import android.service.dreams.DreamService;
+import android.service.dreams.IDreamManager;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public class DreamBackend {
+ private static final String TAG = DreamSettings.class.getSimpleName() + ".Backend";
+
+ public static class DreamInfo {
+ CharSequence caption;
+ Drawable icon;
+ boolean isActive;
+ public ComponentName componentName;
+ public ComponentName settingsComponentName;
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(DreamInfo.class.getSimpleName());
+ sb.append('[').append(caption);
+ if (isActive)
+ sb.append(",active");
+ sb.append(',').append(componentName);
+ if (settingsComponentName != null)
+ sb.append("settings=").append(settingsComponentName);
+ return sb.append(']').toString();
+ }
+ }
+
+ private final Context mContext;
+ private final IDreamManager mDreamManager;
+ private final DreamInfoComparator mComparator;
+ private final boolean mDreamsEnabledByDefault;
+ private final boolean mDreamsActivatedOnSleepByDefault;
+ private final boolean mDreamsActivatedOnDockByDefault;
+
+ public DreamBackend(Context context) {
+ mContext = context;
+ mDreamManager = IDreamManager.Stub.asInterface(
+ ServiceManager.getService(DreamService.DREAM_SERVICE));
+ mComparator = new DreamInfoComparator(getDefaultDream());
+ mDreamsEnabledByDefault = context.getResources()
+ .getBoolean(com.android.internal.R.bool.config_dreamsEnabledByDefault);
+ mDreamsActivatedOnSleepByDefault = context.getResources()
+ .getBoolean(com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault);
+ mDreamsActivatedOnDockByDefault = context.getResources()
+ .getBoolean(com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault);
+ }
+
+ public List getDreamInfos() {
+ logd("getDreamInfos()");
+ ComponentName activeDream = getActiveDream();
+ PackageManager pm = mContext.getPackageManager();
+ Intent dreamIntent = new Intent(DreamService.SERVICE_INTERFACE);
+ List resolveInfos = pm.queryIntentServices(dreamIntent,
+ PackageManager.GET_META_DATA);
+ List dreamInfos = new ArrayList(resolveInfos.size());
+ for (ResolveInfo resolveInfo : resolveInfos) {
+ if (resolveInfo.serviceInfo == null)
+ continue;
+ DreamInfo dreamInfo = new DreamInfo();
+ dreamInfo.caption = resolveInfo.loadLabel(pm);
+ dreamInfo.icon = resolveInfo.loadIcon(pm);
+ dreamInfo.componentName = getDreamComponentName(resolveInfo);
+ dreamInfo.isActive = dreamInfo.componentName.equals(activeDream);
+ dreamInfo.settingsComponentName = getSettingsComponentName(pm, resolveInfo);
+ dreamInfos.add(dreamInfo);
+ }
+ Collections.sort(dreamInfos, mComparator);
+ return dreamInfos;
+ }
+
+ public ComponentName getDefaultDream() {
+ if (mDreamManager == null)
+ return null;
+ try {
+ return mDreamManager.getDefaultDreamComponent();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to get default dream", e);
+ return null;
+ }
+ }
+
+ public CharSequence getActiveDreamName() {
+ ComponentName cn = getActiveDream();
+ if (cn != null) {
+ PackageManager pm = mContext.getPackageManager();
+ try {
+ ServiceInfo ri = pm.getServiceInfo(cn, 0);
+ if (ri != null) {
+ return ri.loadLabel(pm);
+ }
+ } catch (PackageManager.NameNotFoundException exc) {
+ return null; // uninstalled?
+ }
+ }
+ return null;
+ }
+
+ public boolean isEnabled() {
+ return getBoolean(SCREENSAVER_ENABLED, mDreamsEnabledByDefault);
+ }
+
+ public void setEnabled(boolean value) {
+ logd("setEnabled(%s)", value);
+ setBoolean(SCREENSAVER_ENABLED, value);
+ }
+
+ public boolean isActivatedOnDock() {
+ return getBoolean(SCREENSAVER_ACTIVATE_ON_DOCK, mDreamsActivatedOnDockByDefault);
+ }
+
+ public void setActivatedOnDock(boolean value) {
+ logd("setActivatedOnDock(%s)", value);
+ setBoolean(SCREENSAVER_ACTIVATE_ON_DOCK, value);
+ }
+
+ public boolean isActivatedOnSleep() {
+ return getBoolean(SCREENSAVER_ACTIVATE_ON_SLEEP, mDreamsActivatedOnSleepByDefault);
+ }
+
+ public void setActivatedOnSleep(boolean value) {
+ logd("setActivatedOnSleep(%s)", value);
+ setBoolean(SCREENSAVER_ACTIVATE_ON_SLEEP, value);
+ }
+
+ private boolean getBoolean(String key, boolean def) {
+ return Settings.Secure.getInt(mContext.getContentResolver(), key, def ? 1 : 0) == 1;
+ }
+
+ private void setBoolean(String key, boolean value) {
+ Settings.Secure.putInt(mContext.getContentResolver(), key, value ? 1 : 0);
+ }
+
+ public void setActiveDream(ComponentName dream) {
+ logd("setActiveDream(%s)", dream);
+ if (mDreamManager == null)
+ return;
+ try {
+ ComponentName[] dreams = { dream };
+ mDreamManager.setDreamComponents(dream == null ? null : dreams);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to set active dream to " + dream, e);
+ }
+ }
+
+ public ComponentName getActiveDream() {
+ if (mDreamManager == null)
+ return null;
+ try {
+ ComponentName[] dreams = mDreamManager.getDreamComponents();
+ return dreams != null && dreams.length > 0 ? dreams[0] : null;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to get active dream", e);
+ return null;
+ }
+ }
+
+ public void launchSettings(DreamInfo dreamInfo) {
+ logd("launchSettings(%s)", dreamInfo);
+ if (dreamInfo == null || dreamInfo.settingsComponentName == null)
+ return;
+ mContext.startActivity(new Intent().setComponent(dreamInfo.settingsComponentName));
+ }
+
+ public void preview(DreamInfo dreamInfo) {
+ logd("preview(%s)", dreamInfo);
+ if (mDreamManager == null || dreamInfo == null || dreamInfo.componentName == null)
+ return;
+ try {
+ mDreamManager.testDream(dreamInfo.componentName);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to preview " + dreamInfo, e);
+ }
+ }
+
+ public void startDreaming() {
+ logd("startDreaming()");
+ if (mDreamManager == null)
+ return;
+ try {
+ mDreamManager.dream();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to dream", e);
+ }
+ }
+
+ private static ComponentName getDreamComponentName(ResolveInfo resolveInfo) {
+ if (resolveInfo == null || resolveInfo.serviceInfo == null)
+ return null;
+ return new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name);
+ }
+
+ private static ComponentName getSettingsComponentName(PackageManager pm, ResolveInfo resolveInfo) {
+ if (resolveInfo == null
+ || resolveInfo.serviceInfo == null
+ || resolveInfo.serviceInfo.metaData == null)
+ return null;
+ String cn = null;
+ XmlResourceParser parser = null;
+ Exception caughtException = null;
+ try {
+ parser = resolveInfo.serviceInfo.loadXmlMetaData(pm, DreamService.DREAM_META_DATA);
+ if (parser == null) {
+ Log.w(TAG, "No " + DreamService.DREAM_META_DATA + " meta-data");
+ return null;
+ }
+ Resources res = pm.getResourcesForApplication(resolveInfo.serviceInfo.applicationInfo);
+ AttributeSet attrs = Xml.asAttributeSet(parser);
+ int type;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ }
+ String nodeName = parser.getName();
+ if (!"dream".equals(nodeName)) {
+ Log.w(TAG, "Meta-data does not start with dream tag");
+ return null;
+ }
+ TypedArray sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.Dream);
+ cn = sa.getString(com.android.internal.R.styleable.Dream_settingsActivity);
+ sa.recycle();
+ } catch (NameNotFoundException e) {
+ caughtException = e;
+ } catch (IOException e) {
+ caughtException = e;
+ } catch (XmlPullParserException e) {
+ caughtException = e;
+ } finally {
+ if (parser != null) parser.close();
+ }
+ if (caughtException != null) {
+ Log.w(TAG, "Error parsing : " + resolveInfo.serviceInfo.packageName, caughtException);
+ return null;
+ }
+ return cn == null ? null : ComponentName.unflattenFromString(cn);
+ }
+
+ private static void logd(String msg, Object... args) {
+ if (DreamSettings.DEBUG)
+ Log.d(TAG, args == null || args.length == 0 ? msg : String.format(msg, args));
+ }
+
+ private static class DreamInfoComparator implements Comparator {
+ private final ComponentName mDefaultDream;
+
+ public DreamInfoComparator(ComponentName defaultDream) {
+ mDefaultDream = defaultDream;
+ }
+
+ @Override
+ public int compare(DreamInfo lhs, DreamInfo rhs) {
+ return sortKey(lhs).compareTo(sortKey(rhs));
+ }
+
+ private String sortKey(DreamInfo di) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(di.componentName.equals(mDefaultDream) ? '0' : '1');
+ sb.append(di.caption);
+ return sb.toString();
+ }
+ }
+}
diff --git a/src/com/android/settings/DreamSettings.java b/src/com/android/settings/DreamSettings.java
new file mode 100644
index 0000000..32328d9
--- /dev/null
+++ b/src/com/android/settings/DreamSettings.java
@@ -0,0 +1,368 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import android.app.ActionBar;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.MenuItem.OnMenuItemClickListener;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnTouchListener;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.RadioButton;
+import android.widget.Switch;
+import android.widget.TextView;
+
+import com.android.settings.DreamBackend.DreamInfo;
+
+import java.util.List;
+
+public class DreamSettings extends SettingsPreferenceFragment {
+ private static final String TAG = DreamSettings.class.getSimpleName();
+ static final boolean DEBUG = false;
+ private static final int DIALOG_WHEN_TO_DREAM = 1;
+ private static final String PACKAGE_SCHEME = "package";
+
+ private final PackageReceiver mPackageReceiver = new PackageReceiver();
+
+ private Context mContext;
+ private DreamBackend mBackend;
+ private DreamInfoAdapter mAdapter;
+ private Switch mSwitch;
+ private MenuItem[] mMenuItemsWhenEnabled;
+ private boolean mRefreshing;
+
+ @Override
+ public int getHelpResource() {
+ return R.string.help_url_dreams;
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ logd("onAttach(%s)", activity.getClass().getSimpleName());
+ super.onAttach(activity);
+ mContext = activity;
+ }
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ logd("onCreate(%s)", icicle);
+ super.onCreate(icicle);
+ Activity activity = getActivity();
+
+ mBackend = new DreamBackend(activity);
+ mSwitch = new Switch(activity);
+ mSwitch.setOnCheckedChangeListener(new OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ if (!mRefreshing) {
+ mBackend.setEnabled(isChecked);
+ refreshFromBackend();
+ }
+ }
+ });
+
+ final int padding = activity.getResources().getDimensionPixelSize(
+ R.dimen.action_bar_switch_padding);
+ mSwitch.setPadding(0, 0, padding, 0);
+ activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
+ ActionBar.DISPLAY_SHOW_CUSTOM);
+ activity.getActionBar().setCustomView(mSwitch, new ActionBar.LayoutParams(
+ ActionBar.LayoutParams.WRAP_CONTENT,
+ ActionBar.LayoutParams.WRAP_CONTENT,
+ Gravity.CENTER_VERTICAL | Gravity.END));
+
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public void onDestroyView() {
+ getActivity().getActionBar().setCustomView(null);
+ super.onDestroyView();
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ logd("onActivityCreated(%s)", savedInstanceState);
+ super.onActivityCreated(savedInstanceState);
+
+ ListView listView = getListView();
+
+ TextView emptyView = (TextView) getView().findViewById(android.R.id.empty);
+ emptyView.setText(R.string.screensaver_settings_disabled_prompt);
+ listView.setEmptyView(emptyView);
+
+ mAdapter = new DreamInfoAdapter(mContext);
+ listView.setAdapter(mAdapter);
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ logd("onCreateOptionsMenu()");
+
+ boolean isEnabled = mBackend.isEnabled();
+
+ // create "start" action
+ MenuItem start = createMenuItem(menu, R.string.screensaver_settings_dream_start,
+ MenuItem.SHOW_AS_ACTION_ALWAYS,
+ isEnabled, new Runnable(){
+ @Override
+ public void run() {
+ mBackend.startDreaming();
+ }});
+
+ // create "when to dream" overflow menu item
+ MenuItem whenToDream = createMenuItem(menu,
+ R.string.screensaver_settings_when_to_dream,
+ MenuItem.SHOW_AS_ACTION_IF_ROOM,
+ isEnabled,
+ new Runnable() {
+ @Override
+ public void run() {
+ showDialog(DIALOG_WHEN_TO_DREAM);
+ }});
+
+ // create "help" overflow menu item (make sure it appears last)
+ super.onCreateOptionsMenu(menu, inflater);
+
+ mMenuItemsWhenEnabled = new MenuItem[] { start, whenToDream };
+ }
+
+ private MenuItem createMenuItem(Menu menu,
+ int titleRes, int actionEnum, boolean isEnabled, final Runnable onClick) {
+ MenuItem item = menu.add(titleRes);
+ item.setShowAsAction(actionEnum);
+ item.setEnabled(isEnabled);
+ item.setOnMenuItemClickListener(new OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ onClick.run();
+ return true;
+ }
+ });
+ return item;
+ }
+
+ @Override
+ public Dialog onCreateDialog(int dialogId) {
+ logd("onCreateDialog(%s)", dialogId);
+ if (dialogId == DIALOG_WHEN_TO_DREAM)
+ return createWhenToDreamDialog();
+ return super.onCreateDialog(dialogId);
+ }
+
+ private Dialog createWhenToDreamDialog() {
+ final CharSequence[] items = {
+ mContext.getString(R.string.screensaver_settings_summary_dock),
+ mContext.getString(R.string.screensaver_settings_summary_sleep),
+ mContext.getString(R.string.screensaver_settings_summary_either_short)
+ };
+
+ int initialSelection = mBackend.isActivatedOnDock() && mBackend.isActivatedOnSleep() ? 2
+ : mBackend.isActivatedOnDock() ? 0
+ : mBackend.isActivatedOnSleep() ? 1
+ : -1;
+
+ return new AlertDialog.Builder(mContext)
+ .setTitle(R.string.screensaver_settings_when_to_dream)
+ .setSingleChoiceItems(items, initialSelection, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int item) {
+ mBackend.setActivatedOnDock(item == 0 || item == 2);
+ mBackend.setActivatedOnSleep(item == 1 || item == 2);
+ }
+ })
+ .create();
+ }
+
+ @Override
+ public void onPause() {
+ logd("onPause()");
+ super.onPause();
+ mContext.unregisterReceiver(mPackageReceiver);
+ }
+
+ @Override
+ public void onResume() {
+ logd("onResume()");
+ super.onResume();
+ refreshFromBackend();
+
+ // listen for package changes
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+ filter.addDataScheme(PACKAGE_SCHEME);
+ mContext.registerReceiver(mPackageReceiver , filter);
+ }
+
+ public static int getSummaryResource(Context context) {
+ DreamBackend backend = new DreamBackend(context);
+ boolean isEnabled = backend.isEnabled();
+ boolean activatedOnSleep = backend.isActivatedOnSleep();
+ boolean activatedOnDock = backend.isActivatedOnDock();
+ boolean activatedOnEither = activatedOnSleep && activatedOnDock;
+ return !isEnabled ? R.string.screensaver_settings_summary_off
+ : activatedOnEither ? R.string.screensaver_settings_summary_either_long
+ : activatedOnSleep ? R.string.screensaver_settings_summary_sleep
+ : activatedOnDock ? R.string.screensaver_settings_summary_dock
+ : 0;
+ }
+
+ public static CharSequence getSummaryTextWithDreamName(Context context) {
+ DreamBackend backend = new DreamBackend(context);
+ boolean isEnabled = backend.isEnabled();
+ if (!isEnabled) {
+ return context.getString(R.string.screensaver_settings_summary_off);
+ } else {
+ return backend.getActiveDreamName();
+ }
+ }
+
+ private void refreshFromBackend() {
+ logd("refreshFromBackend()");
+ mRefreshing = true;
+ boolean dreamsEnabled = mBackend.isEnabled();
+ if (mSwitch.isChecked() != dreamsEnabled)
+ mSwitch.setChecked(dreamsEnabled);
+
+ mAdapter.clear();
+ if (dreamsEnabled) {
+ List dreamInfos = mBackend.getDreamInfos();
+ mAdapter.addAll(dreamInfos);
+ }
+ if (mMenuItemsWhenEnabled != null)
+ for (MenuItem menuItem : mMenuItemsWhenEnabled)
+ menuItem.setEnabled(dreamsEnabled);
+ mRefreshing = false;
+ }
+
+ private static void logd(String msg, Object... args) {
+ if (DEBUG)
+ Log.d(TAG, args == null || args.length == 0 ? msg : String.format(msg, args));
+ }
+
+ private class DreamInfoAdapter extends ArrayAdapter {
+ private final LayoutInflater mInflater;
+
+ public DreamInfoAdapter(Context context) {
+ super(context, 0);
+ mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ DreamInfo dreamInfo = getItem(position);
+ logd("getView(%s)", dreamInfo.caption);
+ final View row = convertView != null ? convertView : createDreamInfoRow(parent);
+ row.setTag(dreamInfo);
+
+ // bind icon
+ ((ImageView) row.findViewById(android.R.id.icon)).setImageDrawable(dreamInfo.icon);
+
+ // bind caption
+ ((TextView) row.findViewById(android.R.id.title)).setText(dreamInfo.caption);
+
+ // bind radio button
+ RadioButton radioButton = (RadioButton) row.findViewById(android.R.id.button1);
+ radioButton.setChecked(dreamInfo.isActive);
+ radioButton.setOnTouchListener(new OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ row.onTouchEvent(event);
+ return false;
+ }});
+
+ // bind settings button + divider
+ boolean showSettings = dreamInfo.settingsComponentName != null;
+ View settingsDivider = row.findViewById(R.id.divider);
+ settingsDivider.setVisibility(showSettings ? View.VISIBLE : View.INVISIBLE);
+
+ ImageView settingsButton = (ImageView) row.findViewById(android.R.id.button2);
+ settingsButton.setVisibility(showSettings ? View.VISIBLE : View.INVISIBLE);
+ settingsButton.setAlpha(dreamInfo.isActive ? 1f : 0.33f);
+ settingsButton.setEnabled(dreamInfo.isActive);
+ settingsButton.setOnClickListener(new OnClickListener(){
+ @Override
+ public void onClick(View v) {
+ mBackend.launchSettings((DreamInfo) row.getTag());
+ }});
+
+ return row;
+ }
+
+ private View createDreamInfoRow(ViewGroup parent) {
+ final View row = mInflater.inflate(R.layout.dream_info_row, parent, false);
+ row.setOnClickListener(new OnClickListener(){
+ @Override
+ public void onClick(View v) {
+ v.setPressed(true);
+ activate((DreamInfo) row.getTag());
+ }});
+ return row;
+ }
+
+ private DreamInfo getCurrentSelection() {
+ for (int i = 0; i < getCount(); i++) {
+ DreamInfo dreamInfo = getItem(i);
+ if (dreamInfo.isActive)
+ return dreamInfo;
+ }
+ return null;
+ }
+ private void activate(DreamInfo dreamInfo) {
+ if (dreamInfo.equals(getCurrentSelection()))
+ return;
+ for (int i = 0; i < getCount(); i++) {
+ getItem(i).isActive = false;
+ }
+ dreamInfo.isActive = true;
+ mBackend.setActiveDream(dreamInfo.componentName);
+ notifyDataSetChanged();
+ }
+ }
+
+ private class PackageReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ logd("PackageReceiver.onReceive");
+ refreshFromBackend();
+ }
+ }
+}
diff --git a/src/com/android/settings/EditPinPreference.java b/src/com/android/settings/EditPinPreference.java
new file mode 100644
index 0000000..1877d43
--- /dev/null
+++ b/src/com/android/settings/EditPinPreference.java
@@ -0,0 +1,81 @@
+/*
+ * 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.android.settings;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.preference.EditTextPreference;
+import android.text.InputType;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.EditText;
+
+/**
+ * TODO: Add a soft dialpad for PIN entry.
+ */
+class EditPinPreference extends EditTextPreference {
+
+ interface OnPinEnteredListener {
+ void onPinEntered(EditPinPreference preference, boolean positiveResult);
+ }
+
+ private OnPinEnteredListener mPinListener;
+
+ public EditPinPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public EditPinPreference(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public void setOnPinEnteredListener(OnPinEnteredListener listener) {
+ mPinListener = listener;
+ }
+
+ @Override
+ protected void onBindDialogView(View view) {
+ super.onBindDialogView(view);
+
+ final EditText editText = getEditText();
+
+ if (editText != null) {
+ editText.setInputType(InputType.TYPE_CLASS_NUMBER |
+ InputType.TYPE_NUMBER_VARIATION_PASSWORD);
+ }
+ }
+
+ public boolean isDialogOpen() {
+ Dialog dialog = getDialog();
+ return dialog != null && dialog.isShowing();
+ }
+
+ @Override
+ protected void onDialogClosed(boolean positiveResult) {
+ super.onDialogClosed(positiveResult);
+ if (mPinListener != null) {
+ mPinListener.onPinEntered(this, positiveResult);
+ }
+ }
+
+ public void showPinDialog() {
+ Dialog dialog = getDialog();
+ if (dialog == null || !dialog.isShowing()) {
+ showDialog(null);
+ }
+ }
+}
diff --git a/src/com/android/settings/IccLockSettings.java b/src/com/android/settings/IccLockSettings.java
new file mode 100644
index 0000000..ab12587
--- /dev/null
+++ b/src/com/android/settings/IccLockSettings.java
@@ -0,0 +1,401 @@
+/*
+ * 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.android.settings;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.os.AsyncResult;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
+import android.widget.Toast;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.TelephonyIntents;
+
+/**
+ * Implements the preference screen to enable/disable ICC lock and
+ * also the dialogs to change the ICC PIN. In the former case, enabling/disabling
+ * the ICC lock will prompt the user for the current PIN.
+ * In the Change PIN case, it prompts the user for old pin, new pin and new pin
+ * again before attempting to change it. Calls the SimCard interface to execute
+ * these operations.
+ *
+ */
+public class IccLockSettings extends PreferenceActivity
+ implements EditPinPreference.OnPinEnteredListener {
+
+ private static final int OFF_MODE = 0;
+ // State when enabling/disabling ICC lock
+ private static final int ICC_LOCK_MODE = 1;
+ // State when entering the old pin
+ private static final int ICC_OLD_MODE = 2;
+ // State when entering the new pin - first time
+ private static final int ICC_NEW_MODE = 3;
+ // State when entering the new pin - second time
+ private static final int ICC_REENTER_MODE = 4;
+
+ // Keys in xml file
+ private static final String PIN_DIALOG = "sim_pin";
+ private static final String PIN_TOGGLE = "sim_toggle";
+ // Keys in icicle
+ private static final String DIALOG_STATE = "dialogState";
+ private static final String DIALOG_PIN = "dialogPin";
+ private static final String DIALOG_ERROR = "dialogError";
+ private static final String ENABLE_TO_STATE = "enableState";
+
+ // Save and restore inputted PIN code when configuration changed
+ // (ex. portrait<-->landscape) during change PIN code
+ private static final String OLD_PINCODE = "oldPinCode";
+ private static final String NEW_PINCODE = "newPinCode";
+
+ private static final int MIN_PIN_LENGTH = 4;
+ private static final int MAX_PIN_LENGTH = 8;
+ // Which dialog to show next when popped up
+ private int mDialogState = OFF_MODE;
+
+ private String mPin;
+ private String mOldPin;
+ private String mNewPin;
+ private String mError;
+ // Are we trying to enable or disable ICC lock?
+ private boolean mToState;
+
+ private Phone mPhone;
+
+ private EditPinPreference mPinDialog;
+ private CheckBoxPreference mPinToggle;
+
+ private Resources mRes;
+
+ // For async handler to identify request type
+ private static final int MSG_ENABLE_ICC_PIN_COMPLETE = 100;
+ private static final int MSG_CHANGE_ICC_PIN_COMPLETE = 101;
+ private static final int MSG_SIM_STATE_CHANGED = 102;
+
+ // For replies from IccCard interface
+ private Handler mHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ AsyncResult ar = (AsyncResult) msg.obj;
+ switch (msg.what) {
+ case MSG_ENABLE_ICC_PIN_COMPLETE:
+ iccLockChanged(ar.exception == null);
+ break;
+ case MSG_CHANGE_ICC_PIN_COMPLETE:
+ iccPinChanged(ar.exception == null);
+ break;
+ case MSG_SIM_STATE_CHANGED:
+ updatePreferences();
+ break;
+ }
+
+ return;
+ }
+ };
+
+ private final BroadcastReceiver mSimStateReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(action)) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_SIM_STATE_CHANGED));
+ }
+ }
+ };
+
+ // For top-level settings screen to query
+ static boolean isIccLockEnabled() {
+ return PhoneFactory.getDefaultPhone().getIccCard().getIccLockEnabled();
+ }
+
+ static String getSummary(Context context) {
+ Resources res = context.getResources();
+ String summary = isIccLockEnabled()
+ ? res.getString(R.string.sim_lock_on)
+ : res.getString(R.string.sim_lock_off);
+ return summary;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (Utils.isMonkeyRunning()) {
+ finish();
+ return;
+ }
+
+ addPreferencesFromResource(R.xml.sim_lock_settings);
+
+ mPinDialog = (EditPinPreference) findPreference(PIN_DIALOG);
+ mPinToggle = (CheckBoxPreference) findPreference(PIN_TOGGLE);
+ if (savedInstanceState != null && savedInstanceState.containsKey(DIALOG_STATE)) {
+ mDialogState = savedInstanceState.getInt(DIALOG_STATE);
+ mPin = savedInstanceState.getString(DIALOG_PIN);
+ mError = savedInstanceState.getString(DIALOG_ERROR);
+ mToState = savedInstanceState.getBoolean(ENABLE_TO_STATE);
+
+ // Restore inputted PIN code
+ switch (mDialogState) {
+ case ICC_NEW_MODE:
+ mOldPin = savedInstanceState.getString(OLD_PINCODE);
+ break;
+
+ case ICC_REENTER_MODE:
+ mOldPin = savedInstanceState.getString(OLD_PINCODE);
+ mNewPin = savedInstanceState.getString(NEW_PINCODE);
+ break;
+
+ case ICC_LOCK_MODE:
+ case ICC_OLD_MODE:
+ default:
+ break;
+ }
+ }
+
+ mPinDialog.setOnPinEnteredListener(this);
+
+ // Don't need any changes to be remembered
+ getPreferenceScreen().setPersistent(false);
+
+ mPhone = PhoneFactory.getDefaultPhone();
+ mRes = getResources();
+ updatePreferences();
+ }
+
+ private void updatePreferences() {
+ mPinToggle.setChecked(mPhone.getIccCard().getIccLockEnabled());
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ // ACTION_SIM_STATE_CHANGED is sticky, so we'll receive current state after this call,
+ // which will call updatePreferences().
+ final IntentFilter filter = new IntentFilter(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
+ registerReceiver(mSimStateReceiver, filter);
+
+ if (mDialogState != OFF_MODE) {
+ showPinDialog();
+ } else {
+ // Prep for standard click on "Change PIN"
+ resetDialogState();
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ unregisterReceiver(mSimStateReceiver);
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle out) {
+ // Need to store this state for slider open/close
+ // There is one case where the dialog is popped up by the preference
+ // framework. In that case, let the preference framework store the
+ // dialog state. In other cases, where this activity manually launches
+ // the dialog, store the state of the dialog.
+ if (mPinDialog.isDialogOpen()) {
+ out.putInt(DIALOG_STATE, mDialogState);
+ out.putString(DIALOG_PIN, mPinDialog.getEditText().getText().toString());
+ out.putString(DIALOG_ERROR, mError);
+ out.putBoolean(ENABLE_TO_STATE, mToState);
+
+ // Save inputted PIN code
+ switch (mDialogState) {
+ case ICC_NEW_MODE:
+ out.putString(OLD_PINCODE, mOldPin);
+ break;
+
+ case ICC_REENTER_MODE:
+ out.putString(OLD_PINCODE, mOldPin);
+ out.putString(NEW_PINCODE, mNewPin);
+ break;
+
+ case ICC_LOCK_MODE:
+ case ICC_OLD_MODE:
+ default:
+ break;
+ }
+ } else {
+ super.onSaveInstanceState(out);
+ }
+ }
+
+ private void showPinDialog() {
+ if (mDialogState == OFF_MODE) {
+ return;
+ }
+ setDialogValues();
+
+ mPinDialog.showPinDialog();
+ }
+
+ private void setDialogValues() {
+ mPinDialog.setText(mPin);
+ String message = "";
+ switch (mDialogState) {
+ case ICC_LOCK_MODE:
+ message = mRes.getString(R.string.sim_enter_pin);
+ mPinDialog.setDialogTitle(mToState
+ ? mRes.getString(R.string.sim_enable_sim_lock)
+ : mRes.getString(R.string.sim_disable_sim_lock));
+ break;
+ case ICC_OLD_MODE:
+ message = mRes.getString(R.string.sim_enter_old);
+ mPinDialog.setDialogTitle(mRes.getString(R.string.sim_change_pin));
+ break;
+ case ICC_NEW_MODE:
+ message = mRes.getString(R.string.sim_enter_new);
+ mPinDialog.setDialogTitle(mRes.getString(R.string.sim_change_pin));
+ break;
+ case ICC_REENTER_MODE:
+ message = mRes.getString(R.string.sim_reenter_new);
+ mPinDialog.setDialogTitle(mRes.getString(R.string.sim_change_pin));
+ break;
+ }
+ if (mError != null) {
+ message = mError + "\n" + message;
+ mError = null;
+ }
+ mPinDialog.setDialogMessage(message);
+ }
+
+ public void onPinEntered(EditPinPreference preference, boolean positiveResult) {
+ if (!positiveResult) {
+ resetDialogState();
+ return;
+ }
+
+ mPin = preference.getText();
+ if (!reasonablePin(mPin)) {
+ // inject error message and display dialog again
+ mError = mRes.getString(R.string.sim_bad_pin);
+ showPinDialog();
+ return;
+ }
+ switch (mDialogState) {
+ case ICC_LOCK_MODE:
+ tryChangeIccLockState();
+ break;
+ case ICC_OLD_MODE:
+ mOldPin = mPin;
+ mDialogState = ICC_NEW_MODE;
+ mError = null;
+ mPin = null;
+ showPinDialog();
+ break;
+ case ICC_NEW_MODE:
+ mNewPin = mPin;
+ mDialogState = ICC_REENTER_MODE;
+ mPin = null;
+ showPinDialog();
+ break;
+ case ICC_REENTER_MODE:
+ if (!mPin.equals(mNewPin)) {
+ mError = mRes.getString(R.string.sim_pins_dont_match);
+ mDialogState = ICC_NEW_MODE;
+ mPin = null;
+ showPinDialog();
+ } else {
+ mError = null;
+ tryChangePin();
+ }
+ break;
+ }
+ }
+
+ public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
+ if (preference == mPinToggle) {
+ // Get the new, preferred state
+ mToState = mPinToggle.isChecked();
+ // Flip it back and pop up pin dialog
+ mPinToggle.setChecked(!mToState);
+ mDialogState = ICC_LOCK_MODE;
+ showPinDialog();
+ } else if (preference == mPinDialog) {
+ mDialogState = ICC_OLD_MODE;
+ return false;
+ }
+ return true;
+ }
+
+ private void tryChangeIccLockState() {
+ // Try to change icc lock. If it succeeds, toggle the lock state and
+ // reset dialog state. Else inject error message and show dialog again.
+ Message callback = Message.obtain(mHandler, MSG_ENABLE_ICC_PIN_COMPLETE);
+ mPhone.getIccCard().setIccLockEnabled(mToState, mPin, callback);
+ // Disable the setting till the response is received.
+ mPinToggle.setEnabled(false);
+ }
+
+ private void iccLockChanged(boolean success) {
+ if (success) {
+ mPinToggle.setChecked(mToState);
+ } else {
+ Toast.makeText(this, mRes.getString(R.string.sim_lock_failed), Toast.LENGTH_SHORT)
+ .show();
+ }
+ mPinToggle.setEnabled(true);
+ resetDialogState();
+ }
+
+ private void iccPinChanged(boolean success) {
+ if (!success) {
+ Toast.makeText(this, mRes.getString(R.string.sim_change_failed),
+ Toast.LENGTH_SHORT)
+ .show();
+ } else {
+ Toast.makeText(this, mRes.getString(R.string.sim_change_succeeded),
+ Toast.LENGTH_SHORT)
+ .show();
+
+ }
+ resetDialogState();
+ }
+
+ private void tryChangePin() {
+ Message callback = Message.obtain(mHandler, MSG_CHANGE_ICC_PIN_COMPLETE);
+ mPhone.getIccCard().changeIccLockPassword(mOldPin,
+ mNewPin, callback);
+ }
+
+ private boolean reasonablePin(String pin) {
+ if (pin == null || pin.length() < MIN_PIN_LENGTH || pin.length() > MAX_PIN_LENGTH) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ private void resetDialogState() {
+ mError = null;
+ mDialogState = ICC_OLD_MODE; // Default for when Change PIN is clicked
+ mPin = "";
+ setDialogValues();
+ mDialogState = OFF_MODE;
+ }
+}
diff --git a/src/com/android/settings/IconPreferenceScreen.java b/src/com/android/settings/IconPreferenceScreen.java
new file mode 100644
index 0000000..64fce29
--- /dev/null
+++ b/src/com/android/settings/IconPreferenceScreen.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.preference.Preference;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+public class IconPreferenceScreen extends Preference {
+
+ private Drawable mIcon;
+
+ // Whether or not the text and icon should be highlighted (as selected)
+ private boolean mHighlight;
+
+ public IconPreferenceScreen(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public IconPreferenceScreen(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ setLayoutResource(R.layout.preference_icon);
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.IconPreferenceScreen, defStyle, 0);
+ mIcon = a.getDrawable(R.styleable.IconPreferenceScreen_icon);
+ }
+
+ @Override
+ public void onBindView(View view) {
+ super.onBindView(view);
+ ImageView imageView = (ImageView) view.findViewById(R.id.icon);
+ if (imageView != null && mIcon != null) {
+ imageView.setImageDrawable(mIcon);
+ }
+ TextView textView = (TextView) view.findViewById(android.R.id.title);
+ }
+
+ /**
+ * Sets the icon for this Preference with a Drawable.
+ *
+ * @param icon The icon for this Preference
+ */
+ public void setIcon(Drawable icon) {
+ if ((icon == null && mIcon != null) || (icon != null && !icon.equals(mIcon))) {
+ mIcon = icon;
+ notifyChanged();
+ }
+ }
+
+ /**
+ * Returns the icon of this Preference.
+ *
+ * @return The icon.
+ * @see #setIcon(Drawable)
+ */
+ public Drawable getIcon() {
+ return mIcon;
+ }
+
+ public void setHighlighted(boolean highlight) {
+ mHighlight = highlight;
+ notifyChanged();
+ }
+}
diff --git a/src/com/android/settings/KeyguardAppWidgetPickActivity.java b/src/com/android/settings/KeyguardAppWidgetPickActivity.java
new file mode 100644
index 0000000..80dbc0c
--- /dev/null
+++ b/src/com/android/settings/KeyguardAppWidgetPickActivity.java
@@ -0,0 +1,627 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.LauncherActivity.IconResizer;
+import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.IWindowManager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.GridView;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.internal.widget.LockPatternUtils;
+
+import java.lang.ref.WeakReference;
+import java.util.List;
+
+/**
+ * Displays a list of {@link AppWidgetProviderInfo} widgets, along with any
+ * injected special widgets specified through
+ * {@link AppWidgetManager#EXTRA_CUSTOM_INFO} and
+ * {@link AppWidgetManager#EXTRA_CUSTOM_EXTRAS}.
+ *
+ * When an installed {@link AppWidgetProviderInfo} is selected, this activity
+ * will bind it to the given {@link AppWidgetManager#EXTRA_APPWIDGET_ID},
+ * otherwise it will return the requested extras.
+ */
+public class KeyguardAppWidgetPickActivity extends Activity
+ implements GridView.OnItemClickListener,
+ AppWidgetLoader.ItemConstructor {
+ private static final String TAG = "KeyguardAppWidgetPickActivity";
+ private static final int REQUEST_PICK_APPWIDGET = 126;
+ private static final int REQUEST_CREATE_APPWIDGET = 127;
+
+ private AppWidgetLoader- mAppWidgetLoader;
+ private List
- mItems;
+ private GridView mGridView;
+ private AppWidgetAdapter mAppWidgetAdapter;
+ private AppWidgetManager mAppWidgetManager;
+ private int mAppWidgetId;
+ // Might make it possible to make this be false in future
+ private boolean mAddingToKeyguard = true;
+ private Intent mResultData;
+ private LockPatternUtils mLockPatternUtils;
+ private Bundle mExtraConfigureOptions;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ setContentView(R.layout.keyguard_appwidget_picker_layout);
+ super.onCreate(savedInstanceState);
+
+ // Set default return data
+ setResultData(RESULT_CANCELED, null);
+
+ // Read the appWidgetId passed our direction, otherwise bail if not found
+ final Intent intent = getIntent();
+ if (intent.hasExtra(AppWidgetManager.EXTRA_APPWIDGET_ID)) {
+ mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
+ AppWidgetManager.INVALID_APPWIDGET_ID);
+ } else {
+ finish();
+ }
+ mExtraConfigureOptions = intent.getBundleExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS);
+
+ mGridView = (GridView) findViewById(R.id.widget_list);
+ DisplayMetrics dm = new DisplayMetrics();
+ getWindowManager().getDefaultDisplay().getMetrics(dm);
+ int maxGridWidth = getResources().getDimensionPixelSize(
+ R.dimen.keyguard_appwidget_picker_max_width);
+
+ if (maxGridWidth < dm.widthPixels) {
+ mGridView.getLayoutParams().width = maxGridWidth;
+ }
+ mAppWidgetManager = AppWidgetManager.getInstance(this);
+ mAppWidgetLoader = new AppWidgetLoader
- (this, mAppWidgetManager, this);
+ mItems = mAppWidgetLoader.getItems(getIntent());
+ mAppWidgetAdapter = new AppWidgetAdapter(this, mItems);
+ mGridView.setAdapter(mAppWidgetAdapter);
+ mGridView.setOnItemClickListener(this);
+
+ mLockPatternUtils = new LockPatternUtils(this); // TEMP-- we want to delete this
+ }
+
+ /**
+ * Convenience method for setting the result code and intent. This method
+ * correctly injects the {@link AppWidgetManager#EXTRA_APPWIDGET_ID} that
+ * most hosts expect returned.
+ */
+ void setResultData(int code, Intent intent) {
+ Intent result = intent != null ? intent : new Intent();
+ result.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
+ mResultData = result;
+ setResult(code, result);
+ }
+
+ /**
+ * Item that appears in the AppWidget picker grid.
+ */
+ public static class Item implements AppWidgetLoader.LabelledItem {
+ protected static IconResizer sResizer;
+
+
+ CharSequence label;
+ int appWidgetPreviewId;
+ int iconId;
+ String packageName;
+ String className;
+ Bundle extras;
+ private WidgetPreviewLoader mWidgetPreviewLoader;
+ private Context mContext;
+
+ /**
+ * Create a list item from given label and icon.
+ */
+ Item(Context context, CharSequence label) {
+ this.label = label;
+ mContext = context;
+ }
+
+ void loadWidgetPreview(ImageView v) {
+ mWidgetPreviewLoader = new WidgetPreviewLoader(mContext, v);
+ mWidgetPreviewLoader.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
+ }
+
+ void cancelLoadingWidgetPreview() {
+ if (mWidgetPreviewLoader != null) {
+ mWidgetPreviewLoader.cancel(false);
+ mWidgetPreviewLoader = null;
+ }
+ }
+
+ /**
+ * Build the {@link Intent} described by this item. If this item
+ * can't create a valid {@link android.content.ComponentName}, it will return
+ * {@link Intent#ACTION_CREATE_SHORTCUT} filled with the item label.
+ */
+ Intent getIntent() {
+ Intent intent = new Intent();
+ if (packageName != null && className != null) {
+ // Valid package and class, so fill details as normal intent
+ intent.setClassName(packageName, className);
+ if (extras != null) {
+ intent.putExtras(extras);
+ }
+ } else {
+ // No valid package or class, so treat as shortcut with label
+ intent.setAction(Intent.ACTION_CREATE_SHORTCUT);
+ intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, label);
+ }
+ return intent;
+ }
+
+ public CharSequence getLabel() {
+ return label;
+ }
+
+ class WidgetPreviewLoader extends AsyncTask {
+ private Resources mResources;
+ private PackageManager mPackageManager;
+ private int mIconDpi;
+ private ImageView mView;
+ public WidgetPreviewLoader(Context context, ImageView v) {
+ super();
+ mResources = context.getResources();
+ mPackageManager = context.getPackageManager();
+ ActivityManager activityManager =
+ (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ mIconDpi = activityManager.getLauncherLargeIconDensity();
+ mView = v;
+ }
+ public Void doInBackground(Void... params) {
+ if (!isCancelled()) {
+ int appWidgetPreviewWidth =
+ mResources.getDimensionPixelSize(R.dimen.appwidget_preview_width);
+ int appWidgetPreviewHeight =
+ mResources.getDimensionPixelSize(R.dimen.appwidget_preview_height);
+ Bitmap b = getWidgetPreview(new ComponentName(packageName, className),
+ appWidgetPreviewId, iconId,
+ appWidgetPreviewWidth, appWidgetPreviewHeight);
+ publishProgress(b);
+ }
+ return null;
+ }
+ public void onProgressUpdate(Bitmap... values) {
+ if (!isCancelled()) {
+ Bitmap b = values[0];
+ mView.setImageBitmap(b);
+ }
+ }
+ abstract class WeakReferenceThreadLocal {
+ private ThreadLocal> mThreadLocal;
+ public WeakReferenceThreadLocal() {
+ mThreadLocal = new ThreadLocal>();
+ }
+
+ abstract T initialValue();
+
+ public void set(T t) {
+ mThreadLocal.set(new WeakReference(t));
+ }
+
+ public T get() {
+ WeakReference reference = mThreadLocal.get();
+ T obj;
+ if (reference == null) {
+ obj = initialValue();
+ mThreadLocal.set(new WeakReference(obj));
+ return obj;
+ } else {
+ obj = reference.get();
+ if (obj == null) {
+ obj = initialValue();
+ mThreadLocal.set(new WeakReference(obj));
+ }
+ return obj;
+ }
+ }
+ }
+
+ class CanvasCache extends WeakReferenceThreadLocal
| |