diff options
Diffstat (limited to 'src')
244 files changed, 31818 insertions, 3133 deletions
diff --git a/src/com/android/settings/AnimationScalePreference.java b/src/com/android/settings/AnimationScalePreference.java new file mode 100644 index 0000000..d1c5877 --- /dev/null +++ b/src/com/android/settings/AnimationScalePreference.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings; + +import android.content.Context; +import android.preference.DialogPreference; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.SeekBar; +import android.widget.TextView; + +public class AnimationScalePreference extends DialogPreference + implements SeekBar.OnSeekBarChangeListener { + + private TextView mScaleText; + private IntervalSeekBar mSeekBar; + + private float mScale = 1.0f; + + public AnimationScalePreference(Context context, AttributeSet attrs) { + super(context, attrs); + + setPositiveButtonText(android.R.string.ok); + setNegativeButtonText(android.R.string.cancel); + + setDialogLayoutResource(R.layout.preference_dialog_fontsize); + } + + @Override + protected View onCreateDialogView() { + LayoutInflater inflater = + (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View view = inflater.inflate(R.layout.preference_dialog_animation_scale, null); + + mScaleText = (TextView) view.findViewById(R.id.scale); + mScaleText.setText(String.valueOf(mScale) + "x"); + + mSeekBar = (IntervalSeekBar) view.findViewById(R.id.scale_seekbar); + mSeekBar.setProgressFloat(mScale); + mSeekBar.setOnSeekBarChangeListener(this); + + return view; + } + + public void setScale(float scale) { + mScale = scale; + setSummary(String.valueOf(scale) + "x"); + } + + @Override + protected void onDialogClosed(boolean positiveResult) { + if (positiveResult) { + callChangeListener(mSeekBar.getProgressFloat()); + } + } + + @Override + protected void onClick() { + // Ignore this until an explicit call to click() + } + + public void click() { + super.onClick(); + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + mScaleText.setText(String.valueOf(mSeekBar.getProgressFloat()) + "x"); + } + + // Not used + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + } +} diff --git a/src/com/android/settings/ApnEditor.java b/src/com/android/settings/ApnEditor.java index 8299c67..3d744d6 100644 --- a/src/com/android/settings/ApnEditor.java +++ b/src/com/android/settings/ApnEditor.java @@ -30,7 +30,6 @@ import android.preference.EditTextPreference; import android.preference.ListPreference; import android.preference.MultiSelectListPreference; import android.preference.Preference; -import android.preference.PreferenceActivity; import android.preference.SwitchPreference; import android.provider.Telephony; import android.telephony.ServiceState; @@ -43,7 +42,10 @@ import android.view.Menu; import android.view.MenuItem; import com.android.internal.logging.MetricsLogger; +import java.util.ArrayList; import java.util.HashSet; +import java.util.Iterator; +import java.util.List; import java.util.Set; public class ApnEditor extends InstrumentedPreferenceActivity @@ -60,10 +62,13 @@ public class ApnEditor extends InstrumentedPreferenceActivity private final static String KEY_BEARER_MULTI = "bearer_multi"; private final static String KEY_MVNO_TYPE = "mvno_type"; + private final static String PROTOCOL_IPV4V6= "IPV4V6"; + private static final int MENU_DELETE = Menu.FIRST; private static final int MENU_SAVE = Menu.FIRST + 1; private static final int MENU_CANCEL = Menu.FIRST + 2; private static final int ERROR_DIALOG_ID = 0; + private static final int DUPLICATE_DIALOG_ID = 1; private static String sNotSet; private EditTextPreference mName; @@ -86,9 +91,11 @@ public class ApnEditor extends InstrumentedPreferenceActivity private MultiSelectListPreference mBearerMulti; private ListPreference mMvnoType; private EditTextPreference mMvnoMatchData; + private EditTextPreference mPppNumber; private String mCurMnc; private String mCurMcc; + private boolean mDisableEditor = false; private Uri mUri; private Cursor mCursor; @@ -127,7 +134,8 @@ public class ApnEditor extends InstrumentedPreferenceActivity Telephony.Carriers.BEARER_BITMASK, // 19 Telephony.Carriers.ROAMING_PROTOCOL, // 20 Telephony.Carriers.MVNO_TYPE, // 21 - Telephony.Carriers.MVNO_MATCH_DATA // 22 + Telephony.Carriers.MVNO_MATCH_DATA, // 22 + "ppp_number" // 23 }; private static final int ID_INDEX = 0; @@ -152,6 +160,7 @@ public class ApnEditor extends InstrumentedPreferenceActivity private static final int ROAMING_PROTOCOL_INDEX = 20; private static final int MVNO_TYPE_INDEX = 21; private static final int MVNO_MATCH_DATA_INDEX = 22; + private static final int PPP_NUMBER_INDEX = 23; @Override @@ -174,6 +183,7 @@ public class ApnEditor extends InstrumentedPreferenceActivity mMcc = (EditTextPreference) findPreference("apn_mcc"); mMnc = (EditTextPreference) findPreference("apn_mnc"); mApnType = (EditTextPreference) findPreference("apn_type"); + mPppNumber = (EditTextPreference) findPreference("apn_ppp_number"); mAuthType = (ListPreference) findPreference(KEY_AUTH_TYPE); mAuthType.setOnPreferenceChangeListener(this); @@ -199,6 +209,11 @@ public class ApnEditor extends InstrumentedPreferenceActivity final String action = intent.getAction(); mSubId = intent.getIntExtra(ApnSettings.SUB_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID); + mDisableEditor = intent.getBooleanExtra("DISABLE_EDITOR", false); + if (mDisableEditor) { + getPreferenceScreen().setEnabled(false); + Log.d(TAG, "ApnEditor form is disabled."); + } mFirstTime = icicle == null; @@ -206,7 +221,10 @@ public class ApnEditor extends InstrumentedPreferenceActivity mUri = intent.getData(); } else if (action.equals(Intent.ACTION_INSERT)) { if (mFirstTime || icicle.getInt(SAVED_POS) == 0) { - mUri = getContentResolver().insert(intent.getData(), new ContentValues()); + ContentValues values = new ContentValues(); + values.put(Telephony.Carriers.PROTOCOL, PROTOCOL_IPV4V6); + values.put(Telephony.Carriers.ROAMING_PROTOCOL, PROTOCOL_IPV4V6); + mUri = getContentResolver().insert(intent.getData(), values); } else { mUri = ContentUris.withAppendedId(Telephony.Carriers.CONTENT_URI, icicle.getInt(SAVED_POS)); @@ -263,6 +281,7 @@ public class ApnEditor extends InstrumentedPreferenceActivity private void fillUi() { if (mFirstTime) { mFirstTime = false; + String numeric = mTelephonyManager.getIccOperatorNumericForData(mSubId); // Fill in all the values from the db in both text editor and summary mName.setText(mCursor.getString(NAME_INDEX)); mApn.setText(mCursor.getString(APN_INDEX)); @@ -278,7 +297,6 @@ public class ApnEditor extends InstrumentedPreferenceActivity mMnc.setText(mCursor.getString(MNC_INDEX)); mApnType.setText(mCursor.getString(TYPE_INDEX)); if (mNewApn) { - String numeric = mTelephonyManager.getSimOperator(mSubId); // MCC is first 3 chars and then in 2 - 3 chars of MNC if (numeric != null && numeric.length() > 4) { // Country code @@ -291,6 +309,7 @@ public class ApnEditor extends InstrumentedPreferenceActivity mCurMnc = mnc; mCurMcc = mcc; } + mApnType.setText(checkNull(getString(R.string.config_default_new_apn_type))); } int authVal = mCursor.getInt(AUTH_TYPE_INDEX); if (authVal != -1) { @@ -334,6 +353,16 @@ public class ApnEditor extends InstrumentedPreferenceActivity mMvnoType.setValue(mMvnoTypeStr); mMvnoMatchData.setText(mMvnoMatchDataStr); } + + String pppNumber = mCursor.getString(PPP_NUMBER_INDEX); + mPppNumber.setText(pppNumber); + if (pppNumber == null) { + if (!mNewApn) { + getPreferenceScreen().removePreference(mPppNumber); + } else if (getResources().getBoolean(R.bool.config_ppp_enabled)) { + getPreferenceScreen().removePreference(mPppNumber); + } + } } mName.setSummary(checkNull(mName.getText())); @@ -350,6 +379,13 @@ public class ApnEditor extends InstrumentedPreferenceActivity mMnc.setSummary(checkNull(mMnc.getText())); mApnType.setSummary(checkNull(mApnType.getText())); + String pppNumber = mPppNumber.getText(); + if (pppNumber != null) { + // Remove this preference if PPP number is not present + // in the APN settings + mPppNumber.setSummary(checkNull(pppNumber)); + } + String authVal = mAuthType.getValue(); if (authVal != null) { int authValIndex = Integer.parseInt(authVal); @@ -453,7 +489,7 @@ public class ApnEditor extends InstrumentedPreferenceActivity if (values[mvnoIndex].equals("SPN")) { mMvnoMatchData.setText(mTelephonyManager.getSimOperatorName()); } else if (values[mvnoIndex].equals("IMSI")) { - String numeric = mTelephonyManager.getSimOperator(mSubId); + String numeric = mTelephonyManager.getIccOperatorNumericForData(mSubId); mMvnoMatchData.setText(numeric + "x"); } else if (values[mvnoIndex].equals("GID")) { mMvnoMatchData.setText(mTelephonyManager.getGroupIdLevel1()); @@ -516,6 +552,10 @@ public class ApnEditor extends InstrumentedPreferenceActivity @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); + if (mDisableEditor) { + Log.d(TAG, "Form is disabled. Do not create the options menu."); + return true; + } // If it's a new APN, then cancel will delete the new entry in onPause if (!mNewApn) { menu.add(0, MENU_DELETE, 0, R.string.menu_delete) @@ -577,6 +617,13 @@ public class ApnEditor extends InstrumentedPreferenceActivity * @return true if the data was saved */ private boolean validateAndSave(boolean force) { + + // If the form is not editable, do nothing and return. + if (mDisableEditor){ + Log.d(TAG, "Form is disabled. Nothing to save."); + return true; + } + String name = checkNotSet(mName.getText()); String apn = checkNotSet(mApn.getText()); String mcc = checkNotSet(mMcc.getText()); @@ -630,6 +677,11 @@ public class ApnEditor extends InstrumentedPreferenceActivity values.put(Telephony.Carriers.NUMERIC, mcc + mnc); + String pppNumber = mPppNumber.getText(); + if (pppNumber != null) { + values.put(getResources().getString(R.string.ppp_number), pppNumber); + } + if (mCurMnc != null && mCurMcc != null) { if (mCurMnc.equals(mnc) && mCurMcc.equals(mcc)) { values.put(Telephony.Carriers.CURRENT, 1); @@ -665,11 +717,81 @@ public class ApnEditor extends InstrumentedPreferenceActivity values.put(Telephony.Carriers.MVNO_MATCH_DATA, checkNotSet(mMvnoMatchData.getText())); values.put(Telephony.Carriers.CARRIER_ENABLED, mCarrierEnabled.isChecked() ? 1 : 0); + + if (isDuplicate(values)) { + showDialog(DUPLICATE_DIALOG_ID); + return false; + } + getContentResolver().update(mUri, values, null, null); return true; } + private boolean isDuplicate(ContentValues row) { + if (!getResources().getBoolean(R.bool.config_enable_duplicate_apn_checking)) { + return false; + } + + final Set<String> keys = row.keySet(); + + StringBuilder queryBuilder = new StringBuilder(); + List<String> selectionArgsList = new ArrayList<>(); + + final Iterator<String> iterator = keys.iterator(); + while (iterator.hasNext()) { + final String key = iterator.next(); + + if (!keyForDuplicateCheck(key) || row.getAsString(key).isEmpty()) { + // Skip keys which don't interest us for the duplicate query. + // Or if the user hasn't yet filled a field in (empty value), skip it. + continue; + } + + queryBuilder.append(key); + queryBuilder.append("=?"); + queryBuilder.append(" AND "); + + selectionArgsList.add(row.getAsString(key)); + } + // remove extra AND at the end + queryBuilder.delete(queryBuilder.length() - " AND ".length(), queryBuilder.length()); + + String[] selectionArgs = new String[selectionArgsList.size()]; + selectionArgsList.toArray(selectionArgs); + + try (Cursor query = getContentResolver().query(Telephony.Carriers.CONTENT_URI, + sProjection, queryBuilder.toString(), selectionArgs, null)) { + return query.getCount() > (mNewApn ? 0 : 1); + } catch (Exception e) { + Log.e(TAG, "error querying for duplicates", e); + return false; + } + } + + /** + * Helper method to decide what columns should be considered valid when checking for + * potential duplicate APNs before allowing the user to add a new one. + * + * @param key the column of the row we want to check + * @return whether to include this key-value pair in the duplicate query + */ + private static boolean keyForDuplicateCheck(String key) { + switch (key) { + case Telephony.Carriers.APN: + case Telephony.Carriers.MMSPROXY: + case Telephony.Carriers.MMSPORT: + case Telephony.Carriers.MMSC: + case Telephony.Carriers.TYPE: + case Telephony.Carriers.MCC: + case Telephony.Carriers.MNC: + case Telephony.Carriers.NUMERIC: + return true; + default: + return false; + } + } + private String getErrorMsg() { String errorMsg = null; @@ -702,6 +824,12 @@ public class ApnEditor extends InstrumentedPreferenceActivity .setPositiveButton(android.R.string.ok, null) .setMessage(msg) .create(); + } else if (id == DUPLICATE_DIALOG_ID) { + return new AlertDialog.Builder(this) + .setTitle(R.string.duplicate_apn_error_title) + .setPositiveButton(android.R.string.ok, null) + .setMessage(getString(R.string.duplicate_apn_error_message)) + .create(); } return super.onCreateDialog(id); diff --git a/src/com/android/settings/ApnPreference.java b/src/com/android/settings/ApnPreference.java index 1e29d22..dc697ea 100644 --- a/src/com/android/settings/ApnPreference.java +++ b/src/com/android/settings/ApnPreference.java @@ -51,6 +51,7 @@ public class ApnPreference extends Preference implements private static CompoundButton mCurrentChecked = null; private boolean mProtectFromCheckedChange = false; private boolean mSelectable = true; + private boolean mApnReadOnly = false; @Override public View getView(View convertView, ViewGroup parent) { @@ -118,7 +119,9 @@ public class ApnPreference extends Preference implements if (context != null) { int pos = Integer.parseInt(getKey()); Uri url = ContentUris.withAppendedId(Telephony.Carriers.CONTENT_URI, pos); - context.startActivity(new Intent(Intent.ACTION_EDIT, url)); + Intent intent = new Intent(Intent.ACTION_EDIT, url); + intent.putExtra("DISABLE_EDITOR", mApnReadOnly); + context.startActivity(intent); } } } @@ -130,4 +133,8 @@ public class ApnPreference extends Preference implements public boolean getSelectable() { return mSelectable; } + + public void setApnReadOnly(boolean apnReadOnly) { + mApnReadOnly = apnReadOnly; + } } diff --git a/src/com/android/settings/ApnSettings.java b/src/com/android/settings/ApnSettings.java index fdc0914..b36ab1c 100644 --- a/src/com/android/settings/ApnSettings.java +++ b/src/com/android/settings/ApnSettings.java @@ -72,8 +72,12 @@ public class ApnSettings extends SettingsPreferenceFragment implements public static final String PREFERRED_APN_URI = "content://telephony/carriers/preferapn"; + public static final Uri PREFERRED_MSIM_APN_URI = + Uri.parse("content://telephony/carriers/preferapn/subIdImsi"); + public static final String APN_ID = "apn_id"; public static final String SUB_ID = "sub_id"; + public static final String EXTRA_IMSI = "imsi"; public static final String MVNO_TYPE = "mvno_type"; public static final String MVNO_MATCH_DATA = "mvno_match_data"; @@ -83,6 +87,7 @@ public class ApnSettings extends SettingsPreferenceFragment implements private static final int TYPES_INDEX = 3; private static final int MVNO_TYPE_INDEX = 4; private static final int MVNO_MATCH_DATA_INDEX = 5; + private static final int RO_INDEX = 6; private static final int MENU_NEW = Menu.FIRST; private static final int MENU_RESTORE = Menu.FIRST + 1; @@ -116,6 +121,8 @@ public class ApnSettings extends SettingsPreferenceFragment implements private boolean mHideImsApn; private boolean mAllowAddingApns; + private String mImsi; + private final BroadcastReceiver mMobileStateReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -156,6 +163,8 @@ public class ApnSettings extends SettingsPreferenceFragment implements final int subId = activity.getIntent().getIntExtra(SUB_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID); + mImsi = activity.getIntent().getStringExtra(EXTRA_IMSI); + mUm = (UserManager) getSystemService(Context.USER_SERVICE); mMobileStateFilter = new IntentFilter( @@ -233,9 +242,10 @@ public class ApnSettings extends SettingsPreferenceFragment implements } private void fillList() { + boolean isSelectedKeyMatch = false; final TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); final String mccmnc = mSubscriptionInfo == null ? "" - : tm.getSimOperator(mSubscriptionInfo.getSubscriptionId()); + : tm.getIccOperatorNumericForData(mSubscriptionInfo.getSubscriptionId()); Log.d(TAG, "mccmnc = " + mccmnc); StringBuilder where = new StringBuilder("numeric=\"" + mccmnc + "\" AND NOT (type='ia' AND (apn=\"\" OR apn IS NULL)) AND user_visible!=0"); @@ -245,7 +255,7 @@ public class ApnSettings extends SettingsPreferenceFragment implements } Cursor cursor = getContentResolver().query(Telephony.Carriers.CONTENT_URI, new String[] { - "_id", "name", "apn", "type", "mvno_type", "mvno_match_data"}, where.toString(), + "_id", "name", "apn", "type", "mvno_type", "mvno_match_data", "read_only"}, where.toString(), null, Telephony.Carriers.DEFAULT_SORT_ORDER); if (cursor != null) { @@ -271,9 +281,11 @@ public class ApnSettings extends SettingsPreferenceFragment implements String type = cursor.getString(TYPES_INDEX); String mvnoType = cursor.getString(MVNO_TYPE_INDEX); String mvnoMatchData = cursor.getString(MVNO_MATCH_DATA_INDEX); + boolean readOnly = (cursor.getInt(RO_INDEX) == 1); ApnPreference pref = new ApnPreference(getActivity()); + pref.setApnReadOnly(readOnly); pref.setKey(key); pref.setTitle(name); pref.setSummary(apn); @@ -285,6 +297,8 @@ public class ApnSettings extends SettingsPreferenceFragment implements if (selectable) { if ((mSelectedKey != null) && mSelectedKey.equals(key)) { pref.setChecked(); + isSelectedKeyMatch = true; + Log.d(TAG, "find select key = " + mSelectedKey); } addApnToList(pref, mnoApnList, mvnoApnList, r, mvnoType, mvnoMatchData); } else { @@ -304,6 +318,15 @@ public class ApnSettings extends SettingsPreferenceFragment implements for (Preference preference : mnoApnList) { apnList.addPreference(preference); } + + //if find no selectedKey, set the first one as selected key + if (!isSelectedKeyMatch && apnList.getPreferenceCount() > 0) { + ApnPreference pref = (ApnPreference) apnList.getPreference(0); + pref.setChecked(); + setSelectedApnKey(pref.getKey()); + Log.d(TAG, "set key to " +pref.getKey()); + } + for (Preference preference : mnoMmsApnList) { apnList.addPreference(preference); } @@ -331,7 +354,7 @@ public class ApnSettings extends SettingsPreferenceFragment implements if (mAllowAddingApns) { menu.add(0, MENU_NEW, 0, getResources().getString(R.string.menu_new)) - .setIcon(android.R.drawable.ic_menu_add) + .setIcon(R.drawable.ic_menu_add_white) .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); } menu.add(0, MENU_RESTORE, 0, @@ -393,19 +416,35 @@ public class ApnSettings extends SettingsPreferenceFragment implements ContentValues values = new ContentValues(); values.put(APN_ID, mSelectedKey); - resolver.update(PREFERAPN_URI, values, null, null); + if (TelephonyManager.getDefault().getPhoneCount() > 1 && mImsi != null) { + Uri qUri = Uri.withAppendedPath(PREFERRED_MSIM_APN_URI, + String.valueOf(mSubscriptionInfo.getSubscriptionId())); + qUri = Uri.withAppendedPath(qUri, mImsi); + resolver.update(qUri, values, null, null); + } else { + resolver.update(PREFERAPN_URI, values, null, null); + } } private String getSelectedApnKey() { String key = null; - Cursor cursor = getContentResolver().query(PREFERAPN_URI, new String[] {"_id"}, - null, null, Telephony.Carriers.DEFAULT_SORT_ORDER); - if (cursor.getCount() > 0) { - cursor.moveToFirst(); - key = cursor.getString(ID_INDEX); + Uri uri; + if (TelephonyManager.getDefault().getPhoneCount() > 1 && mImsi != null + && mSubscriptionInfo != null) { + uri = Uri.withAppendedPath(PREFERRED_MSIM_APN_URI, + String.valueOf(mSubscriptionInfo.getSubscriptionId())); + uri = Uri.withAppendedPath(uri, mImsi); + } else { + uri = PREFERAPN_URI; + } + try (Cursor cursor = getContentResolver().query(uri, new String[] {"_id"}, + null, null, Telephony.Carriers.DEFAULT_SORT_ORDER)) { + if (cursor != null && cursor.getCount() > 0) { + cursor.moveToFirst(); + key = cursor.getString(ID_INDEX); + } } - cursor.close(); return key; } diff --git a/src/com/android/settings/BandMode.java b/src/com/android/settings/BandMode.java index 81e8b49..e22fbaa 100644 --- a/src/com/android/settings/BandMode.java +++ b/src/com/android/settings/BandMode.java @@ -17,18 +17,14 @@ 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. - * + * This will query the device for all available band modes + * and display the options on the screen. If however it fails it will then + * display all the available band modes that are in the BAND_NAMES array. + * After the user selects a band, it will attempt to set the band mode + * regardless of the outcome. However if the bandmode will not work RIL.Java + * will catch it and throw a GENERIC_FAILURE or RADIO_NOT_AVAILABLE error */ public class BandMode extends Activity { private static final String LOG_TAG = "phone"; @@ -36,14 +32,31 @@ public class BandMode extends Activity { private static final int EVENT_BAND_SCAN_COMPLETED = 100; private static final int EVENT_BAND_SELECTION_DONE = 200; - +/* +* pulled from hardware/ril/include/telephony/ril.h and cleaned up a little +* there ought to be a better way to do this... +* make queryAvailableBandMode return something other than just an int array? +*/ private static final String[] BAND_NAMES = new String[] { "Automatic", - "EURO Band", - "USA Band", - "JAPAN Band", - "AUS Band", - "AUS2 Band" + "EURO Band (GSM-900/DCS-1800/WCDMA-IMT-2000)", + "USA Band (GSM-850/PCS-1900/WCDMA-850/WCDMA-PCS-1900)", + "JAPAN Band (WCDMA-800/WCDMA-IMT-2000)", + "AUS Band (GSM-900/DCS-1800/WCDMA-850/WCDMA-IMT-2000)", + "AUS2 Band (GSM-900/DCS-1800/WCDMA-850)", + "Cellular (800-MHz)", + "PCS (1900-MHz)", + "Band Class 3 (JTACS Band)", + "Band Class 4 (Korean PCS Band)", + "Band Class 5 (450-MHz Band)", + "Band Class 6 (2-GMHz IMT2000 Band)", + "Band Class 7 (Upper 700-MHz Band)", + "Band Class 8 (1800-MHz Band)", + "Band Class 9 (900-MHz Band)", + "Band Class 10 (Secondary 800-MHz Band)", + "Band Class 11 (400-MHz European PAMR Band)", + "Band Class 15 (AWS Band)", + "Band Class 16 (US 2.5-GHz Band)" }; private ListView mBandList; @@ -140,21 +153,21 @@ public class BandMode extends Activity { if (result.result != null) { int bands[] = (int[])result.result; - int size = bands[0]; - - if (size > 0) { - for (int i=1; i<size; i++) { - item = new BandListItem(bands[i]); - mBandListAdapter.add(item); - if (DBG) log("Add " + item.toString()); - } - addBandSuccess = true; + //Always show Band 0, ie Automatic + item = new BandListItem(0); + mBandListAdapter.add(item); + if (DBG) log("Add " + item.toString()); + for (int i=0; i<bands.length; i++) { + item = new BandListItem(bands[i]); + mBandListAdapter.add(item); + if (DBG) log("Add " + item.toString()); } + addBandSuccess = true; } if (addBandSuccess == false) { if (DBG) log("Error in query, add default list"); - for (int i=0; i<Phone.BM_BOUNDARY; i++) { + for (int i=0; i<BAND_NAMES.length; i++) { item = new BandListItem(i); mBandListAdapter.add(item); if (DBG) log("Add default " + item.toString()); diff --git a/src/com/android/settings/BugreportPreference.java b/src/com/android/settings/BugreportPreference.java index ba58ef4..ca4fa1d 100644 --- a/src/com/android/settings/BugreportPreference.java +++ b/src/com/android/settings/BugreportPreference.java @@ -16,11 +16,12 @@ package com.android.settings; +import android.app.ActivityManagerNative; import android.app.AlertDialog.Builder; import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; -import android.os.SystemProperties; +import android.os.RemoteException; import android.preference.DialogPreference; import android.util.AttributeSet; import android.view.View; @@ -55,7 +56,11 @@ public class BugreportPreference extends DialogPreference { @Override public void onClick(DialogInterface dialog, int which) { if (which == DialogInterface.BUTTON_POSITIVE) { - SystemProperties.set("ctl.start", "bugreport"); + try { + ActivityManagerNative.getDefault().requestBugReport(); + } catch (RemoteException e) { + // ignore + } } } } diff --git a/src/com/android/settings/ButtonSettings.java b/src/com/android/settings/ButtonSettings.java new file mode 100644 index 0000000..109d7dc --- /dev/null +++ b/src/com/android/settings/ButtonSettings.java @@ -0,0 +1,745 @@ +/* + * Copyright (C) 2013 The CyanogenMod project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings; + +import android.content.ComponentName; +import android.content.ContentResolver; +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.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.Handler; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.PreferenceCategory; +import android.preference.PreferenceManager; +import android.preference.PreferenceScreen; +import android.preference.SwitchPreference; +import android.provider.Settings; + +import android.util.Log; +import android.view.IWindowManager; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.WindowManagerGlobal; + +import com.android.settings.cyanogenmod.ButtonBacklightBrightness; +import com.android.settings.R; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settings.search.Indexable; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.Utils; + +import cyanogenmod.hardware.CMHardwareManager; +import cyanogenmod.providers.CMSettings; + +import org.cyanogenmod.internal.logging.CMMetricsLogger; +import org.cyanogenmod.internal.util.ScreenType; + +import java.util.List; + +import static android.provider.Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED; + +public class ButtonSettings extends SettingsPreferenceFragment implements + Preference.OnPreferenceChangeListener { + private static final String TAG = "SystemSettings"; + + private static final String KEY_BUTTON_BACKLIGHT = "button_backlight"; + private static final String KEY_HOME_LONG_PRESS = "hardware_keys_home_long_press"; + private static final String KEY_HOME_DOUBLE_TAP = "hardware_keys_home_double_tap"; + private static final String KEY_MENU_PRESS = "hardware_keys_menu_press"; + private static final String KEY_MENU_LONG_PRESS = "hardware_keys_menu_long_press"; + private static final String KEY_ASSIST_PRESS = "hardware_keys_assist_press"; + private static final String KEY_ASSIST_LONG_PRESS = "hardware_keys_assist_long_press"; + private static final String KEY_APP_SWITCH_PRESS = "hardware_keys_app_switch_press"; + private static final String KEY_APP_SWITCH_LONG_PRESS = "hardware_keys_app_switch_long_press"; + private static final String KEY_VOLUME_KEY_CURSOR_CONTROL = "volume_key_cursor_control"; + private static final String KEY_SWAP_VOLUME_BUTTONS = "swap_volume_buttons"; + private static final String DISABLE_NAV_KEYS = "disable_nav_keys"; + private static final String KEY_NAVIGATION_BAR_LEFT = "navigation_bar_left"; + private static final String KEY_NAVIGATION_RECENTS_LONG_PRESS = "navigation_recents_long_press"; + private static final String KEY_POWER_END_CALL = "power_end_call"; + private static final String KEY_HOME_ANSWER_CALL = "home_answer_call"; + private static final String KEY_VOLUME_MUSIC_CONTROLS = "volbtn_music_controls"; + private static final String KEY_VOLUME_CONTROL_RING_STREAM = "volume_keys_control_ring_stream"; + private static final String KEY_CAMERA_DOUBLE_TAP_POWER_GESTURE + = "camera_double_tap_power_gesture"; + + private static final String CATEGORY_POWER = "power_key"; + private static final String CATEGORY_HOME = "home_key"; + private static final String CATEGORY_BACK = "back_key"; + private static final String CATEGORY_MENU = "menu_key"; + private static final String CATEGORY_ASSIST = "assist_key"; + private static final String CATEGORY_APPSWITCH = "app_switch_key"; + private static final String CATEGORY_CAMERA = "camera_key"; + private static final String CATEGORY_VOLUME = "volume_keys"; + private static final String CATEGORY_BACKLIGHT = "key_backlight"; + private static final String CATEGORY_NAVBAR = "navigation_bar_category"; + + // Available custom actions to perform on a key press. + // Must match values for KEY_HOME_LONG_PRESS_ACTION in: + // frameworks/base/core/java/android/provider/Settings.java + private static final int ACTION_NOTHING = 0; + private static final int ACTION_MENU = 1; + private static final int ACTION_APP_SWITCH = 2; + private static final int ACTION_SEARCH = 3; + private static final int ACTION_VOICE_SEARCH = 4; + private static final int ACTION_IN_APP_SEARCH = 5; + private static final int ACTION_LAUNCH_CAMERA = 6; + private static final int ACTION_SLEEP = 7; + private static final int ACTION_LAST_APP = 8; + + // Masks for checking presence of hardware keys. + // Must match values in frameworks/base/core/res/res/values/config.xml + public static final int KEY_MASK_HOME = 0x01; + public static final int KEY_MASK_BACK = 0x02; + public static final int KEY_MASK_MENU = 0x04; + public static final int KEY_MASK_ASSIST = 0x08; + public static final int KEY_MASK_APP_SWITCH = 0x10; + public static final int KEY_MASK_CAMERA = 0x20; + public static final int KEY_MASK_VOLUME = 0x40; + + private ListPreference mHomeLongPressAction; + private ListPreference mHomeDoubleTapAction; + private ListPreference mMenuPressAction; + private ListPreference mMenuLongPressAction; + private ListPreference mAssistPressAction; + private ListPreference mAssistLongPressAction; + private ListPreference mAppSwitchPressAction; + private ListPreference mAppSwitchLongPressAction; + private SwitchPreference mCameraWakeScreen; + private SwitchPreference mCameraSleepOnRelease; + private SwitchPreference mCameraLaunch; + private ListPreference mVolumeKeyCursorControl; + private SwitchPreference mVolumeWakeScreen; + private SwitchPreference mVolumeMusicControls; + private SwitchPreference mSwapVolumeButtons; + private SwitchPreference mDisableNavigationKeys; + private SwitchPreference mNavigationBarLeftPref; + private ListPreference mNavigationRecentsLongPressAction; + private SwitchPreference mPowerEndCall; + private SwitchPreference mHomeAnswerCall; + private SwitchPreference mCameraDoubleTapPowerGesture; + + private PreferenceCategory mNavigationPreferencesCat; + + private Handler mHandler; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + addPreferencesFromResource(R.xml.button_settings); + + final Resources res = getResources(); + final ContentResolver resolver = getActivity().getContentResolver(); + final PreferenceScreen prefScreen = getPreferenceScreen(); + + final int deviceKeys = getResources().getInteger( + com.android.internal.R.integer.config_deviceHardwareKeys); + final int deviceWakeKeys = getResources().getInteger( + com.android.internal.R.integer.config_deviceHardwareWakeKeys); + + final boolean hasPowerKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_POWER); + final boolean hasHomeKey = (deviceKeys & KEY_MASK_HOME) != 0; + final boolean hasBackKey = (deviceKeys & KEY_MASK_BACK) != 0; + final boolean hasMenuKey = (deviceKeys & KEY_MASK_MENU) != 0; + final boolean hasAssistKey = (deviceKeys & KEY_MASK_ASSIST) != 0; + final boolean hasAppSwitchKey = (deviceKeys & KEY_MASK_APP_SWITCH) != 0; + final boolean hasCameraKey = (deviceKeys & KEY_MASK_CAMERA) != 0; + final boolean hasVolumeKeys = (deviceKeys & KEY_MASK_VOLUME) != 0; + + final boolean showHomeWake = (deviceWakeKeys & KEY_MASK_HOME) != 0; + final boolean showBackWake = (deviceWakeKeys & KEY_MASK_BACK) != 0; + final boolean showMenuWake = (deviceWakeKeys & KEY_MASK_MENU) != 0; + final boolean showAssistWake = (deviceWakeKeys & KEY_MASK_ASSIST) != 0; + final boolean showAppSwitchWake = (deviceWakeKeys & KEY_MASK_APP_SWITCH) != 0; + final boolean showCameraWake = (deviceWakeKeys & KEY_MASK_CAMERA) != 0; + final boolean showVolumeWake = (deviceWakeKeys & KEY_MASK_VOLUME) != 0; + + boolean hasAnyBindableKey = false; + final PreferenceCategory powerCategory = + (PreferenceCategory) prefScreen.findPreference(CATEGORY_POWER); + final PreferenceCategory homeCategory = + (PreferenceCategory) prefScreen.findPreference(CATEGORY_HOME); + final PreferenceCategory backCategory = + (PreferenceCategory) prefScreen.findPreference(CATEGORY_BACK); + final PreferenceCategory menuCategory = + (PreferenceCategory) prefScreen.findPreference(CATEGORY_MENU); + final PreferenceCategory assistCategory = + (PreferenceCategory) prefScreen.findPreference(CATEGORY_ASSIST); + final PreferenceCategory appSwitchCategory = + (PreferenceCategory) prefScreen.findPreference(CATEGORY_APPSWITCH); + final PreferenceCategory volumeCategory = + (PreferenceCategory) prefScreen.findPreference(CATEGORY_VOLUME); + final PreferenceCategory cameraCategory = + (PreferenceCategory) prefScreen.findPreference(CATEGORY_CAMERA); + + // Power button ends calls. + mPowerEndCall = (SwitchPreference) findPreference(KEY_POWER_END_CALL); + + // Double press power to launch camera. + mCameraDoubleTapPowerGesture + = (SwitchPreference) findPreference(KEY_CAMERA_DOUBLE_TAP_POWER_GESTURE); + + // Home button answers calls. + mHomeAnswerCall = (SwitchPreference) findPreference(KEY_HOME_ANSWER_CALL); + + mHandler = new Handler(); + + // Force Navigation bar related options + mDisableNavigationKeys = (SwitchPreference) findPreference(DISABLE_NAV_KEYS); + + mNavigationPreferencesCat = (PreferenceCategory) findPreference(CATEGORY_NAVBAR); + + // Navigation bar left + mNavigationBarLeftPref = (SwitchPreference) findPreference(KEY_NAVIGATION_BAR_LEFT); + + // Navigation bar recents long press activity needs custom setup + mNavigationRecentsLongPressAction = + initRecentsLongPressAction(KEY_NAVIGATION_RECENTS_LONG_PRESS); + + final CMHardwareManager hardware = CMHardwareManager.getInstance(getActivity()); + + // Only visible on devices that does not have a navigation bar already, + // and don't even try unless the existing keys can be disabled + boolean needsNavigationBar = false; + if (hardware.isSupported(CMHardwareManager.FEATURE_KEY_DISABLE)) { + try { + IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); + needsNavigationBar = wm.needsNavigationBar(); + } catch (RemoteException e) { + } + + if (needsNavigationBar) { + prefScreen.removePreference(mDisableNavigationKeys); + } else { + // Remove keys that can be provided by the navbar + updateDisableNavkeysOption(); + mNavigationPreferencesCat.setEnabled(mDisableNavigationKeys.isChecked()); + updateDisableNavkeysCategories(mDisableNavigationKeys.isChecked()); + } + } else { + prefScreen.removePreference(mDisableNavigationKeys); + } + + if (hasPowerKey) { + if (!Utils.isVoiceCapable(getActivity())) { + powerCategory.removePreference(mPowerEndCall); + mPowerEndCall = null; + } + if (mCameraDoubleTapPowerGesture != null && + isCameraDoubleTapPowerGestureAvailable(getResources())) { + // Update double tap power to launch camera if available. + mCameraDoubleTapPowerGesture.setOnPreferenceChangeListener(this); + int cameraDoubleTapPowerDisabled = Settings.Secure.getInt( + getContentResolver(), CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, 0); + mCameraDoubleTapPowerGesture.setChecked(cameraDoubleTapPowerDisabled == 0); + } else { + powerCategory.removePreference(mCameraDoubleTapPowerGesture); + mCameraDoubleTapPowerGesture = null; + } + } else { + prefScreen.removePreference(powerCategory); + } + + if (hasHomeKey) { + if (!showHomeWake) { + homeCategory.removePreference(findPreference(CMSettings.System.HOME_WAKE_SCREEN)); + } + + if (!Utils.isVoiceCapable(getActivity())) { + homeCategory.removePreference(mHomeAnswerCall); + mHomeAnswerCall = null; + } + + int defaultLongPressAction = res.getInteger( + com.android.internal.R.integer.config_longPressOnHomeBehavior); + if (defaultLongPressAction < ACTION_NOTHING || + defaultLongPressAction > ACTION_LAST_APP) { + defaultLongPressAction = ACTION_NOTHING; + } + + int defaultDoubleTapAction = res.getInteger( + com.android.internal.R.integer.config_doubleTapOnHomeBehavior); + if (defaultDoubleTapAction < ACTION_NOTHING || + defaultDoubleTapAction > ACTION_LAST_APP) { + defaultDoubleTapAction = ACTION_NOTHING; + } + + int longPressAction = CMSettings.System.getInt(resolver, + CMSettings.System.KEY_HOME_LONG_PRESS_ACTION, + defaultLongPressAction); + mHomeLongPressAction = initActionList(KEY_HOME_LONG_PRESS, longPressAction); + + int doubleTapAction = CMSettings.System.getInt(resolver, + CMSettings.System.KEY_HOME_DOUBLE_TAP_ACTION, + defaultDoubleTapAction); + mHomeDoubleTapAction = initActionList(KEY_HOME_DOUBLE_TAP, doubleTapAction); + + hasAnyBindableKey = true; + } else { + prefScreen.removePreference(homeCategory); + } + + if (hasBackKey) { + if (!showBackWake) { + backCategory.removePreference(findPreference(CMSettings.System.BACK_WAKE_SCREEN)); + prefScreen.removePreference(backCategory); + } + } else { + prefScreen.removePreference(backCategory); + } + + if (hasMenuKey) { + if (!showMenuWake) { + menuCategory.removePreference(findPreference(CMSettings.System.MENU_WAKE_SCREEN)); + } + + int pressAction = CMSettings.System.getInt(resolver, + CMSettings.System.KEY_MENU_ACTION, ACTION_MENU); + mMenuPressAction = initActionList(KEY_MENU_PRESS, pressAction); + + int longPressAction = CMSettings.System.getInt(resolver, + CMSettings.System.KEY_MENU_LONG_PRESS_ACTION, + hasAssistKey ? ACTION_NOTHING : ACTION_SEARCH); + mMenuLongPressAction = initActionList(KEY_MENU_LONG_PRESS, longPressAction); + + hasAnyBindableKey = true; + } else { + prefScreen.removePreference(menuCategory); + } + + if (hasAssistKey) { + if (!showAssistWake) { + assistCategory.removePreference(findPreference(CMSettings.System.ASSIST_WAKE_SCREEN)); + } + + int pressAction = CMSettings.System.getInt(resolver, + CMSettings.System.KEY_ASSIST_ACTION, ACTION_SEARCH); + mAssistPressAction = initActionList(KEY_ASSIST_PRESS, pressAction); + + int longPressAction = CMSettings.System.getInt(resolver, + CMSettings.System.KEY_ASSIST_LONG_PRESS_ACTION, ACTION_VOICE_SEARCH); + mAssistLongPressAction = initActionList(KEY_ASSIST_LONG_PRESS, longPressAction); + + hasAnyBindableKey = true; + } else { + prefScreen.removePreference(assistCategory); + } + + if (hasAppSwitchKey) { + if (!showAppSwitchWake) { + appSwitchCategory.removePreference(findPreference( + CMSettings.System.APP_SWITCH_WAKE_SCREEN)); + } + + int pressAction = CMSettings.System.getInt(resolver, + CMSettings.System.KEY_APP_SWITCH_ACTION, ACTION_APP_SWITCH); + mAppSwitchPressAction = initActionList(KEY_APP_SWITCH_PRESS, pressAction); + + int longPressAction = CMSettings.System.getInt(resolver, + CMSettings.System.KEY_APP_SWITCH_LONG_PRESS_ACTION, ACTION_NOTHING); + mAppSwitchLongPressAction = initActionList(KEY_APP_SWITCH_LONG_PRESS, longPressAction); + + hasAnyBindableKey = true; + } else { + prefScreen.removePreference(appSwitchCategory); + } + + if (hasCameraKey) { + mCameraWakeScreen = (SwitchPreference) findPreference(CMSettings.System.CAMERA_WAKE_SCREEN); + mCameraSleepOnRelease = + (SwitchPreference) findPreference(CMSettings.System.CAMERA_SLEEP_ON_RELEASE); + mCameraLaunch = (SwitchPreference) findPreference(CMSettings.System.CAMERA_LAUNCH); + + if (!showCameraWake) { + prefScreen.removePreference(mCameraWakeScreen); + } + // Only show 'Camera sleep on release' if the device has a focus key + if (res.getBoolean(com.android.internal.R.bool.config_singleStageCameraKey)) { + prefScreen.removePreference(mCameraSleepOnRelease); + } + } else { + prefScreen.removePreference(cameraCategory); + } + + if (Utils.hasVolumeRocker(getActivity())) { + if (!showVolumeWake) { + volumeCategory.removePreference(findPreference(CMSettings.System.VOLUME_WAKE_SCREEN)); + } + + int cursorControlAction = Settings.System.getInt(resolver, + Settings.System.VOLUME_KEY_CURSOR_CONTROL, 0); + mVolumeKeyCursorControl = initActionList(KEY_VOLUME_KEY_CURSOR_CONTROL, + cursorControlAction); + + int swapVolumeKeys = CMSettings.System.getInt(getContentResolver(), + CMSettings.System.SWAP_VOLUME_KEYS_ON_ROTATION, 0); + mSwapVolumeButtons = (SwitchPreference) + prefScreen.findPreference(KEY_SWAP_VOLUME_BUTTONS); + if (mSwapVolumeButtons != null) { + mSwapVolumeButtons.setChecked(swapVolumeKeys > 0); + } + } else { + prefScreen.removePreference(volumeCategory); + } + + try { + // Only show the navigation bar category on devices that have a navigation bar + // unless we are forcing it via development settings + boolean forceNavbar = CMSettings.Global.getInt(getContentResolver(), + CMSettings.Global.DEV_FORCE_SHOW_NAVBAR, 0) == 1; + boolean hasNavBar = WindowManagerGlobal.getWindowManagerService().hasNavigationBar() + || forceNavbar; + + if (!ScreenType.isPhone(getActivity())) { + mNavigationPreferencesCat.removePreference(mNavigationBarLeftPref); + } + + if (!hasNavBar && (needsNavigationBar || + !hardware.isSupported(CMHardwareManager.FEATURE_KEY_DISABLE))) { + // Hide navigation bar category + prefScreen.removePreference(mNavigationPreferencesCat); + } + } catch (RemoteException e) { + Log.e(TAG, "Error getting navigation bar status"); + } + + final ButtonBacklightBrightness backlight = + (ButtonBacklightBrightness) findPreference(KEY_BUTTON_BACKLIGHT); + if (!backlight.isButtonSupported() && !backlight.isKeyboardSupported()) { + prefScreen.removePreference(backlight); + } + + if (mCameraWakeScreen != null) { + if (mCameraSleepOnRelease != null && !getResources().getBoolean( + com.android.internal.R.bool.config_singleStageCameraKey)) { + mCameraSleepOnRelease.setDependency(CMSettings.System.CAMERA_WAKE_SCREEN); + } + } + mVolumeWakeScreen = (SwitchPreference) findPreference(CMSettings.System.VOLUME_WAKE_SCREEN); + mVolumeMusicControls = (SwitchPreference) findPreference(KEY_VOLUME_MUSIC_CONTROLS); + + if (mVolumeWakeScreen != null) { + if (mVolumeMusicControls != null) { + mVolumeMusicControls.setDependency(CMSettings.System.VOLUME_WAKE_SCREEN); + mVolumeWakeScreen.setDisableDependentsState(true); + } + } + } + + @Override + public void onResume() { + super.onResume(); + + // Power button ends calls. + if (mPowerEndCall != null) { + final int incallPowerBehavior = Settings.Secure.getInt(getContentResolver(), + Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR, + Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_DEFAULT); + final boolean powerButtonEndsCall = + (incallPowerBehavior == Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP); + mPowerEndCall.setChecked(powerButtonEndsCall); + } + + // Home button answers calls. + if (mHomeAnswerCall != null) { + final int incallHomeBehavior = CMSettings.Secure.getInt(getContentResolver(), + CMSettings.Secure.RING_HOME_BUTTON_BEHAVIOR, + CMSettings.Secure.RING_HOME_BUTTON_BEHAVIOR_DEFAULT); + final boolean homeButtonAnswersCall = + (incallHomeBehavior == CMSettings.Secure.RING_HOME_BUTTON_BEHAVIOR_ANSWER); + mHomeAnswerCall.setChecked(homeButtonAnswersCall); + } + } + + private ListPreference initActionList(String key, int value) { + ListPreference list = (ListPreference) getPreferenceScreen().findPreference(key); + if (list == null) return null; + list.setValue(Integer.toString(value)); + list.setSummary(list.getEntry()); + list.setOnPreferenceChangeListener(this); + return list; + } + + private ListPreference initRecentsLongPressAction(String key) { + ListPreference list = (ListPreference) getPreferenceScreen().findPreference(key); + list.setOnPreferenceChangeListener(this); + + // Read the componentName from Settings.Secure, this is the user's prefered setting + String componentString = CMSettings.Secure.getString(getContentResolver(), + CMSettings.Secure.RECENTS_LONG_PRESS_ACTIVITY); + ComponentName targetComponent = null; + if (componentString == null) { + list.setSummary(getString(R.string.hardware_keys_action_last_app)); + } else { + targetComponent = ComponentName.unflattenFromString(componentString); + } + + // Dyanamically generate the list array, + // query PackageManager for all Activites that are registered for ACTION_RECENTS_LONG_PRESS + PackageManager pm = getPackageManager(); + Intent intent = new Intent(cyanogenmod.content.Intent.ACTION_RECENTS_LONG_PRESS); + List<ResolveInfo> recentsActivities = pm.queryIntentActivities(intent, + PackageManager.MATCH_DEFAULT_ONLY); + if (recentsActivities.size() == 0) { + // No entries available, disable + list.setSummary(getString(R.string.hardware_keys_action_last_app)); + CMSettings.Secure.putString(getContentResolver(), + CMSettings.Secure.RECENTS_LONG_PRESS_ACTIVITY, null); + list.setEnabled(false); + return list; + } + + CharSequence[] entries = new CharSequence[recentsActivities.size() + 1]; + CharSequence[] values = new CharSequence[recentsActivities.size() + 1]; + // First entry is always default last app + entries[0] = getString(R.string.hardware_keys_action_last_app); + values[0] = ""; + list.setValue(values[0].toString()); + int i = 1; + for (ResolveInfo info : recentsActivities) { + try { + // Use pm.getApplicationInfo for the label, + // we cannot rely on ResolveInfo that comes back from queryIntentActivities. + entries[i] = pm.getApplicationInfo(info.activityInfo.packageName, 0).loadLabel(pm); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Error package not found: " + info.activityInfo.packageName, e); + // Fallback to package name + entries[i] = info.activityInfo.packageName; + } + + // Set the value to the ComponentName that will handle this intent + ComponentName entryComponent = new ComponentName(info.activityInfo.packageName, + info.activityInfo.name); + values[i] = entryComponent.flattenToString(); + if (targetComponent != null) { + if (entryComponent.equals(targetComponent)) { + // Update the selected value and the preference summary + list.setSummary(entries[i]); + list.setValue(values[i].toString()); + } + } + i++; + } + list.setEntries(entries); + list.setEntryValues(values); + return list; + } + + private void handleActionListChange(ListPreference pref, Object newValue, String setting) { + String value = (String) newValue; + int index = pref.findIndexOfValue(value); + pref.setSummary(pref.getEntries()[index]); + CMSettings.System.putInt(getContentResolver(), setting, Integer.valueOf(value)); + } + + private void handleSystemActionListChange(ListPreference pref, Object newValue, String setting) { + String value = (String) newValue; + int index = pref.findIndexOfValue(value); + pref.setSummary(pref.getEntries()[index]); + Settings.System.putInt(getContentResolver(), setting, Integer.valueOf(value)); + } + + @Override + protected int getMetricsCategory() { + return CMMetricsLogger.BUTTON_SETTINGS; + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (preference == mHomeLongPressAction) { + handleActionListChange(mHomeLongPressAction, newValue, + CMSettings.System.KEY_HOME_LONG_PRESS_ACTION); + return true; + } else if (preference == mHomeDoubleTapAction) { + handleActionListChange(mHomeDoubleTapAction, newValue, + CMSettings.System.KEY_HOME_DOUBLE_TAP_ACTION); + return true; + } else if (preference == mMenuPressAction) { + handleActionListChange(mMenuPressAction, newValue, + CMSettings.System.KEY_MENU_ACTION); + return true; + } else if (preference == mMenuLongPressAction) { + handleActionListChange(mMenuLongPressAction, newValue, + CMSettings.System.KEY_MENU_LONG_PRESS_ACTION); + return true; + } else if (preference == mAssistPressAction) { + handleActionListChange(mAssistPressAction, newValue, + CMSettings.System.KEY_ASSIST_ACTION); + return true; + } else if (preference == mAssistLongPressAction) { + handleActionListChange(mAssistLongPressAction, newValue, + CMSettings.System.KEY_ASSIST_LONG_PRESS_ACTION); + return true; + } else if (preference == mAppSwitchPressAction) { + handleActionListChange(mAppSwitchPressAction, newValue, + CMSettings.System.KEY_APP_SWITCH_ACTION); + return true; + } else if (preference == mAppSwitchLongPressAction) { + handleActionListChange(mAppSwitchLongPressAction, newValue, + CMSettings.System.KEY_APP_SWITCH_LONG_PRESS_ACTION); + return true; + } else if (preference == mVolumeKeyCursorControl) { + handleSystemActionListChange(mVolumeKeyCursorControl, newValue, + Settings.System.VOLUME_KEY_CURSOR_CONTROL); + return true; + } else if (preference == mNavigationRecentsLongPressAction) { + // RecentsLongPressAction is handled differently because it intentionally uses + // Settings.System + String putString = (String) newValue; + int index = mNavigationRecentsLongPressAction.findIndexOfValue(putString); + CharSequence summary = mNavigationRecentsLongPressAction.getEntries()[index]; + // Update the summary + mNavigationRecentsLongPressAction.setSummary(summary); + if (putString.length() == 0) { + putString = null; + } + CMSettings.Secure.putString(getContentResolver(), + CMSettings.Secure.RECENTS_LONG_PRESS_ACTIVITY, putString); + return true; + } else if (preference == mCameraDoubleTapPowerGesture) { + boolean value = (Boolean) newValue; + Settings.Secure.putInt(getContentResolver(), CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, + value ? 0 : 1 /* Backwards because setting is for disabling */); + return true; + } + return false; + } + + private static void writeDisableNavkeysOption(Context context, boolean enabled) { + CMSettings.Global.putInt(context.getContentResolver(), + CMSettings.Global.DEV_FORCE_SHOW_NAVBAR, enabled ? 1 : 0); + } + + private void updateDisableNavkeysOption() { + boolean enabled = CMSettings.Global.getInt(getActivity().getContentResolver(), + CMSettings.Global.DEV_FORCE_SHOW_NAVBAR, 0) != 0; + + mDisableNavigationKeys.setChecked(enabled); + } + + private void updateDisableNavkeysCategories(boolean navbarEnabled) { + final PreferenceScreen prefScreen = getPreferenceScreen(); + + /* Disable hw-key options if they're disabled */ + final PreferenceCategory homeCategory = + (PreferenceCategory) prefScreen.findPreference(CATEGORY_HOME); + final PreferenceCategory backCategory = + (PreferenceCategory) prefScreen.findPreference(CATEGORY_BACK); + final PreferenceCategory menuCategory = + (PreferenceCategory) prefScreen.findPreference(CATEGORY_MENU); + final PreferenceCategory assistCategory = + (PreferenceCategory) prefScreen.findPreference(CATEGORY_ASSIST); + final PreferenceCategory appSwitchCategory = + (PreferenceCategory) prefScreen.findPreference(CATEGORY_APPSWITCH); + final ButtonBacklightBrightness backlight = + (ButtonBacklightBrightness) prefScreen.findPreference(KEY_BUTTON_BACKLIGHT); + + /* Toggle backlight control depending on navbar state, force it to + off if enabling */ + if (backlight != null) { + backlight.setEnabled(!navbarEnabled); + backlight.updateSummary(); + } + + /* Toggle hardkey control availability depending on navbar state */ + if (homeCategory != null) { + homeCategory.setEnabled(!navbarEnabled); + } + if (backCategory != null) { + backCategory.setEnabled(!navbarEnabled); + } + if (menuCategory != null) { + menuCategory.setEnabled(!navbarEnabled); + } + if (assistCategory != null) { + assistCategory.setEnabled(!navbarEnabled); + } + if (appSwitchCategory != null) { + appSwitchCategory.setEnabled(!navbarEnabled); + } + } + + public static void restoreKeyDisabler(Context context) { + CMHardwareManager hardware = CMHardwareManager.getInstance(context); + if (!hardware.isSupported(CMHardwareManager.FEATURE_KEY_DISABLE)) { + return; + } + + writeDisableNavkeysOption(context, CMSettings.Global.getInt(context.getContentResolver(), + CMSettings.Global.DEV_FORCE_SHOW_NAVBAR, 0) != 0); + } + + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { + if (preference == mSwapVolumeButtons) { + int value = mSwapVolumeButtons.isChecked() + ? (ScreenType.isTablet(getActivity()) ? 2 : 1) : 0; + CMSettings.System.putInt(getActivity().getContentResolver(), + CMSettings.System.SWAP_VOLUME_KEYS_ON_ROTATION, value); + } else if (preference == mDisableNavigationKeys) { + mDisableNavigationKeys.setEnabled(false); + mNavigationPreferencesCat.setEnabled(false); + writeDisableNavkeysOption(getActivity(), mDisableNavigationKeys.isChecked()); + updateDisableNavkeysOption(); + updateDisableNavkeysCategories(true); + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + mDisableNavigationKeys.setEnabled(true); + mNavigationPreferencesCat.setEnabled(mDisableNavigationKeys.isChecked()); + updateDisableNavkeysCategories(mDisableNavigationKeys.isChecked()); + } + }, 1000); + } else if (preference == mPowerEndCall) { + handleTogglePowerButtonEndsCallPreferenceClick(); + return true; + } else if (preference == mHomeAnswerCall) { + handleToggleHomeButtonAnswersCallPreferenceClick(); + return true; + } + + return super.onPreferenceTreeClick(preferenceScreen, preference); + } + + private void handleTogglePowerButtonEndsCallPreferenceClick() { + Settings.Secure.putInt(getContentResolver(), + Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR, (mPowerEndCall.isChecked() + ? Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP + : Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF)); + } + + private void handleToggleHomeButtonAnswersCallPreferenceClick() { + CMSettings.Secure.putInt(getContentResolver(), + CMSettings.Secure.RING_HOME_BUTTON_BEHAVIOR, (mHomeAnswerCall.isChecked() + ? CMSettings.Secure.RING_HOME_BUTTON_BEHAVIOR_ANSWER + : CMSettings.Secure.RING_HOME_BUTTON_BEHAVIOR_DO_NOTHING)); + } + + private static boolean isCameraDoubleTapPowerGestureAvailable(Resources res) { + return res.getBoolean( + com.android.internal.R.bool.config_cameraDoubleTapPowerGestureEnabled); + } +} diff --git a/src/com/android/settings/CarrierSelection.java b/src/com/android/settings/CarrierSelection.java new file mode 100644 index 0000000..1044380 --- /dev/null +++ b/src/com/android/settings/CarrierSelection.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2014-2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Intent; +import android.os.Bundle; + +public class CarrierSelection extends Activity { + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.setComponent(new ComponentName("com.android.phone", + "com.android.phone.NetworkSetting")); + startActivity(intent); + finish(); + } +} diff --git a/src/com/android/settings/ChooseLockGeneric.java b/src/com/android/settings/ChooseLockGeneric.java index 8c0b0b6..502b9fd 100644 --- a/src/com/android/settings/ChooseLockGeneric.java +++ b/src/com/android/settings/ChooseLockGeneric.java @@ -32,6 +32,7 @@ import android.os.Process; import android.os.UserHandle; import android.preference.Preference; import android.preference.PreferenceScreen; +import android.provider.Settings; import android.security.KeyStore; import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.FingerprintManager; @@ -46,6 +47,7 @@ import android.widget.Toast; import com.android.internal.logging.MetricsLogger; import com.android.internal.widget.LockPatternUtils; +import com.android.settings.notification.RedactionInterstitial; public class ChooseLockGeneric extends SettingsActivity { public static final String CONFIRM_CREDENTIALS = "confirm_credentials"; @@ -139,6 +141,7 @@ public class ChooseLockGeneric extends SettingsActivity { mKeyStore = KeyStore.getInstance(); mChooseLockSettingsHelper = new ChooseLockSettingsHelper(this.getActivity()); mLockPatternUtils = new LockPatternUtils(getActivity()); + mLockPatternUtils.sanitizePassword(); // Defaults to needing to confirm credentials final boolean confirmCredentials = getActivity().getIntent() @@ -241,7 +244,8 @@ public class ChooseLockGeneric extends SettingsActivity { mWaitingForConfirmation = false; if (requestCode == CONFIRM_EXISTING_REQUEST && resultCode == Activity.RESULT_OK) { mPasswordConfirmed = true; - mUserPassword = data.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD); + mUserPassword = data == null ? null : + data.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD); updatePreferencesOrFinish(); } else if (requestCode == ENABLE_ENCRYPTION_REQUEST && resultCode == Activity.RESULT_OK) { @@ -468,7 +472,6 @@ public class ChooseLockGeneric extends SettingsActivity { if (!mPasswordConfirmed) { throw new IllegalStateException("Tried to update password without confirming it"); } - quality = upgradeQuality(quality); final Context context = getActivity(); @@ -501,6 +504,7 @@ public class ChooseLockGeneric extends SettingsActivity { mChooseLockSettingsHelper.utils().clearLock(UserHandle.myUserId()); mChooseLockSettingsHelper.utils().setLockScreenDisabled(disabled, UserHandle.myUserId()); + maybeShowRedactionInterstitial(); removeAllFingerprintTemplatesAndFinish(); getActivity().setResult(Activity.RESULT_OK); } else { @@ -517,9 +521,26 @@ public class ChooseLockGeneric extends SettingsActivity { } } + private void maybeShowRedactionInterstitial() { + // do nothing if lock screen disabled + if (mLockPatternUtils.isLockScreenDisabled(UserHandle.myUserId())) return; + + final boolean enabled = Settings.Secure.getInt(getContentResolver(), + Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0) != 0; + final boolean show = Settings.Secure.getInt(getContentResolver(), + Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1) != 0; + if (!(enabled && show)) { + Intent intent = RedactionInterstitial.createStartIntent(getContext()); + if (intent != null) { + startActivity(intent); + } + } + } + @Override public void onDestroy() { super.onDestroy(); + mLockPatternUtils.sanitizePassword(); } @Override diff --git a/src/com/android/settings/ChooseLockPattern.java b/src/com/android/settings/ChooseLockPattern.java index 1dd24f2..5dd6b9c 100644 --- a/src/com/android/settings/ChooseLockPattern.java +++ b/src/com/android/settings/ChooseLockPattern.java @@ -75,7 +75,7 @@ public class ChooseLockPattern extends SettingsActivity { public static Intent createIntent(Context context, boolean requirePassword, boolean confirmCredentials) { - Intent intent = new Intent(context, ChooseLockPattern.class); + Intent intent = new Intent(context, ChooseLockPatternSize.class); intent.putExtra("key_lock_method", "pattern"); intent.putExtra(ChooseLockGeneric.CONFIRM_CREDENTIALS, confirmCredentials); intent.putExtra(EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, requirePassword); @@ -148,16 +148,12 @@ public class ChooseLockPattern extends SettingsActivity { private TextView mFooterRightButton; protected List<LockPatternView.Cell> mChosenPattern = null; + private byte mPatternSize = LockPatternUtils.PATTERN_SIZE_DEFAULT; + /** * The patten used during the help screen to show how to draw a pattern. */ - private final List<LockPatternView.Cell> 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) - )); + private List<LockPatternView.Cell> mAnimatePattern; @Override public void onActivityResult(int requestCode, int resultCode, @@ -206,7 +202,13 @@ public class ChooseLockPattern extends SettingsActivity { 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)) { + + final String chosenPatternStr = LockPatternUtils.patternToString( + mChosenPattern, mPatternSize); + final String potentialPatternStr = LockPatternUtils.patternToString( + pattern, mPatternSize); + + if (chosenPatternStr.equals(potentialPatternStr)) { updateStage(Stage.ChoiceConfirmed); } else { updateStage(Stage.ConfirmWrong); @@ -373,6 +375,16 @@ public class ChooseLockPattern extends SettingsActivity { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + mPatternSize = getActivity().getIntent().getByteExtra("pattern_size", + LockPatternUtils.PATTERN_SIZE_DEFAULT); + LockPatternView.Cell.updateSize(mPatternSize); + mAnimatePattern = Collections.unmodifiableList(Lists.newArrayList( + LockPatternView.Cell.of(0, 0, mPatternSize), + LockPatternView.Cell.of(0, 1, mPatternSize), + LockPatternView.Cell.of(1, 1, mPatternSize), + LockPatternView.Cell.of(2, 1, mPatternSize) + )); + return inflater.inflate(R.layout.choose_lock_pattern, container, false); } @@ -384,6 +396,8 @@ public class ChooseLockPattern extends SettingsActivity { mLockPatternView.setOnPatternListener(mChooseNewLockPatternListener); mLockPatternView.setTactileFeedbackEnabled( mChooseLockSettingsHelper.utils().isTactileFeedbackEnabled()); + mLockPatternView.setLockPatternUtils(mChooseLockSettingsHelper.utils()); + mLockPatternView.setLockPatternSize(mPatternSize); mFooterText = (TextView) view.findViewById(R.id.footerText); @@ -427,7 +441,9 @@ public class ChooseLockPattern extends SettingsActivity { // restore from previous state final String patternString = savedInstanceState.getString(KEY_PATTERN_CHOICE); if (patternString != null) { - mChosenPattern = LockPatternUtils.stringToPattern(patternString); + mChosenPattern = LockPatternUtils.stringToPattern(patternString, + mPatternSize); + mLockPatternView.setPattern(DisplayMode.Correct, mChosenPattern); } if (mCurrentPattern == null) { @@ -530,7 +546,7 @@ public class ChooseLockPattern extends SettingsActivity { outState.putInt(KEY_UI_STAGE, mUiStage.ordinal()); if (mChosenPattern != null) { outState.putString(KEY_PATTERN_CHOICE, - LockPatternUtils.patternToString(mChosenPattern)); + LockPatternUtils.patternToString(mChosenPattern, mPatternSize)); } if (mCurrentPattern != null) { @@ -645,7 +661,7 @@ public class ChooseLockPattern extends SettingsActivity { final boolean required = getActivity().getIntent().getBooleanExtra( EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true); mSaveAndFinishWorker.start(mChooseLockSettingsHelper.utils(), required, - mHasChallenge, mChallenge, mChosenPattern, mCurrentPattern); + mHasChallenge, mChallenge, mChosenPattern, mCurrentPattern, mPatternSize); } @Override @@ -667,14 +683,16 @@ public class ChooseLockPattern extends SettingsActivity { private List<LockPatternView.Cell> mChosenPattern; private String mCurrentPattern; private boolean mLockVirgin; + private byte mPatternSize; public void start(LockPatternUtils utils, boolean credentialRequired, boolean hasChallenge, long challenge, - List<LockPatternView.Cell> chosenPattern, String currentPattern) { + List<LockPatternView.Cell> chosenPattern, String currentPattern, byte patternSize) { prepare(utils, credentialRequired, hasChallenge, challenge); mCurrentPattern = currentPattern; mChosenPattern = chosenPattern; + mPatternSize = patternSize; mLockVirgin = !mUtils.isPatternEverChosen(UserHandle.myUserId()); @@ -685,6 +703,7 @@ public class ChooseLockPattern extends SettingsActivity { protected Intent saveAndVerifyInBackground() { Intent result = null; final int userId = UserHandle.myUserId(); + mUtils.setLockPatternSize(mPatternSize, userId); mUtils.saveLockPattern(mChosenPattern, mCurrentPattern, userId); if (mHasChallenge) { diff --git a/src/com/android/settings/ChooseLockPatternSize.java b/src/com/android/settings/ChooseLockPatternSize.java new file mode 100644 index 0000000..b6f17f1 --- /dev/null +++ b/src/com/android/settings/ChooseLockPatternSize.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2012-2013 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings; + +import android.content.Intent; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.PreferenceScreen; + +import com.android.internal.logging.MetricsConstants; +import com.android.internal.widget.LockPatternUtils; +import org.cyanogenmod.internal.logging.CMMetricsLogger; + +public class ChooseLockPatternSize extends PreferenceActivity { + + @Override + public Intent getIntent() { + Intent modIntent = new Intent(super.getIntent()); + modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ChooseLockPatternSizeFragment.class.getName()); + modIntent.putExtra(EXTRA_NO_HEADERS, true); + return modIntent; + } + + @Override + protected boolean isValidFragment(String fragmentName) { + if (ChooseLockPatternSizeFragment.class.getName().equals(fragmentName)) return true; + return false; + } + + public static class ChooseLockPatternSizeFragment extends SettingsPreferenceFragment { + private ChooseLockSettingsHelper mChooseLockSettingsHelper; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mChooseLockSettingsHelper = new ChooseLockSettingsHelper(this.getActivity()); + if (!(getActivity() instanceof ChooseLockPatternSize)) { + throw new SecurityException("Fragment contained in wrong activity"); + } + addPreferencesFromResource(R.xml.security_settings_pattern_size); + } + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, + Preference preference) { + final String key = preference.getKey(); + + byte patternSize; + if ("lock_pattern_size_4".equals(key)) { + patternSize = 4; + } else if ("lock_pattern_size_5".equals(key)) { + patternSize = 5; + } else if ("lock_pattern_size_6".equals(key)) { + patternSize = 6; + } else { + patternSize = 3; + } + + final boolean isFallback = getActivity().getIntent() + .getBooleanExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, false); + + Intent intent = new Intent(getActivity(), ChooseLockPattern.class); + intent.putExtra("pattern_size", patternSize); + intent.putExtra("key_lock_method", "pattern"); + intent.putExtra("confirm_credentials", false); + intent.putExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, + isFallback); + + Intent originatingIntent = getActivity().getIntent(); + // Forward the challenge extras if available in originating intent. + if (originatingIntent.hasExtra(ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE)) { + intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, + originatingIntent.getBooleanExtra( + ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false)); + + intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, + originatingIntent.getLongExtra( + ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0)); + } + // Forward the Encryption interstitial required password selection + if (originatingIntent.hasExtra(EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD)) { + intent.putExtra(EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, originatingIntent + .getBooleanExtra(EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true)); + } + intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); + startActivity(intent); + + finish(); + return true; + } + + @Override + protected int getMetricsCategory() { + return CMMetricsLogger.CHOOSE_LOCK_PATTERN_SIZE; + } + } +} diff --git a/src/com/android/settings/ChooseLockSettingsHelper.java b/src/com/android/settings/ChooseLockSettingsHelper.java index 665bffe..11d03a7 100644 --- a/src/com/android/settings/ChooseLockSettingsHelper.java +++ b/src/com/android/settings/ChooseLockSettingsHelper.java @@ -25,6 +25,8 @@ import android.os.UserHandle; import com.android.internal.widget.LockPatternUtils; +import org.cyanogenmod.internal.util.CmLockPatternUtils; + public final class ChooseLockSettingsHelper { static final String EXTRA_KEY_TYPE = "type"; @@ -36,12 +38,14 @@ public final class ChooseLockSettingsHelper { private LockPatternUtils mLockPatternUtils; + private CmLockPatternUtils mCmLockPatternUtils; private Activity mActivity; private Fragment mFragment; public ChooseLockSettingsHelper(Activity activity) { mActivity = activity; mLockPatternUtils = new LockPatternUtils(activity); + mCmLockPatternUtils = new CmLockPatternUtils(activity); } public ChooseLockSettingsHelper(Activity activity, Fragment fragment) { @@ -53,6 +57,10 @@ public final class ChooseLockSettingsHelper { return mLockPatternUtils; } + public CmLockPatternUtils cmUtils() { + return mCmLockPatternUtils; + } + /** * If a pattern, password or PIN exists, prompt the user before allowing them to change it. * diff --git a/src/com/android/settings/CmRadioInfo.java b/src/com/android/settings/CmRadioInfo.java new file mode 100644 index 0000000..112f4d8 --- /dev/null +++ b/src/com/android/settings/CmRadioInfo.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings; + +import android.app.Activity; +import android.os.Bundle; +import android.os.SystemProperties; +import android.util.Log; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; + +public class CmRadioInfo extends Activity { + private final String TAG = "CmRadioInfo"; + + private Button mbnAutoLoadButton; + private Button volteAvailOvrButton; + private Button vtAvailOvrButton; + private Button wfcAvailOvrButton; + private Button adbRadioLog; + private Button diagLog; + + static final String PROPERTY_SW_MBN_UPDATE = "persist.radio.sw_mbn_update"; + static final String PROPERTY_SW_MBN_VOLTE = "persist.radio.sw_mbn_volte"; + static final String PROPERTY_VOLTE_AVAIL_OVR = "persist.dbg.volte_avail_ovr"; + static final String PROPERTY_VT_AVAIL_OVR = "persist.dbg.vt_avail_ovr"; + static final String PROPERTY_DATA_IWLAN_ENABLE = "persist.data.iwlan.enable"; + static final String PROPERTY_WFC_AVAIL_OVR = "persist.dbg.wfc_avail_ovr"; + static final String PROPERTY_ADB_LOG_ON = "persist.radio.adb_log_on"; + static final String PROPERTY_DIAG_LOG_ON = "persist.radio.diag_log_on"; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + setContentView(R.layout.cm_radio_info); + + mbnAutoLoadButton = (Button) findViewById(R.id.mbn_auto_load); + mbnAutoLoadButton.setOnClickListener(mMbnAutoLoadHandler); + + volteAvailOvrButton = (Button) findViewById(R.id.volte_avail_ovr); + volteAvailOvrButton.setOnClickListener(mVolteAvailOvrHandler); + + vtAvailOvrButton = (Button) findViewById(R.id.vt_avail_ovr); + vtAvailOvrButton.setOnClickListener(mVtAvailOvrHandler); + + wfcAvailOvrButton = (Button) findViewById(R.id.wfc_avail_ovr); + wfcAvailOvrButton.setOnClickListener(mWfcAvailOvrHandler); + + adbRadioLog = (Button) findViewById(R.id.adb_radio_log); + adbRadioLog.setOnClickListener(mAdbRadioLogHandler); + + diagLog = (Button) findViewById(R.id.diag_log); + diagLog.setOnClickListener(mDiagLogHandler); + } + + @Override + protected void onResume() { + super.onResume(); + + updateMbnAutoLoadState(); + updateVolteAvailOvrState(); + updateVtAvailOvrState(); + updateWfcAvailOvrState(); + updateAdbRadioLogState(); + updateDiagLogState(); + + log("onResume: update cm radio info"); + } + + @Override + public void onPause() { + super.onPause(); + + log("onPause: cm radio info"); + } + + OnClickListener mMbnAutoLoadHandler = new OnClickListener() { + @Override + public void onClick(View v) { + SystemProperties.set(PROPERTY_SW_MBN_UPDATE, (isMbnAutoLoad() ? "0" : "1")); + updateMbnAutoLoadState(); + } + }; + + private boolean isMbnAutoLoad() { + return SystemProperties.getBoolean(PROPERTY_SW_MBN_UPDATE, false); + } + + private void updateMbnAutoLoadState() { + log("updateMbnAutoLoadState isMbnAutoLoad()=" + isMbnAutoLoad()); + String buttonText = isMbnAutoLoad() ? + getString(R.string.cm_radio_info_mbn_auto_load_on_label) : + getString(R.string.cm_radio_info_mbn_auto_load_off_label); + mbnAutoLoadButton.setText(buttonText); + } + + OnClickListener mVolteAvailOvrHandler = new OnClickListener() { + @Override + public void onClick(View v) { + SystemProperties.set(PROPERTY_SW_MBN_VOLTE, (isVolteAvailOvr() ? "0" : "1")); + SystemProperties.set(PROPERTY_VOLTE_AVAIL_OVR, (isVolteAvailOvr() ? "0" : "1")); + updateVolteAvailOvrState(); + } + }; + + private boolean isVolteAvailOvr() { + return SystemProperties.getBoolean(PROPERTY_VOLTE_AVAIL_OVR, false); + } + + private void updateVolteAvailOvrState() { + log("updateVolteAvailOvrState isVolteAvailOvr()=" + isVolteAvailOvr()); + String buttonText = isVolteAvailOvr() ? + getString(R.string.cm_radio_info_volte_avail_ovr_on_label) : + getString(R.string.cm_radio_info_volte_avail_ovr_off_label); + volteAvailOvrButton.setText(buttonText); + } + + OnClickListener mVtAvailOvrHandler = new OnClickListener() { + @Override + public void onClick(View v) { + SystemProperties.set(PROPERTY_VT_AVAIL_OVR, (isVtAvailOvr() ? "0" : "1")); + updateVtAvailOvrState(); + } + }; + + private boolean isVtAvailOvr() { + return SystemProperties.getBoolean(PROPERTY_VT_AVAIL_OVR, false); + } + + private void updateVtAvailOvrState() { + log("updateVtAvailOvrState isVtAvailOvr()=" + isVtAvailOvr()); + String buttonText = isVtAvailOvr() ? + getString(R.string.cm_radio_info_vt_avail_ovr_on_label) : + getString(R.string.cm_radio_info_vt_avail_ovr_off_label); + vtAvailOvrButton.setText(buttonText); + } + + OnClickListener mWfcAvailOvrHandler = new OnClickListener() { + @Override + public void onClick(View v) { + SystemProperties.set(PROPERTY_DATA_IWLAN_ENABLE, (isWfcAvailOvr() ? "false" : "true")); + SystemProperties.set(PROPERTY_WFC_AVAIL_OVR, (isWfcAvailOvr() ? "0" : "1")); + updateWfcAvailOvrState(); + } + }; + + private boolean isWfcAvailOvr() { + return SystemProperties.getBoolean(PROPERTY_WFC_AVAIL_OVR, false); + } + + private void updateWfcAvailOvrState() { + log("updateWfcAvailOvrState isWfcAvailOvr()=" + isWfcAvailOvr()); + String buttonText = isWfcAvailOvr() ? + getString(R.string.cm_radio_info_wfc_avail_ovr_on_label) : + getString(R.string.cm_radio_info_wfc_avail_ovr_off_label); + wfcAvailOvrButton.setText(buttonText); + } + + OnClickListener mAdbRadioLogHandler = new OnClickListener() { + @Override + public void onClick(View v) { + SystemProperties.set(PROPERTY_ADB_LOG_ON, (isAdbRadioLog() ? "0" : "1")); + updateAdbRadioLogState(); + } + }; + + private boolean isAdbRadioLog() { + return SystemProperties.getBoolean(PROPERTY_ADB_LOG_ON, false); + } + + private void updateAdbRadioLogState() { + log("updateAdbRadioLogState isAdbRadioLog()=" + isAdbRadioLog()); + String buttonText = isAdbRadioLog() ? + getString(R.string.cm_radio_info_adb_log_on_label) : + getString(R.string.cm_radio_info_adb_log_off_label); + adbRadioLog.setText(buttonText); + } + + OnClickListener mDiagLogHandler = new OnClickListener() { + @Override + public void onClick(View v) { + SystemProperties.set(PROPERTY_DIAG_LOG_ON, (isDiagLog() ? "0" : "1")); + updateDiagLogState(); + } + }; + + private boolean isDiagLog() { + return SystemProperties.getBoolean(PROPERTY_DIAG_LOG_ON, false); + } + + private void updateDiagLogState() { + log("updateDiagLogState isDiagLog()=" + isDiagLog()); + String buttonText = isDiagLog() ? + getString(R.string.cm_radio_info_diag_log_on_label) : + getString(R.string.cm_radio_info_diag_log_off_label); + diagLog.setText(buttonText); + } + + private void log(String s) { + Log.d(TAG, "[Phone] " + s); + } +} diff --git a/src/com/android/settings/ConfirmDeviceCredentialBaseActivity.java b/src/com/android/settings/ConfirmDeviceCredentialBaseActivity.java index d9af800..2d78de2 100644 --- a/src/com/android/settings/ConfirmDeviceCredentialBaseActivity.java +++ b/src/com/android/settings/ConfirmDeviceCredentialBaseActivity.java @@ -92,11 +92,17 @@ public abstract class ConfirmDeviceCredentialBaseActivity extends SettingsActivi } public void prepareEnterAnimation() { - getFragment().prepareEnterAnimation(); + final ConfirmDeviceCredentialBaseFragment f = getFragment(); + if (f != null) { + f.prepareEnterAnimation(); + } } public void startEnterAnimation() { - getFragment().startEnterAnimation(); + final ConfirmDeviceCredentialBaseFragment f = getFragment(); + if (f != null) { + f.startEnterAnimation(); + } } /** diff --git a/src/com/android/settings/ConfirmLockPattern.java b/src/com/android/settings/ConfirmLockPattern.java index 94ba01a..2e74691 100644 --- a/src/com/android/settings/ConfirmLockPattern.java +++ b/src/com/android/settings/ConfirmLockPattern.java @@ -16,6 +16,7 @@ package com.android.settings; +import android.os.UserHandle; import com.android.internal.logging.MetricsLogger; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockPatternView; @@ -145,6 +146,7 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity { mLockPatternUtils.isTactileFeedbackEnabled()); mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled( mEffectiveUserId)); + mLockPatternView.setLockPatternSize(mLockPatternUtils.getLockPatternSize(mEffectiveUserId)); mLockPatternView.setOnPatternListener(mConfirmExistingLockPatternListener); updateStage(Stage.NeedToUnlock); @@ -457,7 +459,7 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity { intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_TYPE, StorageManager.CRYPT_TYPE_PATTERN); intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, - LockPatternUtils.patternToString(pattern)); + mLockPatternUtils.patternToString(pattern, localEffectiveUserId)); } mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs, localEffectiveUserId); diff --git a/src/com/android/settings/CryptKeeper.java b/src/com/android/settings/CryptKeeper.java index 45b4ff1..cfb27e1 100644 --- a/src/com/android/settings/CryptKeeper.java +++ b/src/com/android/settings/CryptKeeper.java @@ -48,6 +48,8 @@ import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; import android.view.View.OnClickListener; import android.view.View.OnKeyListener; import android.view.View.OnTouchListener; @@ -66,6 +68,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockPatternView; import com.android.internal.widget.LockPatternView.Cell; +import java.util.ArrayList; import java.util.List; import static com.android.internal.widget.LockPatternView.DisplayMode; @@ -84,7 +87,7 @@ import static com.android.internal.widget.LockPatternView.DisplayMode; * </pre> */ public class CryptKeeper extends Activity implements TextView.OnEditorActionListener, - OnKeyListener, OnTouchListener, TextWatcher { + OnKeyListener, OnTouchListener, TextWatcher, OnClickListener { private static final String TAG = "CryptKeeper"; private static final String DECRYPT_STATE = "trigger_restart_framework"; @@ -115,6 +118,8 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList private boolean mEncryptionGoneBad; /** If gone bad, should we show encryption failed (false) or corrupt (true)*/ private boolean mCorrupt; + /** If gone bad and mdtp is activated we should not allow recovery screen, only wipe the data */ + private boolean mMdtpActivated; /** A flag to indicate when the back event should be ignored */ /** When set, blocks unlocking. Set every COOL_DOWN_ATTEMPTS attempts, only cleared by power cycling phone. */ @@ -123,6 +128,15 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList PowerManager.WakeLock mWakeLock; private EditText mPasswordEntry; private LockPatternView mLockPatternView; + private TextView mStatusText; + private List<Button> mLockPatternButtons = new ArrayList<>(); + private static final int[] LOCK_BUTTON_IDS = new int[] { + R.id.lock_pattern_size_3, + R.id.lock_pattern_size_4, + R.id.lock_pattern_size_5, + R.id.lock_pattern_size_6 + }; + /** Number of calls to {@link #notifyUser()} to ignore before notifying. */ private int mNotificationCountdown = 0; /** Number of calls to {@link #notifyUser()} before we release the wakelock */ @@ -176,6 +190,9 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList @Override protected void onPreExecute() { super.onPreExecute(); + if (mLockPatternView != null) { + mLockPatternView.removeCallbacks(mFakeUnlockAttemptRunnable); + } beginAttempt(); } @@ -199,23 +216,36 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList mLockPatternView.removeCallbacks(mClearPatternRunnable); mLockPatternView.postDelayed(mClearPatternRunnable, RIGHT_PATTERN_CLEAR_TIMEOUT_MS); } - final TextView status = (TextView) findViewById(R.id.status); - status.setText(R.string.starting_android); + mStatusText.setText(R.string.starting_android); hide(R.id.passwordEntry); hide(R.id.switch_ime_button); hide(R.id.lockPattern); hide(R.id.owner_info); hide(R.id.emergencyCallButton); + hide(R.id.pattern_sizes); } else if (failedAttempts == MAX_FAILED_ATTEMPTS) { // Factory reset the device. - Intent intent = new Intent(Intent.ACTION_MASTER_CLEAR); - intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); - intent.putExtra(Intent.EXTRA_REASON, "CryptKeeper.MAX_FAILED_ATTEMPTS"); - sendBroadcast(intent); + if(mMdtpActivated){ + Log.d(TAG, + " CryptKeeper.MAX_FAILED_ATTEMPTS, calling encryptStorage with wipe"); + try { + final IMountService service = getMountService(); + service.encryptWipeStorage(StorageManager.CRYPT_TYPE_DEFAULT, ""); + + } catch (RemoteException e) { + Log.w(TAG, "Unable to call MountService properly"); + return; + } + } else { + Intent intent = new Intent(Intent.ACTION_MASTER_CLEAR); + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + intent.putExtra(Intent.EXTRA_REASON, "CryptKeeper.MAX_FAILED_ATTEMPTS"); + sendBroadcast(intent); + } } else if (failedAttempts == -1) { // Right password, but decryption failed. Tell user bad news ... setContentView(R.layout.crypt_keeper_progress); - showFactoryReset(true); + showFactoryReset(true, false); return; } else { handleBadAttempt(failedAttempts); @@ -224,8 +254,7 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList } private void beginAttempt() { - final TextView status = (TextView) findViewById(R.id.status); - status.setText(R.string.checking_decryption); + mStatusText.setText(R.string.checking_decryption); } private void handleBadAttempt(Integer failedAttempts) { @@ -241,14 +270,12 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList // at this point. cooldown(); } else { - final TextView status = (TextView) findViewById(R.id.status); - int remainingAttempts = MAX_FAILED_ATTEMPTS - failedAttempts; if (remainingAttempts < COOL_DOWN_ATTEMPTS) { CharSequence warningTemplate = getText(R.string.crypt_keeper_warn_wipe); CharSequence warning = TextUtils.expandTemplate(warningTemplate, Integer.toString(remainingAttempts)); - status.setText(warning); + mStatusText.setText(warning); } else { int passwordType = StorageManager.CRYPT_TYPE_PASSWORD; try { @@ -259,17 +286,18 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList } if (passwordType == StorageManager.CRYPT_TYPE_PIN) { - status.setText(R.string.cryptkeeper_wrong_pin); + mStatusText.setText(R.string.cryptkeeper_wrong_pin); } else if (passwordType == StorageManager.CRYPT_TYPE_PATTERN) { - status.setText(R.string.cryptkeeper_wrong_pattern); + mStatusText.setText(R.string.cryptkeeper_wrong_pattern); } else { - status.setText(R.string.cryptkeeper_wrong_password); + mStatusText.setText(R.string.cryptkeeper_wrong_password); } } if (mLockPatternView != null) { mLockPatternView.setDisplayMode(DisplayMode.Wrong); mLockPatternView.setEnabled(true); + setPatternButtonsEnabled(true); } // Reenable the password entry @@ -296,10 +324,13 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList 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; + mMdtpActivated = (state == IMountService.ENCRYPTION_STATE_ERROR_MDTP_ACTIVATED) || + (state == IMountService.ENCRYPTION_STATE_OK_MDTP_ACTIVATED); + return (state == IMountService.ENCRYPTION_STATE_OK) || + (state == IMountService.ENCRYPTION_STATE_OK_MDTP_ACTIVATED); } catch (RemoteException e) { Log.w(TAG, "Unable to get encryption state properly"); - return true; + return false; } } @@ -400,6 +431,13 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + getWindow().getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar + | View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar + | View.SYSTEM_UI_FLAG_IMMERSIVE); + // 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))) { @@ -456,7 +494,7 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList private void setupUi() { if (mEncryptionGoneBad || isDebugView(FORCE_VIEW_ERROR)) { setContentView(R.layout.crypt_keeper_progress); - showFactoryReset(mCorrupt); + showFactoryReset(mCorrupt, mMdtpActivated); return; } @@ -502,8 +540,7 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList setContentView(R.layout.crypt_keeper_password_entry); mStatusString = R.string.enter_password; } - final TextView status = (TextView) findViewById(R.id.status); - status.setText(mStatusString); + mStatusText.setText(mStatusString); final TextView ownerInfo = (TextView) findViewById(R.id.owner_info); ownerInfo.setText(owner_info); @@ -562,6 +599,12 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList } } + @Override + public void setContentView(int layoutResID) { + super.setContentView(layoutResID); + mStatusText = (TextView) findViewById(R.id.status); + } + /** * Start encrypting the device. */ @@ -590,8 +633,10 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList * there is nothing else we can do * @param corrupt true if userdata is corrupt, false if encryption failed * partway through + * @param mdtp_activated true if MDTP is activated according to MountService + * state. */ - private void showFactoryReset(final boolean corrupt) { + private void showFactoryReset(final boolean corrupt, final boolean mdtp_activated) { // Hide the encryption-bot to make room for the "factory reset" button findViewById(R.id.encroid).setVisibility(View.GONE); @@ -601,12 +646,24 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList button.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - // Factory reset the device. - Intent intent = new Intent(Intent.ACTION_MASTER_CLEAR); - intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); - intent.putExtra(Intent.EXTRA_REASON, - "CryptKeeper.showFactoryReset() corrupt=" + corrupt); - sendBroadcast(intent); + if(mdtp_activated){ + Log.d(TAG, " Calling encryptStorage with wipe"); + try { + final IMountService service = getMountService(); + service.encryptWipeStorage(StorageManager.CRYPT_TYPE_DEFAULT, ""); + + } catch (RemoteException e) { + Log.w(TAG, "Unable to call MountService properly"); + return; + } + } else { + // Factory reset the device. + Intent intent = new Intent(Intent.ACTION_MASTER_CLEAR); + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + intent.putExtra(Intent.EXTRA_REASON, + "CryptKeeper.showFactoryReset() corrupt=" + corrupt); + sendBroadcast(intent); + } } }); @@ -630,7 +687,7 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList final String state = SystemProperties.get("vold.encrypt_progress"); if ("error_partially_encrypted".equals(state)) { - showFactoryReset(false); + showFactoryReset(false, false); return; } @@ -660,9 +717,8 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList // Will happen if no time etc - show percentage } - final TextView tv = (TextView) findViewById(R.id.status); - if (tv != null) { - tv.setText(TextUtils.expandTemplate(status, progress)); + if (mStatusText != null) { + mStatusText.setText(TextUtils.expandTemplate(status, progress)); } // Check the progress every 1 seconds @@ -678,12 +734,13 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList if (mPasswordEntry != null) { mPasswordEntry.setEnabled(false); } + if (mLockPatternView != null) { mLockPatternView.setEnabled(false); + setPatternButtonsEnabled(false); } - final TextView status = (TextView) findViewById(R.id.status); - status.setText(R.string.crypt_keeper_force_power_cycle); + mStatusText.setText(R.string.crypt_keeper_force_power_cycle); } /** @@ -708,18 +765,21 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList @Override public void onPatternStart() { + setPatternButtonsEnabled(false); mLockPatternView.removeCallbacks(mClearPatternRunnable); } @Override public void onPatternCleared() { + setPatternButtonsEnabled(true); } @Override public void onPatternDetected(List<LockPatternView.Cell> pattern) { mLockPatternView.setEnabled(false); if (pattern.size() >= MIN_LENGTH_BEFORE_REPORT) { - new DecryptTask().execute(LockPatternUtils.patternToString(pattern)); + new DecryptTask().execute(LockPatternUtils.patternToString(pattern, + mLockPatternView.getLockPatternSize())); } else { // Allow user to make as many of these as they want. fakeUnlockAttempt(mLockPatternView); @@ -743,10 +803,18 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList mPasswordEntry.addTextChangedListener(this); } + mLockPatternButtons.clear(); // Pattern case mLockPatternView = (LockPatternView) findViewById(R.id.lockPattern); if (mLockPatternView != null) { mLockPatternView.setOnPatternListener(mChooseNewLockPatternListener); + for (int id : LOCK_BUTTON_IDS) { + Button btn = (Button) findViewById(id); + if (btn != null) { + btn.setOnClickListener(this); + mLockPatternButtons.add(btn); + } + } } // Disable the Emergency call button if the device has no voice telephone capability @@ -1026,4 +1094,39 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList pm.setComponentEnabledSetting(name, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); } + + @Override + public void onClick(View v) { + if (mLockPatternView == null || !mLockPatternView.isEnabled()) { + return; + } + byte size; + switch (v.getId()) { + default: + case R.id.lock_pattern_size_3: + size = 3; + break; + case R.id.lock_pattern_size_4: + size = 4; + break; + case R.id.lock_pattern_size_5: + size = 5; + break; + case R.id.lock_pattern_size_6: + size = 6; + break; + } + setContentView(R.layout.crypt_keeper_pattern_entry); + passwordEntryInit(); + + mStatusText.setText(mStatusString = R.string.enter_pattern); + mLockPatternView.setLockPatternSize(size); + mLockPatternView.postInvalidate(); + } + + private void setPatternButtonsEnabled(boolean enabled) { + for (Button btn : mLockPatternButtons) { + btn.setEnabled(enabled); + } + } } diff --git a/src/com/android/settings/DataUsageSummary.java b/src/com/android/settings/DataUsageSummary.java index 135328b..8bfff58 100644 --- a/src/com/android/settings/DataUsageSummary.java +++ b/src/com/android/settings/DataUsageSummary.java @@ -25,6 +25,8 @@ 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.POLICY_REJECT_ON_WLAN_BACKGROUND; +import static android.net.NetworkPolicyManager.POLICY_REJECT_ON_DATA; import static android.net.NetworkPolicyManager.computeLastCycleBoundary; import static android.net.NetworkPolicyManager.computeNextCycleBoundary; import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER; @@ -81,8 +83,6 @@ import android.net.TrafficStats; 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; @@ -98,7 +98,6 @@ 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; @@ -130,20 +129,22 @@ import android.widget.Toast; import com.android.internal.logging.MetricsLogger; import com.android.internal.telephony.PhoneConstants; +import com.android.settings.DataUsageUtils; 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.search.BaseSearchIndexProvider; import com.android.settings.search.Indexable; import com.android.settings.search.SearchIndexableRaw; import com.android.settings.widget.ChartDataUsageView; import com.android.settings.widget.ChartDataUsageView.DataUsageChartListener; import com.android.settings.widget.ChartNetworkSeriesView; +import com.android.settingslib.AppItem; +import com.android.settingslib.NetworkPolicyEditor; +import com.android.settingslib.net.ChartData; +import com.android.settingslib.net.ChartDataLoader; +import com.android.settingslib.net.SummaryForAllUidLoader; +import com.android.settingslib.net.UidDetail; +import com.android.settingslib.net.UidDetailProvider; import com.google.android.collect.Lists; import libcore.util.Objects; @@ -178,6 +179,8 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable private static final String TAB_ETHERNET = "ethernet"; private static final String TAG_CONFIRM_DATA_DISABLE = "confirmDataDisable"; + private static final String TAG_CONFIRM_DATA_RESET = "confirmDataReset"; + private static final String TAG_CONFIRM_APP_RESTRICT_CELLULAR = "confirmAppRestrictCellular"; private static final String TAG_CONFIRM_LIMIT = "confirmLimit"; private static final String TAG_CYCLE_EDITOR = "cycleEditor"; private static final String TAG_WARNING_EDITOR = "warningEditor"; @@ -196,6 +199,9 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable private static final int LOADER_CHART_DATA = 2; private static final int LOADER_SUMMARY = 3; + private static final int DATA_USAGE_BACKGROUND_FULL_ACCESS = 0; + private static final int DATA_USAGE_BACKGROUND_WLAN_ACCESS = 1; + private static final int DATA_USAGE_BACKGROUND_NO_ACCESS = 2; private INetworkManagementService mNetworkService; private INetworkStatsService mStatsService; @@ -208,6 +214,7 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable 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 static final String PREF_ENABLE_DATA_USAGE_NOTIFY = "enable_data_usage_notify"; private SharedPreferences mPrefs; @@ -252,11 +259,17 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable private Button mAppSettings; private LinearLayout mAppSwitches; - private Switch mAppRestrict; + private Switch mAppDataAlert; + private Switch mAppCellularAccess; private View mAppRestrictView; + private View mAppDataAlertView; + private View mAppCellularAccessView; + private Spinner mRestrictSpinner; private boolean mShowWifi = false; private boolean mShowEthernet = false; + private boolean mShowAlerts = false; + private boolean mDataAlertsSupported = false; private NetworkTemplate mTemplate; private ChartData mChartData; @@ -275,6 +288,8 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable private MenuItem mMenuShowEthernet; private MenuItem mMenuSimCards; private MenuItem mMenuCellularNetworks; + private MenuItem mMenuDataAlerts; + private MenuItem mMenuResetStats; private List<SubscriptionInfo> mSubInfoList; private Map<Integer,String> mMobileTagMap; @@ -334,8 +349,9 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable throw new RuntimeException(e); } - mShowWifi = mPrefs.getBoolean(PREF_SHOW_WIFI, false); + mShowWifi = mPrefs.getBoolean(PREF_SHOW_WIFI, true); mShowEthernet = mPrefs.getBoolean(PREF_SHOW_ETHERNET, false); + mShowAlerts = mPrefs.getBoolean(PREF_ENABLE_DATA_USAGE_NOTIFY, false); // override preferences when no mobile radio if (!hasReadyMobileRadio(context)) { @@ -446,14 +462,51 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable mAppSettings = (Button) mAppDetail.findViewById(R.id.app_settings); - mAppRestrict = new Switch(inflater.getContext()); - mAppRestrict.setClickable(false); - mAppRestrict.setFocusable(false); - mAppRestrictView = inflatePreference(inflater, mAppSwitches, mAppRestrict); + ArrayAdapter<String> restrictAdapter = new ArrayAdapter<String>( + inflater.getContext(), android.R.layout.simple_spinner_dropdown_item, + getResources().getStringArray(R.array.background_data_access_choices)); + + mRestrictSpinner = new Spinner(inflater.getContext()); + mRestrictSpinner.setAdapter(restrictAdapter); + mRestrictSpinner.setOnItemSelectedListener(mAppRestrictListener); + + mAppRestrictView = inflatePreferenceWithInvisibleWidget(inflater, + mAppSwitches, mRestrictSpinner); mAppRestrictView.setClickable(true); mAppRestrictView.setFocusable(true); - mAppRestrictView.setOnClickListener(mAppRestrictListener); + mAppRestrictView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mRestrictSpinner.performClick(); + } + }); mAppSwitches.addView(mAppRestrictView); + + //switch for per app data alert enable/disable + mAppDataAlert = new Switch(inflater.getContext()); + mAppDataAlert.setClickable(false); + mAppDataAlert.setFocusable(false); + mAppDataAlertView = inflatePreference(inflater, mAppSwitches, mAppDataAlert); + mAppDataAlertView.setClickable(true); + mAppDataAlertView.setFocusable(true); + mAppDataAlertView.setOnClickListener(mAppDataAlertListner); + mAppSwitches.addView(mAppDataAlertView); + + // check if content provider is installed. If not, hide data alert switch + mDataAlertsSupported = DataUsageUtils.isDbEnabled(context); + if (!mDataAlertsSupported) { + mAppDataAlertView.setVisibility(View.GONE); + } + + // switch for per app cellular access enabled/disable + mAppCellularAccess = new Switch(inflater.getContext()); + mAppCellularAccess.setClickable(false); + mAppCellularAccess.setFocusable(false); + mAppCellularAccessView = inflatePreference(inflater, mAppSwitches, mAppCellularAccess); + mAppCellularAccessView.setClickable(true); + mAppCellularAccessView.setFocusable(true); + mAppCellularAccessView.setOnClickListener(mAppRestrictCellularListener); + mAppSwitches.addView(mAppCellularAccessView); } mDisclaimer = mHeader.findViewById(R.id.disclaimer); @@ -485,7 +538,7 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable // a header at the top. FrameLayout pinnedHeader = (FrameLayout) rootView.findViewById(R.id.pinned_header); AppHeader.createAppHeader(getActivity(), detail.icon, detail.label, null, pinnedHeader); - AppDetailsFragment.show(DataUsageSummary.this, app, detail.label, true); + AppDetailsFragment.show(DataUsageSummary.this, app, detail.label, false); } catch (NameNotFoundException e) { Log.w(TAG, "Could not find " + mShowAppImmediatePkg, e); Toast.makeText(getActivity(), getString(R.string.unknown_app), Toast.LENGTH_LONG) @@ -593,6 +646,17 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable help.setVisible(false); } + mMenuDataAlerts = menu.findItem(R.id.data_usage_menu_data_alerts); + + if (mDataAlertsSupported) { + mMenuDataAlerts.setVisible(!appDetailMode); + } else { + mMenuDataAlerts.setVisible(false); + } + + mMenuResetStats = menu.findItem(R.id.data_usage_menu_reset_stats); + mMenuResetStats.setVisible(!appDetailMode); + updateMenuTitles(); } @@ -614,6 +678,11 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable } else { mMenuShowEthernet.setTitle(R.string.data_usage_menu_show_ethernet); } + if (mShowAlerts) { + mMenuDataAlerts.setTitle(R.string.data_usage_menu_disable_data_alerts); + } else { + mMenuDataAlerts.setTitle(R.string.data_usage_menu_enable_data_alerts); + } } @Override @@ -660,6 +729,14 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable R.string.data_usage_metered_title, null, this, 0); return true; } + case R.id.data_usage_menu_reset_stats: { + ConfirmDataResetFragment.show(DataUsageSummary.this, mTemplate); + return true; + } + case R.id.data_usage_menu_data_alerts: { + updateShowAlertsState(!mShowAlerts); + return true; + } } return false; } @@ -677,6 +754,13 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable super.onDestroy(); } + private void updateShowAlertsState(boolean showAlert) { + mShowAlerts = showAlert; + mPrefs.edit().putBoolean(PREF_ENABLE_DATA_USAGE_NOTIFY, mShowAlerts).apply(); + updateMenuTitles(); + DataUsageUtils.enableDataUsageService(getContext(), mShowAlerts); + } + /** * Build and assign {@link LayoutTransition} to various containers. Should * only be assigned after initial layout is complete. @@ -734,6 +818,13 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable mTabHost.addTab(buildTabSpec(TAB_ETHERNET, R.string.data_usage_tab_ethernet)); } + if (getResources().getBoolean(R.bool.config_gcf_disable_default_tabtext_allcaps)) { + for (int i = 0; i < mTabWidget.getTabCount(); i++) { + TextView tv = (TextView) mTabWidget.getChildAt(i).findViewById(android.R.id.title); + tv.setAllCaps(false); + } + } + final boolean noTabs = mTabWidget.getTabCount() == 0; final boolean multipleTabs = mTabWidget.getTabCount() > 1; mTabWidget.setVisibility(multipleTabs ? View.VISIBLE : View.GONE); @@ -903,7 +994,7 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable mBinding = false; int seriesColor = context.getColor(R.color.sim_noitification); - if (mCurrentTab != null && mCurrentTab.length() > TAB_MOBILE.length() ){ + if (mCurrentTab != null && mCurrentTab.startsWith(TAB_MOBILE)) { final int slotId = Integer.parseInt(mCurrentTab.substring(TAB_MOBILE.length(), mCurrentTab.length())); final SubscriptionInfo sir = mSubscriptionManager @@ -936,11 +1027,9 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable if (isAppDetailMode()) { mAppDetail.setVisibility(View.VISIBLE); mCycleAdapter.setChangeVisible(false); - mChart.setVisibility(View.GONE); } else { mAppDetail.setVisibility(View.GONE); mCycleAdapter.setChangeVisible(true); - mChart.setVisibility(View.VISIBLE); // hide detail stats when not in detail mode mChart.bindDetailNetworkStats(null); @@ -1023,17 +1112,52 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable 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)); + if (UserHandle.isApp(uid) && isBandwidthControlEnabled()) { + setPreferenceTitle(mAppRestrictView, R.string.background_data_access); + int backgroundPolicy = getAppRestrictBackground(); + final int summaryResId, position; + if ((backgroundPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0) { + if ((backgroundPolicy & POLICY_REJECT_ON_WLAN_BACKGROUND) != 0) { + summaryResId = R.string.allow_background_none; + position = DATA_USAGE_BACKGROUND_NO_ACCESS; + } else { + summaryResId = R.string.allow_background_wlan; + position = DATA_USAGE_BACKGROUND_WLAN_ACCESS; + } + } else { + summaryResId = R.string.allow_background_both; + position = DATA_USAGE_BACKGROUND_FULL_ACCESS; + } + setPreferenceSummary(mAppRestrictView, getString(summaryResId)); + mRestrictSpinner.setSelection(position); mAppRestrictView.setVisibility(View.VISIBLE); - mAppRestrict.setChecked(getAppRestrictBackground()); + if (isMobileTab(mCurrentTab) || TAB_3G.equals(mCurrentTab) + || TAB_4G.equals(mCurrentTab)) { + setPreferenceTitle(mAppCellularAccessView, R.string.restrict_cellular_access_title); + setPreferenceSummary(mAppCellularAccessView, + getString(R.string.restrict_cellular_access_summary)); + mAppCellularAccessView.setVisibility(View.VISIBLE); + mAppCellularAccess.setChecked(getAppRestrictCellular()); + } else { + mAppCellularAccessView.setVisibility(View.GONE); + } + + if (mDataAlertsSupported) { + setPreferenceTitle(mAppDataAlertView, R.string.mobile_data_alert); + setPreferenceSummary(mAppDataAlertView, + getString(R.string.mobile_data_alert_summary)); + + mAppDataAlertView.setVisibility(View.VISIBLE); + mAppDataAlert.setChecked(getAppDataAlert()); + } else { + mAppDataAlertView.setVisibility(View.GONE); + } } else { mAppRestrictView.setVisibility(View.GONE); + mAppDataAlertView.setVisibility(View.GONE); + mAppCellularAccessView.setVisibility(View.GONE); } } @@ -1073,12 +1197,36 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable } private void setMobileDataEnabled(int subId, boolean enabled) { - if (LOGD) Log.d(TAG, "setMobileDataEnabled()"); + if (LOGD) Log.d(TAG, "setMobileDataEnabled: subId = " + subId + " enabled = " + enabled); + int dataSubId = mSubscriptionManager.getDefaultDataSubId(); mTelephonyManager.setDataEnabled(subId, enabled); mMobileDataEnabled.put(String.valueOf(subId), enabled); updatePolicy(false); } + private void resetDataStats(NetworkTemplate template) { + // kick off background task to reset stats + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... params) { + try { + mStatsService.resetDataUsageHistoryForAllUid(mTemplate); + mPolicyEditor.setPolicyLimitBytes(mTemplate, + mPolicyEditor.getPolicyLimitBytes(mTemplate)); + mStatsService.forceUpdate(); + } catch (RemoteException e) { + + } + return null; + } + @Override + protected void onPostExecute (Void result) { + updateBody(); + } + + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + private boolean isNetworkPolicyModifiable(NetworkPolicy policy) { return policy != null && isBandwidthControlEnabled() && mDataEnabled.isChecked() && ActivityManager.getCurrentUser() == UserHandle.USER_OWNER; @@ -1098,18 +1246,106 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable updateMenuTitles(); } - private boolean getAppRestrictBackground() { + private int getAppRestrictBackground() { final int uid = mCurrentApp.key; final int uidPolicy = mPolicyManager.getUidPolicy(uid); - return (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0; + + return ((uidPolicy & POLICY_REJECT_METERED_BACKGROUND) | (uidPolicy & + POLICY_REJECT_ON_WLAN_BACKGROUND)); } - private void setAppRestrictBackground(boolean restrictBackground) { + private void setAppRestrictBackground(int newPolicy) { if (LOGD) Log.d(TAG, "setAppRestrictBackground()"); final int uid = mCurrentApp.key; - mPolicyManager.setUidPolicy( - uid, restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE); - mAppRestrict.setChecked(restrictBackground); + final int currentPolicy = mPolicyManager.getUidPolicy(uid); + + if (newPolicy == currentPolicy) { + return; + } + + if (((newPolicy & POLICY_REJECT_METERED_BACKGROUND) ^ (currentPolicy & + POLICY_REJECT_METERED_BACKGROUND)) != 0 ) { + if ((newPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0) { + mPolicyManager.addUidPolicy(uid, POLICY_REJECT_METERED_BACKGROUND); + } else { + mPolicyManager.removeUidPolicy(uid, POLICY_REJECT_METERED_BACKGROUND); + } + } + + if (((newPolicy & POLICY_REJECT_ON_WLAN_BACKGROUND) ^ (currentPolicy & + POLICY_REJECT_ON_WLAN_BACKGROUND)) != 0 ) { + if ((newPolicy & POLICY_REJECT_ON_WLAN_BACKGROUND) != 0) { + mPolicyManager.addUidPolicy(uid, POLICY_REJECT_ON_WLAN_BACKGROUND); + } else { + mPolicyManager.removeUidPolicy(uid, POLICY_REJECT_ON_WLAN_BACKGROUND); + } + } + + final int summaryResId; + if ((newPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0) { + if ((newPolicy & POLICY_REJECT_ON_WLAN_BACKGROUND) != 0) { + summaryResId = R.string.allow_background_none; + } else { + summaryResId = R.string.allow_background_wlan; + } + } else { + summaryResId = R.string.allow_background_both; + } + setPreferenceSummary(mAppRestrictView, getString(summaryResId)); + } + + private boolean getAppRestrictCellular() { + final int uid = mCurrentApp.key; + final int uidPolicy = mPolicyManager.getUidPolicy(uid); + return (uidPolicy & POLICY_REJECT_ON_DATA) != 0; + } + + private void setAppRestrictCellular(boolean restrictCellular) { + if (LOGD) Log.d(TAG, "setAppRestrictCellular()"); + final int uid = mCurrentApp.key; + if (restrictCellular) { + mPolicyManager.addUidPolicy(uid, POLICY_REJECT_ON_DATA); + } else { + mPolicyManager.removeUidPolicy(uid, POLICY_REJECT_ON_DATA); + } + mAppCellularAccess.setChecked(restrictCellular); + } + + + private void setAppDataAlert(boolean enableDataAlert) { + final int uid = mCurrentApp.key; + + // Get app's details to send to the DataUsage Provider. Don't block if not in the + // DetailProvider cache. (should be in the cache, as the app's label had already + // been displayed in the list of apps) + UidDetail detail = mUidDetailProvider.getUidDetail(uid, false); + String label = detail != null ? detail.label.toString() : ""; + + try { + DataUsageUtils.enableApp(getContext(), uid, enableDataAlert, label); + } catch (Exception e) { + //content provider may not be installed. + Log.d(TAG, "Unable to set data alert state"); + return; + } + mAppDataAlert.setChecked(enableDataAlert); + + // automatically enable global alert for notifications when enabling first per app alert + if (enableDataAlert && !mShowAlerts) { + updateShowAlertsState(true); + } + } + + private boolean getAppDataAlert() { + final int uid = mCurrentApp.key; + + try { + return DataUsageUtils.isAppEnabled(getContext(), uid); + } catch (Exception e) { + //content provider may not be installed. + Log.d(TAG, "Unable to get data alert state"); + return false; + } } /** @@ -1251,16 +1487,6 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable } } - private void disableDataForOtherSubscriptions(SubscriptionInfo currentSir) { - if (mSubInfoList != null) { - for (SubscriptionInfo subInfo : mSubInfoList) { - if (subInfo.getSubscriptionId() != currentSir.getSubscriptionId()) { - setMobileDataEnabled(subInfo.getSubscriptionId(), false); - } - } - } - } - private View.OnClickListener mDataEnabledListener = new View.OnClickListener() { @Override public void onClick(View v) { @@ -1271,12 +1497,7 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable if (isMobileTab(currentTab)) { MetricsLogger.action(getContext(), MetricsLogger.ACTION_CELL_DATA_TOGGLE, enabled); if (enabled) { - // If we are showing the Sim Card tile then we are a Multi-Sim device. - if (Utils.showSimCardTile(getActivity())) { - handleMultiSimDataDialog(); - } else { - setMobileDataEnabled(getSubId(currentTab), true); - } + setMobileDataEnabled(getSubId(currentTab), true); } else { // disabling data; show confirmation dialog which eventually // calls setMobileDataEnabled() once user confirms. @@ -1288,55 +1509,6 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable } }; - private void handleMultiSimDataDialog() { - final Context context = getActivity(); - final SubscriptionInfo currentSir = getCurrentTabSubInfo(context); - - //If sim has not loaded after toggling data switch, return. - if (currentSir == null) { - return; - } - - final SubscriptionInfo nextSir = mSubscriptionManager.getDefaultDataSubscriptionInfo(); - - // If the device is single SIM or is enabling data on the active data SIM then forgo - // the pop-up. - if (!Utils.showSimCardTile(context) || - (nextSir != null && currentSir != null && - currentSir.getSubscriptionId() == nextSir.getSubscriptionId())) { - setMobileDataEnabled(currentSir.getSubscriptionId(), true); - if (nextSir != null && currentSir != null && - currentSir.getSubscriptionId() == nextSir.getSubscriptionId()) { - disableDataForOtherSubscriptions(currentSir); - } - updateBody(); - return; - } - - final String previousName = (nextSir == null) - ? context.getResources().getString(R.string.sim_selection_required_pref) - : nextSir.getDisplayName().toString(); - - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - - builder.setTitle(R.string.sim_change_data_title); - builder.setMessage(getActivity().getResources().getString(R.string.sim_change_data_message, - currentSir.getDisplayName(), previousName)); - - builder.setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - mSubscriptionManager.setDefaultDataSubId(currentSir.getSubscriptionId()); - setMobileDataEnabled(currentSir.getSubscriptionId(), true); - disableDataForOtherSubscriptions(currentSir); - updateBody(); - } - }); - builder.setNegativeButton(R.string.cancel, null); - - builder.create().show(); - } - private View.OnClickListener mDisableAtLimitListener = new View.OnClickListener() { @Override public void onClick(View v) { @@ -1351,22 +1523,53 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable } }; - private View.OnClickListener mAppRestrictListener = new View.OnClickListener() { + private AdapterView.OnItemSelectedListener mAppRestrictListener = + new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView<?> parent, View v, int position, long id) { + final int backgroundPolicy; + + if (position == DATA_USAGE_BACKGROUND_WLAN_ACCESS) { + backgroundPolicy = POLICY_REJECT_METERED_BACKGROUND; + } else if (position == DATA_USAGE_BACKGROUND_NO_ACCESS) { + backgroundPolicy = POLICY_REJECT_METERED_BACKGROUND | + POLICY_REJECT_ON_WLAN_BACKGROUND; + } else { + backgroundPolicy = DATA_USAGE_BACKGROUND_FULL_ACCESS; + } + setAppRestrictBackground(backgroundPolicy); + } + + @Override + public void onNothingSelected(AdapterView<?> parent) { + // noop + } + }; + + private View.OnClickListener mAppRestrictCellularListener = new View.OnClickListener() { @Override public void onClick(View v) { - final boolean restrictBackground = !mAppRestrict.isChecked(); + final boolean restrictCellular = !mAppCellularAccess.isChecked(); - if (restrictBackground) { + if (restrictCellular) { // enabling restriction; show confirmation dialog which - // eventually calls setRestrictBackground() once user + // eventually calls setRestrictCellular() once user // confirms. - ConfirmAppRestrictFragment.show(DataUsageSummary.this); + ConfirmAppRestrictCellularFragment.show(DataUsageSummary.this); } else { - setAppRestrictBackground(false); + setAppRestrictCellular(false); } } }; + private View.OnClickListener mAppDataAlertListner = new View.OnClickListener() { + @Override + public void onClick(View v) { + final boolean enableDataAlert = !mAppDataAlert.isChecked(); + setAppDataAlert(enableDataAlert); + } + }; + private OnItemClickListener mListListener = new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { @@ -1689,70 +1892,6 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable } } - public static class AppItem implements Comparable<AppItem>, Parcelable { - public static final int CATEGORY_USER = 0; - public static final int CATEGORY_APP_TITLE = 1; - public static final int CATEGORY_APP = 2; - - public final int key; - public boolean restricted; - public int category; - - public SparseBooleanArray uids = new SparseBooleanArray(); - public long total; - - public AppItem() { - this.key = 0; - } - - 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) { - int comparison = Integer.compare(category, another.category); - if (comparison == 0) { - comparison = Long.compare(another.total, total); - } - return comparison; - } - - public static final Creator<AppItem> CREATOR = new Creator<AppItem>() { - @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. */ @@ -2023,16 +2162,6 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable target.mCurrentApp = null; target.updateBody(); } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - getFragmentManager().popBackStack(); - return true; - } - return super.onOptionsItemSelected(item); - } } /** @@ -2326,6 +2455,45 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable } /** + * Dialog to request user confirmation before resetting data. + */ + public static class ConfirmDataResetFragment extends DialogFragment { + static NetworkTemplate mTemplate; + public static void show(DataUsageSummary parent, NetworkTemplate template) { + mTemplate = template; + if (!parent.isAdded()) return; + + final ConfirmDataResetFragment dialog = new ConfirmDataResetFragment(); + dialog.setTargetFragment(parent, 0); + dialog.show(parent.getFragmentManager(), TAG_CONFIRM_DATA_RESET); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Context context = getActivity(); + + final AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(R.string.data_usage_menu_reset_stats); + builder.setMessage(R.string.reset_data_stats_msg); + + builder.setPositiveButton(R.string.reset_stats_confirm, + 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.resetDataStats(mTemplate); + } + } + }); + builder.setNegativeButton(android.R.string.cancel, null); + + return builder.create(); + } + } + + /** * Dialog to request user confirmation before setting * {@link INetworkPolicyManager#setRestrictBackground(boolean)}. */ @@ -2394,15 +2562,16 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable /** * Dialog to request user confirmation before setting - * {@link #POLICY_REJECT_METERED_BACKGROUND}. + * {@link #POLICY_REJECT_ON_DATA}. */ - public static class ConfirmAppRestrictFragment extends DialogFragment { + public static class ConfirmAppRestrictCellularFragment extends DialogFragment { public static void show(DataUsageSummary parent) { if (!parent.isAdded()) return; - final ConfirmAppRestrictFragment dialog = new ConfirmAppRestrictFragment(); + final ConfirmAppRestrictCellularFragment dialog = new + ConfirmAppRestrictCellularFragment(); dialog.setTargetFragment(parent, 0); - dialog.show(parent.getFragmentManager(), TAG_CONFIRM_APP_RESTRICT); + dialog.show(parent.getFragmentManager(), TAG_CONFIRM_APP_RESTRICT_CELLULAR); } @Override @@ -2410,15 +2579,15 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable 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.setTitle(R.string.restrict_cellular_access_dialog_title); + builder.setMessage(R.string.restrict_cellular_access_dialog_summary); 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); + target.setAppRestrictCellular(true); } } }); @@ -2644,6 +2813,16 @@ public class DataUsageSummary extends HighlightingFragment implements Indexable return view; } + private static View inflatePreferenceWithInvisibleWidget(LayoutInflater inflater, + ViewGroup root, View widget) { + final ViewGroup view = (ViewGroup) inflater.inflate(R.layout.preference, root, false); + view.addView(widget, 0); + final ViewGroup.LayoutParams lp = widget.getLayoutParams(); + lp.width = 0; + widget.setLayoutParams(lp); + return view; + } + /** * Test if any networks are currently limited. */ diff --git a/src/com/android/settings/DataUsageUtils.java b/src/com/android/settings/DataUsageUtils.java new file mode 100644 index 0000000..5f08067 --- /dev/null +++ b/src/com/android/settings/DataUsageUtils.java @@ -0,0 +1,121 @@ +package com.android.settings; + + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.util.Log; + +import cyanogenmod.providers.DataUsageContract; + + +/** + * This class contains utility helper functions for accessing DataUsageProvider + */ +public class DataUsageUtils { + private static final String TAG = DataUsageUtils.class.getSimpleName(); + private static final int DATAUSAGE_SERVICE_ALARM_ID = 0x102030; + private static boolean DEBUG = true; + + public static void addApp(Context context, int uid, String label) { + if (DEBUG) { + Log.v(TAG, "addApp: uid:" + uid + " label:" + label); + } + + ContentValues values = new ContentValues(); + + values.put(DataUsageContract.UID, uid); + values.put(DataUsageContract.LABEL, label); + + context.getContentResolver().insert( + DataUsageContract.CONTENT_URI, + values + ); + } + + public static void removeApp(Context context, int uid) { + if (DEBUG) { + Log.v(TAG, "removeApp: uid:" + uid); + } + context.getContentResolver().delete( + DataUsageContract.CONTENT_URI, + DataUsageContract.UID + " = ? ", + new String [] { String.valueOf(uid)} + ); + } + + public static void enableApp(Context context, int uid, boolean enable) { + enableApp(context, uid, enable, null); + } + + public static void enableApp(Context context, int uid, boolean enable, String label) { + if (DEBUG) { + Log.v(TAG, "enableApp: uid:" + uid + " enable:" + enable + + (label == null ? "" : " label:" + label)); + } + ContentValues values = new ContentValues(); + + values.put(DataUsageContract.ENABLE, enable); + values.put(DataUsageContract.ACTIVE, 0); + if (label != null) { + values.put(DataUsageContract.LABEL, label); + } + context.getContentResolver().update( + DataUsageContract.CONTENT_URI, + values, + DataUsageContract.UID + " = ? ", + new String [] { String.valueOf(uid)} + ); + } + + public static boolean isDbEnabled(Context context) { + boolean dbEnabled = false; + Cursor cursor = context.getContentResolver().query( + DataUsageContract.CONTENT_URI, + null, + DataUsageContract.UID + " = ? ", + new String [] { String.valueOf("0") }, + null + ); + + if (cursor != null) { + cursor.close(); + dbEnabled = true; + } + return dbEnabled; + } + + + public static boolean isAppEnabled(Context context, int uid) { + boolean appEnabled = false; + Cursor cursor = context.getContentResolver().query( + DataUsageContract.CONTENT_URI, + null, + DataUsageContract.UID + " = ? ", + new String [] { String.valueOf(uid) }, + null + ); + if (cursor != null) { + if (cursor.moveToFirst()) { + appEnabled = cursor.getInt(DataUsageContract.COLUMN_OF_ENABLE) == 1; + } + cursor.close(); + } + + if (DEBUG) { + Log.v(TAG, "isAppEnabled: uid:" + uid + " enabled:" + appEnabled); + } + + return appEnabled; + } + + public static void enableDataUsageService(Context context, boolean enable) { + Intent intent = new Intent(); + intent.setAction("org.cyanogenmod.providers.datausage.enable"); + intent.putExtra("enable", enable); + context.sendBroadcast(intent); + } +} diff --git a/src/com/android/settings/net/UidDetail.java b/src/com/android/settings/DateChangeReceiver.java index 0b14254..6a3ec88 100644 --- a/src/com/android/settings/net/UidDetail.java +++ b/src/com/android/settings/DateChangeReceiver.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 The Android Open Source Project + * Copyright (C) 2016 The CyanogenMod Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,14 +14,15 @@ * limitations under the License. */ -package com.android.settings.net; +package com.android.settings; -import android.graphics.drawable.Drawable; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; -public class UidDetail { - public CharSequence label; - public CharSequence contentDescription; - public CharSequence[] detailLabels; - public CharSequence[] detailContentDescriptions; - public Drawable icon; +public class DateChangeReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + DateTimeSettings.updateLocaleStrings(); + } } diff --git a/src/com/android/settings/DateTimeSettings.java b/src/com/android/settings/DateTimeSettings.java index 8a9e2ad..34753ef 100644 --- a/src/com/android/settings/DateTimeSettings.java +++ b/src/com/android/settings/DateTimeSettings.java @@ -40,9 +40,12 @@ import android.widget.TimePicker; import com.android.internal.logging.MetricsLogger; import com.android.settingslib.datetime.ZoneGetter; +import libcore.icu.TimeZoneNames; +import java.text.DateFormatSymbols; import java.util.Calendar; import java.util.Date; +import java.util.Locale; public class DateTimeSettings extends SettingsPreferenceFragment implements OnSharedPreferenceChangeListener, @@ -329,6 +332,16 @@ public class DateTimeSettings extends SettingsPreferenceFragment if (when / 1000 < Integer.MAX_VALUE) { ((AlarmManager) context.getSystemService(Context.ALARM_SERVICE)).setTime(when); } + updateLocaleStrings(); + } + + static void updateLocaleStrings() { + // If a device was boot up with date 1970 and then date changes to some later time, + // the wrong string might be cached if the country's zones have changed. + // See external/icu/icu4c/source/data/misc/metaZones.txt for complete mapping + TimeZoneNames.clearLocaleCache(); + Locale locale = Locale.getDefault(); + DateFormatSymbols.getInstance(locale).setZoneStrings(TimeZoneNames.getZoneStrings(locale)); } /* package */ static void setTime(Context context, int hourOfDay, int minute) { diff --git a/src/com/android/settings/DefaultRingtonePreference.java b/src/com/android/settings/DefaultRingtonePreference.java index 4607bcd..4bf2cfa 100644 --- a/src/com/android/settings/DefaultRingtonePreference.java +++ b/src/com/android/settings/DefaultRingtonePreference.java @@ -44,12 +44,22 @@ public class DefaultRingtonePreference extends RingtonePreference { @Override protected void onSaveRingtone(Uri ringtoneUri) { - RingtoneManager.setActualDefaultRingtoneUri(getContext(), getRingtoneType(), ringtoneUri); + if (getRingtoneType() == RingtoneManager.TYPE_RINGTONE) { + RingtoneManager.setActualRingtoneUriBySubId(getContext(), + getSubId(), ringtoneUri); + } else { + RingtoneManager.setActualDefaultRingtoneUri(getContext(), + getRingtoneType(), ringtoneUri); + } } @Override protected Uri onRestoreRingtone() { - return RingtoneManager.getActualDefaultRingtoneUri(getContext(), getRingtoneType()); + if (getRingtoneType() == RingtoneManager.TYPE_RINGTONE) { + return RingtoneManager.getActualRingtoneUriBySubId(getContext(), getSubId()); + } else { + return RingtoneManager.getActualDefaultRingtoneUri(getContext(), getRingtoneType()); + } } - + } diff --git a/src/com/android/settings/DevelopmentSettings.java b/src/com/android/settings/DevelopmentSettings.java index 8bb99c53..b5b89cb 100644 --- a/src/com/android/settings/DevelopmentSettings.java +++ b/src/com/android/settings/DevelopmentSettings.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2008 The Android Open Source Project + * Copyright (C) 2013-2014 The CyanogenMod Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,6 +39,9 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.res.Resources; +import android.net.NetworkUtils; +import android.net.wifi.IWifiManager; +import android.net.wifi.WifiInfo; import android.hardware.usb.IUsbManager; import android.hardware.usb.UsbManager; import android.net.wifi.WifiManager; @@ -56,6 +60,7 @@ import android.os.UserManager; import android.preference.ListPreference; import android.preference.Preference; import android.preference.Preference.OnPreferenceChangeListener; +import android.preference.Preference.OnPreferenceClickListener; import android.preference.PreferenceGroup; import android.preference.PreferenceScreen; import android.preference.SwitchPreference; @@ -71,13 +76,17 @@ import android.view.ViewGroup; import android.view.accessibility.AccessibilityManager; import android.widget.Switch; import android.widget.TextView; +import android.widget.Toast; import com.android.internal.logging.MetricsLogger; +import com.android.settings.Settings.AppOpsSummaryActivity; import com.android.settings.fuelgauge.InactiveApps; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.Indexable; import com.android.settings.widget.SwitchBar; +import cyanogenmod.providers.CMSettings; +import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -88,7 +97,8 @@ import java.util.List; */ public class DevelopmentSettings extends SettingsPreferenceFragment implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener, - OnPreferenceChangeListener, SwitchBar.OnSwitchChangeListener, Indexable { + OnPreferenceChangeListener, SwitchBar.OnSwitchChangeListener, Indexable, + OnPreferenceClickListener { private static final String TAG = "DevelopmentSettings"; /** @@ -102,9 +112,11 @@ public class DevelopmentSettings extends SettingsPreferenceFragment public static final String PREF_SHOW = "show"; private static final String ENABLE_ADB = "enable_adb"; + private static final String ADB_NOTIFY = "adb_notify"; + private static final String ADB_TCPIP = "adb_over_network"; private static final String CLEAR_ADB_KEYS = "clear_adb_keys"; private static final String ENABLE_TERMINAL = "enable_terminal"; - private static final String KEEP_SCREEN_ON = "keep_screen_on"; + private static final String KEEP_SCREEN_ON_MODES = "keep_screen_on_modes"; private static final String BT_HCI_SNOOP_LOG = "bt_hci_snoop_log"; private static final String ENABLE_OEM_UNLOCK = "oem_unlock_enable"; private static final String HDCP_CHECKING_KEY = "hdcp_checking"; @@ -116,6 +128,7 @@ public class DevelopmentSettings extends SettingsPreferenceFragment 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 TUNER_UI_KEY = "tuner_ui"; + private static final String COLOR_TEMPERATURE_PROPERTY = "persist.sys.debug.color_temp"; private static final String DEBUG_APP_KEY = "debug_app"; private static final String WAIT_FOR_DEBUGGER_KEY = "wait_for_debugger"; @@ -157,21 +170,38 @@ public class DevelopmentSettings extends SettingsPreferenceFragment private static final String WIFI_LEGACY_DHCP_CLIENT_KEY = "legacy_dhcp_client"; private static final String MOBILE_DATA_ALWAYS_ON = "mobile_data_always_on"; private static final String KEY_COLOR_MODE = "color_mode"; + private static final String COLOR_TEMPERATURE_KEY = "color_temperature"; private static final String INACTIVE_APPS_KEY = "inactive_apps"; private static final String OPENGL_TRACES_KEY = "enable_opengl_traces"; + private static final String ROOT_ACCESS_KEY = "root_access"; + private static final String ROOT_ACCESS_PROPERTY = "persist.sys.root_access"; + + private static final String ROOT_APPOPS_KEY = "root_appops"; + + private static final String UPDATE_RECOVERY_KEY = "update_recovery"; + private static final String UPDATE_RECOVERY_PROPERTY = "persist.sys.recovery_update"; + 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 KILL_APP_LONGPRESS_BACK = "kill_app_longpress_back"; + private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive"; private static final String TERMINAL_APP_PACKAGE = "com.android.terminal"; + private static final String DEVELOPMENT_TOOLS = "development_tools"; + + private static final String ADVANCED_REBOOT_KEY = "advanced_reboot"; + + private static final String DEVELOPMENT_SHORTCUT_KEY = "development_shortcut"; + private static final int RESULT_DEBUG_APP = 1000; private static final int RESULT_MOCK_LOCATION_APP = 1001; @@ -184,6 +214,9 @@ public class DevelopmentSettings extends SettingsPreferenceFragment private static final int[] MOCK_LOCATION_APP_OPS = new int[] {AppOpsManager.OP_MOCK_LOCATION}; private static final String MULTI_WINDOW_SYSTEM_PROPERTY = "persist.sys.debug.multi_window"; + + private static final String SUPERUSER_BINARY_PATH = "/system/xbin/su"; + private IWindowManager mWindowManager; private IBackupManager mBackupManager; private DevicePolicyManager mDpm; @@ -196,11 +229,13 @@ public class DevelopmentSettings extends SettingsPreferenceFragment private boolean mDontPokeProperties; private SwitchPreference mEnableAdb; + private SwitchPreference mAdbNotify; + private SwitchPreference mAdbOverNetwork; private Preference mClearAdbKeys; private SwitchPreference mEnableTerminal; private Preference mBugreport; private SwitchPreference mBugreportInPower; - private SwitchPreference mKeepScreenOn; + private ListPreference mKeepScreenOn; private SwitchPreference mBtHciSnoopLog; private SwitchPreference mEnableOemUnlock; private SwitchPreference mDebugViewAttributes; @@ -239,9 +274,9 @@ public class DevelopmentSettings extends SettingsPreferenceFragment private ListPreference mUsbConfiguration; private ListPreference mTrackFrameTime; private ListPreference mShowNonRectClip; - private ListPreference mWindowAnimationScale; - private ListPreference mTransitionAnimationScale; - private ListPreference mAnimatorDurationScale; + private AnimationScalePreference mWindowAnimationScale; + private AnimationScalePreference mTransitionAnimationScale; + private AnimationScalePreference mAnimatorDurationScale; private ListPreference mOverlayDisplayDevices; private ListPreference mOpenGLTraces; @@ -253,9 +288,23 @@ public class DevelopmentSettings extends SettingsPreferenceFragment private ListPreference mAppProcessLimit; private SwitchPreference mShowAllANRs; + private SwitchPreference mKillAppLongpressBack; + private ListPreference mRootAccess; + private Object mSelectedRootValue; + private PreferenceScreen mDevelopmentTools; private ColorModePreference mColorModePreference; + private Preference mRootAppops; + + private SwitchPreference mAdvancedReboot; + + private SwitchPreference mUpdateRecovery; + + private SwitchPreference mDevelopmentShortcut; + + private SwitchPreference mColorTemperaturePreference; + private final ArrayList<Preference> mAllPrefs = new ArrayList<Preference>(); private final ArrayList<SwitchPreference> mResetSwitchPrefs @@ -266,9 +315,11 @@ public class DevelopmentSettings extends SettingsPreferenceFragment private boolean mDialogClicked; private Dialog mEnableDialog; private Dialog mAdbDialog; - + private Dialog mAdbTcpDialog; private Dialog mAdbKeysDialog; private boolean mUnavailable; + private Dialog mRootDialog; + private Dialog mUpdateRecoveryDialog; @Override protected int getMetricsCategory() { @@ -303,6 +354,11 @@ public class DevelopmentSettings extends SettingsPreferenceFragment final PreferenceGroup debugDebuggingCategory = (PreferenceGroup) findPreference(DEBUG_DEBUGGING_CATEGORY_KEY); mEnableAdb = findAndInitSwitchPref(ENABLE_ADB); + + mAdbNotify = (SwitchPreference) findPreference(ADB_NOTIFY); + mAllPrefs.add(mAdbNotify); + mAdbOverNetwork = findAndInitSwitchPref(ADB_TCPIP); + mClearAdbKeys = findPreference(CLEAR_ADB_KEYS); if (!SystemProperties.getBoolean("ro.adb.secure", false)) { if (debugDebuggingCategory != null) { @@ -318,7 +374,7 @@ public class DevelopmentSettings extends SettingsPreferenceFragment mBugreport = findPreference(BUGREPORT); mBugreportInPower = findAndInitSwitchPref(BUGREPORT_IN_POWER_KEY); - mKeepScreenOn = findAndInitSwitchPref(KEEP_SCREEN_ON); + mKeepScreenOn = addListPreference(KEEP_SCREEN_ON_MODES); mBtHciSnoopLog = findAndInitSwitchPref(BT_HCI_SNOOP_LOG); mEnableOemUnlock = findAndInitSwitchPref(ENABLE_OEM_UNLOCK); if (!showEnableOemUnlockPreference()) { @@ -329,6 +385,9 @@ public class DevelopmentSettings extends SettingsPreferenceFragment mDebugViewAttributes = findAndInitSwitchPref(DEBUG_VIEW_ATTRIBUTES); mPassword = (PreferenceScreen) findPreference(LOCAL_BACKUP_PASSWORD); mAllPrefs.add(mPassword); + mAdvancedReboot = findAndInitSwitchPref(ADVANCED_REBOOT_KEY); + mUpdateRecovery = findAndInitSwitchPref(UPDATE_RECOVERY_KEY); + mDevelopmentShortcut = findAndInitSwitchPref(DEVELOPMENT_SHORTCUT_KEY); if (!android.os.Process.myUserHandle().equals(UserHandle.OWNER)) { @@ -336,6 +395,9 @@ public class DevelopmentSettings extends SettingsPreferenceFragment disableForUser(mClearAdbKeys); disableForUser(mEnableTerminal); disableForUser(mPassword); + disableForUser(mAdvancedReboot); + disableForUser(mUpdateRecovery); + disableForUser(mDevelopmentShortcut); } mDebugAppPref = findPreference(DEBUG_APP_KEY); @@ -345,7 +407,8 @@ public class DevelopmentSettings extends SettingsPreferenceFragment mMockLocationAppPref = findPreference(MOCK_LOCATION_APP_KEY); mAllPrefs.add(mMockLocationAppPref); - mVerifyAppsOverUsb = findAndInitSwitchPref(VERIFY_APPS_OVER_USB_KEY); + mVerifyAppsOverUsb = (SwitchPreference) findPreference(VERIFY_APPS_OVER_USB_KEY); + mAllPrefs.add(mVerifyAppsOverUsb); if (!showVerifierSetting()) { if (debugDebuggingCategory != null) { debugDebuggingCategory.removePreference(mVerifyAppsOverUsb); @@ -377,9 +440,9 @@ public class DevelopmentSettings extends SettingsPreferenceFragment mLogdSize = addListPreference(SELECT_LOGD_SIZE_KEY); mUsbConfiguration = addListPreference(USB_CONFIGURATION_KEY); - mWindowAnimationScale = addListPreference(WINDOW_ANIMATION_SCALE_KEY); - mTransitionAnimationScale = addListPreference(TRANSITION_ANIMATION_SCALE_KEY); - mAnimatorDurationScale = addListPreference(ANIMATOR_DURATION_SCALE_KEY); + mWindowAnimationScale = findAndInitAnimationScalePreference(WINDOW_ANIMATION_SCALE_KEY); + mTransitionAnimationScale = findAndInitAnimationScalePreference(TRANSITION_ANIMATION_SCALE_KEY); + mAnimatorDurationScale = findAndInitAnimationScalePreference(ANIMATOR_DURATION_SCALE_KEY); mOverlayDisplayDevices = addListPreference(OVERLAY_DISPLAY_DEVICES_KEY); mEnableMultiWindow = findAndInitSwitchPref(ENABLE_MULTI_WINDOW_KEY); if (!showEnableMultiWindowPreference()) { @@ -409,18 +472,62 @@ public class DevelopmentSettings extends SettingsPreferenceFragment mAllPrefs.add(mShowAllANRs); mResetSwitchPrefs.add(mShowAllANRs); + mKillAppLongpressBack = findAndInitSwitchPref(KILL_APP_LONGPRESS_BACK); + Preference hdcpChecking = findPreference(HDCP_CHECKING_KEY); if (hdcpChecking != null) { mAllPrefs.add(hdcpChecking); removePreferenceForProduction(hdcpChecking); } + mRootAccess = (ListPreference) findPreference(ROOT_ACCESS_KEY); + mRootAccess.setOnPreferenceChangeListener(this); + + mRootAppops = (Preference) findPreference(ROOT_APPOPS_KEY); + mRootAppops.setOnPreferenceClickListener(this); + + if (!removeRootOptionsIfRequired()) { + if (isRootForAppsAvailable()) { + mRootAccess.setEntries(R.array.root_access_entries); + mRootAccess.setEntryValues(R.array.root_access_values); + } else { + mRootAccess.setEntries(R.array.root_access_entries_adb); + mRootAccess.setEntryValues(R.array.root_access_values_adb); + } + mAllPrefs.add(mRootAccess); + mAllPrefs.add(mRootAppops); + } + + mDevelopmentTools = (PreferenceScreen) findPreference(DEVELOPMENT_TOOLS); + if (Utils.updatePreferenceToSpecificActivityOrRemove(getActivity(), + getPreferenceScreen(), mDevelopmentTools.getKey(), 0)) { + mAllPrefs.add(mDevelopmentTools); + } + mColorModePreference = (ColorModePreference) findPreference(KEY_COLOR_MODE); mColorModePreference.updateCurrentAndSupported(); if (mColorModePreference.getTransformsCount() < 2) { removePreference(KEY_COLOR_MODE); mColorModePreference = null; } + + mColorTemperaturePreference = (SwitchPreference) findPreference(COLOR_TEMPERATURE_KEY); + if (getResources().getBoolean(R.bool.config_enableColorTemperature)) { + mAllPrefs.add(mColorTemperaturePreference); + mResetSwitchPrefs.add(mColorTemperaturePreference); + } else { + removePreference(COLOR_TEMPERATURE_KEY); + mColorTemperaturePreference = null; + } + + if (!getResources().getBoolean(R.bool.config_enableRecoveryUpdater)) { + removePreference(mUpdateRecovery); + mUpdateRecovery = null; + if (SystemProperties.getBoolean(UPDATE_RECOVERY_PROPERTY, false)) { + SystemProperties.set(UPDATE_RECOVERY_PROPERTY, "false"); + pokeSystemProperties(); + } + } } private ListPreference addListPreference(String prefKey) { @@ -437,6 +544,14 @@ public class DevelopmentSettings extends SettingsPreferenceFragment } } + private AnimationScalePreference findAndInitAnimationScalePreference(String key) { + AnimationScalePreference pref = (AnimationScalePreference) findPreference(key); + pref.setOnPreferenceChangeListener(this); + pref.setOnPreferenceClickListener(this); + mAllPrefs.add(pref); + return pref; + } + private SwitchPreference findAndInitSwitchPref(String key) { SwitchPreference pref = (SwitchPreference) findPreference(key); if (pref == null) { @@ -447,6 +562,18 @@ public class DevelopmentSettings extends SettingsPreferenceFragment return pref; } + private boolean removeRootOptionsIfRequired() { + // user builds don't get root, and eng always gets root + if (!(Build.IS_DEBUGGABLE || "eng".equals(Build.TYPE))) { + if (mRootAccess != null) { + getPreferenceScreen().removePreference(mRootAccess); + return true; + } + } + + return false; + } + @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); @@ -526,6 +653,7 @@ public class DevelopmentSettings extends SettingsPreferenceFragment setPrefsEnabledState(mLastEnabledState); } mSwitchBar.show(); + updateKillAppLongpressBackOptions(); if (mColorModePreference != null) { mColorModePreference.startListening(); @@ -573,6 +701,11 @@ public class DevelopmentSettings extends SettingsPreferenceFragment mHaveDebugSettings = false; updateSwitchPreference(mEnableAdb, Settings.Global.getInt(cr, Settings.Global.ADB_ENABLED, 0) != 0); + + mAdbNotify.setChecked(CMSettings.Secure.getInt(cr, + CMSettings.Secure.ADB_NOTIFY, 1) != 0); + updateAdbOverNetwork(); + if (mEnableTerminal != null) { updateSwitchPreference(mEnableTerminal, context.getPackageManager().getApplicationEnabledSetting(TERMINAL_APP_PACKAGE) @@ -580,8 +713,7 @@ public class DevelopmentSettings extends SettingsPreferenceFragment } updateSwitchPreference(mBugreportInPower, Settings.Secure.getInt(cr, Settings.Secure.BUGREPORT_IN_POWER_MENU, 0) != 0); - updateSwitchPreference(mKeepScreenOn, Settings.Global.getInt(cr, - Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0) != 0); + updateStayAwakeOptions(); updateSwitchPreference(mBtHciSnoopLog, Settings.Secure.getInt(cr, Settings.Secure.BLUETOOTH_HCI_LOG, 0) != 0); if (mEnableOemUnlock != null) { @@ -628,6 +760,70 @@ public class DevelopmentSettings extends SettingsPreferenceFragment updateMobileDataAlwaysOnOptions(); updateSimulateColorSpace(); updateUSBAudioOptions(); + updateRootAccessOptions(); + updateAdvancedRebootOptions(); + updateDevelopmentShortcutOptions(); + if (mUpdateRecovery != null) { + updateUpdateRecoveryOptions(); + } + if (mColorTemperaturePreference != null) { + updateColorTemperature(); + } + } + + private void writeAdvancedRebootOptions() { + CMSettings.Secure.putInt(getActivity().getContentResolver(), + CMSettings.Secure.ADVANCED_REBOOT, + mAdvancedReboot.isChecked() ? 1 : 0); + } + + private void updateAdvancedRebootOptions() { + mAdvancedReboot.setChecked(CMSettings.Secure.getInt(getActivity().getContentResolver(), + CMSettings.Secure.ADVANCED_REBOOT, 0) != 0); + } + + private void resetDevelopmentShortcutOptions() { + CMSettings.Secure.putInt(getActivity().getContentResolver(), + CMSettings.Secure.DEVELOPMENT_SHORTCUT, 0); + } + + private void writeDevelopmentShortcutOptions() { + CMSettings.Secure.putInt(getActivity().getContentResolver(), + CMSettings.Secure.DEVELOPMENT_SHORTCUT, + mDevelopmentShortcut.isChecked() ? 1 : 0); + } + + private void updateDevelopmentShortcutOptions() { + mDevelopmentShortcut.setChecked(CMSettings.Secure.getInt(getActivity().getContentResolver(), + CMSettings.Secure.DEVELOPMENT_SHORTCUT, 0) != 0); + } + + private void updateAdbOverNetwork() { + int port = CMSettings.Secure.getInt(getActivity().getContentResolver(), + CMSettings.Secure.ADB_PORT, 0); + boolean enabled = port > 0; + + updateSwitchPreference(mAdbOverNetwork, enabled); + + WifiInfo wifiInfo = null; + + if (enabled) { + IWifiManager wifiManager = IWifiManager.Stub.asInterface( + ServiceManager.getService(Context.WIFI_SERVICE)); + try { + wifiInfo = wifiManager.getConnectionInfo(); + } catch (RemoteException e) { + Log.e(TAG, "wifiManager, getConnectionInfo()", e); + } + } + + if (wifiInfo != null) { + String hostAddress = NetworkUtils.intToInetAddress( + wifiInfo.getIpAddress()).getHostAddress(); + mAdbOverNetwork.setSummary(hostAddress + ":" + String.valueOf(port)); + } else { + mAdbOverNetwork.setSummary(R.string.adb_over_network_summary); + } } private void resetDangerousOptions() { @@ -641,6 +837,13 @@ public class DevelopmentSettings extends SettingsPreferenceFragment } resetDebuggerOptions(); writeLogdSizeOption(null); + resetRootAccessOptions(); + resetAdbNotifyOptions(); + resetVerifyAppsOverUsbOptions(); + resetDevelopmentShortcutOptions(); + if (mUpdateRecovery != null) { + resetUpdateRecoveryOptions(); + } writeAnimationScaleOption(0, mWindowAnimationScale, null); writeAnimationScaleOption(1, mTransitionAnimationScale, null); writeAnimationScaleOption(2, mAnimatorDurationScale, null); @@ -656,6 +859,84 @@ public class DevelopmentSettings extends SettingsPreferenceFragment pokeSystemProperties(); } + private void updateRootAccessOptions() { + String value = SystemProperties.get(ROOT_ACCESS_PROPERTY, "0"); + mRootAccess.setValue(value); + mRootAccess.setSummary(getResources() + .getStringArray(R.array.root_access_entries)[Integer.valueOf(value)]); + + if (mRootAppops != null) { + mRootAppops.setEnabled(isRootForAppsEnabled()); + } + } + + private boolean isRootForAppsAvailable() { + boolean exists = false; + try { + File f = new File(SUPERUSER_BINARY_PATH); + exists = f.exists(); + } catch (SecurityException e) { + // Ignore + } + return exists; + } + + public static boolean isRootForAppsEnabled() { + int value = SystemProperties.getInt(ROOT_ACCESS_PROPERTY, 0); + boolean daemonState = + SystemProperties.get("init.svc.su_daemon", "absent").equals("running"); + return daemonState && (value == 1 || value == 3); + } + + private void writeRootAccessOptions(Object newValue) { + String oldValue = SystemProperties.get(ROOT_ACCESS_PROPERTY, "0"); + SystemProperties.set(ROOT_ACCESS_PROPERTY, newValue.toString()); + if (Integer.valueOf(newValue.toString()) < 2 && !oldValue.equals(newValue) + && "1".equals(SystemProperties.get("service.adb.root", "0"))) { + SystemProperties.set("service.adb.root", "0"); + Settings.Global.putInt(getActivity().getContentResolver(), + Settings.Global.ADB_ENABLED, 0); + Settings.Global.putInt(getActivity().getContentResolver(), + Settings.Global.ADB_ENABLED, 1); + } + updateRootAccessOptions(); + } + + private void resetRootAccessOptions() { + String oldValue = SystemProperties.get(ROOT_ACCESS_PROPERTY, "0"); + SystemProperties.set(ROOT_ACCESS_PROPERTY, "0"); + if (!oldValue.equals("0") && "1".equals(SystemProperties.get("service.adb.root", "0"))) { + SystemProperties.set("service.adb.root", "0"); + Settings.Global.putInt(getActivity().getContentResolver(), + Settings.Global.ADB_ENABLED, 0); + Settings.Global.putInt(getActivity().getContentResolver(), + Settings.Global.ADB_ENABLED, 1); + } + updateRootAccessOptions(); + } + + private void resetAdbNotifyOptions() { + CMSettings.Secure.putInt(getActivity().getContentResolver(), + CMSettings.Secure.ADB_NOTIFY, 1); + } + + private void updateStayAwakeOptions() { + int index = Settings.Global.getInt(getActivity().getContentResolver(), + Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0); + final String[] values = getResources().getStringArray(R.array.keep_screen_on_values); + final String[] summaries = getResources().getStringArray(R.array.keep_screen_on_titles); + // The old value contained 0 (disable) or 3 (BATTERY_PLUGGED_AC|BATTERY_PLUGGED_USB) + // Currently only have 3 values (0: Not enabled; 1: debugging over usb; >2: charging) + // NOTE: If we have newer values, then we need to migrate + // this property + if (index >= values.length) { + index = values.length - 1; + } + mKeepScreenOn.setValue(values[index]); + mKeepScreenOn.setSummary(summaries[index]); + mKeepScreenOn.setOnPreferenceChangeListener(this); + } + private void updateHdcpValues() { ListPreference hdcpChecking = (ListPreference) findPreference(HDCP_CHECKING_KEY); if (hdcpChecking != null) { @@ -675,8 +956,23 @@ public class DevelopmentSettings extends SettingsPreferenceFragment } } + private void writeKillAppLongpressBackOptions() { + CMSettings.Secure.putInt(getActivity().getContentResolver(), + CMSettings.Secure.KILL_APP_LONGPRESS_BACK, + mKillAppLongpressBack.isChecked() ? 1 : 0); + } + + private void updateKillAppLongpressBackOptions() { + mKillAppLongpressBack.setChecked(CMSettings.Secure.getInt( + getActivity().getContentResolver(), CMSettings.Secure.KILL_APP_LONGPRESS_BACK, 0) != 0); + } + private void updatePasswordSummary() { try { + if (mBackupManager == null) { + Log.e(TAG, "Backup Manager is unavailable!"); + return; + } if (mBackupManager.hasBackupPassword()) { mPassword.setSummary(R.string.local_backup_password_summary_change); } else { @@ -814,6 +1110,11 @@ public class DevelopmentSettings extends SettingsPreferenceFragment Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, mVerifyAppsOverUsb.isChecked() ? 1 : 0); } + private void resetVerifyAppsOverUsbOptions() { + Settings.Global.putInt(getActivity().getContentResolver(), + Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, 1); + } + private boolean enableVerifierSetting() { final ContentResolver cr = getActivity().getContentResolver(); if (Settings.Global.getInt(cr, Settings.Global.ADB_ENABLED, 0) == 0) { @@ -1052,6 +1353,13 @@ public class DevelopmentSettings extends SettingsPreferenceFragment mShowNonRectClip.setSummary(mShowNonRectClip.getEntries()[0]); } + private void writeStayAwakeOptions(Object newValue) { + int val = Integer.parseInt((String) newValue); + Settings.Global.putInt(getActivity().getContentResolver(), + Settings.Global.STAY_ON_WHILE_PLUGGED_IN, val); + updateStayAwakeOptions(); + } + private void writeShowNonRectClipOptions(Object newValue) { SystemProperties.set(HardwareRenderer.DEBUG_SHOW_NON_RECTANGULAR_CLIP_PROPERTY, newValue == null ? "" : newValue.toString()); @@ -1172,6 +1480,18 @@ public class DevelopmentSettings extends SettingsPreferenceFragment } } + private void updateColorTemperature() { + updateSwitchPreference(mColorTemperaturePreference, + SystemProperties.getBoolean(COLOR_TEMPERATURE_PROPERTY, false)); + } + + private void writeColorTemperature() { + SystemProperties.set(COLOR_TEMPERATURE_PROPERTY, + mColorTemperaturePreference.isChecked() ? "1" : "0"); + pokeSystemProperties(); + Toast.makeText(getActivity(), R.string.color_temperature_toast, Toast.LENGTH_LONG).show(); + } + private void updateUSBAudioOptions() { updateSwitchPreference(mUSBAudio, Settings.Secure.getInt(getContentResolver(), Settings.Secure.USB_AUDIO_AUTOMATIC_ROUTING_DISABLED, 0) != 0); @@ -1309,14 +1629,15 @@ public class DevelopmentSettings extends SettingsPreferenceFragment updateLogdSizeValues(); } - private void updateUsbConfigurationValues() { + private void updateUsbConfigurationValues(boolean isUnlocked) { if (mUsbConfiguration != null) { UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE); String[] values = getResources().getStringArray(R.array.usb_configuration_values); String[] titles = getResources().getStringArray(R.array.usb_configuration_titles); int index = 0; - for (int i = 0; i < titles.length; i++) { + // Assume if !isUnlocked -> charging, which should be at index 0 + for (int i = 0; i < titles.length && isUnlocked; i++) { if (manager.isFunctionEnabled(values[i])) { index = i; break; @@ -1331,10 +1652,11 @@ public class DevelopmentSettings extends SettingsPreferenceFragment private void writeUsbConfigurationOption(Object newValue) { UsbManager manager = (UsbManager)getActivity().getSystemService(Context.USB_SERVICE); String function = newValue.toString(); - manager.setCurrentFunction(function); if (function.equals("none")) { + manager.setCurrentFunction(null); manager.setUsbDataUnlocked(false); } else { + manager.setCurrentFunction(function); manager.setUsbDataUnlocked(true); } } @@ -1371,23 +1693,13 @@ public class DevelopmentSettings extends SettingsPreferenceFragment getActivity().getContentResolver(), Settings.Global.ALWAYS_FINISH_ACTIVITIES, 0) != 0); } - private void updateAnimationScaleValue(int which, ListPreference pref) { + private void updateAnimationScaleValue(int which, AnimationScalePreference pref) { try { float scale = mWindowManager.getAnimationScale(which); if (scale != 1) { mHaveDebugSettings = true; } - CharSequence[] values = pref.getEntryValues(); - for (int i=0; i<values.length; i++) { - float val = Float.parseFloat(values[i].toString()); - if (scale <= val) { - pref.setValueIndex(i); - pref.setSummary(pref.getEntries()[i]); - return; - } - } - pref.setValueIndex(values.length-1); - pref.setSummary(pref.getEntries()[0]); + pref.setScale(scale); } catch (RemoteException e) { } } @@ -1398,7 +1710,8 @@ public class DevelopmentSettings extends SettingsPreferenceFragment updateAnimationScaleValue(2, mAnimatorDurationScale); } - private void writeAnimationScaleOption(int which, ListPreference pref, Object newValue) { + private void writeAnimationScaleOption(int which, AnimationScalePreference pref, + Object newValue) { try { float scale = newValue != null ? Float.parseFloat(newValue.toString()) : 1; mWindowManager.setAnimationScale(which, scale); @@ -1498,10 +1811,12 @@ public class DevelopmentSettings extends SettingsPreferenceFragment } private void confirmEnableOemUnlock() { - DialogInterface.OnClickListener onConfirmListener = new DialogInterface.OnClickListener() { + DialogInterface.OnClickListener onEnableOemListener = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - Utils.setOemUnlockEnabled(getActivity(), true); + if (which == DialogInterface.BUTTON_POSITIVE) { + Utils.setOemUnlockEnabled(getActivity(), true); + } updateAllOptions(); } }; @@ -1509,8 +1824,9 @@ public class DevelopmentSettings extends SettingsPreferenceFragment new AlertDialog.Builder(getActivity()) .setTitle(R.string.confirm_enable_oem_unlock_title) .setMessage(R.string.confirm_enable_oem_unlock_text) - .setPositiveButton(R.string.enable_text, onConfirmListener) - .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(R.string.enable_text, onEnableOemListener) + .setNegativeButton(android.R.string.cancel, onEnableOemListener) + .setCancelable(false) .create() .show(); } @@ -1529,10 +1845,36 @@ public class DevelopmentSettings extends SettingsPreferenceFragment .setMessage(R.string.confirm_enable_multi_window_text) .setPositiveButton(R.string.enable_text, onConfirmListener) .setNegativeButton(android.R.string.cancel, onConfirmListener) + .setCancelable(false) .create() .show(); } + private void updateUpdateRecoveryOptions() { + updateSwitchPreference(mUpdateRecovery, SystemProperties.getBoolean( + UPDATE_RECOVERY_PROPERTY, false)); + } + + private void writeUpdateRecoveryOptions() { + SystemProperties.set(UPDATE_RECOVERY_PROPERTY, + mUpdateRecovery.isChecked() ? "true" : "false"); + pokeSystemProperties(); + } + + private static void resetUpdateRecoveryOptions() { + // User builds should update recovery by default + if ("user".equals(Build.TYPE)) { + SystemProperties.set(UPDATE_RECOVERY_PROPERTY, "true"); + } + } + + public static void initializeUpdateRecoveryOption(Context context) { + if (TextUtils.isEmpty(SystemProperties.get(UPDATE_RECOVERY_PROPERTY)) && + context.getResources().getBoolean(R.bool.config_enableRecoveryUpdater)) { + resetUpdateRecoveryOptions(); + } + } + @Override public void onSwitchChanged(Switch switchView, boolean isChecked) { if (switchView != mSwitchBar.getSwitch()) { @@ -1541,7 +1883,9 @@ public class DevelopmentSettings extends SettingsPreferenceFragment if (isChecked != mLastEnabledState) { if (isChecked) { mDialogClicked = false; - if (mEnableDialog != null) dismissDialogs(); + if (mEnableDialog != null) { + dismissDialogs(); + } mEnableDialog = new AlertDialog.Builder(getActivity()).setMessage( getActivity().getResources().getString( R.string.dev_settings_warning_message)) @@ -1556,6 +1900,11 @@ public class DevelopmentSettings extends SettingsPreferenceFragment Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0); mLastEnabledState = isChecked; setPrefsEnabledState(mLastEnabledState); + + // Hide development settings from the Settings menu (Android 4.2 behaviour) + getActivity().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE).edit() + .putBoolean(PREF_SHOW, false) + .apply(); } } } @@ -1588,6 +1937,23 @@ public class DevelopmentSettings extends SettingsPreferenceFragment } @Override + public boolean onPreferenceClick(Preference preference) { + if (preference == mWindowAnimationScale || + preference == mTransitionAnimationScale || + preference == mAnimatorDurationScale) { + ((AnimationScalePreference) preference).click(); + } else if (preference == mRootAppops) { + Activity mActivity = getActivity(); + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.putExtra("appops_tab", getString(R.string.app_ops_categories_su)); + intent.setClass(mActivity, AppOpsSummaryActivity.class); + mActivity.startActivity(intent); + return true; + } + return false; + } + + @Override public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { if (Utils.isMonkeyRunning()) { return false; @@ -1596,7 +1962,9 @@ public class DevelopmentSettings extends SettingsPreferenceFragment if (preference == mEnableAdb) { if (mEnableAdb.isChecked()) { mDialogClicked = false; - if (mAdbDialog != null) dismissDialogs(); + if (mAdbDialog != null) { + dismissDialogs(); + } mAdbDialog = new AlertDialog.Builder(getActivity()).setMessage( getActivity().getResources().getString(R.string.adb_warning_message)) .setTitle(R.string.adb_warning_title) @@ -1611,6 +1979,27 @@ public class DevelopmentSettings extends SettingsPreferenceFragment mVerifyAppsOverUsb.setChecked(false); updateBugreportOptions(); } + } else if (preference == mAdbNotify) { + CMSettings.Secure.putInt(getActivity().getContentResolver(), + CMSettings.Secure.ADB_NOTIFY, + mAdbNotify.isChecked() ? 1 : 0); + } else if (preference == mAdbOverNetwork) { + if (mAdbOverNetwork.isChecked()) { + if (mAdbTcpDialog != null) { + dismissDialogs(); + } + mAdbTcpDialog = new AlertDialog.Builder(getActivity()).setMessage( + getResources().getString(R.string.adb_over_network_warning)) + .setTitle(R.string.adb_over_network) + .setPositiveButton(android.R.string.yes, this) + .setNegativeButton(android.R.string.no, this) + .show(); + mAdbTcpDialog.setOnDismissListener(this); + } else { + CMSettings.Secure.putInt(getActivity().getContentResolver(), + CMSettings.Secure.ADB_PORT, -1); + updateAdbOverNetwork(); + } } else if (preference == mClearAdbKeys) { if (mAdbKeysDialog != null) dismissDialogs(); mAdbKeysDialog = new AlertDialog.Builder(getActivity()) @@ -1627,11 +2016,6 @@ public class DevelopmentSettings extends SettingsPreferenceFragment Settings.Secure.putInt(getActivity().getContentResolver(), Settings.Secure.BUGREPORT_IN_POWER_MENU, mBugreportInPower.isChecked() ? 1 : 0); - } else if (preference == mKeepScreenOn) { - Settings.Global.putInt(getActivity().getContentResolver(), - Settings.Global.STAY_ON_WHILE_PLUGGED_IN, - mKeepScreenOn.isChecked() ? - (BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB) : 0); } else if (preference == mBtHciSnoopLog) { writeBtHciSnoopLogOptions(); } else if (preference == mEnableOemUnlock) { @@ -1705,10 +2089,40 @@ public class DevelopmentSettings extends SettingsPreferenceFragment writeLegacyDhcpClientOptions(); } else if (preference == mMobileDataAlwaysOn) { writeMobileDataAlwaysOnOptions(); + } else if (preference == mColorTemperaturePreference) { + writeColorTemperature(); } else if (preference == mUSBAudio) { writeUSBAudioOptions(); + } else if (preference == mAdvancedReboot) { + writeAdvancedRebootOptions(); } else if (INACTIVE_APPS_KEY.equals(preference.getKey())) { startInactiveAppsFragment(); + } else if (preference == mDevelopmentShortcut) { + writeDevelopmentShortcutOptions(); + } else if (preference == mKillAppLongpressBack) { + writeKillAppLongpressBackOptions(); + } else if (preference == mUpdateRecovery) { + if (mSwitchBar.isChecked()) { + if (mUpdateRecoveryDialog != null) { + dismissDialogs(); + } + if (mUpdateRecovery.isChecked()) { + mUpdateRecoveryDialog = new AlertDialog.Builder(getActivity()).setMessage( + getResources().getString(R.string.update_recovery_on_warning)) + .setTitle(R.string.update_recovery_title) + .setPositiveButton(android.R.string.yes, this) + .setNegativeButton(android.R.string.no, this) + .show(); + } else { + mUpdateRecoveryDialog = new AlertDialog.Builder(getActivity()).setMessage( + getResources().getString(R.string.update_recovery_off_warning)) + .setTitle(R.string.update_recovery_title) + .setPositiveButton(android.R.string.yes, this) + .setNegativeButton(android.R.string.no, this) + .show(); + } + mUpdateRecoveryDialog.setOnDismissListener(this); + } } else { return super.onPreferenceTreeClick(preferenceScreen, preference); } @@ -1770,6 +2184,27 @@ public class DevelopmentSettings extends SettingsPreferenceFragment } else if (preference == mSimulateColorSpace) { writeSimulateColorSpace(newValue); return true; + } else if (preference == mRootAccess) { + if ("0".equals(SystemProperties.get(ROOT_ACCESS_PROPERTY, "0")) + && !"0".equals(newValue)) { + mSelectedRootValue = newValue; + mDialogClicked = false; + if (mRootDialog != null) { + dismissDialogs(); + } + mRootDialog = new AlertDialog.Builder(getActivity()) + .setMessage(getResources().getString(R.string.root_access_warning_message)) + .setTitle(R.string.root_access_warning_title) + .setPositiveButton(android.R.string.yes, this) + .setNegativeButton(android.R.string.no, this).show(); + mRootDialog.setOnDismissListener(this); + } else { + writeRootAccessOptions(newValue); + } + return true; + } else if (preference == mKeepScreenOn) { + writeStayAwakeOptions(newValue); + return true; } return false; } @@ -1779,6 +2214,10 @@ public class DevelopmentSettings extends SettingsPreferenceFragment mAdbDialog.dismiss(); mAdbDialog = null; } + if (mAdbTcpDialog != null) { + mAdbTcpDialog.dismiss(); + mAdbTcpDialog = null; + } if (mAdbKeysDialog != null) { mAdbKeysDialog.dismiss(); mAdbKeysDialog = null; @@ -1787,6 +2226,14 @@ public class DevelopmentSettings extends SettingsPreferenceFragment mEnableDialog.dismiss(); mEnableDialog = null; } + if (mRootDialog != null) { + mRootDialog.dismiss(); + mRootDialog = null; + } + if (mUpdateRecoveryDialog != null) { + mUpdateRecoveryDialog.dismiss(); + mUpdateRecoveryDialog = null; + } } public void onClick(DialogInterface dialog, int which) { @@ -1798,9 +2245,11 @@ public class DevelopmentSettings extends SettingsPreferenceFragment mVerifyAppsOverUsb.setEnabled(true); updateVerifyAppsOverUsbOptions(); updateBugreportOptions(); - } else { - // Reset the toggle - mEnableAdb.setChecked(false); + } + } else if (dialog == mAdbTcpDialog) { + if (which == DialogInterface.BUTTON_POSITIVE) { + CMSettings.Secure.putInt(getActivity().getContentResolver(), + CMSettings.Secure.ADB_PORT, 5555); } } else if (dialog == mAdbKeysDialog) { if (which == DialogInterface.BUTTON_POSITIVE) { @@ -1819,9 +2268,24 @@ public class DevelopmentSettings extends SettingsPreferenceFragment Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1); mLastEnabledState = true; setPrefsEnabledState(mLastEnabledState); + + // Make sure the development settings is visible in the main Settings menu + // This is needed since we may have just turned off dev settings and want to + // turn it on again + getActivity().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE).edit() + .putBoolean(PREF_SHOW, true) + .apply(); + } + } else if (dialog == mRootDialog) { + if (which == DialogInterface.BUTTON_POSITIVE) { + writeRootAccessOptions(mSelectedRootValue); } else { - // Reset the toggle - mSwitchBar.setChecked(false); + // Reset the option + writeRootAccessOptions("0"); + } + } else if (dialog == mUpdateRecoveryDialog) { + if (which == DialogInterface.BUTTON_POSITIVE) { + writeUpdateRecoveryOptions(); } } } @@ -1833,11 +2297,20 @@ public class DevelopmentSettings extends SettingsPreferenceFragment mEnableAdb.setChecked(false); } mAdbDialog = null; + } else if (dialog == mAdbTcpDialog) { + updateAdbOverNetwork(); + mAdbTcpDialog = null; } else if (dialog == mEnableDialog) { if (!mDialogClicked) { mSwitchBar.setChecked(false); } mEnableDialog = null; + } else if (dialog == mRootDialog) { + updateRootAccessOptions(); + mRootDialog = null; + } else if (dialog == mUpdateRecoveryDialog) { + updateUpdateRecoveryOptions(); + mUpdateRecoveryDialog = null; } } @@ -1857,7 +2330,8 @@ public class DevelopmentSettings extends SettingsPreferenceFragment private BroadcastReceiver mUsbReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - updateUsbConfigurationValues(); + boolean isUnlocked = intent.getBooleanExtra(UsbManager.USB_DATA_UNLOCKED, false); + updateUsbConfigurationValues(isUnlocked); } }; diff --git a/src/com/android/settings/DeviceAdminSettings.java b/src/com/android/settings/DeviceAdminSettings.java index f0b3070..1f05131 100644 --- a/src/com/android/settings/DeviceAdminSettings.java +++ b/src/com/android/settings/DeviceAdminSettings.java @@ -337,6 +337,7 @@ public class DeviceAdminSettings extends ListFragment { } vh.checkbox.setEnabled(enabled); vh.name.setEnabled(enabled); + vh.name.setSelected(true); vh.description.setEnabled(enabled); vh.icon.setEnabled(enabled); } diff --git a/src/com/android/settings/DeviceInfoSettings.java b/src/com/android/settings/DeviceInfoSettings.java index 8fc41ea..39eaa5a 100644 --- a/src/com/android/settings/DeviceInfoSettings.java +++ b/src/com/android/settings/DeviceInfoSettings.java @@ -17,10 +17,14 @@ package com.android.settings; import android.app.Activity; +import android.content.ClipData; +import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.os.Build; import android.os.Bundle; @@ -41,6 +45,7 @@ import android.text.format.DateFormat; import android.util.Log; import android.widget.Toast; +import cyanogenmod.hardware.CMHardwareManager; import com.android.internal.logging.MetricsLogger; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.Index; @@ -73,6 +78,7 @@ public class DeviceInfoSettings extends SettingsPreferenceFragment implements In private static final String KEY_KERNEL_VERSION = "kernel_version"; private static final String KEY_BUILD_NUMBER = "build_number"; private static final String KEY_DEVICE_MODEL = "device_model"; + private static final String KEY_DEVICE_NAME = "device_name"; private static final String KEY_SELINUX_STATUS = "selinux_status"; private static final String KEY_BASEBAND_VERSION = "baseband_version"; private static final String KEY_FIRMWARE_VERSION = "firmware_version"; @@ -82,12 +88,19 @@ public class DeviceInfoSettings extends SettingsPreferenceFragment implements In private static final String PROPERTY_EQUIPMENT_ID = "ro.ril.fccid"; private static final String KEY_DEVICE_FEEDBACK = "device_feedback"; private static final String KEY_SAFETY_LEGAL = "safetylegal"; + private static final String KEY_MOD_VERSION = "mod_version"; + private static final String KEY_MOD_BUILD_DATE = "build_date"; + private static final String KEY_MOD_API_LEVEL = "mod_api_level"; + private static final String KEY_CM_UPDATES = "cm_updates"; static final int TAPS_TO_BE_A_DEVELOPER = 7; + static final int TAPS_TO_SHOW_DEVICEID = 7; long[] mHits = new long[3]; int mDevHitCountdown; + int mDevIdCountdown; Toast mDevHitToast; + Toast mDevIdToast; @Override protected int getMetricsCategory() { @@ -123,12 +136,21 @@ public class DeviceInfoSettings extends SettingsPreferenceFragment implements In } setValueSummary(KEY_BASEBAND_VERSION, "gsm.version.baseband"); - setStringSummary(KEY_DEVICE_MODEL, Build.MODEL + getMsvSuffix()); setValueSummary(KEY_EQUIPMENT_ID, PROPERTY_EQUIPMENT_ID); setStringSummary(KEY_DEVICE_MODEL, Build.MODEL); setStringSummary(KEY_BUILD_NUMBER, Build.DISPLAY); findPreference(KEY_BUILD_NUMBER).setEnabled(true); - findPreference(KEY_KERNEL_VERSION).setSummary(getFormattedKernelVersion()); + + final Preference kernelPref = findPreference(KEY_KERNEL_VERSION); + kernelPref.setEnabled(true); + kernelPref.setSummary(getFormattedKernelVersion()); + findPreference(KEY_MOD_VERSION).setSummary( + cyanogenmod.os.Build.CYANOGENMOD_DISPLAY_VERSION); + findPreference(KEY_MOD_VERSION).setEnabled(true); + setValueSummary(KEY_MOD_BUILD_DATE, "ro.build.date"); + setExplicitValueSummary(KEY_MOD_API_LEVEL, constructApiLevelString()); + findPreference(KEY_MOD_API_LEVEL).setEnabled(true); + findPreference(KEY_MOD_BUILD_DATE).setEnabled(true); if (!SELinux.isSELinuxEnabled()) { String status = getResources().getString(R.string.selinux_status_disabled); @@ -138,10 +160,20 @@ public class DeviceInfoSettings extends SettingsPreferenceFragment implements In setStringSummary(KEY_SELINUX_STATUS, status); } + setStringSummary(KEY_DEVICE_NAME, Build.PRODUCT); + removePreferenceIfBoolFalse(KEY_DEVICE_NAME, R.bool.config_displayDeviceName); + // Remove selinux information if property is not present removePreferenceIfPropertyMissing(getPreferenceScreen(), KEY_SELINUX_STATUS, PROPERTY_SELINUX_STATUS); + // Only the owner should see the Updater settings, if it exists + if (UserHandle.myUserId() == UserHandle.USER_OWNER) { + removePreferenceIfPackageNotInstalled(findPreference(KEY_CM_UPDATES)); + } else { + getPreferenceScreen().removePreference(findPreference(KEY_CM_UPDATES)); + } + // Remove Safety information preference if PROPERTY_URL_SAFETYLEGAL is not set removePreferenceIfPropertyMissing(getPreferenceScreen(), KEY_SAFETY_LEGAL, PROPERTY_URL_SAFETYLEGAL); @@ -172,6 +204,9 @@ public class DeviceInfoSettings extends SettingsPreferenceFragment implements In Utils.updatePreferenceToSpecificActivityOrRemove(act, parentPreference, KEY_SYSTEM_UPDATE_SETTINGS, Utils.UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY); + /* Make sure the activity is provided by who we want... */ + if (findPreference(KEY_SYSTEM_UPDATE_SETTINGS) != null) + removePreferenceIfPackageNotInstalled(findPreference(KEY_SYSTEM_UPDATE_SETTINGS)); } else { // Remove for secondary users removePreference(KEY_SYSTEM_UPDATE_SETTINGS); @@ -184,7 +219,7 @@ public class DeviceInfoSettings extends SettingsPreferenceFragment implements In // Remove manual entry if none present. removePreferenceIfBoolFalse(KEY_MANUAL, R.bool.config_show_manual); - // Remove regulatory information if none present. + // Remove regulatory information if none present final Intent intent = new Intent(Settings.ACTION_SHOW_REGULATORY_INFO); if (getPackageManager().queryIntentActivities(intent, 0).isEmpty()) { Preference pref = findPreference(KEY_REGULATORY_INFO); @@ -201,6 +236,8 @@ public class DeviceInfoSettings extends SettingsPreferenceFragment implements In Context.MODE_PRIVATE).getBoolean(DevelopmentSettings.PREF_SHOW, android.os.Build.TYPE.equals("eng")) ? -1 : TAPS_TO_BE_A_DEVELOPER; mDevHitToast = null; + mDevIdCountdown = TAPS_TO_SHOW_DEVICEID; + mDevIdToast = null; } @Override @@ -224,6 +261,55 @@ public class DeviceInfoSettings extends SettingsPreferenceFragment implements In Log.e(LOG_TAG, "Unable to start activity " + intent.toString()); } } + } else if (preference.getKey().equals(KEY_MOD_BUILD_DATE)) { + System.arraycopy(mHits, 1, mHits, 0, mHits.length-1); + mHits[mHits.length-1] = SystemClock.uptimeMillis(); + if (mHits[0] >= (SystemClock.uptimeMillis()-500)) { + Intent intent = new Intent(); + intent.setClassName("com.android.systemui", + "com.android.systemui.tuner.TunerActivity$DemoModeActivity"); + try { + startActivity(intent); + } catch (Exception e) { + Log.e(LOG_TAG, "Unable to start activity " + intent.toString()); + } + } + } else if (preference.getKey().equals(KEY_KERNEL_VERSION)) { + + mDevIdCountdown --; + if (mDevIdCountdown == 0) { + final CMHardwareManager hwMgr = CMHardwareManager.getInstance(getActivity().getApplicationContext()); + final String deviceID = hwMgr.getUniqueDeviceId(); + CharSequence msg; + if (deviceID == null) { + msg = getText(R.string.show_device_id_failed_cm); + } + else { + final ClipboardManager clipboardMgr = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); + clipboardMgr.setPrimaryClip(ClipData.newPlainText(getResources(). + getString(R.string.show_device_id_clipboard_label), + deviceID)); + msg = getResources().getString( + R.string.show_device_id_copied_cm, deviceID); + } + + mDevIdToast = Toast.makeText(getActivity(), msg, + Toast.LENGTH_LONG); + mDevIdToast.show(); + mDevIdCountdown = TAPS_TO_SHOW_DEVICEID; + } + else if (mDevIdCountdown > 0 + && mDevIdCountdown < (TAPS_TO_SHOW_DEVICEID-2)) { + + if (mDevIdToast != null) { + mDevIdToast.cancel(); + } + mDevIdToast = Toast.makeText(getActivity(), getResources().getQuantityString( + R.plurals.show_device_id_countdown_cm, mDevIdCountdown, mDevIdCountdown), + Toast.LENGTH_SHORT); + mDevIdToast.show(); + } + } else if (preference.getKey().equals(KEY_BUILD_NUMBER)) { // Don't enable developer options for secondary users. if (UserHandle.myUserId() != UserHandle.USER_OWNER) return true; @@ -246,7 +332,7 @@ public class DeviceInfoSettings extends SettingsPreferenceFragment implements In if (mDevHitToast != null) { mDevHitToast.cancel(); } - mDevHitToast = Toast.makeText(getActivity(), R.string.show_dev_on, + mDevHitToast = Toast.makeText(getActivity(), R.string.show_dev_on_cm, Toast.LENGTH_LONG); mDevHitToast.show(); // This is good time to index the Developer Options @@ -260,7 +346,7 @@ public class DeviceInfoSettings extends SettingsPreferenceFragment implements In mDevHitToast.cancel(); } mDevHitToast = Toast.makeText(getActivity(), getResources().getQuantityString( - R.plurals.show_dev_countdown, mDevHitCountdown, mDevHitCountdown), + R.plurals.show_dev_countdown_cm, mDevHitCountdown, mDevHitCountdown), Toast.LENGTH_SHORT); mDevHitToast.show(); } @@ -268,7 +354,7 @@ public class DeviceInfoSettings extends SettingsPreferenceFragment implements In if (mDevHitToast != null) { mDevHitToast.cancel(); } - mDevHitToast = Toast.makeText(getActivity(), R.string.show_dev_already, + mDevHitToast = Toast.makeText(getActivity(), R.string.show_dev_already_cm, Toast.LENGTH_LONG); mDevHitToast.show(); } @@ -281,6 +367,20 @@ public class DeviceInfoSettings extends SettingsPreferenceFragment implements In if (b.getBoolean(CarrierConfigManager.KEY_CI_ACTION_ON_SYS_UPDATE_BOOL)) { ciActionOnSysUpdate(b); } + } else if (preference.getKey().equals(KEY_MOD_VERSION)) { + System.arraycopy(mHits, 1, mHits, 0, mHits.length-1); + mHits[mHits.length-1] = SystemClock.uptimeMillis(); + if (mHits[0] >= (SystemClock.uptimeMillis()-500)) { + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.putExtra("is_cm", true); + 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()); + } + } } return super.onPreferenceTreeClick(preferenceScreen, preference); } @@ -348,6 +448,14 @@ public class DeviceInfoSettings extends SettingsPreferenceFragment implements In } } + private void setExplicitValueSummary(String preference, String value) { + try { + findPreference(preference).setSummary(value); + } catch (RuntimeException e) { + // No recovery + } + } + private void sendFeedback() { String reporterPackage = getFeedbackReporterPackage(getActivity()); if (TextUtils.isEmpty(reporterPackage)) { @@ -386,6 +494,14 @@ public class DeviceInfoSettings extends SettingsPreferenceFragment implements In } } + private static String constructApiLevelString() { + int sdkInt = cyanogenmod.os.Build.CM_VERSION.SDK_INT; + StringBuilder builder = new StringBuilder(); + builder.append(cyanogenmod.os.Build.getNameForSDKInt(sdkInt)) + .append(" (" + sdkInt + ")"); + return builder.toString(); + } + public static String formatKernelVersion(String rawKernelVersion) { // Example (see tests for more): // Linux version 3.0.31-g6fb96c9 (android-build@xxx.xxx.xxx.xxx.com) \ @@ -519,5 +635,28 @@ public class DeviceInfoSettings extends SettingsPreferenceFragment implements In } }; + private boolean removePreferenceIfPackageNotInstalled(Preference preference) { + String intentUri=((PreferenceScreen) preference).getIntent().toUri(1); + Pattern pattern = Pattern.compile("component=([^/]+)/"); + Matcher matcher = pattern.matcher(intentUri); + + String packageName=matcher.find()?matcher.group(1):null; + if(packageName != null) { + try { + PackageInfo pi = getPackageManager().getPackageInfo(packageName, + PackageManager.GET_ACTIVITIES); + if (!pi.applicationInfo.enabled) { + Log.e(LOG_TAG,"package "+packageName+" is disabled, hiding preference."); + getPreferenceScreen().removePreference(preference); + return true; + } + } catch (NameNotFoundException e) { + Log.e(LOG_TAG,"package "+packageName+" not installed, hiding preference."); + getPreferenceScreen().removePreference(preference); + return true; + } + } + return false; + } } diff --git a/src/com/android/settings/Display.java b/src/com/android/settings/Display.java deleted file mode 100644 index fa29318..0000000 --- a/src/com/android/settings/Display.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * 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<String> adapter = new ArrayAdapter<String>(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 index de15d4c..47efd2f 100644 --- a/src/com/android/settings/DisplaySettings.java +++ b/src/com/android/settings/DisplaySettings.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2010 The Android Open Source Project + * Copyright (C) 2014 The CyanogenMod Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,14 +16,25 @@ */ package com.android.settings; - import com.android.internal.logging.MetricsLogger; + +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.preference.CheckBoxPreference; + +import android.os.UserHandle; +import android.view.Display; +import android.view.IWindowManager; +import android.view.WindowManager; +import android.view.WindowManagerGlobal; +import android.view.WindowManagerImpl; +import android.widget.Toast; import com.android.internal.view.RotationPolicy; import com.android.settings.DropDownPreference.Callback; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.Indexable; -import static android.provider.Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED; import static android.provider.Settings.Secure.CAMERA_GESTURE_DISABLED; import static android.provider.Settings.Secure.DOUBLE_TAP_TO_WAKE; import static android.provider.Settings.Secure.DOZE_ENABLED; @@ -35,6 +47,8 @@ import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT; import android.app.Activity; import android.app.ActivityManagerNative; import android.app.Dialog; +import android.app.IActivityManager; +import android.app.ProgressDialog; import android.app.UiModeManager; import android.app.admin.DevicePolicyManager; import android.content.ContentResolver; @@ -43,22 +57,36 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.hardware.Sensor; import android.hardware.SensorManager; +import android.os.AsyncTask; import android.os.Build; +import android.database.ContentObserver; import android.os.Bundle; +import android.os.Handler; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.SystemProperties; import android.preference.ListPreference; import android.preference.Preference; +import android.preference.PreferenceManager; import android.preference.Preference.OnPreferenceClickListener; +import android.preference.PreferenceCategory; +import android.preference.PreferenceManager; import android.preference.PreferenceScreen; import android.preference.SwitchPreference; import android.provider.SearchIndexableResource; import android.provider.Settings; import android.text.TextUtils; +import android.util.DisplayMetrics; import android.util.Log; import java.util.ArrayList; import java.util.List; +import com.android.settings.Utils; +import com.android.settings.cyanogenmod.DisplayRotation; + +import cyanogenmod.hardware.CMHardwareManager; +import cyanogenmod.hardware.LiveDisplayManager; +import cyanogenmod.providers.CMSettings; public class DisplaySettings extends SettingsPreferenceFragment implements Preference.OnPreferenceChangeListener, OnPreferenceClickListener, Indexable { @@ -67,7 +95,11 @@ public class DisplaySettings extends SettingsPreferenceFragment implements /** If there is no setting in the provider, use this. */ private static final int FALLBACK_SCREEN_TIMEOUT_VALUE = 30000; + private static final String KEY_CATEGORY_LIGHTS = "lights"; + private static final String KEY_CATEGORY_DISPLAY = "display"; + private static final String KEY_CATEGORY_INTERFACE = "interface"; private static final String KEY_SCREEN_TIMEOUT = "screen_timeout"; + private static final String KEY_LCD_DENSITY = "lcd_density"; private static final String KEY_FONT_SIZE = "font_size"; private static final String KEY_SCREEN_SAVER = "screensaver"; private static final String KEY_LIFT_TO_WAKE = "lift_to_wake"; @@ -77,24 +109,54 @@ public class DisplaySettings extends SettingsPreferenceFragment implements private static final String KEY_AUTO_ROTATE = "auto_rotate"; private static final String KEY_NIGHT_MODE = "night_mode"; private static final String KEY_CAMERA_GESTURE = "camera_gesture"; - private static final String KEY_CAMERA_DOUBLE_TAP_POWER_GESTURE - = "camera_double_tap_power_gesture"; + private static final String KEY_PROXIMITY_WAKE = "proximity_on_wake"; + private static final String KEY_DISPLAY_ROTATION = "display_rotation"; + private static final String KEY_WAKE_WHEN_PLUGGED_OR_UNPLUGGED = "wake_when_plugged_or_unplugged"; + private static final String KEY_NOTIFICATION_LIGHT = "notification_light"; + private static final String KEY_BATTERY_LIGHT = "battery_light"; + private static final String KEY_LIVEDISPLAY = "live_display"; + private static final String KEY_HIGH_TOUCH_SENSITIVITY = "high_touch_sensitivity"; private static final int DLG_GLOBAL_CHANGE_WARNING = 1; - private WarnedListPreference mFontSizePref; + private ListPreference mLcdDensityPreference; + private FontDialogPreference mFontSizePref; + private PreferenceScreen mDisplayRotationPreference; + private PreferenceScreen mLiveDisplayPreference; private final Configuration mCurConfig = new Configuration(); private ListPreference mScreenTimeoutPreference; private ListPreference mNightModePreference; private Preference mScreenSaverPreference; + private SwitchPreference mAccelerometer; private SwitchPreference mLiftToWakePreference; private SwitchPreference mDozePreference; private SwitchPreference mTapToWakePreference; + private SwitchPreference mHighTouchSensitivity; + private SwitchPreference mProximityCheckOnWakePreference; private SwitchPreference mAutoBrightnessPreference; + private SwitchPreference mWakeWhenPluggedOrUnplugged; + + private CMHardwareManager mHardware; + + private ContentObserver mAccelerometerRotationObserver = + new ContentObserver(new Handler()) { + @Override + public void onChange(boolean selfChange) { + updateDisplayRotationPreferenceDescription(); + updateAccelerometerRotationSwitch(); + } + }; + + private final RotationPolicy.RotationPolicyListener mRotationPolicyListener = + new RotationPolicy.RotationPolicyListener() { + @Override + public void onChange() { + updateDisplayRotationPreferenceDescription(); + } + }; private SwitchPreference mCameraGesturePreference; - private SwitchPreference mCameraDoubleTapPowerGesturePreference; @Override protected int getMetricsCategory() { @@ -106,14 +168,30 @@ public class DisplaySettings extends SettingsPreferenceFragment implements super.onCreate(savedInstanceState); final Activity activity = getActivity(); final ContentResolver resolver = activity.getContentResolver(); + addPreferencesFromResource(R.xml.display); + + mHardware = CMHardwareManager.getInstance(activity); + + PreferenceCategory displayPrefs = (PreferenceCategory) + findPreference(KEY_CATEGORY_DISPLAY); + PreferenceCategory interfacePrefs = (PreferenceCategory) + findPreference(KEY_CATEGORY_INTERFACE); + mDisplayRotationPreference = (PreferenceScreen) findPreference(KEY_DISPLAY_ROTATION); + mAccelerometer = (SwitchPreference) findPreference(DisplayRotation.KEY_ACCELEROMETER); + if (mAccelerometer != null) { + mAccelerometer.setPersistent(false); + } - addPreferencesFromResource(R.xml.display_settings); + mLiveDisplayPreference = (PreferenceScreen) findPreference(KEY_LIVEDISPLAY); + if (!LiveDisplayManager.getInstance(getActivity()).getConfig().isAvailable()) { + displayPrefs.removePreference(mLiveDisplayPreference); + } mScreenSaverPreference = findPreference(KEY_SCREEN_SAVER); if (mScreenSaverPreference != null && getResources().getBoolean( com.android.internal.R.bool.config_dreamsSupported) == false) { - getPreferenceScreen().removePreference(mScreenSaverPreference); + interfacePrefs.removePreference(mScreenSaverPreference); } mScreenTimeoutPreference = (ListPreference) findPreference(KEY_SCREEN_TIMEOUT); @@ -123,90 +201,87 @@ public class DisplaySettings extends SettingsPreferenceFragment implements mScreenTimeoutPreference.setOnPreferenceChangeListener(this); disableUnusableTimeouts(mScreenTimeoutPreference); updateTimeoutPreferenceDescription(currentTimeout); + updateDisplayRotationPreferenceDescription(); + + mLcdDensityPreference = (ListPreference) findPreference(KEY_LCD_DENSITY); + if (mLcdDensityPreference != null) { + if (UserHandle.myUserId() != UserHandle.USER_OWNER) { + interfacePrefs.removePreference(mLcdDensityPreference); + } else { + int defaultDensity = getDefaultDensity(); + int currentDensity = getCurrentDensity(); + if (currentDensity < 10 || currentDensity >= 1000) { + // Unsupported value, force default + currentDensity = defaultDensity; + } - mFontSizePref = (WarnedListPreference) findPreference(KEY_FONT_SIZE); + int factor = defaultDensity >= 480 ? 40 : 20; + int minimumDensity = defaultDensity - 4 * factor; + int currentIndex = -1; + String[] densityEntries = new String[7]; + String[] densityValues = new String[7]; + for (int idx = 0; idx < 7; ++idx) { + int val = minimumDensity + factor * idx; + int valueFormatResId = val == defaultDensity + ? R.string.lcd_density_default_value_format + : R.string.lcd_density_value_format; + + densityEntries[idx] = getString(valueFormatResId, val); + densityValues[idx] = Integer.toString(val); + if (currentDensity == val) { + currentIndex = idx; + } + } + mLcdDensityPreference.setEntries(densityEntries); + mLcdDensityPreference.setEntryValues(densityValues); + if (currentIndex != -1) { + mLcdDensityPreference.setValueIndex(currentIndex); + } + mLcdDensityPreference.setOnPreferenceChangeListener(this); + updateLcdDensityPreferenceDescription(currentDensity); + } + } + + mFontSizePref = (FontDialogPreference) findPreference(KEY_FONT_SIZE); mFontSizePref.setOnPreferenceChangeListener(this); mFontSizePref.setOnPreferenceClickListener(this); - if (isAutomaticBrightnessAvailable(getResources())) { - mAutoBrightnessPreference = (SwitchPreference) findPreference(KEY_AUTO_BRIGHTNESS); + mAutoBrightnessPreference = (SwitchPreference) findPreference(KEY_AUTO_BRIGHTNESS); + if (mAutoBrightnessPreference != null && isAutomaticBrightnessAvailable(getResources())) { mAutoBrightnessPreference.setOnPreferenceChangeListener(this); } else { - removePreference(KEY_AUTO_BRIGHTNESS); + if (displayPrefs != null && mAutoBrightnessPreference != null) { + displayPrefs.removePreference(mAutoBrightnessPreference); + mAutoBrightnessPreference = null; + } } - if (isLiftToWakeAvailable(activity)) { - mLiftToWakePreference = (SwitchPreference) findPreference(KEY_LIFT_TO_WAKE); + mLiftToWakePreference = (SwitchPreference) findPreference(KEY_LIFT_TO_WAKE); + if (mLiftToWakePreference != null && isLiftToWakeAvailable(activity)) { mLiftToWakePreference.setOnPreferenceChangeListener(this); } else { - removePreference(KEY_LIFT_TO_WAKE); + if (displayPrefs != null && mLiftToWakePreference != null) { + displayPrefs.removePreference(mLiftToWakePreference); + mLiftToWakePreference = null; + } } - if (isDozeAvailable(activity)) { - mDozePreference = (SwitchPreference) findPreference(KEY_DOZE); + mDozePreference = (SwitchPreference) findPreference(KEY_DOZE); + if (mDozePreference != null && Utils.isDozeAvailable(activity)) { mDozePreference.setOnPreferenceChangeListener(this); } else { - removePreference(KEY_DOZE); - } - - if (isTapToWakeAvailable(getResources())) { - mTapToWakePreference = (SwitchPreference) findPreference(KEY_TAP_TO_WAKE); - mTapToWakePreference.setOnPreferenceChangeListener(this); - } else { - removePreference(KEY_TAP_TO_WAKE); + if (displayPrefs != null && mDozePreference != null) { + displayPrefs.removePreference(mDozePreference); + } } - if (isCameraGestureAvailable(getResources())) { - mCameraGesturePreference = (SwitchPreference) findPreference(KEY_CAMERA_GESTURE); + mCameraGesturePreference = (SwitchPreference) findPreference(KEY_CAMERA_GESTURE); + if (mCameraGesturePreference != null && isCameraGestureAvailable(getResources())) { mCameraGesturePreference.setOnPreferenceChangeListener(this); } else { - removePreference(KEY_CAMERA_GESTURE); - } - - if (isCameraDoubleTapPowerGestureAvailable(getResources())) { - mCameraDoubleTapPowerGesturePreference - = (SwitchPreference) findPreference(KEY_CAMERA_DOUBLE_TAP_POWER_GESTURE); - mCameraDoubleTapPowerGesturePreference.setOnPreferenceChangeListener(this); - } else { - removePreference(KEY_CAMERA_DOUBLE_TAP_POWER_GESTURE); - } - - if (RotationPolicy.isRotationLockToggleVisible(activity)) { - DropDownPreference rotatePreference = - (DropDownPreference) findPreference(KEY_AUTO_ROTATE); - rotatePreference.addItem(activity.getString(R.string.display_auto_rotate_rotate), - false); - int rotateLockedResourceId; - // The following block sets the string used when rotation is locked. - // If the device locks specifically to portrait or landscape (rather than current - // rotation), then we use a different string to include this information. - if (allowAllRotations(activity)) { - rotateLockedResourceId = R.string.display_auto_rotate_stay_in_current; - } else { - if (RotationPolicy.getRotationLockOrientation(activity) - == Configuration.ORIENTATION_PORTRAIT) { - rotateLockedResourceId = - R.string.display_auto_rotate_stay_in_portrait; - } else { - rotateLockedResourceId = - R.string.display_auto_rotate_stay_in_landscape; - } + if (displayPrefs != null && mCameraGesturePreference != null) { + displayPrefs.removePreference(mCameraGesturePreference); } - rotatePreference.addItem(activity.getString(rotateLockedResourceId), true); - rotatePreference.setSelectedItem(RotationPolicy.isRotationLocked(activity) ? - 1 : 0); - rotatePreference.setCallback(new Callback() { - @Override - public boolean onItemSelected(int pos, Object value) { - final boolean locked = (Boolean) value; - MetricsLogger.action(getActivity(), MetricsLogger.ACTION_ROTATION_LOCK, - locked); - RotationPolicy.setRotationLock(activity, locked); - return true; - } - }); - } else { - removePreference(KEY_AUTO_ROTATE); } mNightModePreference = (ListPreference) findPreference(KEY_NIGHT_MODE); @@ -217,6 +292,67 @@ public class DisplaySettings extends SettingsPreferenceFragment implements mNightModePreference.setValue(String.valueOf(currentNightMode)); mNightModePreference.setOnPreferenceChangeListener(this); } + + mTapToWakePreference = (SwitchPreference) findPreference(KEY_TAP_TO_WAKE); + if (mTapToWakePreference != null && isTapToWakeAvailable(getResources())) { + mTapToWakePreference.setOnPreferenceChangeListener(this); + } else { + if (displayPrefs != null && mTapToWakePreference != null) { + displayPrefs.removePreference(mTapToWakePreference); + } + } + + mHighTouchSensitivity = (SwitchPreference) findPreference(KEY_HIGH_TOUCH_SENSITIVITY); + if (!mHardware.isSupported( + CMHardwareManager.FEATURE_HIGH_TOUCH_SENSITIVITY)) { + displayPrefs.removePreference(mHighTouchSensitivity); + mHighTouchSensitivity = null; + } else { + mHighTouchSensitivity.setChecked( + mHardware.get(CMHardwareManager.FEATURE_HIGH_TOUCH_SENSITIVITY)); + } + + mProximityCheckOnWakePreference = (SwitchPreference) findPreference(KEY_PROXIMITY_WAKE); + boolean proximityCheckOnWake = getResources().getBoolean( + org.cyanogenmod.platform.internal.R.bool.config_proximityCheckOnWake); + if (!proximityCheckOnWake) { + if (displayPrefs != null && mProximityCheckOnWakePreference != null) { + displayPrefs.removePreference(mProximityCheckOnWakePreference); + } + CMSettings.System.putInt(getContentResolver(), CMSettings.System.PROXIMITY_ON_WAKE, 0); + } else { + boolean proximityCheckOnWakeDefault = getResources().getBoolean( + org.cyanogenmod.platform.internal.R.bool.config_proximityCheckOnWakeEnabledByDefault); + mProximityCheckOnWakePreference.setChecked(CMSettings.System.getInt(getContentResolver(), + CMSettings.System.PROXIMITY_ON_WAKE, + (proximityCheckOnWakeDefault ? 1 : 0)) == 1); + } + + mWakeWhenPluggedOrUnplugged = + (SwitchPreference) findPreference(KEY_WAKE_WHEN_PLUGGED_OR_UNPLUGGED); + initPulse((PreferenceCategory) findPreference(KEY_CATEGORY_LIGHTS)); + } + + private int getDefaultDensity() { + IWindowManager wm = IWindowManager.Stub.asInterface(ServiceManager.checkService( + Context.WINDOW_SERVICE)); + try { + return wm.getInitialDisplayDensity(Display.DEFAULT_DISPLAY); + } catch (RemoteException e) { + e.printStackTrace(); + } + return DisplayMetrics.DENSITY_DEVICE; + } + + private int getCurrentDensity() { + IWindowManager wm = IWindowManager.Stub.asInterface(ServiceManager.checkService( + Context.WINDOW_SERVICE)); + try { + return wm.getBaseDisplayDensity(Display.DEFAULT_DISPLAY); + } catch (RemoteException e) { + e.printStackTrace(); + } + return DisplayMetrics.DENSITY_DEVICE; } private static boolean allowAllRotations(Context context) { @@ -229,15 +365,6 @@ public class DisplaySettings extends SettingsPreferenceFragment implements return sensors != null && sensors.getDefaultSensor(Sensor.TYPE_WAKE_GESTURE) != null; } - private static boolean isDozeAvailable(Context context) { - String name = Build.IS_DEBUGGABLE ? SystemProperties.get("debug.doze.component") : null; - if (TextUtils.isEmpty(name)) { - name = context.getResources().getString( - com.android.internal.R.string.config_dozeComponent); - } - return !TextUtils.isEmpty(name); - } - private static boolean isTapToWakeAvailable(Resources res) { return res.getBoolean(com.android.internal.R.bool.config_supportDoubleTapWake); } @@ -246,6 +373,59 @@ public class DisplaySettings extends SettingsPreferenceFragment implements return res.getBoolean(com.android.internal.R.bool.config_automatic_brightness_available); } + private void updateAccelerometerRotationSwitch() { + if (mAccelerometer != null) { + mAccelerometer.setChecked(!RotationPolicy.isRotationLocked(getActivity())); + } + } + private void updateDisplayRotationPreferenceDescription() { + if (mDisplayRotationPreference == null) { + // The preference was removed, do nothing + return; + } + + // We have a preference, lets update the summary + boolean rotationEnabled = Settings.System.getInt(getContentResolver(), + Settings.System.ACCELEROMETER_ROTATION, 0) != 0; + + if (!rotationEnabled) { + mDisplayRotationPreference.setSummary(R.string.display_rotation_disabled); + return; + } + + StringBuilder summary = new StringBuilder(); + int mode = Settings.System.getInt(getContentResolver(), + Settings.System.ACCELEROMETER_ROTATION_ANGLES, + DisplayRotation.ROTATION_0_MODE + | DisplayRotation.ROTATION_90_MODE + | DisplayRotation.ROTATION_270_MODE); + ArrayList<String> rotationList = new ArrayList<String>(); + String delim = ""; + + if ((mode & DisplayRotation.ROTATION_0_MODE) != 0) { + rotationList.add("0"); + } + if ((mode & DisplayRotation.ROTATION_90_MODE) != 0) { + rotationList.add("90"); + } + if ((mode & DisplayRotation.ROTATION_180_MODE) != 0) { + rotationList.add("180"); + } + if ((mode & DisplayRotation.ROTATION_270_MODE) != 0) { + rotationList.add("270"); + } + for (int i = 0; i < rotationList.size(); i++) { + summary.append(delim).append(rotationList.get(i)); + if ((rotationList.size() - i) > 2) { + delim = ", "; + } else { + delim = " & "; + } + } + summary.append(" " + getString(R.string.display_rotation_unit)); + mDisplayRotationPreference.setSummary(summary); + } + private static boolean isCameraGestureAvailable(Resources res) { boolean configSet = res.getInteger( com.android.internal.R.integer.config_cameraLaunchGestureSensorType) != -1; @@ -253,11 +433,6 @@ public class DisplaySettings extends SettingsPreferenceFragment implements !SystemProperties.getBoolean("gesture.disable_camera_launch", false); } - private static boolean isCameraDoubleTapPowerGestureAvailable(Resources res) { - return res.getBoolean( - com.android.internal.R.bool.config_cameraDoubleTapPowerGestureEnabled); - } - private void updateTimeoutPreferenceDescription(long currentTimeout) { ListPreference preference = mScreenTimeoutPreference; String summary; @@ -284,6 +459,12 @@ public class DisplaySettings extends SettingsPreferenceFragment implements preference.setSummary(summary); } + private void updateLcdDensityPreferenceDescription(int currentDensity) { + final int summaryResId = currentDensity == getDefaultDensity() + ? R.string.lcd_density_default_value_format : R.string.lcd_density_value_format; + mLcdDensityPreference.setSummary(getString(summaryResId, currentDensity)); + } + private void disableUnusableTimeouts(ListPreference screenTimeoutPreference) { final DevicePolicyManager dpm = (DevicePolicyManager) getActivity().getSystemService( @@ -325,41 +506,44 @@ public class DisplaySettings extends SettingsPreferenceFragment implements 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<indices.length; i++) { - float thisVal = Float.parseFloat(indices[i]); - if (val < (lastVal + (thisVal-lastVal)*.5f)) { - return i-1; - } - lastVal = thisVal; - } - return indices.length-1; - } + @Override + public void onResume() { + super.onResume(); + updateDisplayRotationPreferenceDescription(); - public void readFontSizePreference(ListPreference pref) { - try { - mCurConfig.updateFrom(ActivityManagerNative.getDefault().getConfiguration()); - } catch (RemoteException e) { - Log.w(TAG, "Unable to retrieve font size"); - } + RotationPolicy.registerRotationPolicyListener(getActivity(), + mRotationPolicyListener); - // mark the appropriate item in the preferences list - int index = floatToIndex(mCurConfig.fontScale); - pref.setValueIndex(index); + final ContentResolver resolver = getContentResolver(); - // report the current size in the summary text - final Resources res = getResources(); - String[] fontSizeNames = res.getStringArray(R.array.entries_font_size); - pref.setSummary(String.format(res.getString(R.string.summary_font_size), - fontSizeNames[index])); + // Display rotation observer + resolver.registerContentObserver( + Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), true, + mAccelerometerRotationObserver); + + // Default value for wake-on-plug behavior from config.xml + boolean wakeUpWhenPluggedOrUnpluggedConfig = getResources().getBoolean( + com.android.internal.R.bool.config_unplugTurnsOnScreen); + + if (mWakeWhenPluggedOrUnplugged != null) { + mWakeWhenPluggedOrUnplugged.setChecked(CMSettings.Global.getInt(getContentResolver(), + CMSettings.Global.WAKE_WHEN_PLUGGED_OR_UNPLUGGED, + (wakeUpWhenPluggedOrUnpluggedConfig ? 1 : 0)) == 1); + } + + updateState(); + updateAccelerometerRotationSwitch(); } @Override - public void onResume() { - super.onResume(); - updateState(); + public void onPause() { + super.onPause(); + + RotationPolicy.unregisterRotationPolicyListener(getActivity(), + mRotationPolicyListener); + + // Display rotation observer + getContentResolver().unregisterContentObserver(mAccelerometerRotationObserver); } @Override @@ -410,13 +594,6 @@ public class DisplaySettings extends SettingsPreferenceFragment implements int value = Settings.Secure.getInt(getContentResolver(), CAMERA_GESTURE_DISABLED, 0); mCameraGesturePreference.setChecked(value == 0); } - - // Update camera gesture #2 if it is available. - if (mCameraDoubleTapPowerGesturePreference != null) { - int value = Settings.Secure.getInt( - getContentResolver(), CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, 0); - mCameraDoubleTapPowerGesturePreference.setChecked(value == 0); - } } private void updateScreenSaverSummary() { @@ -426,6 +603,79 @@ public class DisplaySettings extends SettingsPreferenceFragment implements } } + private void writeLcdDensityPreference(final Context context, final int density) { + final IActivityManager am = ActivityManagerNative.asInterface( + ServiceManager.checkService("activity")); + final IWindowManager wm = IWindowManager.Stub.asInterface(ServiceManager.checkService( + Context.WINDOW_SERVICE)); + AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() { + @Override + protected void onPreExecute() { + ProgressDialog dialog = new ProgressDialog(context); + dialog.setMessage(getResources().getString(R.string.restarting_ui)); + dialog.setCancelable(false); + dialog.setIndeterminate(true); + dialog.show(); + } + @Override + protected Void doInBackground(Void... params) { + // Give the user a second to see the dialog + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // Ignore + } + + try { + wm.setForcedDisplayDensity(Display.DEFAULT_DISPLAY, density); + } catch (RemoteException e) { + Log.e(TAG, "Failed to set density to " + density, e); + } + + // Restart the UI + try { + am.restart(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to restart"); + } + return null; + } + }; + task.execute(); + } + + // === Pulse notification light === + + private void initPulse(PreferenceCategory parent) { + if (!getResources().getBoolean( + com.android.internal.R.bool.config_intrusiveNotificationLed)) { + parent.removePreference(parent.findPreference(KEY_NOTIFICATION_LIGHT)); + } + if (!getResources().getBoolean( + com.android.internal.R.bool.config_intrusiveBatteryLed) + || UserHandle.myUserId() != UserHandle.USER_OWNER) { + parent.removePreference(parent.findPreference(KEY_BATTERY_LIGHT)); + } + if (parent.getPreferenceCount() == 0) { + getPreferenceScreen().removePreference(parent); + } + } + /** + * Reads the current font size and sets the value in the summary text + */ + public void readFontSizePreference(Preference pref) { + try { + mCurConfig.updateFrom(ActivityManagerNative.getDefault().getConfiguration()); + } catch (RemoteException e) { + Log.w(TAG, "Unable to retrieve font size"); + } + + // report the current size in the summary text + final Resources res = getResources(); + String fontDesc = FontDialogPreference.getFontSizeDescription(res, mCurConfig.fontScale); + pref.setSummary(getString(R.string.summary_font_size, fontDesc)); + } + public void writeFontSizePreference(Object objValue) { try { mCurConfig.fontScale = Float.parseFloat(objValue.toString()); @@ -437,6 +687,22 @@ public class DisplaySettings extends SettingsPreferenceFragment implements @Override public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { + if (preference == mWakeWhenPluggedOrUnplugged) { + CMSettings.Global.putInt(getContentResolver(), + CMSettings.Global.WAKE_WHEN_PLUGGED_OR_UNPLUGGED, + mWakeWhenPluggedOrUnplugged.isChecked() ? 1 : 0); + return true; + } else if (preference == mAccelerometer) { + RotationPolicy.setRotationLockForAccessibility(getActivity(), + !mAccelerometer.isChecked()); + } else if (preference == mHighTouchSensitivity) { + boolean mHighTouchSensitivityEnable = mHighTouchSensitivity.isChecked(); + CMSettings.System.putInt(getActivity().getContentResolver(), + CMSettings.System.HIGH_TOUCH_SENSITIVITY_ENABLE, + mHighTouchSensitivityEnable ? 1 : 0); + return true; + } + return super.onPreferenceTreeClick(preferenceScreen, preference); } @@ -452,6 +718,14 @@ public class DisplaySettings extends SettingsPreferenceFragment implements Log.e(TAG, "could not persist screen timeout setting", e); } } + if (KEY_LCD_DENSITY.equals(key)) { + String newValue = (String) objValue; + String oldValue = mLcdDensityPreference.getValue(); + if (!TextUtils.equals(newValue, oldValue)) { + showLcdConfirmationDialog((String) objValue); + } + return false; + } if (KEY_FONT_SIZE.equals(key)) { writeFontSizePreference(objValue); } @@ -477,11 +751,6 @@ public class DisplaySettings extends SettingsPreferenceFragment implements Settings.Secure.putInt(getContentResolver(), CAMERA_GESTURE_DISABLED, value ? 0 : 1 /* Backwards because setting is for disabling */); } - if (preference == mCameraDoubleTapPowerGesturePreference) { - boolean value = (Boolean) objValue; - Settings.Secure.putInt(getContentResolver(), CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, - value ? 0 : 1 /* Backwards because setting is for disabling */); - } if (preference == mNightModePreference) { try { final int value = Integer.parseInt((String) objValue); @@ -495,6 +764,26 @@ public class DisplaySettings extends SettingsPreferenceFragment implements return true; } + private void showLcdConfirmationDialog(final String lcdDensity) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setTitle(R.string.lcd_density); + builder.setMessage(R.string.lcd_density_prompt_message); + builder.setPositiveButton(R.string.print_restart, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + try { + int value = Integer.parseInt(lcdDensity); + writeLcdDensityPreference(getActivity(), value); + updateLcdDensityPreferenceDescription(value); + } catch (NumberFormatException e) { + Log.e(TAG, "could not persist display density setting", e); + } + } + }); + builder.setNegativeButton(android.R.string.cancel, null); + builder.show(); + } + @Override public boolean onPreferenceClick(Preference preference) { if (preference == mFontSizePref) { @@ -515,6 +804,7 @@ public class DisplaySettings extends SettingsPreferenceFragment implements public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new BaseSearchIndexProvider() { + @Override public List<SearchIndexableResource> getXmlResourcesToIndex(Context context, boolean enabled) { @@ -522,7 +812,7 @@ public class DisplaySettings extends SettingsPreferenceFragment implements new ArrayList<SearchIndexableResource>(); SearchIndexableResource sir = new SearchIndexableResource(context); - sir.xmlResId = R.xml.display_settings; + sir.xmlResId = R.xml.display; result.add(sir); return result; @@ -530,33 +820,57 @@ public class DisplaySettings extends SettingsPreferenceFragment implements @Override public List<String> getNonIndexableKeys(Context context) { + final CMHardwareManager hardware = CMHardwareManager.getInstance(context); + ArrayList<String> result = new ArrayList<String>(); if (!context.getResources().getBoolean( com.android.internal.R.bool.config_dreamsSupported)) { result.add(KEY_SCREEN_SAVER); } + if (!context.getResources().getBoolean( + com.android.internal.R.bool.config_intrusiveNotificationLed)) { + result.add(KEY_NOTIFICATION_LIGHT); + } + if (!context.getResources().getBoolean( + com.android.internal.R.bool.config_intrusiveBatteryLed)) { + result.add(KEY_BATTERY_LIGHT); + } if (!isAutomaticBrightnessAvailable(context.getResources())) { result.add(KEY_AUTO_BRIGHTNESS); } if (!isLiftToWakeAvailable(context)) { result.add(KEY_LIFT_TO_WAKE); } - if (!isDozeAvailable(context)) { + if (!Utils.isDozeAvailable(context)) { result.add(KEY_DOZE); } - if (!RotationPolicy.isRotationLockToggleVisible(context)) { - result.add(KEY_AUTO_ROTATE); - } if (!isTapToWakeAvailable(context.getResources())) { result.add(KEY_TAP_TO_WAKE); } + if (!context.getResources().getBoolean( + org.cyanogenmod.platform.internal.R.bool.config_proximityCheckOnWake)) { + result.add(KEY_PROXIMITY_WAKE); + } if (!isCameraGestureAvailable(context.getResources())) { result.add(KEY_CAMERA_GESTURE); } - if (!isCameraDoubleTapPowerGestureAvailable(context.getResources())) { - result.add(KEY_CAMERA_DOUBLE_TAP_POWER_GESTURE); + if (!hardware.isSupported(CMHardwareManager.FEATURE_HIGH_TOUCH_SENSITIVITY)) { + result.add(KEY_HIGH_TOUCH_SENSITIVITY); } return result; } }; + + public static void restore(Context context) { + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + final CMHardwareManager hardware = CMHardwareManager.getInstance(context); + if (hardware.isSupported(CMHardwareManager.FEATURE_HIGH_TOUCH_SENSITIVITY)) { + final boolean enabled = prefs.getBoolean(KEY_HIGH_TOUCH_SENSITIVITY, + hardware.get(CMHardwareManager.FEATURE_HIGH_TOUCH_SENSITIVITY)); + CMSettings.System.putInt(context.getContentResolver(), + CMSettings.System.HIGH_TOUCH_SENSITIVITY_ENABLE, + enabled ? 1 : 0); + } + } + } diff --git a/src/com/android/settings/DraggableSortListView.java b/src/com/android/settings/DraggableSortListView.java new file mode 100644 index 0000000..ba0ddc5 --- /dev/null +++ b/src/com/android/settings/DraggableSortListView.java @@ -0,0 +1,361 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.PixelFormat; +import android.graphics.Rect; + +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.AdapterView; +import android.widget.ImageView; +import android.widget.ListView; + +/** + * A draggable/sortable listview + */ +public class DraggableSortListView extends ListView { + + public interface DragListener { + void drag(int from, int to); + } + + public interface DropListener { + void drop(int from, int to); + } + + private ImageView mDragView; + private WindowManager mWindowManager; + private WindowManager.LayoutParams mWindowParams; + private int mDragPos; + private int mFirstDragPos; + private int mDragPoint; + private int mCoordOffset; + private DragListener mDragListener; + private DropListener mDropListener; + private int mUpperBound; + private int mLowerBound; + private int mHeight; + private Rect mTempRect = new Rect(); + private Bitmap mDragBitmap; + private final int mTouchSlop; + private int mItemHeight; + + public DraggableSortListView(Context context) { + super(context); + mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if ((mDragListener != null || mDropListener != null) && getChildCount() > 1) { + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: + int x = (int) ev.getX(); + int y = (int) ev.getY(); + int itemnum = pointToPosition(x, y); + if (itemnum == AdapterView.INVALID_POSITION) { + break; + } + ViewGroup item = (ViewGroup) getChildAt(itemnum - getFirstVisiblePosition()); + mItemHeight = item.getHeight(); + mDragPoint = y - item.getTop(); + mCoordOffset = ((int) ev.getRawY()) - y; + View dragger = item.findViewById(com.android.internal.R.id.icon); + + // The dragger icon itself is quite small, so pretend the + // touch area is bigger + int x1 = item.getLeft() + dragger.getLeft() - (dragger.getWidth() / 2); + int x2 = item.getLeft() + dragger.getRight() + (dragger.getWidth() / 2); + if (x > x1 && x < x2) { + // Fix x position while dragging + int[] itemPos = new int[2]; + item.getLocationOnScreen(itemPos); + + item.setDrawingCacheEnabled(true); + // Create a copy of the drawing cache so that it does + // not get recycled + // by the framework when the list tries to clean up + // memory + Bitmap bitmap = Bitmap.createBitmap(item.getDrawingCache()); + startDragging(bitmap, itemPos[0], y); + mDragPos = itemnum; + mFirstDragPos = mDragPos; + mHeight = getHeight(); + int touchSlop = mTouchSlop; + mUpperBound = Math.min(y - touchSlop, mHeight / 3); + mLowerBound = Math.max(y + touchSlop, mHeight * 2 / 3); + return false; + } + stopDragging(); + break; + } + } + return super.onInterceptTouchEvent(ev); + } + + /* + * pointToPosition() doesn't consider invisible views, but we need to, so + * implement a slightly different version. + */ + private int myPointToPosition(int x, int y) { + + if (y < 0) { + // when dragging off the top of the screen, calculate position + // by going back from a visible item + int pos = myPointToPosition(x, y + mItemHeight); + if (pos > 0) { + return pos - 1; + } + } + + Rect frame = mTempRect; + final int count = getChildCount(); + for (int i = count - 1; i >= 0; i--) { + final View child = getChildAt(i); + child.getHitRect(frame); + if (frame.contains(x, y)) { + return getFirstVisiblePosition() + i; + } + } + return INVALID_POSITION; + } + + private int getItemForPosition(int y) { + int adjustedy = y - mDragPoint - (mItemHeight / 2); + int pos = myPointToPosition(0, adjustedy); + if (pos >= 0) { + if (pos <= mFirstDragPos) { + pos += 1; + } + } else if (adjustedy < 0) { + // this shouldn't happen anymore now that myPointToPosition deals + // with this situation + pos = 0; + } + return pos; + } + + private void adjustScrollBounds(int y) { + if (y >= mHeight / 3) { + mUpperBound = mHeight / 3; + } + if (y <= mHeight * 2 / 3) { + mLowerBound = mHeight * 2 / 3; + } + } + + /* + * Restore size and visibility for all listitems + */ + private void unExpandViews(boolean deletion) { + for (int i = 0;; i++) { + View v = getChildAt(i); + if (v == null) { + if (deletion) { + // HACK force update of mItemCount + int position = getFirstVisiblePosition(); + int y = getChildAt(0).getTop(); + setAdapter(getAdapter()); + setSelectionFromTop(position, y); + // end hack + } + layoutChildren(); // force children to be recreated where needed + v = getChildAt(i); + if (v == null) { + break; + } + } + ViewGroup.LayoutParams params = v.getLayoutParams(); + params.height = mItemHeight; + v.setLayoutParams(params); + v.setVisibility(View.VISIBLE); + // Reset the drawing cache, the positions might have changed. + // We don't want the cache to be wrong. + v.setDrawingCacheEnabled(false); + } + } + + /* + * Adjust visibility and size to make it appear as though an item is being + * dragged around and other items are making room for it: If dropping the + * item would result in it still being in the same place, then make the + * dragged listitem's size normal, but make the item invisible. Otherwise, + * if the dragged listitem is still on screen, make it as small as possible + * and expand the item below the insert point. If the dragged item is not on + * screen, only expand the item below the current insertpoint. + */ + private void doExpansion() { + int childnum = mDragPos - getFirstVisiblePosition(); + if (mDragPos > mFirstDragPos) { + childnum++; + } + + View first = getChildAt(mFirstDragPos - getFirstVisiblePosition()); + + for (int i = 0;; i++) { + View vv = getChildAt(i); + if (vv == null) { + break; + } + int height = mItemHeight; + int visibility = View.VISIBLE; + if (vv.equals(first)) { + // processing the item that is being dragged + if (mDragPos == mFirstDragPos) { + // hovering over the original location + visibility = View.INVISIBLE; + } else { + // not hovering over it + height = 1; + } + } else if (i == childnum) { + if (mDragPos < getCount() - 1) { + height = mItemHeight * 2; + } + } + ViewGroup.LayoutParams params = vv.getLayoutParams(); + params.height = height; + vv.setLayoutParams(params); + vv.setVisibility(visibility); + } + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + if ((mDragListener != null || mDropListener != null) && mDragView != null) { + int action = ev.getAction(); + switch (action) { + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + Rect r = mTempRect; + mDragView.getDrawingRect(r); + stopDragging(); + if (mDropListener != null && mDragPos >= 0 && mDragPos < getCount()) { + mDropListener.drop(mFirstDragPos, mDragPos); + } + unExpandViews(false); + break; + + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_MOVE: + int x = (int) ev.getX(); + int y = (int) ev.getY(); + dragView(x, y); + int itemnum = getItemForPosition(y); + if (itemnum >= 0) { + if (action == MotionEvent.ACTION_DOWN || itemnum != mDragPos) { + if (mDragListener != null) { + mDragListener.drag(mDragPos, itemnum); + } + mDragPos = itemnum; + doExpansion(); + } + int speed = 0; + adjustScrollBounds(y); + if (y > mLowerBound) { + // scroll the list up a bit + speed = y > (mHeight + mLowerBound) / 2 ? 16 : 4; + } else if (y < mUpperBound) { + // scroll the list down a bit + speed = y < mUpperBound / 2 ? -16 : -4; + } + if (speed != 0) { + int ref = pointToPosition(0, mHeight / 2); + if (ref == AdapterView.INVALID_POSITION) { + // we hit a divider or an invisible view, check + // somewhere else + ref = pointToPosition(0, mHeight / 2 + getDividerHeight() + 64); + } + View v = getChildAt(ref - getFirstVisiblePosition()); + if (v != null) { + int pos = v.getTop(); + setSelectionFromTop(ref, pos - speed); + } + } + } + break; + } + return true; + } + return super.onTouchEvent(ev); + } + + private void startDragging(Bitmap bm, int x, int y) { + stopDragging(); + + mWindowParams = new WindowManager.LayoutParams(); + mWindowParams.gravity = Gravity.TOP | Gravity.LEFT; + mWindowParams.x = x; + mWindowParams.y = y - mDragPoint + mCoordOffset; + + mWindowParams.height = WindowManager.LayoutParams.WRAP_CONTENT; + mWindowParams.width = WindowManager.LayoutParams.WRAP_CONTENT; + mWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE + | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; + mWindowParams.format = PixelFormat.TRANSLUCENT; + mWindowParams.windowAnimations = 0; + + Context context = getContext(); + ImageView v = new ImageView(context); + int backGroundColor = context.getResources().getColor(R.color.theme_accent); + v.setAlpha((float) 0.7); + v.setBackgroundColor(backGroundColor); + v.setImageBitmap(bm); + mDragBitmap = bm; + + mWindowManager = (WindowManager) context.getSystemService("window"); + mWindowManager.addView(v, mWindowParams); + mDragView = v; + } + + private void dragView(int x, int y) { + mWindowParams.y = y - mDragPoint + mCoordOffset; + mWindowManager.updateViewLayout(mDragView, mWindowParams); + } + + private void stopDragging() { + if (mDragView != null) { + mDragView.setVisibility(GONE); + WindowManager wm = (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE); + wm.removeView(mDragView); + mDragView.setImageDrawable(null); + mDragView = null; + } + if (mDragBitmap != null) { + mDragBitmap.recycle(); + mDragBitmap = null; + } + } + + public void setDragListener(DragListener l) { + mDragListener = l; + } + + public void setDropListener(DropListener l) { + mDropListener = l; + } +} diff --git a/src/com/android/settings/DreamSettings.java b/src/com/android/settings/DreamSettings.java index 8137cd4..8431721 100644 --- a/src/com/android/settings/DreamSettings.java +++ b/src/com/android/settings/DreamSettings.java @@ -307,7 +307,7 @@ public class DreamSettings extends SettingsPreferenceFragment implements ((TextView) row.findViewById(android.R.id.title)).setText(dreamInfo.caption); // bind radio button - RadioButton radioButton = (RadioButton) row.findViewById(android.R.id.button1); + RadioButton radioButton = (RadioButton) row.findViewById(R.id.radio); radioButton.setChecked(dreamInfo.isActive); radioButton.setOnTouchListener(new OnTouchListener() { @Override @@ -321,7 +321,7 @@ public class DreamSettings extends SettingsPreferenceFragment implements View settingsDivider = row.findViewById(R.id.divider); settingsDivider.setVisibility(showSettings ? View.VISIBLE : View.INVISIBLE); - ImageView settingsButton = (ImageView) row.findViewById(android.R.id.button2); + ImageView settingsButton = (ImageView) row.findViewById(R.id.settings); settingsButton.setVisibility(showSettings ? View.VISIBLE : View.INVISIBLE); settingsButton.setAlpha(dreamInfo.isActive ? 1f : Utils.DISABLED_ALPHA); settingsButton.setEnabled(dreamInfo.isActive); @@ -337,8 +337,7 @@ public class DreamSettings extends SettingsPreferenceFragment implements private View createDreamInfoRow(ViewGroup parent) { final View row = mInflater.inflate(R.layout.dream_info_row, parent, false); - final View header = row.findViewById(android.R.id.widget_frame); - header.setOnClickListener(new OnClickListener(){ + row.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { v.setPressed(true); diff --git a/src/com/android/settings/FontDialogPreference.java b/src/com/android/settings/FontDialogPreference.java new file mode 100644 index 0000000..e9906e0 --- /dev/null +++ b/src/com/android/settings/FontDialogPreference.java @@ -0,0 +1,156 @@ +package com.android.settings; + +import android.app.ActivityManagerNative; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.os.RemoteException; +import android.preference.DialogPreference; +import android.preference.Preference; +import android.provider.Settings; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.TypedValue; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.SeekBar; +import android.widget.TextView; + +public class FontDialogPreference extends DialogPreference + implements SeekBar.OnSeekBarChangeListener { + + private TextView mDescriptionText; + private TextView mPercentageText; + private IntervalSeekBar mSeekBar; + + private DisplayMetrics mDisplayMetrics; + private int mLargeTextSp; + private int mSmallTextSp; + + public FontDialogPreference(Context context, AttributeSet attrs) { + super(context, attrs); + + setPositiveButtonText(android.R.string.ok); + setNegativeButtonText(android.R.string.cancel); + + initDisplayMetrics(); + + setDialogLayoutResource(R.layout.preference_dialog_fontsize); + setDialogTitle(null); // Hide the title bar + } + + @Override + protected View onCreateDialogView() { + LayoutInflater inflater = + (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View view = inflater.inflate(R.layout.preference_dialog_fontsize, null); + + mDescriptionText = (TextView) view.findViewById(R.id.description); + mPercentageText = (TextView) view.findViewById(R.id.percentage); + + // Calculate original sp sizes for the text views + mLargeTextSp = Math.round(mDescriptionText.getTextSize() / mDisplayMetrics.scaledDensity); + mSmallTextSp = Math.round(mPercentageText.getTextSize() / mDisplayMetrics.scaledDensity); + + mSeekBar = (IntervalSeekBar) view.findViewById(R.id.font_size); + + String strFontSize = getPersistedString(String.valueOf(mSeekBar.getDefault())); + float fontSize = Float.parseFloat(strFontSize); + + mSeekBar.setProgressFloat(fontSize); + mSeekBar.setOnSeekBarChangeListener(this); + + setPrompt(fontSize); + + return view; + } + + private void initDisplayMetrics() { + DisplayMetrics metrics = getContext().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; + } + + @Override + protected void onDialogClosed(boolean positiveResult) { + if (positiveResult) { + // Notify the Display settings screen (parent) that the font size + // is about to change. This can determine whether to persist the + // current value + if (callChangeListener(mSeekBar.getProgressFloat())) { + // Originally font scaling was a float stored as a String, + // so using persistFloat raises a ClassCastException + persistString(Float.toString(mSeekBar.getProgressFloat())); + } + } + } + + @Override + protected void onClick() { + // Ignore this until an explicit call to click() + } + + public void click() { + super.onClick(); + } + + /** + * Get an approximate description for the font size scale. + * Assumes that the string arrays entries_font_size and + * entryvalues_font_size have the same length and correspond to each other + * i.e. they are in the same order. + */ + static String getFontSizeDescription(Resources r, float val) { + String[] names = r.getStringArray(R.array.entries_font_size); + String[] indices = r.getStringArray(R.array.entryvalues_font_size); + + float lastVal = Float.parseFloat(indices[0]); + for (int i = 1; i < indices.length; i++) { + float thisVal = Float.parseFloat(indices[i]); + if (val < (lastVal + (thisVal-lastVal)*.5f)) { + return names[i - 1]; + } + lastVal = thisVal; + } + return names[indices.length - 1]; + } + + /** + * Set the TextView indicating the font scaling + */ + private void setPrompt(float fontScaling) { + // Update the preview text + String percentage = Math.round(fontScaling * 100) + "%"; + mPercentageText.setText(percentage); + + // Update the preview sizes + mDisplayMetrics.scaledDensity = mDisplayMetrics.density * fontScaling; + float largeSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, mLargeTextSp, + mDisplayMetrics); + float smallSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, mSmallTextSp, + mDisplayMetrics); + mDescriptionText.setTextSize(TypedValue.COMPLEX_UNIT_PX, largeSize); + mPercentageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, smallSize); + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + setPrompt(mSeekBar.getProgressFloat()); + } + + // Not used + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + } +} diff --git a/src/com/android/settings/HostnamePreference.java b/src/com/android/settings/HostnamePreference.java new file mode 100644 index 0000000..d9ebf21 --- /dev/null +++ b/src/com/android/settings/HostnamePreference.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2013 The CyanogenMod project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings; + +import android.content.Context; +import android.os.SystemProperties; +import android.preference.EditTextPreference; +import android.provider.Settings; +import android.text.InputFilter; +import android.text.Spanned; +import android.util.AttributeSet; +import android.util.Log; + +import cyanogenmod.providers.CMSettings; + +public class HostnamePreference extends EditTextPreference { + + private static final String TAG = "HostnamePreference"; + + private static final String PROP_HOSTNAME = "net.hostname"; + + private final String DEFAULT_HOSTNAME; + + InputFilter mHostnameInputFilter = new InputFilter() { + @Override + public CharSequence filter(CharSequence source, int start, int end, + Spanned dest, int dstart, int dend) { + + if (source.length() == 0) + return null; + + // remove any character that is not alphanumeric, period, or hyphen + return source.subSequence(start, end).toString().replaceAll("[^-.a-zA-Z0-9]", ""); + } + }; + + public HostnamePreference(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + // determine the default hostname + String id = Settings.Secure.getString(getContext().getContentResolver(), + Settings.Secure.ANDROID_ID); + if (id != null && id.length() > 0) { + DEFAULT_HOSTNAME = "android-".concat(id); + } else { + DEFAULT_HOSTNAME = ""; + } + + setSummary(getText()); + getEditText().setFilters(new InputFilter[] { mHostnameInputFilter }); + getEditText().setHint(DEFAULT_HOSTNAME); + } + + public HostnamePreference(Context context, AttributeSet attrs) { + this(context, attrs, com.android.internal.R.attr.editTextPreferenceStyle); + } + + public HostnamePreference(Context context) { + this(context, null); + } + + @Override + protected void onDialogClosed(boolean positiveResult) { + if (positiveResult) { + String hostname = getEditText().getText().toString(); + + // remove any preceding or succeeding periods or hyphens + hostname = hostname.replaceAll("(?:\\.|-)+$", ""); + hostname = hostname.replaceAll("^(?:\\.|-)+", ""); + + if (hostname.length() == 0) { + if (DEFAULT_HOSTNAME.length() != 0) { + // if no hostname is given, use the default + hostname = DEFAULT_HOSTNAME; + } else { + // if no other name can be determined + // fall back on the current hostname + hostname = getText(); + } + } + setText(hostname); + } + } + + @Override + public void setText(String text) { + if (text == null) { + Log.e(TAG, "tried to set null hostname, request ignored"); + return; + } else if (text.length() == 0) { + Log.w(TAG, "setting empty hostname"); + } else { + Log.i(TAG, "hostname has been set: " + text); + } + SystemProperties.set(PROP_HOSTNAME, text); + persistHostname(text); + setSummary(text); + } + + @Override + public String getText() { + return SystemProperties.get(PROP_HOSTNAME); + } + + @Override + public void onSetInitialValue(boolean restoreValue, Object defaultValue) { + String hostname = getText(); + persistHostname(hostname); + } + + public void persistHostname(String hostname) { + CMSettings.Secure.putString(getContext().getContentResolver(), + CMSettings.Secure.DEVICE_HOSTNAME, hostname); + } +} diff --git a/src/com/android/settings/IccLockSettings.java b/src/com/android/settings/IccLockSettings.java index 168d3c8..f4daceb 100644 --- a/src/com/android/settings/IccLockSettings.java +++ b/src/com/android/settings/IccLockSettings.java @@ -16,6 +16,7 @@ package com.android.settings; +import android.app.ActionBar; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -33,16 +34,13 @@ import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.util.Log; +import android.view.MenuItem; import android.view.View; import android.widget.ListView; -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.Toast; import com.android.internal.logging.MetricsLogger; +import com.android.internal.telephony.IccCardConstants.State; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneFactory; import com.android.internal.telephony.TelephonyIntents; @@ -71,6 +69,9 @@ public class IccLockSettings extends InstrumentedPreferenceActivity // State when entering the new pin - second time private static final int ICC_REENTER_MODE = 4; + static final String EXTRA_SUB_ID = "slot_id"; + static final String EXTRA_SUB_DISPLAY_NAME = "sub_display_name"; + // Keys in xml file private static final String PIN_DIALOG = "sim_pin"; private static final String PIN_TOGGLE = "sim_toggle"; @@ -97,10 +98,6 @@ public class IccLockSettings extends InstrumentedPreferenceActivity // Are we trying to enable or disable ICC lock? private boolean mToState; - private TabHost mTabHost; - private TabWidget mTabWidget; - private ListView mListView; - private Phone mPhone; private EditPinPreference mPinDialog; @@ -201,42 +198,45 @@ public class IccLockSettings extends InstrumentedPreferenceActivity // Don't need any changes to be remembered getPreferenceScreen().setPersistent(false); - if (numSims > 1) { - setContentView(R.layout.icc_lock_tabs); - - mTabHost = (TabHost) findViewById(android.R.id.tabhost); - mTabWidget = (TabWidget) findViewById(android.R.id.tabs); - mListView = (ListView) findViewById(android.R.id.list); - - mTabHost.setup(); - mTabHost.setOnTabChangedListener(mTabListener); - mTabHost.clearAllTabs(); - - SubscriptionManager sm = SubscriptionManager.from(this); - for (int i = 0; i < numSims; ++i) { - final SubscriptionInfo subInfo = sm.getActiveSubscriptionInfoForSimSlotIndex(i); - mTabHost.addTab(buildTabSpec(String.valueOf(i), - String.valueOf(subInfo == null - ? context.getString(R.string.sim_editor_title, i + 1) - : subInfo.getDisplayName()))); - } - final SubscriptionInfo sir = sm.getActiveSubscriptionInfoForSimSlotIndex(0); - - mPhone = (sir == null) ? null - : PhoneFactory.getPhone(SubscriptionManager.getPhoneId(sir.getSubscriptionId())); - } else { - mPhone = PhoneFactory.getDefaultPhone(); + Intent intent = getIntent(); + ActionBar actionBar = getActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setSubtitle(intent.getStringExtra(EXTRA_SUB_DISPLAY_NAME)); } + + int subId = intent.getIntExtra(EXTRA_SUB_ID, SubscriptionManager.getDefaultSubId()); + int phoneId = SubscriptionManager.getPhoneId(subId); + mPhone = PhoneFactory.getPhone(phoneId); mRes = getResources(); updatePreferences(); } - private void updatePreferences() { - mPinDialog.setEnabled(mPhone != null); - mPinToggle.setEnabled(mPhone != null); + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } + return false; + } + + private void updatePreferences() { if (mPhone != null) { + if (mPhone.getIccCard().getState() != State.READY) { + // if SIM State is NOT READY, it is not possible to interact with UICC app + // for enabling/disabling PIN so disable PIN options. + mPinToggle.setEnabled(false); + mPinDialog.setEnabled(false); + } else { + mPinToggle.setEnabled(true); + mPinDialog.setEnabled(true); + } mPinToggle.setChecked(mPhone.getIccCard().getIccLockEnabled()); + } else { + mPinDialog.setEnabled(false); + mPinToggle.setEnabled(false); } } @@ -472,31 +472,4 @@ public class IccLockSettings extends InstrumentedPreferenceActivity setDialogValues(); mDialogState = OFF_MODE; } - - private OnTabChangeListener mTabListener = new OnTabChangeListener() { - @Override - public void onTabChanged(String tabId) { - final int slotId = Integer.parseInt(tabId); - final SubscriptionInfo sir = SubscriptionManager.from(getBaseContext()) - .getActiveSubscriptionInfoForSimSlotIndex(slotId); - - mPhone = (sir == null) ? null - : PhoneFactory.getPhone(SubscriptionManager.getPhoneId(sir.getSubscriptionId())); - - // The User has changed tab; update the body. - updatePreferences(); - } - }; - - private TabContentFactory mEmptyTabContent = new TabContentFactory() { - @Override - public View createTabContent(String tag) { - return new View(mTabHost.getContext()); - } - }; - - private TabSpec buildTabSpec(String tag, String title) { - return mTabHost.newTabSpec(tag).setIndicator(title).setContent( - mEmptyTabContent); - } } diff --git a/src/com/android/settings/IntervalSeekBar.java b/src/com/android/settings/IntervalSeekBar.java new file mode 100644 index 0000000..5fbdb81 --- /dev/null +++ b/src/com/android/settings/IntervalSeekBar.java @@ -0,0 +1,87 @@ +package com.android.settings; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.util.Log; +import android.widget.SeekBar; + +/** + * Custom SeekBar that allows setting both a minimum and maximum value. + * This also handles floating point values (to 2 decimal places) through + * integer conversions. + */ +public class IntervalSeekBar extends SeekBar { + private float mMin; + private float mMax; + private float mDefault; + private float mMultiplier; + + public IntervalSeekBar(Context context, AttributeSet attrs) { + super(context, attrs); + + TypedArray seekBarType = context.obtainStyledAttributes(attrs, + R.styleable.IntervalSeekBar, 0, 0); + + mMax = seekBarType.getFloat(R.styleable.IntervalSeekBar_max, 1.5f); + mMin = seekBarType.getFloat(R.styleable.IntervalSeekBar_min, 0.5f); + mDefault = seekBarType.getFloat(R.styleable.IntervalSeekBar_defaultValue, 1.0f); + + int digits = seekBarType.getInt(R.styleable.IntervalSeekBar_digits, 0); + mMultiplier = (float) Math.pow(10, digits); + + if (mMin > mMax) { + float temp = mMax; + mMax = mMin; + mMin = temp; + } + + setMax(convertFloatToProgress(mMax)); + setProgressFloat(mDefault); + + seekBarType.recycle(); + } + + /* + * Converts from SeekBar units (which the SeekBar uses), to scale units + * (which are saved). + * This operation is the inverse of setFontScaling. + */ + public float getProgressFloat() { + return (getProgress() / mMultiplier) + mMin; + } + + /* + * Converts from scale units (which are saved), to SeekBar units + * (which the SeekBar uses). This also sets the SeekBar progress. + * This operation is the inverse of getProgressFloat. + */ + public void setProgressFloat(float progress) { + setProgress(convertFloatToProgress(progress)); + } + + private int convertFloatToProgress(float value) { + return Math.round((value - mMin) * mMultiplier); + } + + public float getMinimum() { + return mMin; + } + + public float getMaximum() { + return mMax; + } + + public float getDefault() { + return mDefault; + } + + public void setMaximum(float max) { + mMax = max; + setMax(convertFloatToProgress(mMax)); + } + + public void setMinimum(float min) { + mMin = min; + } +} diff --git a/src/com/android/settings/LegalSettings.java b/src/com/android/settings/LegalSettings.java index cd91d20..a28fae5 100644 --- a/src/com/android/settings/LegalSettings.java +++ b/src/com/android/settings/LegalSettings.java @@ -22,9 +22,14 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.net.Uri; import android.os.Bundle; +import android.os.SystemProperties; +import android.preference.Preference; import android.preference.PreferenceGroup; +import android.preference.PreferenceScreen; import android.provider.SearchIndexableResource; +import android.util.Log; import com.android.internal.logging.MetricsLogger; import com.android.settings.search.BaseSearchIndexProvider; @@ -36,10 +41,13 @@ import java.util.List; public class LegalSettings extends SettingsPreferenceFragment implements Indexable { + private static final String LOG_TAG = "LegalSettings"; private static final String KEY_TERMS = "terms"; private static final String KEY_LICENSE = "license"; private static final String KEY_COPYRIGHT = "copyright"; private static final String KEY_WEBVIEW_LICENSE = "webview_license"; + private static final String PROPERTY_CMLICENSE_URL = "ro.cmlegal.url"; + private static final String KEY_CM_LICENSE = "cmlicense"; public void onCreate(Bundle icicle) { super.onCreate(icicle); @@ -59,6 +67,22 @@ public class LegalSettings extends SettingsPreferenceFragment implements Indexab } @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { + if (preference.getKey().equals(KEY_CM_LICENSE)) { + String userCMLicenseUrl = SystemProperties.get(PROPERTY_CMLICENSE_URL); + final Intent intent = new Intent(Intent.ACTION_VIEW); + intent.addCategory(Intent.CATEGORY_DEFAULT); + intent.setData(Uri.parse(userCMLicenseUrl)); + try { + startActivity(intent); + } catch (Exception e) { + Log.e(LOG_TAG, "Unable to start activity " + intent.toString()); + } + } + return super.onPreferenceTreeClick(preferenceScreen, preference); + } + + @Override protected int getMetricsCategory() { return MetricsLogger.ABOUT_LEGAL_SETTINGS; } diff --git a/src/com/android/settings/MasterClear.java b/src/com/android/settings/MasterClear.java index b6cbebe..caa9b53 100644 --- a/src/com/android/settings/MasterClear.java +++ b/src/com/android/settings/MasterClear.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2008 The Android Open Source Project + * Copyright (C) 2015 The CyanogenMod Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +17,6 @@ package com.android.settings; -import android.accounts.Account; -import android.accounts.AccountManager; -import android.accounts.AuthenticatorDescription; import android.app.Activity; import android.app.Fragment; import android.content.Context; @@ -27,6 +25,9 @@ import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.content.res.Resources; import android.graphics.drawable.Drawable; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Resources; import android.os.Bundle; import android.os.Environment; import android.os.Process; @@ -39,7 +40,6 @@ import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.CheckBox; -import android.widget.LinearLayout; import android.widget.TextView; import com.android.internal.logging.MetricsLogger; @@ -60,11 +60,14 @@ public class MasterClear extends InstrumentedFragment { private static final int KEYGUARD_REQUEST = 55; - static final String ERASE_EXTERNAL_EXTRA = "erase_sd"; + //must match MasterClearReceiver.java extra + public static final String EXTRA_WIPE_MEDIA = "wipe_media"; private View mContentView; private Button mInitiateButton; private View mExternalStorageContainer; + private View mInternalStorageContainer; + private CheckBox mInternalStorage; private CheckBox mExternalStorage; /** @@ -97,10 +100,9 @@ public class MasterClear extends InstrumentedFragment { } private void showFinalConfirmation() { - Bundle args = new Bundle(); - args.putBoolean(ERASE_EXTERNAL_EXTRA, mExternalStorage.isChecked()); - ((SettingsActivity) getActivity()).startPreferencePanel(MasterClearConfirm.class.getName(), - args, R.string.master_clear_confirm_title, null, null, 0); + MasterClearConfirm.createInstance(mInternalStorage.isChecked(), + mExternalStorage.isChecked()) .show(getFragmentManager(), + MasterClearConfirm.class.getSimpleName()); } /** @@ -132,60 +134,73 @@ public class MasterClear extends InstrumentedFragment { private void establishInitialState() { mInitiateButton = (Button) mContentView.findViewById(R.id.initiate_master_clear); mInitiateButton.setOnClickListener(mInitiateListener); + mInternalStorage = (CheckBox) mContentView.findViewById(R.id.erase_internal); + mInternalStorageContainer = mContentView.findViewById(R.id.erase_internal_container); mExternalStorageContainer = mContentView.findViewById(R.id.erase_external_container); mExternalStorage = (CheckBox) mContentView.findViewById(R.id.erase_external); - /* - * If the external storage is emulated, it will be erased with a factory - * reset at any rate. There is no need to have a separate option until - * we have a factory reset that only erases some directories and not - * others. Likewise, if it's non-removable storage, it could potentially have been - * encrypted, and will also need to be wiped. - */ - boolean isExtStorageEmulated = Environment.isExternalStorageEmulated(); - if (isExtStorageEmulated - || (!Environment.isExternalStorageRemovable() && isExtStorageEncrypted())) { - mExternalStorageContainer.setVisibility(View.GONE); - - final View externalOption = mContentView.findViewById(R.id.erase_external_option_text); - externalOption.setVisibility(View.GONE); - - final View externalAlsoErased = mContentView.findViewById(R.id.also_erases_external); - externalAlsoErased.setVisibility(View.VISIBLE); + boolean hasExternalStorage = false; - // If it's not emulated, it is on a separate partition but it means we're doing - // a force wipe due to encryption. - mExternalStorage.setChecked(!isExtStorageEmulated); - } else { - mExternalStorageContainer.setOnClickListener(new View.OnClickListener() { + /** + * Here we do some logic to ensure the proper states are initialized. + * - hide internal memory section if device doesn't support it + * - force internal memory to be erased if the device is encrypted + * - show and hide the sd card section if the device supports this (and its inserted) + * TODO: mutli SD card support: no devices we support have this, but that might change + */ + if (Environment.isExternalStorageEmulated()) { + // we may have to force wipe internal storage due to encryption. + mInternalStorageContainer.setEnabled(!isExtStorageEncrypted() + && !Environment.isExternalStorageRemovable()); + mInternalStorageContainer.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - mExternalStorage.toggle(); + mInternalStorage.toggle(); } }); + if (!mInternalStorageContainer.isEnabled()) { + // force internal wipe + mInternalStorage.setChecked(true); + TextView internalSummaryText = (TextView) mContentView.findViewById( + R.id.erase_storage_checkbox_description); + internalSummaryText.setText( + R.string.factory_reset_erase_stored_content_summary_forced); + } + } else { + // there's no storage emulation; hide internal storage + mInternalStorageContainer.setVisibility(View.GONE); + + // primary storage can be removed. but does it exist? } - final UserManager um = (UserManager) getActivity().getSystemService(Context.USER_SERVICE); - loadAccountList(um); + hasExternalStorage = Environment.isExternalStorageRemovable() + && Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); + mExternalStorageContainer.setVisibility(hasExternalStorage ? View.VISIBLE : View.GONE); + mExternalStorageContainer.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mExternalStorage.toggle(); + } + }); + StringBuffer contentDescription = new StringBuffer(); - View masterClearContainer = mContentView.findViewById(R.id.master_clear_container); - getContentDescription(masterClearContainer, contentDescription); - masterClearContainer.setContentDescription(contentDescription); + getContentDescription(mInternalStorageContainer, contentDescription); + mInternalStorageContainer.setContentDescription(contentDescription); } private void getContentDescription(View v, StringBuffer description) { - if (v instanceof ViewGroup) { - ViewGroup vGroup = (ViewGroup) v; - for (int i = 0; i < vGroup.getChildCount(); i++) { - View nextChild = vGroup.getChildAt(i); - getContentDescription(nextChild, description); - } - } else if (v instanceof TextView) { - TextView vText = (TextView) v; - description.append(vText.getText()); - description.append(","); // Allow Talkback to pause between sections. - } + if (v instanceof ViewGroup) { + ViewGroup vGroup = (ViewGroup) v; + for (int i = 0; i < vGroup.getChildCount(); i++) { + View nextChild = vGroup.getChildAt(i); + getContentDescription(nextChild, description); + } + } else if (v instanceof TextView) { + TextView vText = (TextView) v; + description.append(vText.getText()); + description.append(","); // Allow Talkback to pause between sections. + } } private boolean isExtStorageEncrypted() { @@ -193,91 +208,6 @@ public class MasterClear extends InstrumentedFragment { return !"".equals(state); } - private void loadAccountList(final UserManager um) { - View accountsLabel = mContentView.findViewById(R.id.accounts_label); - LinearLayout contents = (LinearLayout)mContentView.findViewById(R.id.accounts); - contents.removeAllViews(); - - Context context = getActivity(); - final List<UserInfo> profiles = um.getProfiles(UserHandle.myUserId()); - final int profilesSize = profiles.size(); - - AccountManager mgr = AccountManager.get(context); - - LayoutInflater inflater = (LayoutInflater)context.getSystemService( - Context.LAYOUT_INFLATER_SERVICE); - - int accountsCount = 0; - for (int profileIndex = 0; profileIndex < profilesSize; profileIndex++) { - final UserInfo userInfo = profiles.get(profileIndex); - final int profileId = userInfo.id; - final UserHandle userHandle = new UserHandle(profileId); - Account[] accounts = mgr.getAccountsAsUser(profileId); - final int N = accounts.length; - if (N == 0) { - continue; - } - accountsCount += N; - - AuthenticatorDescription[] descs = AccountManager.get(context) - .getAuthenticatorTypesAsUser(profileId); - final int M = descs.length; - - View titleView = Utils.inflateCategoryHeader(inflater, contents); - final TextView titleText = (TextView) titleView.findViewById(android.R.id.title); - titleText.setText(userInfo.isManagedProfile() ? R.string.category_work - : R.string.category_personal); - contents.addView(titleView); - - for (int i = 0; i < N; i++) { - Account account = accounts[i]; - AuthenticatorDescription desc = null; - for (int j = 0; j < M; j++) { - if (account.type.equals(descs[j].type)) { - desc = descs[j]; - break; - } - } - if (desc == null) { - Log.w(TAG, "No descriptor for account name=" + account.name - + " type=" + account.type); - continue; - } - Drawable icon = null; - try { - if (desc.iconId != 0) { - Context authContext = context.createPackageContextAsUser(desc.packageName, - 0, userHandle); - icon = context.getPackageManager().getUserBadgedIcon( - authContext.getDrawable(desc.iconId), userHandle); - } - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "Bad package name for account type " + desc.type); - } catch (Resources.NotFoundException e) { - Log.w(TAG, "Invalid icon id for account type " + desc.type, e); - } - if (icon == null) { - icon = context.getPackageManager().getDefaultActivityIcon(); - } - - TextView child = (TextView)inflater.inflate(R.layout.master_clear_account, - contents, false); - child.setText(account.name); - child.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null); - contents.addView(child); - } - } - - if (accountsCount > 0) { - accountsLabel.setVisibility(View.VISIBLE); - contents.setVisibility(View.VISIBLE); - } - // Checking for all other users and their profiles if any. - View otherUsers = mContentView.findViewById(R.id.other_users_present); - final boolean hasOtherUsers = (um.getUserCount() - profilesSize) > 0; - otherUsers.setVisibility(hasOtherUsers ? View.VISIBLE : View.GONE); - } - @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -287,7 +217,7 @@ public class MasterClear extends InstrumentedFragment { return inflater.inflate(R.layout.master_clear_disallowed_screen, null); } - mContentView = inflater.inflate(R.layout.master_clear, null); + mContentView = inflater.inflate(R.layout.master_clear_cm, null); establishInitialState(); return mContentView; diff --git a/src/com/android/settings/MasterClearConfirm.java b/src/com/android/settings/MasterClearConfirm.java index da10b2a..2845d13 100644 --- a/src/com/android/settings/MasterClearConfirm.java +++ b/src/com/android/settings/MasterClearConfirm.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2010 The Android Open Source Project + * Copyright (C) 2015 The CyanogenMod Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +17,13 @@ package com.android.settings; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; import android.app.ProgressDialog; import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; import android.content.pm.ActivityInfo; import android.os.AsyncTask; import android.provider.Settings; @@ -28,10 +34,7 @@ import com.android.internal.logging.MetricsLogger; import android.content.Intent; import android.os.Bundle; import android.os.UserManager; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; +import android.service.persistentdata.PersistentDataBlockManager; import android.widget.TextView; /** @@ -44,114 +47,174 @@ import android.widget.TextView; * * This is the confirmation screen. */ -public class MasterClearConfirm extends InstrumentedFragment { +public class MasterClearConfirm extends DialogFragment { - private View mContentView; - private boolean mEraseSdCard; + private static final String REASON_MASTER_CLEAR_CONFIRM = "MasterClearConfirm"; - /** - * The user has gone through the multiple confirmation, so now we go ahead - * and invoke the Checkin Service to reset the device to its factory-default - * state (rebooting in the process). - */ - private Button.OnClickListener mFinalClickListener = new Button.OnClickListener() { + private boolean mEraseInternal; + private boolean mEraseExternal; - public void onClick(View v) { - if (Utils.isMonkeyRunning()) { - return; - } + public static class FrpDialog extends DialogFragment { - final PersistentDataBlockManager pdbManager = (PersistentDataBlockManager) - getActivity().getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE); - - if (pdbManager != null && !pdbManager.getOemUnlockEnabled() && - Settings.Global.getInt(getActivity().getContentResolver(), - Settings.Global.DEVICE_PROVISIONED, 0) != 0) { - // if OEM unlock is enabled, this will be wiped during FR process. If disabled, it - // will be wiped here, unless the device is still being provisioned, in which case - // the persistent data block will be preserved. - new AsyncTask<Void, Void, Void>() { - int mOldOrientation; - ProgressDialog mProgressDialog; - - @Override - protected Void doInBackground(Void... params) { - pdbManager.wipe(); - return null; - } - - @Override - protected void onPostExecute(Void aVoid) { - mProgressDialog.hide(); - getActivity().setRequestedOrientation(mOldOrientation); - doMasterClear(); - } - - @Override - protected void onPreExecute() { - mProgressDialog = getProgressDialog(); - mProgressDialog.show(); - - // need to prevent orientation changes as we're about to go into - // a long IO request, so we won't be able to access inflate resources on flash - mOldOrientation = getActivity().getRequestedOrientation(); - getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED); - } - }.execute(); - } else { - doMasterClear(); - } + private int mOriginalOrientation; + + public static FrpDialog createInstance(boolean wipeInternal, boolean wipeExternal) { + Bundle b = new Bundle(); + b.putBoolean(MasterClear.EXTRA_WIPE_MEDIA, wipeInternal); + b.putBoolean(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, wipeExternal); + FrpDialog fragment = new FrpDialog(); + fragment.setArguments(b); + + return fragment; } - private ProgressDialog getProgressDialog() { - final ProgressDialog progressDialog = new ProgressDialog(getActivity()); + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final ProgressDialog progressDialog = new ProgressDialog(getActivity(), getTheme()); progressDialog.setIndeterminate(true); progressDialog.setCancelable(false); - progressDialog.setTitle( - getActivity().getString(R.string.master_clear_progress_title)); - progressDialog.setMessage( - getActivity().getString(R.string.master_clear_progress_text)); + progressDialog.setTitle(getActivity().getString(R.string.master_clear_progress_title)); + progressDialog.setMessage(getActivity().getString(R.string.master_clear_progress_text)); return progressDialog; } - }; - private void doMasterClear() { - Intent intent = new Intent(Intent.ACTION_MASTER_CLEAR); - intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); - intent.putExtra(Intent.EXTRA_REASON, "MasterClearConfirm"); - intent.putExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, mEraseSdCard); - getActivity().sendBroadcast(intent); - // Intent handling is asynchronous -- assume it will happen soon. + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setShowsDialog(true); + setCancelable(false); + } + + @Override + public void onStart() { + super.onStart(); + + // need to prevent orientation changes as we're about to go into + // a long IO request, so we won't be able to access inflate resources on flash + mOriginalOrientation = getActivity().getRequestedOrientation(); + getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED); + } + + @Override + public void onStop() { + super.onStop(); + getActivity().setRequestedOrientation(mOriginalOrientation); + } + + @Override + public void onResume() { + super.onResume(); + new AsyncTask<Void, Void, Void>() { + + Context mContext; + boolean mWipeMedia; + boolean mWipeExternal; + + @Override + protected void onPreExecute() { + mContext = getActivity().getApplicationContext(); + mWipeMedia = getArguments().getBoolean(MasterClear.EXTRA_WIPE_MEDIA); + mWipeExternal = getArguments().getBoolean(Intent.EXTRA_WIPE_EXTERNAL_STORAGE); + } + + @Override + protected Void doInBackground(Void... params) { + final PersistentDataBlockManager pdbManager = (PersistentDataBlockManager) + mContext.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE); + if (pdbManager != null) pdbManager.wipe(); + return null; + } + + @Override + protected void onPostExecute(Void aVoid) { + FrpDialog.this.dismissAllowingStateLoss(); + doMasterClear(mContext, mWipeMedia, mWipeExternal); + } + }.execute(); + } + } + + public static MasterClearConfirm createInstance(boolean wipeInternal, boolean wipeExternal) { + Bundle b = new Bundle(); + b.putBoolean(MasterClear.EXTRA_WIPE_MEDIA, wipeInternal); + b.putBoolean(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, wipeExternal); + MasterClearConfirm fragment = new MasterClearConfirm(); + fragment.setArguments(b); + + return fragment; } /** - * Configure the UI for the final confirmation interaction + * The user has gone through the multiple confirmation, so now we go ahead + * and invoke the Checkin Service to reset the device to its factory-default + * state (rebooting in the process). */ - private void establishFinalConfirmationState() { - mContentView.findViewById(R.id.execute_master_clear) - .setOnClickListener(mFinalClickListener); + private void onResetConfirmed() { + if (Utils.isMonkeyRunning()) { + return; + } + + final PersistentDataBlockManager pdbManager = (PersistentDataBlockManager) + getActivity().getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE); + + if (pdbManager != null && !pdbManager.getOemUnlockEnabled()) { + // if OEM unlock is enabled, this will be wiped during FR process. + FrpDialog.createInstance(mEraseInternal, mEraseExternal) + .show(getFragmentManager(), "frp_dialog"); + } else { + doMasterClear(getActivity(), mEraseInternal, mEraseExternal); + } + } + + private static void doMasterClear(Context context, boolean eraseInternal, + boolean eraseExternal) { + Intent intent = new Intent(Intent.ACTION_MASTER_CLEAR); + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + intent.putExtra(MasterClear.EXTRA_WIPE_MEDIA, eraseInternal); + intent.putExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, eraseExternal); + intent.putExtra(Intent.EXTRA_REASON, REASON_MASTER_CLEAR_CONFIRM); + context.sendBroadcast(intent); + // Intent handling is asynchronous -- assume it will happen soon. } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + public Dialog onCreateDialog(Bundle savedInstanceState) { if (UserManager.get(getActivity()).hasUserRestriction( UserManager.DISALLOW_FACTORY_RESET)) { - return inflater.inflate(R.layout.master_clear_disallowed_screen, null); + return new AlertDialog.Builder(getActivity()) + .setMessage(R.string.master_clear_not_available) + .create(); } - mContentView = inflater.inflate(R.layout.master_clear_confirm, null); - establishFinalConfirmationState(); - setAccessibilityTitle(); - return mContentView; + final AlertDialog alertDialog = new AlertDialog.Builder(getActivity()) + .setTitle(R.string.device_reset_title) + .setMessage(getString(R.string.factory_reset_warning_text_message)) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.factory_reset_warning_text_reset_now, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + onResetConfirmed(); + } + }) + .create(); + alertDialog.setOnShowListener(new DialogInterface.OnShowListener() { + @Override + public void onShow(DialogInterface dialog) { + AlertDialog d = (AlertDialog) dialog; + d.getButton(DialogInterface.BUTTON_POSITIVE) + .setTextColor(getResources().getColor(R.color.factory_reset_color)); + } + }); + + return alertDialog; } private void setAccessibilityTitle() { CharSequence currentTitle = getActivity().getTitle(); - TextView confirmationMessage = - (TextView) mContentView.findViewById(R.id.master_clear_confirm); + CharSequence confirmationMessage = getText(R.string.factory_reset_warning_text_reset_now); if (confirmationMessage != null) { String accessibileText = new StringBuilder(currentTitle).append(",").append( - confirmationMessage.getText()).toString(); + confirmationMessage).toString(); getActivity().setTitle(Utils.createAccessibleSequence(currentTitle, accessibileText)); } } @@ -159,14 +222,15 @@ public class MasterClearConfirm extends InstrumentedFragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + if (getActivity() != null) { + getActivity().setTitle(R.string.device_reset_title); + setAccessibilityTitle(); + } Bundle args = getArguments(); - mEraseSdCard = args != null - && args.getBoolean(MasterClear.ERASE_EXTERNAL_EXTRA); - } + mEraseInternal = args != null && args.getBoolean(MasterClear.EXTRA_WIPE_MEDIA, false); + mEraseExternal = args != null && args.getBoolean(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, false); - @Override - protected int getMetricsCategory() { - return MetricsLogger.MASTER_CLEAR_CONFIRM; + setShowsDialog(true); } } diff --git a/src/com/android/settings/RegulatoryInfoDisplayActivity.java b/src/com/android/settings/RegulatoryInfoDisplayActivity.java index c674f13..42b0d22 100644 --- a/src/com/android/settings/RegulatoryInfoDisplayActivity.java +++ b/src/com/android/settings/RegulatoryInfoDisplayActivity.java @@ -25,9 +25,12 @@ import android.os.Bundle; import android.os.SystemProperties; import android.text.TextUtils; import android.view.Gravity; +import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; +import com.android.settings.deviceinfo.Status; /** * {@link Activity} that displays regulatory information for the "Regulatory information" @@ -56,41 +59,48 @@ public class RegulatoryInfoDisplayActivity extends Activity implements } AlertDialog.Builder builder = new AlertDialog.Builder(this) - .setTitle(R.string.regulatory_information) + .setTitle(R.string.regulatory_information_dialog_title) .setOnDismissListener(this); + View view = getLayoutInflater().inflate(R.layout.regulatory_info, null); + boolean regulatoryInfoDrawableExists = false; int resId = getResourceId(); if (resId != 0) { try { Drawable d = getDrawable(resId); - // set to false if the width or height is <= 2 - // (missing PNG can return an empty 2x2 pixel Drawable) - regulatoryInfoDrawableExists = (d.getIntrinsicWidth() > 2 - && d.getIntrinsicHeight() > 2); + // set to false if the width or height is <= 32 + // (default PNG is a 1x1 pixel image scaled to the display density) + regulatoryInfoDrawableExists = (d.getIntrinsicWidth() > 32 + && d.getIntrinsicHeight() > 32); } catch (Resources.NotFoundException ignored) { regulatoryInfoDrawableExists = false; } } - CharSequence regulatoryText = resources.getText(R.string.regulatory_info_text); - if (regulatoryInfoDrawableExists) { - View view = getLayoutInflater().inflate(R.layout.regulatory_info, null); ImageView image = (ImageView) view.findViewById(R.id.regulatoryInfo); + image.setVisibility(View.VISIBLE); image.setImageResource(resId); - builder.setView(view); - builder.show(); - } else if (regulatoryText.length() > 0) { - builder.setMessage(regulatoryText); - AlertDialog dialog = builder.show(); - // we have to show the dialog first, or the setGravity() call will throw a NPE - TextView messageText = (TextView) dialog.findViewById(android.R.id.message); - messageText.setGravity(Gravity.CENTER); - } else { - // neither drawable nor text resource exists, finish activity - finish(); } + + String sarValues = Status.getSarValues(getResources()); + TextView sarText = (TextView) view.findViewById(R.id.sarValues); + if (!TextUtils.isEmpty(sarValues)) { + sarText.setVisibility(resources.getBoolean(R.bool.config_show_sar_enable) + ? View.VISIBLE : View.GONE); + sarText.setText(sarValues); + } + + String icCodes = Status.getIcCodes(getResources()); + TextView icCode = (TextView) view.findViewById(R.id.icCodes); + if (!TextUtils.isEmpty(icCodes)) { + icCode.setVisibility(resources.getBoolean(R.bool.config_show_ic_enable) + ? View.VISIBLE : View.GONE); + icCode.setText(icCodes); + } + builder.setView(view); + builder.show(); } private int getResourceId() { diff --git a/src/com/android/settings/ResetNetworkConfirm.java b/src/com/android/settings/ResetNetworkConfirm.java index db4b9a5..df708b9 100644 --- a/src/com/android/settings/ResetNetworkConfirm.java +++ b/src/com/android/settings/ResetNetworkConfirm.java @@ -34,6 +34,7 @@ import android.widget.Button; import android.widget.Spinner; import android.widget.Toast; +import com.android.ims.ImsManager; import com.android.internal.logging.MetricsLogger; import com.android.internal.telephony.PhoneConstants; @@ -100,6 +101,8 @@ public class ResetNetworkConfirm extends InstrumentedFragment { btManager.getAdapter().factoryReset(); } + ImsManager.factoryReset(context); + Toast.makeText(context, R.string.reset_network_complete_toast, Toast.LENGTH_SHORT) .show(); } diff --git a/src/com/android/settings/SecuritySettings.java b/src/com/android/settings/SecuritySettings.java index 892d61f..7db4a4e 100644 --- a/src/com/android/settings/SecuritySettings.java +++ b/src/com/android/settings/SecuritySettings.java @@ -31,6 +31,7 @@ import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.FingerprintManager; import android.os.Bundle; import android.os.PersistableBundle; +import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.preference.ListPreference; @@ -53,7 +54,9 @@ import android.util.Log; import com.android.internal.logging.MetricsLogger; import com.android.internal.widget.LockPatternUtils; +import com.android.settings.Settings.LockScreenSettingsActivity; import com.android.settings.TrustAgentUtils.TrustAgentComponentInfo; +import com.android.settings.cyanogenmod.LiveLockScreenSettings; import com.android.settings.fingerprint.FingerprintEnrollIntroduction; import com.android.settings.fingerprint.FingerprintSettings; import com.android.settings.search.BaseSearchIndexProvider; @@ -61,10 +64,15 @@ import com.android.settings.search.Index; import com.android.settings.search.Indexable; import com.android.settings.search.SearchIndexableRaw; +import org.cyanogenmod.internal.util.CmLockPatternUtils; + +import cyanogenmod.providers.CMSettings; + import java.util.ArrayList; import java.util.List; import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT; +import static cyanogenmod.content.Intent.ACTION_OPEN_LIVE_LOCKSCREEN_SETTINGS; /** * Gesture lock pattern settings. @@ -77,9 +85,18 @@ public class SecuritySettings extends SettingsPreferenceFragment private static final Intent TRUST_AGENT_INTENT = new Intent(TrustAgentService.SERVICE_INTERFACE); + // Fitler types for this panel + protected static final String FILTER_TYPE_EXTRA = "filter_type"; + protected static final int TYPE_LOCKSCREEN_EXTRA = 0; + private static final int TYPE_SECURITY_EXTRA = 1; + private static final int TYPE_EXTERNAL_RESOLUTION = 2; + // Lock Settings private static final String KEY_UNLOCK_SET_OR_CHANGE = "unlock_set_or_change"; + private static final String KEY_DIRECTLY_SHOW = "directlyshow"; private static final String KEY_VISIBLE_PATTERN = "visiblepattern"; + private static final String KEY_VISIBLE_ERROR_PATTERN = "visible_error_pattern"; + private static final String KEY_VISIBLE_DOTS = "visibledots"; private static final String KEY_SECURITY_CATEGORY = "security_category"; private static final String KEY_DEVICE_ADMIN_CATEGORY = "device_admin_category"; private static final String KEY_LOCK_AFTER_TIMEOUT = "lock_after_timeout"; @@ -88,11 +105,14 @@ public class SecuritySettings extends SettingsPreferenceFragment private static final String KEY_MANAGE_TRUST_AGENTS = "manage_trust_agents"; private static final String KEY_FINGERPRINT_SETTINGS = "fingerprint_settings"; + private static final String KEY_LOCKSCREEN_ENABLED_INTERNAL = "lockscreen_enabled_internally"; + private static final int SET_OR_CHANGE_LOCK_METHOD_REQUEST = 123; private static final int CHANGE_TRUST_AGENT_SETTINGS = 126; // Misc Settings private static final String KEY_SIM_LOCK = "sim_lock"; + private static final String KEY_SIM_LOCK_SETTINGS = "sim_lock_settings"; private static final String KEY_SHOW_PASSWORD = "show_password"; private static final String KEY_CREDENTIAL_STORAGE_TYPE = "credential_storage_type"; private static final String KEY_RESET_CREDENTIALS = "credentials_reset"; @@ -103,17 +123,24 @@ public class SecuritySettings extends SettingsPreferenceFragment private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive"; private static final String KEY_TRUST_AGENT = "trust_agent"; private static final String KEY_SCREEN_PINNING = "screen_pinning_settings"; + private static final String KEY_SMS_SECURITY_CHECK_PREF = "sms_security_check_limit"; + private static final String KEY_GENERAL_CATEGORY = "general_category"; + private static final String KEY_LIVE_LOCK_SCREEN = "live_lock_screen"; + private static final String KEY_LOCK_SCREEN_BLUR = CMSettings.Secure.LOCK_SCREEN_BLUR_ENABLED; // These switch preferences need special handling since they're not all stored in Settings. private static final String SWITCH_PREFERENCE_KEYS[] = { KEY_LOCK_AFTER_TIMEOUT, - KEY_VISIBLE_PATTERN, KEY_POWER_INSTANTLY_LOCKS, KEY_SHOW_PASSWORD, - KEY_TOGGLE_INSTALL_APPLICATIONS }; + KEY_VISIBLE_PATTERN, KEY_VISIBLE_ERROR_PATTERN, KEY_VISIBLE_DOTS, KEY_DIRECTLY_SHOW, + KEY_POWER_INSTANTLY_LOCKS, KEY_SHOW_PASSWORD, KEY_TOGGLE_INSTALL_APPLICATIONS }; // Only allow one trust agent on the platform. private static final boolean ONLY_ONE_TRUST_AGENT = true; - private static final int MY_USER_ID = UserHandle.myUserId(); + protected static final int MY_USER_ID = UserHandle.myUserId(); + protected static final String LIVE_LOCK_SCREEN_FEATURE = "org.cyanogenmod.livelockscreen"; + + private PackageManager mPM; private DevicePolicyManager mDPM; private SubscriptionManager mSubscriptionManager; @@ -121,7 +148,10 @@ public class SecuritySettings extends SettingsPreferenceFragment private LockPatternUtils mLockPatternUtils; private ListPreference mLockAfter; + private SwitchPreference mDirectlyShow; private SwitchPreference mVisiblePattern; + private SwitchPreference mVisibleErrorPattern; + private SwitchPreference mVisibleDots; private SwitchPreference mShowPassword; @@ -132,10 +162,16 @@ public class SecuritySettings extends SettingsPreferenceFragment private DialogInterface mWarnInstallApps; private SwitchPreference mPowerButtonInstantlyLocks; + private ListPreference mSmsSecurityCheck; + private boolean mIsPrimary; private Intent mTrustAgentClickIntent; + private Preference mOwnerInfoPref; + private int mFilterType = TYPE_SECURITY_EXTRA; + + private Preference mLockscreenDisabledPreference; @Override protected int getMetricsCategory() { @@ -146,6 +182,26 @@ public class SecuritySettings extends SettingsPreferenceFragment public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + // Ugly hack for legacy shortcuts :'( + Intent intent = getActivity().getIntent(); + ComponentName componentName = intent.getComponent(); + if (componentName.getClassName().equals( + LockScreenSettingsActivity.class.getName())) { + mFilterType = TYPE_LOCKSCREEN_EXTRA; + } else { + Bundle bundle = getArguments(); + if (bundle != null) { + mFilterType = bundle.getInt(FILTER_TYPE_EXTRA, TYPE_SECURITY_EXTRA); + } + } + + Bundle extras = getActivity().getIntent().getExtras(); + // Even uglier hack to make cts verifier expectations make sense. + if (extras != null && extras.get(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS) != null && + extras.get(SettingsActivity.EXTRA_SHOW_FRAGMENT_AS_SHORTCUT) == null) { + mFilterType = TYPE_EXTERNAL_RESOLUTION; + } + mSubscriptionManager = SubscriptionManager.from(getActivity()); mLockPatternUtils = new LockPatternUtils(getActivity()); @@ -160,7 +216,7 @@ public class SecuritySettings extends SettingsPreferenceFragment } } - private static int getResIdForLockUnlockScreen(Context context, + protected static int getResIdForLockUnlockScreen(Context context, LockPatternUtils lockPatternUtils) { int resid = 0; if (!lockPatternUtils.isSecure(MY_USER_ID)) { @@ -202,25 +258,45 @@ public class SecuritySettings extends SettingsPreferenceFragment addPreferencesFromResource(R.xml.security_settings); root = getPreferenceScreen(); - // Add options for lock/unlock screen - final int resid = getResIdForLockUnlockScreen(getActivity(), mLockPatternUtils); - addPreferencesFromResource(resid); + // Add package manager to check if features are available + PackageManager pm = getPackageManager(); // Add options for device encryption mIsPrimary = MY_USER_ID == UserHandle.USER_OWNER; - mOwnerInfoPref = findPreference(KEY_OWNER_INFO_SETTINGS); - if (mOwnerInfoPref != null) { - mOwnerInfoPref.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - OwnerInfoSettings.show(SecuritySettings.this); - return true; - } - }); + if (CMSettings.Secure.getIntForUser(getContentResolver(), + CMSettings.Secure.LOCKSCREEN_INTERNALLY_ENABLED, 1, UserHandle.USER_OWNER) != 1) { + // lock screen is disabled by quick settings tile, let the user know!~ + mLockscreenDisabledPreference = new Preference(getActivity()); + mLockscreenDisabledPreference.setKey(KEY_LOCKSCREEN_ENABLED_INTERNAL); + mLockscreenDisabledPreference.setTitle(R.string.lockscreen_disabled_by_qs_tile_title); + mLockscreenDisabledPreference.setSummary(R.string.lockscreen_disabled_by_qs_tile_summary); + root.addPreference(mLockscreenDisabledPreference); } - if (mIsPrimary) { + final boolean securityOrExternal = mFilterType == TYPE_SECURITY_EXTRA + || mFilterType == TYPE_EXTERNAL_RESOLUTION; + final boolean lockscreenOrExternal = mFilterType == TYPE_LOCKSCREEN_EXTRA + || mFilterType == TYPE_EXTERNAL_RESOLUTION; + + if (lockscreenOrExternal) { + // Add options for lock/unlock screen + final int resid = getResIdForLockUnlockScreen(getActivity(), mLockPatternUtils); + addPreferencesFromResource(resid); + + mOwnerInfoPref = findPreference(KEY_OWNER_INFO_SETTINGS); + if (mOwnerInfoPref != null) { + mOwnerInfoPref.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + OwnerInfoSettings.show(SecuritySettings.this); + return true; + } + }); + } + } + + if (mIsPrimary && securityOrExternal) { if (LockPatternUtils.isDeviceEncryptionEnabled()) { // The device is currently encrypted. addPreferencesFromResource(R.xml.security_settings_encrypted); @@ -230,114 +306,216 @@ public class SecuritySettings extends SettingsPreferenceFragment } } - // Fingerprint and trust agents - PreferenceGroup securityCategory = (PreferenceGroup) - root.findPreference(KEY_SECURITY_CATEGORY); - if (securityCategory != null) { - maybeAddFingerprintPreference(securityCategory); - addTrustAgentSettings(securityCategory); - } + if (lockscreenOrExternal) { + // Fingerprint and trust agents + PreferenceGroup securityCategory = (PreferenceGroup) + root.findPreference(KEY_SECURITY_CATEGORY); + if (securityCategory != null) { + maybeAddFingerprintPreference(securityCategory); + addTrustAgentSettings(securityCategory); + } - // lock after preference - mLockAfter = (ListPreference) root.findPreference(KEY_LOCK_AFTER_TIMEOUT); - if (mLockAfter != null) { - setupLockAfterPreference(); - updateLockAfterPreferenceSummary(); - } + // lock after preference + mLockAfter = (ListPreference) root.findPreference(KEY_LOCK_AFTER_TIMEOUT); + if (mLockAfter != null) { + setupLockAfterPreference(); + updateLockAfterPreferenceSummary(); + } - // visible pattern - mVisiblePattern = (SwitchPreference) root.findPreference(KEY_VISIBLE_PATTERN); - - // lock instantly on power key press - mPowerButtonInstantlyLocks = (SwitchPreference) root.findPreference( - KEY_POWER_INSTANTLY_LOCKS); - Preference trustAgentPreference = root.findPreference(KEY_TRUST_AGENT); - if (mPowerButtonInstantlyLocks != null && - trustAgentPreference != null && - trustAgentPreference.getTitle().length() > 0) { - mPowerButtonInstantlyLocks.setSummary(getString( - R.string.lockpattern_settings_power_button_instantly_locks_summary, - trustAgentPreference.getTitle())); - } - - // Append the rest of the settings - addPreferencesFromResource(R.xml.security_settings_misc); - - // Do not display SIM lock for devices without an Icc card - TelephonyManager tm = TelephonyManager.getDefault(); - CarrierConfigManager cfgMgr = (CarrierConfigManager) - getActivity().getSystemService(Context.CARRIER_CONFIG_SERVICE); - PersistableBundle b = cfgMgr.getConfig(); - if (!mIsPrimary || !isSimIccReady() || - b.getBoolean(CarrierConfigManager.KEY_HIDE_SIM_LOCK_SETTINGS_BOOL)) { - root.removePreference(root.findPreference(KEY_SIM_LOCK)); - } else { - // Disable SIM lock if there is no ready SIM card. - root.findPreference(KEY_SIM_LOCK).setEnabled(isSimReady()); - } - if (Settings.System.getInt(getContentResolver(), - Settings.System.LOCK_TO_APP_ENABLED, 0) != 0) { - root.findPreference(KEY_SCREEN_PINNING).setSummary( - getResources().getString(R.string.switch_on_text)); + // directly show + mDirectlyShow = (SwitchPreference) root.findPreference(KEY_DIRECTLY_SHOW); + + // visible pattern + mVisiblePattern = (SwitchPreference) root.findPreference(KEY_VISIBLE_PATTERN); + + // visible error pattern + mVisibleErrorPattern = (SwitchPreference) root.findPreference( + KEY_VISIBLE_ERROR_PATTERN); + + // visible dots + mVisibleDots = (SwitchPreference) root.findPreference(KEY_VISIBLE_DOTS); + + // lock instantly on power key press + mPowerButtonInstantlyLocks = (SwitchPreference) root.findPreference( + KEY_POWER_INSTANTLY_LOCKS); + Preference trustAgentPreference = root.findPreference(KEY_TRUST_AGENT); + if (mPowerButtonInstantlyLocks != null && + trustAgentPreference != null && + trustAgentPreference.getTitle().length() > 0) { + mPowerButtonInstantlyLocks.setSummary(getString( + R.string.lockpattern_settings_power_button_instantly_locks_summary, + trustAgentPreference.getTitle())); + } + + // Add live lock screen preference if supported + PreferenceGroup generalCategory = (PreferenceGroup) + root.findPreference(KEY_GENERAL_CATEGORY); + if (pm.hasSystemFeature(LIVE_LOCK_SCREEN_FEATURE) && generalCategory != null && Utils.isUserOwner()) { + boolean moveToTop = getResources().getBoolean( + R.bool.config_showLiveLockScreenSettingsFirst); + + PreferenceGroup groupToAddTo = moveToTop ? root : generalCategory; + Preference liveLockPreference = new Preference(getContext(), null); + liveLockPreference.setIntent(new Intent(ACTION_OPEN_LIVE_LOCKSCREEN_SETTINGS)); + liveLockPreference.setOrder(-1); + setLiveLockScreenPreferenceTitleAndSummary(liveLockPreference); + groupToAddTo.addPreference(liveLockPreference); + } + + // only show blur setting for devices that support it + boolean blurSupported = getResources().getBoolean( + com.android.internal.R.bool.config_ui_blur_enabled); + if (!blurSupported && generalCategory != null) { + Preference blurEnabledPref = generalCategory.findPreference(KEY_LOCK_SCREEN_BLUR); + if (blurEnabledPref != null) generalCategory.removePreference(blurEnabledPref); + } } - // Show password - mShowPassword = (SwitchPreference) root.findPreference(KEY_SHOW_PASSWORD); - mResetCredentials = root.findPreference(KEY_RESET_CREDENTIALS); + if (securityOrExternal) { + // Append the rest of the settings + addPreferencesFromResource(R.xml.security_settings_misc); - // Credential storage - final UserManager um = (UserManager) getActivity().getSystemService(Context.USER_SERVICE); - mKeyStore = KeyStore.getInstance(); // needs to be initialized for onResume() - if (!um.hasUserRestriction(UserManager.DISALLOW_CONFIG_CREDENTIALS)) { - Preference credentialStorageType = root.findPreference(KEY_CREDENTIAL_STORAGE_TYPE); - - final int storageSummaryRes = - mKeyStore.isHardwareBacked() ? R.string.credential_storage_type_hardware - : R.string.credential_storage_type_software; - credentialStorageType.setSummary(storageSummaryRes); - } else { - PreferenceGroup credentialsManager = (PreferenceGroup) - root.findPreference(KEY_CREDENTIALS_MANAGER); - credentialsManager.removePreference(root.findPreference(KEY_RESET_CREDENTIALS)); - credentialsManager.removePreference(root.findPreference(KEY_CREDENTIALS_INSTALL)); - credentialsManager.removePreference(root.findPreference(KEY_CREDENTIAL_STORAGE_TYPE)); - } - - // Application install - PreferenceGroup deviceAdminCategory = (PreferenceGroup) - root.findPreference(KEY_DEVICE_ADMIN_CATEGORY); - mToggleAppInstallation = (SwitchPreference) findPreference( - KEY_TOGGLE_INSTALL_APPLICATIONS); - mToggleAppInstallation.setChecked(isNonMarketAppsAllowed()); - // Side loading of apps. - // Disable for restricted profiles. For others, check if policy disallows it. - mToggleAppInstallation.setEnabled(!um.getUserInfo(MY_USER_ID).isRestricted()); - if (um.hasUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES) - || um.hasUserRestriction(UserManager.DISALLOW_INSTALL_APPS)) { - mToggleAppInstallation.setEnabled(false); - } - - // Advanced Security features - PreferenceGroup advancedCategory = - (PreferenceGroup)root.findPreference(KEY_ADVANCED_SECURITY); - if (advancedCategory != null) { - Preference manageAgents = advancedCategory.findPreference(KEY_MANAGE_TRUST_AGENTS); - if (manageAgents != null && !mLockPatternUtils.isSecure(MY_USER_ID)) { - manageAgents.setEnabled(false); - manageAgents.setSummary(R.string.disabled_because_no_backup_security); - } - } - - // The above preferences come and go based on security state, so we need to update - // the index. This call is expected to be fairly cheap, but we may want to do something - // smarter in the future. - Index.getInstance(getActivity()) - .updateFromClassNameResource(SecuritySettings.class.getName(), true, true); + // Do not display SIM lock for devices without an Icc card + CarrierConfigManager cfgMgr = (CarrierConfigManager) + getActivity().getSystemService(Context.CARRIER_CONFIG_SERVICE); + PersistableBundle b = cfgMgr.getConfig(); + PreferenceGroup iccLockGroup = (PreferenceGroup) root.findPreference(KEY_SIM_LOCK); + Preference iccLock = root.findPreference(KEY_SIM_LOCK_SETTINGS); + + if (!mIsPrimary + || b.getBoolean(CarrierConfigManager.KEY_HIDE_SIM_LOCK_SETTINGS_BOOL)) { + root.removePreference(iccLockGroup); + } else { + SubscriptionManager subMgr = SubscriptionManager.from(getActivity()); + TelephonyManager tm = TelephonyManager.getDefault(); + int numPhones = tm.getPhoneCount(); + boolean hasAnySim = false; + + for (int i = 0; i < numPhones; i++) { + final Preference pref; + + if (numPhones > 1) { + SubscriptionInfo sir = subMgr.getActiveSubscriptionInfoForSimSlotIndex(i); + if (sir == null) { + continue; + } + + pref = new Preference(getActivity()); + pref.setOrder(iccLock.getOrder()); + pref.setTitle(getString(R.string.sim_card_lock_settings_title, i + 1)); + pref.setSummary(sir.getDisplayName()); + + Intent intent = new Intent(getActivity(), IccLockSettings.class); + intent.putExtra(IccLockSettings.EXTRA_SUB_ID, sir.getSubscriptionId()); + intent.putExtra(IccLockSettings.EXTRA_SUB_DISPLAY_NAME, + sir.getDisplayName()); + pref.setIntent(intent); + + iccLockGroup.addPreference(pref); + } else { + pref = iccLock; + } + + // Do not display SIM lock for devices without an Icc card + hasAnySim |= tm.hasIccCard(i); + + int simState = tm.getSimState(i); + boolean simPresent = simState != TelephonyManager.SIM_STATE_ABSENT + && simState != TelephonyManager.SIM_STATE_UNKNOWN + && simState != TelephonyManager.SIM_STATE_CARD_IO_ERROR; + if (!simPresent) { + pref.setEnabled(false); + } + } + + if (!hasAnySim) { + root.removePreference(iccLockGroup); + } else if (numPhones > 1) { + iccLockGroup.removePreference(iccLock); + } + } + + if (Settings.System.getInt(getContentResolver(), + Settings.System.LOCK_TO_APP_ENABLED, 0) != 0) { + root.findPreference(KEY_SCREEN_PINNING).setSummary( + getResources().getString(R.string.switch_on_text)); + } + + // SMS rate limit security check + boolean isTelephony = pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY); + if (isTelephony) { + mSmsSecurityCheck = (ListPreference) root.findPreference(KEY_SMS_SECURITY_CHECK_PREF); + mSmsSecurityCheck.setOnPreferenceChangeListener(this); + int smsSecurityCheck = Integer.valueOf(mSmsSecurityCheck.getValue()); + updateSmsSecuritySummary(smsSecurityCheck); + } + + // needed by Credential storage and Application install sections + final UserManager um = (UserManager) getActivity().getSystemService(Context.USER_SERVICE); + + // Both "Show password" and "Credential storage" options depend on having a KeyStore. + // However, KeyStore assumes that there's an android.security.keystore service around. + // Let's validate if it indeed exists here, to avoid breakage. + if (ServiceManager.getService("android.security.keystore") != null) { + // Show password + mShowPassword = (SwitchPreference) root.findPreference(KEY_SHOW_PASSWORD); + mResetCredentials = root.findPreference(KEY_RESET_CREDENTIALS); + + // Credential storage + mKeyStore = KeyStore.getInstance(); // needs to be initialized for onResume() + + if (!um.hasUserRestriction(UserManager.DISALLOW_CONFIG_CREDENTIALS)) { + Preference credentialStorageType = root.findPreference(KEY_CREDENTIAL_STORAGE_TYPE); + + final int storageSummaryRes = + mKeyStore.isHardwareBacked() ? R.string.credential_storage_type_hardware + : R.string.credential_storage_type_software; + credentialStorageType.setSummary(storageSummaryRes); + } else { + PreferenceGroup credentialsManager = (PreferenceGroup) + root.findPreference(KEY_CREDENTIALS_MANAGER); + credentialsManager.removePreference(root.findPreference(KEY_RESET_CREDENTIALS)); + credentialsManager.removePreference(root.findPreference(KEY_CREDENTIALS_INSTALL)); + credentialsManager.removePreference(root.findPreference(KEY_CREDENTIAL_STORAGE_TYPE)); + } + } + + // Application install + PreferenceGroup deviceAdminCategory = (PreferenceGroup) + root.findPreference(KEY_DEVICE_ADMIN_CATEGORY); + mToggleAppInstallation = (SwitchPreference) findPreference( + KEY_TOGGLE_INSTALL_APPLICATIONS); + mToggleAppInstallation.setChecked(isNonMarketAppsAllowed()); + // Side loading of apps. + // Disable for restricted profiles. For others, check if policy disallows it. + mToggleAppInstallation.setEnabled(!um.getUserInfo(MY_USER_ID).isRestricted()); + if (um.hasUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES) + || um.hasUserRestriction(UserManager.DISALLOW_INSTALL_APPS)) { + mToggleAppInstallation.setEnabled(false); + } + // Advanced Security features + PreferenceGroup advancedCategory = + (PreferenceGroup)root.findPreference(KEY_ADVANCED_SECURITY); + if (advancedCategory != null) { + Preference manageAgents = advancedCategory.findPreference(KEY_MANAGE_TRUST_AGENTS); + if (manageAgents != null && !mLockPatternUtils.isSecure(MY_USER_ID)) { + manageAgents.setEnabled(false); + manageAgents.setSummary(R.string.disabled_because_no_backup_security); + } + } + + // The above preferences come and go based on security state, so we need to update + // the index. This call is expected to be fairly cheap, but we may want to do something + // smarter in the future. + Index.getInstance(getActivity()) + .updateFromClassNameResource(SecuritySettings.class.getName(), true, true); + } for (int i = 0; i < SWITCH_PREFERENCE_KEYS.length; i++) { final Preference pref = findPreference(SWITCH_PREFERENCE_KEYS[i]); if (pref != null) pref.setOnPreferenceChangeListener(this); } + return root; } @@ -399,44 +577,7 @@ public class SecuritySettings extends SettingsPreferenceFragment } } - /* Return true if a there is a Slot that has Icc. - */ - private boolean isSimIccReady() { - TelephonyManager tm = TelephonyManager.getDefault(); - final List<SubscriptionInfo> subInfoList = - mSubscriptionManager.getActiveSubscriptionInfoList(); - - if (subInfoList != null) { - for (SubscriptionInfo subInfo : subInfoList) { - if (tm.hasIccCard(subInfo.getSimSlotIndex())) { - return true; - } - } - } - - return false; - } - - /* Return true if a SIM is ready for locking. - * TODO: consider adding to TelephonyManager or SubscritpionManasger. - */ - private boolean isSimReady() { - int simState = TelephonyManager.SIM_STATE_UNKNOWN; - final List<SubscriptionInfo> subInfoList = - mSubscriptionManager.getActiveSubscriptionInfoList(); - if (subInfoList != null) { - for (SubscriptionInfo subInfo : subInfoList) { - simState = TelephonyManager.getDefault().getSimState(subInfo.getSimSlotIndex()); - if((simState != TelephonyManager.SIM_STATE_ABSENT) && - (simState != TelephonyManager.SIM_STATE_UNKNOWN)){ - return true; - } - } - } - return false; - } - - private static ArrayList<TrustAgentComponentInfo> getActiveTrustAgents( + protected static ArrayList<TrustAgentComponentInfo> getActiveTrustAgents( PackageManager pm, LockPatternUtils utils, DevicePolicyManager dpm) { ArrayList<TrustAgentComponentInfo> result = new ArrayList<TrustAgentComponentInfo>(); List<ResolveInfo> resolveInfos = pm.queryIntentServices(TRUST_AGENT_INTENT, @@ -513,6 +654,13 @@ public class SecuritySettings extends SettingsPreferenceFragment } } + private void updateSmsSecuritySummary(int selection) { + String message = selection > 0 + ? getString(R.string.sms_security_check_limit_summary, selection) + : getString(R.string.sms_security_check_limit_summary_none); + mSmsSecurityCheck.setSummary(message); + } + private void setupLockAfterPreference() { // Compatible with pre-Froyo long currentTimeout = Settings.Secure.getLong(getContentResolver(), @@ -604,9 +752,18 @@ public class SecuritySettings extends SettingsPreferenceFragment createPreferenceHierarchy(); final LockPatternUtils lockPatternUtils = mChooseLockSettingsHelper.utils(); + final CmLockPatternUtils cmLockPatternUtils = mChooseLockSettingsHelper.cmUtils(); + if (mDirectlyShow != null) { + mDirectlyShow.setChecked(cmLockPatternUtils.shouldPassToSecurityView(MY_USER_ID)); + } if (mVisiblePattern != null) { - mVisiblePattern.setChecked(lockPatternUtils.isVisiblePatternEnabled( - MY_USER_ID)); + mVisiblePattern.setChecked(lockPatternUtils.isVisiblePatternEnabled(MY_USER_ID)); + } + if (mVisibleErrorPattern != null) { + mVisibleErrorPattern.setChecked(lockPatternUtils.isShowErrorPath(MY_USER_ID)); + } + if (mVisibleDots != null) { + mVisibleDots.setChecked(lockPatternUtils.isVisibleDotsEnabled(MY_USER_ID)); } if (mPowerButtonInstantlyLocks != null) { mPowerButtonInstantlyLocks.setChecked(lockPatternUtils.getPowerButtonInstantlyLocks( @@ -645,11 +802,18 @@ public class SecuritySettings extends SettingsPreferenceFragment mTrustAgentClickIntent = preference.getIntent(); boolean confirmationLaunched = helper.launchConfirmationActivity( CHANGE_TRUST_AGENT_SETTINGS, preference.getTitle()); - if (!confirmationLaunched&& mTrustAgentClickIntent != null) { + if (!confirmationLaunched && mTrustAgentClickIntent != null) { // If this returns false, it means no password confirmation is required. startActivity(mTrustAgentClickIntent); mTrustAgentClickIntent = null; } + } else if (KEY_LOCKSCREEN_ENABLED_INTERNAL.equals(key)) { + CMSettings.Secure.putIntForUser(getActivity().getContentResolver(), + CMSettings.Secure.LOCKSCREEN_INTERNALLY_ENABLED, + 1, UserHandle.USER_CURRENT); + mLockscreenDisabledPreference.setEnabled(false); + mLockscreenDisabledPreference.setSummary( + R.string.lockscreen_disabled_by_qs_tile_summary_enabled); } else { // If we didn't handle it, let preferences handle it. return super.onPreferenceTreeClick(preferenceScreen, preference); @@ -678,6 +842,7 @@ public class SecuritySettings extends SettingsPreferenceFragment boolean result = true; final String key = preference.getKey(); final LockPatternUtils lockPatternUtils = mChooseLockSettingsHelper.utils(); + final CmLockPatternUtils cmLockPatternUtils = mChooseLockSettingsHelper.cmUtils(); if (KEY_LOCK_AFTER_TIMEOUT.equals(key)) { int timeout = Integer.parseInt((String) value); try { @@ -687,8 +852,14 @@ public class SecuritySettings extends SettingsPreferenceFragment Log.e("SecuritySettings", "could not persist lockAfter timeout setting", e); } updateLockAfterPreferenceSummary(); + } else if (KEY_DIRECTLY_SHOW.equals(key)) { + cmLockPatternUtils.setPassToSecurityView((Boolean) value, MY_USER_ID); } else if (KEY_VISIBLE_PATTERN.equals(key)) { lockPatternUtils.setVisiblePatternEnabled((Boolean) value, MY_USER_ID); + } else if (KEY_VISIBLE_ERROR_PATTERN.equals(key)) { + lockPatternUtils.setShowErrorPath((Boolean) value, MY_USER_ID); + } else if (KEY_VISIBLE_DOTS.equals(key)) { + lockPatternUtils.setVisibleDotsEnabled((Boolean) value, MY_USER_ID); } else if (KEY_POWER_INSTANTLY_LOCKS.equals(key)) { mLockPatternUtils.setPowerButtonInstantlyLocks((Boolean) value, MY_USER_ID); } else if (KEY_SHOW_PASSWORD.equals(key)) { @@ -704,6 +875,11 @@ public class SecuritySettings extends SettingsPreferenceFragment } else { setNonMarketAppsAllowed(false); } + } else if (KEY_SMS_SECURITY_CHECK_PREF.equals(key)) { + int smsSecurityCheck = Integer.valueOf((String) value); + Settings.Global.putInt(getContentResolver(), Settings.Global.SMS_OUTGOING_CHECK_MAX_COUNT, + smsSecurityCheck); + updateSmsSecuritySummary(smsSecurityCheck); } return result; } @@ -714,6 +890,46 @@ public class SecuritySettings extends SettingsPreferenceFragment } /** + * Loads the title and summary for live lock screen preference. If an external package supports + * the {@link cyanogenmod.content.Intent#ACTION_OPEN_LIVE_LOCKSCREEN_SETTINGS} we attempt to + * load the title and summary from that package and use defaults if those cannot be loaded or + * no other package is found to support the action. + * @param pref + */ + private void setLiveLockScreenPreferenceTitleAndSummary(Preference pref) { + String title = getString(R.string.live_lock_screen_title); + String summary = getString(R.string.live_lock_screen_summary); + PackageManager pm = getPackageManager(); + List<ResolveInfo> infos = pm.queryIntentActivities( + new Intent(ACTION_OPEN_LIVE_LOCKSCREEN_SETTINGS), 0); + if (infos != null && infos.size() > 1) { + for (ResolveInfo info : infos) { + if (!getActivity().getPackageName().equals(info.activityInfo.packageName)) { + try { + final Context ctx = getActivity().createPackageContext( + info.activityInfo.packageName, 0); + final Resources res = ctx.getResources(); + int titleId = res.getIdentifier("live_lock_screen_title", "string", + info.activityInfo.packageName); + int summaryId = res.getIdentifier("live_lock_screen_summary", "string", + info.activityInfo.packageName); + if (titleId !=0 && summaryId != 0) { + title = res.getString(titleId); + summary = res.getString(summaryId); + } + } catch (PackageManager.NameNotFoundException e) { + /* ignore and use defaults */ + } + break; + } + } + } + + pref.setTitle(title); + pref.setSummary(summary); + } + + /** * For Search. Please keep it in sync when updating "createPreferenceHierarchy()" */ public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = @@ -735,14 +951,8 @@ public class SecuritySettings extends SettingsPreferenceFragment List<SearchIndexableResource> result = new ArrayList<SearchIndexableResource>(); - LockPatternUtils lockPatternUtils = new LockPatternUtils(context); - // Add options for lock/unlock screen - int resId = getResIdForLockUnlockScreen(context, lockPatternUtils); - - SearchIndexableResource sir = new SearchIndexableResource(context); - sir.xmlResId = resId; - result.add(sir); - + int resId = 0; + SearchIndexableResource sir; if (mIsPrimary) { DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); @@ -793,22 +1003,6 @@ public class SecuritySettings extends SettingsPreferenceFragment result.add(data); } - // Fingerprint - FingerprintManager fpm = - (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE); - if (fpm.isHardwareDetected()) { - // This catches the title which can be overloaded in an overlay - data = new SearchIndexableRaw(context); - data.title = res.getString(R.string.security_settings_fingerprint_preference_title); - data.screenTitle = screenTitle; - result.add(data); - // Fallback for when the above doesn't contain "fingerprint" - data = new SearchIndexableRaw(context); - data.title = res.getString(R.string.fingerprint_manage_category_title); - data.screenTitle = screenTitle; - result.add(data); - } - // Credential storage final UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); @@ -825,20 +1019,7 @@ public class SecuritySettings extends SettingsPreferenceFragment result.add(data); } - // Advanced - final LockPatternUtils lockPatternUtils = new LockPatternUtils(context); - if (lockPatternUtils.isSecure(MY_USER_ID)) { - ArrayList<TrustAgentComponentInfo> agents = - getActiveTrustAgents(context.getPackageManager(), lockPatternUtils, - context.getSystemService(DevicePolicyManager.class)); - for (int i = 0; i < agents.size(); i++) { - final TrustAgentComponentInfo agent = agents.get(i); - data = new SearchIndexableRaw(context); - data.title = agent.title; - data.screenTitle = screenTitle; - result.add(data); - } - } + return result; } @@ -847,8 +1028,6 @@ public class SecuritySettings extends SettingsPreferenceFragment final List<String> keys = new ArrayList<String>(); LockPatternUtils lockPatternUtils = new LockPatternUtils(context); - // Add options for lock/unlock screen - int resId = getResIdForLockUnlockScreen(context, lockPatternUtils); // Do not display SIM lock for devices without an Icc card TelephonyManager tm = TelephonyManager.getDefault(); diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java index 7b94d79..0834b4f 100644 --- a/src/com/android/settings/Settings.java +++ b/src/com/android/settings/Settings.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2008 The Android Open Source Project + * Copyright (C) 2016 The CyanogenMod Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +18,7 @@ package com.android.settings; import com.android.settings.applications.AppOpsSummary; +import com.android.settings.blacklist.BlacklistSettings; /** * Top-level Settings activity @@ -59,7 +61,7 @@ public class Settings extends SettingsActivity { return true; } return super.isValidFragment(className); - } + } } public static class StorageUseActivity extends SettingsActivity { /* empty */ } public static class DevelopmentSettingsActivity extends SettingsActivity { /* empty */ } @@ -75,7 +77,6 @@ public class Settings extends SettingsActivity { public static class RunningServicesActivity extends SettingsActivity { /* empty */ } public static class ManageAccountsSettingsActivity extends SettingsActivity { /* empty */ } public static class PowerUsageSummaryActivity extends SettingsActivity { /* empty */ } - public static class BatterySaverSettingsActivity extends SettingsActivity { /* empty */ } public static class AccountSyncSettingsActivity extends SettingsActivity { /* empty */ } public static class AccountSettingsActivity extends SettingsActivity { /* empty */ } public static class AccountSyncSettingsInAddAccountActivity extends SettingsActivity { /* empty */ } @@ -104,7 +105,6 @@ public class Settings extends SettingsActivity { public static class ZenModeScheduleRuleSettingsActivity extends SettingsActivity { /* empty */ } public static class ZenModeEventRuleSettingsActivity extends SettingsActivity { /* empty */ } public static class ZenModeExternalRuleSettingsActivity extends SettingsActivity { /* empty */ } - public static class NotificationSettingsActivity extends SettingsActivity { /* empty */ } public static class NotificationAppListActivity extends SettingsActivity { /* empty */ } public static class AppNotificationSettingsActivity extends SettingsActivity { /* empty */ } public static class OtherSoundSettingsActivity extends SettingsActivity { /* empty */ } @@ -118,4 +118,15 @@ public class Settings extends SettingsActivity { public static class WriteSettingsActivity extends SettingsActivity { /* empty */ } public static class AppDrawOverlaySettingsActivity extends SettingsActivity { /* empty */ } public static class AppWriteSettingsActivity extends SettingsActivity { /* empty */ } + public static class LiveDisplayActivity extends SettingsActivity { /* empty */ } + public static class DisplayRotationActivity extends SettingsActivity { /* empty */ } + public static class BlacklistSettingsActivity extends SettingsActivity { /* empty */ } + public static class ProfilesSettingsActivity extends SettingsActivity { /* empty */ } + public static class AnonymousStatsActivity extends Settings { /* empty */ } + public static class ContributorsCloudActivity extends SettingsActivity { /* empty */ } + public static class CMSoundSettingsActivity extends SettingsActivity { /* empty */ } + public static class LockScreenSettingsActivity extends SettingsActivity { /* empty */ } + public static class LiveLockScreenSettingsActivity extends SettingsActivity { /* empty */ } + public static class NotificationManagerActivity extends SettingsActivity { /* empty */ } + public static class WeatherProviderServicesActivity extends SettingsActivity { /* empty */ } } diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java index 39c0e90..b54d771 100644 --- a/src/com/android/settings/SettingsActivity.java +++ b/src/com/android/settings/SettingsActivity.java @@ -37,9 +37,11 @@ import android.content.res.Configuration; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.nfc.NfcAdapter; +import android.nfc.Tag; import android.os.Bundle; import android.os.Handler; import android.os.Message; +import android.os.Process; import android.os.UserHandle; import android.os.UserManager; import android.preference.Preference; @@ -79,7 +81,12 @@ import com.android.settings.applications.ProcessStatsSummary; import com.android.settings.applications.ProcessStatsUi; import com.android.settings.applications.UsageAccessDetails; import com.android.settings.applications.WriteSettingsDetails; +import com.android.settings.blacklist.BlacklistSettings; import com.android.settings.bluetooth.BluetoothSettings; +import com.android.settings.contributors.ContributorsCloudFragment; +import com.android.settings.cyanogenmod.DisplayRotation; +import com.android.settings.cyanogenmod.LiveLockScreenSettings; +import com.android.settings.cyanogenmod.WeatherServiceSettings; import com.android.settings.dashboard.DashboardCategory; import com.android.settings.dashboard.DashboardSummary; import com.android.settings.dashboard.DashboardTile; @@ -89,9 +96,16 @@ import com.android.settings.deviceinfo.PrivateVolumeForget; import com.android.settings.deviceinfo.PrivateVolumeSettings; import com.android.settings.deviceinfo.PublicVolumeSettings; import com.android.settings.deviceinfo.StorageSettings; -import com.android.settings.fuelgauge.BatterySaverSettings; import com.android.settings.fuelgauge.PowerUsageDetail; import com.android.settings.fuelgauge.PowerUsageSummary; +import com.android.settings.livedisplay.LiveDisplay; +import com.android.settings.notification.NotificationManagerSettings; +import com.android.settings.notification.OtherSoundSettings; +import com.android.settings.notification.SoundSettings; +import com.android.settings.profiles.NFCProfileTagCallback; +import com.android.settings.profiles.ProfilesSettings; +import com.android.settings.search.DynamicIndexableContentMonitor; +import com.android.settings.search.Index; import com.android.settings.inputmethod.InputMethodAndLanguageSettings; import com.android.settings.inputmethod.KeyboardLayoutPickerFragment; import com.android.settings.inputmethod.SpellCheckersSettings; @@ -101,7 +115,6 @@ import com.android.settings.nfc.AndroidBeam; import com.android.settings.nfc.PaymentSettings; import com.android.settings.notification.AppNotificationSettings; import com.android.settings.notification.NotificationAccessSettings; -import com.android.settings.notification.NotificationSettings; import com.android.settings.notification.NotificationStation; import com.android.settings.notification.OtherSoundSettings; import com.android.settings.notification.ZenAccessSettings; @@ -115,6 +128,7 @@ import com.android.settings.print.PrintJobSettingsFragment; import com.android.settings.print.PrintSettingsFragment; import com.android.settings.search.DynamicIndexableContentMonitor; import com.android.settings.search.Index; +import com.android.settings.privacyguard.PrivacyGuardPrefs; import com.android.settings.sim.SimSettings; import com.android.settings.tts.TextToSpeechSettings; import com.android.settings.users.UserSettings; @@ -126,6 +140,7 @@ import com.android.settings.wifi.SavedAccessPointsWifiSettings; import com.android.settings.wifi.WifiSettings; import com.android.settings.wifi.p2p.WifiP2pSettings; +import cyanogenmod.app.CMContextConstants; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -252,6 +267,8 @@ public class SettingsActivity extends Activity private CharSequence mInitialTitle; private int mInitialTitleResId; + private NFCProfileTagCallback mNfcProfileCallback; + // Show only these settings for restricted users private int[] SETTINGS_FOR_RESTRICTED = { R.id.wireless_section, @@ -261,8 +278,11 @@ public class SettingsActivity extends Activity R.id.sim_settings, R.id.wireless_settings, R.id.device_section, - R.id.notification_settings, - R.id.display_settings, + R.id.sound_settings, + R.id.display_and_lights_settings, + R.id.lockscreen_settings, + R.id.notification_manager, + R.id.status_bar_settings, R.id.storage_settings, R.id.application_settings, R.id.battery_settings, @@ -277,9 +297,9 @@ public class SettingsActivity extends Activity R.id.about_settings, R.id.accessibility_settings, R.id.print_settings, - R.id.nfc_payment_settings, R.id.home_settings, - R.id.dashboard + R.id.dashboard, + R.id.privacy_settings_cyanogenmod, }; private static final String[] ENTRY_FRAGMENTS = { @@ -336,11 +356,10 @@ public class SettingsActivity extends Activity PaymentSettings.class.getName(), KeyboardLayoutPickerFragment.class.getName(), ZenModeSettings.class.getName(), - NotificationSettings.class.getName(), + SoundSettings.class.getName(), ChooseLockPassword.ChooseLockPasswordFragment.class.getName(), ChooseLockPattern.ChooseLockPatternFragment.class.getName(), InstalledAppDetails.class.getName(), - BatterySaverSettings.class.getName(), AppNotificationSettings.class.getName(), OtherSoundSettings.class.getName(), ApnSettings.class.getName(), @@ -355,6 +374,15 @@ public class SettingsActivity extends Activity ProcessStatsSummary.class.getName(), DrawOverlayDetails.class.getName(), WriteSettingsDetails.class.getName(), + LiveDisplay.class.getName(), + com.android.settings.cyanogenmod.DisplayRotation.class.getName(), + com.android.settings.cyanogenmod.PrivacySettings.class.getName(), + BlacklistSettings.class.getName(), + ProfilesSettings.class.getName(), + ContributorsCloudFragment.class.getName(), + NotificationManagerSettings.class.getName(), + LiveLockScreenSettings.class.getName(), + WeatherServiceSettings.class.getName() }; @@ -616,7 +644,7 @@ public class SettingsActivity extends Activity 1 /* one home activity by default */); } else { if (!mIsShowingDashboard) { - mDisplaySearch = false; + mDisplaySearch = Process.myUid() == Process.SYSTEM_UID; // UP will be shown only if it is a sub settings if (mIsShortcut) { mDisplayHomeAsUpEnabled = isSubSettings; @@ -1161,6 +1189,11 @@ public class SettingsActivity extends Activity com.android.internal.R.styleable.PreferenceHeader_fragment); sa.recycle(); + sa = context.obtainStyledAttributes(attrs, R.styleable.DashboardTile); + tile.switchControl = sa.getString( + R.styleable.DashboardTile_switchClass); + sa.recycle(); + if (curBundle == null) { curBundle = new Bundle(); } @@ -1192,10 +1225,7 @@ public class SettingsActivity extends Activity curBundle = null; } - // Show the SIM Cards setting if there are more than 2 SIMs installed. - if(tile.id != R.id.sim_settings || Utils.showSimCardTile(context)){ - category.addTile(tile); - } + category.addTile(tile); } else if (innerNodeName.equals("external-tiles")) { category.externalIndex = category.getTilesCount(); @@ -1253,6 +1283,15 @@ public class SettingsActivity extends Activity if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) { removeTile = true; } + } else if (id == R.id.mobile_networks) { + if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY) + || Utils.showSimCardTile(this)) { + removeTile = true; + } + } else if (id == R.id.sim_settings) { + if (!Utils.showSimCardTile(this)) { + removeTile = true; + } } else if (id == R.id.data_usage_settings) { // Remove data usage when kernel module not enabled if (!Utils.isBandwidthControlEnabled()) { @@ -1277,18 +1316,6 @@ public class SettingsActivity extends Activity || Utils.isMonkeyRunning()) { removeTile = true; } - } else if (id == R.id.nfc_payment_settings) { - if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)) { - removeTile = true; - } else { - // Only show if NFC is on and we have the HCE feature - NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this); - if (adapter == null || !adapter.isEnabled() || - !getPackageManager().hasSystemFeature( - PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) { - removeTile = true; - } - } } else if (id == R.id.print_settings) { boolean hasPrintingSupport = getPackageManager().hasSystemFeature( PackageManager.FEATURE_PRINTING); @@ -1300,6 +1327,20 @@ public class SettingsActivity extends Activity UserManager.DISALLOW_DEBUGGING_FEATURES)) { removeTile = true; } + } else if (id == R.id.button_settings) { + boolean hasDeviceKeys = getResources().getInteger( + com.android.internal.R.integer.config_deviceHardwareKeys) != 0; + if (!hasDeviceKeys) { + removeTile = true; + } + } else if (id == R.id.weather_settings) { + final boolean showWeatherMenu = getResources() + .getBoolean(R.bool.config_showWeatherMenu); + + if (!getPackageManager().hasSystemFeature( + CMContextConstants.Features.WEATHER_SERVICES) || !showWeatherMenu) { + removeTile = true; + } } if (UserHandle.MU_ENABLED && UserHandle.myUserId() != 0 @@ -1364,7 +1405,8 @@ public class SettingsActivity extends Activity activityInfo.packageName, activityInfo.name); Utils.updateTileToSpecificActivityFromMetaDataOrRemove(this, tile); - if (category.externalIndex == -1) { + if (category.externalIndex == -1 + || category.externalIndex > category.getTilesCount()) { // If no location for external tiles has been specified for this category, // then just put them at the end. category.addTile(tile); @@ -1530,4 +1572,21 @@ public class SettingsActivity extends Activity public void setResultIntentData(Intent resultIntentData) { mResultIntentData = resultIntentData; } + + public void setNfcProfileCallback(NFCProfileTagCallback callback) { + mNfcProfileCallback = callback; + } + + @Override + protected void onNewIntent(Intent intent) { + if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) { + Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); + if (mNfcProfileCallback != null) { + mNfcProfileCallback.onTagRead(detectedTag); + } + return; + } + super.onNewIntent(intent); + } + } diff --git a/src/com/android/settings/SettingsPreferenceFragment.java b/src/com/android/settings/SettingsPreferenceFragment.java index b957c38..02f8a03 100644 --- a/src/com/android/settings/SettingsPreferenceFragment.java +++ b/src/com/android/settings/SettingsPreferenceFragment.java @@ -72,12 +72,12 @@ public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceF private DataSetObserver mDataSetObserver = new DataSetObserver() { @Override public void onChanged() { - highlightPreferenceIfNeeded(); + highlightPreferenceIfNeeded(true); } @Override public void onInvalidated() { - highlightPreferenceIfNeeded(); + highlightPreferenceIfNeeded(true); } }; @@ -121,8 +121,10 @@ public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceF } public void setPinnedHeaderView(View pinnedHeader) { - mPinnedHeaderFrameLayout.addView(pinnedHeader); - mPinnedHeaderFrameLayout.setVisibility(View.VISIBLE); + if (mPinnedHeaderFrameLayout != null) { + mPinnedHeaderFrameLayout.addView(pinnedHeader); + mPinnedHeaderFrameLayout.setVisibility(View.VISIBLE); + } } @Override @@ -147,7 +149,7 @@ public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceF final Bundle args = getArguments(); if (args != null) { mPreferenceKey = args.getString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY); - highlightPreferenceIfNeeded(); + highlightPreferenceIfNeeded(false); } } @@ -194,8 +196,9 @@ public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceF } } - public void highlightPreferenceIfNeeded() { - if (isAdded() && !mPreferenceHighlighted &&!TextUtils.isEmpty(mPreferenceKey)) { + public void highlightPreferenceIfNeeded(boolean forceHighlight) { + if (isAdded() && (!mPreferenceHighlighted || forceHighlight) + && !TextUtils.isEmpty(mPreferenceKey)) { highlightPreference(mPreferenceKey); } } diff --git a/src/com/android/settings/SubSettings.java b/src/com/android/settings/SubSettings.java index 04955b2..2bf451e 100644 --- a/src/com/android/settings/SubSettings.java +++ b/src/com/android/settings/SubSettings.java @@ -35,4 +35,6 @@ public class SubSettings extends SettingsActivity { Log.d("SubSettings", "Launching fragment " + fragmentName); return true; } + public static class BluetoothSubSettings extends SubSettings { /* empty */ } + public static class SecuritySubSettings extends SubSettings { /* empty */ } } diff --git a/src/com/android/settings/TetherSettings.java b/src/com/android/settings/TetherSettings.java index a0cd4da..594dc68 100644 --- a/src/com/android/settings/TetherSettings.java +++ b/src/com/android/settings/TetherSettings.java @@ -40,6 +40,7 @@ import android.os.Bundle; import android.os.Environment; import android.os.UserHandle; import android.os.UserManager; +import android.preference.ListPreference; import android.preference.Preference; import android.preference.PreferenceScreen; import android.preference.SwitchPreference; @@ -64,6 +65,7 @@ public class TetherSettings extends SettingsPreferenceFragment private static final String ENABLE_WIFI_AP = "enable_wifi_ap"; private static final String ENABLE_BLUETOOTH_TETHERING = "enable_bluetooth_tethering"; private static final String TETHER_CHOICE = "TETHER_TYPE"; + private static final String HOTSPOT_TIMEOUT = "hotstpot_inactivity_timeout"; private static final int DIALOG_AP_SETTINGS = 1; @@ -74,6 +76,8 @@ public class TetherSettings extends SettingsPreferenceFragment private SwitchPreference mBluetoothTether; + private ListPreference mHotspotInactivityTimeout; + private BroadcastReceiver mTetherChangeReceiver; private String[] mUsbRegexs; @@ -143,6 +147,7 @@ public class TetherSettings extends SettingsPreferenceFragment Preference wifiApSettings = findPreference(WIFI_AP_SSID_AND_SECURITY); mUsbTether = (SwitchPreference) findPreference(USB_TETHER_SETTINGS); mBluetoothTether = (SwitchPreference) findPreference(ENABLE_BLUETOOTH_TETHERING); + mHotspotInactivityTimeout = (ListPreference) findPreference(HOTSPOT_TIMEOUT); ConnectivityManager cm = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); @@ -165,6 +170,7 @@ public class TetherSettings extends SettingsPreferenceFragment } else { getPreferenceScreen().removePreference(mEnableWifiAp); getPreferenceScreen().removePreference(wifiApSettings); + getPreferenceScreen().removePreference(mHotspotInactivityTimeout); } if (!bluetoothAvailable) { @@ -180,6 +186,7 @@ public class TetherSettings extends SettingsPreferenceFragment mProvisionApp = getResources().getStringArray( com.android.internal.R.array.config_mobile_hotspot_provision_app); + mHotspotInactivityTimeout.setOnPreferenceChangeListener(this); } @Override @@ -207,6 +214,25 @@ public class TetherSettings extends SettingsPreferenceFragment mWifiConfig.SSID, mSecurityType[index])); } + updateHotspotTimeoutSummary(mWifiConfig); + } + + private void updateHotspotTimeoutSummary(WifiConfiguration wifiConfig) { + if (wifiConfig == null) { + mHotspotInactivityTimeout.setValue("0"); + mHotspotInactivityTimeout.setSummary( + getString(R.string.hotstpot_inactivity_timeout_never_summary_text)); + } else { + mHotspotInactivityTimeout.setValue(Long.toString(wifiConfig.wifiApInactivityTimeout)); + if (wifiConfig.wifiApInactivityTimeout > 0) { + mHotspotInactivityTimeout.setSummary(String.format( + getString(R.string.hotstpot_inactivity_timeout_summary_text, + mHotspotInactivityTimeout.getEntry()))); + } else { + mHotspotInactivityTimeout.setSummary( + getString(R.string.hotstpot_inactivity_timeout_never_summary_text)); + } + } } private BluetoothProfile.ServiceListener mProfileServiceListener = @@ -458,15 +484,30 @@ public class TetherSettings extends SettingsPreferenceFragment } public boolean onPreferenceChange(Preference preference, Object value) { - boolean enable = (Boolean) value; + if (preference == mEnableWifiAp) { + boolean enable = (Boolean) value; - if (enable) { - startProvisioningIfNecessary(TETHERING_WIFI); - } else { - if (TetherUtil.isProvisioningNeeded(getActivity())) { - TetherService.cancelRecheckAlarmIfNecessary(getActivity(), TETHERING_WIFI); + if (enable) { + startProvisioningIfNecessary(TETHERING_WIFI); + } else { + if (TetherUtil.isProvisioningNeeded(getActivity())) { + TetherService.cancelRecheckAlarmIfNecessary(getActivity(), TETHERING_WIFI); + } + mWifiApEnabler.setSoftapEnabled(false); + } + return false; + } else if (preference == mHotspotInactivityTimeout) { + if (mWifiConfig != null) { + mWifiConfig.wifiApInactivityTimeout = Long.parseLong((String) value); + updateHotspotTimeoutSummary(mWifiConfig); + if (mWifiManager.getWifiApState() == WifiManager.WIFI_AP_STATE_ENABLED) { + mWifiManager.setWifiApEnabled(null, false); + mWifiManager.setWifiApEnabled(mWifiConfig, true); + } else { + mWifiManager.setWifiApConfiguration(mWifiConfig); + } + return true; } - mWifiApEnabler.setSoftapEnabled(false); } return false; } diff --git a/src/com/android/settings/TrustAgentUtils.java b/src/com/android/settings/TrustAgentUtils.java index 109663a..7fed4ec 100644 --- a/src/com/android/settings/TrustAgentUtils.java +++ b/src/com/android/settings/TrustAgentUtils.java @@ -55,7 +55,7 @@ public class TrustAgentUtils { public static class TrustAgentComponentInfo { ComponentName componentName; - String title; + public String title; String summary; boolean disabledByAdministrator; } diff --git a/src/com/android/settings/UserAdapter.java b/src/com/android/settings/UserAdapter.java index dcdc7eb..2ac908e 100644 --- a/src/com/android/settings/UserAdapter.java +++ b/src/com/android/settings/UserAdapter.java @@ -33,7 +33,7 @@ import android.widget.SpinnerAdapter; import android.widget.TextView; import com.android.internal.util.UserIcons; -import com.android.settings.drawable.CircleFramedDrawable; +import com.android.settingslib.drawable.CircleFramedDrawable; import java.util.ArrayList; @@ -173,4 +173,4 @@ public class UserAdapter implements SpinnerAdapter, ListAdapter { public boolean isEnabled(int position) { return true; } -}
\ No newline at end of file +} diff --git a/src/com/android/settings/UserDictionarySettings.java b/src/com/android/settings/UserDictionarySettings.java index 1e9fd0a..78e033a 100644 --- a/src/com/android/settings/UserDictionarySettings.java +++ b/src/com/android/settings/UserDictionarySettings.java @@ -165,7 +165,7 @@ public class UserDictionarySettings extends ListFragment { public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { MenuItem actionItem = menu.add(0, OPTIONS_MENU_ADD, 0, R.string.user_dict_settings_add_menu_title) - .setIcon(R.drawable.ic_menu_add_dark); + .setIcon(R.drawable.ic_menu_add_word); actionItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT); } diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java index d2d04ec..49b93e6 100644 --- a/src/com/android/settings/Utils.java +++ b/src/com/android/settings/Utils.java @@ -17,6 +17,7 @@ package com.android.settings; import android.annotation.Nullable; +import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityManagerNative; import android.app.AlertDialog; @@ -31,6 +32,7 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.IntentFilterVerificationInfo; @@ -40,6 +42,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.Signature; import android.content.pm.UserInfo; +import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.Resources.NotFoundException; import android.content.res.TypedArray; @@ -52,11 +55,13 @@ import android.net.ConnectivityManager; import android.net.LinkProperties; import android.net.Uri; import android.os.BatteryManager; +import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.INetworkManagementService; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.os.storage.StorageManager; @@ -68,7 +73,9 @@ import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Data; import android.provider.ContactsContract.Profile; import android.provider.ContactsContract.RawContacts; +import android.provider.Settings; import android.service.persistentdata.PersistentDataBlockManager; +import android.telephony.ServiceState; import android.telephony.TelephonyManager; import android.text.Spannable; import android.text.SpannableString; @@ -78,6 +85,7 @@ import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; import android.view.LayoutInflater; +import android.view.Surface; import android.view.View; import android.view.ViewGroup; import android.view.animation.Animation; @@ -88,9 +96,10 @@ import android.widget.TabWidget; import com.android.internal.util.UserIcons; import com.android.settings.UserAdapter.UserDetails; +import com.android.settings.bluetooth.BluetoothSettings; import com.android.settings.dashboard.DashboardTile; -import com.android.settings.drawable.CircleFramedDrawable; import com.android.settingslib.applications.ApplicationsState; +import com.android.settingslib.drawable.CircleFramedDrawable; import java.io.IOException; import java.io.InputStream; @@ -208,6 +217,98 @@ public final class Utils { return false; } + /** + * Finds a matching activity for a preference's intent. If a matching + * activity is not found, it will remove the preference. The icon, title and + * summary of the preference will also be updated with the values retrieved + * from the activity's meta-data elements. If no meta-data elements are + * specified then the preference title will be set to match the label of the + * activity, an icon and summary text will not be displayed. + * + * @param context The context. + * @param parentPreferenceGroup The preference group that contains the + * preference whose intent is being resolved. + * @param preferenceKey The key of the preference whose intent is being + * resolved. + * + * @return Whether an activity was found. If false, the preference was + * removed. + * + * @see {@link #META_DATA_PREFERENCE_ICON} + * {@link #META_DATA_PREFERENCE_TITLE} + * {@link #META_DATA_PREFERENCE_SUMMARY} + */ + public static boolean updatePreferenceToSpecificActivityFromMetaDataOrRemove(Context context, + PreferenceGroup parentPreferenceGroup, String preferenceKey) { + + Preference preference = parentPreferenceGroup.findPreference(preferenceKey); + if (preference == null) { + return false; + } + + Intent intent = preference.getIntent(); + if (intent != null) { + // Find the activity that is in the system image + PackageManager pm = context.getPackageManager(); + List<ResolveInfo> list = pm.queryIntentActivities(intent, PackageManager.GET_META_DATA); + int listSize = list.size(); + for (int i = 0; i < listSize; i++) { + ResolveInfo resolveInfo = list.get(i); + if ((resolveInfo.activityInfo.applicationInfo.flags + & ApplicationInfo.FLAG_SYSTEM) != 0) { + Drawable icon = null; + String title = null; + String summary = null; + + // Get the activity's meta-data + try { + Resources res = pm + .getResourcesForApplication(resolveInfo.activityInfo.packageName); + Bundle metaData = resolveInfo.activityInfo.metaData; + + if (res != null && metaData != null) { + if (preference instanceof IconPreferenceScreen) { + icon = res.getDrawable(metaData.getInt(META_DATA_PREFERENCE_ICON)); + } + title = res.getString(metaData.getInt(META_DATA_PREFERENCE_TITLE)); + summary = res.getString(metaData.getInt(META_DATA_PREFERENCE_SUMMARY)); + } + } catch (NameNotFoundException e) { + // Ignore + } catch (NotFoundException e) { + // Ignore + } + + // Set the preference title to the activity's label if no + // meta-data is found + if (TextUtils.isEmpty(title)) { + title = resolveInfo.loadLabel(pm).toString(); + } + + // Set icon, title and summary for the preference + preference.setTitle(title); + preference.setSummary(summary); + if (preference instanceof IconPreferenceScreen) { + IconPreferenceScreen iconPreference = (IconPreferenceScreen) preference; + iconPreference.setIcon(icon); + } + + // Replace the intent with this specific activity + preference.setIntent(new Intent().setClassName( + resolveInfo.activityInfo.packageName, + resolveInfo.activityInfo.name)); + + return true; + } + } + } + + // Did not find a matching activity, so remove the preference + parentPreferenceGroup.removePreference(preference); + + return false; + } + public static boolean updateTileToSpecificActivityFromMetaDataOrRemove(Context context, DashboardTile tile) { @@ -260,8 +361,10 @@ public final class Utils { } // Set icon, title and summary for the preference - tile.iconRes = icon; - tile.iconPkg = resolveInfo.activityInfo.packageName; + if (icon != 0) { + tile.iconRes = icon; + tile.iconPkg = resolveInfo.activityInfo.packageName; + } tile.title = title; tile.summary = summary; // Replace the intent with this specific activity @@ -384,6 +487,20 @@ public final class Utils { return (level * 100) / scale; } + public static boolean isDockBatteryPresent(Intent batteryChangedIntent) { + return batteryChangedIntent.getBooleanExtra(BatteryManager.EXTRA_DOCK_PRESENT, true); + } + + public static String getDockBatteryPercentage(Intent batteryChangedIntent) { + return formatPercentage(getDockBatteryLevel(batteryChangedIntent)); + } + + public static int getDockBatteryLevel(Intent batteryChangedIntent) { + int level = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_DOCK_LEVEL, 0); + int scale = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_DOCK_SCALE, 100); + return (level * 100) / scale; + } + public static String getBatteryStatus(Resources res, Intent batteryChangedIntent) { final Intent intent = batteryChangedIntent; @@ -416,6 +533,36 @@ public final class Utils { return statusString; } + public static String getDockBatteryStatus(Resources res, Intent batteryChangedIntent) { + final Intent intent = batteryChangedIntent; + + int plugType = intent.getIntExtra(BatteryManager.EXTRA_DOCK_PLUGGED, 0); + int status = intent.getIntExtra(BatteryManager.EXTRA_DOCK_STATUS, + BatteryManager.BATTERY_STATUS_UNKNOWN); + String statusString; + if (status == BatteryManager.BATTERY_STATUS_CHARGING) { + int resId; + if (plugType == BatteryManager.BATTERY_DOCK_PLUGGED_AC) { + resId = R.string.battery_info_status_charging_dock_ac; + } else if (plugType == BatteryManager.BATTERY_DOCK_PLUGGED_USB) { + resId = R.string.battery_info_status_charging_dock_usb; + } else { + resId = R.string.battery_info_status_charging; + } + statusString = res.getString(resId); + } else if (status == BatteryManager.BATTERY_STATUS_DISCHARGING) { + statusString = res.getString(R.string.battery_info_status_discharging); + } else if (status == BatteryManager.BATTERY_STATUS_NOT_CHARGING) { + statusString = res.getString(R.string.battery_info_status_not_charging); + } else if (status == BatteryManager.BATTERY_STATUS_FULL) { + statusString = res.getString(R.string.battery_info_status_full); + } else { + statusString = res.getString(R.string.battery_info_status_unknown); + } + + return statusString; + } + public static void forcePrepareCustomPreferencesList( ViewGroup parent, View child, ListView list, boolean ignoreSidePadding) { list.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY); @@ -460,36 +607,6 @@ public final class Utils { view.setPaddingRelative(paddingStart, 0, paddingEnd, paddingBottom); } - /** - * Return string resource that best describes combination of tethering - * options available on this device. - */ - public static int getTetheringLabel(ConnectivityManager cm) { - String[] usbRegexs = cm.getTetherableUsbRegexs(); - String[] wifiRegexs = cm.getTetherableWifiRegexs(); - String[] bluetoothRegexs = cm.getTetherableBluetoothRegexs(); - - boolean usbAvailable = usbRegexs.length != 0; - boolean wifiAvailable = wifiRegexs.length != 0; - boolean bluetoothAvailable = bluetoothRegexs.length != 0; - - if (wifiAvailable && usbAvailable && bluetoothAvailable) { - return R.string.tether_settings_title_all; - } else if (wifiAvailable && usbAvailable) { - return R.string.tether_settings_title_all; - } else if (wifiAvailable && bluetoothAvailable) { - return R.string.tether_settings_title_all; - } else if (wifiAvailable) { - return R.string.tether_settings_title_wifi; - } else if (usbAvailable && bluetoothAvailable) { - return R.string.tether_settings_title_usb_bluetooth; - } else if (usbAvailable) { - return R.string.tether_settings_title_usb; - } else { - return R.string.tether_settings_title_bluetooth; - } - } - /* Used by UserSettings as well. Call this on a non-ui thread. */ public static boolean copyMeProfilePhoto(Context context, UserInfo user) { Uri contactUri = Profile.CONTENT_URI; @@ -607,6 +724,37 @@ public final class Utils { .getUsers().size() > 1; } + public static boolean isRestrictedProfile(Context context) { + UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); + return um.getUserInfo(um.getUserHandle()).isRestricted(); + } + + /* returns whether the device has volume rocker or not. */ + public static boolean hasVolumeRocker(Context context) { + final int deviceKeys = context.getResources().getInteger( + com.android.internal.R.integer.config_deviceHardwareKeys); + return (deviceKeys & ButtonSettings.KEY_MASK_VOLUME) != 0; + } + + public static boolean isPackageInstalled(Context context, String pkg, boolean ignoreState) { + if (pkg != null) { + try { + PackageInfo pi = context.getPackageManager().getPackageInfo(pkg, 0); + if (!pi.applicationInfo.enabled && !ignoreState) { + return false; + } + } catch (NameNotFoundException e) { + return false; + } + } + + return true; + } + + public static boolean isPackageInstalled(Context context, String pkg) { + return isPackageInstalled(context, pkg, true); + } + /** * Start a new instance of the activity, showing only the given fragment. * When launched in this mode, the given preference fragment will be instantiated and fill the @@ -716,7 +864,15 @@ public final class Utils { Bundle args, String titleResPackageName, int titleResId, CharSequence title, boolean isShortcut) { Intent intent = new Intent(Intent.ACTION_MAIN); - intent.setClass(context, SubSettings.class); + if (BluetoothSettings.class.getName().equals(fragmentName)) { + intent.setClass(context, SubSettings.BluetoothSubSettings.class); + intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, true); + } else if (SecuritySettings.class.getName().equals(fragmentName)) { + intent.setClass(context, SubSettings.SecuritySubSettings.class); + intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, true); + } else { + intent.setClass(context, SubSettings.class); + } intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, fragmentName); intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, args); intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RES_PACKAGE_NAME, @@ -905,45 +1061,6 @@ public final class Utils { } /** - * Returns a circular icon for a user. - */ - public static Drawable getUserIcon(Context context, UserManager um, UserInfo user) { - if (user.isManagedProfile()) { - // We use predefined values for managed profiles - Bitmap b = BitmapFactory.decodeResource(context.getResources(), - com.android.internal.R.drawable.ic_corp_icon); - return CircleFramedDrawable.getInstance(context, b); - } - if (user.iconPath != null) { - Bitmap icon = um.getUserIcon(user.id); - if (icon != null) { - return CircleFramedDrawable.getInstance(context, icon); - } - } - return CircleFramedDrawable.getInstance(context, UserIcons.convertToBitmap( - UserIcons.getDefaultUserIcon(user.id, /* light= */ false))); - } - - /** - * Returns a label for the user, in the form of "User: user name" or "Work profile". - */ - public static String getUserLabel(Context context, UserInfo info) { - String name = info != null ? info.name : null; - if (info.isManagedProfile()) { - // We use predefined values for managed profiles - return context.getString(R.string.managed_user_title); - } else if (info.isGuest()) { - name = context.getString(R.string.user_guest); - } - if (name == null && info != null) { - name = Integer.toString(info.id); - } else if (info == null) { - name = context.getString(R.string.unknown); - } - return context.getResources().getString(R.string.running_process_item_user_label, name); - } - - /** * Return whether or not the user should have a SIM Cards option in Settings. * TODO: Change back to returning true if count is greater than one after testing. * TODO: See bug 16533525. @@ -951,8 +1068,8 @@ public final class Utils { public static boolean showSimCardTile(Context context) { final TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - - return tm.getSimCount() > 1; + final boolean isPrimary = UserHandle.myUserId() == UserHandle.USER_OWNER; + return isPrimary && tm.getSimCount() > 1; } /** @@ -1245,5 +1362,100 @@ public final class Utils { return UserHandle.myUserId(); } } -} + public static boolean isDozeAvailable(Context context) { + String name = Build.IS_DEBUGGABLE ? SystemProperties.get("debug.doze.component") : null; + if (TextUtils.isEmpty(name)) { + name = context.getResources().getString( + com.android.internal.R.string.config_dozeComponent); + } + return !TextUtils.isEmpty(name); + } + + public static String getServiceStateString(int state, Resources res) { + switch (state) { + case ServiceState.STATE_IN_SERVICE: + return res.getString(R.string.radioInfo_service_in); + case ServiceState.STATE_OUT_OF_SERVICE: + case ServiceState.STATE_EMERGENCY_ONLY: + return res.getString(R.string.radioInfo_service_out); + case ServiceState.STATE_POWER_OFF: + return res.getString(R.string.radioInfo_service_off); + default: + return res.getString(R.string.radioInfo_unknown); + } + } + + /** + * Locks the activity orientation to the current device orientation + * @param activity + */ + public static void lockCurrentOrientation(Activity activity) { + int currentRotation = activity.getWindowManager().getDefaultDisplay().getRotation(); + int orientation = activity.getResources().getConfiguration().orientation; + int frozenRotation = 0; + switch (currentRotation) { + case Surface.ROTATION_0: + frozenRotation = orientation == Configuration.ORIENTATION_LANDSCAPE + ? ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE + : ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; + break; + case Surface.ROTATION_90: + frozenRotation = orientation == Configuration.ORIENTATION_PORTRAIT + ? ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT + : ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; + break; + case Surface.ROTATION_180: + frozenRotation = orientation == Configuration.ORIENTATION_LANDSCAPE + ? ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE + : ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; + break; + case Surface.ROTATION_270: + frozenRotation = orientation == Configuration.ORIENTATION_PORTRAIT + ? ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + : ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; + break; + } + activity.setRequestedOrientation(frozenRotation); + } + + public static boolean isUserOwner() { + return UserHandle.myUserId() == UserHandle.USER_OWNER; + } + + public static boolean canUserMakeCallsSms(Context context) { + UserManager userManager = UserManager.get(context); + UserHandle userHandle = new UserHandle(UserHandle.myUserId()); + boolean callSmsNotAllowed = userManager.hasUserRestriction( + userManager.DISALLOW_OUTGOING_CALLS, userHandle); + callSmsNotAllowed &= userManager.hasUserRestriction( + UserManager.DISALLOW_SMS, userHandle); + return !callSmsNotAllowed; + } + + public static String join(Resources res, List<String> items) { + final int count = items.size(); + if (items.isEmpty()) { + return null; + } else if (count == 1) { + return items.get(0); + } else if (count == 2) { + return res.getString(R.string.join_two_items, items.get(0), items.get(1)); + } else { + String middle = items.get(count - 2); + for (int i = count - 3; i > 0; i--) { + middle = res.getString(R.string.join_many_items_middle, + items.get(i), middle); + } + final String allButLast = res.getString(R.string.join_many_items_first, + items.get(0), middle); + return res.getString(R.string.join_many_items_last, allButLast, + items.get(count - 1)); + } + } + + public static boolean isAirplaneModeEnabled(Context context) { + return Settings.Global.getInt(context.getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, 0) != 0; + } +} diff --git a/src/com/android/settings/WifiCallingSettings.java b/src/com/android/settings/WifiCallingSettings.java index 787ccb4..fe83dda 100644 --- a/src/com/android/settings/WifiCallingSettings.java +++ b/src/com/android/settings/WifiCallingSettings.java @@ -23,9 +23,11 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; +import android.os.PersistableBundle; import android.preference.ListPreference; import android.preference.Preference; import android.preference.PreferenceScreen; +import android.telephony.CarrierConfigManager; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; import android.util.Log; @@ -58,6 +60,7 @@ public class WifiCallingSettings extends SettingsPreferenceFragment private TextView mEmptyView; private boolean mValidListener = false; + private boolean mEditableWfcMode = true; private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() { /* @@ -161,6 +164,15 @@ public class WifiCallingSettings extends SettingsPreferenceFragment mIntentFilter = new IntentFilter(); mIntentFilter.addAction(ImsManager.ACTION_IMS_REGISTRATION_ERROR); + + CarrierConfigManager configManager = (CarrierConfigManager) + getSystemService(Context.CARRIER_CONFIG_SERVICE); + if (configManager != null) { + PersistableBundle b = configManager.getConfig(); + if (b != null) { + mEditableWfcMode = b.getBoolean(CarrierConfigManager.KEY_EDITABLE_WFC_MODE_BOOL); + } + } } @Override @@ -178,6 +190,12 @@ public class WifiCallingSettings extends SettingsPreferenceFragment mValidListener = true; } + if (!isWfcModeSupported()) { + android.provider.Settings.Global.putInt(context.getContentResolver(), + android.provider.Settings.Global.WFC_IMS_MODE, + ImsConfig.WfcModeFeatureValueConstants.WIFI_PREFERRED); + } + // NOTE: Buttons will be enabled/disabled in mPhoneStateListener boolean wfcEnabled = ImsManager.isWfcEnabledByUser(context) && ImsManager.isNonTtyOrTtyOnVolteEnabled(context); @@ -235,11 +253,12 @@ public class WifiCallingSettings extends SettingsPreferenceFragment mButtonWfcMode.setEnabled(wfcEnabled); final PreferenceScreen preferenceScreen = getPreferenceScreen(); - if (wfcEnabled) { + if (wfcEnabled && isWfcModeSupported()) { preferenceScreen.addPreference(mButtonWfcMode); } else { preferenceScreen.removePreference(mButtonWfcMode); } + preferenceScreen.setEnabled(mEditableWfcMode); } @Override @@ -277,4 +296,9 @@ public class WifiCallingSettings extends SettingsPreferenceFragment } return resId; } + + private boolean isWfcModeSupported() { + return getActivity().getResources().getBoolean( + R.bool.config_wfc_mode_supported); + } } diff --git a/src/com/android/settings/WirelessSettings.java b/src/com/android/settings/WirelessSettings.java index 8cc98cc..4a0c9e3 100644 --- a/src/com/android/settings/WirelessSettings.java +++ b/src/com/android/settings/WirelessSettings.java @@ -37,6 +37,7 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.preference.Preference; +import android.preference.PreferenceCategory; import android.preference.PreferenceScreen; import android.preference.SwitchPreference; import android.provider.SearchIndexableResource; @@ -61,6 +62,7 @@ public class WirelessSettings extends SettingsPreferenceFragment implements Inde private static final String TAG = "WirelessSettings"; private static final String KEY_TOGGLE_AIRPLANE = "toggle_airplane"; + private static final String KEY_NFC_CATEGORY_SETTINGS = "nfc_category_settings"; private static final String KEY_TOGGLE_NFC = "toggle_nfc"; private static final String KEY_WIMAX_SETTINGS = "wimax_settings"; private static final String KEY_ANDROID_BEAM_SETTINGS = "android_beam_settings"; @@ -72,6 +74,7 @@ public class WirelessSettings extends SettingsPreferenceFragment implements Inde private static final String KEY_TOGGLE_NSD = "toggle_nsd"; //network service discovery private static final String KEY_CELL_BROADCAST_SETTINGS = "cell_broadcast_settings"; private static final String KEY_WFC_SETTINGS = "wifi_calling_settings"; + private static final String KEY_NFC_PAYMENT_SETTINGS = "nfc_payment_settings"; public static final String EXIT_ECM_RESULT = "exit_ecm_result"; public static final int REQUEST_CODE_EXIT_ECM = 1; @@ -232,10 +235,13 @@ public class WirelessSettings extends SettingsPreferenceFragment implements Inde mAirplaneModePreference = (SwitchPreference) findPreference(KEY_TOGGLE_AIRPLANE); SwitchPreference nfc = (SwitchPreference) findPreference(KEY_TOGGLE_NFC); PreferenceScreen androidBeam = (PreferenceScreen) findPreference(KEY_ANDROID_BEAM_SETTINGS); + PreferenceScreen nfcPayment = (PreferenceScreen) findPreference(KEY_NFC_PAYMENT_SETTINGS); SwitchPreference nsd = (SwitchPreference) findPreference(KEY_TOGGLE_NSD); + PreferenceCategory nfcCategory = (PreferenceCategory) + findPreference(KEY_NFC_CATEGORY_SETTINGS); mAirplaneModeEnabler = new AirplaneModeEnabler(activity, mAirplaneModePreference); - mNfcEnabler = new NfcEnabler(activity, nfc, androidBeam); + mNfcEnabler = new NfcEnabler(activity, nfc, androidBeam, nfcPayment); mButtonWfc = (PreferenceScreen) findPreference(KEY_WFC_SETTINGS); @@ -280,14 +286,17 @@ public class WirelessSettings extends SettingsPreferenceFragment implements Inde if (toggleable == null || !toggleable.contains(Settings.Global.RADIO_NFC)) { findPreference(KEY_TOGGLE_NFC).setDependency(KEY_TOGGLE_AIRPLANE); findPreference(KEY_ANDROID_BEAM_SETTINGS).setDependency(KEY_TOGGLE_AIRPLANE); + findPreference(KEY_NFC_PAYMENT_SETTINGS).setDependency(KEY_TOGGLE_AIRPLANE); } // Remove NFC if not available mNfcAdapter = NfcAdapter.getDefaultAdapter(activity); if (mNfcAdapter == null) { - getPreferenceScreen().removePreference(nfc); - getPreferenceScreen().removePreference(androidBeam); + getPreferenceScreen().removePreference(nfcCategory); mNfcEnabler = null; + } else if (!mPm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) { + // Only show if we have the HCE feature + nfcCategory.removePreference(nfcPayment); } // Remove Mobile Network Settings and Manage Mobile Plan for secondary users, @@ -301,7 +310,7 @@ public class WirelessSettings extends SettingsPreferenceFragment implements Inde // if config_show_mobile_plan sets false. final boolean isMobilePlanEnabled = this.getResources().getBoolean( R.bool.config_show_mobile_plan); - if (!isMobilePlanEnabled) { + if (!isMobilePlanEnabled || mCm.getMobileProvisioningUrl().isEmpty()) { Preference pref = findPreference(KEY_MANAGE_MOBILE_PLAN); if (pref != null) { removePreference(KEY_MANAGE_MOBILE_PLAN); @@ -329,7 +338,7 @@ public class WirelessSettings extends SettingsPreferenceFragment implements Inde getPreferenceScreen().removePreference(findPreference(KEY_TETHER_SETTINGS)); } else { Preference p = findPreference(KEY_TETHER_SETTINGS); - p.setTitle(Utils.getTetheringLabel(cm)); + p.setTitle(com.android.settingslib.Utils.getTetheringLabel(cm)); // Grey out if provisioning is not available. p.setEnabled(!TetherSettings @@ -438,6 +447,7 @@ public class WirelessSettings extends SettingsPreferenceFragment implements Inde result.add(KEY_TOGGLE_NSD); + final PackageManager pm = context.getPackageManager(); final UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); final int myUserId = UserHandle.myUserId(); final boolean isSecondaryUser = myUserId != UserHandle.USER_OWNER; @@ -461,6 +471,11 @@ public class WirelessSettings extends SettingsPreferenceFragment implements Inde if (adapter == null) { result.add(KEY_TOGGLE_NFC); result.add(KEY_ANDROID_BEAM_SETTINGS); + result.add(KEY_NFC_PAYMENT_SETTINGS); + } else if (!pm.hasSystemFeature( + PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) { + // Only show if we have the HCE feature + result.add(KEY_NFC_PAYMENT_SETTINGS); } } @@ -470,16 +485,17 @@ public class WirelessSettings extends SettingsPreferenceFragment implements Inde result.add(KEY_MANAGE_MOBILE_PLAN); } + ConnectivityManager cm = (ConnectivityManager) + context.getSystemService(Context.CONNECTIVITY_SERVICE); + // Remove Mobile Network Settings and Manage Mobile Plan // if config_show_mobile_plan sets false. final boolean isMobilePlanEnabled = context.getResources().getBoolean( R.bool.config_show_mobile_plan); - if (!isMobilePlanEnabled) { + if (!isMobilePlanEnabled || cm.getMobileProvisioningUrl().isEmpty()) { result.add(KEY_MANAGE_MOBILE_PLAN); } - final PackageManager pm = context.getPackageManager(); - // Remove Airplane Mode settings if it's a stationary device such as a TV. if (pm.hasSystemFeature(PackageManager.FEATURE_TELEVISION)) { result.add(KEY_TOGGLE_AIRPLANE); @@ -489,8 +505,6 @@ public class WirelessSettings extends SettingsPreferenceFragment implements Inde result.add(KEY_PROXY_SETTINGS); // Disable Tethering if it's not allowed or if it's a wifi-only device - ConnectivityManager cm = (ConnectivityManager) - context.getSystemService(Context.CONNECTIVITY_SERVICE); if (isSecondaryUser || !cm.isTetheringSupported()) { result.add(KEY_TETHER_SETTINGS); } @@ -512,7 +526,12 @@ public class WirelessSettings extends SettingsPreferenceFragment implements Inde result.add(KEY_CELL_BROADCAST_SETTINGS); } + if (!ImsManager.isWfcEnabledByPlatform(context)) { + result.add(KEY_WFC_SETTINGS); + } + return result; } }; + } diff --git a/src/com/android/settings/accessibility/AccessibilitySettings.java b/src/com/android/settings/accessibility/AccessibilitySettings.java index 96577a0..1dd5508 100644 --- a/src/com/android/settings/accessibility/AccessibilitySettings.java +++ b/src/com/android/settings/accessibility/AccessibilitySettings.java @@ -44,8 +44,6 @@ import android.view.KeyEvent; import android.view.accessibility.AccessibilityManager; import com.android.internal.content.PackageMonitor; import com.android.internal.logging.MetricsLogger; -import com.android.internal.view.RotationPolicy; -import com.android.internal.view.RotationPolicy.RotationPolicyListener; import com.android.settings.DialogCreatable; import com.android.settings.R; import com.android.settings.SettingsPreferenceFragment; @@ -82,10 +80,6 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements "toggle_high_text_contrast_preference"; private static final String TOGGLE_INVERSION_PREFERENCE = "toggle_inversion_preference"; - private static final String TOGGLE_POWER_BUTTON_ENDS_CALL_PREFERENCE = - "toggle_power_button_ends_call_preference"; - private static final String TOGGLE_LOCK_SCREEN_ROTATION_PREFERENCE = - "toggle_lock_screen_rotation_preference"; private static final String TOGGLE_SPEAK_PASSWORD_PREFERENCE = "toggle_speak_password_preference"; private static final String SELECT_LONG_PRESS_TIMEOUT_PREFERENCE = @@ -166,25 +160,16 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements @Override public void onChange(boolean selfChange, Uri uri) { loadInstalledServices(); - updateServicesPreferences(); + updateAllPreferences(); } }; - private final RotationPolicyListener mRotationPolicyListener = new RotationPolicyListener() { - @Override - public void onChange() { - updateLockScreenRotationCheckbox(); - } - }; - // Preference controls. private PreferenceCategory mServicesCategory; private PreferenceCategory mSystemsCategory; private SwitchPreference mToggleLargeTextPreference; private SwitchPreference mToggleHighTextContrastPreference; - private SwitchPreference mTogglePowerButtonEndsCallPreference; - private SwitchPreference mToggleLockScreenRotationPreference; private SwitchPreference mToggleSpeakPasswordPreference; private ListPreference mSelectLongPressTimeoutPreference; private Preference mNoServicesMessagePreference; @@ -225,20 +210,12 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements mSettingsPackageMonitor.register(getActivity(), getActivity().getMainLooper(), false); mSettingsContentObserver.register(getContentResolver()); - if (RotationPolicy.isRotationSupported(getActivity())) { - RotationPolicy.registerRotationPolicyListener(getActivity(), - mRotationPolicyListener); - } } @Override public void onPause() { mSettingsPackageMonitor.unregister(); mSettingsContentObserver.unregister(getContentResolver()); - if (RotationPolicy.isRotationSupported(getActivity())) { - RotationPolicy.unregisterRotationPolicyListener(getActivity(), - mRotationPolicyListener); - } super.onPause(); } @@ -274,12 +251,6 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements } else if (mToggleHighTextContrastPreference == preference) { handleToggleTextContrastPreferenceClick(); return true; - } else if (mTogglePowerButtonEndsCallPreference == preference) { - handleTogglePowerButtonEndsCallPreferenceClick(); - return true; - } else if (mToggleLockScreenRotationPreference == preference) { - handleLockScreenRotationPreferenceClick(); - return true; } else if (mToggleSpeakPasswordPreference == preference) { handleToggleSpeakPasswordPreferenceClick(); return true; @@ -308,19 +279,6 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements (mToggleHighTextContrastPreference.isChecked() ? 1 : 0)); } - private void handleTogglePowerButtonEndsCallPreferenceClick() { - Settings.Secure.putInt(getContentResolver(), - Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR, - (mTogglePowerButtonEndsCallPreference.isChecked() - ? Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP - : Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF)); - } - - private void handleLockScreenRotationPreferenceClick() { - RotationPolicy.setRotationLockForAccessibility(getActivity(), - !mToggleLockScreenRotationPreference.isChecked()); - } - private void handleToggleSpeakPasswordPreferenceClick() { Settings.Secure.putInt(getContentResolver(), Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, @@ -367,21 +325,6 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements mToggleInversionPreference = (SwitchPreference) findPreference(TOGGLE_INVERSION_PREFERENCE); mToggleInversionPreference.setOnPreferenceChangeListener(this); - // Power button ends calls. - mTogglePowerButtonEndsCallPreference = - (SwitchPreference) findPreference(TOGGLE_POWER_BUTTON_ENDS_CALL_PREFERENCE); - if (!KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_POWER) - || !Utils.isVoiceCapable(getActivity())) { - mSystemsCategory.removePreference(mTogglePowerButtonEndsCallPreference); - } - - // Lock screen rotation. - mToggleLockScreenRotationPreference = - (SwitchPreference) findPreference(TOGGLE_LOCK_SCREEN_ROTATION_PREFERENCE); - if (!RotationPolicy.isRotationSupported(getActivity())) { - mSystemsCategory.removePreference(mToggleLockScreenRotationPreference); - } - // Speak passwords. mToggleSpeakPasswordPreference = (SwitchPreference) findPreference(TOGGLE_SPEAK_PASSWORD_PREFERENCE); @@ -549,20 +492,6 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements mToggleInversionPreference.setChecked(Settings.Secure.getInt(getContentResolver(), Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, 0) == 1); - // Power button ends calls. - if (KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_POWER) - && Utils.isVoiceCapable(getActivity())) { - final int incallPowerBehavior = Settings.Secure.getInt(getContentResolver(), - Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR, - Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_DEFAULT); - final boolean powerButtonEndsCall = - (incallPowerBehavior == Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP); - mTogglePowerButtonEndsCallPreference.setChecked(powerButtonEndsCall); - } - - // Auto-rotate screen - updateLockScreenRotationCheckbox(); - // Speak passwords. final boolean speakPasswordEnabled = Settings.Secure.getInt(getContentResolver(), Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) != 0; @@ -600,14 +529,6 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements : R.string.accessibility_feature_state_off); } - private void updateLockScreenRotationCheckbox() { - Context context = getActivity(); - if (context != null) { - mToggleLockScreenRotationPreference.setChecked( - !RotationPolicy.isRotationLocked(context)); - } - } - private void loadInstalledServices() { Set<ComponentName> installedServices = sInstalledServices; installedServices.clear(); diff --git a/src/com/android/settings/accessibility/SettingsContentObserver.java b/src/com/android/settings/accessibility/SettingsContentObserver.java index c3baec5..383f54c 100644 --- a/src/com/android/settings/accessibility/SettingsContentObserver.java +++ b/src/com/android/settings/accessibility/SettingsContentObserver.java @@ -32,6 +32,8 @@ abstract class SettingsContentObserver extends ContentObserver { Settings.Secure.ACCESSIBILITY_ENABLED), false, this); contentResolver.registerContentObserver(Settings.Secure.getUriFor( Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES), false, this); + contentResolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED), false, this); } public void unregister(ContentResolver contentResolver) { diff --git a/src/com/android/settings/accounts/AccountPreferenceBase.java b/src/com/android/settings/accounts/AccountPreferenceBase.java index a34be22..ec5c65b 100644 --- a/src/com/android/settings/accounts/AccountPreferenceBase.java +++ b/src/com/android/settings/accounts/AccountPreferenceBase.java @@ -25,6 +25,7 @@ import android.content.SyncStatusObserver; import android.content.pm.PackageManager; import android.content.res.Resources; import android.content.res.Resources.Theme; +import android.content.res.ThemeConfig; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Handler; @@ -151,8 +152,13 @@ abstract class AccountPreferenceBase extends SettingsPreferenceFragment // correct text colors. Control colors will still be wrong, // but there's not much we can do about it since we can't // reference local color resources. + final ThemeConfig themeConfig = getActivity().getResources() + .getConfiguration().themeConfig; + final String themePkgName = themeConfig != null + ? themeConfig.getOverlayPkgNameForApp(getActivity().getPackageName()) + : null; final Context targetCtx = getActivity().createPackageContextAsUser( - desc.packageName, 0, mUserHandle); + desc.packageName, themePkgName, 0, mUserHandle); final Theme baseTheme = getResources().newTheme(); baseTheme.applyStyle(com.android.settings.R.style.Theme_SettingsBase, true); final Context themedCtx = new ContextThemeWrapper(targetCtx, 0); diff --git a/src/com/android/settings/applications/AppInfoBase.java b/src/com/android/settings/applications/AppInfoBase.java index ff618c2..cd8d1b5 100644 --- a/src/com/android/settings/applications/AppInfoBase.java +++ b/src/com/android/settings/applications/AppInfoBase.java @@ -123,7 +123,8 @@ public abstract class AppInfoBase extends SettingsPreferenceFragment mPackageInfo = mPm.getPackageInfo(mAppEntry.info.packageName, PackageManager.GET_DISABLED_COMPONENTS | PackageManager.GET_UNINSTALLED_PACKAGES | - PackageManager.GET_SIGNATURES); + PackageManager.GET_SIGNATURES | + PackageManager.GET_ACTIVITIES); } catch (NameNotFoundException e) { Log.e(TAG, "Exception when retrieving package:" + mAppEntry.info.packageName, e); } @@ -155,7 +156,7 @@ public abstract class AppInfoBase extends SettingsPreferenceFragment @Override public void onRunningStateChanged(boolean running) { - // No op. + refreshUi(); } @Override diff --git a/src/com/android/settings/applications/AppOpsCategory.java b/src/com/android/settings/applications/AppOpsCategory.java index 03ebb9e..5823443 100644 --- a/src/com/android/settings/applications/AppOpsCategory.java +++ b/src/com/android/settings/applications/AppOpsCategory.java @@ -335,7 +335,7 @@ public class AppOpsCategory extends ListFragment implements SettingsActivity sa = (SettingsActivity) getActivity(); sa.startPreferencePanel(AppOpsDetails.class.getName(), args, - R.string.app_ops_settings, null, this, RESULT_APP_DETAILS); + R.string.privacy_guard_manager_title, null, this, RESULT_APP_DETAILS); } @Override public void onListItemClick(ListView l, View v, int position, long id) { diff --git a/src/com/android/settings/applications/AppOpsDetails.java b/src/com/android/settings/applications/AppOpsDetails.java index a8320b1..1d4a684 100644 --- a/src/com/android/settings/applications/AppOpsDetails.java +++ b/src/com/android/settings/applications/AppOpsDetails.java @@ -16,6 +16,7 @@ package com.android.settings.applications; +import android.Manifest; import android.app.Activity; import android.app.AppOpsManager; import android.app.Fragment; @@ -28,14 +29,22 @@ import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.content.res.Resources; import android.graphics.drawable.Drawable; +import android.net.NetworkPolicyManager; import android.os.Bundle; +import android.os.IDeviceIdleController; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.util.ArraySet; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.AdapterView; import android.widget.CompoundButton; import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.Spinner; import android.widget.Switch; import android.widget.TextView; @@ -45,8 +54,13 @@ import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.Utils; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import static android.net.NetworkPolicyManager.POLICY_REJECT_ON_DATA; +import static android.net.NetworkPolicyManager.POLICY_REJECT_ON_WLAN; + public class AppOpsDetails extends InstrumentedFragment { static final String TAG = "AppOpsDetails"; @@ -59,6 +73,42 @@ public class AppOpsDetails extends InstrumentedFragment { private LayoutInflater mInflater; private View mRootView; private LinearLayout mOperationsSection; + private NetworkPolicyManager mPolicyManager; + + private final int MODE_ALLOWED = 0; + private final int MODE_IGNORED = 1; + private final int MODE_ASK = 2; + + private int modeToPosition (int mode) { + switch(mode) { + case AppOpsManager.MODE_ALLOWED: + return MODE_ALLOWED; + case AppOpsManager.MODE_IGNORED: + return MODE_IGNORED; + case AppOpsManager.MODE_ASK: + return MODE_ASK; + }; + + return MODE_IGNORED; + } + + private int positionToMode (int position) { + switch(position) { + case MODE_ALLOWED: + return AppOpsManager.MODE_ALLOWED; + case MODE_IGNORED: + return AppOpsManager.MODE_IGNORED; + case MODE_ASK: + return AppOpsManager.MODE_ASK; + }; + + return AppOpsManager.MODE_IGNORED; + } + + private boolean isPlatformSigned() { + final int match = mPm.checkSignatures("android", mPackageInfo.packageName); + return match >= PackageManager.SIGNATURE_MATCH; + } // Utility method to set application label and icon. private void setAppLabelAndIcon(PackageInfo pkgInfo) { @@ -66,7 +116,7 @@ public class AppOpsDetails extends InstrumentedFragment { CharSequence label = mPm.getApplicationLabel(pkgInfo.applicationInfo); Drawable icon = mPm.getApplicationIcon(pkgInfo.applicationInfo); InstalledAppDetails.setupAppSnippet(appSnippet, label, icon, - pkgInfo != null ? pkgInfo.versionName : null); + pkgInfo != null ? pkgInfo.versionName : null, null); } private String retrieveAppEntry() { @@ -100,9 +150,31 @@ public class AppOpsDetails extends InstrumentedFragment { Resources res = getActivity().getResources(); + final IDeviceIdleController iDeviceIdleController = IDeviceIdleController.Stub.asInterface( + ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); + List<String> allowInPowerSave; + if (iDeviceIdleController != null) { + try { + allowInPowerSave = Arrays.asList(iDeviceIdleController.getSystemPowerWhitelist()); + } catch (RemoteException e) { + Log.e(TAG, "couldn't get system power white list", e); + allowInPowerSave = new ArrayList<>(); + } + } else { + allowInPowerSave = new ArrayList<>(); + } + mOperationsSection.removeAllViews(); String lastPermGroup = ""; + boolean isPlatformSigned = isPlatformSigned(); for (AppOpsState.OpsTemplate tpl : AppOpsState.ALL_TEMPLATES) { + /* If we are platform signed, only show the root switch, this + * one is safe to toggle while other permission-based ones could + * certainly cause system-wide problems + */ + if (isPlatformSigned && tpl != AppOpsState.SU_TEMPLATE) { + continue; + } List<AppOpsState.AppOpEntry> entries = mState.buildState(tpl, mPackageInfo.applicationInfo.uid, mPackageInfo.packageName); for (final AppOpsState.AppOpEntry entry : entries) { @@ -125,28 +197,128 @@ public class AppOpsDetails extends InstrumentedFragment { } catch (NameNotFoundException e) { } } - ((TextView)view.findViewById(R.id.op_name)).setText( - entry.getSwitchText(mState)); - ((TextView)view.findViewById(R.id.op_time)).setText( - entry.getTimeText(res, true)); - Switch sw = (Switch)view.findViewById(R.id.switchWidget); + + Spinner sp = (Spinner) view.findViewById(R.id.spinnerWidget); + sp.setVisibility(View.GONE); + Switch sw = (Switch) view.findViewById(R.id.switchWidget); + sw.setVisibility(View.GONE); + final int switchOp = AppOpsManager.opToSwitch(firstOp.getOp()); - sw.setChecked(mAppOps.checkOp(switchOp, entry.getPackageOps().getUid(), - entry.getPackageOps().getPackageName()) == AppOpsManager.MODE_ALLOWED); - sw.setOnCheckedChangeListener(new Switch.OnCheckedChangeListener() { + int mode = mAppOps.checkOp(switchOp, entry.getPackageOps().getUid(), + entry.getPackageOps().getPackageName()); + + final TextView opNameText = (TextView) view.findViewById(R.id.op_name); + final TextView opCountText = (TextView) view.findViewById(R.id.op_counts); + final TextView opTimeText = (TextView) view.findViewById(R.id.op_time); + + opNameText.setText(entry.getSwitchText(mState)); + + if (switchOp == AppOpsManager.OP_WAKE_LOCK + && allowInPowerSave.contains(entry.getPackageOps().getPackageName())) { + // sooper special case; app is marked to be allowed in power save; it is + // probably critical to functionality, don't allow user to change it, because + // we'll ignore it either way + sw.setVisibility(View.VISIBLE); + sw.setChecked(true); + sw.setEnabled(false); + + opCountText.setVisibility(View.GONE); + opTimeText.setText(R.string.app_ops_disabled_by_optimization); + + continue; + } + + opCountText.setText(entry.getCountsText(res)); + opTimeText.setText(entry.getTimeText(res, true)); + + sp.setSelection(modeToPosition(mode)); + sp.setOnItemSelectedListener(new Spinner.OnItemSelectedListener() { + boolean firstMode = true; + @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + public void onItemSelected(AdapterView<?> parentView, View selectedItemView, + int position, long id) { + if (firstMode) { + firstMode = false; + return; + } mAppOps.setMode(switchOp, entry.getPackageOps().getUid(), - entry.getPackageOps().getPackageName(), isChecked - ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED); + entry.getPackageOps().getPackageName(), positionToMode(position)); + } + + @Override + public void onNothingSelected(AdapterView<?> parentView) { + // Do nothing + } + }); + + sw.setChecked(mAppOps.checkOp(switchOp, entry.getPackageOps() + .getUid(), entry.getPackageOps().getPackageName()) == AppOpsManager.MODE_ALLOWED); + sw.setOnCheckedChangeListener(new Switch.OnCheckedChangeListener() { + public void onCheckedChanged(CompoundButton buttonView, + boolean isChecked) { + mAppOps.setMode(switchOp, entry.getPackageOps() + .getUid(), entry.getPackageOps() + .getPackageName(), + isChecked ? AppOpsManager.MODE_ALLOWED + : AppOpsManager.MODE_IGNORED); } }); + if (AppOpsManager.isStrictOp(switchOp)) { + sp.setVisibility(View.VISIBLE); + } else { + sw.setVisibility(View.VISIBLE); + } } } + if (UserHandle.isApp(mPackageInfo.applicationInfo.uid) && + mPm.checkPermission(Manifest.permission.INTERNET, + mPackageInfo.packageName) == PackageManager.PERMISSION_GRANTED) { + TextView internetCategory = (TextView) mInflater.inflate( + R.layout.preference_category_material, null); + internetCategory.setText(R.string.privacy_guard_internet_category); + mOperationsSection.addView(internetCategory); + + addInternetSwitch(POLICY_REJECT_ON_WLAN); + addInternetSwitch(POLICY_REJECT_ON_DATA); + } + return true; } + private void addInternetSwitch(final int policy) { + // Add internet category permissions + final View view = mInflater.inflate(R.layout.app_ops_details_item, + mOperationsSection, false); + mOperationsSection.addView(view); + + ((TextView)view.findViewById(R.id.op_name)).setText( + policy == POLICY_REJECT_ON_DATA ? R.string.restrict_app_cellular_title : + R.string.restrict_app_wlan_title); + view.findViewById(R.id.op_counts).setVisibility(View.INVISIBLE); + view.findViewById(R.id.op_time).setVisibility(View.INVISIBLE); + view.findViewById(R.id.spinnerWidget).setVisibility(View.GONE); + + Switch sw = (Switch) view.findViewById(R.id.switchWidget); + sw.setChecked((mPolicyManager.getUidPolicy( + mPackageInfo.applicationInfo.uid) & policy) != 0); + sw.setTag(policy); + sw.setVisibility(View.VISIBLE); + sw.setOnCheckedChangeListener(new Switch.OnCheckedChangeListener() { + public void onCheckedChanged(CompoundButton buttonView, + boolean isChecked) { + if (isChecked) { + mPolicyManager.addUidPolicy(mPackageInfo.applicationInfo.uid, + policy); + } else { + mPolicyManager.removeUidPolicy(mPackageInfo.applicationInfo.uid, + policy); + } + } + }); + } + private void setIntentAndFinish(boolean finish, boolean appChanged) { Intent intent = new Intent(); intent.putExtra(ManageApplications.APP_CHG, appChanged); @@ -163,6 +335,7 @@ public class AppOpsDetails extends InstrumentedFragment { mPm = getActivity().getPackageManager(); mInflater = (LayoutInflater)getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE); mAppOps = (AppOpsManager)getActivity().getSystemService(Context.APP_OPS_SERVICE); + mPolicyManager = NetworkPolicyManager.from(getActivity()); retrieveAppEntry(); diff --git a/src/com/android/settings/applications/AppOpsDetailsTop.java b/src/com/android/settings/applications/AppOpsDetailsTop.java new file mode 100644 index 0000000..c3c7084 --- /dev/null +++ b/src/com/android/settings/applications/AppOpsDetailsTop.java @@ -0,0 +1,37 @@ +/** + * Copyright (C) 2015 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.android.settings.applications; + +import android.content.Intent; +import android.preference.PreferenceActivity; + +public class AppOpsDetailsTop extends PreferenceActivity { + + @Override + public Intent getIntent() { + Intent modIntent = new Intent(super.getIntent()); + modIntent.putExtra(EXTRA_SHOW_FRAGMENT, AppOpsDetails.class.getName()); + modIntent.putExtra(EXTRA_NO_HEADERS, true); + return modIntent; + } + + @Override + protected boolean isValidFragment(String fragmentName) { + if (AppOpsDetails.class.getName().equals(fragmentName)) return true; + return false; + } +} diff --git a/src/com/android/settings/applications/AppOpsState.java b/src/com/android/settings/applications/AppOpsState.java index 07e955d..e46d94a 100644 --- a/src/com/android/settings/applications/AppOpsState.java +++ b/src/com/android/settings/applications/AppOpsState.java @@ -16,6 +16,7 @@ package com.android.settings.applications; +import android.app.Activity; import android.app.AppOpsManager; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -23,6 +24,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; +import android.content.SharedPreferences; import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; @@ -52,12 +54,15 @@ public class AppOpsState { List<AppOpEntry> mApps; + private SharedPreferences mPreferences; + public AppOpsState(Context context) { mContext = context; mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE); mPm = context.getPackageManager(); - mOpSummaries = context.getResources().getTextArray(R.array.app_ops_summaries); - mOpLabels = context.getResources().getTextArray(R.array.app_ops_labels); + mOpSummaries = context.getResources().getTextArray(R.array.app_ops_summaries_cm); + mOpLabels = context.getResources().getTextArray(R.array.app_ops_labels_cm); + mPreferences = context.getSharedPreferences("appops_manager", Activity.MODE_PRIVATE); } public static class OpsTemplate implements Parcelable { @@ -167,7 +172,7 @@ public class AppOpsState { AppOpsManager.OP_AUDIO_ALARM_VOLUME, AppOpsManager.OP_AUDIO_NOTIFICATION_VOLUME, AppOpsManager.OP_AUDIO_BLUETOOTH_VOLUME, - AppOpsManager.OP_MUTE_MICROPHONE}, + AppOpsManager.OP_MUTE_MICROPHONE }, new boolean[] { false, true, true, @@ -180,6 +185,7 @@ public class AppOpsState { false, false, false, + false, false } ); @@ -193,7 +199,11 @@ public class AppOpsState { AppOpsManager.OP_PROJECT_MEDIA, AppOpsManager.OP_ACTIVATE_VPN, AppOpsManager.OP_ASSIST_STRUCTURE, - AppOpsManager.OP_ASSIST_SCREENSHOT}, + AppOpsManager.OP_ASSIST_SCREENSHOT, + AppOpsManager.OP_WIFI_CHANGE, + AppOpsManager.OP_BLUETOOTH_CHANGE, + AppOpsManager.OP_NFC_CHANGE, + AppOpsManager.OP_DATA_CONNECT_CHANGE }, new boolean[] { false, true, true, @@ -203,12 +213,26 @@ public class AppOpsState { false, false, false, - false } + false, + true, + true, + true, + true } + ); + + public static final OpsTemplate BOOTUP_TEMPLATE = new OpsTemplate( + new int[] { AppOpsManager.OP_BOOT_COMPLETED }, + new boolean[] { true, } + ); + + public static final OpsTemplate SU_TEMPLATE = new OpsTemplate( + new int[] { AppOpsManager.OP_SU }, + new boolean[] { false } ); public static final OpsTemplate[] ALL_TEMPLATES = new OpsTemplate[] { LOCATION_TEMPLATE, PERSONAL_TEMPLATE, MESSAGING_TEMPLATE, - MEDIA_TEMPLATE, DEVICE_TEMPLATE + MEDIA_TEMPLATE, DEVICE_TEMPLATE, BOOTUP_TEMPLATE, SU_TEMPLATE }; /** @@ -364,30 +388,59 @@ public class AppOpsState { } private CharSequence getCombinedText(ArrayList<AppOpsManager.OpEntry> ops, - CharSequence[] items) { - if (ops.size() == 1) { - return items[ops.get(0).getOp()]; - } else { - StringBuilder builder = new StringBuilder(); - for (int i=0; i<ops.size(); i++) { - if (i > 0) { - builder.append(", "); - } - builder.append(items[ops.get(i).getOp()]); + CharSequence[] items, Resources res, boolean withTerseCounts) { + StringBuilder builder = new StringBuilder(); + for (int i=0; i<ops.size(); i++) { + if (i > 0) { + builder.append(", "); + } + AppOpsManager.OpEntry op = ops.get(i); + int count = op.getAllowedCount() + op.getIgnoredCount(); + + if (withTerseCounts && count > 0) { + String quantity = res.getQuantityString(R.plurals.app_ops_count, + count, count); + builder.append(res.getString(R.string.app_ops_entry_summary, + items[op.getOp()], quantity)); + } else { + builder.append(items[op.getOp()]); } - return builder.toString(); } + return builder.toString(); + } + + public CharSequence getCountsText(Resources res) { + AppOpsManager.OpEntry op = mOps.get(0); + int allowed = op.getAllowedCount(); + int denied = op.getIgnoredCount(); + + if (allowed == 0 && denied == 0) { + return null; + } + + CharSequence allowedQuantity = res.getQuantityString(R.plurals.app_ops_count, + allowed, allowed); + CharSequence deniedQuantity = res.getQuantityString(R.plurals.app_ops_count, + denied, denied); + + if (denied == 0) { + return res.getString(R.string.app_ops_allowed_count, allowedQuantity); + } else if (allowed == 0) { + return res.getString(R.string.app_ops_ignored_count, deniedQuantity); + } + return res.getString(R.string.app_ops_both_count, allowedQuantity, deniedQuantity); } public CharSequence getSummaryText(AppOpsState state) { - return getCombinedText(mOps, state.mOpSummaries); + return getCombinedText(mOps, state.mOpSummaries, state.mContext.getResources(), true); } public CharSequence getSwitchText(AppOpsState state) { + final Resources res = state.mContext.getResources(); if (mSwitchOps.size() > 0) { - return getCombinedText(mSwitchOps, state.mOpLabels); + return getCombinedText(mSwitchOps, state.mOpLabels, res, false); } else { - return getCombinedText(mOps, state.mOpLabels); + return getCombinedText(mOps, state.mOpLabels, res, false); } } @@ -471,19 +524,34 @@ public class AppOpsState { } private AppEntry getAppEntry(final Context context, final HashMap<String, AppEntry> appEntries, - final String packageName, ApplicationInfo appInfo) { + final String packageName, ApplicationInfo appInfo, boolean applyFilters) { + + if (appInfo == null) { + try { + appInfo = mPm.getApplicationInfo(packageName, + PackageManager.GET_DISABLED_COMPONENTS + | PackageManager.GET_UNINSTALLED_PACKAGES); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Unable to find info for package " + packageName); + return null; + } + } + + if (applyFilters) { + // Hide user apps if needed + if (!shouldShowUserApps() && + (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { + return null; + } + // Hide system apps if needed + if (!shouldShowSystemApps() && + (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + return null; + } + } + AppEntry appEntry = appEntries.get(packageName); if (appEntry == null) { - if (appInfo == null) { - try { - appInfo = mPm.getApplicationInfo(packageName, - PackageManager.GET_DISABLED_COMPONENTS - | PackageManager.GET_UNINSTALLED_PACKAGES); - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "Unable to find info for package " + packageName); - return null; - } - } appEntry = new AppEntry(this, appInfo); appEntry.loadLabel(context); appEntries.put(packageName, appEntry); @@ -491,6 +559,14 @@ public class AppOpsState { return appEntry; } + private boolean shouldShowUserApps() { + return mPreferences.getBoolean("show_user_apps", true); + } + + private boolean shouldShowSystemApps() { + return mPreferences.getBoolean("show_system_apps", true); + } + public List<AppOpEntry> buildState(OpsTemplate tpl, int uid, String packageName) { final Context context = mContext; @@ -511,6 +587,9 @@ public class AppOpsState { } } + // Whether to apply hide user / system app filters + final boolean applyFilters = (packageName == null); + List<AppOpsManager.PackageOps> pkgs; if (packageName != null) { pkgs = mAppOps.getOpsForPackage(uid, packageName, tpl.ops); @@ -521,7 +600,8 @@ public class AppOpsState { if (pkgs != null) { for (int i=0; i<pkgs.size(); i++) { AppOpsManager.PackageOps pkgOps = pkgs.get(i); - AppEntry appEntry = getAppEntry(context, appEntries, pkgOps.getPackageName(), null); + AppEntry appEntry = getAppEntry(context, appEntries, pkgOps.getPackageName(), null, + applyFilters); if (appEntry == null) { continue; } @@ -549,7 +629,7 @@ public class AppOpsState { for (int i=0; i<apps.size(); i++) { PackageInfo appInfo = apps.get(i); AppEntry appEntry = getAppEntry(context, appEntries, appInfo.packageName, - appInfo.applicationInfo); + appInfo.applicationInfo, applyFilters); if (appEntry == null) { continue; } @@ -583,7 +663,7 @@ public class AppOpsState { } AppOpsManager.OpEntry opEntry = new AppOpsManager.OpEntry( - permOps.get(k), AppOpsManager.MODE_ALLOWED, 0, 0, 0, -1, null); + permOps.get(k), AppOpsManager.MODE_ALLOWED, 0, 0, 0, -1, null, 0, 0); dummyOps.add(opEntry); addOp(entries, pkgOps, appEntry, opEntry, packageName == null, packageName == null ? 0 : opToOrder[opEntry.getOp()]); diff --git a/src/com/android/settings/applications/AppOpsSummary.java b/src/com/android/settings/applications/AppOpsSummary.java index 782b8fb..4ec390d 100644 --- a/src/com/android/settings/applications/AppOpsSummary.java +++ b/src/com/android/settings/applications/AppOpsSummary.java @@ -1,5 +1,6 @@ /** * Copyright (C) 2013 The Android Open Source Project + * Copyright (C) 2016 The CyanogenMod Project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy @@ -16,65 +17,81 @@ package com.android.settings.applications; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.AppOpsManager; import android.app.Fragment; import android.app.FragmentManager; -import android.content.res.TypedArray; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.Resources; +import android.content.SharedPreferences; +import android.os.Build; import android.os.Bundle; import android.preference.PreferenceFrameLayout; import android.support.v13.app.FragmentPagerAdapter; import android.support.v4.view.PagerTabStrip; import android.support.v4.view.ViewPager; +import android.util.Pair; +import android.util.TypedValue; import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + import com.android.internal.logging.MetricsLogger; +import com.android.settings.DevelopmentSettings; import com.android.settings.InstrumentedFragment; import com.android.settings.R; public class AppOpsSummary extends InstrumentedFragment { // layout inflater object used to inflate views private LayoutInflater mInflater; - + private ViewGroup mContentContainer; private View mRootView; private ViewPager mViewPager; - CharSequence[] mPageNames; - static AppOpsState.OpsTemplate[] sPageTemplates = new AppOpsState.OpsTemplate[] { - AppOpsState.LOCATION_TEMPLATE, - AppOpsState.PERSONAL_TEMPLATE, - AppOpsState.MESSAGING_TEMPLATE, - AppOpsState.MEDIA_TEMPLATE, - AppOpsState.DEVICE_TEMPLATE - }; + private MyPagerAdapter mAdapter; - int mCurPos; + private Activity mActivity; + private SharedPreferences mPreferences; @Override protected int getMetricsCategory() { return MetricsLogger.APP_OPS_SUMMARY; } - class MyPagerAdapter extends FragmentPagerAdapter implements ViewPager.OnPageChangeListener { + static class MyPagerAdapter extends FragmentPagerAdapter + implements ViewPager.OnPageChangeListener { + private List<Pair<CharSequence, AppOpsState.OpsTemplate>> mPageData; + private int mCurPos; - public MyPagerAdapter(FragmentManager fm) { + public MyPagerAdapter(FragmentManager fm, + List<Pair<CharSequence, AppOpsState.OpsTemplate>> data) { super(fm); + mPageData = data; } @Override public Fragment getItem(int position) { - return new AppOpsCategory(sPageTemplates[position]); + return new AppOpsCategory(mPageData.get(position).second); } @Override public int getCount() { - return sPageTemplates.length; + return mPageData.size(); } @Override public CharSequence getPageTitle(int position) { - return mPageNames[position]; + return mPageData.get(position).first; } @Override @@ -86,6 +103,10 @@ public class AppOpsSummary extends InstrumentedFragment { mCurPos = position; } + public int getCurrentPage() { + return mCurPos; + } + @Override public void onPageScrollStateChanged(int state) { if (state == ViewPager.SCROLL_STATE_IDLE) { @@ -94,6 +115,14 @@ public class AppOpsSummary extends InstrumentedFragment { } } + private void resetAdapter() { + // trigger adapter load, preserving the selected page + int curPos = mAdapter.getCurrentPage(); + mViewPager.setAdapter(mAdapter); + mViewPager.setOnPageChangeListener(mAdapter); + mViewPager.setCurrentItem(curPos); + } + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // initialize the inflater @@ -104,22 +133,35 @@ public class AppOpsSummary extends InstrumentedFragment { mContentContainer = container; mRootView = rootView; - mPageNames = getResources().getTextArray(R.array.app_ops_categories); + CharSequence[] pageNames = getResources().getTextArray(R.array.app_ops_categories_cm); + AppOpsState.OpsTemplate[] templates = AppOpsState.ALL_TEMPLATES; + assert(pageNames.length == templates.length); + + int specificTab = -1; + Bundle bundle = getArguments(); + if (bundle != null) { + specificTab = Arrays.asList(pageNames).indexOf(bundle.getString("appops_tab", "")); + } + + List<Pair<CharSequence, AppOpsState.OpsTemplate>> pageData = new ArrayList<>(); + for (int i = 0; i < pageNames.length; i++) { + pageData.add(Pair.create(pageNames[i], templates[i])); + } + filterPageData(pageData, specificTab); mViewPager = (ViewPager) rootView.findViewById(R.id.pager); - MyPagerAdapter adapter = new MyPagerAdapter(getChildFragmentManager()); - mViewPager.setAdapter(adapter); - mViewPager.setOnPageChangeListener(adapter); + mAdapter = new MyPagerAdapter(getChildFragmentManager(), pageData); + mViewPager.setAdapter(mAdapter); + mViewPager.setOnPageChangeListener(mAdapter); PagerTabStrip tabs = (PagerTabStrip) rootView.findViewById(R.id.tabs); - // This should be set in the XML layout, but PagerTabStrip lives in - // support-v4 and doesn't have styleable attributes. - final TypedArray ta = tabs.getContext().obtainStyledAttributes( - new int[] { android.R.attr.colorAccent }); - final int colorAccent = ta.getColor(0, 0); - ta.recycle(); - - tabs.setTabIndicatorColorResource(colorAccent); + Resources.Theme theme = tabs.getContext().getTheme(); + TypedValue typedValue = new TypedValue(); + theme.resolveAttribute(android.R.attr.colorAccent, typedValue, true); + final int colorAccent = typedValue.resourceId != 0 + ? getContext().getColor(typedValue.resourceId) + : getContext().getColor(R.color.switch_accent_color); + tabs.setTabIndicatorColor(colorAccent); // We have to do this now because PreferenceFrameLayout looks at it // only when the view is added. @@ -127,6 +169,108 @@ public class AppOpsSummary extends InstrumentedFragment { ((PreferenceFrameLayout.LayoutParams) rootView.getLayoutParams()).removeBorders = true; } + mActivity = getActivity(); + return rootView; } + + private void filterPageData(List<Pair<CharSequence, AppOpsState.OpsTemplate>> data, int tab) { + if (tab >= 0 && tab < data.size()) { + Pair<CharSequence, AppOpsState.OpsTemplate> item = data.get(tab); + data.clear(); + data.add(item); + } else if (!DevelopmentSettings.isRootForAppsEnabled()) { + for (Pair<CharSequence, AppOpsState.OpsTemplate> item : data) { + if (item.second == AppOpsState.SU_TEMPLATE) { + data.remove(item); + return; + } + } + } + } + + private boolean shouldShowUserApps() { + return mPreferences.getBoolean("show_user_apps", true); + } + + private boolean shouldShowSystemApps() { + return mPreferences.getBoolean("show_system_apps", true) && + mActivity.getResources().getBoolean(R.bool.config_showBuiltInAppsForPG); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + // get shared preferences + mPreferences = mActivity.getSharedPreferences("appops_manager", Activity.MODE_PRIVATE); + + setHasOptionsMenu(true); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + inflater.inflate(R.menu.appops_manager, menu); + menu.findItem(R.id.show_user_apps).setChecked(shouldShowUserApps()); + if (!mActivity.getResources().getBoolean(R.bool.config_showBuiltInAppsForPG)) { + menu.removeItem(R.id.show_system_apps); + } else { + menu.findItem(R.id.show_system_apps).setChecked(shouldShowSystemApps()); + } + } + + private void resetCounters() { + final AppOpsManager appOps = + (AppOpsManager) mActivity.getSystemService(Context.APP_OPS_SERVICE); + if (appOps == null) { + return; + } + appOps.resetCounters(); + // reload content + resetAdapter(); + } + + private void resetCountersConfirm() { + new AlertDialog.Builder(getActivity()) + .setIcon(android.R.drawable.ic_dialog_alert) + .setTitle(R.string.app_ops_reset_confirm_title) + .setMessage(R.string.app_ops_reset_confirm_mesg) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) { + resetCounters(); + } + }) + .setNegativeButton(android.R.string.cancel, null) + .show(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.show_user_apps: + final String prefNameUserApps = "show_user_apps"; + // set the menu checkbox and save it in shared preference + item.setChecked(!item.isChecked()); + mPreferences.edit().putBoolean(prefNameUserApps, item.isChecked()).commit(); + // reload content + resetAdapter(); + return true; + case R.id.show_system_apps: + final String prefNameSysApps = "show_system_apps"; + // set the menu checkbox and save it in shared preference + item.setChecked(!item.isChecked()); + mPreferences.edit().putBoolean(prefNameSysApps, item.isChecked()).commit(); + // reload view content + resetAdapter(); + return true; + case R.id.reset_counters: + resetCountersConfirm(); + return true; + default: + return super.onContextItemSelected(item); + } + } } diff --git a/src/com/android/settings/applications/ExpandedDesktopExtraPrefs.java b/src/com/android/settings/applications/ExpandedDesktopExtraPrefs.java new file mode 100644 index 0000000..f729aa3 --- /dev/null +++ b/src/com/android/settings/applications/ExpandedDesktopExtraPrefs.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2015 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.applications; + +import android.app.Activity; +import android.content.ContentResolver; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Bundle; + +import android.os.Handler; +import android.os.RemoteException; +import android.preference.ListPreference; +import android.preference.Preference; +import android.provider.Settings; +import android.view.WindowManager; +import android.view.WindowManagerGlobal; +import android.view.WindowManagerPolicyControl; + +import com.android.internal.logging.MetricsLogger; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.R; + +public class ExpandedDesktopExtraPrefs extends SettingsPreferenceFragment + implements Preference.OnPreferenceChangeListener{ + private static final String KEY_EXPANDED_DESKTOP_STYLE = "expanded_desktop_style"; + + private ListPreference mExpandedDesktopStylePref; + private int mExpandedDesktopStyle; + private final Handler mHandler = new Handler(); + private final SettingsObserver mSettingsObserver = new SettingsObserver(); + + public static ExpandedDesktopExtraPrefs newInstance() { + ExpandedDesktopExtraPrefs expandedDesktopExtraPrefs = new ExpandedDesktopExtraPrefs(); + return expandedDesktopExtraPrefs; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + boolean hasNavigationBar = true; + try { + hasNavigationBar = WindowManagerGlobal.getWindowManagerService().hasNavigationBar(); + } catch (RemoteException e) { + // Do nothing + } + if (hasNavigationBar) { + addPreferencesFromResource(R.xml.expanded_desktop_prefs); + mExpandedDesktopStyle = getExpandedDesktopStyle(); + createPreferences(); + } + } + + @Override + protected int getMetricsCategory() { + return MetricsLogger.DISPLAY; + } + + @Override + public void onResume() { + super.onResume(); + mSettingsObserver.register(true); + } + + @Override + public void onPause() { + mSettingsObserver.register(false); + super.onPause(); + } + + public void createPreferences() { + mExpandedDesktopStylePref = (ListPreference) findPreference(KEY_EXPANDED_DESKTOP_STYLE); + mExpandedDesktopStylePref.setOnPreferenceChangeListener(this); + updateExpandedDesktopStyle(); + } + + private void updateExpandedDesktopStyle() { + if (mExpandedDesktopStylePref == null) { + return; + } + mExpandedDesktopStyle = getExpandedDesktopStyle(); + mExpandedDesktopStylePref.setValueIndex(mExpandedDesktopStyle); + mExpandedDesktopStylePref.setSummary(getDesktopSummary(mExpandedDesktopStyle)); + // We need to visually show the change + // TODO: This is hacky, but it works + writeValue(""); + writeValue("immersive.full=*"); + } + + private int getDesktopSummary(int state) { + switch (state) { + case WindowManagerPolicyControl.ImmersiveDefaultStyles.IMMERSIVE_STATUS: + return R.string.expanded_hide_status; + case WindowManagerPolicyControl.ImmersiveDefaultStyles.IMMERSIVE_NAVIGATION: + return R.string.expanded_hide_navigation; + case WindowManagerPolicyControl.ImmersiveDefaultStyles.IMMERSIVE_FULL: + default: + return R.string.expanded_hide_both; + } + } + + private int getExpandedDesktopStyle() { + return Settings.Global.getInt(getContentResolver(), + Settings.Global.POLICY_CONTROL_STYLE, + WindowManagerPolicyControl.ImmersiveDefaultStyles.IMMERSIVE_FULL); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object value) { + final int val = Integer.valueOf((String) value); + WindowManagerPolicyControl.saveStyleToSettings(getActivity(), val); + return false; + } + + private void writeValue(String value) { + Settings.Global.putString(getContentResolver(), Settings.Global.POLICY_CONTROL, value); + } + + // === Window Policy Style Callbacks === + + private final class SettingsObserver extends ContentObserver { + private final Uri DEFAULT_WINDOW_POLICY_STYLE = + Settings.Global.getUriFor(Settings.Global.POLICY_CONTROL_STYLE); + + public SettingsObserver() { + super(mHandler); + } + + public void register(boolean register) { + final ContentResolver cr = getContentResolver(); + if (register) { + cr.registerContentObserver(DEFAULT_WINDOW_POLICY_STYLE, false, this); + } else { + cr.unregisterContentObserver(this); + } + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + super.onChange(selfChange, uri); + if (DEFAULT_WINDOW_POLICY_STYLE.equals(uri)) { + updateExpandedDesktopStyle(); + } + } + } +} diff --git a/src/com/android/settings/applications/ExpandedDesktopPreferenceFragment.java b/src/com/android/settings/applications/ExpandedDesktopPreferenceFragment.java new file mode 100644 index 0000000..4f4f234 --- /dev/null +++ b/src/com/android/settings/applications/ExpandedDesktopPreferenceFragment.java @@ -0,0 +1,613 @@ +/* + * Copyright (C) 2015 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.applications; + +import android.annotation.Nullable; +import android.app.Fragment; +import android.app.FragmentManager; +import android.app.FragmentTransaction; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.Bundle; +import android.os.RemoteException; +import android.provider.Settings; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManagerGlobal; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.SectionIndexer; +import android.widget.Spinner; +import android.widget.Switch; +import android.widget.TextView; +import android.view.WindowManagerPolicyControl; +import com.android.internal.logging.MetricsLogger; +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.widget.SwitchBar; +import com.android.settingslib.applications.ApplicationsState; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ExpandedDesktopPreferenceFragment extends SettingsPreferenceFragment + implements AdapterView.OnItemClickListener, ApplicationsState.Callbacks, + SwitchBar.OnSwitchChangeListener { + + private static final int STATE_DISABLED = 0; + private static final int STATE_STATUS_HIDDEN = 1; + private static final int STATE_NAVIGATION_HIDDEN = 2; + private static final int STATE_BOTH_HIDDEN = 3; + + private static final int STATE_ENABLE_FOR_ALL = 0; + private static final int STATE_USER_CONFIGURABLE = 1; + + private static final String EXPANDED_DESKTOP_PREFERENCE_TAG = "desktop_prefs"; + + private AllPackagesAdapter mAllPackagesAdapter; + private ApplicationsState mApplicationsState; + private View mEmptyView; + private View mProgressBar; + private ListView mUserListView; + private FrameLayout mExtraOptions; + private ApplicationsState.Session mSession; + private ActivityFilter mActivityFilter; + private Map<String, ApplicationsState.AppEntry> mEntryMap = + new HashMap<String, ApplicationsState.AppEntry>(); + private int mExpandedDesktopState; + private SwitchBar mSwitchBar; + + private int getExpandedDesktopState(ContentResolver cr) { + String value = Settings.Global.getString(cr, Settings.Global.POLICY_CONTROL); + if ("immersive.full=*".equals(value)) { + return STATE_ENABLE_FOR_ALL; + } + return STATE_USER_CONFIGURABLE; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mApplicationsState = ApplicationsState.getInstance(getActivity().getApplication()); + mSession = mApplicationsState.newSession(this); + mSession.resume(); + mActivityFilter = new ActivityFilter(getActivity().getPackageManager()); + + mExpandedDesktopState = getExpandedDesktopState(getActivity().getContentResolver()); + if (mExpandedDesktopState == STATE_USER_CONFIGURABLE) { + WindowManagerPolicyControl.reloadFromSetting(getActivity(), + Settings.Global.POLICY_CONTROL); + } + mAllPackagesAdapter = new AllPackagesAdapter(getActivity()); + + setHasOptionsMenu(true); + } + + @Override + protected int getMetricsCategory() { + return MetricsLogger.DISPLAY; + } + + @Override + public void onResume() { + super.onResume(); + rebuild(); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.expanded_desktop, container, false); + } + + @Override + public void onDestroy() { + super.onDestroy(); + save(); + mSession.pause(); + mSession.release(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + if (mSwitchBar != null) { + mSwitchBar.removeOnSwitchChangeListener(this); + } + } + + @Override + public void onViewCreated(final View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + mUserListView = (ListView) view.findViewById(R.id.user_list_view); + mExtraOptions = (FrameLayout) view.findViewById(R.id.extra_content); + mUserListView.setAdapter(mAllPackagesAdapter); + mUserListView.setFastScrollEnabled(true); + mUserListView.setOnItemClickListener(this); + + mSwitchBar = ((SettingsActivity) getActivity()).getSwitchBar(); + mSwitchBar.addOnSwitchChangeListener(this); + mSwitchBar.setOnStateOffLabel(R.string.expanded_enabled_for_all); + mSwitchBar.setOnStateOnLabel(R.string.expanded_enabled_for_all); + mSwitchBar.show(); + + mEmptyView = view.findViewById(R.id.nothing_to_show); + mProgressBar = view.findViewById(R.id.progress_bar); + + if (mExpandedDesktopState == STATE_USER_CONFIGURABLE) { + mSwitchBar.setChecked(false); + showListView(); + mExtraOptions.setVisibility(View.GONE); + } else { + mSwitchBar.setChecked(true); + mProgressBar.setVisibility(View.GONE); + hideListView(); + mExtraOptions.setVisibility(View.VISIBLE); + } + } + + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + ViewHolder holder = (ViewHolder) view.getTag(); + holder.mode.performClick(); + } + + private void enableForAll() { + mExpandedDesktopState = STATE_ENABLE_FOR_ALL; + writeValue("immersive.full=*"); + mAllPackagesAdapter.notifyDataSetInvalidated(); + hideListView(); + transactFragment(); + mExtraOptions.setVisibility(View.VISIBLE); + } + + private void userConfigurableSettings() { + mExpandedDesktopState = STATE_USER_CONFIGURABLE; + writeValue(""); + WindowManagerPolicyControl.reloadFromSetting(getActivity()); + mAllPackagesAdapter.notifyDataSetInvalidated(); + showListView(); + mExtraOptions.setVisibility(View.GONE); + removeFragment(); + } + + private void transactFragment() { + Fragment expandedDesktopExtraPrefs = ExpandedDesktopExtraPrefs.newInstance(); + FragmentTransaction fragmentTransaction = getChildFragmentManager().beginTransaction(); + fragmentTransaction.replace(R.id.extra_content, expandedDesktopExtraPrefs, + EXPANDED_DESKTOP_PREFERENCE_TAG); + fragmentTransaction.commit(); + } + + private void removeFragment() { + FragmentManager fragmentManager = getChildFragmentManager(); + Fragment fragment = fragmentManager.findFragmentByTag(EXPANDED_DESKTOP_PREFERENCE_TAG); + if (fragment != null) { + FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); + fragmentTransaction.remove(fragment).commit(); + } + } + + private void hideListView() { + mUserListView.setVisibility(View.GONE); + mEmptyView.setVisibility(View.VISIBLE); + } + + private void showListView() { + mUserListView.setVisibility(View.VISIBLE); + mEmptyView.setVisibility(View.GONE); + } + + private void writeValue(String value) { + Settings.Global.putString(getContentResolver(), Settings.Global.POLICY_CONTROL, value); + } + + private static int getStateForPackage(String packageName) { + int state = STATE_DISABLED; + + if (WindowManagerPolicyControl.immersiveStatusFilterMatches(packageName)) { + state = STATE_STATUS_HIDDEN; + } + if (WindowManagerPolicyControl.immersiveNavigationFilterMatches(packageName)) { + if (state == STATE_DISABLED) { + state = STATE_NAVIGATION_HIDDEN; + } else { + state = STATE_BOTH_HIDDEN; + } + } + + return state; + } + + @Override + public void onRunningStateChanged(boolean running) { + } + + @Override + public void onPackageListChanged() { + mActivityFilter.updateLauncherInfoList(); + rebuild(); + } + + @Override + public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> entries) { + rebuild(); + } + + @Override + public void onPackageIconChanged() { + } + + @Override + public void onPackageSizeChanged(String packageName) { + } + + @Override + public void onAllSizesComputed() { + } + + @Override + public void onLauncherInfoChanged() { + + } + + @Override + public void onLoadEntriesCompleted() { + rebuild(); + } + + private void handleAppEntries(List<ApplicationsState.AppEntry> entries) { + String lastSectionIndex = null; + ArrayList<String> sections = new ArrayList<String>(); + ArrayList<Integer> positions = new ArrayList<Integer>(); + PackageManager pm = getPackageManager(); + int count = entries.size(), offset = 0; + + for (int i = 0; i < count; i++) { + ApplicationInfo info = entries.get(i).info; + String label = (String) info.loadLabel(pm); + String sectionIndex; + + if (!info.enabled) { + sectionIndex = "--"; //XXX + } else if (TextUtils.isEmpty(label)) { + sectionIndex = ""; + } else { + sectionIndex = label.substring(0, 1).toUpperCase(); + } + + if (lastSectionIndex == null || + !TextUtils.equals(sectionIndex, lastSectionIndex)) { + sections.add(sectionIndex); + positions.add(offset); + lastSectionIndex = sectionIndex; + } + + offset++; + } + + mAllPackagesAdapter.setEntries(entries, sections, positions); + mEntryMap.clear(); + for (ApplicationsState.AppEntry e : entries) { + mEntryMap.put(e.info.packageName, e); + } + + if (mProgressBar != null) { + mProgressBar.setVisibility(View.GONE); + } + + if (mExpandedDesktopState != STATE_USER_CONFIGURABLE) { + hideListView(); + } + } + + private void rebuild() { + ArrayList<ApplicationsState.AppEntry> newEntries = mSession.rebuild( + mActivityFilter, ApplicationsState.ALPHA_COMPARATOR); + if (newEntries != null) { + handleAppEntries(newEntries); + mAllPackagesAdapter.notifyDataSetChanged(); + } + } + + private void save() { + if (mExpandedDesktopState == STATE_USER_CONFIGURABLE) { + WindowManagerPolicyControl.saveToSettings(getActivity(), + Settings.Global.POLICY_CONTROL); + } + } + + int getStateDrawable(int state) { + switch (state) { + case STATE_STATUS_HIDDEN: + return R.drawable.ic_extdesk_hidestatusbar; + case STATE_NAVIGATION_HIDDEN: + return R.drawable.ic_extdesk_hidenavbar; + case STATE_BOTH_HIDDEN: + return R.drawable.ic_extdesk_hideboth; + case STATE_DISABLED: + default: + return R.drawable.ic_extdesk_hidenone; + } + } + + @Override + public void onSwitchChanged(Switch switchView, boolean isChecked) { + if (isChecked) { + enableForAll(); + } else { + userConfigurableSettings(); + } + } + + private class AllPackagesAdapter extends BaseAdapter + implements AdapterView.OnItemSelectedListener, SectionIndexer { + + private final LayoutInflater inflater; + private List<ApplicationsState.AppEntry> entries = new ArrayList<>(); + private final ModeAdapter mModesAdapter; + private String[] mSections; + private int[] mPositions; + + public AllPackagesAdapter(Context context) { + this.inflater = LayoutInflater.from(context); + mModesAdapter = new ModeAdapter(context); + mActivityFilter = new ActivityFilter(context.getPackageManager()); + } + + @Override + public int getCount() { + return entries.size(); + } + + @Override + public Object getItem(int position) { + return entries.get(position); + } + + @Override + public boolean hasStableIds() { + return true; + } + + @Override + public long getItemId(int position) { + return entries.get(position).id; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + ViewHolder holder; + if (convertView == null) { + holder = new ViewHolder(inflater.inflate(R.layout.expanded_item, parent, false)); + holder.mode.setAdapter(mModesAdapter); + holder.mode.setOnItemSelectedListener(this); + } else { + holder = (ViewHolder) convertView.getTag(); + } + + ApplicationsState.AppEntry entry = entries.get(position); + + if (entry == null) { + return holder.rootView; + } + + holder.title.setText(entry.label); + mApplicationsState.ensureIcon(entry); + holder.icon.setImageDrawable(entry.icon); + holder.mode.setSelection(getStateForPackage(entry.info.packageName), false); + holder.mode.setTag(entry); + holder.stateIcon.setImageResource(getStateDrawable( + getStateForPackage(entry.info.packageName))); + return holder.rootView; + } + + private void setEntries(List<ApplicationsState.AppEntry> entries, + List<String> sections, List<Integer> positions) { + this.entries = entries; + if (mUserListView != null && mUserListView.getEmptyView() != mEmptyView) { + mUserListView.setEmptyView(mEmptyView); + } + + mSections = sections.toArray(new String[sections.size()]); + mPositions = new int[positions.size()]; + for (int i = 0; i < positions.size(); i++) { + mPositions[i] = positions.get(i); + } + notifyDataSetChanged(); + } + + + @Override + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { + ApplicationsState.AppEntry entry = (ApplicationsState.AppEntry) parent.getTag(); + + WindowManagerPolicyControl.removeFromWhiteLists(entry.info.packageName); + switch (position) { + case STATE_STATUS_HIDDEN: + WindowManagerPolicyControl.addToStatusWhiteList(entry.info.packageName); + break; + case STATE_NAVIGATION_HIDDEN: + WindowManagerPolicyControl.addToNavigationWhiteList(entry.info.packageName); + break; + case STATE_BOTH_HIDDEN: + WindowManagerPolicyControl.addToStatusWhiteList(entry.info.packageName); + WindowManagerPolicyControl.addToNavigationWhiteList(entry.info.packageName); + break; + } + save(); + notifyDataSetChanged(); + } + + @Override + public void onNothingSelected(AdapterView<?> parent) { + } + + @Override + public int getPositionForSection(int section) { + if (section < 0 || section >= mSections.length) { + return -1; + } + + return mPositions[section]; + } + + @Override + public int getSectionForPosition(int position) { + if (position < 0 || position >= getCount()) { + return -1; + } + + int index = Arrays.binarySearch(mPositions, position); + + /* + * Consider this example: section positions are 0, 3, 5; the supplied + * position is 4. The section corresponding to position 4 starts at + * position 3, so the expected return value is 1. Binary search will not + * find 4 in the array and thus will return -insertPosition-1, i.e. -3. + * To get from that number to the expected value of 1 we need to negate + * and subtract 2. + */ + return index >= 0 ? index : -index - 2; + } + + @Override + public Object[] getSections() { + return mSections; + } + } + + private static class ViewHolder { + private TextView title; + private Spinner mode; + private ImageView icon; + private View rootView; + private ImageView stateIcon; + + private ViewHolder(View view) { + this.title = (TextView) view.findViewById(R.id.app_name); + this.mode = (Spinner) view.findViewById(R.id.app_mode); + this.icon = (ImageView) view.findViewById(R.id.app_icon); + this.stateIcon = (ImageView) view.findViewById(R.id.state); + this.rootView = view; + + view.setTag(this); + } + } + + private static class ModeAdapter extends BaseAdapter { + + private final LayoutInflater inflater; + private boolean hasNavigationBar = true; + private final int[] items = {R.string.expanded_hide_nothing, R.string.expanded_hide_status, + R.string.expanded_hide_navigation, R.string.expanded_hide_both}; + + private ModeAdapter(Context context) { + inflater = LayoutInflater.from(context); + + try { + hasNavigationBar = WindowManagerGlobal.getWindowManagerService().hasNavigationBar(); + } catch (RemoteException e) { + // Do nothing + } + } + + @Override + public int getCount() { + return hasNavigationBar ? 4 : 2; + } + + @Override + public Object getItem(int position) { + return items[position]; + } + + @Override + public long getItemId(int position) { + return 0; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + TextView view; + if (convertView != null) { + view = (TextView) convertView; + } else { + view = (TextView) inflater.inflate(android.R.layout.simple_spinner_dropdown_item, + parent, false); + } + + view.setText(items[position]); + + return view; + } + } + + private class ActivityFilter implements ApplicationsState.AppFilter { + + private final PackageManager mPackageManager; + private final List<String> launcherResolveInfoList = new ArrayList<String>(); + private boolean onlyLauncher = true; + + private ActivityFilter(PackageManager packageManager) { + this.mPackageManager = packageManager; + + updateLauncherInfoList(); + } + + public void updateLauncherInfoList() { + Intent i = new Intent(Intent.ACTION_MAIN); + i.addCategory(Intent.CATEGORY_LAUNCHER); + List<ResolveInfo> resolveInfoList = mPackageManager.queryIntentActivities(i, 0); + + synchronized (launcherResolveInfoList) { + launcherResolveInfoList.clear(); + for (ResolveInfo ri : resolveInfoList) { + launcherResolveInfoList.add(ri.activityInfo.packageName); + } + } + } + + @Override + public void init() { + } + + @Override + public boolean filterApp(ApplicationsState.AppEntry info) { + boolean show = !mAllPackagesAdapter.entries.contains(info.info.packageName); + if (show && onlyLauncher) { + synchronized (launcherResolveInfoList) { + show = launcherResolveInfoList.contains(info.info.packageName); + } + } + return show; + } + } +} diff --git a/src/com/android/settings/applications/InstalledAppDetails.java b/src/com/android/settings/applications/InstalledAppDetails.java index 74ae90d..ef3788c 100755 --- a/src/com/android/settings/applications/InstalledAppDetails.java +++ b/src/com/android/settings/applications/InstalledAppDetails.java @@ -28,6 +28,7 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.Loader; +import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -63,25 +64,26 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; +import com.android.settings.cyanogenmod.ProtectedAppsReceiver; import com.android.internal.logging.MetricsLogger; import com.android.internal.os.BatterySipper; import com.android.internal.os.BatteryStatsHelper; import com.android.settings.DataUsageSummary; -import com.android.settings.DataUsageSummary.AppItem; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.Utils; import com.android.settings.applications.PermissionsSummaryHelper.PermissionsResultCallback; import com.android.settings.fuelgauge.BatteryEntry; import com.android.settings.fuelgauge.PowerUsageDetail; -import com.android.settings.net.ChartData; -import com.android.settings.net.ChartDataLoader; import com.android.settings.notification.AppNotificationSettings; import com.android.settings.notification.NotificationBackend; import com.android.settings.notification.NotificationBackend.AppRow; -import com.android.settingslib.applications.ApplicationsState; +import com.android.settingslib.AppItem; import com.android.settingslib.applications.ApplicationsState.AppEntry; +import com.android.settingslib.applications.ApplicationsState; +import com.android.settingslib.net.ChartData; +import com.android.settingslib.net.ChartDataLoader; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -106,10 +108,12 @@ public class InstalledAppDetails extends AppInfoBase // Menu identifiers public static final int UNINSTALL_ALL_USERS_MENU = 1; public static final int UNINSTALL_UPDATES = 2; + public static final int OPEN_PROTECTED_APPS = 3; // Result code identifiers public static final int REQUEST_UNINSTALL = 0; private static final int SUB_INFO_FRAGMENT = 1; + public static final int REQUEST_TOGGLE_PROTECTION = 3; private static final int LOADER_CHART_DATA = 2; @@ -236,6 +240,12 @@ public class InstalledAppDetails extends AppInfoBase enabled = false; } + // This is a protected app component. + // You cannot a uninstall a protected component + if (mPackageInfo.applicationInfo.protect) { + enabled = false; + } + mUninstallButton.setEnabled(enabled); if (enabled) { // Register listener @@ -375,6 +385,9 @@ public class InstalledAppDetails extends AppInfoBase .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); menu.add(0, UNINSTALL_ALL_USERS_MENU, 1, R.string.uninstall_all_users_text) .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + menu.add(0, OPEN_PROTECTED_APPS, Menu.NONE, R.string.protected_apps) + .setIcon(getResources().getDrawable(R.drawable.folder_lock)) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); } @Override @@ -399,6 +412,9 @@ public class InstalledAppDetails extends AppInfoBase menu.findItem(UNINSTALL_ALL_USERS_MENU).setVisible(showIt); mUpdatedSysApp = (mAppEntry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; menu.findItem(UNINSTALL_UPDATES).setVisible(mUpdatedSysApp && !mAppControlRestricted); + + menu.findItem(OPEN_PROTECTED_APPS).setVisible(mPackageInfo != null && + mPackageInfo.applicationInfo != null && mPackageInfo.applicationInfo.protect); } @Override @@ -410,6 +426,10 @@ public class InstalledAppDetails extends AppInfoBase case UNINSTALL_UPDATES: showDialogInner(DLG_FACTORY_RESET, 0); return true; + case OPEN_PROTECTED_APPS: + // Verify protection for toggling protected component status + Intent protectedApps = new Intent(getActivity(), LockPatternActivity.class); + startActivityForResult(protectedApps, REQUEST_TOGGLE_PROTECTION); } return false; } @@ -435,6 +455,37 @@ public class InstalledAppDetails extends AppInfoBase if (!refreshUi()) { setIntentAndFinish(true, true); } + } else if (requestCode == REQUEST_TOGGLE_PROTECTION) { + switch (resultCode) { + case Activity.RESULT_OK: + new ToggleProtectedAppComponents().execute(); + break; + case Activity.RESULT_CANCELED: + // User failed to enter/confirm a lock pattern, do nothing + break; + } + } + } + + private class ToggleProtectedAppComponents extends AsyncTask<Void, Void, Void> { + @Override + protected void onPostExecute(Void aVoid) { + getActivity().invalidateOptionsMenu(); + if (!refreshUi()) { + setIntentAndFinish(true, true); + } + } + + @Override + protected Void doInBackground(Void... params) { + ArrayList<ComponentName> components = new ArrayList<ComponentName>(); + for (ActivityInfo aInfo : mPackageInfo.activities) { + components.add(new ComponentName(aInfo.packageName, aInfo.name)); + } + + ProtectedAppsReceiver.updateProtectedAppComponentsAndNotify(getActivity(), + components, PackageManager.COMPONENT_VISIBLE_STATUS); + return null; } } @@ -443,7 +494,7 @@ public class InstalledAppDetails extends AppInfoBase final View appSnippet = mHeader.findViewById(R.id.app_snippet); mState.ensureIcon(mAppEntry); setupAppSnippet(appSnippet, mAppEntry.label, mAppEntry.icon, - pkgInfo != null ? pkgInfo.versionName : null); + pkgInfo != null ? pkgInfo.versionName : null, pkgInfo.packageName); } private boolean signaturesMatch(String pkg1, String pkg2) { @@ -658,6 +709,10 @@ public class InstalledAppDetails extends AppInfoBase } private void checkForceStop() { + if (getActivity() == null || getActivity().isFinishing()) { + return; + } + if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) { // User can't force stop device admin. updateForceStopButton(false); @@ -766,7 +821,7 @@ public class InstalledAppDetails extends AppInfoBase } public static void setupAppSnippet(View appSnippet, CharSequence label, Drawable icon, - CharSequence versionName) { + CharSequence versionName, String packageName) { LayoutInflater.from(appSnippet.getContext()).inflate(R.layout.widget_text_views, (ViewGroup) appSnippet.findViewById(android.R.id.widget_frame)); @@ -778,6 +833,12 @@ public class InstalledAppDetails extends AppInfoBase // Version number of application TextView appVersion = (TextView) appSnippet.findViewById(R.id.widget_text1); + if (packageName != null) { + TextView appPackage = (TextView) appSnippet.findViewById(R.id.widget_text2); + appPackage.setText(packageName); + appPackage.setSelected(true); + } + if (!TextUtils.isEmpty(versionName)) { appVersion.setSelected(true); appVersion.setVisibility(View.VISIBLE); @@ -927,6 +988,13 @@ public class InstalledAppDetails extends AppInfoBase mPm.setApplicationEnabledSetting(mInfo.packageName, mState, 0); return null; } + + @Override + protected void onPostExecute(Object o) { + if (mActivity.get() != null) { + mActivity.get().refreshUi(); + } + } } private final LoaderCallbacks<ChartData> mDataCallbacks = new LoaderCallbacks<ChartData>() { @@ -951,7 +1019,9 @@ public class InstalledAppDetails extends AppInfoBase private final BroadcastReceiver mCheckKillProcessesReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - updateForceStopButton(getResultCode() != Activity.RESULT_CANCELED); + if (getActivity() != null && !getActivity().isDestroyed()) { + updateForceStopButton(getResultCode() != Activity.RESULT_CANCELED); + } } }; diff --git a/src/com/android/settings/applications/LockPatternActivity.java b/src/com/android/settings/applications/LockPatternActivity.java new file mode 100644 index 0000000..05d30ec --- /dev/null +++ b/src/com/android/settings/applications/LockPatternActivity.java @@ -0,0 +1,466 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.applications; + +import android.app.Activity; +import android.content.SharedPreferences; +import android.hardware.fingerprint.FingerprintManager; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.util.Base64; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; + +import android.widget.Toast; +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.LockPatternView; +import com.android.settings.R; +import com.android.settings.cyanogenmod.ProtectedAccountView; +import com.android.settings.cyanogenmod.ProtectedAccountView.OnNotifyAccountReset; +import com.android.settings.fingerprint.FingerprintUiHelper; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.List; + +public class LockPatternActivity extends Activity implements OnNotifyAccountReset, FingerprintUiHelper.Callback { + public static final String PATTERN_LOCK_PROTECTED_APPS = "pattern_lock_protected_apps"; + public static final String RECREATE_PATTERN = "recreate_pattern_lock"; + + private static final String STATE_IS_ACCOUNT_VIEW = "isAccountView"; + private static final String STATE_CONTINUE_ENABLED = "continueEnabled"; + private static final String STATE_CONFIRMING = "confirming"; + private static final String STATE_RETRY_PATTERN = "retrypattern"; + private static final String STATE_RETRY = "retry"; + private static final String STATE_PATTERN_HASH = "pattern_hash"; + private static final String STATE_CREATE = "create"; + + private static String TIMEOUT_PREF_KEY = "retry_timeout"; + + private static final int MIN_PATTERN_SIZE = 4; + private static final int MAX_PATTERN_RETRY = 5; + private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000; + private static final long FAILED_ATTEMPT_RETRY = 30; + + private static final int MENU_RESET = 0; + + LockPatternView mLockPatternView; + ProtectedAccountView mAccountView; + ImageView mFingerprintIconView; + + TextView mPatternLockHeader; + MenuItem mItem; + Button mCancel; + Button mContinue; + byte[] mPatternHash; + + int mRetry = 0; + + boolean mCreate; + boolean mRetryPattern = true; + boolean mConfirming = false; + boolean mFingerPrintSetUp = false; + boolean mRetryLocked = false; + + private FingerprintManager mFingerprintManager; + private FingerprintUiHelper mFingerPrintUiHelper; + + Runnable mCancelPatternRunnable = new Runnable() { + public void run() { + mLockPatternView.clearPattern(); + mContinue.setEnabled(false); + + if (mCreate) { + if (mConfirming) { + mPatternLockHeader.setText(getResources() + .getString(R.string.lockpattern_need_to_confirm)); + } else { + mPatternLockHeader.setText(getResources().getString( + R.string.lockpattern_recording_intro_header)); + mCancel.setText(getResources().getString(R.string.cancel)); + } + } else { + mPatternLockHeader.setText(mFingerPrintSetUp ? + getResources().getString(R.string.pa_pattern_or_fingerprint_header) + : getResources().getString(R.string.lockpattern_settings_enable_summary)); + } + } + }; + + View.OnClickListener mCancelOnClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mCreate && !mConfirming && !mRetryPattern) { + // Retry + mRetryPattern = true; + resetPatternState(true); + return; + } + setResult(RESULT_CANCELED); + finish(); + } + }; + + View.OnClickListener mContinueOnClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + Button btn = (Button) v; + if (mConfirming) { + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()); + SharedPreferences.Editor editor = prefs.edit(); + editor.putString(PATTERN_LOCK_PROTECTED_APPS, + Base64.encodeToString(mPatternHash, Base64.DEFAULT)); + editor.commit(); + setResult(RESULT_OK); + finish(); + } else { + mConfirming = true; + mCancel.setText(getResources().getString(R.string.cancel)); + mLockPatternView.clearPattern(); + + mPatternLockHeader.setText(getResources().getString( + R.string.lockpattern_need_to_confirm)); + btn.setText(getResources().getString(R.string.lockpattern_confirm_button_text)); + btn.setEnabled(false); + } + } + }; + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + menu.clear(); + menu.add(0, MENU_RESET, 0, R.string.lockpattern_reset_button) + .setIcon(R.drawable.ic_lockscreen_ime_white) + .setAlphabeticShortcut('r') + .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | + MenuItem.SHOW_AS_ACTION_WITH_TEXT); + mItem = menu.findItem(0); + if (mRetryLocked) { + mItem.setIcon(R.drawable.ic_settings_lockscreen_white); + } + + return true; + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putBoolean(STATE_IS_ACCOUNT_VIEW, mAccountView.getVisibility() == View.VISIBLE); + outState.putBoolean(STATE_CONTINUE_ENABLED, mContinue.isEnabled()); + outState.putBoolean(STATE_CONFIRMING, mConfirming); + outState.putBoolean(STATE_RETRY_PATTERN, mRetryPattern); + outState.putInt(STATE_RETRY, mRetry); + outState.putByteArray(STATE_PATTERN_HASH, mPatternHash); + outState.putBoolean(STATE_CREATE, mCreate); + } + + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + if (savedInstanceState.getBoolean(STATE_IS_ACCOUNT_VIEW)) { + switchToAccount(); + } else { + switchToPattern(false); + mPatternHash = savedInstanceState.getByteArray(STATE_PATTERN_HASH); + mConfirming = savedInstanceState.getBoolean(STATE_CONFIRMING); + mRetryPattern = savedInstanceState.getBoolean(STATE_RETRY_PATTERN); + mRetry = savedInstanceState.getInt(STATE_RETRY); + mCreate = savedInstanceState.getBoolean(STATE_CREATE); + mContinue.setEnabled(savedInstanceState.getBoolean(STATE_CONTINUE_ENABLED, + mContinue.isEnabled())); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case MENU_RESET: + if (mAccountView.getVisibility() == View.VISIBLE) { + switchToPattern(false); + } else { + switchToAccount(); + } + return true; + case android.R.id.home: + setResult(RESULT_CANCELED); + finish(); + return true; + default: + return false; + } + } + + @Override + public void onNotifyAccountReset() { + switchToPattern(true); + } + + private void switchToPattern(boolean reset) { + if (isRetryLocked()) { + return; + } + if (reset) { + resetPatternState(false); + } + mPatternLockHeader.setText(mFingerPrintSetUp ? + getResources().getString(R.string.pa_pattern_or_fingerprint_header) + : getResources().getString(R.string.lockpattern_settings_enable_summary)); + mItem.setIcon(R.drawable.ic_lockscreen_ime_white); + mAccountView.clearFocusOnInput(); + mAccountView.setVisibility(View.GONE); + mLockPatternView.setVisibility(View.VISIBLE); + } + + private void switchToAccount() { + mPatternLockHeader.setText(getResources() + .getString(R.string.lockpattern_settings_reset_summary)); + if (mItem != null) { + mItem.setIcon(R.drawable.ic_settings_lockscreen_white); + } + mAccountView.setVisibility(View.VISIBLE); + mLockPatternView.setVisibility(View.GONE); + } + + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.patternlock); + + getActionBar().setDisplayHomeAsUpEnabled(true); + + mPatternLockHeader = (TextView) findViewById(R.id.pattern_lock_header); + mCancel = (Button) findViewById(R.id.pattern_lock_btn_cancel); + mCancel.setOnClickListener(mCancelOnClickListener); + mContinue = (Button) findViewById(R.id.pattern_lock_btn_continue); + mContinue.setOnClickListener(mContinueOnClickListener); + + mAccountView = (ProtectedAccountView) findViewById(R.id.lock_account_view); + mAccountView.setOnNotifyAccountResetCb(this); + mLockPatternView = (LockPatternView) findViewById(R.id.lock_pattern_view); + mFingerprintIconView = (ImageView) findViewById(R.id.protected_apps_fingerprint_icon); + + resetPatternState(false); + + //Setup Pattern Lock View + mLockPatternView.setFocusable(false); + mLockPatternView.setOnPatternListener(new UnlockPatternListener()); + + mFingerprintManager = (FingerprintManager) getSystemService(FingerprintManager.class); + + if (mFingerprintManager.isHardwareDetected()) { + if (mFingerprintManager.hasEnrolledFingerprints() && !mCreate) { + mFingerPrintSetUp = true; + mFingerPrintUiHelper = + new FingerprintUiHelper(mFingerprintIconView, mPatternLockHeader, this); + mFingerPrintUiHelper.setDarkIconography(true); + mFingerPrintUiHelper.setIdleText(getString( + R.string.pa_pattern_or_fingerprint_header)); + } else { + mFingerPrintSetUp = false; + } + } + } + + private void resetPatternState(boolean clear) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + String pattern = prefs.getString(PATTERN_LOCK_PROTECTED_APPS, null); + mCreate = pattern == null || RECREATE_PATTERN.equals(getIntent().getAction()) + || clear; + + mPatternHash = null; + if (pattern != null) { + mPatternHash = Base64.decode(pattern, Base64.DEFAULT); + } + + mContinue.setEnabled(!mCreate); + mCancel.setVisibility(mCreate ? View.VISIBLE : View.GONE); + mCancel.setText(getResources().getString(R.string.cancel)); + mContinue.setVisibility(mCreate ? View.VISIBLE : View.GONE); + mPatternLockHeader.setText(mCreate ? + getResources().getString(R.string.lockpattern_recording_intro_header) + : (mFingerPrintSetUp ? + getResources().getString(R.string.pa_pattern_or_fingerprint_header) + : getResources().getString(R.string.lockpattern_settings_enable_summary))); + mLockPatternView.clearPattern(); + + invalidateOptionsMenu(); + } + + @Override + public void onAuthenticated() { + setResult(RESULT_OK); + finish(); + } + + @Override + public void onFingerprintIconVisibilityChanged(boolean visible) { + + } + + private class UnlockPatternListener implements LockPatternView.OnPatternListener { + + public void onPatternStart() { + mLockPatternView.removeCallbacks(mCancelPatternRunnable); + + mPatternLockHeader.setText(getResources().getText( + R.string.lockpattern_recording_inprogress)); + mContinue.setEnabled(false); + } + + public void onPatternCleared() { + } + + public void onPatternDetected(List<LockPatternView.Cell> pattern) { + //Check inserted Pattern + if (mCreate) { + if (pattern.size() < MIN_PATTERN_SIZE) { + mPatternLockHeader.setText(getResources().getString( + R.string.lockpattern_recording_incorrect_too_short, + LockPatternUtils.MIN_LOCK_PATTERN_SIZE)); + + mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong); + mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS); + mCancel.setText(getResources() + .getString(R.string.lockpattern_retry_button_text)); + mRetryPattern = false; + return; + } + + if (mConfirming) { + if (Arrays.equals(mPatternHash, patternToHash(pattern))) { + mContinue.setText(getResources() + .getString(R.string.lockpattern_confirm_button_text)); + mContinue.setEnabled(true); + mPatternLockHeader.setText(getResources().getString( + R.string.lockpattern_pattern_confirmed_header)); + } else { + mContinue.setEnabled(false); + + mPatternLockHeader.setText(getResources().getString( + R.string.lockpattern_need_to_unlock_wrong)); + mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong); + mLockPatternView.postDelayed(mCancelPatternRunnable, + PATTERN_CLEAR_TIMEOUT_MS); + } + } else { + //Save pattern, user needs to redraw to confirm + mCancel.setText(getResources() + .getString(R.string.lockpattern_retry_button_text)); + mRetryPattern = false; + + mPatternHash = patternToHash(pattern); + + mPatternLockHeader.setText(getResources().getString( + R.string.lockpattern_pattern_entered_header)); + mContinue.setEnabled(true); + } + } else { + //Check against existing pattern + if (Arrays.equals(mPatternHash, patternToHash(pattern))) { + setResult(RESULT_OK); + finish(); + } else { + mRetry++; + mPatternLockHeader.setText(getResources().getString( + R.string.lockpattern_need_to_unlock_wrong)); + + mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong); + mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS); + + if (mRetry >= MAX_PATTERN_RETRY) { + setPatternTimeout(); + mLockPatternView.removeCallbacks(mCancelPatternRunnable); + Toast.makeText(getApplicationContext(), + getResources().getString( + R.string.lockpattern_too_many_failed_confirmation_attempts, + FAILED_ATTEMPT_RETRY), + Toast.LENGTH_SHORT).show(); + switchToAccount(); + } + } + } + } + + public void onPatternCellAdded(List<LockPatternView.Cell> pattern) {} + } + + /* + * Generate an SHA-1 hash for the pattern. Not the most secure, but it is + * at least a second level of protection. First level is that the file + * is in a location only readable by the system process. + * @param pattern the gesture pattern. + * @return the hash of the pattern in a byte array. + */ + public byte[] patternToHash(List<LockPatternView.Cell> pattern) { + if (pattern == null) { + return null; + } + + final int patternSize = pattern.size(); + byte[] res = new byte[patternSize]; + for (int i = 0; i < patternSize; i++) { + LockPatternView.Cell cell = pattern.get(i); + res[i] = (byte) (cell.getRow() * 3 + cell.getColumn()); + } + try { + MessageDigest md = MessageDigest.getInstance("SHA-1"); + byte[] hash = md.digest(res); + return hash; + } catch (NoSuchAlgorithmException nsa) { + return res; + } + } + + @Override + protected void onPause() { + if (mFingerPrintSetUp) { + mFingerPrintUiHelper.stopListening(); + } + super.onPause(); + } + + @Override + protected void onResume() { + super.onResume(); + if (mFingerPrintSetUp) { + mPatternLockHeader.setText(getString(R.string.pa_pattern_or_fingerprint_header)); + mFingerPrintUiHelper.startListening(); + } + if (isRetryLocked()) { + invalidateOptionsMenu(); + switchToAccount(); + } + } + + private boolean isRetryLocked() { + long time = System.currentTimeMillis(); + SharedPreferences prefs = getSharedPreferences(getPackageName(), MODE_PRIVATE); + long retryTime = prefs.getLong(TIMEOUT_PREF_KEY, 0); + mRetryLocked = (time - retryTime) < (FAILED_ATTEMPT_RETRY * 1000); + return mRetryLocked; + } + + private void setPatternTimeout() { + SharedPreferences prefs = getSharedPreferences(getPackageName(), MODE_PRIVATE); + prefs.edit().putLong(TIMEOUT_PREF_KEY, System.currentTimeMillis()).apply(); + } +} diff --git a/src/com/android/settings/applications/ManageApplications.java b/src/com/android/settings/applications/ManageApplications.java index 61c2ebb..5902fc2 100644 --- a/src/com/android/settings/applications/ManageApplications.java +++ b/src/com/android/settings/applications/ManageApplications.java @@ -87,7 +87,8 @@ import java.util.Comparator; * intent. */ public class ManageApplications extends InstrumentedFragment - implements OnItemClickListener, OnItemSelectedListener { + implements OnItemClickListener, OnItemSelectedListener, + ResetAppsHelper.ResetCompletedCallback { static final String TAG = "ManageApplications"; static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @@ -276,7 +277,7 @@ public class ManageApplications extends InstrumentedFragment mInvalidSizeStr = getActivity().getText(R.string.invalid_size_value); - mResetAppsHelper = new ResetAppsHelper(getActivity()); + mResetAppsHelper = new ResetAppsHelper(getActivity(), this); } @@ -302,13 +303,6 @@ public class ManageApplications extends InstrumentedFragment lv.setItemsCanFocus(true); lv.setTextFilterEnabled(true); mListView = lv; - mApplications = new ApplicationsAdapter(mApplicationsState, this, mFilter); - if (savedInstanceState != null) { - mApplications.mHasReceivedLoadEntries = - savedInstanceState.getBoolean(EXTRA_HAS_ENTRIES, false); - } - mListView.setAdapter(mApplications); - mListView.setRecyclerListener(mApplications); Utils.prepareCustomPreferencesList(container, mRootView, mListView, false); } @@ -319,8 +313,6 @@ public class ManageApplications extends InstrumentedFragment ((PreferenceFrameLayout.LayoutParams) mRootView.getLayoutParams()).removeBorders = true; } - createHeader(); - mResetAppsHelper.onRestoreInstanceState(savedInstanceState); return mRootView; @@ -365,6 +357,14 @@ public class ManageApplications extends InstrumentedFragment FrameLayout pinnedHeader = (FrameLayout) mRootView.findViewById(R.id.pinned_header); AppHeader.createAppHeader(getActivity(), null, mVolumeName, null, pinnedHeader); } + mApplications = new ApplicationsAdapter(mApplicationsState, this, mFilter); + if (savedInstanceState != null) { + mApplications.mHasReceivedLoadEntries = + savedInstanceState.getBoolean(EXTRA_HAS_ENTRIES, false); + } + mListView.setAdapter(mApplications); + mListView.setRecyclerListener(mApplications); + createHeader(); } private int getDefaultFilter() { @@ -538,9 +538,11 @@ public class ManageApplications extends InstrumentedFragment } mOptionsMenu.findItem(R.id.advanced).setVisible(mListType == LIST_TYPE_MAIN); - mOptionsMenu.findItem(R.id.sort_order_alpha).setVisible(mListType == LIST_TYPE_STORAGE + mOptionsMenu.findItem(R.id.sort_order_alpha).setVisible( + (mListType == LIST_TYPE_STORAGE || mListType == LIST_TYPE_MAIN) && mSortOrder != R.id.sort_order_alpha); - mOptionsMenu.findItem(R.id.sort_order_size).setVisible(mListType == LIST_TYPE_STORAGE + mOptionsMenu.findItem(R.id.sort_order_size).setVisible( + (mListType == LIST_TYPE_STORAGE || mListType == LIST_TYPE_MAIN) && mSortOrder != R.id.sort_order_size); mOptionsMenu.findItem(R.id.show_system).setVisible(!mShowSystem @@ -618,6 +620,14 @@ public class ManageApplications extends InstrumentedFragment mFilterAdapter.setFilterEnabled(FILTER_APPS_DISABLED, hasDisabledApps); } + @Override + public void onResetCompleted() { + /* mExtraInfoBridge can be null when doing reset app preference without + * any changes on apps */ + if (mApplications.mExtraInfoBridge != null) + mApplications.mExtraInfoBridge.onPackageListChanged(); + } + static class FilterSpinnerAdapter extends ArrayAdapter<CharSequence> { private final ManageApplications mManageApplications; @@ -877,7 +887,8 @@ public class ManageApplications extends InstrumentedFragment Utils.handleLoadingContainer(mManageApplications.mLoadingContainer, mManageApplications.mListContainer, true, true); } - if (mManageApplications.mListType == LIST_TYPE_USAGE_ACCESS) { + if (mManageApplications.mListType == LIST_TYPE_USAGE_ACCESS + || mManageApplications.mListType == LIST_TYPE_STORAGE) { // No enabled or disabled filters for usage access. return; } diff --git a/src/com/android/settings/applications/ManageDefaultApps.java b/src/com/android/settings/applications/ManageDefaultApps.java index f4ec843..b4ac174 100644 --- a/src/com/android/settings/applications/ManageDefaultApps.java +++ b/src/com/android/settings/applications/ManageDefaultApps.java @@ -38,6 +38,7 @@ import com.android.settings.SettingsPreferenceFragment; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.Index; import com.android.settings.search.Indexable; +import com.android.settings.Utils; import java.util.ArrayList; import java.util.Arrays; @@ -158,7 +159,8 @@ public class ManageDefaultApps extends SettingsPreferenceFragment // Restricted users cannot currently read/write SMS. // Remove SMS Application if the device does not support SMS - if (isRestrictedUser || !DefaultSmsPreference.isAvailable(getActivity())) { + if (isRestrictedUser || !Utils.canUserMakeCallsSms(getActivity()) + || !DefaultSmsPreference.isAvailable(getActivity())) { removePreference(KEY_SMS_APPLICATION); } diff --git a/src/com/android/settings/applications/ProcessStatsSummary.java b/src/com/android/settings/applications/ProcessStatsSummary.java index dc24c73..2cf9445 100644 --- a/src/com/android/settings/applications/ProcessStatsSummary.java +++ b/src/com/android/settings/applications/ProcessStatsSummary.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2016 The CyanogenMod Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +16,9 @@ */ package com.android.settings.applications; +import android.app.Activity; import android.content.Context; +import android.content.Intent; import android.os.Bundle; import android.preference.Preference; import android.preference.Preference.OnPreferenceClickListener; @@ -26,6 +29,7 @@ import android.widget.TextView; import com.android.internal.logging.MetricsLogger; import com.android.settings.R; +import com.android.settings.Settings.AppOpsSummaryActivity; import com.android.settings.Utils; import com.android.settings.applications.ProcStatsData.MemInfo; @@ -38,6 +42,9 @@ public class ProcessStatsSummary extends ProcessStatsBase implements OnPreferenc private static final String KEY_AVERAGY_USED = "average_used"; private static final String KEY_FREE = "free"; private static final String KEY_APP_LIST = "apps_list"; + private static final String KEY_APP_STARTUP = "apps_startup"; + + private Activity mActivity; private LinearColorBar mColors; private LayoutPreference mHeader; @@ -48,11 +55,14 @@ public class ProcessStatsSummary extends ProcessStatsBase implements OnPreferenc private Preference mAverageUsed; private Preference mFree; private Preference mAppListPreference; + private Preference mAppStartupPreference; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); + mActivity = getActivity(); + addPreferencesFromResource(R.xml.process_stats_summary); mHeader = (LayoutPreference) findPreference(KEY_STATUS_HEADER); mMemStatus = (TextView) mHeader.findViewById(R.id.memory_state); @@ -64,6 +74,8 @@ public class ProcessStatsSummary extends ProcessStatsBase implements OnPreferenc mFree = findPreference(KEY_FREE); mAppListPreference = findPreference(KEY_APP_LIST); mAppListPreference.setOnPreferenceClickListener(this); + mAppStartupPreference = findPreference(KEY_APP_STARTUP); + mAppStartupPreference.setOnPreferenceClickListener(this); } @Override @@ -119,6 +131,12 @@ public class ProcessStatsSummary extends ProcessStatsBase implements OnPreferenc startFragment(this, ProcessStatsUi.class.getName(), R.string.app_list_memory_use, 0, args); return true; + } else if (preference == mAppStartupPreference) { + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.putExtra("appops_tab", getString(R.string.app_ops_categories_bootup)); + intent.setClass(mActivity, AppOpsSummaryActivity.class); + mActivity.startActivity(intent); + return true; } return false; } diff --git a/src/com/android/settings/applications/ProtectedAppsActivity.java b/src/com/android/settings/applications/ProtectedAppsActivity.java new file mode 100644 index 0000000..7156c92 --- /dev/null +++ b/src/com/android/settings/applications/ProtectedAppsActivity.java @@ -0,0 +1,512 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.applications; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Configuration; +import android.graphics.drawable.Drawable; +import android.os.AsyncTask; +import android.os.Bundle; +import android.provider.Settings; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.CheckBox; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; +import com.android.settings.R; +import com.android.settings.cyanogenmod.ProtectedAppsReceiver; + +import cyanogenmod.providers.CMSettings; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +public class ProtectedAppsActivity extends Activity { + private static final int REQ_ENTER_PATTERN = 1; + private static final int REQ_RESET_PATTERN = 2; + + private static final String NEEDS_UNLOCK = "needs_unlock"; + private static final String TARGET_INTENT = "target_intent"; + + private ListView mListView; + + private static final int MENU_RESET = 0; + private static final int MENU_RESET_LOCK = 1; + + private PackageManager mPackageManager; + + private AppsAdapter mAppsAdapter; + + private ArrayList<ComponentName> mProtect; + + private boolean mWaitUserAuth = false; + private boolean mUserIsAuth = false; + private Intent mTargetIntent; + private int mOrientation; + + private HashSet<ComponentName> mProtectedApps = new HashSet<ComponentName>(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Handle incoming target activity + Intent incomingIntent = getIntent(); + if (incomingIntent.hasExtra("com.android.settings.PROTECTED_APP_TARGET_INTENT")) { + mTargetIntent = + incomingIntent.getParcelableExtra( + "com.android.settings.PROTECTED_APP_TARGET_INTENT"); + } + + setTitle(R.string.protected_apps); + setContentView(R.layout.hidden_apps_list); + + mPackageManager = getPackageManager(); + mAppsAdapter = new AppsAdapter(this, R.layout.hidden_apps_list_item); + mAppsAdapter.setNotifyOnChange(true); + + mListView = (ListView) findViewById(R.id.protected_apps_list); + mListView.setAdapter(mAppsAdapter); + + mProtect = new ArrayList<ComponentName>(); + + if (savedInstanceState != null) { + mUserIsAuth = savedInstanceState.getBoolean(NEEDS_UNLOCK); + mTargetIntent = savedInstanceState.getParcelable(TARGET_INTENT); + } else { + if (!mUserIsAuth) { + // Require unlock + mWaitUserAuth = true; + Intent lockPattern = new Intent(this, LockPatternActivity.class); + startActivityForResult(lockPattern, REQ_ENTER_PATTERN); + } else { + //LAUNCH + if (mTargetIntent != null) { + launchTargetActivityInfoAndFinish(); + } + } + } + mOrientation = getResources().getConfiguration().orientation; + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putBoolean(NEEDS_UNLOCK, mUserIsAuth); + outState.putParcelable(TARGET_INTENT, mTargetIntent); + } + + @Override + protected void onResume() { + super.onResume(); + + AsyncTask<Void, Void, List<AppEntry>> refreshAppsTask = + new AsyncTask<Void, Void, List<AppEntry>>() { + + @Override + protected void onPostExecute(List<AppEntry> apps) { + mAppsAdapter.clear(); + mAppsAdapter.addAll(apps); + } + + @Override + protected List<AppEntry> doInBackground(Void... params) { + return refreshApps(); + } + }; + refreshAppsTask.execute(null, null, null); + + getActionBar().setDisplayHomeAsUpEnabled(true); + + // Update Protected Apps list + updateProtectedComponentsList(); + } + + private void updateProtectedComponentsList() { + String protectedComponents = CMSettings.Secure.getString(getContentResolver(), + CMSettings.Secure.PROTECTED_COMPONENTS); + protectedComponents = protectedComponents == null ? "" : protectedComponents; + String [] flattened = protectedComponents.split("\\|"); + mProtectedApps = new HashSet<ComponentName>(flattened.length); + for (String flat : flattened) { + ComponentName cmp = ComponentName.unflattenFromString(flat); + if (cmp != null) { + mProtectedApps.add(cmp); + } + } + } + + @Override + public void onPause() { + super.onPause(); + + // Close this app to prevent unauthorized access when + // 1) not waiting for authorization and + // 2) there is no portrait/landscape mode switching + if (!mWaitUserAuth && (mOrientation == getResources().getConfiguration().orientation)) { + finish(); + } + } + + private boolean getProtectedStateFromComponentName(ComponentName componentName) { + return mProtectedApps.contains(componentName); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case REQ_ENTER_PATTERN: + mWaitUserAuth = false; + switch (resultCode) { + case RESULT_OK: + //Nothing to do, proceed! + mUserIsAuth = true; + if (mTargetIntent != null) { + launchTargetActivityInfoAndFinish(); + } + break; + case RESULT_CANCELED: + // user failed to define a pattern, do not lock the folder + finish(); + break; + } + break; + case REQ_RESET_PATTERN: + mWaitUserAuth = false; + mUserIsAuth = false; + } + } + + private void launchTargetActivityInfoAndFinish() { + Intent launchIntent = mTargetIntent; + startActivity(launchIntent); + finish(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + menu.add(0, MENU_RESET, 0, R.string.menu_hidden_apps_delete) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + menu.add(0, MENU_RESET_LOCK, 0, R.string.menu_hidden_apps_reset_lock) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + return true; + } + + private void reset() { + ArrayList<ComponentName> componentsList = new ArrayList<ComponentName>(); + + // Check to see if any components that have been protected that aren't present in + // the ListView. This can happen if there are components which have been protected + // but do not respond to the queryIntentActivities for Launcher Category + ContentResolver resolver = getContentResolver(); + String hiddenComponents = CMSettings.Secure.getString(resolver, + CMSettings.Secure.PROTECTED_COMPONENTS); + + if (hiddenComponents != null && !hiddenComponents.equals("")) { + for (String flattened : hiddenComponents.split("\\|")) { + ComponentName cmp = ComponentName.unflattenFromString(flattened); + + if (!componentsList.contains(cmp)) { + componentsList.add(cmp); + } + } + } + + AppProtectList list = new AppProtectList(componentsList, + PackageManager.COMPONENT_VISIBLE_STATUS); + StoreComponentProtectedStatus task = new StoreComponentProtectedStatus(this); + task.execute(list); + } + + private void resetLock() { + mWaitUserAuth = true; + Intent lockPattern = new Intent(LockPatternActivity.RECREATE_PATTERN, null, + this, LockPatternActivity.class); + startActivityForResult(lockPattern, REQ_RESET_PATTERN); + } + + private List<AppEntry> refreshApps() { + Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); + mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); + List<ResolveInfo> apps = mPackageManager.queryIntentActivities(mainIntent, 0); + Collections.sort(apps, new ResolveInfo.DisplayNameComparator(mPackageManager)); + List<AppEntry> appEntries = new ArrayList<AppEntry>(apps.size()); + for (ResolveInfo info : apps) { + appEntries.add(new AppEntry(info)); + } + return appEntries; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case MENU_RESET: + reset(); + return true; + case MENU_RESET_LOCK: + resetLock(); + return true; + case android.R.id.home: + finish(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + private final class AppEntry { + public final ComponentName componentName; + public final String title; + + public AppEntry(ResolveInfo info) { + ActivityInfo aInfo = info.activityInfo; + componentName = new ComponentName(aInfo.packageName, aInfo.name); + title = info.loadLabel(mPackageManager).toString(); + } + } + + private final class AppProtectList { + public final ArrayList<ComponentName> componentNames; + public final boolean state; + + public AppProtectList(ArrayList<ComponentName> componentNames, boolean state) { + this.componentNames = new ArrayList<ComponentName>(); + for (ComponentName cn : componentNames) { + this.componentNames.add(cn.clone()); + } + + this.state = state; + } + } + + public class StoreComponentProtectedStatus extends AsyncTask<AppProtectList, Void, Void> { + private ProgressDialog mDialog; + private Context mContext; + + public StoreComponentProtectedStatus(Context context) { + mContext = context; + mDialog = new ProgressDialog(mContext); + } + + @Override + protected void onPreExecute() { + mDialog.setMessage(getResources().getString(R.string.saving_protected_components)); + mDialog.setCancelable(false); + mDialog.setCanceledOnTouchOutside(false); + mDialog.show(); + } + + @Override + protected void onPostExecute(Void aVoid) { + if (mDialog.isShowing()) { + mDialog.dismiss(); + } + + mAppsAdapter.notifyDataSetChanged(); + } + + @Override + protected Void doInBackground(final AppProtectList... args) { + for (AppProtectList appList : args) { + ProtectedAppsReceiver.updateProtectedAppComponentsAndNotify(mContext, + appList.componentNames, appList.state); + } + + updateProtectedComponentsList(); + return null; + } + } + + /** + * App view holder used to reuse the views inside the list. + */ + private static class AppViewHolder { + public final View container; + public final TextView title; + public final ImageView icon; + public final View launch; + public final CheckBox checkBox; + + public AppViewHolder(View parentView) { + container = parentView.findViewById(R.id.app_item); + icon = (ImageView) parentView.findViewById(R.id.icon); + title = (TextView) parentView.findViewById(R.id.title); + launch = parentView.findViewById(R.id.launch_app); + checkBox = (CheckBox) parentView.findViewById(R.id.checkbox); + } + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + } + + public class AppsAdapter extends ArrayAdapter<AppEntry> { + + private final LayoutInflater mInflator; + + private ConcurrentHashMap<String, Drawable> mIcons; + private Drawable mDefaultImg; + private List<AppEntry> mApps; + + public AppsAdapter(Context context, int textViewResourceId) { + super(context, textViewResourceId); + + mApps = new ArrayList<AppEntry>(); + + mInflator = LayoutInflater.from(context); + + // set the default icon till the actual app icon is loaded in async task + mDefaultImg = context.getResources().getDrawable(android.R.mipmap.sym_def_app_icon); + mIcons = new ConcurrentHashMap<String, Drawable>(); + } + + @Override + public View getView(final int position, View convertView, ViewGroup parent) { + AppViewHolder viewHolder; + + if (convertView == null) { + convertView = mInflator.inflate(R.layout.hidden_apps_list_item, parent, false); + viewHolder = new AppViewHolder(convertView); + convertView.setTag(viewHolder); + } else { + viewHolder = (AppViewHolder) convertView.getTag(); + } + + AppEntry app = getItem(position); + + viewHolder.title.setText(app.title); + + Drawable icon = mIcons.get(app.componentName.getPackageName()); + viewHolder.icon.setImageDrawable(icon != null ? icon : mDefaultImg); + + boolean state = getProtectedStateFromComponentName(app.componentName); + viewHolder.checkBox.setChecked(state); + if (state) { + viewHolder.launch.setVisibility(View.VISIBLE); + viewHolder.launch.setTag(app); + viewHolder.launch.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + ComponentName cName = ((AppEntry)v.getTag()).componentName; + Intent intent = new Intent(); + intent.setClassName(cName.getPackageName(), cName.getClassName()); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } + }); + } else { + viewHolder.launch.setVisibility(View.GONE); + } + + viewHolder.container.setTag(position); + viewHolder.container.setOnClickListener(mAppClickListener); + return convertView; + } + + @Override + public boolean hasStableIds() { + return true; + } + + @Override + public void notifyDataSetChanged() { + super.notifyDataSetChanged(); + // If we have new items, we have to load their icons + // If items were deleted, remove them from our mApps + List<AppEntry> newApps = new ArrayList<AppEntry>(getCount()); + List<AppEntry> oldApps = new ArrayList<AppEntry>(getCount()); + for (int i = 0; i < getCount(); i++) { + AppEntry app = getItem(i); + if (mApps.contains(app)) { + oldApps.add(app); + } else { + newApps.add(app); + } + } + + if (newApps.size() > 0) { + new LoadIconsTask().execute(newApps.toArray(new AppEntry[] {})); + newApps.addAll(oldApps); + mApps = newApps; + } else { + mApps = oldApps; + } + } + + /** + * An asynchronous task to load the icons of the installed applications. + */ + private class LoadIconsTask extends AsyncTask<AppEntry, Void, Void> { + @Override + protected Void doInBackground(AppEntry... apps) { + for (AppEntry app : apps) { + try { + String packageName = app.componentName.getPackageName(); + if (mIcons.containsKey(packageName)) { + continue; + } + Drawable icon = mPackageManager.getApplicationIcon(packageName); + mIcons.put(packageName, icon); + publishProgress(); + } catch (PackageManager.NameNotFoundException e) { + // ignored; app will show up with default image + } + } + + return null; + } + + @Override + protected void onProgressUpdate(Void... progress) { + notifyDataSetChanged(); + } + } + } + + private View.OnClickListener mAppClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + int position = (Integer) v.getTag(); + ComponentName cn = mAppsAdapter.getItem(position).componentName; + ArrayList<ComponentName> componentsList = new ArrayList<ComponentName>(); + componentsList.add(cn); + boolean state = getProtectedStateFromComponentName(cn); + + AppProtectList list = new AppProtectList(componentsList, state); + StoreComponentProtectedStatus task = + new StoreComponentProtectedStatus(ProtectedAppsActivity.this); + task.execute(list); + } + }; +} diff --git a/src/com/android/settings/applications/ResetAppsHelper.java b/src/com/android/settings/applications/ResetAppsHelper.java index ad2ea02..2d0f671 100644 --- a/src/com/android/settings/applications/ResetAppsHelper.java +++ b/src/com/android/settings/applications/ResetAppsHelper.java @@ -49,10 +49,11 @@ public class ResetAppsHelper implements DialogInterface.OnClickListener, private final NetworkPolicyManager mNpm; private final AppOpsManager mAom; private final Context mContext; + private final ResetCompletedCallback mResetCompletedCallback; private AlertDialog mResetDialog; - public ResetAppsHelper(Context context) { + public ResetAppsHelper(Context context, ResetCompletedCallback callback) { mContext = context; mPm = context.getPackageManager(); mIPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package")); @@ -60,6 +61,7 @@ public class ResetAppsHelper implements DialogInterface.OnClickListener, ServiceManager.getService(Context.NOTIFICATION_SERVICE)); mNpm = NetworkPolicyManager.from(context); mAom = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); + mResetCompletedCallback = callback; } public void onRestoreInstanceState(Bundle savedInstanceState) { @@ -139,7 +141,12 @@ public class ResetAppsHelper implements DialogInterface.OnClickListener, mNpm.setUidPolicy(uid, POLICY_NONE); } } + mResetCompletedCallback.onResetCompleted(); } }); } + + public interface ResetCompletedCallback { + public void onResetCompleted(); + } } diff --git a/src/com/android/settings/applications/RunningState.java b/src/com/android/settings/applications/RunningState.java index 2286a24..c63bcd8 100644 --- a/src/com/android/settings/applications/RunningState.java +++ b/src/com/android/settings/applications/RunningState.java @@ -45,8 +45,8 @@ import android.util.Log; import android.util.SparseArray; import com.android.settings.R; -import com.android.settings.Utils; import com.android.settingslib.applications.InterestingConfigChanges; +import com.android.settingslib.Utils; import java.util.ArrayList; import java.util.Collections; diff --git a/src/com/android/settings/blacklist/BlacklistPreferences.java b/src/com/android/settings/blacklist/BlacklistPreferences.java new file mode 100644 index 0000000..2ee449f --- /dev/null +++ b/src/com/android/settings/blacklist/BlacklistPreferences.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.blacklist; + +import android.content.Context; +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.MultiSelectListPreference; +import android.preference.Preference; +import android.preference.PreferenceScreen; +import android.provider.Settings; + +import com.android.internal.logging.MetricsLogger; +import com.android.internal.telephony.util.BlacklistUtils; +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.SubSettings; + +import java.util.HashSet; +import java.util.Set; + +public class BlacklistPreferences extends SettingsPreferenceFragment implements + Preference.OnPreferenceChangeListener { + + private static final String BUTTON_BLACKLIST_PRIVATE = "button_blacklist_private_numbers"; + private static final String BUTTON_BLACKLIST_UNKNOWN = "button_blacklist_unknown_numbers"; + + private MultiSelectListPreference mBlacklistPrivate; + private MultiSelectListPreference mBlacklistUnknown; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + addPreferencesFromResource(R.xml.blacklist_prefs); + + PreferenceScreen prefSet = getPreferenceScreen(); + mBlacklistPrivate = + (MultiSelectListPreference) prefSet.findPreference(BUTTON_BLACKLIST_PRIVATE); + mBlacklistPrivate.setOnPreferenceChangeListener(this); + mBlacklistUnknown = + (MultiSelectListPreference) prefSet.findPreference(BUTTON_BLACKLIST_UNKNOWN); + mBlacklistUnknown.setOnPreferenceChangeListener(this); + } + + @Override + public void onResume() { + super.onResume(); + + final Context context = getActivity(); + updateSelectListFromPolicy(mBlacklistPrivate, + Settings.System.PHONE_BLACKLIST_PRIVATE_NUMBER_MODE); + updateSelectListSummary(mBlacklistPrivate, mBlacklistPrivate.getValues(), + R.string.blacklist_private_numbers_summary, + R.string.blacklist_private_numbers_summary_disabled); + updateSelectListFromPolicy(mBlacklistUnknown, + Settings.System.PHONE_BLACKLIST_UNKNOWN_NUMBER_MODE); + updateSelectListSummary(mBlacklistUnknown, mBlacklistUnknown.getValues(), + R.string.blacklist_unknown_numbers_summary, + R.string.blacklist_unknown_numbers_summary_disabled); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object objValue) { + if (preference == mBlacklistUnknown) { + Set<String> newValues = (Set<String>) objValue; + updatePolicyFromSelectList(newValues, + Settings.System.PHONE_BLACKLIST_UNKNOWN_NUMBER_MODE); + updateSelectListSummary(mBlacklistUnknown, newValues, + R.string.blacklist_unknown_numbers_summary, + R.string.blacklist_unknown_numbers_summary_disabled); + } else if (preference == mBlacklistPrivate) { + Set<String> newValues = (Set<String>) objValue; + updatePolicyFromSelectList(newValues, + Settings.System.PHONE_BLACKLIST_PRIVATE_NUMBER_MODE); + updateSelectListSummary(mBlacklistPrivate, newValues, + R.string.blacklist_private_numbers_summary, + R.string.blacklist_private_numbers_summary_disabled); + } + + return true; + } + + private void updateSelectListFromPolicy(MultiSelectListPreference pref, String setting) { + int mode = Settings.System.getInt(getContentResolver(), setting, 0); + Set<String> values = new HashSet<String>(); + + if ((mode & BlacklistUtils.BLOCK_CALLS) != 0) { + values.add(Integer.toString(BlacklistUtils.BLOCK_CALLS)); + } + if ((mode & BlacklistUtils.BLOCK_MESSAGES) != 0) { + values.add(Integer.toString(BlacklistUtils.BLOCK_MESSAGES)); + } + pref.setValues(values); + } + + private int getPolicyFromSelectList(Set<String> values) { + int mode = 0; + + for (String value : values) { + mode |= Integer.parseInt(value); + } + + return mode; + } + + private void updatePolicyFromSelectList(Set<String> values, String setting) { + int mode = getPolicyFromSelectList(values); + Settings.System.putInt(getContentResolver(), setting, mode); + } + + private void updateSelectListSummary(MultiSelectListPreference pref, + Set<String> values, int summaryResId, int disabledSummaryResId) { + int mode = getPolicyFromSelectList(values); + int typeResId; + + if (mode == 0) { + pref.setSummary(getString(disabledSummaryResId)); + return; + } + + if (mode == BlacklistUtils.BLOCK_CALLS) { + typeResId = R.string.blacklist_summary_type_calls_only; + } else if (mode == BlacklistUtils.BLOCK_MESSAGES) { + typeResId = R.string.blacklist_summary_type_messages_only; + } else { + typeResId = R.string.blacklist_summary_type_calls_and_messages; + } + + pref.setSummary(getString(summaryResId, getString(typeResId))); + } + + @Override + protected int getMetricsCategory() { + return MetricsLogger.PRIVACY; + } +} diff --git a/src/com/android/settings/blacklist/BlacklistSettings.java b/src/com/android/settings/blacklist/BlacklistSettings.java new file mode 100644 index 0000000..db7fe5a --- /dev/null +++ b/src/com/android/settings/blacklist/BlacklistSettings.java @@ -0,0 +1,396 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.blacklist; + +import android.app.ListFragment; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.database.ContentObserver; +import android.database.Cursor; +import android.location.CountryDetector; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.Process; +import android.provider.ContactsContract.PhoneLookup; +import android.provider.Settings; +import android.provider.Telephony.Blacklist; +import android.telephony.PhoneNumberUtils; +import android.text.TextUtils; +import android.util.SparseArray; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ListView; +import android.widget.ResourceCursorAdapter; +import android.widget.TextView; + +import com.android.internal.telephony.util.BlacklistUtils; +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settings.SubSettings; +import com.android.settings.cyanogenmod.BaseSystemSettingSwitchBar; + +import java.util.HashMap; + +/** + * Blacklist settings UI for the Phone app. + */ +public class BlacklistSettings extends ListFragment + implements BaseSystemSettingSwitchBar.SwitchBarChangeCallback { + + private static final String[] BLACKLIST_PROJECTION = { + Blacklist._ID, + Blacklist.NUMBER, + Blacklist.PHONE_MODE, + Blacklist.MESSAGE_MODE + }; + private static final int COLUMN_ID = 0; + private static final int COLUMN_NUMBER = 1; + private static final int COLUMN_PHONE = 2; + private static final int COLUMN_MESSAGE = 3; + + private BaseSystemSettingSwitchBar mEnabledSwitch; + private boolean mLastEnabledState; + + private BlacklistAdapter mAdapter; + private Cursor mCursor; + private TextView mEmptyView; + private Context mContext; + private View mFab; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mContext = getActivity(); + } + + @Override + public View onCreateView(LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.preference_blacklist, container, false); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mFab = view.findViewById(R.id.floating_action_button); + mFab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showEntryEditDialog(-1); + } + }); + } + + @Override + public void onActivityCreated(Bundle icicle) { + super.onActivityCreated(icicle); + + setHasOptionsMenu(true); + + mCursor = getActivity().managedQuery(Blacklist.CONTENT_URI, + BLACKLIST_PROJECTION, null, null, null); + mAdapter = new BlacklistAdapter(getActivity(), null); + + mEmptyView = (TextView) getView().findViewById(android.R.id.empty); + + final ListView listView = getListView(); + listView.setAdapter(mAdapter); + listView.setEmptyView(mEmptyView); + + // Add a footer to avoid a situation where the FAB would cover the last + // item's options in a non-scrollable listview. + View footer = LayoutInflater.from(getActivity()) + .inflate(R.layout.empty_list_entry_footer, listView, false); + listView.addFooterView(footer); + listView.setFooterDividersEnabled(false); + footer.setOnClickListener(null); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + inflater.inflate(R.menu.blacklist, menu); + } + + @Override + public void onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + menu.findItem(R.id.blacklist_prefs).setVisible(mLastEnabledState); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.blacklist_prefs: + SettingsActivity pa = (SettingsActivity) getActivity(); + pa.startPreferencePanel(BlacklistPreferences.class.getCanonicalName(), null, + 0, null, this, 0); + return true; + } + + return super.onOptionsItemSelected(item); + } + + @Override + public void onStart() { + super.onStart(); + final SettingsActivity activity = (SettingsActivity) getActivity(); + mEnabledSwitch = new BaseSystemSettingSwitchBar(activity, activity.getSwitchBar(), + Settings.System.PHONE_BLACKLIST_ENABLED, true, this); + } + + @Override + public void onResume() { + super.onResume(); + + final SettingsActivity activity = (SettingsActivity) getActivity(); + if (mEnabledSwitch != null) { + mEnabledSwitch.resume(activity); + } + } + + @Override + public void onPause() { + super.onPause(); + if (mEnabledSwitch != null) { + mEnabledSwitch.pause(); + } + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + if (mEnabledSwitch != null) { + mEnabledSwitch.teardownSwitchBar(); + } + } + + @Override + public void onListItemClick(ListView l, View v, int position, long id) { + showEntryEditDialog(id); + } + + private void showEntryEditDialog(long id) { + EntryEditDialogFragment fragment = EntryEditDialogFragment.newInstance(id); + fragment.show(getFragmentManager(), "blacklist_edit"); + } + + private void updateEnabledState() { + mFab.setVisibility(mLastEnabledState ? View.VISIBLE : View.GONE); + getListView().setEnabled(mLastEnabledState); + getActivity().invalidateOptionsMenu(); + + mEmptyView.setText(mLastEnabledState + ? R.string.blacklist_empty_text + : R.string.blacklist_disabled_empty_text); + mAdapter.swapCursor(mLastEnabledState ? mCursor : null); + } + + @Override + public void onEnablerChanged(boolean isEnabled) { + mLastEnabledState = BlacklistUtils.isBlacklistEnabled(mContext); + updateEnabledState(); + } + + private static class BlacklistAdapter extends ResourceCursorAdapter + implements ToggleImageView.OnCheckedChangeListener { + private Object mLock = new Object(); + private ContentResolver mResolver; + private String mCurrentCountryIso; + private SparseArray<String> mRequestedLookups = new SparseArray<String>(); + private HashMap<String, String> mContactNameCache = new HashMap<String, String>(); + + private Handler mMainHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + int lookupIndex = msg.arg1; + String name = (String) msg.obj; + mContactNameCache.put(mRequestedLookups.get(lookupIndex), + name == null ? "" : name); + mRequestedLookups.delete(lookupIndex); + notifyDataSetChanged(); + } + }; + private Handler mQueryHandler; + + private class QueryHandler extends Handler { + public static final int MSG_LOOKUP = 1; + private static final int MSG_FINISH = 2; + + public QueryHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_LOOKUP: + String name = lookupNameForNumber((String) msg.obj); + mMainHandler.obtainMessage(0, msg.arg1, 0, name).sendToTarget(); + synchronized (mLock) { + if (mQueryHandler != null) { + Message finishMessage = mQueryHandler.obtainMessage(MSG_FINISH); + mQueryHandler.sendMessageDelayed(finishMessage, 3000); + } + } + break; + case MSG_FINISH: + synchronized (mLock) { + if (mQueryHandler != null) { + mQueryHandler.getLooper().quit(); + mQueryHandler = null; + } + } + break; + } + } + + private String lookupNameForNumber(String number) { + if (!TextUtils.isEmpty(mCurrentCountryIso)) { + // Normalise the number: this is needed because the PhoneLookup query + // below does not accept a country code as an input. + String numberE164 = PhoneNumberUtils.formatNumberToE164(number, + mCurrentCountryIso); + if (!TextUtils.isEmpty(numberE164)) { + // Only use it if the number could be formatted to E164. + number = numberE164; + } + } + + String result = null; + final String[] projection = new String[] { PhoneLookup.DISPLAY_NAME }; + Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)); + Cursor cursor = mResolver.query(uri, projection, null, null, null); + if (cursor != null) { + if (cursor.moveToFirst()) { + result = cursor.getString(0); + } + cursor.close(); + } + + return result; + } + } + + public BlacklistAdapter(Context context, Cursor cursor) { + super(context, R.layout.blacklist_entry_row, cursor); + + final CountryDetector detector = + (CountryDetector) context.getSystemService(Context.COUNTRY_DETECTOR); + mCurrentCountryIso = detector.detectCountry().getCountryIso(); + mResolver = context.getContentResolver(); + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + View view = super.newView(context, cursor, parent); + + ViewHolder holder = new ViewHolder(); + holder.mainText = (TextView) view.findViewById(R.id.number); + holder.subText = (TextView) view.findViewById(R.id.name); + holder.callStatus = (ToggleImageView) view.findViewById(R.id.block_calls); + holder.messageStatus = (ToggleImageView) view.findViewById(R.id.block_messages); + + holder.callStatus.setTag(Blacklist.PHONE_MODE); + holder.callStatus.setOnCheckedChangeListener(this); + + holder.messageStatus.setTag(Blacklist.MESSAGE_MODE); + holder.messageStatus.setOnCheckedChangeListener(this); + + view.setTag(holder); + + return view; + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + ViewHolder holder = (ViewHolder) view.getTag(); + String number = cursor.getString(COLUMN_NUMBER); + String name = mContactNameCache.get(number); + String formattedNumber = PhoneNumberUtils.formatNumber(number, + null, mCurrentCountryIso); + + if (TextUtils.isEmpty(name)) { + holder.mainText.setText(formattedNumber); + holder.subText.setVisibility(View.GONE); + } else { + holder.mainText.setText(name); + holder.subText.setText(formattedNumber); + holder.subText.setVisibility(View.VISIBLE); + } + + if (name == null) { + int id = cursor.getInt(COLUMN_ID); + scheduleNameLookup(id, number); + } + + holder.callStatus.setCheckedInternal(cursor.getInt(COLUMN_PHONE) != 0, false); + holder.messageStatus.setCheckedInternal(cursor.getInt(COLUMN_MESSAGE) != 0, false); + holder.position = cursor.getPosition(); + } + + @Override + public void onCheckedChanged(ToggleImageView view, boolean isChecked) { + View parent = (View) view.getParent(); + ViewHolder holder = (ViewHolder) parent.getTag(); + String column = (String) view.getTag(); + long id = getItemId(holder.position); + Uri uri = ContentUris.withAppendedId(Blacklist.CONTENT_URI, id); + ContentValues cv = new ContentValues(); + + cv.put(column, view.isChecked() ? 1 : 0); + if (mResolver.update(uri, cv, null, null) <= 0) { + // something went wrong, force an update to the correct state + notifyDataSetChanged(); + } + } + + private void scheduleNameLookup(int id, String number) { + synchronized (mLock) { + if (mQueryHandler == null) { + HandlerThread thread = new HandlerThread("blacklist_contact_query", + Process.THREAD_PRIORITY_BACKGROUND); + thread.start(); + mQueryHandler = new QueryHandler(thread.getLooper()); + } + } + + mRequestedLookups.put(id, number); + Message msg = mQueryHandler.obtainMessage(QueryHandler.MSG_LOOKUP, id, 0, number); + msg.sendToTarget(); + } + + private static class ViewHolder { + TextView mainText; + TextView subText; + ToggleImageView callStatus; + ToggleImageView messageStatus; + int position; + } + } +} diff --git a/src/com/android/settings/blacklist/EntryEditDialogFragment.java b/src/com/android/settings/blacklist/EntryEditDialogFragment.java new file mode 100644 index 0000000..11c9909 --- /dev/null +++ b/src/com/android/settings/blacklist/EntryEditDialogFragment.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.blacklist; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.app.FragmentManager; +import android.content.ContentUris; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.provider.ContactsContract.CommonDataKinds; +import android.provider.Telephony.Blacklist; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.text.method.ArrowKeyMovementMethod; +import android.text.method.DialerKeyListener; +import android.util.Pair; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.ImageButton; + +import android.widget.Spinner; +import android.widget.Toast; +import com.android.internal.telephony.util.BlacklistUtils; +import com.android.settings.R; +import com.google.i18n.phonenumbers.PhoneNumberUtil; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +public class EntryEditDialogFragment extends DialogFragment + implements TextWatcher, DialogInterface.OnClickListener { + + private EditText mEditText; + private ImageButton mContactPickButton; + private CheckBox mBlockCalls; + private CheckBox mBlockMessages; + private Button mOkButton; + private Spinner mCountryCode; + + private static final String[] BLACKLIST_PROJECTION = { + Blacklist.NUMBER, Blacklist.PHONE_MODE, Blacklist.MESSAGE_MODE + }; + private static final String[] NUMBER_PROJECTION = { + CommonDataKinds.Phone.NUMBER + }; + private static final int COLUMN_NUMBER = 0; + private static final int COLUMN_PHONE = 1; + private static final int COLUMN_MESSAGE = 2; + + private static final int REQUEST_CODE_PICKER = 1; + + private static final String DIALOG_STATE = "blacklist_edit_state"; + private static final String STATE_NUMBER = "number"; + private static final String STATE_PHONE = "phone"; + private static final String STATE_MESSAGE = "message"; + private static final String STATE_EDIT_ENABLED = "edit_enabled"; + private static final String STATE_COUNTRY_CODE = "edit_country_code"; + + private static final String DELETE_CONFIRM_FRAGMENT_TAG = "delete_confirm"; + + public static EntryEditDialogFragment newInstance(long id) { + Bundle args = new Bundle(); + args.putLong("id", id); + + EntryEditDialogFragment fragment = new EntryEditDialogFragment(); + fragment.setArguments(args); + return fragment; + } + + public EntryEditDialogFragment() { + super(); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + long id = getEntryId(); + Bundle dialogState = savedInstanceState != null + ? savedInstanceState.getBundle(DIALOG_STATE) : null; + + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) + .setTitle(R.string.blacklist_edit_dialog_title) + .setPositiveButton(android.R.string.ok, this) + .setNegativeButton(android.R.string.cancel, null) + .setView(createDialogView(id, dialogState)); + + if (id >= 0) { + builder.setNeutralButton(R.string.blacklist_button_delete, this); + } + + return builder.create(); + } + + @Override + public void onStart() { + super.onStart(); + + AlertDialog dialog = (AlertDialog) getDialog(); + Button neutralButton = dialog.getButton(DialogInterface.BUTTON_NEUTRAL); + neutralButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + FragmentManager fragMan = getChildFragmentManager(); + if (fragMan.findFragmentByTag(DELETE_CONFIRM_FRAGMENT_TAG) == null) { + DeleteConfirmationFragment.newInstance() + .show(fragMan, DELETE_CONFIRM_FRAGMENT_TAG); + } + } + }); + + updateOkButtonState(); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + if (which == DialogInterface.BUTTON_POSITIVE) { + updateBlacklistEntry(); + } + } + + private void onDeleteConfirmResult(boolean confirmed) { + if (confirmed) { + Uri uri = ContentUris.withAppendedId(Blacklist.CONTENT_URI, getEntryId()); + getActivity().getContentResolver().delete(uri, null, null); + dismiss(); + } + } + + private long getEntryId() { + return getArguments().getLong("id", -1); + } + + private static String getLocaleCountry() { + final String country = Locale.getDefault().getCountry(); + if (TextUtils.isEmpty(country)) { + return null; + } + return country.toUpperCase(); + } + + private void populateCountryCodes(View view, Bundle savedState) { + PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance(); + // Get all supported country codes + Set<String> countryCodes = new HashSet<String>(); + for (String region : phoneUtil.getSupportedRegions()) { + countryCodes.add(String.valueOf(phoneUtil.getCountryCodeForRegion(region))); + } + List<String> entries = new ArrayList<String>(countryCodes); + Collections.sort(entries, new Comparator<String>() { + @Override + public int compare(String lhs, String rhs) { + return Integer.parseInt(lhs) - Integer.parseInt(rhs); + } + }); + + // If regex is supported, insert regex character + if (BlacklistUtils.isBlacklistRegexEnabled(getContext())) { + entries.add(0, "*"); + } + + // Set current country code as selected position + int selectedIndex = 0; + if (savedState == null) { + String country = getLocaleCountry(); + int currentCode = phoneUtil.getCountryCodeForRegion(country); + selectedIndex = entries.indexOf(String.valueOf(currentCode)); + } else { + selectedIndex = savedState.getInt(STATE_COUNTRY_CODE); + } + + ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(getContext(), + android.R.layout.simple_spinner_item, entries); + arrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + mCountryCode.setAdapter(arrayAdapter); + mCountryCode.setSelection(selectedIndex); + + // Ensure we make the layout visible + View parent = view.findViewById(R.id.country_code_layout); + parent.setVisibility(View.VISIBLE); + } + + private View createDialogView(long id, Bundle savedState) { + final Activity activity = getActivity(); + final LayoutInflater inflater = (LayoutInflater) + activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + final View view = inflater.inflate(R.layout.dialog_blacklist_edit_entry, null); + + mEditText = (EditText) view.findViewById(R.id.number_edit); + mEditText.setMovementMethod(ArrowKeyMovementMethod.getInstance()); + mEditText.setKeyListener(DialerKeyListener.getInstance()); + mEditText.addTextChangedListener(this); + + mCountryCode = (Spinner) view.findViewById(R.id.number_country_code); + + mContactPickButton = (ImageButton) view.findViewById(R.id.select_contact); + mContactPickButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent contactListIntent = new Intent(Intent.ACTION_PICK); + contactListIntent.setType(CommonDataKinds.Phone.CONTENT_TYPE); + + startActivityForResult(contactListIntent, REQUEST_CODE_PICKER, null); + } + }); + + mBlockCalls = (CheckBox) view.findViewById(R.id.incoming_calls); + mBlockMessages = (CheckBox) view.findViewById(R.id.incoming_messages); + + if (savedState != null) { + mEditText.setText(savedState.getCharSequence(STATE_NUMBER)); + mEditText.setEnabled(savedState.getBoolean(STATE_EDIT_ENABLED)); + mBlockCalls.setChecked(savedState.getBoolean(STATE_PHONE)); + mBlockMessages.setChecked(savedState.getBoolean(STATE_MESSAGE)); + } else if (id >= 0) { + Uri uri = ContentUris.withAppendedId(Blacklist.CONTENT_URI, id); + Cursor cursor = activity.getContentResolver().query(uri, + BLACKLIST_PROJECTION, null, null, null); + if (cursor != null && cursor.moveToFirst()) { + mEditText.setText(cursor.getString(COLUMN_NUMBER)); + mEditText.setEnabled(false); + mBlockCalls.setChecked(cursor.getInt(COLUMN_PHONE) != 0); + mBlockMessages.setChecked(cursor.getInt(COLUMN_MESSAGE) != 0); + } else { + id = -1; + } + if (cursor != null) { + cursor.close(); + } + } + + if (id < 0) { + // defaults + mEditText.setText(""); + mBlockCalls.setChecked(true); + mBlockMessages.setChecked(true); + mEditText.setEnabled(true); + } + + // Only populate country codes if new entry + if (id < 0 || savedState != null && mEditText.isEnabled()) { + populateCountryCodes(view, savedState); + } + + // Mirror contacts selector to state of editText + mContactPickButton.setEnabled(mEditText.isEnabled()); + return view; + } + + private void updateBlacklistEntry() { + String plusSymbol = getString(R.string.blacklist_country_code_plus); + String number = plusSymbol + mCountryCode.getSelectedItem() + + mEditText.getText().toString(); + int flags = 0; + int valid = BlacklistUtils.BLOCK_CALLS | BlacklistUtils.BLOCK_MESSAGES; + if (mBlockCalls.isChecked()) { + flags = flags | BlacklistUtils.BLOCK_CALLS; + } + if (mBlockMessages.isChecked()) { + flags = flags | BlacklistUtils.BLOCK_MESSAGES; + } + // Since BlacklistProvider enforces validity for a number to be added + // we should alert the user if and when it gets rejected + if (!BlacklistUtils.addOrUpdate(getActivity(), number, flags, valid)) { + Toast.makeText(getActivity(), getString(R.string.blacklist_bad_number_add), + Toast.LENGTH_LONG).show(); + } + } + + private void updateOkButtonState() { + if (mOkButton == null) { + AlertDialog dialog = (AlertDialog) getDialog(); + if (dialog != null) { + mOkButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE); + } + } + + boolean validInput = false; + String input = mEditText.getText().toString(); + if (!TextUtils.isEmpty(input)) { + Pair<String, Boolean> normalizeResult = + BlacklistUtils.isValidBlacklistInput(getActivity(), input); + if (normalizeResult.second) { + validInput = true; + } + } + + if (!validInput && !TextUtils.isEmpty(input)) { + mEditText.setError(getString(R.string.wifi_error)); + } else { + mEditText.setError(null); + } + + if (mOkButton != null) { + mOkButton.setEnabled(validInput); + } + } + + @Override + public void onSaveInstanceState(Bundle state) { + super.onSaveInstanceState(state); + + Bundle dialogState = new Bundle(); + dialogState.putCharSequence(STATE_NUMBER, mEditText.getText()); + dialogState.putBoolean(STATE_PHONE, mBlockCalls.isChecked()); + dialogState.putBoolean(STATE_MESSAGE, mBlockMessages.isChecked()); + dialogState.putBoolean(STATE_EDIT_ENABLED, mEditText.isEnabled()); + dialogState.putInt(STATE_COUNTRY_CODE, mCountryCode.getSelectedItemPosition()); + state.putBundle(DIALOG_STATE, dialogState); + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void afterTextChanged(Editable s) { + updateOkButtonState(); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode != REQUEST_CODE_PICKER) { + super.onActivityResult(requestCode, resultCode, data); + return; + } + + if (resultCode == Activity.RESULT_OK) { + Cursor cursor = getActivity().getContentResolver().query(data.getData(), + NUMBER_PROJECTION, null, null, null); + if (cursor != null) { + if (cursor.moveToFirst()) { + mEditText.setText(cursor.getString(COLUMN_NUMBER)); + } + cursor.close(); + } + } + } + + public static class DeleteConfirmationFragment extends DialogFragment + implements DialogInterface.OnClickListener { + public DeleteConfirmationFragment() { + } + + public static DialogFragment newInstance() { + return new DeleteConfirmationFragment(); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + AlertDialog dialog = new AlertDialog.Builder(getActivity()) + .setTitle(R.string.remove_blacklist_number_title) + .setMessage(R.string.remove_blacklist_entry) + .setPositiveButton(R.string.yes, this) + .setNegativeButton(R.string.no, this) + .create(); + + return dialog; + } + + @Override + public void onClick(DialogInterface dialog, int which) { + EntryEditDialogFragment parent = (EntryEditDialogFragment) getParentFragment(); + parent.onDeleteConfirmResult(which == DialogInterface.BUTTON_POSITIVE); + } + } +} diff --git a/src/com/android/settings/blacklist/ToggleImageView.java b/src/com/android/settings/blacklist/ToggleImageView.java new file mode 100644 index 0000000..3091c52 --- /dev/null +++ b/src/com/android/settings/blacklist/ToggleImageView.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.blacklist; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.widget.Checkable; +import android.widget.CompoundButton; +import android.widget.ImageView; + +public class ToggleImageView extends ImageView implements Checkable { + public static interface OnCheckedChangeListener { + void onCheckedChanged(ToggleImageView view, boolean isChecked); + } + + private static final int[] CHECKED_STATE_SET = { + com.android.internal.R.attr.state_checked + }; + + private boolean mIsChecked = false; + private OnCheckedChangeListener mOnCheckedChangeListener; + + public ToggleImageView(Context context) { + super(context); + } + + public ToggleImageView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ToggleImageView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.CompoundButton, defStyle, 0); + boolean checked = a.getBoolean( + com.android.internal.R.styleable.CompoundButton_checked, false); + setChecked(checked); + a.recycle(); + } + + @Override + public boolean performClick() { + /* When clicked, toggle the state */ + toggle(); + return super.performClick(); + } + + @Override + public void setChecked(boolean checked) { + setCheckedInternal(checked, true); + } + + @Override + public boolean isChecked() { + return mIsChecked; + } + + @Override + public void toggle() { + setChecked(!mIsChecked); + } + + public void setOnCheckedChangeListener(OnCheckedChangeListener listener) { + mOnCheckedChangeListener = listener; + } + + /* package */ void setCheckedInternal(boolean checked, boolean callListener) { + if (mIsChecked != checked) { + mIsChecked = checked; + setImageState(checked ? CHECKED_STATE_SET : null, true); + if (callListener && mOnCheckedChangeListener != null) { + mOnCheckedChangeListener.onCheckedChanged(this, mIsChecked); + } + } + } +} diff --git a/src/com/android/settings/bluetooth/BluetoothDevicePreference.java b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java index b36d2ea..93f6f5d 100644 --- a/src/com/android/settings/bluetooth/BluetoothDevicePreference.java +++ b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java @@ -19,10 +19,15 @@ package com.android.settings.bluetooth; import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH; import android.app.AlertDialog; +import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; import android.os.UserManager; import android.preference.Preference; import android.text.Html; @@ -58,6 +63,10 @@ public final class BluetoothDevicePreference extends Preference implements private AlertDialog mDisconnectDialog; + private Context mContext; + + private static final int OK_BUTTON = -1; + public BluetoothDevicePreference(Context context, CachedBluetoothDevice cachedDevice) { super(context); @@ -193,21 +202,28 @@ public final class BluetoothDevicePreference extends Preference implements // Show disconnect confirmation dialog for a device. private void askDisconnect() { - Context context = getContext(); + mContext = getContext(); String name = mCachedDevice.getName(); if (TextUtils.isEmpty(name)) { - name = context.getString(R.string.bluetooth_device); + name = mContext.getString(R.string.bluetooth_device); } - String message = context.getString(R.string.bluetooth_disconnect_all_profiles, name); - String title = context.getString(R.string.bluetooth_disconnect_title); + String message = mContext.getString(R.string.bluetooth_disconnect_all_profiles, name); + String title = mContext.getString(R.string.bluetooth_disconnect_title); + + IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); + mContext.registerReceiver(mBluetoothReceiver, filter); DialogInterface.OnClickListener disconnectListener = new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { - mCachedDevice.disconnect(); + // Disconnect only when user has selected OK + if (which == OK_BUTTON) { + mCachedDevice.disconnect(); + } + mContext.unregisterReceiver(mBluetoothReceiver); } }; - mDisconnectDialog = Utils.showDisconnectDialog(context, + mDisconnectDialog = Utils.showDisconnectDialog(mContext, mDisconnectDialog, disconnectListener, title, Html.fromHtml(message)); } @@ -268,6 +284,24 @@ public final class BluetoothDevicePreference extends Preference implements return R.drawable.ic_bt_headset_hfp; } } - return R.drawable.ic_settings_bluetooth; + return R.drawable.ic_bt_bluetooth; } + + private final BroadcastReceiver mBluetoothReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { + switch (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) { + case BluetoothAdapter.STATE_TURNING_OFF: + Log.v(TAG, "Receiver DISABLED_ACTION "); + if (mDisconnectDialog != null && mDisconnectDialog.isShowing()) { + mDisconnectDialog.dismiss(); + } + mContext.unregisterReceiver(mBluetoothReceiver); + break; + } + } + } + }; } diff --git a/src/com/android/settings/bluetooth/BluetoothEnabler.java b/src/com/android/settings/bluetooth/BluetoothEnabler.java index e83f483..b6399b0 100644 --- a/src/com/android/settings/bluetooth/BluetoothEnabler.java +++ b/src/com/android/settings/bluetooth/BluetoothEnabler.java @@ -24,11 +24,13 @@ import android.content.IntentFilter; import android.os.Handler; import android.os.Message; import android.provider.Settings; +import android.widget.CompoundButton; import android.widget.Switch; import android.widget.Toast; import com.android.internal.logging.MetricsLogger; import com.android.settings.R; +import com.android.settings.dashboard.GenericSwitchToggle; import com.android.settings.search.Index; import com.android.settings.widget.SwitchBar; import com.android.settingslib.WirelessUtils; @@ -40,13 +42,9 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager; * preference. It turns on/off Bluetooth and ensures the summary of the * preference reflects the current state. */ -public final class BluetoothEnabler implements SwitchBar.OnSwitchChangeListener { - private Context mContext; - private Switch mSwitch; - private SwitchBar mSwitchBar; - private boolean mValidListener; - private final LocalBluetoothAdapter mLocalAdapter; - private final IntentFilter mIntentFilter; +public final class BluetoothEnabler extends GenericSwitchToggle { + private LocalBluetoothAdapter mLocalAdapter; + private IntentFilter mIntentFilter; private static final String EVENT_DATA_IS_BT_ON = "is_bluetooth_on"; private static final int EVENT_UPDATE_INDEX = 0; @@ -75,16 +73,23 @@ public final class BluetoothEnabler implements SwitchBar.OnSwitchChangeListener }; public BluetoothEnabler(Context context, SwitchBar switchBar) { - mContext = context; - mSwitchBar = switchBar; - mSwitch = switchBar.getSwitch(); - mValidListener = false; + super(context, switchBar); - LocalBluetoothManager manager = Utils.getLocalBtManager(context); + init(); + } + + public BluetoothEnabler(Context context, Switch switch_) { + super(context, switch_); + + init(); + } + + private void init() { + LocalBluetoothManager manager = Utils.getLocalBtManager(mContext); if (manager == null) { // Bluetooth is not supported mLocalAdapter = null; - mSwitch.setEnabled(false); + setEnabled(false); } else { mLocalAdapter = manager.getBluetoothAdapter(); } @@ -99,73 +104,55 @@ public final class BluetoothEnabler implements SwitchBar.OnSwitchChangeListener mSwitchBar.hide(); } + @Override public void resume(Context context) { + super.resume(context); if (mLocalAdapter == null) { - mSwitch.setEnabled(false); + setEnabled(false); return; } - if (mContext != context) { - mContext = context; - } - // Bluetooth state is not sticky, so set it manually handleStateChanged(mLocalAdapter.getBluetoothState()); - mSwitchBar.addOnSwitchChangeListener(this); mContext.registerReceiver(mReceiver, mIntentFilter); - mValidListener = true; } + @Override public void pause() { + super.pause(); if (mLocalAdapter == null) { return; } - mSwitchBar.removeOnSwitchChangeListener(this); mContext.unregisterReceiver(mReceiver); - mValidListener = false; } void handleStateChanged(int state) { switch (state) { case BluetoothAdapter.STATE_TURNING_ON: - mSwitch.setEnabled(false); + setEnabled(false); break; case BluetoothAdapter.STATE_ON: setChecked(true); - mSwitch.setEnabled(true); + setEnabled(true); updateSearchIndex(true); break; case BluetoothAdapter.STATE_TURNING_OFF: - mSwitch.setEnabled(false); + setEnabled(false); break; case BluetoothAdapter.STATE_OFF: setChecked(false); - mSwitch.setEnabled(true); + setEnabled(true); updateSearchIndex(false); break; default: setChecked(false); - mSwitch.setEnabled(true); + setEnabled(true); updateSearchIndex(false); } } - private void setChecked(boolean isChecked) { - if (isChecked != mSwitch.isChecked()) { - // set listener to null, so onCheckedChanged won't be called - // if the checked status on Switch isn't changed by user click - if (mValidListener) { - mSwitchBar.removeOnSwitchChangeListener(this); - } - mSwitch.setChecked(isChecked); - if (mValidListener) { - mSwitchBar.addOnSwitchChangeListener(this); - } - } - } - private void updateSearchIndex(boolean isBluetoothOn) { mHandler.removeMessages(EVENT_UPDATE_INDEX); @@ -177,12 +164,15 @@ public final class BluetoothEnabler implements SwitchBar.OnSwitchChangeListener @Override public void onSwitchChanged(Switch switchView, boolean isChecked) { + if (mStateMachineEvent) { + return; + } // Show toast message if Bluetooth is not allowed in airplane mode if (isChecked && !WirelessUtils.isRadioAllowed(mContext, Settings.Global.RADIO_BLUETOOTH)) { Toast.makeText(mContext, R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show(); // Reset switch to off - switchView.setChecked(false); + setChecked(false); } MetricsLogger.action(mContext, MetricsLogger.ACTION_BLUETOOTH_TOGGLE, isChecked); @@ -190,6 +180,11 @@ public final class BluetoothEnabler implements SwitchBar.OnSwitchChangeListener if (mLocalAdapter != null) { mLocalAdapter.setBluetoothEnabled(isChecked); } - mSwitch.setEnabled(false); + setEnabled(false); + } + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + super.onCheckedChanged(buttonView, isChecked); } } diff --git a/src/com/android/settings/bluetooth/BluetoothNameDialogFragment.java b/src/com/android/settings/bluetooth/BluetoothNameDialogFragment.java index 293a53e..1b1b130 100644 --- a/src/com/android/settings/bluetooth/BluetoothNameDialogFragment.java +++ b/src/com/android/settings/bluetooth/BluetoothNameDialogFragment.java @@ -100,7 +100,7 @@ public final class BluetoothNameDialogFragment extends DialogFragment implements .setPositiveButton(R.string.bluetooth_rename_button, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { - String deviceName = mDeviceNameView.getText().toString(); + String deviceName = mDeviceNameView.getText().toString().trim(); setDeviceName(deviceName); } }) @@ -137,7 +137,10 @@ public final class BluetoothNameDialogFragment extends DialogFragment implements @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (actionId == EditorInfo.IME_ACTION_DONE) { - setDeviceName(v.getText().toString()); + if (v.length() != 0 && !(v.getText().toString().trim().isEmpty())) // Rejecting Empty String + { + setDeviceName(v.getText().toString().trim()); + } mAlertDialog.dismiss(); return true; // action handled } else { diff --git a/src/com/android/settings/bluetooth/BluetoothPairingDialog.java b/src/com/android/settings/bluetooth/BluetoothPairingDialog.java index 1ff99f7..716ce41 100755 --- a/src/com/android/settings/bluetooth/BluetoothPairingDialog.java +++ b/src/com/android/settings/bluetooth/BluetoothPairingDialog.java @@ -66,6 +66,7 @@ public final class BluetoothPairingDialog extends AlertActivity implements private String mPairingKey; private EditText mPairingView; private Button mOkButton; + private boolean mIsButtonPressed; /** * Dismiss the dialog if the bond state changes to bonded or none, @@ -95,6 +96,8 @@ public final class BluetoothPairingDialog extends AlertActivity implements protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + mIsButtonPressed = false; + Intent intent = getIntent(); if (!intent.getAction().equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) { @@ -364,7 +367,9 @@ public final class BluetoothPairingDialog extends AlertActivity implements @Override protected void onDestroy() { super.onDestroy(); - unregisterReceiver(mReceiver); + if (mReceiver != null) { + unregisterReceiver(mReceiver); + } } public void afterTextChanged(Editable s) { @@ -424,6 +429,11 @@ public final class BluetoothPairingDialog extends AlertActivity implements } public void onClick(DialogInterface dialog, int which) { + if(mIsButtonPressed) + { + Log.e(TAG, "button already pressed"); + return; + } switch (which) { case BUTTON_POSITIVE: if (mPairingView != null) { @@ -431,9 +441,11 @@ public final class BluetoothPairingDialog extends AlertActivity implements } else { onPair(null); } + mIsButtonPressed = true; break; case BUTTON_NEGATIVE: + mIsButtonPressed = true; default: onCancel(); break; diff --git a/src/com/android/settings/bluetooth/BluetoothPairingRequest.java b/src/com/android/settings/bluetooth/BluetoothPairingRequest.java index ba1d918..3b2a81e 100644 --- a/src/com/android/settings/bluetooth/BluetoothPairingRequest.java +++ b/src/com/android/settings/bluetooth/BluetoothPairingRequest.java @@ -80,7 +80,7 @@ public final class BluetoothPairingRequest extends BroadcastReceiver { .setTicker(res.getString(R.string.bluetooth_notif_ticker)); PendingIntent pending = PendingIntent.getActivity(context, 0, - pairingIntent, PendingIntent.FLAG_ONE_SHOT); + pairingIntent, PendingIntent.FLAG_UPDATE_CURRENT); String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME); if (TextUtils.isEmpty(name)) { @@ -102,6 +102,16 @@ public final class BluetoothPairingRequest extends BroadcastReceiver { } } else if (action.equals(BluetoothDevice.ACTION_PAIRING_CANCEL)) { + Intent pairingIntent = new Intent(); + + pairingIntent.setClass(context, BluetoothPairingDialog.class); + pairingIntent.setAction(BluetoothDevice.ACTION_PAIRING_REQUEST); + pairingIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + PendingIntent pending = PendingIntent.getActivity(context, 0, + pairingIntent, PendingIntent.FLAG_NO_CREATE); + if (pending != null) { + pending.cancel(); + } // Remove the notification NotificationManager manager = (NotificationManager) context @@ -114,7 +124,8 @@ public final class BluetoothPairingRequest extends BroadcastReceiver { int oldState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, BluetoothDevice.ERROR); if((oldState == BluetoothDevice.BOND_BONDING) && - (bondState == BluetoothDevice.BOND_NONE)) { + (bondState == BluetoothDevice.BOND_NONE || + bondState == BluetoothDevice.BOND_BONDED)) { // Remove the notification NotificationManager manager = (NotificationManager) context .getSystemService(Context.NOTIFICATION_SERVICE); diff --git a/src/com/android/settings/bluetooth/BluetoothSettings.java b/src/com/android/settings/bluetooth/BluetoothSettings.java index 4c48981..37eebe7 100644 --- a/src/com/android/settings/bluetooth/BluetoothSettings.java +++ b/src/com/android/settings/bluetooth/BluetoothSettings.java @@ -18,6 +18,8 @@ package com.android.settings.bluetooth; import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH; +import android.app.ActionBar; +import android.app.Activity; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.content.BroadcastReceiver; @@ -25,6 +27,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.res.Configuration; import android.content.res.Resources; import android.os.Bundle; import android.preference.Preference; @@ -32,15 +35,18 @@ import android.preference.PreferenceCategory; import android.preference.PreferenceGroup; import android.preference.PreferenceScreen; import android.provider.Settings; -import android.text.Spannable; import android.text.style.TextAppearanceSpan; +import android.util.DisplayMetrics; import android.util.Log; +import android.util.TypedValue; import android.view.Gravity; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; +import android.view.ViewGroup.LayoutParams; import android.widget.TextView; +import android.widget.Toolbar; import com.android.internal.logging.MetricsLogger; import com.android.settings.LinkifyUtils; @@ -55,6 +61,8 @@ import com.android.settingslib.bluetooth.BluetoothDeviceFilter; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; +import cyanogenmod.providers.CMSettings; + import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -69,6 +77,7 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment implem private static final int MENU_ID_SCAN = Menu.FIRST; private static final int MENU_ID_RENAME_DEVICE = Menu.FIRST + 1; private static final int MENU_ID_SHOW_RECEIVED = Menu.FIRST + 2; + private static final int MENU_ID_ACCEPT_ALL_FILES = Menu.FIRST + 3; /* Private intent to show the list of received files */ private static final String BTOPP_ACTION_OPEN_RECEIVED_FILES = @@ -131,12 +140,12 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment implem @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - mInitialScanStarted = false; + /* Don't auto start scan if screen reconstructs due to frozen screen*/ + mInitialScanStarted = (savedInstanceState != null); mInitiateDiscoverable = true; mEmptyView = (TextView) getView().findViewById(android.R.id.empty); getListView().setEmptyView(mEmptyView); - mEmptyView.setGravity(Gravity.START | Gravity.CENTER_VERTICAL); final SettingsActivity activity = (SettingsActivity) getActivity(); mSwitchBar = activity.getSwitchBar(); @@ -146,6 +155,59 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment implem } @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + Activity activity = getActivity(); + float titleTextSize; + int actionBarHeight; + int switchBarHeight; + if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { + titleTextSize = activity.getResources().getDimensionPixelSize( + R.dimen.bluetooth_landscape_title_textsize); + switchBarHeight = activity.getResources().getDimensionPixelSize( + R.dimen.bluetooth_landscape_switchbar_height); + actionBarHeight = activity.getResources().getDimensionPixelSize( + R.dimen.bluetooth_landscape_actionbar_height); + } else { + titleTextSize = activity.getResources().getDimensionPixelSize( + R.dimen.bluetooth_portrait_title_textsize); + switchBarHeight = activity.getResources().getDimensionPixelSize( + R.dimen.bluetooth_portrait_switchbar_height); + actionBarHeight = activity.getResources().getDimensionPixelSize( + R.dimen.bluetooth_portrait_switchbar_height); + } + resetBarSize(titleTextSize, actionBarHeight, switchBarHeight); + } + + private void resetBarSize(float titleTextSize, int actionBarHeight, int switchBarHeight) { + Activity activity = getActivity(); + DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics(); + int titleId = Resources.getSystem().getIdentifier("action_bar", "id", "android"); + Toolbar toolbar = (Toolbar) activity.getWindow().findViewById(titleId); + TextView title = null; + if (toolbar != null) { + LayoutParams layoutParams = toolbar.getLayoutParams(); + layoutParams.height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + actionBarHeight, displayMetrics); + for (int i = 0; i < toolbar.getChildCount(); ++i) { + if (toolbar.getChildAt(i) instanceof TextView) { + title = (TextView) toolbar.getChildAt(i); + } + Toolbar.LayoutParams childLayoutParams = (Toolbar.LayoutParams) toolbar.getChildAt( + i).getLayoutParams(); + childLayoutParams.gravity = Gravity.CENTER_VERTICAL; + } + } + if (title != null) + title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, titleTextSize); + if (mSwitchBar != null) { + LayoutParams layoutParams = mSwitchBar.getLayoutParams(); + layoutParams.height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + switchBarHeight, displayMetrics); + } + } + + @Override public void onDestroyView() { super.onDestroyView(); @@ -166,6 +228,10 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment implem if (mBluetoothEnabler != null) { mBluetoothEnabler.resume(getActivity()); } + if (mLocalAdapter != null) { + // enable page and inquiry scan + mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE); + } super.onResume(); mInitiateDiscoverable = true; @@ -191,7 +257,9 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment implem } // Make the device only visible to connected devices. - mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE); + if (mLocalAdapter != null) { + mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE); + } if (isUiRestricted()) { return; @@ -210,6 +278,10 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment implem boolean isDiscovering = mLocalAdapter.isDiscovering(); int textId = isDiscovering ? R.string.bluetooth_searching_for_devices : R.string.bluetooth_search_for_devices; + + boolean isAcceptAllFilesEnabled = CMSettings.System.getInt(getContentResolver(), + CMSettings.System.BLUETOOTH_ACCEPT_ALL_FILES, 0) == 1; + menu.add(Menu.NONE, MENU_ID_SCAN, 0, textId) .setEnabled(bluetoothIsEnabled && !isDiscovering) .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); @@ -218,6 +290,10 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment implem .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); menu.add(Menu.NONE, MENU_ID_SHOW_RECEIVED, 0, R.string.bluetooth_show_received_files) .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + menu.add(Menu.NONE, MENU_ID_ACCEPT_ALL_FILES, 0, R.string.bluetooth_accept_all_files) + .setCheckable(true) + .setChecked(isAcceptAllFilesEnabled) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); super.onCreateOptionsMenu(menu, inflater); } @@ -242,6 +318,13 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment implem Intent intent = new Intent(BTOPP_ACTION_OPEN_RECEIVED_FILES); getActivity().sendBroadcast(intent); return true; + + case MENU_ID_ACCEPT_ALL_FILES: + item.setChecked(!item.isChecked()); + CMSettings.System.putInt(getContentResolver(), + CMSettings.System.BLUETOOTH_ACCEPT_ALL_FILES, + item.isChecked() ? 1 : 0); + return true; } return super.onOptionsItemSelected(item); } @@ -262,7 +345,11 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment implem } mLocalManager.getCachedDeviceManager().clearNonBondedDevices(); - mAvailableDevicesCategory.removeAll(); + if (mAvailableDevicesCategory != null) { + mAvailableDevicesCategory.removeAll(); + } else { + Log.e(TAG, "mAvailableDevicesCategory is null."); + } mInitialScanStarted = true; mLocalAdapter.startScanning(true); } @@ -357,6 +444,10 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment implem case BluetoothAdapter.STATE_OFF: setOffMessage(); + /* reset the progress icon only when available device category present */ + if(mAvailableDevicesCategoryIsPresent) { + ((BluetoothProgressCategory)mAvailableDevicesCategory).setProgress(false); + } if (isUiRestricted()) { messageId = R.string.bluetooth_empty_list_user_restricted; } @@ -407,10 +498,6 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment implem }); } getPreferenceScreen().removeAll(); - Spannable boldSpan = (Spannable) mEmptyView.getText(); - boldSpan.setSpan( - new TextAppearanceSpan(getActivity(), android.R.style.TextAppearance_Medium), 0, - briefText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } @Override diff --git a/src/com/android/settings/bluetooth/DevicePickerFragment.java b/src/com/android/settings/bluetooth/DevicePickerFragment.java index 9441626..aa34224 100644 --- a/src/com/android/settings/bluetooth/DevicePickerFragment.java +++ b/src/com/android/settings/bluetooth/DevicePickerFragment.java @@ -48,6 +48,7 @@ public final class DevicePickerFragment extends DeviceListPreferenceFragment { private String mLaunchPackage; private String mLaunchClass; private boolean mStartScanOnResume; + private boolean mDeviceSelected; @Override void addPreferencesForActivity() { @@ -70,7 +71,8 @@ public final class DevicePickerFragment extends DeviceListPreferenceFragment { public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { menu.add(Menu.NONE, MENU_ID_REFRESH, 0, R.string.bluetooth_search_for_devices) .setEnabled(true) - .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + .setIcon(com.android.internal.R.drawable.ic_menu_refresh) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); super.onCreateOptionsMenu(menu, inflater); } @@ -103,6 +105,7 @@ public final class DevicePickerFragment extends DeviceListPreferenceFragment { public void onResume() { super.onResume(); addCachedDevices(); + mDeviceSelected = false; if (mStartScanOnResume) { mLocalAdapter.startScanning(true); mStartScanOnResume = false; @@ -110,6 +113,21 @@ public final class DevicePickerFragment extends DeviceListPreferenceFragment { } @Override + public void onDestroy() { + super.onDestroy(); + /* Check if any device was selected, if no device selected + * send ACTION_DEVICE_NOT_SELECTED intent, otherwise + * don;t do anything */ + if (!mDeviceSelected) { + Intent intent = new Intent(BluetoothDevicePicker.ACTION_DEVICE_NOT_SELECTED); + if (mLaunchPackage != null && mLaunchClass != null) { + intent.setClassName(mLaunchPackage, mLaunchClass); + } + getActivity().sendBroadcast(intent); + } + } + + @Override void onDevicePreferenceClick(BluetoothDevicePreference btPreference) { mLocalAdapter.stopScanning(); LocalBluetoothPreferences.persistSelectedDeviceInPicker( @@ -144,6 +162,7 @@ public final class DevicePickerFragment extends DeviceListPreferenceFragment { } private void sendDevicePickedIntent(BluetoothDevice device) { + mDeviceSelected = true; Intent intent = new Intent(BluetoothDevicePicker.ACTION_DEVICE_SELECTED); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); if (mLaunchPackage != null && mLaunchClass != null) { diff --git a/src/com/android/settings/bluetooth/DeviceProfilesSettings.java b/src/com/android/settings/bluetooth/DeviceProfilesSettings.java index ae42e3d..abee8cc 100755..100644 --- a/src/com/android/settings/bluetooth/DeviceProfilesSettings.java +++ b/src/com/android/settings/bluetooth/DeviceProfilesSettings.java @@ -62,9 +62,10 @@ public final class DeviceProfilesSettings extends DialogFragment implements private CachedBluetoothDevice mCachedDevice; private LocalBluetoothManager mManager; private LocalBluetoothProfileManager mProfileManager; - private ViewGroup mProfileContainer; private TextView mProfileLabel; + private static final int OK_BUTTON = -1; + private EditTextPreference mDeviceNamePref; private final HashMap<LocalBluetoothProfile, CheckBoxPreference> mAutoConnectPrefs @@ -173,11 +174,16 @@ public final class DeviceProfilesSettings extends DialogFragment implements private void addPreferencesForProfiles() { mProfileContainer.removeAllViews(); for (LocalBluetoothProfile profile : mCachedDevice.getConnectableProfiles()) { - CheckBox pref = createProfilePreference(profile); - mProfileContainer.addView(pref); + // MAP and PBAP profiles would be added based on permission access + if (!((profile instanceof PbapServerProfile) || + (profile instanceof MapProfile))) { + CheckBox pref = createProfilePreference(profile); + mProfileContainer.addView(pref); + } } final int pbapPermission = mCachedDevice.getPhonebookPermissionChoice(); + Log.d(TAG, "addPreferencesForProfiles: pbapPermission = " + pbapPermission); // Only provide PBAP cabability if the client device has requested PBAP. if (pbapPermission != CachedBluetoothDevice.ACCESS_UNKNOWN) { final PbapServerProfile psp = mManager.getProfileManager().getPbapProfile(); @@ -187,6 +193,7 @@ public final class DeviceProfilesSettings extends DialogFragment implements final MapProfile mapProfile = mManager.getProfileManager().getMapProfile(); final int mapPermission = mCachedDevice.getMessagePermissionChoice(); + Log.d(TAG, "addPreferencesForProfiles: mapPermission = " + mapPermission); if (mapPermission != CachedBluetoothDevice.ACCESS_UNKNOWN) { CheckBox mapPreference = createProfilePreference(mapProfile); mProfileContainer.addView(mapPreference); @@ -231,22 +238,16 @@ public final class DeviceProfilesSettings extends DialogFragment implements public void onClick(View v) { if (v instanceof CheckBox) { LocalBluetoothProfile prof = getProfileOf(v); - onProfileClicked(prof, (CheckBox) v); + if (prof != null) + onProfileClicked(prof, (CheckBox) v); + else + Log.e(TAG, "Error: Can't get the profile for the preference"); } } private void onProfileClicked(LocalBluetoothProfile profile, CheckBox profilePref) { BluetoothDevice device = mCachedDevice.getDevice(); - if (KEY_PBAP_SERVER.equals(profilePref.getTag())) { - final int newPermission = mCachedDevice.getPhonebookPermissionChoice() - == CachedBluetoothDevice.ACCESS_ALLOWED ? CachedBluetoothDevice.ACCESS_REJECTED - : CachedBluetoothDevice.ACCESS_ALLOWED; - mCachedDevice.setPhonebookPermissionChoice(newPermission); - profilePref.setChecked(newPermission == CachedBluetoothDevice.ACCESS_ALLOWED); - return; - } - if (!profilePref.isChecked()) { // Recheck it, until the dialog is done. profilePref.setChecked(true); @@ -255,6 +256,11 @@ public final class DeviceProfilesSettings extends DialogFragment implements if (profile instanceof MapProfile) { mCachedDevice.setMessagePermissionChoice(BluetoothDevice.ACCESS_ALLOWED); } + if (profile instanceof PbapServerProfile) { + mCachedDevice.setPhonebookPermissionChoice(BluetoothDevice.ACCESS_ALLOWED); + refreshProfilePreference(profilePref, profile); + return; + } if (profile.isPreferred(device)) { // profile is preferred but not connected: disable auto-connect if (profile instanceof PanProfile) { @@ -288,10 +294,16 @@ public final class DeviceProfilesSettings extends DialogFragment implements DialogInterface.OnClickListener disconnectListener = new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { - device.disconnect(profile); - profile.setPreferred(device.getDevice(), false); - if (profile instanceof MapProfile) { - device.setMessagePermissionChoice(BluetoothDevice.ACCESS_REJECTED); + // Disconnect only when user has selected OK + if (which == OK_BUTTON) { + device.disconnect(profile); + profile.setPreferred(device.getDevice(), false); + if (profile instanceof MapProfile) { + device.setMessagePermissionChoice(BluetoothDevice.ACCESS_REJECTED); + } + if (profile instanceof PbapServerProfile) { + device.setPhonebookPermissionChoice(BluetoothDevice.ACCESS_REJECTED); + } } refreshProfilePreference(findProfile(profile.toString()), profile); } diff --git a/src/com/android/settings/bluetooth/Utils.java b/src/com/android/settings/bluetooth/Utils.java index 2cbe473..ba8dbbc 100755 --- a/src/com/android/settings/bluetooth/Utils.java +++ b/src/com/android/settings/bluetooth/Utils.java @@ -65,7 +65,7 @@ public final class Utils { if (dialog == null) { dialog = new AlertDialog.Builder(context) .setPositiveButton(android.R.string.ok, disconnectListener) - .setNegativeButton(android.R.string.cancel, null) + .setNegativeButton(android.R.string.cancel, disconnectListener) .create(); } else { if (dialog.isShowing()) { diff --git a/src/com/android/settings/cmstats/AnonymousStats.java b/src/com/android/settings/cmstats/AnonymousStats.java new file mode 100644 index 0000000..232a533 --- /dev/null +++ b/src/com/android/settings/cmstats/AnonymousStats.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2015 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.cmstats; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Bundle; + +import android.os.UserHandle; +import android.preference.Preference; +import android.preference.PreferenceScreen; +import android.preference.SwitchPreference; +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; + +import cyanogenmod.providers.CMSettings; + +import org.cyanogenmod.internal.logging.CMMetricsLogger; + +public class AnonymousStats extends SettingsPreferenceFragment { + + private static final String PREF_FILE_NAME = "CMStats"; + /* package */ static final String ANONYMOUS_OPT_IN = "pref_anonymous_opt_in"; + /* package */ static final String ANONYMOUS_LAST_CHECKED = "pref_anonymous_checked_in"; + + /* package */ static final String KEY_LAST_JOB_ID = "last_job_id"; + /* package */ static final int QUEUE_MAX_THRESHOLD = 1000; + + public static final String KEY_STATS = "stats_collection"; + + SwitchPreference mStatsSwitch; + + public static SharedPreferences getPreferences(Context context) { + return context.getSharedPreferences(PREF_FILE_NAME, 0); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.anonymous_stats); + mStatsSwitch = (SwitchPreference) findPreference(KEY_STATS); + } + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { + if (preference == mStatsSwitch) { + boolean checked = mStatsSwitch.isChecked(); + if (checked) { + // clear opt out flags + CMSettings.Secure.putIntForUser(getContentResolver(), + CMSettings.Secure.STATS_COLLECTION_REPORTED, 0, UserHandle.USER_OWNER); + } + // will initiate opt out sequence if necessary + ReportingServiceManager.setAlarm(getActivity()); + return true; + } + return super.onPreferenceTreeClick(preferenceScreen, preference); + } + + public static void updateLastSynced(Context context) { + getPreferences(context) + .edit() + .putLong(ANONYMOUS_LAST_CHECKED,System.currentTimeMillis()) + .commit(); + } + + private static int getLastJobId(Context context) { + return getPreferences(context).getInt(KEY_LAST_JOB_ID, 0); + } + + private static void setLastJobId(Context context, int id) { + getPreferences(context) + .edit() + .putInt(KEY_LAST_JOB_ID, id) + .commit(); + } + + public static int getNextJobId(Context context) { + int lastId = getLastJobId(context); + if (lastId >= QUEUE_MAX_THRESHOLD) { + lastId = 1; + } else { + lastId += 1; + } + setLastJobId(context, lastId); + return lastId; + } + + @Override + protected int getMetricsCategory() { + return CMMetricsLogger.ANONYMOUS_STATS; + } +} diff --git a/src/com/android/settings/cmstats/PreviewData.java b/src/com/android/settings/cmstats/PreviewData.java new file mode 100644 index 0000000..0adacad --- /dev/null +++ b/src/com/android/settings/cmstats/PreviewData.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.cmstats; + +import android.content.Context; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import org.cyanogenmod.internal.logging.CMMetricsLogger; + +public class PreviewData extends SettingsPreferenceFragment { + private static final String UNIQUE_ID = "preview_id"; + private static final String DEVICE = "preview_device"; + private static final String VERSION = "preview_version"; + private static final String COUNTRY = "preview_country"; + private static final String CARRIER = "preview_carrier"; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + addPreferencesFromResource(R.xml.preview_data); + + final PreferenceScreen prefSet = getPreferenceScreen(); + final Context context = getActivity(); + + prefSet.findPreference(UNIQUE_ID).setSummary(Utilities.getUniqueID(context)); + prefSet.findPreference(DEVICE).setSummary(Utilities.getDevice()); + prefSet.findPreference(VERSION).setSummary(Utilities.getModVersion()); + prefSet.findPreference(COUNTRY).setSummary(Utilities.getCountryCode(context)); + prefSet.findPreference(CARRIER).setSummary(Utilities.getCarrier(context)); + } + + @Override + protected int getMetricsCategory() { + return CMMetricsLogger.PREVIEW_DATA; + } +} diff --git a/src/com/android/settings/cmstats/ReportingService.java b/src/com/android/settings/cmstats/ReportingService.java new file mode 100644 index 0000000..8410143 --- /dev/null +++ b/src/com/android/settings/cmstats/ReportingService.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2015 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.cmstats; + +import android.app.IntentService; +import android.app.job.JobInfo; +import android.app.job.JobScheduler; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.os.PersistableBundle; +import android.os.UserHandle; +import android.util.Log; +import cyanogenmod.providers.CMSettings; + +import java.util.List; + +public class ReportingService extends IntentService { + /* package */ static final String TAG = "CMStats"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + public static final String EXTRA_OPTING_OUT = "cmstats::opt_out"; + + public ReportingService() { + super(ReportingService.class.getSimpleName()); + } + + @Override + protected void onHandleIntent(Intent intent) { + JobScheduler js = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); + + String deviceId = Utilities.getUniqueID(getApplicationContext()); + String deviceName = Utilities.getDevice(); + String deviceVersion = Utilities.getModVersion(); + String deviceCountry = Utilities.getCountryCode(getApplicationContext()); + String deviceCarrier = Utilities.getCarrier(getApplicationContext()); + String deviceCarrierId = Utilities.getCarrierId(getApplicationContext()); + boolean optOut = intent.getBooleanExtra(EXTRA_OPTING_OUT, false); + + final int cyanogenJobId = AnonymousStats.getNextJobId(getApplicationContext()); + final int cmOrgJobId = AnonymousStats.getNextJobId(getApplicationContext()); + + if (DEBUG) Log.d(TAG, "scheduling jobs id: " + cyanogenJobId + ", " + cmOrgJobId); + + PersistableBundle cyanogenBundle = new PersistableBundle(); + cyanogenBundle.putBoolean(StatsUploadJobService.KEY_OPT_OUT, optOut); + cyanogenBundle.putString(StatsUploadJobService.KEY_DEVICE_NAME, deviceName); + cyanogenBundle.putString(StatsUploadJobService.KEY_UNIQUE_ID, deviceId); + cyanogenBundle.putString(StatsUploadJobService.KEY_VERSION, deviceVersion); + cyanogenBundle.putString(StatsUploadJobService.KEY_COUNTRY, deviceCountry); + cyanogenBundle.putString(StatsUploadJobService.KEY_CARRIER, deviceCarrier); + cyanogenBundle.putString(StatsUploadJobService.KEY_CARRIER_ID, deviceCarrierId); + cyanogenBundle.putLong(StatsUploadJobService.KEY_TIMESTAMP, System.currentTimeMillis()); + + // get snapshot and persist it + PersistableBundle cmBundle = new PersistableBundle(cyanogenBundle); + + // set job types + cyanogenBundle.putInt(StatsUploadJobService.KEY_JOB_TYPE, + StatsUploadJobService.JOB_TYPE_CYANOGEN); + cmBundle.putInt(StatsUploadJobService.KEY_JOB_TYPE, + StatsUploadJobService.JOB_TYPE_CMORG); + + // schedule cyanogen stats upload + js.schedule(new JobInfo.Builder(cyanogenJobId, new ComponentName(getPackageName(), + StatsUploadJobService.class.getName())) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) + .setMinimumLatency(1000) + .setExtras(cyanogenBundle) + .setPersisted(true) + .build()); + + // schedule cmorg stats upload + js.schedule(new JobInfo.Builder(cmOrgJobId, new ComponentName(getPackageName(), + StatsUploadJobService.class.getName())) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) + .setMinimumLatency(1000) + .setExtras(cmBundle) + .setPersisted(true) + .build()); + + if (optOut) { + // we've successfully scheduled the opt out. + CMSettings.Secure.putIntForUser(getContentResolver(), + CMSettings.Secure.STATS_COLLECTION_REPORTED, 1, UserHandle.USER_OWNER); + } + + // reschedule + AnonymousStats.updateLastSynced(this); + ReportingServiceManager.setAlarm(this); + } +} diff --git a/src/com/android/settings/cmstats/ReportingServiceManager.java b/src/com/android/settings/cmstats/ReportingServiceManager.java new file mode 100644 index 0000000..bce1372 --- /dev/null +++ b/src/com/android/settings/cmstats/ReportingServiceManager.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.cmstats; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.app.job.JobScheduler; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.UserHandle; +import android.util.Log; +import cyanogenmod.providers.CMSettings; + +public class ReportingServiceManager extends BroadcastReceiver { + private static final long MILLIS_PER_HOUR = 60L * 60L * 1000L; + private static final long MILLIS_PER_DAY = 24L * MILLIS_PER_HOUR; + private static final long UPDATE_INTERVAL = 1L * MILLIS_PER_DAY; + + private static final String TAG = ReportingServiceManager.class.getSimpleName(); + + public static final String ACTION_LAUNCH_SERVICE = + "com.android.settings.action.TRIGGER_REPORT_METRICS"; + public static final String EXTRA_FORCE = "force"; + + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) { + setAlarm(context); + } else if (intent.getAction().equals(ACTION_LAUNCH_SERVICE)){ + launchService(context, intent.getBooleanExtra(EXTRA_FORCE, false)); + } + } + + /** + * opt out if we haven't yet + */ + public static void initiateOptOut(Context context) { + final boolean optOutReported = CMSettings.Secure.getIntForUser(context.getContentResolver(), + CMSettings.Secure.STATS_COLLECTION_REPORTED, 0, UserHandle.USER_OWNER) == 1; + if (!optOutReported) { + Intent intent = new Intent(); + intent.setClass(context, ReportingService.class); + intent.putExtra(ReportingService.EXTRA_OPTING_OUT, true); + context.startServiceAsUser(intent, UserHandle.OWNER); + } + } + + public static void setAlarm(Context context) { + SharedPreferences prefs = AnonymousStats.getPreferences(context); + if (prefs.contains(AnonymousStats.ANONYMOUS_OPT_IN)) { + migrate(context, prefs); + } + if (!Utilities.isStatsCollectionEnabled(context)) { + initiateOptOut(context); + return; + } + long lastSynced = prefs.getLong(AnonymousStats.ANONYMOUS_LAST_CHECKED, 0); + if (lastSynced == 0) { + launchService(context, true); // service will reschedule the next alarm + return; + } + long millisFromNow = (lastSynced + UPDATE_INTERVAL) - System.currentTimeMillis(); + + Intent intent = new Intent(ACTION_LAUNCH_SERVICE); + intent.setClass(context, ReportingServiceManager.class); + + AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + millisFromNow, + PendingIntent.getBroadcast(context, 0, intent, 0)); + Log.d(TAG, "Next sync attempt in : " + + (millisFromNow / MILLIS_PER_HOUR) + " hours"); + } + + public static void launchService(Context context, boolean force) { + SharedPreferences prefs = AnonymousStats.getPreferences(context); + + if (!Utilities.isStatsCollectionEnabled(context)) { + return; + } + + if (!force) { + long lastSynced = prefs.getLong(AnonymousStats.ANONYMOUS_LAST_CHECKED, 0); + if (lastSynced == 0) { + setAlarm(context); + return; + } + long timeElapsed = System.currentTimeMillis() - lastSynced; + if (timeElapsed < UPDATE_INTERVAL) { + long timeLeft = UPDATE_INTERVAL - timeElapsed; + Log.d(TAG, "Waiting for next sync : " + + timeLeft / MILLIS_PER_HOUR + " hours"); + return; + } + } + + Intent intent = new Intent(); + intent.setClass(context, ReportingService.class); + context.startServiceAsUser(intent, UserHandle.OWNER); + } + + private static void migrate(Context context, SharedPreferences prefs) { + Utilities.setStatsCollectionEnabled(context, + prefs.getBoolean(AnonymousStats.ANONYMOUS_OPT_IN, true)); + prefs.edit().remove(AnonymousStats.ANONYMOUS_OPT_IN).commit(); + } + +} diff --git a/src/com/android/settings/cmstats/StatsUploadJobService.java b/src/com/android/settings/cmstats/StatsUploadJobService.java new file mode 100644 index 0000000..580a20f --- /dev/null +++ b/src/com/android/settings/cmstats/StatsUploadJobService.java @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2015 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.cmstats; + +import android.app.job.JobParameters; +import android.app.job.JobService; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.PersistableBundle; +import android.util.ArrayMap; +import android.util.Log; +import com.android.settings.R; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Collections; +import java.util.Map; + +public class StatsUploadJobService extends JobService { + + private static final String TAG = StatsUploadJobService.class.getSimpleName(); + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + public static final String KEY_JOB_TYPE = "job_type"; + public static final int JOB_TYPE_CYANOGEN = 1; + public static final int JOB_TYPE_CMORG = 2; + + public static final String KEY_UNIQUE_ID = "uniqueId"; + public static final String KEY_DEVICE_NAME = "deviceName"; + public static final String KEY_VERSION = "version"; + public static final String KEY_COUNTRY = "country"; + public static final String KEY_CARRIER = "carrier"; + public static final String KEY_CARRIER_ID = "carrierId"; + public static final String KEY_TIMESTAMP = "timeStamp"; + public static final String KEY_OPT_OUT = "optOut"; + + private final Map<JobParameters, StatsUploadTask> mCurrentJobs + = Collections.synchronizedMap(new ArrayMap<JobParameters, StatsUploadTask>()); + + @Override + public boolean onStartJob(JobParameters jobParameters) { + if (DEBUG) + Log.d(TAG, "onStartJob() called with " + "jobParameters = [" + jobParameters + "]"); + final StatsUploadTask uploadTask = new StatsUploadTask(jobParameters); + mCurrentJobs.put(jobParameters, uploadTask); + uploadTask.execute((Void) null); + return true; + } + + @Override + public boolean onStopJob(JobParameters jobParameters) { + if (DEBUG) + Log.d(TAG, "onStopJob() called with " + "jobParameters = [" + jobParameters + "]"); + + final StatsUploadTask cancelledJob; + cancelledJob = mCurrentJobs.remove(jobParameters); + + if (cancelledJob != null) { + // cancel the ongoing background task + cancelledJob.cancel(true); + return true; // reschedule + } + + return false; + } + + private class StatsUploadTask extends AsyncTask<Void, Void, Boolean> { + + private JobParameters mJobParams; + + public StatsUploadTask(JobParameters jobParams) { + this.mJobParams = jobParams; + } + + @Override + protected Boolean doInBackground(Void... params) { + + PersistableBundle extras = mJobParams.getExtras(); + + String deviceId = extras.getString(KEY_UNIQUE_ID); + String deviceName = extras.getString(KEY_DEVICE_NAME); + String deviceVersion = extras.getString(KEY_VERSION); + String deviceCountry = extras.getString(KEY_COUNTRY); + String deviceCarrier = extras.getString(KEY_CARRIER); + String deviceCarrierId = extras.getString(KEY_CARRIER_ID); + long timeStamp = extras.getLong(KEY_TIMESTAMP); + boolean optOut = extras.getBoolean(KEY_OPT_OUT); + + boolean success = false; + int jobType = extras.getInt(KEY_JOB_TYPE, -1); + if (!isCancelled()) { + switch (jobType) { + case JOB_TYPE_CYANOGEN: + try { + JSONObject json = new JSONObject(); + json.put("optOut", optOut); + json.put("uniqueId", deviceId); + json.put("deviceName", deviceName); + json.put("version", deviceVersion); + json.put("country", deviceCountry); + json.put("carrier", deviceCarrier); + json.put("carrierId", deviceCarrierId); + json.put("timestamp", timeStamp); + + success = uploadToCyanogen(json); + } catch (IOException | JSONException e) { + Log.e(TAG, "Could not upload stats checkin to cyanogen server", e); + success = false; + } + break; + + case JOB_TYPE_CMORG: + try { + success = uploadToCM(deviceId, deviceName, deviceVersion, deviceCountry, + deviceCarrier, deviceCarrierId, optOut); + } catch (IOException e) { + Log.e(TAG, "Could not upload stats checkin to commnity server", e); + success = false; + } + break; + } + } + if (DEBUG) + Log.d(TAG, "job id " + mJobParams.getJobId() + ", has finished with success=" + + success); + return success; + } + + @Override + protected void onPostExecute(Boolean success) { + mCurrentJobs.remove(mJobParams); + jobFinished(mJobParams, !success); + } + } + + + private boolean uploadToCM(String deviceId, String deviceName, String deviceVersion, + String deviceCountry, String deviceCarrier, String deviceCarrierId, + boolean optOut) + throws IOException { + + final Uri uri = Uri.parse(getString(R.string.stats_cm_url)).buildUpon() + .appendQueryParameter("opt_out", optOut ? "1" : "0") + .appendQueryParameter("device_hash", deviceId) + .appendQueryParameter("device_name", deviceName) + .appendQueryParameter("device_version", deviceVersion) + .appendQueryParameter("device_country", deviceCountry) + .appendQueryParameter("device_carrier", deviceCarrier) + .appendQueryParameter("device_carrier_id", deviceCarrierId).build(); + URL url = new URL(uri.toString()); + HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); + try { + urlConnection.setInstanceFollowRedirects(true); + urlConnection.setDoOutput(true); + urlConnection.connect(); + + final int responseCode = urlConnection.getResponseCode(); + if (DEBUG) Log.d(TAG, "cm server response code=" + responseCode); + final boolean success = responseCode == HttpURLConnection.HTTP_OK; + if (!success) { + Log.w(TAG, "failed sending, server returned: " + getResponse(urlConnection, + !success)); + } + return success; + } finally { + urlConnection.disconnect(); + } + + } + + private boolean uploadToCyanogen(JSONObject json) + throws IOException, JSONException { + String authToken = getAuthToken(); + + if (authToken.isEmpty()) { + Log.w(TAG, "no auth token!"); + } + + URL url = new URL(getString(R.string.stats_cyanogen_url)); + HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); + try { + urlConnection.setInstanceFollowRedirects(true); + urlConnection.setDoInput(true); + urlConnection.setDoOutput(true); + + urlConnection.setRequestProperty("Accept-Encoding", "identity"); + urlConnection.setRequestProperty("Authorization", authToken); + urlConnection.setRequestProperty("Content-Type", "application/json"); + + OutputStream os = urlConnection.getOutputStream(); + BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os, "UTF-8")); + writer.write(json.toString()); + writer.flush(); + writer.close(); + os.close(); + + urlConnection.connect(); + + final int responseCode = urlConnection.getResponseCode(); + final boolean success = responseCode == HttpURLConnection.HTTP_OK; + + final String response = getResponse(urlConnection, !success); + if (DEBUG) + Log.d(TAG, "server responseCode: " + responseCode +", response=" + response); + + if (!success) { + Log.w(TAG, "failed sending, server returned: " + response); + } + return success; + } finally { + urlConnection.disconnect(); + } + } + + private String getAuthToken() { + HttpURLConnection urlConnection = null; + try { + URL url = new URL(getString(R.string.stats_cyanogen_token_url)); + urlConnection = (HttpURLConnection) url.openConnection(); + urlConnection.setInstanceFollowRedirects(true); + urlConnection.setDoInput(true); + + urlConnection.setRequestProperty("Accept-Encoding", "identity"); + urlConnection.setRequestProperty("Content-Type", "text/plain"); + + urlConnection.connect(); + + final int responseCode = urlConnection.getResponseCode(); + final boolean success = responseCode == HttpURLConnection.HTTP_OK; + if (DEBUG) Log.d(TAG, "server auth response code=" + responseCode); + final String response = getResponse(urlConnection, !success); + if (DEBUG) + Log.d(TAG, "server auth response=" + response); + + if (success) { + return response; + } + } catch (IOException e) { + Log.e(TAG, "error getting auth token", e); + } finally { + if (urlConnection != null) { + urlConnection.disconnect(); + } + } + return ""; + } + + private String getResponse(HttpURLConnection httpUrlConnection, boolean errorStream) + throws IOException { + InputStream responseStream = new BufferedInputStream(errorStream + ? httpUrlConnection.getErrorStream() + : httpUrlConnection.getInputStream()); + + BufferedReader responseStreamReader = new BufferedReader( + new InputStreamReader(responseStream)); + String line = ""; + StringBuilder stringBuilder = new StringBuilder(); + while ((line = responseStreamReader.readLine()) != null) { + stringBuilder.append(line).append("\n"); + } + responseStreamReader.close(); + responseStream.close(); + + return stringBuilder.toString(); + } + +} diff --git a/src/com/android/settings/cmstats/Utilities.java b/src/com/android/settings/cmstats/Utilities.java new file mode 100644 index 0000000..1e98888 --- /dev/null +++ b/src/com/android/settings/cmstats/Utilities.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.cmstats; + +import android.content.Context; +import android.os.Build; +import android.os.SystemProperties; +import android.provider.Settings; +import android.telephony.TelephonyManager; +import android.text.TextUtils; + +import cyanogenmod.providers.CMSettings; + +import java.math.BigInteger; +import java.net.NetworkInterface; +import java.security.MessageDigest; + +public class Utilities { + public static String getUniqueID(Context context) { + final String id = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID); + return digest(context.getPackageName() + id); + } + + public static String getCarrier(Context context) { + TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + String carrier = tm.getNetworkOperatorName(); + if (TextUtils.isEmpty(carrier)) { + carrier = "Unknown"; + } + return carrier; + } + + public static String getCarrierId(Context context) { + TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + String carrierId = tm.getNetworkOperator(); + if (TextUtils.isEmpty(carrierId)) { + carrierId = "0"; + } + return carrierId; + } + + public static String getCountryCode(Context context) { + TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + String countryCode = tm.getNetworkCountryIso(); + if (TextUtils.isEmpty(countryCode)) { + countryCode = "Unknown"; + } + return countryCode; + } + + public static String getDevice() { + return SystemProperties.get("ro.cm.device", Build.PRODUCT); + } + + public static String getModVersion() { + return SystemProperties.get("ro.cm.version", Build.DISPLAY); + } + + public static String digest(String input) { + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + return new BigInteger(1, md.digest(input.getBytes())).toString(16).toUpperCase(); + } catch (Exception e) { + return null; + } + } + + /** + * Check to see if global stats are enabled. + * @param context + * @return Whether or not stats collection is enabled. + */ + public static boolean isStatsCollectionEnabled(Context context) { + return CMSettings.Secure.getInt(context.getContentResolver(), + CMSettings.Secure.STATS_COLLECTION, 1) != 0; + } + + /** + * Enabled or disable stats collection + * @param context + * @param enabled Boolean that sets collection being enabled. + */ + public static void setStatsCollectionEnabled(Context context, boolean enabled) { + int enable = (enabled) ? 1 : 0; + CMSettings.Secure.putInt(context.getContentResolver(), + CMSettings.Secure.STATS_COLLECTION, enable); + } +} diff --git a/src/com/android/settings/contributors/ContributorsCloudFragment.java b/src/com/android/settings/contributors/ContributorsCloudFragment.java new file mode 100644 index 0000000..774d552 --- /dev/null +++ b/src/com/android/settings/contributors/ContributorsCloudFragment.java @@ -0,0 +1,770 @@ +/* + * Copyright (C) 2015 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.contributors; + +import android.animation.Animator; +import android.animation.Animator.AnimatorListener; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.app.Activity; +import android.app.ActivityManager; +import android.app.AlertDialog; +import android.app.Fragment; +import android.content.Context; +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Handler; +import android.text.Html; +import android.text.TextUtils; +import android.text.format.DateFormat; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.animation.LinearInterpolator; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.SearchView; +import android.widget.TextView; +import android.widget.AdapterView.OnItemClickListener; + +import com.android.settings.R; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +public class ContributorsCloudFragment extends Fragment implements SearchView.OnQueryTextListener, + SearchView.OnCloseListener, MenuItem.OnActionExpandListener { + + private static final String TAG = "ContributorsCloud"; + + private static final String DB_NAME = "contributors.db"; + + private static final String STATE_SELECTED_CONTRIBUTOR = "state_selected_contributor"; + + private ContributorsCloudViewController mViewController; + private ImageView mImageView; + private View mLoadingView; + private View mFailedView; + private ListView mSearchResults; + private ContributorsAdapter mSearchAdapter; + + private SQLiteDatabase mDatabase; + + private int mTotalContributors; + private int mTotalCommits; + private long mLastUpdate; + + private int mSelectedContributor = -1; + private String mContributorName; + private String mContributorNick; + private int mContributorCommits; + + private MenuItem mSearchMenuItem; + private MenuItem mContributorInfoMenuItem; + private MenuItem mContributionsInfoMenuItem; + private SearchView mSearchView; + + private Handler mHandler; + + private static class ViewInfo { + Bitmap mBitmap; + float mFocusX; + float mFocusY; + } + + private static class ContributorsDataHolder { + int mId; + String mLabel; + } + + private static class ContributorsViewHolder { + TextView mLabel; + } + + private static class ContributorsAdapter extends ArrayAdapter<ContributorsDataHolder> { + + public ContributorsAdapter(Context context) { + super(context, R.id.contributor_name, new ArrayList<ContributorsDataHolder>()); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + LayoutInflater li = LayoutInflater.from(getContext()); + convertView = li.inflate(R.layout.contributors_search_result, null); + ContributorsViewHolder viewHolder = new ContributorsViewHolder(); + viewHolder.mLabel = (TextView) convertView.findViewById(R.id.contributor_name); + convertView.setTag(viewHolder); + } + + ContributorsDataHolder dataHolder = getItem(position); + + ContributorsViewHolder viewHolder = (ContributorsViewHolder) convertView.getTag(); + viewHolder.mLabel.setText(dataHolder.mLabel); + + return convertView; + } + + @Override + public boolean hasStableIds() { + return true; + } + } + + private class ContributorCloudLoaderTask extends AsyncTask<Void, Void, Boolean> { + private ViewInfo mViewInfo; + private final boolean mNotify; + private final boolean mNavigate; + + public ContributorCloudLoaderTask(boolean notify, boolean navigate) { + mNotify = notify; + mNavigate = navigate; + } + + @Override + protected void onPreExecute() { + mLoadingView.setAlpha(1f); + } + + @Override + protected Boolean doInBackground(Void... params) { + try { + loadContributorsInfo(getActivity()); + loadUserInfo(getActivity()); + mViewInfo = generateViewInfo(getActivity(), mSelectedContributor); + if (mViewInfo != null && mViewInfo.mBitmap != null) { + return Boolean.TRUE; + } + + } catch (Exception ex) { + Log.e(TAG, "Failed to generate cloud bitmap", ex); + } + return Boolean.FALSE; + } + + @Override + protected void onPostExecute(Boolean result) { + if (result == true) { + mImageView.setImageBitmap(mViewInfo.mBitmap); + mViewController.update(); + if (mNotify) { + if (mNavigate) { + onLoadCloudDataSuccess(mViewInfo.mFocusX, mViewInfo.mFocusY); + } else { + onLoadCloudDataSuccess(-1, -1); + } + } + } else { + mImageView.setImageBitmap(null); + mViewController.update(); + if (mViewInfo != null && mViewInfo.mBitmap != null) { + mViewInfo.mBitmap.recycle(); + } + if (mNotify) { + onLoadCloudDataFailed(); + } + } + } + + @Override + protected void onCancelled() { + onLoadCloudDataFailed(); + } + } + + public ContributorsCloudFragment() { + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + + if (savedInstanceState != null) { + mSelectedContributor = savedInstanceState.getInt(STATE_SELECTED_CONTRIBUTOR, -1); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (mDatabase != null && mDatabase.isOpen()) { + try { + mDatabase.close(); + } catch (SQLException ex) { + // Ignore + } + } + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt(STATE_SELECTED_CONTRIBUTOR, mSelectedContributor); + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + activity.getWindow().setSoftInputMode( + WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN + | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING); + mHandler = new Handler(); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + + // Remove all previous menus + int count = menu.size(); + for (int i = 0; i < count; i++) { + menu.removeItem(menu.getItem(i).getItemId()); + } + + inflater.inflate(R.menu.contributors_menu, menu); + + mSearchMenuItem = menu.findItem(R.id.contributors_search); + mContributorInfoMenuItem = menu.findItem(R.id.contributor_info); + mContributionsInfoMenuItem = menu.findItem(R.id.contributions_info); + mSearchView = (SearchView) mSearchMenuItem.getActionView(); + mSearchMenuItem.setOnActionExpandListener(this); + mSearchView.setOnQueryTextListener(this); + mSearchView.setOnCloseListener(this); + + showMenuItems(false); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.contributors_search: + mSearchView.setQuery("", false); + mSelectedContributor = -1; + + // Load the data from the database and fill the image + ContributorCloudLoaderTask task = new ContributorCloudLoaderTask(false, false); + task.execute(); + break; + + case R.id.contributor_info: + showUserInfo(getActivity()); + break; + + case R.id.contributions_info: + showContributorsInfo(getActivity()); + break; + + default: + break; + } + return super.onContextItemSelected(item); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle state) { + View v = inflater.inflate(R.layout.contributors_view, container, false); + + mLoadingView= v.findViewById(R.id.contributors_cloud_loading); + mFailedView= v.findViewById(R.id.contributors_cloud_failed); + mImageView = (ImageView) v.findViewById(R.id.contributors_cloud_image); + mViewController = new ContributorsCloudViewController(mImageView); + mViewController.setMaximumScale(20f); + mViewController.setMediumScale(7f); + + mSearchResults = (ListView) v.findViewById(R.id.contributors_cloud_search_results); + mSearchAdapter = new ContributorsAdapter(getActivity()); + mSearchResults.setAdapter(mSearchAdapter); + mSearchResults.setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + ContributorsDataHolder contributor = + (ContributorsDataHolder) parent.getItemAtPosition(position); + onContributorSelected(contributor); + } + }); + + // Load the data from the database and fill the image + ContributorCloudLoaderTask task = new ContributorCloudLoaderTask(true, false); + task.execute(); + + return v; + } + + @Override + public boolean onMenuItemActionExpand(MenuItem item) { + if (item.getItemId() == mSearchMenuItem.getItemId()) { + animateFadeOutFadeIn(mImageView, mSearchResults); + mContributorInfoMenuItem.setVisible(false); + mContributionsInfoMenuItem.setVisible(false); + } + return true; + } + + @Override + public boolean onMenuItemActionCollapse(MenuItem item) { + if (item.getItemId() == mSearchMenuItem.getItemId()) { + animateFadeOutFadeIn(mSearchResults, mImageView); + if (mSelectedContributor != -1) { + mContributorInfoMenuItem.setVisible(true); + } + mContributionsInfoMenuItem.setVisible(true); + } + return true; + } + + @Override + public boolean onClose() { + animateFadeOutFadeIn(mSearchResults, mImageView); + return true; + } + + @Override + public boolean onQueryTextSubmit(String query) { + return false; + } + + @Override + public boolean onQueryTextChange(String newText) { + List<ContributorsDataHolder> contributors = new ArrayList<>(); + if (!TextUtils.isEmpty(newText) || newText.length() >= 3) { + contributors.addAll(performFilter(getActivity(), newText)); + } + mSearchAdapter.clear(); + mSearchAdapter.addAll(contributors); + mSearchAdapter.notifyDataSetChanged(); + return true; + } + + private void showMenuItems(boolean visible) { + mSearchMenuItem.setVisible(visible); + mContributorInfoMenuItem.setVisible(mSelectedContributor != -1 && visible); + mContributionsInfoMenuItem.setVisible(visible); + if (!visible) { + mSearchView.setQuery("", false); + mSearchMenuItem.collapseActionView(); + } + } + + private void onLoadCloudDataSuccess(float focusX, float focusY) { + animateFadeOutFadeIn(mLoadingView.getVisibility() == View.VISIBLE + ? mLoadingView : mSearchResults, mImageView); + showMenuItems(true); + + // Navigate to contributor? + if (focusX != -1 && focusY != -1) { + mViewController.setZoomTransitionDuration(2500); + mViewController.setScale(10, focusX, focusY, true); + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + mViewController.setZoomTransitionDuration(-1); + } + }, 2500); + } + } + + private void onLoadCloudDataFailed() { + // Show the cloud not loaded message + animateFadeOutFadeIn(mLoadingView.getVisibility() == View.VISIBLE + ? mLoadingView : (mImageView.getVisibility() == View.VISIBLE) + ? mImageView : mSearchResults, mFailedView); + showMenuItems(false); + } + + private void animateFadeOutFadeIn(final View src, final View dst) { + if (dst.getVisibility() != View.VISIBLE || dst.getAlpha() != 1f) { + AnimatorSet set = new AnimatorSet(); + set.playSequentially( + ObjectAnimator.ofFloat(src, "alpha", 0f), + ObjectAnimator.ofFloat(dst, "alpha", 1f)); + set.setInterpolator(new LinearInterpolator()); + set.addListener(new AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + src.setAlpha(1f); + dst.setAlpha(0f); + src.setVisibility(View.VISIBLE); + dst.setVisibility(View.VISIBLE); + } + + @Override + public void onAnimationRepeat(Animator animation) { + } + + @Override + public void onAnimationEnd(Animator animation) { + src.setVisibility(View.GONE); + } + + @Override + public void onAnimationCancel(Animator animation) { + } + }); + set.setDuration(250); + set.start(); + } else { + src.setAlpha(1f); + src.setVisibility(View.GONE); + } + } + + private ViewInfo generateViewInfo(Context context, int selectedId) { + Bitmap bitmap = null; + float focusX = -1, focusY = -1; + final Resources res = context.getResources(); + + // Open the database + SQLiteDatabase db = getDatabase(context, true); + if (db == null) { + // We don't have a valid database reference + return null; + } + + // Extract original image size + Cursor c = db.rawQuery("select value from info where key = ?;", new String[]{"orig_size"}); + if (c == null || !c.moveToFirst()) { + // We don't have a valid cursor reference + return null; + } + int osize = c.getInt(0); + c.close(); + + // Query the metadata table to extract all the commits information + c = db.rawQuery("select id, name, x, y, r, fs from metadata;", null); + if (c == null) { + // We don't have a valid cursor reference + return null; + } + try { + int colorForeground = res.getColor(R.color.contributors_cloud_fg_color); + int colorSelected = res.getColor(R.color.contributors_cloud_selected_color); + Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); + + // Create a bitmap large enough to hold the cloud (use large bitmap when available) + int bsize = hasLargeHeap() ? 2048 : 1024; + bitmap = Bitmap.createBitmap(bsize, bsize, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + + // Draw every contributor name + while (c.moveToNext()) { + int id = c.getInt(c.getColumnIndexOrThrow("id")); + + String name = c.getString(c.getColumnIndexOrThrow("name")); + float x = translate(c.getFloat(c.getColumnIndexOrThrow("x")), osize, bsize); + float y = translate(c.getFloat(c.getColumnIndexOrThrow("y")), osize, bsize); + int r = c.getInt(c.getColumnIndexOrThrow("r")); + float fs = translate(c.getFloat(c.getColumnIndexOrThrow("fs")), osize, bsize); + if (id < 0) { + y -= translate(fs, osize, bsize); + } + + // Choose the correct paint + paint.setColor(selectedId == id ? colorSelected : colorForeground); + paint.setTextSize(fs); + + // Check text rotation + float w = 0f, h = 0f; + if (selectedId == id || r != 0) { + Rect bounds = new Rect(); + paint.getTextBounds(name, 0, name.length(), bounds); + h = bounds.height(); + } + if (selectedId == id || r == -1) { + w = paint.measureText(name); + } + if (r == 0) { + // Horizontal + canvas.drawText(name, x, y, paint); + } else { + if (r == -1) { + // Vertical (-90 rotation) + canvas.save(); + canvas.translate(h, w - h); + canvas.rotate(-90, x, y); + canvas.drawText(name, x, y, paint); + canvas.restore(); + } else { + // Vertical (+90 rotation) + canvas.save(); + canvas.translate(h/2, -h); + canvas.rotate(90, x, y); + canvas.drawText(name, x, y, paint); + canvas.restore(); + } + } + + // Calculate focus + if (selectedId == id) { + int iw = mImageView.getWidth(); + int ih = mImageView.getHeight(); + int cx = iw / 2; + int cy = ih / 2; + int cbx = bsize / 2; + int cby = bsize / 2; + float cw = 0f; + float ch = 0f; + if (r == 0) { + cw = translate(w, bsize, Math.min(iw, ih)) / 2; + ch = translate(h, bsize, Math.min(iw, ih)) / 2; + } else { + cw = translate(h, bsize, Math.min(iw, ih)) / 2; + ch = translate(w, bsize, Math.min(iw, ih)) / 2; + } + + focusX = cx + translate(x - cbx, bsize, iw) + cw; + focusY = cy + translate(y - cby, bsize, ih) + ch; + } + } + + } finally { + c.close(); + } + + // Return the bitmap + ViewInfo viewInfo = new ViewInfo(); + viewInfo.mBitmap = bitmap; + viewInfo.mFocusX = focusX; + viewInfo.mFocusY = focusY; + return viewInfo; + } + + private synchronized SQLiteDatabase getDatabase(Context context, boolean retryCopyIfOpenFails) { + if (mDatabase == null) { + File dbPath = context.getDatabasePath(DB_NAME); + try { + mDatabase = SQLiteDatabase.openDatabase(dbPath.getAbsolutePath(), + null, SQLiteDatabase.OPEN_READONLY); + if (mDatabase == null) { + Log.e(TAG, "Cannot open cloud database: " + DB_NAME + ". db == null"); + return null; + } + return mDatabase; + + } catch (SQLException ex) { + Log.e(TAG, "Cannot open cloud database: " + DB_NAME, ex); + if (mDatabase != null && mDatabase.isOpen()) { + try { + mDatabase.close(); + } catch (SQLException ex2) { + // Ignore + } + } + + if (retryCopyIfOpenFails) { + extractContributorsCloudDatabase(context); + mDatabase = getDatabase(context, false); + } + } + + // We don't have a valid connection + return null; + } + return mDatabase; + } + + private void loadContributorsInfo(Context context) { + mTotalContributors = -1; + mTotalCommits = -1; + mLastUpdate = -1; + + // Open the database + SQLiteDatabase db = getDatabase(context, true); + if (db == null) { + // We don't have a valid database reference + return; + } + + // Total contributors + Cursor c = db.rawQuery("select count(*) from metadata where id > 0;", null); + if (c == null || !c.moveToFirst()) { + // We don't have a valid cursor reference + return; + } + mTotalContributors = c.getInt(0); + c.close(); + + // Total commits + c = db.rawQuery("select sum(commits) from metadata where id > 0;", null); + if (c == null || !c.moveToFirst()) { + // We don't have a valid cursor reference + return; + } + mTotalCommits = c.getInt(0); + c.close(); + + // Last update + c = db.rawQuery("select value from info where key = ?;", new String[]{"date"}); + if (c == null || !c.moveToFirst()) { + // We don't have a valid cursor reference + return; + } + mLastUpdate = c.getLong(0); + c.close(); + } + + private void loadUserInfo(Context context) { + // Open the database + SQLiteDatabase db = getDatabase(context, true); + if (db == null) { + // We don't have a valid database reference + return; + } + + // Total contributors + String[] args = new String[]{String.valueOf(mSelectedContributor)}; + Cursor c = db.rawQuery("select m1.name, m1.username, m1.commits " + + "from metadata as m1 where m1.id = ?;", args); + if (c == null || !c.moveToFirst()) { + // We don't have a valid cursor reference + return; + } + mContributorName = c.getString(0); + mContributorNick = c.getString(1); + mContributorCommits = c.getInt(2); + } + + private void showUserInfo(Context context) { + NumberFormat nf = NumberFormat.getNumberInstance(Locale.getDefault()); + String name = mContributorName != null ? mContributorName : "-"; + String nick = mContributorNick != null ? mContributorNick : "-"; + String commits = mContributorName != null ? nf.format(mContributorCommits) : "-"; + + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(R.string.contributor_info_menu); + builder.setMessage(Html.fromHtml(getString(R.string.contributor_info_msg, + name, nick, commits))); + builder.setPositiveButton(android.R.string.ok, null); + AlertDialog dialog = builder.create(); + dialog.show(); + } + + private void showContributorsInfo(Context context) { + NumberFormat nf = NumberFormat.getNumberInstance(Locale.getDefault()); + java.text.DateFormat df = DateFormat.getLongDateFormat(context); + java.text.DateFormat tf = DateFormat.getTimeFormat(context); + String totalContributors = mTotalContributors != -1 + ? nf.format(mTotalContributors) : "-"; + String totalCommits = mTotalCommits != -1 + ? nf.format(mTotalCommits) : "-"; + String lastUpdate = mLastUpdate != -1 + ? df.format(mLastUpdate) + " " + tf.format(mLastUpdate) : "-"; + + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(R.string.contributions_info_menu); + builder.setMessage(Html.fromHtml(getString(R.string.contributions_info_msg, + totalContributors, totalCommits, lastUpdate))); + builder.setPositiveButton(android.R.string.ok, null); + AlertDialog dialog = builder.create(); + dialog.show(); + } + + private List<ContributorsDataHolder> performFilter(Context context, String query) { + // Open the database + SQLiteDatabase db = getDatabase(context, false); + if (db == null) { + // We don't have a valid database reference + return new ArrayList<>(); + } + + // Total contributors + String[] args = new String[]{String.valueOf(query.replaceAll("\\|", ""))}; + Cursor c = db.rawQuery( + "select id, name || case when username is null then '' else ' <'||username||'>' end contributor " + + "from metadata where lower(filter) like lower('%' || ? || '%') and id > 0 " + + "order by commits desc", args); + if (c == null) { + // We don't have a valid cursor reference + return new ArrayList<>(); + } + List<ContributorsDataHolder> results = new ArrayList<>(); + while (c.moveToNext()) { + ContributorsDataHolder result = new ContributorsDataHolder(); + result.mId = c.getInt(0); + result.mLabel = c.getString(1); + results.add(result); + } + return results; + } + + private void onContributorSelected(ContributorsDataHolder contributor) { + mSelectedContributor = contributor.mId; + ContributorCloudLoaderTask task = new ContributorCloudLoaderTask(true, true); + task.execute(); + mSearchMenuItem.collapseActionView(); + } + + private boolean hasLargeHeap() { + ActivityManager am = (ActivityManager) getActivity().getSystemService(Context.ACTIVITY_SERVICE); + return am.getMemoryClass() >= 96; + } + + private float translate(float v, int ssize, int dsize) { + return (v * dsize) / ssize; + } + + + public static void extractContributorsCloudDatabase(Context context) { + final int BUFFER = 1024; + InputStream is = null; + OutputStream os = null; + File databasePath = context.getDatabasePath(DB_NAME); + try { + databasePath.getParentFile().mkdir(); + is = context.getResources().getAssets().open(DB_NAME, AssetManager.ACCESS_BUFFER); + os = new FileOutputStream(databasePath); + int read = -1; + byte[] data = new byte[BUFFER]; + while ((read = is.read(data, 0, BUFFER)) != -1) { + os.write(data, 0, read); + } + } catch (IOException ex) { + Log.e(TAG, "Failed to extract contributors database"); + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException ex) { + // Ignore + } + } + } + } +} diff --git a/src/com/android/settings/contributors/ContributorsCloudViewController.java b/src/com/android/settings/contributors/ContributorsCloudViewController.java new file mode 100644 index 0000000..807d363 --- /dev/null +++ b/src/com/android/settings/contributors/ContributorsCloudViewController.java @@ -0,0 +1,1304 @@ +/******************************************************************************* + * Copyright 2011, 2012 Chris Banes. + * Copyright (C) 2015 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package com.android.settings.contributors; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Matrix; +import android.graphics.Matrix.ScaleToFit; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; +import android.util.Log; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ScaleGestureDetector.OnScaleGestureListener; +import android.view.View.OnLongClickListener; +import android.view.ViewConfiguration; +import android.view.ViewParent; +import android.view.ViewTreeObserver; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.Interpolator; +import android.widget.ImageView; +import android.widget.ImageView.ScaleType; +import android.widget.OverScroller; + +import java.lang.ref.WeakReference; + +import static android.view.MotionEvent.ACTION_CANCEL; +import static android.view.MotionEvent.ACTION_DOWN; +import static android.view.MotionEvent.ACTION_UP; + +public class ContributorsCloudViewController implements View.OnTouchListener, + ViewTreeObserver.OnGlobalLayoutListener { + + private static final String LOG_TAG = "ContributorsCloud"; + + public static final float DEFAULT_MAX_SCALE = 3.0f; + public static final float DEFAULT_MID_SCALE = 1.75f; + public static final float DEFAULT_MIN_SCALE = 1.0f; + public static final int DEFAULT_ZOOM_DURATION = 200; + + // let debug flag be dynamic, but still Proguard can be used to remove from + // release builds + private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG); + + static final Interpolator sInterpolator = new AccelerateDecelerateInterpolator(); + int ZOOM_DURATION = DEFAULT_ZOOM_DURATION; + + static final int EDGE_NONE = -1; + static final int EDGE_LEFT = 0; + static final int EDGE_RIGHT = 1; + static final int EDGE_BOTH = 2; + + private float mMinScale = DEFAULT_MIN_SCALE; + private float mMidScale = DEFAULT_MID_SCALE; + private float mMaxScale = DEFAULT_MAX_SCALE; + + private boolean mAllowParentInterceptOnEdge = true; + private boolean mBlockParentIntercept = false; + + private static final int INVALID_POINTER_ID = -1; + private int mActivePointerId = INVALID_POINTER_ID; + private int mActivePointerIndex = 0; + private float mLastTouchX; + private float mLastTouchY; + private final float mTouchSlop; + private final float mMinimumVelocity; + private VelocityTracker mVelocityTracker; + private boolean mIsDragging; + private boolean mIgnoreDoubleTapScale; + + private static void checkZoomLevels(float minZoom, float midZoom, + float maxZoom) { + if (minZoom >= midZoom) { + throw new IllegalArgumentException( + "MinZoom has to be less than MidZoom"); + } else if (midZoom >= maxZoom) { + throw new IllegalArgumentException( + "MidZoom has to be less than MaxZoom"); + } + } + + /** + * @return true if the ImageView exists, and it's Drawable existss + */ + private static boolean hasDrawable(ImageView imageView) { + return null != imageView && null != imageView.getDrawable(); + } + + /** + * @return true if the ScaleType is supported. + */ + private static boolean isSupportedScaleType(final ScaleType scaleType) { + if (null == scaleType) { + return false; + } + + switch (scaleType) { + case MATRIX: + throw new IllegalArgumentException(scaleType.name() + + " is not supported in PhotoView"); + + default: + return true; + } + } + + /** + * Set's the ImageView's ScaleType to Matrix. + */ + private static void setImageViewScaleTypeMatrix(ImageView imageView) { + /** + * PhotoView sets it's own ScaleType to Matrix, then diverts all calls + * setScaleType to this.setScaleType automatically. + */ + if (null != imageView /*&& !(imageView instanceof IPhotoView)*/) { + if (!ScaleType.MATRIX.equals(imageView.getScaleType())) { + imageView.setScaleType(ScaleType.MATRIX); + } + } + } + + public class DefaultOnDoubleTapListener implements GestureDetector.OnDoubleTapListener { + + private ContributorsCloudViewController controller; + + public DefaultOnDoubleTapListener(ContributorsCloudViewController controller) { + setController(controller); + } + + public void setController(ContributorsCloudViewController controller) { + this.controller = controller; + } + + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + if (controller == null) + return false; + + ImageView imageView = controller.getImageView(); + + if (null != controller.getOnPhotoTapListener()) { + final RectF displayRect = controller.getDisplayRect(); + + if (null != displayRect) { + final float x = e.getX(), y = e.getY(); + + // Check to see if the user tapped on the photo + if (displayRect.contains(x, y)) { + + float xResult = (x - displayRect.left) + / displayRect.width(); + float yResult = (y - displayRect.top) + / displayRect.height(); + + controller.getOnPhotoTapListener().onPhotoTap(imageView, xResult, yResult); + return true; + } + } + } + if (null != controller.getOnViewTapListener()) { + controller.getOnViewTapListener().onViewTap(imageView, e.getX(), e.getY()); + } + + return false; + } + + @Override + public boolean onDoubleTap(MotionEvent ev) { + if (controller == null) + return false; + try { + float scale = controller.getScale(); + float x = ev.getX(); + float y = ev.getY(); + + if (!mIgnoreDoubleTapScale && scale < controller.getMediumScale()) { + controller.setScale(controller.getMediumScale(), x, y, true); + } else if (!mIgnoreDoubleTapScale && scale >= controller.getMediumScale() + && scale < controller.getMaximumScale()) { + controller.setScale(controller.getMaximumScale(), x, y, true); + } else { + controller.setScale(controller.getMinimumScale(), x, y, true); + } + mIgnoreDoubleTapScale = false; + } catch (ArrayIndexOutOfBoundsException e) { + // Can sometimes happen when getX() and getY() is called + } + return true; + } + + @Override + public boolean onDoubleTapEvent(MotionEvent e) { + // Wait for the confirmed onDoubleTap() instead + return false; + } + + } + + private WeakReference<ImageView> mImageView; + + // Gesture Detectors + private GestureDetector mGestureDetector; + private ScaleGestureDetector mScaleDragDetector; + + // These are set so we don't keep allocating them on the heap + private final Matrix mBaseMatrix = new Matrix(); + private final Matrix mDrawMatrix = new Matrix(); + private final Matrix mSuppMatrix = new Matrix(); + private final RectF mDisplayRect = new RectF(); + private final float[] mMatrixValues = new float[9]; + + // Listeners + private OnMatrixChangedListener mMatrixChangeListener; + private OnPhotoTapListener mPhotoTapListener; + private OnViewTapListener mViewTapListener; + private OnLongClickListener mLongClickListener; + private OnScaleChangeListener mScaleChangeListener; + + private int mIvTop, mIvRight, mIvBottom, mIvLeft; + private FlingRunnable mCurrentFlingRunnable; + private int mScrollEdge = EDGE_BOTH; + + private boolean mZoomEnabled; + private ScaleType mScaleType = ScaleType.FIT_CENTER; + + public ContributorsCloudViewController(ImageView imageView) { + this(imageView, true); + } + + public ContributorsCloudViewController(ImageView imageView, boolean zoomable) { + final ViewConfiguration configuration = ViewConfiguration.get(imageView.getContext()); + mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); + mTouchSlop = configuration.getScaledTouchSlop(); + + mImageView = new WeakReference<>(imageView); + + imageView.setDrawingCacheEnabled(true); + imageView.setOnTouchListener(this); + + ViewTreeObserver observer = imageView.getViewTreeObserver(); + if (null != observer) + observer.addOnGlobalLayoutListener(this); + + // Make sure we using MATRIX Scale Type + setImageViewScaleTypeMatrix(imageView); + + if (imageView.isInEditMode()) { + return; + } + + // Create Gesture Detectors... + OnScaleGestureListener mScaleListener = new ScaleGestureDetector.OnScaleGestureListener() { + @Override + public boolean onScale(ScaleGestureDetector detector) { + float scaleFactor = detector.getScaleFactor(); + + if (Float.isNaN(scaleFactor) || Float.isInfinite(scaleFactor)) + return false; + + ContributorsCloudViewController.this.onScale(scaleFactor, + detector.getFocusX(), detector.getFocusY()); + return true; + } + + @Override + public boolean onScaleBegin(ScaleGestureDetector detector) { + return true; + } + + @Override + public void onScaleEnd(ScaleGestureDetector detector) { + // NO-OP + } + }; + mScaleDragDetector = new ScaleGestureDetector(imageView.getContext(), mScaleListener); + + mGestureDetector = new GestureDetector(imageView.getContext(), + new GestureDetector.SimpleOnGestureListener() { + + // forward long click listener + @Override + public void onLongPress(MotionEvent e) { + if (null != mLongClickListener) { + mLongClickListener.onLongClick(getImageView()); + } + } + }); + mGestureDetector.setOnDoubleTapListener(new DefaultOnDoubleTapListener(this)); + + // Finally, update the UI so that we're zoomable + setZoomable(zoomable); + } + + public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener newOnDoubleTapListener) { + if (newOnDoubleTapListener != null) { + this.mGestureDetector.setOnDoubleTapListener(newOnDoubleTapListener); + } else { + this.mGestureDetector.setOnDoubleTapListener(new DefaultOnDoubleTapListener(this)); + } + } + + public void setOnScaleChangeListener(OnScaleChangeListener onScaleChangeListener) { + this.mScaleChangeListener = onScaleChangeListener; + } + + public boolean canZoom() { + return mZoomEnabled; + } + + /** + * Clean-up the resources attached to this object. This needs to be called when the ImageView is + * no longer used. A good example is from {@link android.view.View#onDetachedFromWindow()} or + * from {@link android.app.Activity#onDestroy()}. This is automatically called if you are using + * {@link uk.co.senab.photoview.PhotoView}. + */ + @SuppressWarnings("deprecation") + public void cleanup() { + if (null == mImageView) { + return; // cleanup already done + } + + final ImageView imageView = mImageView.get(); + + if (null != imageView) { + // Remove this as a global layout listener + ViewTreeObserver observer = imageView.getViewTreeObserver(); + if (null != observer && observer.isAlive()) { + observer.removeGlobalOnLayoutListener(this); + } + + // Remove the ImageView's reference to this + imageView.setOnTouchListener(null); + + // make sure a pending fling runnable won't be run + cancelFling(); + } + + if (null != mGestureDetector) { + mGestureDetector.setOnDoubleTapListener(null); + } + + // Clear listeners too + mMatrixChangeListener = null; + mPhotoTapListener = null; + mViewTapListener = null; + + // Finally, clear ImageView + mImageView = null; + } + + public RectF getDisplayRect() { + checkMatrixBounds(); + return getDisplayRect(getDrawMatrix()); + } + + public boolean setDisplayMatrix(Matrix finalMatrix) { + if (finalMatrix == null) + throw new IllegalArgumentException("Matrix cannot be null"); + + ImageView imageView = getImageView(); + if (null == imageView) + return false; + + if (null == imageView.getDrawable()) + return false; + + mSuppMatrix.set(finalMatrix); + setImageViewMatrix(getDrawMatrix()); + checkMatrixBounds(); + + return true; + } + + public void setRotationTo(float degrees) { + mSuppMatrix.setRotate(degrees % 360); + checkAndDisplayMatrix(); + } + + public void setRotationBy(float degrees) { + mSuppMatrix.postRotate(degrees % 360); + checkAndDisplayMatrix(); + } + + public ImageView getImageView() { + ImageView imageView = null; + + if (null != mImageView) { + imageView = mImageView.get(); + } + + // If we don't have an ImageView, call cleanup() + if (null == imageView) { + cleanup(); + Log.i(LOG_TAG, "ImageView no longer exists. You should " + + "not use this reference any more."); + } + + return imageView; + } + + public float getMinimumScale() { + return mMinScale; + } + + public float getMediumScale() { + return mMidScale; + } + + public float getMaximumScale() { + return mMaxScale; + } + + public float getScale() { + return (float) Math.sqrt((float) Math.pow(getValue(mSuppMatrix, Matrix.MSCALE_X), 2) + + (float) Math.pow(getValue(mSuppMatrix, Matrix.MSKEW_Y), 2)); + } + + public ScaleType getScaleType() { + return mScaleType; + } + + public void onDrag(float dx, float dy) { + if (mScaleDragDetector.isInProgress()) { + return; // Do not drag if we are already scaling + } + + if (DEBUG) { + Log.d(LOG_TAG, String.format("onDrag: dx: %.2f. dy: %.2f", dx, dy)); + } + + ImageView imageView = getImageView(); + mSuppMatrix.postTranslate(dx, dy); + checkAndDisplayMatrix(); + + /** + * Here we decide whether to let the ImageView's parent to start taking + * over the touch event. + * + * First we check whether this function is enabled. We never want the + * parent to take over if we're scaling. We then check the edge we're + * on, and the direction of the scroll (i.e. if we're pulling against + * the edge, aka 'overscrolling', let the parent take over). + */ + ViewParent parent = imageView.getParent(); + if (mAllowParentInterceptOnEdge && !mScaleDragDetector.isInProgress() + && !mBlockParentIntercept) { + if (mScrollEdge == EDGE_BOTH + || (mScrollEdge == EDGE_LEFT && dx >= 1f) + || (mScrollEdge == EDGE_RIGHT && dx <= -1f)) { + if (null != parent) + parent.requestDisallowInterceptTouchEvent(false); + } + } else { + if (null != parent) { + parent.requestDisallowInterceptTouchEvent(true); + } + } + mIgnoreDoubleTapScale = false; + } + + public void onFling(float startX, float startY, float velocityX, float velocityY) { + if (DEBUG) { + Log.d(LOG_TAG, "onFling. sX: " + startX + " sY: " + startY + " Vx: " + + velocityX + " Vy: " + velocityY); + } + ImageView imageView = getImageView(); + mCurrentFlingRunnable = new FlingRunnable(imageView.getContext()); + mCurrentFlingRunnable.fling(getImageViewWidth(imageView), + getImageViewHeight(imageView), (int) velocityX, (int) velocityY); + imageView.post(mCurrentFlingRunnable); + mIgnoreDoubleTapScale = false; + } + + @Override + public void onGlobalLayout() { + ImageView imageView = getImageView(); + + if (null != imageView) { + if (mZoomEnabled) { + final int top = imageView.getTop(); + final int right = imageView.getRight(); + final int bottom = imageView.getBottom(); + final int left = imageView.getLeft(); + + /** + * We need to check whether the ImageView's bounds have changed. + * This would be easier if we targeted API 11+ as we could just use + * View.OnLayoutChangeListener. Instead we have to replicate the + * work, keeping track of the ImageView's bounds and then checking + * if the values change. + */ + if (top != mIvTop || bottom != mIvBottom || left != mIvLeft + || right != mIvRight) { + // Update our base matrix, as the bounds have changed + updateBaseMatrix(imageView.getDrawable()); + + // Update values as something has changed + mIvTop = top; + mIvRight = right; + mIvBottom = bottom; + mIvLeft = left; + } + } else { + updateBaseMatrix(imageView.getDrawable()); + } + } + } + + public void onScale(float scaleFactor, float focusX, float focusY) { + if (DEBUG) { + Log.d(LOG_TAG,String.format("onScale: scale: %.2f. fX: %.2f. fY: %.2f", + scaleFactor, focusX, focusY)); + } + + if (getScale() < mMaxScale || scaleFactor < 1f) { + if (null != mScaleChangeListener) { + mScaleChangeListener.onScaleChange(scaleFactor, focusX, focusY); + } + mSuppMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY); + checkAndDisplayMatrix(); + } + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouch(View v, MotionEvent ev) { + boolean handled = false; + + if (mZoomEnabled && hasDrawable((ImageView) v)) { + ViewParent parent = v.getParent(); + switch (ev.getAction()) { + case ACTION_DOWN: + // First, disable the Parent from intercepting the touch + // event + if (null != parent) { + parent.requestDisallowInterceptTouchEvent(true); + } else { + Log.i(LOG_TAG, "onTouch getParent() returned null"); + } + + // If we're flinging, and the user presses down, cancel + // fling + cancelFling(); + break; + + case ACTION_CANCEL: + case ACTION_UP: + // If the user has zoomed less than min scale, zoom back + // to min scale + if (getScale() < mMinScale) { + RectF rect = getDisplayRect(); + if (null != rect) { + v.post(new AnimatedZoomRunnable(getScale(), mMinScale, + rect.centerX(), rect.centerY())); + handled = true; + } + } + break; + } + + // Try the Scale/Drag detector + if (null != mScaleDragDetector) { + boolean wasScaling = mScaleDragDetector.isInProgress(); + boolean wasDragging = mIsDragging; + + handled = onTouchEvent(ev); + + boolean didntScale = !wasScaling && !mScaleDragDetector.isInProgress(); + boolean didntDrag = !wasDragging && !mIsDragging; + + mBlockParentIntercept = didntScale && didntDrag; + } + + // Check to see if the user double tapped + if (null != mGestureDetector && mGestureDetector.onTouchEvent(ev)) { + handled = true; + } + + } + + return handled; + } + + public void setAllowParentInterceptOnEdge(boolean allow) { + mAllowParentInterceptOnEdge = allow; + } + + public void setMinimumScale(float minimumScale) { + checkZoomLevels(minimumScale, mMidScale, mMaxScale); + mMinScale = minimumScale; + } + + public void setMediumScale(float mediumScale) { + checkZoomLevels(mMinScale, mediumScale, mMaxScale); + mMidScale = mediumScale; + } + + public void setMaximumScale(float maximumScale) { + checkZoomLevels(mMinScale, mMidScale, maximumScale); + mMaxScale = maximumScale; + } + + public void setScaleLevels(float minimumScale, float mediumScale, float maximumScale) { + checkZoomLevels(minimumScale, mediumScale, maximumScale); + mMinScale = minimumScale; + mMidScale = mediumScale; + mMaxScale = maximumScale; + } + + public void setOnLongClickListener(OnLongClickListener listener) { + mLongClickListener = listener; + } + + public void setOnMatrixChangeListener(OnMatrixChangedListener listener) { + mMatrixChangeListener = listener; + } + + public void setOnPhotoTapListener(OnPhotoTapListener listener) { + mPhotoTapListener = listener; + } + + public OnPhotoTapListener getOnPhotoTapListener() { + return mPhotoTapListener; + } + + public void setOnViewTapListener(OnViewTapListener listener) { + mViewTapListener = listener; + } + + public OnViewTapListener getOnViewTapListener() { + return mViewTapListener; + } + + public void setScale(float scale) { + setScale(scale, false); + } + + public void setScale(float scale, boolean animate) { + ImageView imageView = getImageView(); + + if (null != imageView) { + setScale(scale, + (imageView.getRight()) / 2, + (imageView.getBottom()) / 2, + animate); + } + } + + public void setScale(float scale, float focalX, float focalY, boolean animate) { + ImageView imageView = getImageView(); + + if (null != imageView) { + // Check to see if the scale is within bounds + if (scale < mMinScale || scale > mMaxScale) { + Log.i(LOG_TAG, "Scale must be within the range of minScale and maxScale"); + return; + } + + if (animate) { + imageView.post(new AnimatedZoomRunnable(getScale(), scale, + focalX, focalY)); + } else { + mSuppMatrix.setScale(scale, scale, focalX, focalY); + checkAndDisplayMatrix(); + } + + // This focuses to some point of the view, so treat it as a return point to + // minimum zoom + if (scale < getMediumScale()) { + mIgnoreDoubleTapScale = true; + } + } + } + + public void setScaleType(ScaleType scaleType) { + if (isSupportedScaleType(scaleType) && scaleType != mScaleType) { + mScaleType = scaleType; + + // Finally update + update(); + } + } + + public void setZoomable(boolean zoomable) { + mZoomEnabled = zoomable; + update(); + } + + public void update() { + ImageView imageView = getImageView(); + + if (null != imageView) { + if (mZoomEnabled) { + // Make sure we using MATRIX Scale Type + setImageViewScaleTypeMatrix(imageView); + + // Update the base matrix using the current drawable + updateBaseMatrix(imageView.getDrawable()); + } else { + // Reset the Matrix... + resetMatrix(); + } + } + } + + public Matrix getDisplayMatrix() { + return new Matrix(getDrawMatrix()); + } + + public Matrix getDrawMatrix() { + mDrawMatrix.set(mBaseMatrix); + mDrawMatrix.postConcat(mSuppMatrix); + return mDrawMatrix; + } + + private void cancelFling() { + if (null != mCurrentFlingRunnable) { + mCurrentFlingRunnable.cancelFling(); + mCurrentFlingRunnable = null; + } + } + + /** + * Helper method that simply checks the Matrix, and then displays the result + */ + private void checkAndDisplayMatrix() { + if (checkMatrixBounds()) { + setImageViewMatrix(getDrawMatrix()); + } + } + + private void checkImageViewScaleType() { + ImageView imageView = getImageView(); + + /** + * PhotoView's getScaleType() will just divert to this.getScaleType() so + * only call if we're not attached to a PhotoView. + */ + if (null != imageView/* && !(imageView instanceof IPhotoView)*/) { + if (!ScaleType.MATRIX.equals(imageView.getScaleType())) { + throw new IllegalStateException("The ImageView's ScaleType has been " + + "changed since attaching to this controller"); + } + } + } + + private boolean checkMatrixBounds() { + final ImageView imageView = getImageView(); + if (null == imageView) { + return false; + } + + final RectF rect = getDisplayRect(getDrawMatrix()); + if (null == rect) { + return false; + } + + final float height = rect.height(), width = rect.width(); + float deltaX = 0, deltaY = 0; + + final int viewHeight = getImageViewHeight(imageView); + if (height <= viewHeight) { + switch (mScaleType) { + case FIT_START: + deltaY = -rect.top; + break; + case FIT_END: + deltaY = viewHeight - height - rect.top; + break; + default: + deltaY = (viewHeight - height) / 2 - rect.top; + break; + } + } else if (rect.top > 0) { + deltaY = -rect.top; + } else if (rect.bottom < viewHeight) { + deltaY = viewHeight - rect.bottom; + } + + final int viewWidth = getImageViewWidth(imageView); + if (width <= viewWidth) { + switch (mScaleType) { + case FIT_START: + deltaX = -rect.left; + break; + case FIT_END: + deltaX = viewWidth - width - rect.left; + break; + default: + deltaX = (viewWidth - width) / 2 - rect.left; + break; + } + mScrollEdge = EDGE_BOTH; + } else if (rect.left > 0) { + mScrollEdge = EDGE_LEFT; + deltaX = -rect.left; + } else if (rect.right < viewWidth) { + deltaX = viewWidth - rect.right; + mScrollEdge = EDGE_RIGHT; + } else { + mScrollEdge = EDGE_NONE; + } + + // Finally actually translate the matrix + mSuppMatrix.postTranslate(deltaX, deltaY); + return true; + } + + /** + * Helper method that maps the supplied Matrix to the current Drawable + * + * @param matrix - Matrix to map Drawable against + * @return RectF - Displayed Rectangle + */ + private RectF getDisplayRect(Matrix matrix) { + ImageView imageView = getImageView(); + + if (null != imageView) { + Drawable d = imageView.getDrawable(); + if (null != d) { + mDisplayRect.set(0, 0, d.getIntrinsicWidth(), + d.getIntrinsicHeight()); + matrix.mapRect(mDisplayRect); + return mDisplayRect; + } + } + return null; + } + + public Bitmap getVisibleRectangleBitmap() { + ImageView imageView = getImageView(); + return imageView == null ? null : imageView.getDrawingCache(); + } + + public void setZoomTransitionDuration(int milliseconds) { + if (milliseconds < 0) + milliseconds = DEFAULT_ZOOM_DURATION; + this.ZOOM_DURATION = milliseconds; + } + + /** + * Helper method that 'unpacks' a Matrix and returns the required value + * + * @param matrix - Matrix to unpack + * @param whichValue - Which value from Matrix.M* to return + * @return float - returned value + */ + private float getValue(Matrix matrix, int whichValue) { + matrix.getValues(mMatrixValues); + return mMatrixValues[whichValue]; + } + + /** + * Resets the Matrix back to FIT_CENTER, and then displays it.s + */ + private void resetMatrix() { + mSuppMatrix.reset(); + setImageViewMatrix(getDrawMatrix()); + checkMatrixBounds(); + } + + private void setImageViewMatrix(Matrix matrix) { + ImageView imageView = getImageView(); + if (null != imageView) { + + checkImageViewScaleType(); + imageView.setImageMatrix(matrix); + + // Call MatrixChangedListener if needed + if (null != mMatrixChangeListener) { + RectF displayRect = getDisplayRect(matrix); + if (null != displayRect) { + mMatrixChangeListener.onMatrixChanged(displayRect); + } + } + } + } + + /** + * Calculate Matrix for FIT_CENTER + * + * @param d - Drawable being displayed + */ + private void updateBaseMatrix(Drawable d) { + ImageView imageView = getImageView(); + if (null == imageView || null == d) { + return; + } + + final float viewWidth = getImageViewWidth(imageView); + final float viewHeight = getImageViewHeight(imageView); + final int drawableWidth = d.getIntrinsicWidth(); + final int drawableHeight = d.getIntrinsicHeight(); + + mBaseMatrix.reset(); + + final float widthScale = viewWidth / drawableWidth; + final float heightScale = viewHeight / drawableHeight; + + if (mScaleType == ScaleType.CENTER) { + mBaseMatrix.postTranslate((viewWidth - drawableWidth) / 2F, + (viewHeight - drawableHeight) / 2F); + + } else if (mScaleType == ScaleType.CENTER_CROP) { + float scale = Math.max(widthScale, heightScale); + mBaseMatrix.postScale(scale, scale); + mBaseMatrix.postTranslate((viewWidth - drawableWidth * scale) / 2F, + (viewHeight - drawableHeight * scale) / 2F); + + } else if (mScaleType == ScaleType.CENTER_INSIDE) { + float scale = Math.min(1.0f, Math.min(widthScale, heightScale)); + mBaseMatrix.postScale(scale, scale); + mBaseMatrix.postTranslate((viewWidth - drawableWidth * scale) / 2F, + (viewHeight - drawableHeight * scale) / 2F); + + } else { + RectF mTempSrc = new RectF(0, 0, drawableWidth, drawableHeight); + RectF mTempDst = new RectF(0, 0, viewWidth, viewHeight); + + switch (mScaleType) { + case FIT_CENTER: + mBaseMatrix + .setRectToRect(mTempSrc, mTempDst, ScaleToFit.CENTER); + break; + + case FIT_START: + mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.START); + break; + + case FIT_END: + mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.END); + break; + + case FIT_XY: + mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.FILL); + break; + + default: + break; + } + } + + resetMatrix(); + } + + private int getImageViewWidth(ImageView imageView) { + if (null == imageView) + return 0; + return imageView.getWidth() - imageView.getPaddingLeft() - imageView.getPaddingRight(); + } + + private int getImageViewHeight(ImageView imageView) { + if (null == imageView) + return 0; + return imageView.getHeight() - imageView.getPaddingTop() - imageView.getPaddingBottom(); + } + + private float getActiveX(MotionEvent ev) { + try { + return ev.getX(mActivePointerIndex); + } catch (Exception e) { + return ev.getX(); + } + } + + private float getActiveY(MotionEvent ev) { + try { + return ev.getY(mActivePointerIndex); + } catch (Exception e) { + return ev.getY(); + } + } + + public boolean onTouchEvent(MotionEvent ev) { + mScaleDragDetector.onTouchEvent(ev); + + final int action = ev.getAction(); + switch (action & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: + mActivePointerId = ev.getPointerId(0); + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + mActivePointerId = INVALID_POINTER_ID; + break; + case MotionEvent.ACTION_POINTER_UP: + // Ignore deprecation, ACTION_POINTER_ID_MASK and + // ACTION_POINTER_ID_SHIFT has same value and are deprecated + // You can have either deprecation or lint target api warning + final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) + >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; + final int pointerId = ev.getPointerId(pointerIndex); + if (pointerId == mActivePointerId) { + // This was our active pointer going up. Choose a new + // active pointer and adjust accordingly. + final int newPointerIndex = pointerIndex == 0 ? 1 : 0; + mActivePointerId = ev.getPointerId(newPointerIndex); + mLastTouchX = ev.getX(newPointerIndex); + mLastTouchY = ev.getY(newPointerIndex); + } + break; + } + + mActivePointerIndex = ev.findPointerIndex( + mActivePointerId != INVALID_POINTER_ID ? mActivePointerId : 0); + + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: { + mVelocityTracker = VelocityTracker.obtain(); + if (null != mVelocityTracker) { + mVelocityTracker.addMovement(ev); + } else { + Log.i(LOG_TAG, "Velocity tracker is null"); + } + + mLastTouchX = getActiveX(ev); + mLastTouchY = getActiveY(ev); + mIsDragging = false; + break; + } + + case MotionEvent.ACTION_MOVE: { + final float x = getActiveX(ev); + final float y = getActiveY(ev); + final float dx = x - mLastTouchX, dy = y - mLastTouchY; + + if (!mIsDragging) { + // Use Pythagoras to see if drag length is larger than + // touch slop + mIsDragging = Math.sqrt((dx * dx) + (dy * dy)) >= mTouchSlop; + } + + if (mIsDragging) { + onDrag(dx, dy); + mLastTouchX = x; + mLastTouchY = y; + + if (null != mVelocityTracker) { + mVelocityTracker.addMovement(ev); + } + } + break; + } + + case MotionEvent.ACTION_CANCEL: { + // Recycle Velocity Tracker + if (null != mVelocityTracker) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + break; + } + + case MotionEvent.ACTION_UP: { + if (mIsDragging) { + if (null != mVelocityTracker) { + mLastTouchX = getActiveX(ev); + mLastTouchY = getActiveY(ev); + + // Compute velocity within the last 1000ms + mVelocityTracker.addMovement(ev); + mVelocityTracker.computeCurrentVelocity(1000); + + final float vX = mVelocityTracker.getXVelocity(), vY = mVelocityTracker + .getYVelocity(); + + // If the velocity is greater than minVelocity, call + // listener + if (Math.max(Math.abs(vX), Math.abs(vY)) >= mMinimumVelocity) { + onFling(mLastTouchX, mLastTouchY, -vX, -vY); + } + } + } + + // Recycle Velocity Tracker + if (null != mVelocityTracker) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + break; + } + } + + return true; + } + + /** + * Interface definition for a callback to be invoked when the internal Matrix has changed for + * this View. + * + * @author Chris Banes + */ + public static interface OnMatrixChangedListener { + /** + * Callback for when the Matrix displaying the Drawable has changed. This could be because + * the View's bounds have changed, or the user has zoomed. + * + * @param rect - Rectangle displaying the Drawable's new bounds. + */ + void onMatrixChanged(RectF rect); + } + + /** + * Interface definition for callback to be invoked when attached ImageView scale changes + * + * @author Marek Sebera + */ + public static interface OnScaleChangeListener { + /** + * Callback for when the scale changes + * + * @param scaleFactor the scale factor (<1 for zoom out, >1 for zoom in) + * @param focusX focal point X position + * @param focusY focal point Y position + */ + void onScaleChange(float scaleFactor, float focusX, float focusY); + } + + /** + * Interface definition for a callback to be invoked when the Photo is tapped with a single + * tap. + * + * @author Chris Banes + */ + public static interface OnPhotoTapListener { + + /** + * A callback to receive where the user taps on a photo. You will only receive a callback if + * the user taps on the actual photo, tapping on 'whitespace' will be ignored. + * + * @param view - View the user tapped. + * @param x - where the user tapped from the of the Drawable, as percentage of the + * Drawable width. + * @param y - where the user tapped from the top of the Drawable, as percentage of the + * Drawable height. + */ + void onPhotoTap(View view, float x, float y); + } + + /** + * Interface definition for a callback to be invoked when the ImageView is tapped with a single + * tap. + * + * @author Chris Banes + */ + public static interface OnViewTapListener { + + /** + * A callback to receive where the user taps on a ImageView. You will receive a callback if + * the user taps anywhere on the view, tapping on 'whitespace' will not be ignored. + * + * @param view - View the user tapped. + * @param x - where the user tapped from the left of the View. + * @param y - where the user tapped from the top of the View. + */ + void onViewTap(View view, float x, float y); + } + + private class AnimatedZoomRunnable implements Runnable { + + private final float mFocalX, mFocalY; + private final long mStartTime; + private final float mZoomStart, mZoomEnd; + + public AnimatedZoomRunnable(final float currentZoom, final float targetZoom, + final float focalX, final float focalY) { + mFocalX = focalX; + mFocalY = focalY; + mStartTime = System.currentTimeMillis(); + mZoomStart = currentZoom; + mZoomEnd = targetZoom; + } + + @Override + public void run() { + ImageView imageView = getImageView(); + if (imageView == null) { + return; + } + + float t = interpolate(); + float scale = mZoomStart + t * (mZoomEnd - mZoomStart); + float deltaScale = scale / getScale(); + + onScale(deltaScale, mFocalX, mFocalY); + + // We haven't hit our target scale yet, so post ourselves again + if (t < 1f) { + imageView.postOnAnimation(this); + } + } + + private float interpolate() { + float t = 1f * (System.currentTimeMillis() - mStartTime) / ZOOM_DURATION; + t = Math.min(1f, t); + t = sInterpolator.getInterpolation(t); + return t; + } + } + + private class FlingRunnable implements Runnable { + + protected final OverScroller mScroller; + private int mCurrentX, mCurrentY; + + public FlingRunnable(Context context) { + mScroller = new OverScroller(context); + } + + public void cancelFling() { + if (DEBUG) { + Log.d(LOG_TAG, "Cancel Fling"); + } + mScroller.forceFinished(true); + } + + public void fling(int viewWidth, int viewHeight, int velocityX, + int velocityY) { + final RectF rect = getDisplayRect(); + if (null == rect) { + return; + } + + final int startX = Math.round(-rect.left); + final int minX, maxX, minY, maxY; + + if (viewWidth < rect.width()) { + minX = 0; + maxX = Math.round(rect.width() - viewWidth); + } else { + minX = maxX = startX; + } + + final int startY = Math.round(-rect.top); + if (viewHeight < rect.height()) { + minY = 0; + maxY = Math.round(rect.height() - viewHeight); + } else { + minY = maxY = startY; + } + + mCurrentX = startX; + mCurrentY = startY; + + if (DEBUG) { + Log.d(LOG_TAG, "fling. StartX:" + startX + " StartY:" + startY + + " MaxX:" + maxX + " MaxY:" + maxY); + } + + // If we actually can move, fling the scroller + if (startX != maxX || startY != maxY) { + mScroller.fling(startX, startY, velocityX, velocityY, minX, + maxX, minY, maxY, 0, 0); + } + } + + @Override + public void run() { + if (mScroller.isFinished()) { + return; // remaining post that should not be handled + } + + ImageView imageView = getImageView(); + if (null != imageView && mScroller.computeScrollOffset()) { + + final int newX = mScroller.getCurrX(); + final int newY = mScroller.getCurrY(); + + if (DEBUG) { + Log.d(LOG_TAG, "fling run(). CurrentX:" + mCurrentX + " CurrentY:" + + mCurrentY + " NewX:" + newX + " NewY:" + newY); + } + + mSuppMatrix.postTranslate(mCurrentX - newX, mCurrentY - newY); + setImageViewMatrix(getDrawMatrix()); + + mCurrentX = newX; + mCurrentY = newY; + + // Post On animation + imageView.postOnAnimation(this); + } + } + } +} diff --git a/src/com/android/settings/cyanogenmod/BacklightTimeoutSeekBar.java b/src/com/android/settings/cyanogenmod/BacklightTimeoutSeekBar.java new file mode 100644 index 0000000..66f1fdd --- /dev/null +++ b/src/com/android/settings/cyanogenmod/BacklightTimeoutSeekBar.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.cyanogenmod; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.widget.SeekBar; + +public class BacklightTimeoutSeekBar extends SeekBar { + private int mMax; + private int mGap; + private boolean mUpdatingThumb; + + public BacklightTimeoutSeekBar(Context context) { + super(context); + } + + public BacklightTimeoutSeekBar(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public BacklightTimeoutSeekBar(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + mUpdatingThumb = true; + super.onSizeChanged(w, h, oldw, oldh); + mUpdatingThumb = false; + } + + @Override + public void setThumb(Drawable thumb) { + mUpdatingThumb = true; + super.setThumb(thumb); + mUpdatingThumb = false; + } + + @Override + public void setMax(int max) { + mMax = max; + mGap = max / 10; + super.setMax(max + 2 * mGap - 1); + } + + @Override + protected int updateTouchProgress(int lastProgress, int newProgress) { + if (newProgress < mMax) { + return newProgress; + } + if (newProgress < mMax + mGap) { + return mMax - 1; + } + return getMax(); + } +} diff --git a/src/com/android/settings/cyanogenmod/BaseSystemSettingSwitchBar.java b/src/com/android/settings/cyanogenmod/BaseSystemSettingSwitchBar.java new file mode 100644 index 0000000..22f5277 --- /dev/null +++ b/src/com/android/settings/cyanogenmod/BaseSystemSettingSwitchBar.java @@ -0,0 +1,155 @@ +/* +* Copyright (C) 2014 The CyanogenMod Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +package com.android.settings.cyanogenmod; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.provider.Settings; +import android.widget.Switch; +import com.android.settings.widget.SwitchBar; + +public class BaseSystemSettingSwitchBar implements SwitchBar.OnSwitchChangeListener { + private Context mContext; + private SwitchBar mSwitchBar; + private SettingsObserver mSettingsObserver; + private boolean mListeningToOnSwitchChange = false; + + private boolean mStateMachineEvent; + + private final String mSettingKey; + private final int mDefaultState; + + private final SwitchBarChangeCallback mCallback; + public interface SwitchBarChangeCallback { + public void onEnablerChanged(boolean isEnabled); + } + + public BaseSystemSettingSwitchBar(Context context, SwitchBar switchBar, String key, + boolean defaultState, SwitchBarChangeCallback callback) { + mContext = context; + mSwitchBar = switchBar; + mSettingKey = key; + mDefaultState = defaultState ? 1 : 0; + mCallback = callback; + mSettingsObserver = new SettingsObserver(new Handler()); + setupSwitchBar(); + } + + public void setupSwitchBar() { + setSwitchState(); + if (!mListeningToOnSwitchChange) { + mSwitchBar.addOnSwitchChangeListener(this); + mListeningToOnSwitchChange = true; + } + mSwitchBar.show(); + } + + public void teardownSwitchBar() { + if (mListeningToOnSwitchChange) { + mSwitchBar.removeOnSwitchChangeListener(this); + mListeningToOnSwitchChange = false; + } + mSwitchBar.hide(); + } + + public void resume(Context context) { + mContext = context; + if (!mListeningToOnSwitchChange) { + mSwitchBar.addOnSwitchChangeListener(this); + mSettingsObserver.observe(); + + mListeningToOnSwitchChange = true; + } + } + + public void pause() { + if (mListeningToOnSwitchChange) { + mSwitchBar.removeOnSwitchChangeListener(this); + mSettingsObserver.unobserve(); + + mListeningToOnSwitchChange = false; + } + } + + private void setSwitchBarChecked(boolean checked) { + mStateMachineEvent = true; + mSwitchBar.setChecked(checked); + mStateMachineEvent = false; + if (mCallback != null) { + mCallback.onEnablerChanged(checked); + } + } + + private void setSwitchState() { + boolean enabled = Settings.System.getInt(mContext.getContentResolver(), + mSettingKey, mDefaultState) == 1; + mStateMachineEvent = true; + setSwitchBarChecked(enabled); + mStateMachineEvent = false; + } + + @Override + public void onSwitchChanged(Switch switchView, boolean isChecked) { + //Do nothing if called as a result of a state machine event + if (mStateMachineEvent) { + return; + } + + // Handle a switch change + Settings.System.putInt(mContext.getContentResolver(), + mSettingKey, isChecked ? 1 : 0); + + if (mCallback != null) { + mCallback.onEnablerChanged(isChecked); + } + } + + class SettingsObserver extends ContentObserver { + SettingsObserver(Handler handler) { + super(handler); + } + + void observe() { + ContentResolver resolver = mContext.getContentResolver(); + resolver.registerContentObserver(Settings.System.getUriFor( + mSettingKey), false, this); + update(); + } + + void unobserve() { + ContentResolver resolver = mContext.getContentResolver(); + resolver.unregisterContentObserver(this); + } + + @Override + public void onChange(boolean selfChange) { + update(); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + update(); + } + + public void update() { + setSwitchState(); + } + } +} diff --git a/src/com/android/settings/cyanogenmod/BootReceiver.java b/src/com/android/settings/cyanogenmod/BootReceiver.java new file mode 100644 index 0000000..406ccb1 --- /dev/null +++ b/src/com/android/settings/cyanogenmod/BootReceiver.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.cyanogenmod; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; + +import com.android.settings.ButtonSettings; +import com.android.settings.DisplaySettings; +import com.android.settings.R; +import com.android.settings.Utils; +import com.android.settings.contributors.ContributorsCloudFragment; +import com.android.settings.hardware.VibratorIntensity; +import com.android.settings.inputmethod.InputMethodAndLanguageSettings; +import com.android.settings.location.LocationSettings; +import com.android.settings.DevelopmentSettings; + +public class BootReceiver extends BroadcastReceiver { + + private static final String TAG = "BootReceiver"; + private static final String ONE_TIME_TUNABLE_RESTORE = "hardware_tunable_restored"; + + @Override + public void onReceive(Context ctx, Intent intent) { + if (!hasRestoredTunable(ctx)) { + /* Restore the hardware tunable values */ + ButtonSettings.restoreKeyDisabler(ctx); + VibratorIntensity.restore(ctx); + InputMethodAndLanguageSettings.restore(ctx); + DisplaySettings.restore(ctx); + setRestoredTunable(ctx); + } + + LocationSettings.restore(ctx); + + // Extract the contributors database + ContributorsCloudFragment.extractContributorsCloudDatabase(ctx); + + DevelopmentSettings.initializeUpdateRecoveryOption(ctx); + } + + private boolean hasRestoredTunable(Context context) { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); + return preferences.getBoolean(ONE_TIME_TUNABLE_RESTORE, false); + } + + private void setRestoredTunable(Context context) { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); + preferences.edit().putBoolean(ONE_TIME_TUNABLE_RESTORE, true).apply(); + } +} diff --git a/src/com/android/settings/cyanogenmod/ButtonBacklightBrightness.java b/src/com/android/settings/cyanogenmod/ButtonBacklightBrightness.java new file mode 100644 index 0000000..7c63f34 --- /dev/null +++ b/src/com/android/settings/cyanogenmod/ButtonBacklightBrightness.java @@ -0,0 +1,460 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.cyanogenmod; + +import android.app.AlertDialog; +import android.content.ContentResolver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.preference.DialogPreference; +import android.preference.PreferenceManager; +import android.provider.Settings; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager.LayoutParams; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.SeekBar; +import android.widget.TextView; + +import com.android.settings.R; +import com.android.settings.ButtonSettings; +import cyanogenmod.providers.CMSettings; + +public class ButtonBacklightBrightness extends DialogPreference implements + SeekBar.OnSeekBarChangeListener { + private static final int DEFAULT_BUTTON_TIMEOUT = 5; + + public static final String KEY_BUTTON_BACKLIGHT = "pre_navbar_button_backlight"; + + private Window mWindow; + + private BrightnessControl mButtonBrightness; + private BrightnessControl mKeyboardBrightness; + private BrightnessControl mActiveControl; + + private ViewGroup mTimeoutContainer; + private SeekBar mTimeoutBar; + private TextView mTimeoutValue; + + private ContentResolver mResolver; + + public ButtonBacklightBrightness(Context context, AttributeSet attrs) { + super(context, attrs); + + mResolver = context.getContentResolver(); + + setDialogLayoutResource(R.layout.button_backlight); + + if (isKeyboardSupported()) { + mKeyboardBrightness = new BrightnessControl( + CMSettings.Secure.KEYBOARD_BRIGHTNESS, false); + mActiveControl = mKeyboardBrightness; + } + if (isButtonSupported()) { + boolean isSingleValue = !context.getResources().getBoolean( + com.android.internal.R.bool.config_deviceHasVariableButtonBrightness); + + int defaultBrightness = context.getResources().getInteger( + com.android.internal.R.integer.config_buttonBrightnessSettingDefault); + + mButtonBrightness = new BrightnessControl( + CMSettings.Secure.BUTTON_BRIGHTNESS, isSingleValue, defaultBrightness); + mActiveControl = mButtonBrightness; + } + + updateSummary(); + } + + @Override + protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { + builder.setNeutralButton(R.string.settings_reset_button, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + } + }); + } + + @Override + protected void onBindDialogView(View view) { + super.onBindDialogView(view); + + mTimeoutContainer = (ViewGroup) view.findViewById(R.id.timeout_container); + mTimeoutBar = (SeekBar) view.findViewById(R.id.timeout_seekbar); + mTimeoutValue = (TextView) view.findViewById(R.id.timeout_value); + mTimeoutBar.setMax(30); + mTimeoutBar.setOnSeekBarChangeListener(this); + mTimeoutBar.setProgress(getTimeout()); + handleTimeoutUpdate(mTimeoutBar.getProgress()); + + ViewGroup buttonContainer = (ViewGroup) view.findViewById(R.id.button_container); + if (mButtonBrightness != null) { + mButtonBrightness.init(buttonContainer); + } else { + buttonContainer.setVisibility(View.GONE); + mTimeoutContainer.setVisibility(View.GONE); + } + + ViewGroup keyboardContainer = (ViewGroup) view.findViewById(R.id.keyboard_container); + if (mKeyboardBrightness != null) { + mKeyboardBrightness.init(keyboardContainer); + } else { + keyboardContainer.setVisibility(View.GONE); + } + + if (mButtonBrightness == null || mKeyboardBrightness == null) { + view.findViewById(R.id.button_keyboard_divider).setVisibility(View.GONE); + } + } + + @Override + protected void showDialog(Bundle state) { + super.showDialog(state); + + // Can't use onPrepareDialogBuilder for this as we want the dialog + // to be kept open on click + AlertDialog d = (AlertDialog) getDialog(); + Button defaultsButton = d.getButton(DialogInterface.BUTTON_NEUTRAL); + defaultsButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mTimeoutBar.setProgress(DEFAULT_BUTTON_TIMEOUT); + if (mButtonBrightness != null) { + mButtonBrightness.reset(); + } + if (mKeyboardBrightness != null) { + mKeyboardBrightness.reset(); + } + } + }); + + if (getDialog() != null) { + mWindow = getDialog().getWindow(); + } + updateBrightnessPreview(); + } + + @Override + protected void onDialogClosed(boolean positiveResult) { + super.onDialogClosed(positiveResult); + + if (!positiveResult) { + return; + } + + if (mButtonBrightness != null) { + PreferenceManager.getDefaultSharedPreferences(getContext()) + .edit() + .putInt(KEY_BUTTON_BACKLIGHT, mButtonBrightness.getBrightness(false)) + .apply(); + } + + applyTimeout(mTimeoutBar.getProgress()); + if (mButtonBrightness != null) { + mButtonBrightness.applyBrightness(); + } + if (mKeyboardBrightness != null) { + mKeyboardBrightness.applyBrightness(); + } + + updateSummary(); + } + + @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.timeout = mTimeoutBar.getProgress(); + if (mButtonBrightness != null) { + myState.button = mButtonBrightness.getBrightness(false); + } + if (mKeyboardBrightness != null) { + myState.keyboard = mKeyboardBrightness.getBrightness(false); + } + + 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()); + + mTimeoutBar.setProgress(myState.timeout); + if (mButtonBrightness != null) { + mButtonBrightness.setBrightness(myState.button); + } + if (mKeyboardBrightness != null) { + mKeyboardBrightness.setBrightness(myState.keyboard); + } + } + + public boolean isButtonSupported() { + final Resources res = getContext().getResources(); + final int deviceKeys = res.getInteger( + com.android.internal.R.integer.config_deviceHardwareKeys); + // All hardware keys besides volume and camera can possibly have a backlight + boolean hasBacklightKey = (deviceKeys & ButtonSettings.KEY_MASK_HOME) != 0 + || (deviceKeys & ButtonSettings.KEY_MASK_BACK) != 0 + || (deviceKeys & ButtonSettings.KEY_MASK_MENU) != 0 + || (deviceKeys & ButtonSettings.KEY_MASK_ASSIST) != 0 + || (deviceKeys & ButtonSettings.KEY_MASK_APP_SWITCH) != 0; + boolean hasBacklight = res.getInteger( + com.android.internal.R.integer.config_buttonBrightnessSettingDefault) > 0; + + return hasBacklightKey && hasBacklight; + } + + public boolean isKeyboardSupported() { + return getContext().getResources().getInteger( + com.android.internal.R.integer.config_keyboardBrightnessSettingDefault) > 0; + } + + public void updateSummary() { + if (mButtonBrightness != null) { + int buttonBrightness = mButtonBrightness.getBrightness(true); + int timeout = getTimeout(); + + if (buttonBrightness == 0) { + setSummary(R.string.backlight_summary_disabled); + } else if (timeout == 0) { + setSummary(R.string.backlight_timeout_unlimited); + } else { + setSummary(getContext().getString(R.string.backlight_summary_enabled_with_timeout, + getTimeoutString(timeout))); + } + } else if (mKeyboardBrightness != null && mKeyboardBrightness.getBrightness(true) != 0) { + setSummary(R.string.backlight_summary_enabled); + } else { + setSummary(R.string.backlight_summary_disabled); + } + } + + private String getTimeoutString(int timeout) { + return getContext().getResources().getQuantityString( + R.plurals.backlight_timeout_time, timeout, timeout); + } + + private int getTimeout() { + return CMSettings.Secure.getInt(mResolver, + CMSettings.Secure.BUTTON_BACKLIGHT_TIMEOUT, DEFAULT_BUTTON_TIMEOUT * 1000) / 1000; + } + + private void applyTimeout(int timeout) { + CMSettings.Secure.putInt(mResolver, + CMSettings.Secure.BUTTON_BACKLIGHT_TIMEOUT, timeout * 1000); + } + + private void updateBrightnessPreview() { + if (mWindow != null) { + LayoutParams params = mWindow.getAttributes(); + if (mActiveControl != null) { + params.buttonBrightness = (float) mActiveControl.getBrightness(false) / 255.0f; + } else { + params.buttonBrightness = -1; + } + mWindow.setAttributes(params); + } + } + + private void updateTimeoutEnabledState() { + int buttonBrightness = mButtonBrightness != null + ? mButtonBrightness.getBrightness(false) : 0; + int count = mTimeoutContainer.getChildCount(); + for (int i = 0; i < count; i++) { + mTimeoutContainer.getChildAt(i).setEnabled(buttonBrightness != 0); + } + } + + private void handleTimeoutUpdate(int timeout) { + if (timeout == 0) { + mTimeoutValue.setText(R.string.backlight_timeout_unlimited); + } else { + mTimeoutValue.setText(getTimeoutString(timeout)); + } + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + handleTimeoutUpdate(progress); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + // Do nothing here + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + // Do nothing here + } + + private static class SavedState extends BaseSavedState { + int timeout; + int button; + int keyboard; + + public SavedState(Parcelable superState) { + super(superState); + } + + public SavedState(Parcel source) { + super(source); + timeout = source.readInt(); + button = source.readInt(); + keyboard = source.readInt(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(timeout); + dest.writeInt(button); + dest.writeInt(keyboard); + } + + public static final Parcelable.Creator<SavedState> CREATOR = + new Parcelable.Creator<SavedState>() { + + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } + + private class BrightnessControl implements + SeekBar.OnSeekBarChangeListener, CheckBox.OnCheckedChangeListener { + private String mSetting; + private boolean mIsSingleValue; + private int mDefaultBrightness; + private CheckBox mCheckBox; + private SeekBar mSeekBar; + private TextView mValue; + + public BrightnessControl(String setting, boolean singleValue, int defaultBrightness) { + mSetting = setting; + mIsSingleValue = singleValue; + mDefaultBrightness = defaultBrightness; + } + + public BrightnessControl(String setting, boolean singleValue) { + this(setting, singleValue, 255); + } + + public void init(ViewGroup container) { + int brightness = getBrightness(true); + + if (mIsSingleValue) { + container.findViewById(R.id.seekbar_container).setVisibility(View.GONE); + mCheckBox = (CheckBox) container.findViewById(R.id.backlight_switch); + mCheckBox.setChecked(brightness != 0); + mCheckBox.setOnCheckedChangeListener(this); + } else { + container.findViewById(R.id.checkbox_container).setVisibility(View.GONE); + mSeekBar = (SeekBar) container.findViewById(R.id.seekbar); + mValue = (TextView) container.findViewById(R.id.value); + + mSeekBar.setMax(255); + mSeekBar.setProgress(brightness); + mSeekBar.setOnSeekBarChangeListener(this); + } + + handleBrightnessUpdate(brightness); + } + + public int getBrightness(boolean persisted) { + if (mCheckBox != null && !persisted) { + return mCheckBox.isChecked() ? mDefaultBrightness : 0; + } else if (mSeekBar != null && !persisted) { + return mSeekBar.getProgress(); + } + return CMSettings.Secure.getInt(mResolver, mSetting, mDefaultBrightness); + } + + public void applyBrightness() { + CMSettings.Secure.putInt(mResolver, mSetting, getBrightness(false)); + } + + /* Behaviors when it's a seekbar */ + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + handleBrightnessUpdate(progress); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + mActiveControl = this; + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + // Do nothing here + } + + /* Behaviors when it's a plain checkbox */ + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + mActiveControl = this; + updateBrightnessPreview(); + updateTimeoutEnabledState(); + } + + public void setBrightness(int value) { + if (mIsSingleValue) { + mCheckBox.setChecked(value != 0); + } else { + mSeekBar.setProgress(value); + } + } + + public void reset() { + setBrightness(mDefaultBrightness); + } + + private void handleBrightnessUpdate(int brightness) { + updateBrightnessPreview(); + if (mValue != null) { + mValue.setText(String.format("%d%%", (int)((brightness * 100) / 255))); + } + updateTimeoutEnabledState(); + } + } +} diff --git a/src/com/android/settings/cyanogenmod/CMBaseSystemSettingSwitchBar.java b/src/com/android/settings/cyanogenmod/CMBaseSystemSettingSwitchBar.java new file mode 100644 index 0000000..1f46738 --- /dev/null +++ b/src/com/android/settings/cyanogenmod/CMBaseSystemSettingSwitchBar.java @@ -0,0 +1,156 @@ +/* +* Copyright (C) 2015 The CyanogenMod Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +package com.android.settings.cyanogenmod; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.widget.Switch; +import com.android.settings.widget.SwitchBar; + +import cyanogenmod.providers.CMSettings; + +public class CMBaseSystemSettingSwitchBar implements SwitchBar.OnSwitchChangeListener { + private Context mContext; + private SwitchBar mSwitchBar; + private SettingsObserver mSettingsObserver; + private boolean mListeningToOnSwitchChange = false; + + private boolean mStateMachineEvent; + + private final String mSettingKey; + private final int mDefaultState; + + private final SwitchBarChangeCallback mCallback; + public interface SwitchBarChangeCallback { + public void onEnablerChanged(boolean isEnabled); + } + + public CMBaseSystemSettingSwitchBar(Context context, SwitchBar switchBar, String key, + boolean defaultState, SwitchBarChangeCallback callback) { + mContext = context; + mSwitchBar = switchBar; + mSettingKey = key; + mDefaultState = defaultState ? 1 : 0; + mCallback = callback; + mSettingsObserver = new SettingsObserver(new Handler()); + setupSwitchBar(); + } + + public void setupSwitchBar() { + setSwitchState(); + if (!mListeningToOnSwitchChange) { + mSwitchBar.addOnSwitchChangeListener(this); + mListeningToOnSwitchChange = true; + } + mSwitchBar.show(); + } + + public void teardownSwitchBar() { + if (mListeningToOnSwitchChange) { + mSwitchBar.removeOnSwitchChangeListener(this); + mListeningToOnSwitchChange = false; + } + mSwitchBar.hide(); + } + + public void resume(Context context) { + mContext = context; + if (!mListeningToOnSwitchChange) { + mSwitchBar.addOnSwitchChangeListener(this); + mSettingsObserver.observe(); + + mListeningToOnSwitchChange = true; + } + } + + public void pause() { + if (mListeningToOnSwitchChange) { + mSwitchBar.removeOnSwitchChangeListener(this); + mSettingsObserver.unobserve(); + + mListeningToOnSwitchChange = false; + } + } + + private void setSwitchBarChecked(boolean checked) { + mStateMachineEvent = true; + mSwitchBar.setChecked(checked); + mStateMachineEvent = false; + if (mCallback != null) { + mCallback.onEnablerChanged(checked); + } + } + + private void setSwitchState() { + boolean enabled = CMSettings.System.getInt(mContext.getContentResolver(), + mSettingKey, mDefaultState) == 1; + mStateMachineEvent = true; + setSwitchBarChecked(enabled); + mStateMachineEvent = false; + } + + @Override + public void onSwitchChanged(Switch switchView, boolean isChecked) { + //Do nothing if called as a result of a state machine event + if (mStateMachineEvent) { + return; + } + + // Handle a switch change + CMSettings.System.putInt(mContext.getContentResolver(), + mSettingKey, isChecked ? 1 : 0); + + if (mCallback != null) { + mCallback.onEnablerChanged(isChecked); + } + } + + class SettingsObserver extends ContentObserver { + SettingsObserver(Handler handler) { + super(handler); + } + + void observe() { + ContentResolver resolver = mContext.getContentResolver(); + resolver.registerContentObserver(CMSettings.System.getUriFor( + mSettingKey), false, this); + update(); + } + + void unobserve() { + ContentResolver resolver = mContext.getContentResolver(); + resolver.unregisterContentObserver(this); + } + + @Override + public void onChange(boolean selfChange) { + update(); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + update(); + } + + public void update() { + setSwitchState(); + } + } +} diff --git a/src/com/android/settings/cyanogenmod/CMGlobalSettingSwitchPreference.java b/src/com/android/settings/cyanogenmod/CMGlobalSettingSwitchPreference.java new file mode 100644 index 0000000..8609659 --- /dev/null +++ b/src/com/android/settings/cyanogenmod/CMGlobalSettingSwitchPreference.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2016 The CyanogenMod project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.cyanogenmod; + +import android.content.Context; +import android.preference.SwitchPreference; +import android.util.AttributeSet; + +import cyanogenmod.providers.CMSettings; + +public class CMGlobalSettingSwitchPreference extends SwitchPreference { + public CMGlobalSettingSwitchPreference(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public CMGlobalSettingSwitchPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public CMGlobalSettingSwitchPreference(Context context) { + super(context, null); + } + + @Override + protected boolean persistBoolean(boolean value) { + if (shouldPersist()) { + if (value == getPersistedBoolean(!value)) { + // It's already there, so the same as persisting + return true; + } + CMSettings.Global.putInt(getContext().getContentResolver(), getKey(), value ? 1 : 0); + return true; + } + return false; + } + + @Override + protected boolean getPersistedBoolean(boolean defaultReturnValue) { + if (!shouldPersist()) { + return defaultReturnValue; + } + return CMSettings.Global.getInt(getContext().getContentResolver(), + getKey(), defaultReturnValue ? 1 : 0) != 0; + } + + @Override + protected boolean isPersisted() { + // Using getString instead of getInt so we can simply check for null + // instead of catching an exception. (All values are stored as strings.) + return CMSettings.Global.getString(getContext().getContentResolver(), getKey()) != null; + } +} diff --git a/src/com/android/settings/cyanogenmod/CMSecureSettingSwitchPreference.java b/src/com/android/settings/cyanogenmod/CMSecureSettingSwitchPreference.java new file mode 100644 index 0000000..878ee95 --- /dev/null +++ b/src/com/android/settings/cyanogenmod/CMSecureSettingSwitchPreference.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2015 The CyanogenMod project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.cyanogenmod; + +import android.content.Context; +import android.preference.SwitchPreference; +import android.util.AttributeSet; + +import cyanogenmod.providers.CMSettings; + +public class CMSecureSettingSwitchPreference extends SwitchPreference { + public CMSecureSettingSwitchPreference(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public CMSecureSettingSwitchPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public CMSecureSettingSwitchPreference(Context context) { + super(context, null); + } + + @Override + protected boolean persistBoolean(boolean value) { + if (shouldPersist()) { + if (value == getPersistedBoolean(!value)) { + // It's already there, so the same as persisting + return true; + } + CMSettings.Secure.putInt(getContext().getContentResolver(), getKey(), value ? 1 : 0); + return true; + } + return false; + } + + @Override + protected boolean getPersistedBoolean(boolean defaultReturnValue) { + if (!shouldPersist()) { + return defaultReturnValue; + } + return CMSettings.Secure.getInt(getContext().getContentResolver(), + getKey(), defaultReturnValue ? 1 : 0) != 0; + } + + @Override + protected boolean isPersisted() { + // Using getString instead of getInt so we can simply check for null + // instead of catching an exception. (All values are stored as strings.) + return CMSettings.Secure.getString(getContext().getContentResolver(), getKey()) != null; + } +} diff --git a/src/com/android/settings/cyanogenmod/CMSystemSettingSwitchPreference.java b/src/com/android/settings/cyanogenmod/CMSystemSettingSwitchPreference.java new file mode 100644 index 0000000..d8199e4 --- /dev/null +++ b/src/com/android/settings/cyanogenmod/CMSystemSettingSwitchPreference.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2015 The CyanogenMod project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.cyanogenmod; + +import android.content.Context; +import android.preference.SwitchPreference; +import android.util.AttributeSet; + +import cyanogenmod.providers.CMSettings; + +public class CMSystemSettingSwitchPreference extends SwitchPreference { + public CMSystemSettingSwitchPreference(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public CMSystemSettingSwitchPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public CMSystemSettingSwitchPreference(Context context) { + super(context, null); + } + + @Override + protected boolean persistBoolean(boolean value) { + if (shouldPersist()) { + if (value == getPersistedBoolean(!value)) { + // It's already there, so the same as persisting + return true; + } + CMSettings.System.putInt(getContext().getContentResolver(), getKey(), value ? 1 : 0); + return true; + } + return false; + } + + @Override + protected boolean getPersistedBoolean(boolean defaultReturnValue) { + if (!shouldPersist()) { + return defaultReturnValue; + } + return CMSettings.System.getInt(getContext().getContentResolver(), + getKey(), defaultReturnValue ? 1 : 0) != 0; + } + + @Override + protected boolean isPersisted() { + // Using getString instead of getInt so we can simply check for null + // instead of catching an exception. (All values are stored as strings.) + return CMSettings.System.getString(getContext().getContentResolver(), getKey()) != null; + } +} diff --git a/src/com/android/settings/cyanogenmod/DeviceUtils.java b/src/com/android/settings/cyanogenmod/DeviceUtils.java new file mode 100644 index 0000000..1196976 --- /dev/null +++ b/src/com/android/settings/cyanogenmod/DeviceUtils.java @@ -0,0 +1,26 @@ +package com.android.settings.cyanogenmod; + +import android.bluetooth.BluetoothAdapter; +import android.content.ContentResolver; +import android.content.Context; +import android.net.ConnectivityManager; +import android.nfc.NfcAdapter; +import android.provider.Settings; + +/** + * Created by roman on 11/24/14. + */ +public class DeviceUtils { + public static boolean deviceSupportsMobileData(Context ctx) { + ConnectivityManager cm = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE); + return cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE); + } + + public static boolean deviceSupportsBluetooth() { + return (BluetoothAdapter.getDefaultAdapter() != null); + } + + public static boolean deviceSupportsNfc(Context ctx) { + return NfcAdapter.getDefaultAdapter(ctx) != null; + } +} diff --git a/src/com/android/settings/cyanogenmod/DisplayRotation.java b/src/com/android/settings/cyanogenmod/DisplayRotation.java new file mode 100644 index 0000000..0ee5812 --- /dev/null +++ b/src/com/android/settings/cyanogenmod/DisplayRotation.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.cyanogenmod; + +import android.database.ContentObserver; +import android.os.Bundle; +import android.os.Handler; +import android.preference.CheckBoxPreference; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceChangeListener; +import android.preference.PreferenceScreen; +import android.preference.SwitchPreference; +import android.provider.Settings; + +import com.android.internal.view.RotationPolicy; +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; + +import org.cyanogenmod.internal.logging.CMMetricsLogger; + +public class DisplayRotation extends SettingsPreferenceFragment { + private static final String TAG = "DisplayRotation"; + + public static final String KEY_ACCELEROMETER = "accelerometer"; + private static final String KEY_LOCKSCREEN_ROTATION = "lockscreen_rotation"; + private static final String ROTATION_0_PREF = "display_rotation_0"; + private static final String ROTATION_90_PREF = "display_rotation_90"; + private static final String ROTATION_180_PREF = "display_rotation_180"; + private static final String ROTATION_270_PREF = "display_rotation_270"; + + private SwitchPreference mAccelerometer; + private CheckBoxPreference mRotation0Pref; + private CheckBoxPreference mRotation90Pref; + private CheckBoxPreference mRotation180Pref; + private CheckBoxPreference mRotation270Pref; + + public static final int ROTATION_0_MODE = 1; + public static final int ROTATION_90_MODE = 2; + public static final int ROTATION_180_MODE = 4; + public static final int ROTATION_270_MODE = 8; + + private ContentObserver mAccelerometerRotationObserver = new ContentObserver(new Handler()) { + @Override + public void onChange(boolean selfChange) { + updateAccelerometerRotationSwitch(); + } + }; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + addPreferencesFromResource(R.xml.display_rotation); + + PreferenceScreen prefSet = getPreferenceScreen(); + + mAccelerometer = (SwitchPreference) findPreference(KEY_ACCELEROMETER); + mAccelerometer.setPersistent(false); + + mRotation0Pref = (CheckBoxPreference) prefSet.findPreference(ROTATION_0_PREF); + mRotation90Pref = (CheckBoxPreference) prefSet.findPreference(ROTATION_90_PREF); + mRotation180Pref = (CheckBoxPreference) prefSet.findPreference(ROTATION_180_PREF); + mRotation270Pref = (CheckBoxPreference) prefSet.findPreference(ROTATION_270_PREF); + + int mode = Settings.System.getInt(getContentResolver(), + Settings.System.ACCELEROMETER_ROTATION_ANGLES, + ROTATION_0_MODE | ROTATION_90_MODE | ROTATION_270_MODE); + + mRotation0Pref.setChecked((mode & ROTATION_0_MODE) != 0); + mRotation90Pref.setChecked((mode & ROTATION_90_MODE) != 0); + mRotation180Pref.setChecked((mode & ROTATION_180_MODE) != 0); + mRotation270Pref.setChecked((mode & ROTATION_270_MODE) != 0); + + boolean hasRotationLock = false; +// getResources().getBoolean( +// com.android.internal.R.bool.config_hasRotationLockSwitch); + + if (hasRotationLock) { + // Disable accelerometer switch, but leave others enabled + mAccelerometer.setEnabled(false); + mRotation0Pref.setDependency(null); + mRotation90Pref.setDependency(null); + mRotation180Pref.setDependency(null); + mRotation270Pref.setDependency(null); + } + + final SwitchPreference lockScreenRotation = + (SwitchPreference) findPreference(KEY_LOCKSCREEN_ROTATION); + boolean canRotateLockscreen = getResources().getBoolean( + com.android.internal.R.bool.config_enableLockScreenRotation); + + if (lockScreenRotation != null && !canRotateLockscreen) { + getPreferenceScreen().removePreference(lockScreenRotation); + } + } + + @Override + protected int getMetricsCategory() { + return CMMetricsLogger.DISPLAY_ROTATION; + } + + @Override + public void onResume() { + super.onResume(); + + updateState(); + getContentResolver().registerContentObserver( + Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), true, + mAccelerometerRotationObserver); + } + + @Override + public void onPause() { + super.onPause(); + + getContentResolver().unregisterContentObserver(mAccelerometerRotationObserver); + } + + private void updateState() { + updateAccelerometerRotationSwitch(); + } + + private void updateAccelerometerRotationSwitch() { + mAccelerometer.setChecked(!RotationPolicy.isRotationLocked(getActivity())); + } + + private int getRotationBitmask() { + int mode = 0; + if (mRotation0Pref.isChecked()) { + mode |= ROTATION_0_MODE; + } + if (mRotation90Pref.isChecked()) { + mode |= ROTATION_90_MODE; + } + if (mRotation180Pref.isChecked()) { + mode |= ROTATION_180_MODE; + } + if (mRotation270Pref.isChecked()) { + mode |= ROTATION_270_MODE; + } + return mode; + } + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, + Preference preference) { + if (preference == mAccelerometer) { + RotationPolicy.setRotationLockForAccessibility(getActivity(), + !mAccelerometer.isChecked()); + } else if (preference == mRotation0Pref || + preference == mRotation90Pref || + preference == mRotation180Pref || + preference == mRotation270Pref) { + int mode = getRotationBitmask(); + if (mode == 0) { + mode |= ROTATION_0_MODE; + mRotation0Pref.setChecked(true); + } + Settings.System.putInt(getActivity().getContentResolver(), + Settings.System.ACCELEROMETER_ROTATION_ANGLES, mode); + return true; + } + + return super.onPreferenceTreeClick(preferenceScreen, preference); + } +} diff --git a/src/com/android/settings/cyanogenmod/GlobalSettingSwitchPreference.java b/src/com/android/settings/cyanogenmod/GlobalSettingSwitchPreference.java new file mode 100644 index 0000000..88e0a0e --- /dev/null +++ b/src/com/android/settings/cyanogenmod/GlobalSettingSwitchPreference.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2015 The CyanogenMod project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.cyanogenmod; + +import android.content.Context; +import android.preference.SwitchPreference; +import android.provider.Settings; +import android.util.AttributeSet; + +public class GlobalSettingSwitchPreference extends SwitchPreference { + public GlobalSettingSwitchPreference(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public GlobalSettingSwitchPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public GlobalSettingSwitchPreference(Context context) { + super(context, null); + } + + @Override + protected boolean persistBoolean(boolean value) { + if (shouldPersist()) { + if (value == getPersistedBoolean(!value)) { + // It's already there, so the same as persisting + return true; + } + Settings.Global.putInt(getContext().getContentResolver(), getKey(), value ? 1 : 0); + return true; + } + return false; + } + + @Override + protected boolean getPersistedBoolean(boolean defaultReturnValue) { + if (!shouldPersist()) { + return defaultReturnValue; + } + return Settings.Global.getInt(getContext().getContentResolver(), + getKey(), defaultReturnValue ? 1 : 0) != 0; + } + + @Override + protected boolean isPersisted() { + // Using getString instead of getInt so we can simply check for null + // instead of catching an exception. (All values are stored as strings.) + return Settings.Global.getString(getContext().getContentResolver(), getKey()) != null; + } +} diff --git a/src/com/android/settings/cyanogenmod/LiveLockScreenSettings.java b/src/com/android/settings/cyanogenmod/LiveLockScreenSettings.java new file mode 100644 index 0000000..17be5cd --- /dev/null +++ b/src/com/android/settings/cyanogenmod/LiveLockScreenSettings.java @@ -0,0 +1,497 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.cyanogenmod; + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +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.Bundle; +import android.util.AttributeSet; +import android.util.Log; +import android.util.Xml; +import android.view.LayoutInflater; +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.ImageView; +import android.widget.ListView; +import android.widget.RadioButton; +import android.widget.Switch; +import android.widget.TextView; +import com.android.internal.logging.MetricsLogger; +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.Utils; +import com.android.settings.widget.SwitchBar; +import cyanogenmod.app.LiveLockScreenManager; +import cyanogenmod.externalviews.KeyguardExternalViewProviderService; +import cyanogenmod.providers.CMSettings; +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; + +import static cyanogenmod.providers.CMSettings.Secure.LIVE_LOCK_SCREEN_ENABLED; + +public class LiveLockScreenSettings extends SettingsPreferenceFragment implements + SwitchBar.OnSwitchChangeListener { + private static final String TAG = LiveLockScreenSettings.class.getSimpleName(); + static final boolean DEBUG = false; + private static final String PACKAGE_SCHEME = "package"; + + private final PackageReceiver mPackageReceiver = new PackageReceiver(); + + private Context mContext; + private LiveLockScreenBackend mBackend; + private LiveLockScreenInfoAdapter mAdapter; + private SwitchBar mSwitchBar; + private boolean mRefreshing; + + @Override + public void onAttach(Activity activity) { + logd("onAttach(%s)", activity.getClass().getSimpleName()); + super.onAttach(activity); + mContext = activity; + } + + @Override + protected int getMetricsCategory() { + return MetricsLogger.LOCKSCREEN; + } + + @Override + public void onCreate(Bundle icicle) { + logd("onCreate(%s)", icicle); + super.onCreate(icicle); + + mBackend = new LiveLockScreenBackend(getActivity()); + } + + @Override + public void onSwitchChanged(Switch switchView, boolean isChecked) { + if (!mRefreshing) { + mBackend.setEnabled(isChecked); + refreshFromBackend(); + } + } + + @Override + public void onStart() { + logd("onStart()"); + super.onStart(); + } + + @Override + public void onDestroyView() { + logd("onDestroyView()"); + super.onDestroyView(); + + mSwitchBar.removeOnSwitchChangeListener(this); + mSwitchBar.hide(); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + logd("onActivityCreated(%s)", savedInstanceState); + super.onActivityCreated(savedInstanceState); + + ListView listView = getListView(); + listView.setItemsCanFocus(true); + + TextView emptyView = (TextView) getView().findViewById(android.R.id.empty); + emptyView.setText(R.string.live_lock_screen_settings_disabled_prompt); + listView.setEmptyView(emptyView); + + mAdapter = new LiveLockScreenInfoAdapter(mContext); + listView.setAdapter(mAdapter); + + final SettingsActivity sa = (SettingsActivity) getActivity(); + mSwitchBar = sa.getSwitchBar(); + mSwitchBar.addOnSwitchChangeListener(this); + mSwitchBar.show(); + } + + @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); + } + + private void refreshFromBackend() { + logd("refreshFromBackend()"); + mRefreshing = true; + boolean liveLockScreenEnabled = mBackend.isEnabled(); + if (mSwitchBar.isChecked() != liveLockScreenEnabled) { + mSwitchBar.setChecked(liveLockScreenEnabled); + } + + mAdapter.clear(); + if (liveLockScreenEnabled) { + List<LiveLockScreenBackend.LiveLockScreenInfo> liveLockScreenInfos = + mBackend.getLiveLockScreenInfos(); + mAdapter.addAll(liveLockScreenInfos); + } + 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 LiveLockScreenInfoAdapter extends ArrayAdapter<LiveLockScreenBackend.LiveLockScreenInfo> { + private final LayoutInflater mInflater; + + public LiveLockScreenInfoAdapter(Context context) { + super(context, 0); + mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + LiveLockScreenBackend.LiveLockScreenInfo liveLockScreenInfo = getItem(position); + logd("getView(%s)", liveLockScreenInfo.caption); + final View row = convertView != null ? convertView : + createLiveLockScreenInfoRow(parent); + row.setTag(liveLockScreenInfo); + + // bind icon + ((ImageView) row.findViewById(android.R.id.icon)) + .setImageDrawable(liveLockScreenInfo.icon); + + // bind caption + ((TextView) row.findViewById(android.R.id.title)).setText(liveLockScreenInfo.caption); + + // bind radio button + RadioButton radioButton = (RadioButton) row.findViewById(R.id.radio); + radioButton.setChecked(liveLockScreenInfo.isActive); + radioButton.setOnTouchListener(new OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + row.onTouchEvent(event); + return false; + }}); + + // bind settings button + divider + boolean showSettings = liveLockScreenInfo.settingsComponentName != null; + View settingsDivider = row.findViewById(R.id.divider); + settingsDivider.setVisibility(showSettings ? View.VISIBLE : View.INVISIBLE); + + ImageView settingsButton = (ImageView) row.findViewById(R.id.settings); + settingsButton.setVisibility(showSettings ? View.VISIBLE : View.INVISIBLE); + settingsButton.setAlpha(liveLockScreenInfo.isActive ? 1f : Utils.DISABLED_ALPHA); + settingsButton.setEnabled(liveLockScreenInfo.isActive); + settingsButton.setFocusable(liveLockScreenInfo.isActive); + settingsButton.setOnClickListener(new OnClickListener(){ + @Override + public void onClick(View v) { + mBackend.launchSettings( + (LiveLockScreenBackend.LiveLockScreenInfo) row.getTag()); + }}); + + return row; + } + + private View createLiveLockScreenInfoRow(ViewGroup parent) { + final View row = mInflater.inflate(R.layout.live_lock_screen_info_row, parent, false); + row.setOnClickListener(new OnClickListener(){ + @Override + public void onClick(View v) { + v.setPressed(true); + activate((LiveLockScreenBackend.LiveLockScreenInfo) row.getTag()); + }}); + return row; + } + + private LiveLockScreenBackend.LiveLockScreenInfo getCurrentSelection() { + for (int i = 0; i < getCount(); i++) { + LiveLockScreenBackend.LiveLockScreenInfo liveLockScreenInfo = getItem(i); + if (liveLockScreenInfo.isActive) { + return liveLockScreenInfo; + } + } + return null; + } + private void activate(LiveLockScreenBackend.LiveLockScreenInfo liveLockScreenInfo) { + if (liveLockScreenInfo.equals(getCurrentSelection())) { + return; + } + for (int i = 0; i < getCount(); i++) { + getItem(i).isActive = false; + } + liveLockScreenInfo.isActive = true; + mBackend.setActiveLiveLockScreen(liveLockScreenInfo.componentName); + if (liveLockScreenInfo.settingsComponentName != null) { + mBackend.launchSettings(liveLockScreenInfo); + } + notifyDataSetChanged(); + } + } + + private class PackageReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + logd("PackageReceiver.onReceive"); + refreshFromBackend(); + } + } + + public static class LiveLockScreenBackend { + private static final String TAG = LiveLockScreenSettings.class.getSimpleName() + ".Backend"; + + public static class LiveLockScreenInfo { + CharSequence caption; + Drawable icon; + boolean isActive; + public ComponentName componentName; + public ComponentName settingsComponentName; + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(LiveLockScreenInfo.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 LiveLockScreenInfoComparator mComparator; + private LiveLockScreenManager mLLSM; + + public LiveLockScreenBackend(Context context) { + mContext = context; + mComparator = new LiveLockScreenInfoComparator(null); + mLLSM = LiveLockScreenManager.getInstance(context); + } + + public List<LiveLockScreenInfo> getLiveLockScreenInfos() { + logd("getLiveLockScreenInfos()"); + ComponentName activeLiveLockScreen = getActiveLiveLockScreen(); + PackageManager pm = mContext.getPackageManager(); + Intent liveLockScreenIntent = + new Intent(KeyguardExternalViewProviderService.SERVICE_INTERFACE); + List<ResolveInfo> resolveInfos = pm.queryIntentServices(liveLockScreenIntent, + PackageManager.GET_META_DATA); + List<LiveLockScreenInfo> liveLockScreenInfos = new ArrayList<>(resolveInfos.size()); + for (ResolveInfo resolveInfo : resolveInfos) { + if (resolveInfo.serviceInfo == null) { + continue; + } + LiveLockScreenInfo liveLockScreenInfo = new LiveLockScreenInfo(); + liveLockScreenInfo.caption = resolveInfo.loadLabel(pm); + liveLockScreenInfo.icon = resolveInfo.loadIcon(pm); + liveLockScreenInfo.componentName = getLiveLockScreenComponentName(resolveInfo); + liveLockScreenInfo.isActive = liveLockScreenInfo.componentName.equals( + activeLiveLockScreen); + liveLockScreenInfo.settingsComponentName = + getSettingsComponentName(pm, resolveInfo); + liveLockScreenInfos.add(liveLockScreenInfo); + } + Collections.sort(liveLockScreenInfos, mComparator); + return liveLockScreenInfos; + } + + public CharSequence getActiveLiveLockScreenName() { + ComponentName cn = getActiveLiveLockScreen(); + 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(LIVE_LOCK_SCREEN_ENABLED, false); + } + + public void setEnabled(boolean value) { + logd("setEnabled(%s)", value); + setBoolean(LIVE_LOCK_SCREEN_ENABLED, value); + } + + private boolean getBoolean(String key, boolean def) { + return CMSettings.Secure.getInt(mContext.getContentResolver(), key, def ? 1 : 0) == 1; + } + + private void setBoolean(String key, boolean value) { + mLLSM.setLiveLockScreenEnabled(value); + } + + public void setActiveLiveLockScreen(ComponentName liveLockScreen) { + logd("setActiveLiveLockScreen(%s)", liveLockScreen); + cyanogenmod.app.LiveLockScreenInfo.Builder builder = + new cyanogenmod.app.LiveLockScreenInfo.Builder(); + builder.setComponent(liveLockScreen); + mLLSM.setDefaultLiveLockScreen(builder.build()); + } + + public ComponentName getActiveLiveLockScreen() { + cyanogenmod.app.LiveLockScreenInfo llsInfo = mLLSM.getDefaultLiveLockScreen(); + return llsInfo != null ? llsInfo.component : null; + } + + public void launchSettings(LiveLockScreenInfo liveLockScreenInfo) { + logd("launchSettings(%s)", liveLockScreenInfo); + if (liveLockScreenInfo == null || liveLockScreenInfo.settingsComponentName == null) { + return; + } + mContext.startActivity(new Intent().setComponent( + liveLockScreenInfo.settingsComponentName)); + } + + private static ComponentName getLiveLockScreenComponentName(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, + KeyguardExternalViewProviderService.META_DATA); + if (parser == null) { + Log.w(TAG, "No " + KeyguardExternalViewProviderService.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 (!"lockscreen".equals(nodeName)) { + Log.w(TAG, "Meta-data does not start with lockscreen tag"); + return null; + } + // Dream styleable has the attributes we need so we'll piggy back off of that + TypedArray sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.Dream); + cn = sa.getString(com.android.internal.R.styleable.Dream_settingsActivity); + sa.recycle(); + } catch (PackageManager.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; + } + if (cn != null && cn.indexOf('/') < 0) { + cn = resolveInfo.serviceInfo.packageName + "/" + cn; + } + return cn == null ? null : ComponentName.unflattenFromString(cn); + } + + private static void logd(String msg, Object... args) { + if (LiveLockScreenSettings.DEBUG) { + Log.d(TAG, args == null || args.length == 0 ? msg : String.format(msg, args)); + } + } + + private static class LiveLockScreenInfoComparator implements + Comparator<LiveLockScreenInfo> { + private final ComponentName mDefaultLiveLockScreen; + + public LiveLockScreenInfoComparator(ComponentName defaultLiveLockScreen) { + mDefaultLiveLockScreen = defaultLiveLockScreen; + } + + @Override + public int compare(LiveLockScreenInfo lhs, LiveLockScreenInfo rhs) { + return sortKey(lhs).compareTo(sortKey(rhs)); + } + + private String sortKey(LiveLockScreenInfo di) { + StringBuilder sb = new StringBuilder(); + sb.append(di.componentName.equals(mDefaultLiveLockScreen) ? '0' : '1'); + sb.append(di.caption); + return sb.toString(); + } + } + } +} diff --git a/src/com/android/settings/cyanogenmod/LockscreenSettingsAlias.java b/src/com/android/settings/cyanogenmod/LockscreenSettingsAlias.java new file mode 100644 index 0000000..f89de89 --- /dev/null +++ b/src/com/android/settings/cyanogenmod/LockscreenSettingsAlias.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.cyanogenmod; + +import android.app.admin.DevicePolicyManager; +import com.android.internal.widget.LockPatternUtils; +import com.android.settings.SecuritySettings; +import com.android.settings.TrustAgentUtils; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settings.search.SearchIndexableRaw; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.hardware.fingerprint.FingerprintManager; +import android.os.Bundle; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.SearchIndexableResource; +import com.android.settings.R; +import java.util.ArrayList; +import java.util.List; + +public class LockscreenSettingsAlias extends SecuritySettings { + public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new SecuritySearchIndexProvider(); + + private static class SecuritySearchIndexProvider extends BaseSearchIndexProvider { + + boolean mIsPrimary; + + public SecuritySearchIndexProvider() { + super(); + + mIsPrimary = MY_USER_ID == UserHandle.USER_OWNER; + } + + @Override + public List<SearchIndexableResource> getXmlResourcesToIndex( + Context context, boolean enabled) { + + List<SearchIndexableResource> result = new ArrayList<SearchIndexableResource>(); + + LockPatternUtils lockPatternUtils = new LockPatternUtils(context); + // Add options for lock/unlock screen + int resId = getResIdForLockUnlockScreen(context, lockPatternUtils); + + SearchIndexableResource sir = new SearchIndexableResource(context); + sir.xmlResId = resId; + result.add(sir); + + return result; + } + + @Override + public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) { + final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>(); + final Resources res = context.getResources(); + + final String screenTitle = res.getString(R.string.lockscreen_settings); + + SearchIndexableRaw data = new SearchIndexableRaw(context); + data.title = screenTitle; + data.screenTitle = screenTitle; + result.add(data); + + if (!mIsPrimary) { + int resId = (UserManager.get(context).isLinkedUser()) ? + R.string.profile_info_settings_title : R.string.user_info_settings_title; + + data = new SearchIndexableRaw(context); + data.title = res.getString(resId); + data.screenTitle = screenTitle; + result.add(data); + } + + // Fingerprint + FingerprintManager fpm = + (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE); + if (fpm.isHardwareDetected()) { + // This catches the title which can be overloaded in an overlay + data = new SearchIndexableRaw(context); + data.title = res.getString(R.string.security_settings_fingerprint_preference_title); + data.screenTitle = screenTitle; + result.add(data); + // Fallback for when the above doesn't contain "fingerprint" + data = new SearchIndexableRaw(context); + data.title = res.getString(R.string.fingerprint_manage_category_title); + data.screenTitle = screenTitle; + result.add(data); + } + + PackageManager pm = context.getPackageManager(); + if (pm.hasSystemFeature(LIVE_LOCK_SCREEN_FEATURE)) { + data = new SearchIndexableRaw(context); + data.title = res.getString(R.string.live_lock_screen_title); + data.screenTitle = screenTitle; + result.add(data); + } + + // Advanced + final LockPatternUtils lockPatternUtils = new LockPatternUtils(context); + if (lockPatternUtils.isSecure(MY_USER_ID)) { + ArrayList<TrustAgentUtils.TrustAgentComponentInfo> agents = + getActiveTrustAgents(context.getPackageManager(), lockPatternUtils, + context.getSystemService(DevicePolicyManager.class)); + for (int i = 0; i < agents.size(); i++) { + final TrustAgentUtils.TrustAgentComponentInfo agent = agents.get(i); + data = new SearchIndexableRaw(context); + data.title = agent.title; + data.screenTitle = screenTitle; + result.add(data); + } + } + + return result; + } + } + + @Override + public void onCreate(Bundle savedInstanceState) { + Bundle bundle = getArguments(); + if (bundle != null) { + bundle.putInt(FILTER_TYPE_EXTRA, TYPE_LOCKSCREEN_EXTRA); + } + super.onCreate(savedInstanceState); + } +} diff --git a/src/com/android/settings/cyanogenmod/LtoService.java b/src/com/android/settings/cyanogenmod/LtoService.java new file mode 100644 index 0000000..c0b211b --- /dev/null +++ b/src/com/android/settings/cyanogenmod/LtoService.java @@ -0,0 +1,338 @@ +/* + * Copyright (C) 2015 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.cyanogenmod; + +import static cyanogenmod.hardware.CMHardwareManager.FEATURE_LONG_TERM_ORBITS; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.os.AsyncTask; +import android.os.IBinder; +import android.os.PowerManager; +import android.os.PowerManager.WakeLock; +import android.os.UserHandle; +import android.preference.PreferenceManager; +import android.util.Log; + +import com.android.settings.location.LocationSettings; + +import cyanogenmod.hardware.CMHardwareManager; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.util.Date; + +public class LtoService extends Service { + private static final String TAG = "LtoService"; + private static final boolean ALOGV = true; + + private static final String KEY_LAST_DOWNLOAD = "lto_last_download"; + + public static final String ACTION_NEW_GPS_DATA = "com.cyanogenmod.actions.NEW_GPS_DATA"; + + private static final int DOWNLOAD_TIMEOUT = 45000; /* 45 seconds */ + + private CMHardwareManager mHardware; + private LtoDownloadTask mTask; + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (mHardware == null || + !mHardware.isSupported(FEATURE_LONG_TERM_ORBITS)) { + if (ALOGV) Log.v(TAG, "LTO is not supported by this device"); + return START_NOT_STICKY; + } + if (!LocationSettings.isLocationModeEnabled(this)) { + if (ALOGV) Log.v(TAG, "Location mode not enabled in this device"); + return START_NOT_STICKY; + } + + if (mTask != null && mTask.getStatus() != AsyncTask.Status.FINISHED) { + if (ALOGV) Log.v(TAG, "LTO download is still active, not starting new download"); + return START_REDELIVER_INTENT; + } + + if (!shouldDownload()) { + Log.d(TAG, "Service started, but shouldn't download ... stopping"); + stopSelf(); + return START_NOT_STICKY; + } + + mTask = new LtoDownloadTask(mHardware.getLtoSource(), + new File(mHardware.getLtoDestination())); + mTask.execute(); + + return START_REDELIVER_INTENT; + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public void onCreate() { + super.onCreate(); + mHardware = CMHardwareManager.getInstance(this); + } + + @Override + public void onDestroy() { + if (mTask != null && mTask.getStatus() != AsyncTask.Status.FINISHED) { + mTask.cancel(true); + mTask = null; + } + } + + private boolean shouldDownload() { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); + NetworkInfo info = cm.getActiveNetworkInfo(); + + if (info == null || !info.isConnected()) { + if (ALOGV) Log.v(TAG, "No network connection is available for LTO download"); + } else { + boolean wifiOnly = prefs.getBoolean( + LocationSettings.KEY_LTO_DOWNLOAD_DATA_WIFI_ONLY, false); + if (wifiOnly && info.getType() != ConnectivityManager.TYPE_WIFI) { + if (ALOGV) { + Log.v(TAG, "Active network is of type " + + info.getTypeName() + ", but Wifi only was selected"); + } + return false; + } + } + + long now = System.currentTimeMillis(); + long lastDownload = getLastDownload(); + long due = lastDownload + mHardware.getLtoDownloadInterval(); + + if (ALOGV) { + Log.v(TAG, "Now " + now + " due " + due + "(" + new Date(due) + ")"); + } + + if (lastDownload != 0 && now < due) { + if (ALOGV) Log.v(TAG, "LTO download is not due yet"); + return false; + } + + return true; + } + + private class LtoDownloadTask extends AsyncTask<Void, Integer, Integer> { + private String mSource; + private File mDestination; + private File mTempFile; + private WakeLock mWakeLock; + + private static final int RESULT_SUCCESS = 0; + private static final int RESULT_FAILURE = 1; + private static final int RESULT_CANCELLED = 2; + + public LtoDownloadTask(String source, File destination) { + mSource = source; + mDestination = destination; + try { + mTempFile = File.createTempFile("lto-download", null, getCacheDir()); + } catch (IOException e) { + Log.w(TAG, "Could not create temporary file", e); + } + + PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); + mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); + } + + @Override + protected void onPreExecute() { + mWakeLock.acquire(); + } + + @Override + protected Integer doInBackground(Void... params) { + BufferedInputStream in = null; + BufferedOutputStream out = null; + int result = RESULT_SUCCESS; + + try { + final URLConnection connection = new URL(mSource).openConnection(); + connection.setRequestProperty("Connection", "close"); + connection.setConnectTimeout(DOWNLOAD_TIMEOUT); + connection.setReadTimeout(DOWNLOAD_TIMEOUT); + final Thread interruptThread = new Thread(new Runnable() { + @Override + public void run() { + try { + Thread.sleep(DOWNLOAD_TIMEOUT); + ((HttpURLConnection)connection).disconnect(); + } catch (InterruptedException e) { + } + } + }); + interruptThread.start(); + + File outputFile = mTempFile != null ? mTempFile : mDestination; + + in = new BufferedInputStream(connection.getInputStream()); + out = new BufferedOutputStream(new FileOutputStream(outputFile)); + + byte[] buffer = new byte[2048]; + int count, total = 0; + long length = 0; + try { + String value = connection.getHeaderField("Content-Length"); + if (value != null) { + length = Long.parseLong(value); + } + } catch (NumberFormatException ex) { + // Ignore + } + + // Read all the buffer + while ((count = in.read(buffer, 0, buffer.length)) != -1) { + if (isCancelled()) { + result = RESULT_CANCELLED; + break; + } + out.write(buffer, 0, count); + total += count; + + if (length > 0) { + float progress = (float) total * 100 / length; + publishProgress((int) progress); + } + } + + // The file is currently downloaded. Interrupt the timeout thread + interruptThread.interrupt(); + + Log.d(TAG, "Downloaded " + total + "/" + length + " bytes of LTO data"); + if (total == 0 || (length > 0 && total != length)) { + result = RESULT_FAILURE; + } + in.close(); + out.close(); + } catch (MalformedURLException e) { + Log.e(TAG, "URI syntax wrong", e); + result = RESULT_FAILURE; + } catch (IOException e) { + Log.e(TAG, "Failed downloading LTO data", e); + result = RESULT_FAILURE; + } finally { + try { + if (in != null) { + in.close(); + } + if (out != null) { + out.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + Log.d(TAG, "return " + result); + return result; + } + + @Override + protected void onPostExecute(Integer result) { + if (result != null) { + finish(result); + } + } + + @Override + protected void onCancelled() { + finish(RESULT_CANCELLED); + } + + private void finish(int result) { + final Context context = LtoService.this; + + if (mTempFile != null) { + if (result == RESULT_SUCCESS) { + mDestination.delete(); + if (!mTempFile.renameTo(mDestination)) { + Log.w(TAG, "Could not move temporary file to destination"); + } else { + mDestination.setReadable(true, false); + } + } + mTempFile.delete(); + } else if (result != RESULT_SUCCESS) { + mDestination.delete(); + } else { + mDestination.setReadable(true, false); + } + + if (result == RESULT_SUCCESS) { + long now = System.currentTimeMillis(); + SharedPreferences.Editor editor = + PreferenceManager.getDefaultSharedPreferences(context).edit(); + editor.putLong(KEY_LAST_DOWNLOAD, now); + editor.apply(); + scheduleNextDownload(now); + notifyNewGpsData(); + + } else if (result == RESULT_FAILURE) { + /* failure, schedule next download in 1 hour */ + long lastDownload = getLastDownload() + (60 * 60 * 1000); + scheduleNextDownload(lastDownload); + } else { + /* cancelled, likely due to lost network - we'll get restarted + * when network comes back */ + } + + mWakeLock.release(); + stopSelf(); + } + } + + private void notifyNewGpsData() { + Intent intent = new Intent(ACTION_NEW_GPS_DATA); + sendStickyBroadcastAsUser(intent, UserHandle.ALL); + } + + private PendingIntent scheduleNextDownload(long lastDownload) { + AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE); + Intent intent = new Intent(this, LtoService.class); + PendingIntent pi = PendingIntent.getService(this, 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT); + + long nextLtoDownload = lastDownload + mHardware.getLtoDownloadInterval(); + am.set(AlarmManager.RTC, nextLtoDownload, pi); + return pi; + } + + private long getLastDownload() { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + return prefs.getLong(LtoService.KEY_LAST_DOWNLOAD, 0); + } +} diff --git a/src/com/android/settings/cyanogenmod/NavBar.java b/src/com/android/settings/cyanogenmod/NavBar.java new file mode 100644 index 0000000..a4ea5e7 --- /dev/null +++ b/src/com/android/settings/cyanogenmod/NavBar.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2015 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.cyanogenmod; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Fragment; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.os.Bundle; +import android.provider.Settings; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import com.android.settings.R; +import com.android.settings.Utils; +import cyanogenmod.providers.CMSettings; + +public class NavBar extends Fragment implements View.OnClickListener { + + private LinearLayout mRestore, mSave, mEdit; + private boolean mEditMode; + private Activity mActivity; + private final static Intent mIntent = new Intent("android.intent.action.NAVBAR_EDIT"); + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + mActivity = activity; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.nav_bar, container, false); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + mEdit = (LinearLayout) view.findViewById(R.id.navbar_edit); + mEdit.setOnClickListener(this); + mSave = (LinearLayout) view.findViewById(R.id.navbar_save); + mSave.setOnClickListener(this); + mRestore = (LinearLayout) view.findViewById(R.id.navbar_restore); + mRestore.setOnClickListener(this); + } + + @Override + public void onDetach() { + mActivity = null; + super.onDetach(); + } + + @Override + public void onPause() { + super.onPause(); + toggleEditMode(false, false); + } + + @Override + public void onClick(View v) { + if (v == mEdit) { + mEditMode = !mEditMode; + toggleEditMode(mEditMode, false); + } else if (v == mSave) { + mEditMode = !mEditMode; + toggleEditMode(mEditMode, true); + } else if (v == mRestore) { + new AlertDialog.Builder(mActivity) + .setTitle(R.string.profile_reset_title) + .setIcon(R.drawable.ic_navbar_restore) + .setMessage(R.string.navigation_bar_reset_message) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + if (mEditMode) { + toggleEditMode(false, false); + } + CMSettings.System.putString(getActivity().getContentResolver(), + CMSettings.System.NAV_BUTTONS, null); + toggleEditMode(true, false); + toggleEditMode(false, false); + mEditMode = false; + } + }).setNegativeButton(R.string.cancel, null) + .create().show(); + } + } + + /** + * Toggles navbar edit mode + * @param on True to enter edit mode / false to exit + * @param save True to save changes / false to discard them + */ + private void toggleEditMode(boolean on, boolean save) { + mIntent.putExtra("edit", on); + mIntent.putExtra("save", save); + mActivity.sendBroadcast(mIntent); + if (on) { + Utils.lockCurrentOrientation(mActivity); + } else { + mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); + } + toggleEditSaveViews(on); + } + + private void toggleEditSaveViews(boolean on) { + mEdit.setVisibility(!on ? View.VISIBLE : View.GONE); + mSave.setVisibility(on + ? View.VISIBLE : View.GONE); + } +} diff --git a/src/com/android/settings/cyanogenmod/PackageListAdapter.java b/src/com/android/settings/cyanogenmod/PackageListAdapter.java new file mode 100644 index 0000000..cbf22e9 --- /dev/null +++ b/src/com/android/settings/cyanogenmod/PackageListAdapter.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2012-2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.cyanogenmod; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.Message; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import com.android.settings.R; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.TreeSet; + +public class PackageListAdapter extends BaseAdapter implements Runnable { + private PackageManager mPm; + private LayoutInflater mInflater; + private List<PackageItem> mInstalledPackages = new LinkedList<PackageItem>(); + + // Packages which don't have launcher icons, but which we want to show nevertheless + private static final String[] PACKAGE_WHITELIST = new String[] { + "android", /* system server */ + "com.android.systemui", /* system UI */ + "com.android.providers.downloads" /* download provider */ + }; + + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + PackageItem item = (PackageItem) msg.obj; + int index = Collections.binarySearch(mInstalledPackages, item); + if (index < 0) { + mInstalledPackages.add(-index - 1, item); + } else { + mInstalledPackages.get(index).activityTitles.addAll(item.activityTitles); + } + notifyDataSetChanged(); + } + }; + + public static class PackageItem implements Comparable<PackageItem> { + public final String packageName; + public final CharSequence title; + private final TreeSet<CharSequence> activityTitles = new TreeSet<CharSequence>(); + public final Drawable icon; + + PackageItem(String packageName, CharSequence title, Drawable icon) { + this.packageName = packageName; + this.title = title; + this.icon = icon; + } + + @Override + public int compareTo(PackageItem another) { + int result = title.toString().compareToIgnoreCase(another.title.toString()); + return result != 0 ? result : packageName.compareTo(another.packageName); + } + } + + public PackageListAdapter(Context context) { + mPm = context.getPackageManager(); + mInflater = LayoutInflater.from(context); + reloadList(); + } + + @Override + public int getCount() { + synchronized (mInstalledPackages) { + return mInstalledPackages.size(); + } + } + + @Override + public PackageItem getItem(int position) { + synchronized (mInstalledPackages) { + return mInstalledPackages.get(position); + } + } + + @Override + public long getItemId(int position) { + synchronized (mInstalledPackages) { + // packageName is guaranteed to be unique in mInstalledPackages + return mInstalledPackages.get(position).packageName.hashCode(); + } + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + ViewHolder holder; + if (convertView != null) { + holder = (ViewHolder) convertView.getTag(); + } else { + convertView = mInflater.inflate(R.layout.preference_icon, null, false); + holder = new ViewHolder(); + convertView.setTag(holder); + holder.title = (TextView) convertView.findViewById(com.android.internal.R.id.title); + holder.summary = (TextView) convertView.findViewById(com.android.internal.R.id.summary); + holder.icon = (ImageView) convertView.findViewById(R.id.icon); + } + + PackageItem applicationInfo = getItem(position); + holder.title.setText(applicationInfo.title); + holder.icon.setImageDrawable(applicationInfo.icon); + + boolean needSummary = applicationInfo.activityTitles.size() > 0; + if (applicationInfo.activityTitles.size() == 1) { + if (TextUtils.equals(applicationInfo.title, applicationInfo.activityTitles.first())) { + needSummary = false; + } + } + + if (needSummary) { + holder.summary.setText(TextUtils.join(", ", applicationInfo.activityTitles)); + holder.summary.setVisibility(View.VISIBLE); + } else { + holder.summary.setVisibility(View.GONE); + } + + return convertView; + } + + private void reloadList() { + mInstalledPackages.clear(); + new Thread(this).start(); + } + + @Override + public void run() { + final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); + mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); + List<ResolveInfo> installedAppsInfo = mPm.queryIntentActivities(mainIntent, 0); + + for (ResolveInfo info : installedAppsInfo) { + ApplicationInfo appInfo = info.activityInfo.applicationInfo; + final PackageItem item = new PackageItem(appInfo.packageName, + appInfo.loadLabel(mPm), appInfo.loadIcon(mPm)); + item.activityTitles.add(info.loadLabel(mPm)); + mHandler.obtainMessage(0, item).sendToTarget(); + } + + for (String packageName : PACKAGE_WHITELIST) { + try { + ApplicationInfo appInfo = mPm.getApplicationInfo(packageName, 0); + final PackageItem item = new PackageItem(appInfo.packageName, + appInfo.loadLabel(mPm), appInfo.loadIcon(mPm)); + mHandler.obtainMessage(0, item).sendToTarget(); + } catch (PackageManager.NameNotFoundException ignored) { + // package not present, so nothing to add -> ignore it + } + } + } + + private static class ViewHolder { + TextView title; + TextView summary; + ImageView icon; + } +} diff --git a/src/com/android/settings/cyanogenmod/PowerMenuActions.java b/src/com/android/settings/cyanogenmod/PowerMenuActions.java new file mode 100644 index 0000000..6a02a97 --- /dev/null +++ b/src/com/android/settings/cyanogenmod/PowerMenuActions.java @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2014-2015 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.cyanogenmod; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.UserInfo; +import android.content.res.Resources; +import android.os.Bundle; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.os.UserManager; +import android.preference.CheckBoxPreference; +import android.preference.Preference; +import android.preference.PreferenceScreen; +import android.preference.ListPreference; +import android.preference.Preference.OnPreferenceChangeListener; +import android.provider.Settings; + +import com.android.internal.logging.MetricsLogger; +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.internal.util.cm.PowerMenuConstants; + +import cyanogenmod.providers.CMSettings; + +import org.cyanogenmod.internal.logging.CMMetricsLogger; + +import static com.android.internal.util.cm.PowerMenuConstants.*; + +import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; + +public class PowerMenuActions extends SettingsPreferenceFragment { + final static String TAG = "PowerMenuActions"; + + private CheckBoxPreference mRebootPref; + private CheckBoxPreference mScreenshotPref; + private CheckBoxPreference mAirplanePref; + private CheckBoxPreference mUsersPref; + private CheckBoxPreference mSettingsPref; + private CheckBoxPreference mLockdownPref; + private CheckBoxPreference mBugReportPref; + private CheckBoxPreference mSilentPref; + private CheckBoxPreference mVoiceAssistPref; + private CheckBoxPreference mAssistPref; + + Context mContext; + private ArrayList<String> mLocalUserConfig = new ArrayList<String>(); + private String[] mAvailableActions; + private String[] mAllActions; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + addPreferencesFromResource(R.xml.power_menu_settings); + mContext = getActivity().getApplicationContext(); + + mAvailableActions = getActivity().getResources().getStringArray( + R.array.power_menu_actions_array); + mAllActions = PowerMenuConstants.getAllActions(); + + for (String action : mAllActions) { + // Remove preferences not present in the overlay + if (!isActionAllowed(action)) { + getPreferenceScreen().removePreference(findPreference(action)); + continue; + } + + if (action.equals(GLOBAL_ACTION_KEY_REBOOT)) { + mRebootPref = (CheckBoxPreference) findPreference(GLOBAL_ACTION_KEY_REBOOT); + } else if (action.equals(GLOBAL_ACTION_KEY_SCREENSHOT)) { + mScreenshotPref = (CheckBoxPreference) findPreference(GLOBAL_ACTION_KEY_SCREENSHOT); + } else if (action.equals(GLOBAL_ACTION_KEY_AIRPLANE)) { + mAirplanePref = (CheckBoxPreference) findPreference(GLOBAL_ACTION_KEY_AIRPLANE); + } else if (action.equals(GLOBAL_ACTION_KEY_USERS)) { + mUsersPref = (CheckBoxPreference) findPreference(GLOBAL_ACTION_KEY_USERS); + } else if (action.equals(GLOBAL_ACTION_KEY_SETTINGS)) { + mSettingsPref = (CheckBoxPreference) findPreference(GLOBAL_ACTION_KEY_SETTINGS); + } else if (action.equals(GLOBAL_ACTION_KEY_LOCKDOWN)) { + mLockdownPref = (CheckBoxPreference) findPreference(GLOBAL_ACTION_KEY_LOCKDOWN); + } else if (action.equals(GLOBAL_ACTION_KEY_BUGREPORT)) { + mBugReportPref = (CheckBoxPreference) findPreference(GLOBAL_ACTION_KEY_BUGREPORT); + } else if (action.equals(GLOBAL_ACTION_KEY_SILENT)) { + mSilentPref = (CheckBoxPreference) findPreference(GLOBAL_ACTION_KEY_SILENT); + } else if (action.equals(GLOBAL_ACTION_KEY_VOICEASSIST)) { + mSilentPref = (CheckBoxPreference) findPreference(GLOBAL_ACTION_KEY_VOICEASSIST); + } else if (action.equals(GLOBAL_ACTION_KEY_ASSIST)) { + mSilentPref = (CheckBoxPreference) findPreference(GLOBAL_ACTION_KEY_ASSIST); + } + } + + getUserConfig(); + } + + @Override + protected int getMetricsCategory() { + return CMMetricsLogger.POWER_MENU_ACTIONS; + } + + @Override + public void onStart() { + super.onStart(); + + if (mRebootPref != null) { + mRebootPref.setChecked(settingsArrayContains(GLOBAL_ACTION_KEY_REBOOT)); + } + + if (mScreenshotPref != null) { + mScreenshotPref.setChecked(settingsArrayContains(GLOBAL_ACTION_KEY_SCREENSHOT)); + } + + if (mAirplanePref != null) { + mAirplanePref.setChecked(settingsArrayContains(GLOBAL_ACTION_KEY_AIRPLANE)); + } + + if (mUsersPref != null) { + if (!UserHandle.MU_ENABLED || !UserManager.supportsMultipleUsers()) { + getPreferenceScreen().removePreference(findPreference(GLOBAL_ACTION_KEY_USERS)); + mUsersPref = null; + } else { + List<UserInfo> users = ((UserManager) mContext.getSystemService( + Context.USER_SERVICE)).getUsers(); + boolean enabled = (users.size() > 1); + mUsersPref.setChecked(settingsArrayContains(GLOBAL_ACTION_KEY_USERS) && enabled); + mUsersPref.setEnabled(enabled); + } + } + + if (mSettingsPref != null) { + mSettingsPref.setChecked(settingsArrayContains(GLOBAL_ACTION_KEY_SETTINGS)); + } + + if (mLockdownPref != null) { + mLockdownPref.setChecked(settingsArrayContains(GLOBAL_ACTION_KEY_LOCKDOWN)); + } + + if (mBugReportPref != null) { + mBugReportPref.setChecked(settingsArrayContains(GLOBAL_ACTION_KEY_BUGREPORT)); + } + + if (mSilentPref != null) { + mSilentPref.setChecked(settingsArrayContains(GLOBAL_ACTION_KEY_SILENT)); + } + + if (mVoiceAssistPref != null) { + mVoiceAssistPref.setChecked(settingsArrayContains(GLOBAL_ACTION_KEY_VOICEASSIST)); + } + + if (mAssistPref != null) { + mAssistPref.setChecked(settingsArrayContains(GLOBAL_ACTION_KEY_ASSIST)); + } + + updatePreferences(); + } + + @Override + public void onResume() { + super.onResume(); + updatePreferences(); + } + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { + boolean value; + + if (preference == mRebootPref) { + value = mRebootPref.isChecked(); + updateUserConfig(value, GLOBAL_ACTION_KEY_REBOOT); + + } else if (preference == mScreenshotPref) { + value = mScreenshotPref.isChecked(); + updateUserConfig(value, GLOBAL_ACTION_KEY_SCREENSHOT); + + } else if (preference == mAirplanePref) { + value = mAirplanePref.isChecked(); + updateUserConfig(value, GLOBAL_ACTION_KEY_AIRPLANE); + + } else if (preference == mUsersPref) { + value = mUsersPref.isChecked(); + updateUserConfig(value, GLOBAL_ACTION_KEY_USERS); + + } else if (preference == mSettingsPref) { + value = mSettingsPref.isChecked(); + updateUserConfig(value, GLOBAL_ACTION_KEY_SETTINGS); + + } else if (preference == mLockdownPref) { + value = mLockdownPref.isChecked(); + updateUserConfig(value, GLOBAL_ACTION_KEY_LOCKDOWN); + + } else if (preference == mBugReportPref) { + value = mBugReportPref.isChecked(); + updateUserConfig(value, GLOBAL_ACTION_KEY_BUGREPORT); + + } else if (preference == mSilentPref) { + value = mSilentPref.isChecked(); + updateUserConfig(value, GLOBAL_ACTION_KEY_SILENT); + + } else if (preference == mVoiceAssistPref) { + value = mVoiceAssistPref.isChecked(); + updateUserConfig(value, GLOBAL_ACTION_KEY_VOICEASSIST); + + } else if (preference == mAssistPref) { + value = mAssistPref.isChecked(); + updateUserConfig(value, GLOBAL_ACTION_KEY_ASSIST); + + } else { + return super.onPreferenceTreeClick(preferenceScreen, preference); + } + return true; + } + + private boolean settingsArrayContains(String preference) { + return mLocalUserConfig.contains(preference); + } + + private boolean isActionAllowed(String action) { + if (Arrays.asList(mAvailableActions).contains(action)) { + return true; + } + return false; + } + + private void updateUserConfig(boolean enabled, String action) { + if (enabled) { + if (!settingsArrayContains(action)) { + mLocalUserConfig.add(action); + } + } else { + if (settingsArrayContains(action)) { + mLocalUserConfig.remove(action); + } + } + saveUserConfig(); + } + + private void updatePreferences() { + boolean bugreport = Settings.Secure.getInt(getContentResolver(), + Settings.Secure.BUGREPORT_IN_POWER_MENU, 0) != 0; + + if (mBugReportPref != null) { + mBugReportPref.setEnabled(bugreport); + if (bugreport) { + mBugReportPref.setSummary(null); + } else { + mBugReportPref.setSummary(R.string.power_menu_bug_report_disabled); + } + } + } + + private void getUserConfig() { + mLocalUserConfig.clear(); + String[] defaultActions; + String savedActions = CMSettings.Secure.getStringForUser(mContext.getContentResolver(), + CMSettings.Secure.POWER_MENU_ACTIONS, UserHandle.USER_CURRENT); + + if (savedActions == null) { + defaultActions = mContext.getResources().getStringArray( + com.android.internal.R.array.config_globalActionsList); + for (String action : defaultActions) { + mLocalUserConfig.add(action); + } + } else { + for (String action : savedActions.split("\\|")) { + mLocalUserConfig.add(action); + } + } + } + + private void saveUserConfig() { + StringBuilder s = new StringBuilder(); + + // TODO: Use DragSortListView + ArrayList<String> setactions = new ArrayList<String>(); + for (String action : mAllActions) { + if (settingsArrayContains(action) && isActionAllowed(action)) { + setactions.add(action); + } else { + continue; + } + } + + for (int i = 0; i < setactions.size(); i++) { + s.append(setactions.get(i).toString()); + if (i != setactions.size() - 1) { + s.append("|"); + } + } + + CMSettings.Secure.putStringForUser(getContentResolver(), + CMSettings.Secure.POWER_MENU_ACTIONS, s.toString(), UserHandle.USER_CURRENT); + updatePowerMenuDialog(); + } + + private void updatePowerMenuDialog() { + Intent u = new Intent(); + u.setAction(Intent.UPDATE_POWER_MENU); + mContext.sendBroadcastAsUser(u, UserHandle.ALL); + } +} diff --git a/src/com/android/settings/cyanogenmod/PrivacySettings.java b/src/com/android/settings/cyanogenmod/PrivacySettings.java new file mode 100644 index 0000000..f6a9c15 --- /dev/null +++ b/src/com/android/settings/cyanogenmod/PrivacySettings.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.cyanogenmod; + +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.Utils; +import com.android.internal.telephony.util.BlacklistUtils; +import org.cyanogenmod.internal.logging.CMMetricsLogger; + +/** + * Privacy settings + */ +public class PrivacySettings extends SettingsPreferenceFragment { + + private static final String KEY_BLACKLIST = "blacklist"; + private static final String KEY_STATS = "cmstats"; + + private PreferenceScreen mBlacklist; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.privacy_settings_cyanogenmod); + + mBlacklist = (PreferenceScreen) findPreference(KEY_BLACKLIST); + + // Add package manager to check if features are available + PackageManager pm = getPackageManager(); + + boolean isOwner = Utils.isUserOwner(); + if (!isOwner) { + PreferenceScreen root = getPreferenceScreen(); + root.removePreference(findPreference(KEY_STATS)); + } + + // Determine options based on device telephony support + if (!pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) || !isOwner) { + // No telephony, remove dependent options + PreferenceScreen root = getPreferenceScreen(); + root.removePreference(mBlacklist); + } + } + + @Override + protected int getMetricsCategory() { + return CMMetricsLogger.PRIVACY_SETTINGS; + } + + @Override + public void onResume() { + super.onResume(); + updateBlacklistSummary(); + } + + private void updateBlacklistSummary() { + if (BlacklistUtils.isBlacklistEnabled(getActivity())) { + mBlacklist.setSummary(R.string.blacklist_summary); + } else { + mBlacklist.setSummary(R.string.blacklist_summary_disabled); + } + } + +} diff --git a/src/com/android/settings/cyanogenmod/ProtectedAccountView.java b/src/com/android/settings/cyanogenmod/ProtectedAccountView.java new file mode 100644 index 0000000..4b9e14e --- /dev/null +++ b/src/com/android/settings/cyanogenmod/ProtectedAccountView.java @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.cyanogenmod; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AccountManagerCallback; +import android.accounts.AccountManagerFuture; +import android.accounts.AuthenticatorException; +import android.accounts.OperationCanceledException; +import android.app.Activity; +import android.app.ActivityManager; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.SharedPreferences; +import android.graphics.Rect; +import android.os.Bundle; +import android.os.UserHandle; +import android.preference.PreferenceManager; +import android.text.InputFilter; +import android.text.LoginFilter; +import android.util.AttributeSet; +import android.view.View; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.Toast; +import com.android.internal.widget.LockPatternUtils; +import com.android.settings.R; +import com.android.settings.applications.LockPatternActivity; + +import java.io.IOException; + +/** + * When the user forgets their password a bunch of times, we fall back on their + * account's login/password to unlock protected apps (and reset their lock pattern). + */ +public class ProtectedAccountView extends LinearLayout implements View.OnClickListener { + + public static interface OnNotifyAccountReset { + void onNotifyAccountReset(); + } + + private EditText mLogin; + private EditText mPassword; + private Button mOk; + private Context mContext; + private LockPatternUtils mLockPatternUtils; + private OnNotifyAccountReset mNotifyAccountResetCb; + + /** + * Shown while making asynchronous check of password. + */ + private ProgressDialog mCheckingDialog; + + public ProtectedAccountView(Context context) { + this(context, null); + } + + public ProtectedAccountView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ProtectedAccountView(Context context, AttributeSet st, int ds) { + super(context, st, ds); + mContext = context; + mLockPatternUtils = new LockPatternUtils(mContext); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + mLogin = (EditText) findViewById(R.id.login); + mLogin.setFilters(new InputFilter[] { new LoginFilter.UsernameFilterGeneric() } ); + mPassword = (EditText) findViewById(R.id.password); + + mOk = (Button) findViewById(R.id.ok); + mOk.setOnClickListener(this); + + reset(); + } + + @Override + protected boolean onRequestFocusInDescendants(int direction, + Rect previouslyFocusedRect) { + // send focus to the login field + return mLogin.requestFocus(direction, previouslyFocusedRect); + } + + public boolean needsInput() { + return true; + } + + public void setOnNotifyAccountResetCb(OnNotifyAccountReset callback) { + this.mNotifyAccountResetCb = callback; + } + + public void clearFocusOnInput() { + mLogin.clearFocus(); + mPassword.clearFocus(); + + // hide keyboard + final InputMethodManager imm = (InputMethodManager) + mContext.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(mLogin.getWindowToken(), 0); + imm.hideSoftInputFromWindow(mPassword.getWindowToken(), 0); + } + + public void reset() { + mLogin.setText(""); + mPassword.setText(""); + mLogin.requestFocus(); + } + + /** {@inheritDoc} */ + public void cleanUp() { + if (mCheckingDialog != null) { + mCheckingDialog.hide(); + } + } + + public void onClick(View v) { + if (v == mOk) { + asyncCheckPassword(); + } + } + + private void postOnCheckPasswordResult(final boolean success) { + // ensure this runs on UI thread + mLogin.post(new Runnable() { + public void run() { + if (success) { + + Activity baseActivity = (Activity) mContext; + + if (!baseActivity.isFinishing()) { + // Remove pattern + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(mContext); + SharedPreferences.Editor editor = prefs.edit(); + editor.remove(LockPatternActivity.PATTERN_LOCK_PROTECTED_APPS); + editor.commit(); + + if (mNotifyAccountResetCb != null) { + mNotifyAccountResetCb.onNotifyAccountReset(); + } else { + baseActivity.setResult(Activity.RESULT_OK); + baseActivity.finish(); + } + } + } else { + Toast.makeText(mContext, + getResources().getString( + R.string.pa_login_incorrect_login), + Toast.LENGTH_SHORT).show(); + mPassword.setText(""); + } + } + }); + } + + /** + * Given the string the user entered in the 'username' field, find + * the stored account that they probably intended. Prefer, in order: + * + * - an exact match for what was typed, or + * - a case-insensitive match for what was typed, or + * - if they didn't include a domain, an exact match of the username, or + * - if they didn't include a domain, a case-insensitive + * match of the username. + * + * If there is a tie for the best match, choose neither -- + * the user needs to be more specific. + * + * @return an account name from the database, or null if we can't + * find a single best match. + */ + private Account findIntendedAccount(String username) { + Account[] accounts = AccountManager.get(mContext).getAccountsByTypeAsUser("com.google", + new UserHandle(ActivityManager.getCurrentUser())); + + // Try to figure out which account they meant if they + // typed only the username (and not the domain), or got + // the case wrong. + + Account bestAccount = null; + int bestScore = 0; + for (Account a: accounts) { + int score = 0; + if (username.equals(a.name)) { + score = 4; + } else if (username.equalsIgnoreCase(a.name)) { + score = 3; + } else if (username.indexOf('@') < 0) { + int i = a.name.indexOf('@'); + if (i >= 0) { + String aUsername = a.name.substring(0, i); + if (username.equals(aUsername)) { + score = 2; + } else if (username.equalsIgnoreCase(aUsername)) { + score = 1; + } + } + } + if (score > bestScore) { + bestAccount = a; + bestScore = score; + } else if (score == bestScore) { + bestAccount = null; + } + } + return bestAccount; + } + + private void asyncCheckPassword() { + final String login = mLogin.getText().toString(); + final String password = mPassword.getText().toString(); + Account account = findIntendedAccount(login); + if (account == null) { + postOnCheckPasswordResult(false); + return; + } + getProgressDialog().show(); + Bundle options = new Bundle(); + options.putString(AccountManager.KEY_PASSWORD, password); + AccountManager.get(mContext).confirmCredentialsAsUser(account, options, null /* activity */, + new AccountManagerCallback<Bundle>() { + public void run(AccountManagerFuture<Bundle> future) { + try { + final Bundle result = future.getResult(); + final boolean verified = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT); + postOnCheckPasswordResult(verified); + } catch (OperationCanceledException e) { + postOnCheckPasswordResult(false); + } catch (IOException e) { + postOnCheckPasswordResult(false); + } catch (AuthenticatorException e) { + postOnCheckPasswordResult(false); + } finally { + mLogin.post(new Runnable() { + public void run() { + getProgressDialog().hide(); + } + }); + } + } + }, null /* handler */, new UserHandle(ActivityManager.getCurrentUser())); + } + + private Dialog getProgressDialog() { + if (mCheckingDialog == null) { + mCheckingDialog = new ProgressDialog(mContext); + mCheckingDialog.setMessage( + mContext.getString(R.string.pa_login_checking_password)); + mCheckingDialog.setIndeterminate(true); + mCheckingDialog.setCancelable(false); + mCheckingDialog.getWindow().setType( + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + } + return mCheckingDialog; + } +} + diff --git a/src/com/android/settings/cyanogenmod/ProtectedAppsReceiver.java b/src/com/android/settings/cyanogenmod/ProtectedAppsReceiver.java new file mode 100644 index 0000000..a86fe95 --- /dev/null +++ b/src/com/android/settings/cyanogenmod/ProtectedAppsReceiver.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.cyanogenmod; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.provider.Settings; +import android.util.Log; + +import static cyanogenmod.content.Intent.ACTION_PROTECTED; +import static cyanogenmod.content.Intent.ACTION_PROTECTED_CHANGED; +import static cyanogenmod.content.Intent.EXTRA_PROTECTED_COMPONENTS; +import static cyanogenmod.content.Intent.EXTRA_PROTECTED_STATE; + +import cyanogenmod.providers.CMSettings; + +import java.util.ArrayList; +import java.util.HashSet; + +public class ProtectedAppsReceiver extends BroadcastReceiver { + private static final String TAG = "ProtectedAppsReceiver"; + + private static final String PROTECTED_APP_PERMISSION = cyanogenmod.platform.Manifest + .permission.PROTECTED_APP; + + @Override + public void onReceive(Context context, Intent intent) { + if (ACTION_PROTECTED.equals(intent.getAction())) { + boolean protect = intent.getBooleanExtra(EXTRA_PROTECTED_STATE, + PackageManager.COMPONENT_VISIBLE_STATUS); + ArrayList<ComponentName> components = + intent.getParcelableArrayListExtra(EXTRA_PROTECTED_COMPONENTS); + if (components != null) { + updateProtectedAppComponentsAndNotify(context, components, protect); + } + } + } + + public static void updateProtectedAppComponentsAndNotify(Context context, + ArrayList<ComponentName> components, boolean state) { + updateProtectedAppComponents(context, components, state); + updateSettingsSecure(context, components, state); + notifyProtectedChanged(context, components, state); + } + + public static void updateProtectedAppComponents(Context context, + ArrayList<ComponentName> components, boolean state) { + PackageManager pm = context.getPackageManager(); + + for (ComponentName component : components) { + try { + pm.setComponentProtectedSetting(component, state); + } catch (NoSuchMethodError nsm) { + Log.e(TAG, "Unable to protected app via PackageManager"); + } + } + } + + public static void updateSettingsSecure(Context context, + ArrayList<ComponentName> components, boolean state) { + ContentResolver resolver = context.getContentResolver(); + String hiddenComponents = CMSettings.Secure.getString(resolver, + CMSettings.Secure.PROTECTED_COMPONENTS); + HashSet<ComponentName> newComponentList = new HashSet<ComponentName>(); + + if (hiddenComponents != null) { + for (String flattened : hiddenComponents.split("\\|")) { + ComponentName cmp = ComponentName.unflattenFromString(flattened); + if (cmp != null) { + newComponentList.add(cmp); + } + } + } + + boolean update = state == PackageManager.COMPONENT_PROTECTED_STATUS + ? newComponentList.addAll(components) + : newComponentList.removeAll(components); + + if (update) { + StringBuilder flattenedList = new StringBuilder(); + for (ComponentName cmp : newComponentList) { + if (flattenedList.length() > 0) { + flattenedList.append("|"); + } + flattenedList.append(cmp.flattenToString()); + } + CMSettings.Secure.putString(resolver, CMSettings.Secure.PROTECTED_COMPONENTS, + flattenedList.toString()); + } + } + + public static void notifyProtectedChanged(Context context, + ArrayList<ComponentName> components, boolean state) { + Intent intent = new Intent(ACTION_PROTECTED_CHANGED); + intent.putExtra(EXTRA_PROTECTED_STATE, state); + intent.putExtra(EXTRA_PROTECTED_COMPONENTS, components); + + context.sendBroadcast(intent, PROTECTED_APP_PERMISSION); + } +} diff --git a/src/com/android/settings/cyanogenmod/SecureSettingSwitchPreference.java b/src/com/android/settings/cyanogenmod/SecureSettingSwitchPreference.java new file mode 100644 index 0000000..c894e45 --- /dev/null +++ b/src/com/android/settings/cyanogenmod/SecureSettingSwitchPreference.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2015 The CyanogenMod project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.cyanogenmod; + +import android.content.Context; +import android.preference.SwitchPreference; +import android.provider.Settings; +import android.util.AttributeSet; + +public class SecureSettingSwitchPreference extends SwitchPreference { + public SecureSettingSwitchPreference(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public SecureSettingSwitchPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public SecureSettingSwitchPreference(Context context) { + super(context, null); + } + + @Override + protected boolean persistBoolean(boolean value) { + if (shouldPersist()) { + if (value == getPersistedBoolean(!value)) { + // It's already there, so the same as persisting + return true; + } + Settings.Secure.putInt(getContext().getContentResolver(), getKey(), value ? 1 : 0); + return true; + } + return false; + } + + @Override + protected boolean getPersistedBoolean(boolean defaultReturnValue) { + if (!shouldPersist()) { + return defaultReturnValue; + } + return Settings.Secure.getInt(getContext().getContentResolver(), + getKey(), defaultReturnValue ? 1 : 0) != 0; + } + + @Override + protected boolean isPersisted() { + // Using getString instead of getInt so we can simply check for null + // instead of catching an exception. (All values are stored as strings.) + return Settings.Secure.getString(getContext().getContentResolver(), getKey()) != null; + } +} diff --git a/src/com/android/settings/cyanogenmod/ShortcutPickHelper.java b/src/com/android/settings/cyanogenmod/ShortcutPickHelper.java new file mode 100644 index 0000000..07a4ee1 --- /dev/null +++ b/src/com/android/settings/cyanogenmod/ShortcutPickHelper.java @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2011 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.cyanogenmod; + +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.AlertDialog.Builder; +import android.app.Fragment; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.Intent.ShortcutIconResource; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseExpandableListAdapter; +import android.widget.ExpandableListView; +import android.widget.TextView; + +import com.android.settings.R; +import com.android.settings.cyanogenmod.ShortcutPickHelper.AppExpandableAdapter.GroupInfo; + +public class ShortcutPickHelper { + + private Activity mParent; + private AlertDialog mAlertDialog; + private OnPickListener mListener; + private PackageManager mPackageManager; + private static final int REQUEST_PICK_SHORTCUT = 100; + private static final int REQUEST_PICK_APPLICATION = 101; + private static final int REQUEST_CREATE_SHORTCUT = 102; + private int lastFragmentId; + + public interface OnPickListener { + void shortcutPicked(String uri, String friendlyName, boolean isApplication); + } + + public ShortcutPickHelper(Activity parent, OnPickListener listener) { + mParent = parent; + mPackageManager = mParent.getPackageManager(); + mListener = listener; + } + + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode == Activity.RESULT_OK) { + switch (requestCode) { + case REQUEST_PICK_APPLICATION: + completeSetCustomApp(data); + break; + case REQUEST_CREATE_SHORTCUT: + completeSetCustomShortcut(data); + break; + case REQUEST_PICK_SHORTCUT: + processShortcut(data, REQUEST_PICK_APPLICATION, REQUEST_CREATE_SHORTCUT); + break; + } + } + } + + public void pickShortcut(String[] names, ShortcutIconResource[] icons, int fragmentId) { + Bundle bundle = new Bundle(); + + ArrayList<String> shortcutNames = new ArrayList<String>(); + if (names != null) { + for (String s : names) { + shortcutNames.add(s); + } + } + shortcutNames.add(mParent.getString(R.string.profile_applist_title)); + shortcutNames.add(mParent.getString(R.string.picker_activities)); + bundle.putStringArrayList(Intent.EXTRA_SHORTCUT_NAME, shortcutNames); + + ArrayList<ShortcutIconResource> shortcutIcons = new ArrayList<ShortcutIconResource>(); + if (icons != null) { + for (ShortcutIconResource s : icons) { + shortcutIcons.add(s); + } + } + shortcutIcons.add(ShortcutIconResource.fromContext(mParent, android.R.drawable.sym_def_app_icon)); + shortcutIcons.add(ShortcutIconResource.fromContext(mParent, R.drawable.activities_icon)); + bundle.putParcelableArrayList(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, shortcutIcons); + + Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY); + pickIntent.putExtra(Intent.EXTRA_INTENT, new Intent(Intent.ACTION_CREATE_SHORTCUT)); + pickIntent.putExtra(Intent.EXTRA_TITLE, mParent.getText(R.string.select_custom_app_title)); + pickIntent.putExtras(bundle); + lastFragmentId = fragmentId; + startFragmentOrActivity(pickIntent, REQUEST_PICK_SHORTCUT); + } + + private void startFragmentOrActivity(Intent pickIntent, int requestCode) { + if (lastFragmentId == 0) { + mParent.startActivityForResult(pickIntent, requestCode); + } else { + Fragment cFrag = mParent.getFragmentManager().findFragmentById(lastFragmentId); + if (cFrag != null) { + mParent.startActivityFromFragment(cFrag, pickIntent, requestCode); + } + } + } + + private void processShortcut(final Intent intent, int requestCodeApplication, int requestCodeShortcut) { + // Handle case where user selected "Applications" + String applicationName = mParent.getString(R.string.profile_applist_title); + String application2name = mParent.getString(R.string.picker_activities); + String shortcutName = intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); + if (applicationName != null && applicationName.equals(shortcutName)) { + Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); + mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); + + Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY); + pickIntent.putExtra(Intent.EXTRA_INTENT, mainIntent); + startFragmentOrActivity(pickIntent, requestCodeApplication); + } else if (application2name != null && application2name.equals(shortcutName)){ + final List<PackageInfo> pInfos = mPackageManager.getInstalledPackages(PackageManager.GET_ACTIVITIES); + ExpandableListView appListView = new ExpandableListView(mParent); + AppExpandableAdapter appAdapter = new AppExpandableAdapter(pInfos, mParent); + appListView.setAdapter(appAdapter); + appListView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() { + @Override + public boolean onChildClick(ExpandableListView parent, View v, + int groupPosition, int childPosition, long id) { + Intent shortIntent = new Intent(Intent.ACTION_MAIN); + String pkgName = ((GroupInfo)parent.getExpandableListAdapter().getGroup(groupPosition)) + .info.packageName; + String actName = ((GroupInfo)parent.getExpandableListAdapter().getGroup(groupPosition)) + .info.activities[childPosition].name; + shortIntent.setClassName(pkgName, actName); + completeSetCustomApp(shortIntent); + mAlertDialog.dismiss(); + return true; + } + }); + Builder builder = new Builder(mParent); + builder.setView(appListView); + mAlertDialog = builder.create(); + mAlertDialog.setTitle(mParent.getString(R.string.select_custom_activity_title)); + mAlertDialog.show(); + mAlertDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + mListener.shortcutPicked(null, null, false); + } + }); + } else { + startFragmentOrActivity(intent, requestCodeShortcut); + } + } + + public class AppExpandableAdapter extends BaseExpandableListAdapter { + + ArrayList<GroupInfo> allList = new ArrayList<GroupInfo>(); + final int groupPadding; + + public class LabelCompare implements Comparator<GroupInfo>{ + @Override + public int compare(GroupInfo item1, GroupInfo item2) { + String rank1 = item1.label.toLowerCase(); + String rank2 = item2.label.toLowerCase(); + int result = rank1.compareTo(rank2); + if(result == 0) { + return 0; + } else if(result < 0) { + return -1; + } else { + return +1; + } + } + } + + class GroupInfo { + String label; + PackageInfo info; + GroupInfo (String l, PackageInfo p) { + label = l; + info = p; + } + } + + public AppExpandableAdapter(List<PackageInfo> pInfos, Context context) { + for (PackageInfo i : pInfos) { + allList.add(new GroupInfo(i.applicationInfo.loadLabel(mPackageManager).toString(), i)); + } + Collections.sort(allList, new LabelCompare()); + groupPadding = context.getResources().getDimensionPixelSize(R.dimen.shortcut_picker_left_padding); + } + + public String getChild(int groupPosition, int childPosition) { + return allList.get(groupPosition).info.activities[childPosition].name; + } + + public long getChildId(int groupPosition, int childPosition) { + return childPosition; + } + + public int getChildrenCount(int groupPosition) { + if (allList.get(groupPosition).info.activities != null) { + return allList.get(groupPosition).info.activities.length; + } else { + return 0; + } + } + + + public View getChildView(int groupPosition, int childPosition, boolean isLastChild, + View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = View.inflate(mParent, android.R.layout.simple_list_item_1, null); + convertView.setPadding(groupPadding, 0, 0, 0); + + } + TextView textView = (TextView)convertView.findViewById(android.R.id.text1); + textView.setText(getChild(groupPosition, childPosition).replaceFirst(allList.get(groupPosition).info.packageName + ".", "")); + return convertView; + } + + public GroupInfo getGroup(int groupPosition) { + return allList.get(groupPosition); + } + + public int getGroupCount() { + return allList.size(); + } + + public long getGroupId(int groupPosition) { + return groupPosition; + } + + public View getGroupView(int groupPosition, boolean isExpanded, View convertView, + ViewGroup parent) { + if (convertView == null) { + convertView = View.inflate(mParent, android.R.layout.simple_list_item_1, null); + convertView.setPadding(70, 0, 0, 0); + } + TextView textView = (TextView)convertView.findViewById(android.R.id.text1); + textView.setText(getGroup(groupPosition).label.toString()); + return convertView; + } + + public boolean isChildSelectable(int groupPosition, int childPosition) { + return true; + } + + public boolean hasStableIds() { + return true; + } + + } + + private void completeSetCustomApp(Intent data) { + mListener.shortcutPicked(data.toUri(0), getFriendlyActivityName(data, false), true); + } + + private void completeSetCustomShortcut(Intent data) { + Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT); + /* preserve shortcut name, we want to restore it later */ + intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME)); + String appUri = intent.toUri(0); + appUri = appUri.replaceAll("com.android.contacts.action.QUICK_CONTACT", "android.intent.action.VIEW"); + mListener.shortcutPicked(appUri, getFriendlyShortcutName(intent), false); + } + + private String getFriendlyActivityName(Intent intent, boolean labelOnly) { + ActivityInfo ai = intent.resolveActivityInfo(mPackageManager, PackageManager.GET_ACTIVITIES); + String friendlyName = null; + if (ai != null) { + friendlyName = ai.loadLabel(mPackageManager).toString(); + if (friendlyName == null && !labelOnly) { + friendlyName = ai.name; + } + } + return friendlyName != null || labelOnly ? friendlyName : intent.toUri(0); + } + + private String getFriendlyShortcutName(Intent intent) { + String activityName = getFriendlyActivityName(intent, true); + String name = intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); + + if (activityName != null && name != null) { + return activityName + ": " + name; + } + return name != null ? name : intent.toUri(0); + } + + public String getFriendlyNameForUri(String uri) { + if (uri == null) { + return null; + } + + try { + Intent intent = Intent.parseUri(uri, 0); + if (Intent.ACTION_MAIN.equals(intent.getAction())) { + return getFriendlyActivityName(intent, false); + } + return getFriendlyShortcutName(intent); + } catch (URISyntaxException e) { + } + + return uri; + } +}
\ No newline at end of file diff --git a/src/com/android/settings/cyanogenmod/SpamList.java b/src/com/android/settings/cyanogenmod/SpamList.java new file mode 100644 index 0000000..b1a24a8 --- /dev/null +++ b/src/com/android/settings/cyanogenmod/SpamList.java @@ -0,0 +1,307 @@ +package com.android.settings.cyanogenmod; + +import java.util.ArrayList; +import java.util.List; + +import android.app.AlertDialog; +import android.app.ListFragment; +import android.content.ContentResolver; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.database.ContentObserver; +import android.database.Cursor; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.text.TextUtils; +import android.text.format.DateUtils; +import android.view.Gravity; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; + +import com.android.internal.util.cm.SpamFilter; +import static com.android.internal.util.cm.SpamFilter.*; +import com.android.settings.R; +import com.android.settings.Settings; +import com.android.settings.Settings.NotificationStationActivity; + +public class SpamList extends ListFragment { + + private static final int MENU_NOTIFICATIONS = Menu.FIRST; + private static final Uri PACKAGES_URI; + private static final Uri MESSAGES_URI; + + static { + Uri.Builder builder = new Uri.Builder(); + builder.scheme(ContentResolver.SCHEME_CONTENT); + builder.authority(SpamFilter.AUTHORITY); + builder.encodedPath("packages"); + PACKAGES_URI = builder.build(); + + MESSAGES_URI = builder + .encodedPath("messages") + .build(); + } + + private SpamAdapter mAdapter; + private FetchFilters mTask; + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + mTask = new FetchFilters(); + mTask.execute(); + getListView().setDividerHeight(0); + addEmptyView(); + setHasOptionsMenu(true); + getActivity().getContentResolver().registerContentObserver( + SpamFilter.NOTIFICATION_URI, true, mObserver); + } + + private void addEmptyView() { + TextView v = new TextView(getActivity()); + v.setText(R.string.no_filters_title); + v.setGravity(Gravity.CENTER); + LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + getActivity().addContentView(v, params); + getListView().setEmptyView(v); + } + + @Override + public void onListItemClick(ListView l, View v, final int position, long id) { + if (mAdapter.getItemViewType(position) == SpamAdapter.HEADER_TYPE) { + return; + } + NotificationInfo info = (NotificationInfo) mAdapter.getItem(position); + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setTitle(info.appLabel); + int baseTitleId = info.count == 0 ? R.string.spam_added_title : R.string.spam_last_blocked_title; + String baseTitle = getActivity().getString(baseTitleId); + StringBuilder msg = new StringBuilder(); + msg.append(String.format(baseTitle, DateUtils.getRelativeTimeSpanString(info.date))).append("\n\n"); + msg.append(getActivity().getString(R.string.app_ops_ignored_count, info.count)); + builder.setMessage(msg.toString()); + builder.setPositiveButton(android.R.string.ok, null); + builder.setNeutralButton(R.string.blacklist_button_delete, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mAdapter.removeItem(position); + dialog.dismiss(); + } + }); + AlertDialog dialog = builder.show(); + TextView textView = (TextView) dialog.findViewById(android.R.id.message); + textView.setTextSize(17); + } + + private ContentObserver mObserver = new ContentObserver(null) { + @Override + public void onChange(boolean selfChange) { + if (mTask != null) { + mTask.cancel(true); + } + mTask = new FetchFilters(); + mTask.execute(); + } + }; + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + menu.add(0, MENU_NOTIFICATIONS, 0, R.string.volume_notification_description) + .setIcon(R.drawable.ic_filter_notifications) + .setAlphabeticShortcut('n') + .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case MENU_NOTIFICATIONS: + Intent i = new Intent(getActivity(), NotificationStationActivity.class); + startActivity(i); + return true; + default: + return false; + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + getActivity().getContentResolver().unregisterContentObserver(mObserver); + } + + private static class ItemInfo { + String id; + } + + private static final class PackageInfo extends ItemInfo { + String packageName; + CharSequence applicationLabel; + } + + private static final class NotificationInfo extends ItemInfo { + String messageText; + CharSequence appLabel; + long date; + int count; + } + + private class FetchFilters extends AsyncTask<Void, Void, List<ItemInfo>> { + + private void addNotificationsForPackage(PackageInfo pInfo, List<ItemInfo> items) { + String selection = SpamContract.NotificationTable.PACKAGE_ID + "=?"; + String[] selectionArgs = new String[] {pInfo.id}; + Cursor c = getActivity().getContentResolver().query(MESSAGES_URI, null, selection, + selectionArgs, null); + if (c != null) { + int notificationIdIndex = c.getColumnIndex(SpamContract.NotificationTable.ID); + int notificationMessageIndex = c.getColumnIndex(SpamContract.NotificationTable.MESSAGE_TEXT); + int notificationBlockedIndex = c.getColumnIndex(SpamContract.NotificationTable.LAST_BLOCKED); + int notificationCountIndex = c.getColumnIndex(SpamContract.NotificationTable.COUNT); + while (c.moveToNext()) { + NotificationInfo nInfo = new NotificationInfo(); + nInfo.messageText = c.getString(notificationMessageIndex); + nInfo.id = c.getString(notificationIdIndex); + nInfo.date = c.getLong(notificationBlockedIndex); + nInfo.count = c.getInt(notificationCountIndex); + nInfo.appLabel = pInfo.applicationLabel; + items.add(nInfo); + } + c.close(); + } + } + + @Override + protected List<ItemInfo> doInBackground(Void... params) { + List<ItemInfo> items = new ArrayList<ItemInfo>(); + Cursor c = getActivity().getContentResolver().query( + PACKAGES_URI, null, null, null, null); + if (c != null) { + int packageIdIndex = c.getColumnIndex(SpamContract.PackageTable.ID); + int packageNameIndex = c.getColumnIndex(SpamContract.PackageTable.PACKAGE_NAME); + while (c.moveToNext()) { + PackageInfo pInfo = new PackageInfo(); + pInfo.packageName = c.getString(packageNameIndex); + getAppInfo(pInfo); + pInfo.id = c.getString(packageIdIndex); + items.add(pInfo); + addNotificationsForPackage(pInfo, items); + } + c.close(); + } + return items; + } + + private void getAppInfo(PackageInfo info) { + ApplicationInfo appInfo = null; + PackageManager pm = getActivity().getPackageManager(); + try { + appInfo = pm.getApplicationInfo(info.packageName, 0); + info.applicationLabel = appInfo.loadLabel(pm); + } catch (PackageManager.NameNotFoundException e) { + info.applicationLabel = info.packageName; + } + } + + @Override + protected void onPostExecute(List<ItemInfo> result) { + mAdapter = new SpamAdapter(result); + setListAdapter(mAdapter); + mTask = null; + } + } + + private class SpamAdapter extends BaseAdapter { + + private static final int HEADER_TYPE = 0; + private static final int ENTRY_TYPE = 1; + private List<ItemInfo> mItems; + + SpamAdapter(List<ItemInfo> items) { + mItems = items; + } + + @Override + public int getCount() { + return mItems.size(); + } + + @Override + public ItemInfo getItem(int position) { + return mItems.get(position); + } + + @Override + public long getItemId(int position) { + return 0; + } + + @Override + public int getItemViewType(int position) { + return getItem(position) instanceof + PackageInfo ? HEADER_TYPE : ENTRY_TYPE; + } + + @Override + public int getViewTypeCount() { + return 2; + } + + public void removeItem(int position) { + ItemInfo item = mItems.get(position); + Uri.Builder builder = new Uri.Builder(); + builder.scheme(ContentResolver.SCHEME_CONTENT); + builder.authority(SpamFilter.AUTHORITY); + builder.encodedPath(SpamFilter.MESSAGE_PATH); + builder.appendEncodedPath(((NotificationInfo) item).id); + getActivity().getContentResolver().delete(builder.build(), null, null); + notifyDataSetChanged(); + } + + @Override + public boolean areAllItemsEnabled() { + return false; + } + + @Override + public boolean isEnabled(int position) { + return getItemViewType(position) == ENTRY_TYPE; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + int viewType = getItemViewType(position); + TextView titleView = null; + ItemInfo info = getItem(position); + String text; + if (viewType == HEADER_TYPE) { + if (convertView == null) { + convertView = new TextView(getActivity(), null, + android.R.attr.listSeparatorTextViewStyle); + } + titleView = (TextView) convertView; + text = (String) ((PackageInfo) info).applicationLabel; + } else { + if (convertView == null) { + convertView = View.inflate(getActivity(), R.layout.item_row, null); + } + titleView = ((TextView) convertView.findViewById(R.id.label)); + text = ((NotificationInfo) info).messageText; + } + titleView.setText(text); + return convertView; + } + } +} diff --git a/src/com/android/settings/cyanogenmod/StatusBarSettings.java b/src/com/android/settings/cyanogenmod/StatusBarSettings.java new file mode 100644 index 0000000..4b37558 --- /dev/null +++ b/src/com/android/settings/cyanogenmod/StatusBarSettings.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2014-2015 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.cyanogenmod; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.os.Bundle; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceChangeListener; +import android.provider.SearchIndexableResource; +import android.provider.Settings; +import android.telephony.TelephonyManager; +import android.text.format.DateFormat; +import android.view.View; + +import com.android.internal.logging.MetricsLogger; +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.Utils; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settings.search.Indexable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import cyanogenmod.providers.CMSettings; + +public class StatusBarSettings extends SettingsPreferenceFragment + implements OnPreferenceChangeListener, Indexable { + + private static final String TAG = "StatusBar"; + + private static final String STATUS_BAR_CLOCK_STYLE = "status_bar_clock"; + private static final String STATUS_BAR_AM_PM = "status_bar_am_pm"; + private static final String STATUS_BAR_BATTERY_STYLE = "status_bar_battery_style"; + private static final String STATUS_BAR_SHOW_BATTERY_PERCENT = "status_bar_show_battery_percent"; + private static final String STATUS_BAR_QUICK_QS_PULLDOWN = "qs_quick_pulldown"; + + private static final int STATUS_BAR_BATTERY_STYLE_HIDDEN = 4; + private static final int STATUS_BAR_BATTERY_STYLE_TEXT = 6; + + private ListPreference mStatusBarClock; + private ListPreference mStatusBarAmPm; + private ListPreference mStatusBarBattery; + private ListPreference mStatusBarBatteryShowPercent; + private ListPreference mQuickPulldown; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + addPreferencesFromResource(R.xml.status_bar_settings); + + ContentResolver resolver = getActivity().getContentResolver(); + + mStatusBarClock = (ListPreference) findPreference(STATUS_BAR_CLOCK_STYLE); + mStatusBarAmPm = (ListPreference) findPreference(STATUS_BAR_AM_PM); + mStatusBarBattery = (ListPreference) findPreference(STATUS_BAR_BATTERY_STYLE); + mStatusBarBatteryShowPercent = + (ListPreference) findPreference(STATUS_BAR_SHOW_BATTERY_PERCENT); + mQuickPulldown = (ListPreference) findPreference(STATUS_BAR_QUICK_QS_PULLDOWN); + + int clockStyle = CMSettings.System.getInt(resolver, + CMSettings.System.STATUS_BAR_CLOCK, 1); + mStatusBarClock.setValue(String.valueOf(clockStyle)); + mStatusBarClock.setSummary(mStatusBarClock.getEntry()); + mStatusBarClock.setOnPreferenceChangeListener(this); + + if (DateFormat.is24HourFormat(getActivity())) { + mStatusBarAmPm.setEnabled(false); + mStatusBarAmPm.setSummary(R.string.status_bar_am_pm_info); + } else { + int statusBarAmPm = CMSettings.System.getInt(resolver, + CMSettings.System.STATUS_BAR_AM_PM, 2); + mStatusBarAmPm.setValue(String.valueOf(statusBarAmPm)); + mStatusBarAmPm.setSummary(mStatusBarAmPm.getEntry()); + mStatusBarAmPm.setOnPreferenceChangeListener(this); + } + + int batteryStyle = CMSettings.System.getInt(resolver, + CMSettings.System.STATUS_BAR_BATTERY_STYLE, 0); + mStatusBarBattery.setValue(String.valueOf(batteryStyle)); + mStatusBarBattery.setSummary(mStatusBarBattery.getEntry()); + mStatusBarBattery.setOnPreferenceChangeListener(this); + + int batteryShowPercent = CMSettings.System.getInt(resolver, + CMSettings.System.STATUS_BAR_SHOW_BATTERY_PERCENT, 0); + mStatusBarBatteryShowPercent.setValue(String.valueOf(batteryShowPercent)); + mStatusBarBatteryShowPercent.setSummary(mStatusBarBatteryShowPercent.getEntry()); + enableStatusBarBatteryDependents(batteryStyle); + mStatusBarBatteryShowPercent.setOnPreferenceChangeListener(this); + + int quickPulldown = CMSettings.System.getInt(resolver, + CMSettings.System.STATUS_BAR_QUICK_QS_PULLDOWN, 1); + mQuickPulldown.setValue(String.valueOf(quickPulldown)); + updatePulldownSummary(quickPulldown); + mQuickPulldown.setOnPreferenceChangeListener(this); + } + + @Override + protected int getMetricsCategory() { + // todo add a constant in MetricsLogger.java + return MetricsLogger.MAIN_SETTINGS; + } + + @Override + public void onResume() { + super.onResume(); + // Adjust clock position for RTL if necessary + Configuration config = getResources().getConfiguration(); + if (config.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { + mStatusBarClock.setEntries(getActivity().getResources().getStringArray( + R.array.status_bar_clock_style_entries_rtl)); + mStatusBarClock.setSummary(mStatusBarClock.getEntry()); + } + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + ContentResolver resolver = getActivity().getContentResolver(); + if (preference == mStatusBarClock) { + int clockStyle = Integer.parseInt((String) newValue); + int index = mStatusBarClock.findIndexOfValue((String) newValue); + CMSettings.System.putInt( + resolver, CMSettings.System.STATUS_BAR_CLOCK, clockStyle); + mStatusBarClock.setSummary(mStatusBarClock.getEntries()[index]); + return true; + } else if (preference == mStatusBarAmPm) { + int statusBarAmPm = Integer.valueOf((String) newValue); + int index = mStatusBarAmPm.findIndexOfValue((String) newValue); + CMSettings.System.putInt( + resolver, CMSettings.System.STATUS_BAR_AM_PM, statusBarAmPm); + mStatusBarAmPm.setSummary(mStatusBarAmPm.getEntries()[index]); + return true; + } else if (preference == mStatusBarBattery) { + int batteryStyle = Integer.valueOf((String) newValue); + int index = mStatusBarBattery.findIndexOfValue((String) newValue); + CMSettings.System.putInt( + resolver, CMSettings.System.STATUS_BAR_BATTERY_STYLE, batteryStyle); + mStatusBarBattery.setSummary(mStatusBarBattery.getEntries()[index]); + enableStatusBarBatteryDependents(batteryStyle); + return true; + } else if (preference == mStatusBarBatteryShowPercent) { + int batteryShowPercent = Integer.valueOf((String) newValue); + int index = mStatusBarBatteryShowPercent.findIndexOfValue((String) newValue); + CMSettings.System.putInt( + resolver, CMSettings.System.STATUS_BAR_SHOW_BATTERY_PERCENT, batteryShowPercent); + mStatusBarBatteryShowPercent.setSummary( + mStatusBarBatteryShowPercent.getEntries()[index]); + return true; + } else if (preference == mQuickPulldown) { + int quickPulldown = Integer.valueOf((String) newValue); + CMSettings.System.putInt( + resolver, CMSettings.System.STATUS_BAR_QUICK_QS_PULLDOWN, quickPulldown); + updatePulldownSummary(quickPulldown); + return true; + } + return false; + } + + private void enableStatusBarBatteryDependents(int batteryIconStyle) { + if (batteryIconStyle == STATUS_BAR_BATTERY_STYLE_HIDDEN || + batteryIconStyle == STATUS_BAR_BATTERY_STYLE_TEXT) { + mStatusBarBatteryShowPercent.setEnabled(false); + } else { + mStatusBarBatteryShowPercent.setEnabled(true); + } + } + + private void updatePulldownSummary(int value) { + Resources res = getResources(); + + if (value == 0) { + // quick pulldown deactivated + mQuickPulldown.setSummary(res.getString(R.string.status_bar_quick_qs_pulldown_off)); + } else { + String direction = res.getString(value == 2 + ? R.string.status_bar_quick_qs_pulldown_summary_left + : R.string.status_bar_quick_qs_pulldown_summary_right); + mQuickPulldown.setSummary(res.getString(R.string.status_bar_quick_qs_pulldown_summary, direction)); + } + } + + public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider() { + @Override + public List<SearchIndexableResource> getXmlResourcesToIndex(Context context, + boolean enabled) { + ArrayList<SearchIndexableResource> result = + new ArrayList<SearchIndexableResource>(); + + SearchIndexableResource sir = new SearchIndexableResource(context); + sir.xmlResId = R.xml.status_bar_settings; + result.add(sir); + + return result; + } + + @Override + public List<String> getNonIndexableKeys(Context context) { + ArrayList<String> result = new ArrayList<String>(); + return result; + } + }; +} diff --git a/src/com/android/settings/cyanogenmod/SystemSettingSwitchPreference.java b/src/com/android/settings/cyanogenmod/SystemSettingSwitchPreference.java new file mode 100644 index 0000000..a936149 --- /dev/null +++ b/src/com/android/settings/cyanogenmod/SystemSettingSwitchPreference.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2013 The CyanogenMod project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.cyanogenmod; + +import android.content.Context; +import android.preference.SwitchPreference; +import android.provider.Settings; +import android.util.AttributeSet; + +public class SystemSettingSwitchPreference extends SwitchPreference { + public SystemSettingSwitchPreference(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public SystemSettingSwitchPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public SystemSettingSwitchPreference(Context context) { + super(context, null); + } + + @Override + protected boolean persistBoolean(boolean value) { + if (shouldPersist()) { + if (value == getPersistedBoolean(!value)) { + // It's already there, so the same as persisting + return true; + } + Settings.System.putInt(getContext().getContentResolver(), getKey(), value ? 1 : 0); + return true; + } + return false; + } + + @Override + protected boolean getPersistedBoolean(boolean defaultReturnValue) { + if (!shouldPersist()) { + return defaultReturnValue; + } + return Settings.System.getInt(getContext().getContentResolver(), + getKey(), defaultReturnValue ? 1 : 0) != 0; + } + + @Override + protected boolean isPersisted() { + // Using getString instead of getInt so we can simply check for null + // instead of catching an exception. (All values are stored as strings.) + return Settings.System.getString(getContext().getContentResolver(), getKey()) != null; + } +} diff --git a/src/com/android/settings/cyanogenmod/WeatherServiceSettings.java b/src/com/android/settings/cyanogenmod/WeatherServiceSettings.java new file mode 100644 index 0000000..903af8b --- /dev/null +++ b/src/com/android/settings/cyanogenmod/WeatherServiceSettings.java @@ -0,0 +1,468 @@ +/* + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.cyanogenmod; + +import android.app.Activity; +import android.content.ActivityNotFoundException; +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.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.UserHandle; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.PreferenceCategory; +import android.preference.PreferenceScreen; +import android.util.AttributeSet; +import android.util.Log; +import android.util.Xml; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ListView; +import android.widget.RadioButton; +import android.widget.TextView; +import android.widget.Toast; +import com.android.internal.content.PackageMonitor; +import com.android.internal.os.BackgroundThread; +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.Utils; +import cyanogenmod.providers.CMSettings; +import cyanogenmod.providers.WeatherContract; +import cyanogenmod.weather.CMWeatherManager; +import cyanogenmod.weatherservice.WeatherProviderService; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import static org.cyanogenmod.internal.logging.CMMetricsLogger.WEATHER_SETTINGS; + +public class WeatherServiceSettings extends SettingsPreferenceFragment + implements Preference.OnPreferenceChangeListener { + + private Context mContext; + private Handler mHandler; + private static final String TAG = WeatherServiceSettings.class.getSimpleName(); + + private static final String PREFERENCE_GENERAL = "weather_general_settings"; + private static final String PREFERENCE_PROVIDERS = "weather_service_providers"; + private static final String PREFERENCE_TEMP_UNIT = "weather_temperature_unit"; + + private PreferenceCategory mGeneralSettingsCategory; + private PreferenceCategory mProvidersCategory; + private ListPreference mTemperatureUnit; + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + mContext = activity; + mHandler = new Handler(mContext.getMainLooper()); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.weather_settings); + + final PreferenceScreen ps = getPreferenceScreen(); + mGeneralSettingsCategory = (PreferenceCategory) ps.findPreference(PREFERENCE_GENERAL); + mProvidersCategory = (PreferenceCategory) ps.findPreference(PREFERENCE_PROVIDERS); + mTemperatureUnit = (ListPreference) ps.findPreference(PREFERENCE_TEMP_UNIT); + mTemperatureUnit.setOnPreferenceChangeListener(this); + } + + @Override + protected int getMetricsCategory() { + return WEATHER_SETTINGS; + } + + @Override + public void onResume() { + super.onResume(); + updateAdapter(); + registerPackageMonitor(); + + mTemperatureUnit.setValue(String.valueOf(getSelectedTemperatureUnit(mContext))); + } + + @Override + public void onPause() { + super.onPause(); + unregisterPackageMonitor(); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (preference == mTemperatureUnit) { + CMSettings.Global.putInt(mContext.getContentResolver(), + CMSettings.Global.WEATHER_TEMPERATURE_UNIT, + Integer.valueOf((String) newValue)); + } + return true; + } + + private void registerPackageMonitor() { + mPackageMonitor.register(mContext, BackgroundThread.getHandler().getLooper(), + UserHandle.ALL, true); + } + + private void launchGetWeatherProviders() { + try { + startActivity(new Intent(Intent.ACTION_VIEW, + Uri.parse(getString(R.string.weather_settings_play_store_market_url))) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + } catch (ActivityNotFoundException e) { + startActivity(new Intent(Intent.ACTION_VIEW, + Uri.parse(getString(R.string.weather_settings_play_store_http_url))) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + } + } + + private void unregisterPackageMonitor() { + mPackageMonitor.unregister(); + } + + private PackageMonitor mPackageMonitor = new PackageMonitor() { + @Override + public void onPackageAdded(String packageName, int uid) { + mHandler.post(new Runnable() { + @Override + public void run() { + updateAdapter(); + } + }); + } + + @Override + public void onPackageRemoved(String packageName, int uid) { + mHandler.post(new Runnable() { + @Override + public void run() { + updateAdapter(); + } + }); + } + }; + + private void updateAdapter() { + final PackageManager pm = getContext().getPackageManager(); + final Intent intent = new Intent(WeatherProviderService.SERVICE_INTERFACE); + List<ResolveInfo> resolveInfoList = pm.queryIntentServices(intent, + PackageManager.GET_SERVICES | PackageManager.GET_META_DATA); + List<WeatherProviderServiceInfo> weatherProviderServiceInfos + = new ArrayList<>(resolveInfoList.size()); + ComponentName activeService = getEnabledWeatherServiceProvider(); + for (ResolveInfo resolveInfo : resolveInfoList) { + if (resolveInfo.serviceInfo == null) continue; + + if (resolveInfo.serviceInfo.packageName == null + || resolveInfo.serviceInfo.name == null) { + //Really? + continue; + } + + if (!resolveInfo.serviceInfo.permission.equals( + cyanogenmod.platform.Manifest.permission.BIND_WEATHER_PROVIDER_SERVICE)) { + continue; + } + WeatherProviderServiceInfo serviceInfo = new WeatherProviderServiceInfo(); + serviceInfo.componentName = new ComponentName(resolveInfo.serviceInfo.packageName, + resolveInfo.serviceInfo.name); + serviceInfo.isActive = serviceInfo.componentName.equals(activeService); + serviceInfo.caption = resolveInfo.loadLabel(pm); + serviceInfo.icon = resolveInfo.loadIcon(pm); + serviceInfo.settingsComponentName = getSettingsComponent(pm, resolveInfo); + + weatherProviderServiceInfos.add(serviceInfo); + } + + final PreferenceScreen ps = getPreferenceScreen(); + if (!weatherProviderServiceInfos.isEmpty()) { + if (ps.findPreference(PREFERENCE_GENERAL) == null) { + ps.addPreference(mGeneralSettingsCategory); + } + if (ps.findPreference(PREFERENCE_PROVIDERS) == null) { + ps.addPreference(mProvidersCategory); + } + + mProvidersCategory.removeAll(); + for (WeatherProviderServiceInfo info : weatherProviderServiceInfos) { + mProvidersCategory.addPreference(new WeatherProviderPreference(mContext, info)); + } + + Preference addServicePreference = new Preference(mContext); + addServicePreference.setTitle(R.string.weather_settings_add_weather_provider); + addServicePreference.setIcon(R.drawable.ic_add); + addServicePreference.setOnPreferenceClickListener( + new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + launchGetWeatherProviders(); + return false; + } + }); + mProvidersCategory.addPreference(addServicePreference); + + } else { + ps.removePreference(mGeneralSettingsCategory); + ps.removePreference(mProvidersCategory); + } + + } + + /** + * Gets the currently selected temperature unit. + * If none is selected yet, returns a unit appropriate for the current locale + */ + public static int getSelectedTemperatureUnit(Context context) { + int tempUnit = CMSettings.Global.getInt(context.getContentResolver(), + CMSettings.Global.WEATHER_TEMPERATURE_UNIT, -1); + if (tempUnit != -1) { + return tempUnit; + } + + Locale locale = context.getResources().getConfiguration().locale; + boolean useFahrenheit = locale.equals(Locale.US) + || locale.toString().equals("ms_MY") // Malaysia + || locale.toString().equals("si_LK"); // Sri Lanka + return useFahrenheit + ? WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT + : WeatherContract.WeatherColumns.TempUnit.CELSIUS; + } + + private static class WeatherProviderPreference extends Preference + implements View.OnClickListener { + private WeatherProviderServiceInfo mInfo; + private View mView; + private RadioButton mRadioButton; + private View mSettingsButton; + private Context mContext; + + public WeatherProviderPreference(Context context, WeatherProviderServiceInfo info) { + super(context); + mInfo = info; + mContext = context; + + setLayoutResource(R.layout.weather_service_provider_info_row); + setTitle(mInfo.caption); + setIcon(mInfo.icon); + } + + @Override + protected void onBindView(final View view) { + super.onBindView(view); + mView = view; + mView.setOnClickListener(this); + + mRadioButton = (RadioButton) view.findViewById(R.id.radio); + mRadioButton.setChecked(mInfo.isActive); + mRadioButton.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + view.onTouchEvent(event); + return false; + } + }); + + boolean showSettings = mInfo.settingsComponentName != null; + View settingsDivider = view.findViewById(R.id.divider); + settingsDivider.setVisibility(showSettings ? View.VISIBLE : View.INVISIBLE); + + mSettingsButton = view.findViewById(R.id.settings); + mSettingsButton.setVisibility(showSettings ? View.VISIBLE : View.INVISIBLE); + mSettingsButton.setAlpha(mInfo.isActive ? 1f : Utils.DISABLED_ALPHA); + mSettingsButton.setEnabled(mInfo.isActive); + mSettingsButton.setFocusable(mInfo.isActive); + mSettingsButton.setOnClickListener(this); + } + + @Override + public void onClick(View v) { + if (v == mView) { + v.setPressed(true); + setActiveWeatherProviderService(); + } + launchSettingsActivity(mInfo); + } + + private boolean isActiveProvider() { + return mInfo.isActive; + } + + public void setActiveState(boolean active) { + mInfo.isActive = active; + mRadioButton.setChecked(active); + + boolean hasSettings = mInfo.settingsComponentName != null; + if (hasSettings) { + mSettingsButton.setAlpha(mInfo.isActive ? 1f : Utils.DISABLED_ALPHA); + mSettingsButton.setEnabled(mInfo.isActive); + mSettingsButton.setFocusable(mInfo.isActive); + } + } + + private void launchSettingsActivity(WeatherProviderServiceInfo info) { + if (info != null && info.settingsComponentName != null) { + try { + mContext.startActivity(new Intent().setComponent(info.settingsComponentName)); + } catch (ActivityNotFoundException e) { + Toast.makeText(mContext, + R.string.weather_settings_activity_not_found, + Toast.LENGTH_LONG) + .show(); + Log.w(TAG, info.settingsComponentName + " not found"); + } + } + } + + private void setActiveWeatherProviderService() { + if (!mInfo.isActive) { + markAsActiveProvider(); + CMSettings.Secure.putString(mContext.getContentResolver(), + CMSettings.Secure.WEATHER_PROVIDER_SERVICE, + mInfo.componentName.flattenToString()); + } + } + + private void markAsActiveProvider() { + // Check for current active provider + PreferenceCategory providersCategory = (PreferenceCategory) findPreferenceInHierarchy( + WeatherServiceSettings.PREFERENCE_PROVIDERS); + if (providersCategory != null) { + final int count = providersCategory.getPreferenceCount(); + for (int index = 0; index < count; index++) { + Preference p = providersCategory.getPreference(index); + if (p instanceof WeatherProviderPreference) { + WeatherProviderPreference preference = (WeatherProviderPreference) p; + if (preference.isActiveProvider()) { + preference.setActiveState(false); + break; + } + } + } + } + // Mark this provider as active + setActiveState(true); + } + } + + private ComponentName getSettingsComponent(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, + WeatherProviderService.SERVICE_META_DATA); + if (parser == null) { + Log.w(TAG, "Can't find " + WeatherProviderService.SERVICE_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 (!"weather-provider-service".equals(nodeName)) { + Log.w(TAG, "Meta-data does not start with weather-provider-service tag"); + return null; + } + //Will use Dream styleable for now, it has the attribute we need + TypedArray sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.Dream); + cn = sa.getString(com.android.internal.R.styleable.Dream_settingsActivity); + sa.recycle(); + } catch (PackageManager.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; + } + if (cn != null && cn.indexOf('/') < 0) { + cn = resolveInfo.serviceInfo.packageName + "/" + cn; + } + return cn == null ? null : ComponentName.unflattenFromString(cn); + } + + private ComponentName getEnabledWeatherServiceProvider() { + String activeWeatherServiceProvider = CMSettings.Secure.getString( + mContext.getContentResolver(), CMSettings.Secure.WEATHER_PROVIDER_SERVICE); + if (activeWeatherServiceProvider == null) return null; + return ComponentName.unflattenFromString(activeWeatherServiceProvider); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + ViewGroup contentRoot = (ViewGroup) getListView().getParent(); + View emptyView = getActivity().getLayoutInflater().inflate( + R.layout.empty_weather_state, contentRoot, false); + TextView emptyTextView = (TextView) emptyView.findViewById(R.id.message); + emptyTextView.setText(R.string.weather_settings_no_services_prompt); + + Button addProviderButton = (Button) emptyView.findViewById(R.id.add_weather_provider); + addProviderButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + launchGetWeatherProviders(); + } + }); + + contentRoot.addView(emptyView); + + ListView listView = getListView(); + listView.setEmptyView(emptyView); + } + + private class WeatherProviderServiceInfo { + CharSequence caption; + Drawable icon; + boolean isActive; + ComponentName componentName; + public ComponentName settingsComponentName; + } +} diff --git a/src/com/android/settings/dashboard/DashboardSummary.java b/src/com/android/settings/dashboard/DashboardSummary.java index 6408636..ebd32c1 100644 --- a/src/com/android/settings/dashboard/DashboardSummary.java +++ b/src/com/android/settings/dashboard/DashboardSummary.java @@ -35,6 +35,7 @@ import android.view.MenuInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; +import android.widget.Switch; import android.widget.TextView; import com.android.internal.logging.MetricsLogger; @@ -42,6 +43,8 @@ import com.android.settings.HelpUtils; import com.android.settings.InstrumentedFragment; import com.android.settings.R; import com.android.settings.SettingsActivity; +import com.android.settings.Utils; +import com.android.settings.widget.SwitchBar; import java.util.List; @@ -103,6 +106,10 @@ public class DashboardSummary extends InstrumentedFragment { filter.addAction(Intent.ACTION_PACKAGE_REPLACED); filter.addDataScheme("package"); getActivity().registerReceiver(mHomePackageReceiver, filter); + + final IntentFilter airplaneModeFilter + = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED); + getActivity().registerReceiver(mHomePackageReceiver, airplaneModeFilter); } @Override @@ -158,10 +165,14 @@ public class DashboardSummary extends InstrumentedFragment { DashboardTileView tileView = new DashboardTileView(context); updateTileView(context, res, tile, tileView.getImageView(), - tileView.getTitleTextView(), tileView.getStatusTextView()); + tileView.getTitleTextView(), tileView.getStatusTextView(), + tileView.getSwitchView()); tileView.setTile(tile); + if (tile.id == R.id.mobile_networks) { + tileView.setEnabledTile(!Utils.isAirplaneModeEnabled(context)); + } categoryContent.addView(tileView); } @@ -173,7 +184,7 @@ public class DashboardSummary extends InstrumentedFragment { } private void updateTileView(Context context, Resources res, DashboardTile tile, - ImageView tileIcon, TextView tileTextView, TextView statusTextView) { + ImageView tileIcon, TextView tileTextView, TextView statusTextView, Switch switchBar) { if (!TextUtils.isEmpty(tile.iconPkg)) { try { @@ -181,10 +192,16 @@ public class DashboardSummary extends InstrumentedFragment { .getResourcesForApplication(tile.iconPkg).getDrawable(tile.iconRes, null); if (!tile.iconPkg.equals(context.getPackageName()) && drawable != null) { // If this drawable is coming from outside Settings, tint it to match the color. - TypedValue tintColor = new TypedValue(); - context.getTheme().resolveAttribute(com.android.internal.R.attr.colorAccent, - tintColor, true); - drawable.setTint(tintColor.data); + TypedValue tintColorValue = new TypedValue(); + context.getResources().getValue(R.color.external_tile_icon_tint_color, + tintColorValue, true); + // If tintColorValue is TYPE_ATTRIBUTE, resolve it + if (tintColorValue.type == TypedValue.TYPE_ATTRIBUTE) { + context.getTheme().resolveAttribute(tintColorValue.data, + tintColorValue, true); + } + drawable.setTintMode(android.graphics.PorterDuff.Mode.SRC_ATOP); + drawable.setTint(tintColorValue.data); } tileIcon.setImageDrawable(drawable); } catch (NameNotFoundException | Resources.NotFoundException e) { @@ -207,6 +224,12 @@ public class DashboardSummary extends InstrumentedFragment { } else { statusTextView.setVisibility(View.GONE); } + + if (tile.switchControl != null) { + switchBar.setVisibility(View.VISIBLE); + } else { + switchBar.setVisibility(View.GONE); + } } private void sendRebuildUI() { diff --git a/src/com/android/settings/dashboard/DashboardTile.java b/src/com/android/settings/dashboard/DashboardTile.java index 5e7e49a..ecb9a48 100644 --- a/src/com/android/settings/dashboard/DashboardTile.java +++ b/src/com/android/settings/dashboard/DashboardTile.java @@ -81,6 +81,13 @@ public class DashboardTile implements Parcelable { public String iconPkg; /** + * Optional location of a class which implements GenericSwitchTile + * to be displayed on the dashboard. + * @attr ref R.styleable#DashbaordTile_switchClass + */ + public String switchControl; + + /** * Full class name of the fragment to display when this tile is * selected. * @attr ref android.R.styleable#PreferenceHeader_fragment @@ -164,6 +171,12 @@ public class DashboardTile implements Parcelable { userHandle.get(i).writeToParcel(dest, flags); } dest.writeBundle(extras); + if (switchControl != null) { + dest.writeInt(1); + dest.writeString(switchControl); + } else { + dest.writeInt(0); + } } public void readFromParcel(Parcel in) { @@ -184,6 +197,9 @@ public class DashboardTile implements Parcelable { userHandle.add(UserHandle.CREATOR.createFromParcel(in)); } extras = in.readBundle(); + if (in.readInt() != 0) { + switchControl = in.readString(); + } } DashboardTile(Parcel in) { diff --git a/src/com/android/settings/dashboard/DashboardTileView.java b/src/com/android/settings/dashboard/DashboardTileView.java index 0896b82..50bfabe 100644 --- a/src/com/android/settings/dashboard/DashboardTileView.java +++ b/src/com/android/settings/dashboard/DashboardTileView.java @@ -23,12 +23,16 @@ import android.view.LayoutInflater; import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; +import android.widget.Switch; import android.widget.TextView; import com.android.settings.ProfileSelectDialog; import com.android.settings.R; import com.android.settings.Utils; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + public class DashboardTileView extends FrameLayout implements View.OnClickListener { private static final int DEFAULT_COL_SPAN = 1; @@ -37,6 +41,8 @@ public class DashboardTileView extends FrameLayout implements View.OnClickListen private TextView mTitleTextView; private TextView mStatusTextView; private View mDivider; + private Switch mSwitch; + private GenericSwitchToggle mSwitchToggle; private int mColSpan = DEFAULT_COL_SPAN; @@ -55,6 +61,7 @@ public class DashboardTileView extends FrameLayout implements View.OnClickListen mTitleTextView = (TextView) view.findViewById(R.id.title); mStatusTextView = (TextView) view.findViewById(R.id.status); mDivider = view.findViewById(R.id.tile_divider); + mSwitch = (Switch) view.findViewById(R.id.dashboard_switch); setOnClickListener(this); setBackgroundResource(R.drawable.dashboard_tile_background); @@ -73,8 +80,41 @@ public class DashboardTileView extends FrameLayout implements View.OnClickListen return mImageView; } + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (mSwitchToggle != null) { + mSwitchToggle.resume(getContext()); + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (mSwitchToggle != null) { + mSwitchToggle.pause(); + } + } + public void setTile(DashboardTile tile) { mTile = tile; + + if (mTile.switchControl != null) { + try { + Class<?> clazz = getClass().getClassLoader().loadClass(mTile.switchControl); + Constructor<?> constructor = clazz.getConstructor(Context.class, Switch.class); + GenericSwitchToggle sw = (GenericSwitchToggle) constructor.newInstance( + getContext(), mSwitch); + mSwitchToggle = sw; + mSwitchToggle.resume(getContext()); + } catch (ClassNotFoundException + | NoSuchMethodException + | InvocationTargetException + | InstantiationException + | IllegalAccessException e) { + e.printStackTrace(); + } + } } public void setDividerVisibility(boolean visible) { @@ -105,4 +145,18 @@ public class DashboardTileView extends FrameLayout implements View.OnClickListen } } } + + public Switch getSwitchView() { + return mSwitch; + } + + public void setEnabledTile(boolean enabled) { + mImageView.setAlpha(enabled ? 1f : Utils.DISABLED_ALPHA); + mTitleTextView.setEnabled(enabled); + mStatusTextView.setEnabled(enabled); + mSwitch.setEnabled(enabled); + mSwitch.setClickable(enabled); + setFocusable(enabled); + setEnabled(enabled); + } } diff --git a/src/com/android/settings/dashboard/GenericSwitchToggle.java b/src/com/android/settings/dashboard/GenericSwitchToggle.java new file mode 100644 index 0000000..95eec5c --- /dev/null +++ b/src/com/android/settings/dashboard/GenericSwitchToggle.java @@ -0,0 +1,92 @@ +package com.android.settings.dashboard; + +import android.content.Context; +import android.widget.CompoundButton; +import android.widget.Switch; +import com.android.settings.widget.SwitchBar; + +public abstract class GenericSwitchToggle implements SwitchBar.OnSwitchChangeListener, + CompoundButton.OnCheckedChangeListener { + + protected Context mContext; + protected Switch mSwitch; + protected SwitchBar mSwitchBar; + + protected boolean mStateMachineEvent; + protected boolean mListeningToOnSwitchChange = false; + + public GenericSwitchToggle(Context context, Switch switch_) { + mContext = context; + mSwitch = switch_; + } + + public GenericSwitchToggle(Context context, SwitchBar switch_) { + mContext = context; + mSwitchBar = switch_; + } + + public void pause() { + if (mListeningToOnSwitchChange) { + if (mSwitchBar != null) { + mSwitchBar.removeOnSwitchChangeListener(this); + } + if (mSwitch != null) { + mSwitch.setOnCheckedChangeListener(null); + } + mListeningToOnSwitchChange = false; + } + } + + public void resume(Context context) { + mContext = context; + + if (!mListeningToOnSwitchChange) { + if (mSwitchBar != null) { + mSwitchBar.addOnSwitchChangeListener(this); + mListeningToOnSwitchChange = true; + } + if (mSwitch != null) { + mSwitch.setOnCheckedChangeListener(this); + mListeningToOnSwitchChange = true; + } + } + } + + public void teardownSwitchBar() { + if (mSwitchBar == null) { + return; + } + if (mListeningToOnSwitchChange) { + mSwitchBar.removeOnSwitchChangeListener(this); + mListeningToOnSwitchChange = false; + } + mSwitchBar.hide(); + } + + protected void setChecked(boolean checked) { + mStateMachineEvent = true; + if (mSwitchBar != null) { + mSwitchBar.setChecked(checked); + } + if (mSwitch != null) { + mSwitch.setChecked(checked); + } + mStateMachineEvent = false; + } + + protected void setEnabled(boolean enabled) { + if (mSwitchBar != null) { + mSwitchBar.setEnabled(enabled); + } + if (mSwitch != null) { + mSwitch.setEnabled(enabled); + } + } + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + onSwitchChanged(mSwitch, isChecked); + } + + public abstract void onSwitchChanged(Switch switchView, boolean isChecked); +} diff --git a/src/com/android/settings/dashboard/MobileNetworksEnabler.java b/src/com/android/settings/dashboard/MobileNetworksEnabler.java new file mode 100644 index 0000000..98dc59d --- /dev/null +++ b/src/com/android/settings/dashboard/MobileNetworksEnabler.java @@ -0,0 +1,138 @@ +/* + * 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.dashboard; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.wifi.SupplicantState; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.os.Handler; +import android.os.Message; +import android.provider.Settings; +import android.telephony.TelephonyManager; +import android.widget.CompoundButton; +import android.widget.Switch; +import android.widget.Toast; + +import com.android.settings.AirplaneModeEnabler; +import com.android.settings.R; +import com.android.settings.WirelessSettings; +import com.android.settings.search.Index; +import com.android.settings.widget.SwitchBar; +import com.android.settings.wifi.WifiSettings; + +import java.util.concurrent.atomic.AtomicBoolean; + +public class MobileNetworksEnabler extends GenericSwitchToggle { + private TelephonyManager mTelephonyManager; + private IntentFilter mIntentFilter; + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (TelephonyManager.ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED.equals(action)) { + updateState(); + } + } + }; + + public MobileNetworksEnabler(Context context, SwitchBar switchBar) { + super(context, switchBar); + + init(); + setupSwitches(); + } + + public MobileNetworksEnabler(Context context, Switch switch_) { + super(context, switch_); + + init(); + setupSwitches(); + } + + private void init() { + mTelephonyManager = (TelephonyManager) + mContext.getSystemService(Context.TELEPHONY_SERVICE); + mIntentFilter = new IntentFilter( + TelephonyManager.ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED); + } + + private void setupSwitches() { + updateState(); + if (mSwitchBar != null) { + mSwitchBar.show(); + } + } + + private void updateState() { + setEnabled(mTelephonyManager.getDataState() != TelephonyManager.DATA_UNKNOWN); + setChecked(mTelephonyManager.getDataEnabled()); + } + + @Override + public void resume(Context context) { + super.resume(context); + mContext.registerReceiver(mReceiver, mIntentFilter); + } + + @Override + public void pause() { + super.pause(); + mContext.unregisterReceiver(mReceiver); + } + + private boolean isAirplaneModeOn() { + return (Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, 0) != 0); + } + + public boolean isRadioAllowed(String type) { + if (!isAirplaneModeOn()) { + return true; + } + // Here we use the same logic in onCreate(). + String toggleable = Settings.Global.getString(mContext.getContentResolver(), + Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS); + return toggleable != null && toggleable.contains(type); + } + + @Override + public void onSwitchChanged(Switch switchView, boolean isChecked) { + //Do nothing if called as a result of a state machine event + if (mStateMachineEvent) { + return; + } + if (isChecked && !isRadioAllowed(Settings.Global.RADIO_CELL)) { + Toast.makeText(mContext, R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show(); + setChecked(false); + return; + } + + mTelephonyManager.setDataEnabled(isChecked); + } + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + super.onCheckedChanged(buttonView, isChecked); + } +} diff --git a/src/com/android/settings/dashboard/SearchResultsSummary.java b/src/com/android/settings/dashboard/SearchResultsSummary.java index ca764b3..9b1b169 100644 --- a/src/com/android/settings/dashboard/SearchResultsSummary.java +++ b/src/com/android/settings/dashboard/SearchResultsSummary.java @@ -236,7 +236,9 @@ public class SearchResultsSummary extends InstrumentedFragment { mShowResults = true; mQuery = cursor.getString(0); - mSearchView.setQuery(mQuery, false); + if (mSearchView != null) { + mSearchView.setQuery(mQuery, false); + } } }); mSuggestionsListView.addHeaderView( diff --git a/src/com/android/settings/deviceinfo/ImeiInformation.java b/src/com/android/settings/deviceinfo/ImeiInformation.java index d82e6c9..e34a84f 100644 --- a/src/com/android/settings/deviceinfo/ImeiInformation.java +++ b/src/com/android/settings/deviceinfo/ImeiInformation.java @@ -16,10 +16,12 @@ package com.android.settings.deviceinfo; import com.android.internal.logging.MetricsLogger; +import com.android.internal.telephony.ConfigResourceUtil; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.PhoneFactory; +import android.app.ActionBar; import android.content.Context; import android.os.Bundle; import android.preference.Preference; @@ -28,6 +30,7 @@ import android.preference.PreferenceScreen; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.text.TextUtils; +import android.view.MenuItem; import com.android.settings.InstrumentedPreferenceActivity; import com.android.settings.R; @@ -42,6 +45,7 @@ public class ImeiInformation extends InstrumentedPreferenceActivity { private SubscriptionManager mSubscriptionManager; private boolean isMultiSIM = false; + private static final int IMEI_14_DIGIT = 14; @Override protected void onCreate(Bundle savedInstanceState) { @@ -50,6 +54,20 @@ public class ImeiInformation extends InstrumentedPreferenceActivity { final TelephonyManager telephonyManager = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE); initPreferenceScreen(telephonyManager.getSimCount()); + + ActionBar actionBar = getActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } + return false; } // Since there are multiple phone for dsds, therefore need to show information for different @@ -65,8 +83,24 @@ public class ImeiInformation extends InstrumentedPreferenceActivity { private void setPreferenceValue(int phoneId) { final Phone phone = PhoneFactory.getPhone(phoneId); + ConfigResourceUtil mConfigResUtil = new ConfigResourceUtil(); + String imeiStr = null; + + boolean enable14DigitImei = false; + try { + enable14DigitImei = mConfigResUtil.getBooleanValue(phone.getContext(), + "config_enable_display_14digit_imei"); + } catch(RuntimeException ex) { + //do Nothing + } if (phone != null) { + imeiStr = phone.getImei(); + if (enable14DigitImei && + imeiStr != null && imeiStr.length() > 14) { + imeiStr = imeiStr.substring(0, IMEI_14_DIGIT); + } + if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) { setSummaryText(KEY_MEID_NUMBER, phone.getMeid()); setSummaryText(KEY_MIN_NUMBER, phone.getCdmaMin()); @@ -81,7 +115,7 @@ public class ImeiInformation extends InstrumentedPreferenceActivity { if (phone.getLteOnCdmaMode() == PhoneConstants.LTE_ON_CDMA_TRUE) { // Show ICC ID and IMEI for LTE device setSummaryText(KEY_ICC_ID, phone.getIccSerialNumber()); - setSummaryText(KEY_IMEI, phone.getImei()); + setSummaryText(KEY_IMEI, imeiStr); } else { // device is not GSM/UMTS, do not display GSM/UMTS features // check Null in case no specified preference in overlay xml @@ -89,7 +123,7 @@ public class ImeiInformation extends InstrumentedPreferenceActivity { removePreferenceFromScreen(KEY_ICC_ID); } } else { - setSummaryText(KEY_IMEI, phone.getImei()); + setSummaryText(KEY_IMEI, imeiStr); setSummaryText(KEY_IMEI_SV, phone.getDeviceSvn()); // device is not CDMA, do not display CDMA features // check Null in case no specified preference in overlay xml diff --git a/src/com/android/settings/deviceinfo/PublicVolumeSettings.java b/src/com/android/settings/deviceinfo/PublicVolumeSettings.java index c9b4beb..7708cf0 100644 --- a/src/com/android/settings/deviceinfo/PublicVolumeSettings.java +++ b/src/com/android/settings/deviceinfo/PublicVolumeSettings.java @@ -152,7 +152,7 @@ public class PublicVolumeSettings extends SettingsPreferenceFragment { if (mVolume.getState() == VolumeInfo.STATE_UNMOUNTED) { addPreference(mMount); } - if (mVolume.isMountedReadable()) { + if (!mDisk.isNonRemovable() && mVolume.isMountedReadable()) { addPreference(mUnmount); } addPreference(mFormatPublic); diff --git a/src/com/android/settings/deviceinfo/SimStatus.java b/src/com/android/settings/deviceinfo/SimStatus.java index 83043c7..b40a255 100644 --- a/src/com/android/settings/deviceinfo/SimStatus.java +++ b/src/com/android/settings/deviceinfo/SimStatus.java @@ -16,6 +16,7 @@ package com.android.settings.deviceinfo; +import android.app.ActionBar; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -36,23 +37,19 @@ import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; +import android.view.MenuItem; +import android.view.View; +import android.widget.ListView; import com.android.internal.logging.MetricsLogger; import com.android.internal.telephony.DefaultPhoneNotifier; import com.android.internal.telephony.Phone; +import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.PhoneFactory; import com.android.settings.InstrumentedPreferenceActivity; import com.android.settings.R; import com.android.settings.Utils; -import android.view.View; -import android.widget.ListView; -import android.widget.TabHost; -import android.widget.TabHost.OnTabChangeListener; -import android.widget.TabHost.TabContentFactory; -import android.widget.TabHost.TabSpec; -import android.widget.TabWidget; - import java.util.ArrayList; import java.util.List; @@ -62,7 +59,6 @@ import java.util.List; * # Phone Number * # Network * # Roaming - * # Device Id (IMEI in GSM and MEID in CDMA) * # Network type * # Operator info (area info cell broadcast for Brazil) * # Signal Strength @@ -79,8 +75,6 @@ public class SimStatus extends InstrumentedPreferenceActivity { private static final String KEY_LATEST_AREA_INFO = "latest_area_info"; private static final String KEY_PHONE_NUMBER = "number"; private static final String KEY_SIGNAL_STRENGTH = "signal_strength"; - private static final String KEY_IMEI = "imei"; - private static final String KEY_IMEI_SV = "imei_sv"; private static final String COUNTRY_ABBREVIATION_BRAZIL = "br"; static final String CB_AREA_INFO_RECEIVED_ACTION = @@ -93,6 +87,7 @@ public class SimStatus extends InstrumentedPreferenceActivity { static final String CB_AREA_INFO_SENDER_PERMISSION = "android.permission.RECEIVE_EMERGENCY_BROADCAST"; + static final String EXTRA_SLOT_ID = "slot_id"; private TelephonyManager mTelephonyManager; private Phone mPhone = null; @@ -104,11 +99,6 @@ public class SimStatus extends InstrumentedPreferenceActivity { // Default summary for items private String mDefaultText; - private TabHost mTabHost; - private TabWidget mTabWidget; - private ListView mListView; - private List<SubscriptionInfo> mSelectableSubInfos; - private PhoneStateListener mPhoneStateListener; private BroadcastReceiver mAreaInfoReceiver = new BroadcastReceiver() { @Override @@ -135,8 +125,6 @@ public class SimStatus extends InstrumentedPreferenceActivity { super.onCreate(icicle); mTelephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE); - mSelectableSubInfos = SubscriptionManager.from(this).getActiveSubscriptionInfoList(); - addPreferencesFromResource(R.xml.device_info_sim_status); mRes = getResources(); @@ -144,29 +132,20 @@ public class SimStatus extends InstrumentedPreferenceActivity { // Note - missing in zaku build, be careful later... mSignalStrength = findPreference(KEY_SIGNAL_STRENGTH); - if (mSelectableSubInfos == null) { - mSir = null; - } else { - mSir = mSelectableSubInfos.size() > 0 ? mSelectableSubInfos.get(0) : null; - - if (mSelectableSubInfos.size() > 1) { - setContentView(com.android.internal.R.layout.common_tab_settings); + SubscriptionManager subscriptionManager = SubscriptionManager.from(this); + int slotId = getIntent().getIntExtra(EXTRA_SLOT_ID, 0); + mSir = subscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(slotId); - mTabHost = (TabHost) findViewById(android.R.id.tabhost); - mTabWidget = (TabWidget) findViewById(android.R.id.tabs); - mListView = (ListView) findViewById(android.R.id.list); + updatePhoneInfos(); - mTabHost.setup(); - mTabHost.setOnTabChangedListener(mTabListener); - mTabHost.clearAllTabs(); + if (getIntent().hasExtra(EXTRA_SLOT_ID)) { + setTitle(getString(R.string.sim_card_status_title, slotId + 1)); + } - for (int i = 0; i < mSelectableSubInfos.size(); i++) { - mTabHost.addTab(buildTabSpec(String.valueOf(i), - String.valueOf(mSelectableSubInfos.get(i).getDisplayName()))); - } - } + ActionBar actionBar = getActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); } - updatePhoneInfos(); } @Override @@ -192,6 +171,8 @@ public class SimStatus extends InstrumentedPreferenceActivity { CB_AREA_INFO_SENDER_PERMISSION, null); // Ask CellBroadcastReceiver to broadcast the latest area info received Intent getLatestIntent = new Intent(GET_LATEST_CB_AREA_INFO_ACTION); + getLatestIntent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, + mSir.getSubscriptionId()); sendBroadcastAsUser(getLatestIntent, UserHandle.ALL, CB_AREA_INFO_SENDER_PERMISSION); } @@ -211,6 +192,15 @@ public class SimStatus extends InstrumentedPreferenceActivity { } } + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } + return false; + } + /** * Removes the specified preference, if it exists. * @param key the key for the Preference item @@ -289,28 +279,26 @@ public class SimStatus extends InstrumentedPreferenceActivity { private void updateServiceState(ServiceState serviceState) { final int state = serviceState.getState(); - String display = mRes.getString(R.string.radioInfo_unknown); + final int dataState = mPhone.getServiceState().getDataRegState(); switch (state) { - case ServiceState.STATE_IN_SERVICE: - display = mRes.getString(R.string.radioInfo_service_in); - break; case ServiceState.STATE_OUT_OF_SERVICE: // Set signal strength to 0 when service state is STATE_OUT_OF_SERVICE - mSignalStrength.setSummary("0"); - case ServiceState.STATE_EMERGENCY_ONLY: - // Set summary string of service state to radioInfo_service_out when - // service state is both STATE_OUT_OF_SERVICE & STATE_EMERGENCY_ONLY - display = mRes.getString(R.string.radioInfo_service_out); + if (ServiceState.STATE_OUT_OF_SERVICE == dataState) { + mSignalStrength.setSummary("0"); + } break; case ServiceState.STATE_POWER_OFF: - display = mRes.getString(R.string.radioInfo_service_off); // Also set signal strength to 0 mSignalStrength.setSummary("0"); break; } + String voiceDisplay = Utils.getServiceStateString(state, mRes); + + String dataDisplay = Utils.getServiceStateString(dataState, mRes); - setSummaryText(KEY_SERVICE_STATE, display); + setSummaryText(KEY_SERVICE_STATE, getString(R.string.sim_status_format_string, + voiceDisplay, dataDisplay)); if (serviceState.getRoaming()) { setSummaryText(KEY_ROAMING_STATE, mRes.getString(R.string.radioInfo_roaming_in)); @@ -329,9 +317,11 @@ public class SimStatus extends InstrumentedPreferenceActivity { void updateSignalStrength(SignalStrength signalStrength) { if (mSignalStrength != null) { final int state = mPhone.getServiceState().getState(); + final int dataState = mPhone.getServiceState().getDataRegState(); Resources r = getResources(); - if ((ServiceState.STATE_OUT_OF_SERVICE == state) || + if (((ServiceState.STATE_OUT_OF_SERVICE == state) && + (ServiceState.STATE_OUT_OF_SERVICE == dataState)) || (ServiceState.STATE_POWER_OFF == state)) { mSignalStrength.setSummary("0"); return; @@ -369,8 +359,6 @@ public class SimStatus extends InstrumentedPreferenceActivity { } // If formattedNumber is null or empty, it'll display as "Unknown". setSummaryText(KEY_PHONE_NUMBER, formattedNumber); - setSummaryText(KEY_IMEI, mPhone.getImei()); - setSummaryText(KEY_IMEI_SV, mPhone.getDeviceSvn()); if (!mShowLatestAreaInfo) { removePreferenceFromScreen(KEY_LATEST_AREA_INFO); @@ -389,6 +377,12 @@ public class SimStatus extends InstrumentedPreferenceActivity { } mPhone = phone; + updateAreaInfo(""); + Intent getLatestIntent = new Intent(GET_LATEST_CB_AREA_INFO_ACTION); + getLatestIntent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, + mSir.getSubscriptionId()); + sendBroadcastAsUser(getLatestIntent, UserHandle.ALL, + CB_AREA_INFO_SENDER_PERMISSION); mPhoneStateListener = new PhoneStateListener(mSir.getSubscriptionId()) { @Override public void onDataConnectionStateChanged(int state) { @@ -409,33 +403,4 @@ public class SimStatus extends InstrumentedPreferenceActivity { } } } - private OnTabChangeListener mTabListener = new OnTabChangeListener() { - @Override - public void onTabChanged(String tabId) { - final int slotId = Integer.parseInt(tabId); - mSir = mSelectableSubInfos.get(slotId); - - // The User has changed tab; update the SIM information. - updatePhoneInfos(); - mTelephonyManager.listen(mPhoneStateListener, - PhoneStateListener.LISTEN_DATA_CONNECTION_STATE - | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS - | PhoneStateListener.LISTEN_SERVICE_STATE); - updateDataState(); - updateNetworkType(); - updatePreference(); - } - }; - - private TabContentFactory mEmptyTabContent = new TabContentFactory() { - @Override - public View createTabContent(String tag) { - return new View(mTabHost.getContext()); - } - }; - - private TabSpec buildTabSpec(String tag, String title) { - return mTabHost.newTabSpec(tag).setIndicator(title).setContent( - mEmptyTabContent); - } } diff --git a/src/com/android/settings/deviceinfo/Status.java b/src/com/android/settings/deviceinfo/Status.java index b52a0ad..4c905eb 100644 --- a/src/com/android/settings/deviceinfo/Status.java +++ b/src/com/android/settings/deviceinfo/Status.java @@ -16,6 +16,7 @@ package com.android.settings.deviceinfo; +import android.app.ActionBar; import android.bluetooth.BluetoothAdapter; import android.content.BroadcastReceiver; import android.content.ClipboardManager; @@ -35,7 +36,12 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.preference.Preference; import android.preference.PreferenceActivity; +import android.preference.PreferenceScreen; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; import android.text.TextUtils; +import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; import android.widget.ListAdapter; @@ -47,6 +53,8 @@ import com.android.settings.InstrumentedPreferenceActivity; import com.android.settings.R; import com.android.settings.Utils; +import cyanogenmod.hardware.CMHardwareManager; + import java.lang.ref.WeakReference; /** @@ -198,7 +206,7 @@ public class Status extends InstrumentedPreferenceActivity { updateConnectivity(); - String serial = Build.SERIAL; + String serial = getSerialNumber(); if (serial != null && !serial.equals("")) { setSummaryText(KEY_SERIAL_NUMBER, serial); } else { @@ -211,6 +219,36 @@ public class Status extends InstrumentedPreferenceActivity { || Utils.isWifiOnly(this)) { removePreferenceFromScreen(KEY_SIM_STATUS); removePreferenceFromScreen(KEY_IMEI_INFO); + } else { + int numPhones = TelephonyManager.getDefault().getPhoneCount(); + + if (numPhones > 1) { + PreferenceScreen prefSet = getPreferenceScreen(); + Preference singleSimPref = prefSet.findPreference(KEY_SIM_STATUS); + SubscriptionManager subscriptionManager = SubscriptionManager.from(this); + + for (int i = 0; i < numPhones; i++) { + SubscriptionInfo sir = + subscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(i); + Preference pref = new Preference(this); + + pref.setOrder(singleSimPref.getOrder()); + pref.setTitle(getString(R.string.sim_card_status_title, i + 1)); + if (sir != null) { + pref.setSummary(sir.getDisplayName()); + } else { + pref.setSummary(R.string.sim_card_summary_empty); + } + + Intent intent = new Intent(this, SimStatus.class); + intent.putExtra(SimStatus.EXTRA_SLOT_ID, i); + pref.setIntent(intent); + + prefSet.addPreference(pref); + } + + prefSet.removePreference(singleSimPref); + } } // Make every pref on this screen copy its data to the clipboard on longpress. @@ -233,6 +271,20 @@ public class Status extends InstrumentedPreferenceActivity { return true; } }); + + ActionBar actionBar = getActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } + return false; } @Override @@ -361,4 +413,29 @@ public class Status extends InstrumentedPreferenceActivity { return h + ":" + pad(m) + ":" + pad(s); } + + private String getSerialNumber() { + CMHardwareManager hardware = CMHardwareManager.getInstance(this); + if (hardware.isSupported(CMHardwareManager.FEATURE_SERIAL_NUMBER)) { + return hardware.getSerialNumber(); + } else { + return Build.SERIAL; + } + } + + public static String getSarValues(Resources res) { + String headLevel = String.format(res.getString(R.string.maximum_head_level, + res.getString(R.string.sar_head_level).split(","))); + String bodyLevel = String.format(res.getString(R.string.maximum_body_level, + res.getString(R.string.sar_body_level).split(","))); + return headLevel + "\n" + bodyLevel; + } + + public static String getIcCodes(Resources resources) { + String model = String.format(resources.getString(R.string.ic_code_model, + Build.MODEL)); + String icCode = String.format(resources.getString(R.string.ic_code_full, + resources.getString(R.string.ic_code))); + return model + "\n" + icCode; + } } diff --git a/src/com/android/settings/deviceinfo/StorageSettings.java b/src/com/android/settings/deviceinfo/StorageSettings.java index c36d0df..8f56a7c 100644 --- a/src/com/android/settings/deviceinfo/StorageSettings.java +++ b/src/com/android/settings/deviceinfo/StorageSettings.java @@ -23,8 +23,8 @@ import android.app.Fragment; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; -import android.graphics.Color; import android.graphics.drawable.Drawable; +import android.graphics.PorterDuff; import android.os.AsyncTask; import android.os.Bundle; import android.os.storage.DiskInfo; @@ -63,16 +63,10 @@ public class StorageSettings extends SettingsPreferenceFragment implements Index private static final String TAG_VOLUME_UNMOUNTED = "volume_unmounted"; private static final String TAG_DISK_INIT = "disk_init"; - static final int COLOR_PUBLIC = Color.parseColor("#ff9e9e9e"); - static final int COLOR_WARNING = Color.parseColor("#fff4511e"); - static final int[] COLOR_PRIVATE = new int[] { - Color.parseColor("#ff26a69a"), - Color.parseColor("#ffab47bc"), - Color.parseColor("#fff2a600"), - Color.parseColor("#ffec407a"), - Color.parseColor("#ffc0ca33"), - }; + private int mPublicColor; + + private int[] mPrivateColors; private StorageManager mStorageManager; @@ -108,6 +102,14 @@ public class StorageSettings extends SettingsPreferenceFragment implements Index mInternalSummary = new StorageSummaryPreference(context); setHasOptionsMenu(true); + mPublicColor = context.getColor(R.color.storage_volume_color_public); + mPrivateColors = new int[] { + context.getColor(R.color.storage_volume_color_private1), + context.getColor(R.color.storage_volume_color_private2), + context.getColor(R.color.storage_volume_color_private3), + context.getColor(R.color.storage_volume_color_private4), + context.getColor(R.color.storage_volume_color_private5), + }; } private final StorageEventListener mStorageListener = new StorageEventListener() { @@ -147,7 +149,7 @@ public class StorageSettings extends SettingsPreferenceFragment implements Index for (VolumeInfo vol : volumes) { if (vol.getType() == VolumeInfo.TYPE_PRIVATE) { - final int color = COLOR_PRIVATE[privateCount++ % COLOR_PRIVATE.length]; + final int color = mPrivateColors[privateCount++ % mPrivateColors.length]; mInternalCategory.addPreference( new StorageVolumePreference(context, vol, color)); if (vol.isMountedReadable()) { @@ -157,7 +159,7 @@ public class StorageSettings extends SettingsPreferenceFragment implements Index } } else if (vol.getType() == VolumeInfo.TYPE_PUBLIC) { mExternalCategory.addPreference( - new StorageVolumePreference(context, vol, COLOR_PUBLIC)); + new StorageVolumePreference(context, vol, mPublicColor)); } } @@ -169,7 +171,8 @@ public class StorageSettings extends SettingsPreferenceFragment implements Index // TODO: add actual storage type to record final Drawable icon = context.getDrawable(R.drawable.ic_sim_sd); icon.mutate(); - icon.setTint(COLOR_PUBLIC); + icon.setTint(mPublicColor); + icon.setTintMode(PorterDuff.Mode.SRC_ATOP); final Preference pref = new Preference(context); pref.setKey(rec.getFsUuid()); diff --git a/src/com/android/settings/deviceinfo/StorageSummaryPreference.java b/src/com/android/settings/deviceinfo/StorageSummaryPreference.java index 2641cb6..919e42d 100644 --- a/src/com/android/settings/deviceinfo/StorageSummaryPreference.java +++ b/src/com/android/settings/deviceinfo/StorageSummaryPreference.java @@ -17,6 +17,7 @@ package com.android.settings.deviceinfo; import android.content.Context; +import android.content.res.Resources; import android.graphics.Color; import android.preference.Preference; import android.view.View; @@ -41,6 +42,7 @@ public class StorageSummaryPreference extends Preference { @Override protected void onBindView(View view) { + Resources res = getContext().getResources(); final ProgressBar progress = (ProgressBar) view.findViewById(android.R.id.progress); if (mPercent != -1) { progress.setVisibility(View.VISIBLE); @@ -50,7 +52,7 @@ public class StorageSummaryPreference extends Preference { } final TextView summary = (TextView) view.findViewById(android.R.id.summary); - summary.setTextColor(Color.parseColor("#8a000000")); + summary.setTextColor(res.getColor(R.color.storage_summary_used_text_color)); super.onBindView(view); } diff --git a/src/com/android/settings/deviceinfo/StorageVolumePreference.java b/src/com/android/settings/deviceinfo/StorageVolumePreference.java index 3511b91..7668884 100644 --- a/src/com/android/settings/deviceinfo/StorageVolumePreference.java +++ b/src/com/android/settings/deviceinfo/StorageVolumePreference.java @@ -18,8 +18,10 @@ package com.android.settings.deviceinfo; import android.content.Context; import android.content.res.ColorStateList; +import android.content.res.Resources; import android.graphics.Color; import android.graphics.drawable.Drawable; +import android.graphics.PorterDuff; import android.os.storage.StorageManager; import android.os.storage.VolumeInfo; import android.preference.Preference; @@ -77,7 +79,7 @@ public class StorageVolumePreference extends Preference { mUsedPercent = (int) ((usedBytes * 100) / totalBytes); if (freeBytes < mStorageManager.getStorageLowBytes(path)) { - mColor = StorageSettings.COLOR_WARNING; + mColor = context.getColor(R.color.storage_volume_color_warning); icon = context.getDrawable(R.drawable.ic_warning_24dp); } @@ -88,9 +90,11 @@ public class StorageVolumePreference extends Preference { icon.mutate(); icon.setTint(mColor); + icon.setTintMode(PorterDuff.Mode.SRC_ATOP); setIcon(icon); if (volume.getType() == VolumeInfo.TYPE_PUBLIC + && !volume.disk.isNonRemovable() && volume.isMountedReadable()) { setWidgetLayoutResource(R.layout.preference_storage_action); } @@ -98,9 +102,12 @@ public class StorageVolumePreference extends Preference { @Override protected void onBindView(View view) { + final ImageView unmount = (ImageView) view.findViewById(R.id.unmount); + if (unmount != null) { - unmount.setImageTintList(ColorStateList.valueOf(Color.parseColor("#8a000000"))); + unmount.setImageTintList(ColorStateList.valueOf( + getContext().getColor(R.color.eject_icon_tint_color))); unmount.setOnClickListener(mUnmountListener); } diff --git a/src/com/android/settings/deviceinfo/UsbBackend.java b/src/com/android/settings/deviceinfo/UsbBackend.java index 210e0a0..340eba5 100644 --- a/src/com/android/settings/deviceinfo/UsbBackend.java +++ b/src/com/android/settings/deviceinfo/UsbBackend.java @@ -18,6 +18,7 @@ package com.android.settings.deviceinfo; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; import android.hardware.usb.UsbManager; import android.hardware.usb.UsbPort; import android.hardware.usb.UsbPortStatus; @@ -36,6 +37,7 @@ public class UsbBackend { public static final int MODE_DATA_MIDI = 0x03 << 1; private final boolean mRestricted; + private final boolean mMidi; private UserManager mUserManager; private UsbManager mUsbManager; @@ -53,6 +55,8 @@ public class UsbBackend { mUsbManager = context.getSystemService(UsbManager.class); mRestricted = mUserManager.hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER); + mMidi = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI); + UsbPort[] ports = mUsbManager.getPorts(); // For now look for a connected port, in the future we should identify port in the // notification and pick based on that. @@ -135,6 +139,11 @@ public class UsbBackend { // No USB data modes are supported. return false; } + + if (!mMidi && (mode & MODE_DATA_MASK) == MODE_DATA_MIDI) { + return false; + } + if (mPort != null) { int power = modeToPower(mode); if ((mode & MODE_DATA_MASK) != 0) { diff --git a/src/com/android/settings/deviceinfo/UsbModeChooserActivity.java b/src/com/android/settings/deviceinfo/UsbModeChooserActivity.java index 77fc388..1105718 100644 --- a/src/com/android/settings/deviceinfo/UsbModeChooserActivity.java +++ b/src/com/android/settings/deviceinfo/UsbModeChooserActivity.java @@ -20,7 +20,12 @@ import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityManager; import android.app.AlertDialog; +import android.content.BroadcastReceiver; +import android.content.Context; import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.hardware.usb.UsbManager; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -49,6 +54,19 @@ public class UsbModeChooserActivity extends Activity { private AlertDialog mDialog; private LayoutInflater mLayoutInflater; + private BroadcastReceiver mDisconnectedReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (UsbManager.ACTION_USB_STATE.equals(action)) { + boolean connected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false); + if (!connected) { + mDialog.dismiss(); + } + } + } + }; + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { @@ -84,6 +102,20 @@ public class UsbModeChooserActivity extends Activity { } } + @Override + public void onStart() { + super.onStart(); + + IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_STATE); + registerReceiver(mDisconnectedReceiver, filter); + } + + @Override + protected void onStop() { + unregisterReceiver(mDisconnectedReceiver); + super.onStop(); + } + private void inflateOption(final int mode, boolean selected, LinearLayout container) { View v = mLayoutInflater.inflate(R.layout.radio_with_summary, container, false); diff --git a/src/com/android/settings/drawable/CircleFramedDrawable.java b/src/com/android/settings/drawable/CircleFramedDrawable.java deleted file mode 100644 index 31b8922..0000000 --- a/src/com/android/settings/drawable/CircleFramedDrawable.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2013 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.drawable; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.ColorFilter; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.PixelFormat; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffXfermode; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.drawable.Drawable; - -import com.android.settings.R; - -/** - * Converts the user avatar icon to a circularly clipped one. - * TODO: Move this to an internal framework class and share with the one in Keyguard. - */ -public class CircleFramedDrawable extends Drawable { - - private final Bitmap mBitmap; - private final int mSize; - private final Paint mPaint; - - private float mScale; - private Rect mSrcRect; - private RectF mDstRect; - - public static CircleFramedDrawable getInstance(Context context, Bitmap icon) { - Resources res = context.getResources(); - float iconSize = res.getDimension(R.dimen.circle_avatar_size); - - CircleFramedDrawable instance = new CircleFramedDrawable(icon, (int) iconSize); - return instance; - } - - public CircleFramedDrawable(Bitmap icon, int size) { - super(); - mSize = size; - - mBitmap = Bitmap.createBitmap(mSize, mSize, Bitmap.Config.ARGB_8888); - final Canvas canvas = new Canvas(mBitmap); - - final int width = icon.getWidth(); - final int height = icon.getHeight(); - final int square = Math.min(width, height); - - final Rect cropRect = new Rect((width - square) / 2, (height - square) / 2, square, square); - final RectF circleRect = new RectF(0f, 0f, mSize, mSize); - - final Path fillPath = new Path(); - fillPath.addArc(circleRect, 0f, 360f); - - canvas.drawColor(0, PorterDuff.Mode.CLEAR); - - // opaque circle matte - mPaint = new Paint(); - mPaint.setAntiAlias(true); - mPaint.setColor(Color.BLACK); - mPaint.setStyle(Paint.Style.FILL); - canvas.drawPath(fillPath, mPaint); - - // mask in the icon where the bitmap is opaque - mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); - canvas.drawBitmap(icon, cropRect, circleRect, mPaint); - - // prepare paint for frame drawing - mPaint.setXfermode(null); - - mScale = 1f; - - mSrcRect = new Rect(0, 0, mSize, mSize); - mDstRect = new RectF(0, 0, mSize, mSize); - } - - @Override - public void draw(Canvas canvas) { - final float inside = mScale * mSize; - final float pad = (mSize - inside) / 2f; - - mDstRect.set(pad, pad, mSize - pad, mSize - pad); - canvas.drawBitmap(mBitmap, mSrcRect, mDstRect, null); - } - - public void setScale(float scale) { - mScale = scale; - } - - public float getScale() { - return mScale; - } - - @Override - public int getOpacity() { - return PixelFormat.TRANSLUCENT; - } - - @Override - public void setAlpha(int alpha) { - } - - @Override - public void setColorFilter(ColorFilter cf) { - } - - @Override - public int getIntrinsicWidth() { - return mSize; - } - - @Override - public int getIntrinsicHeight() { - return mSize; - } -} diff --git a/src/com/android/settings/fingerprint/FingerprintEnrollFindSensor.java b/src/com/android/settings/fingerprint/FingerprintEnrollFindSensor.java index 63d9335..df3646e 100644 --- a/src/com/android/settings/fingerprint/FingerprintEnrollFindSensor.java +++ b/src/com/android/settings/fingerprint/FingerprintEnrollFindSensor.java @@ -19,6 +19,8 @@ package com.android.settings.fingerprint; import android.content.Intent; import android.hardware.fingerprint.FingerprintManager; import android.os.Bundle; +import android.view.View; +import android.widget.TextView; import com.android.internal.logging.MetricsLogger; import com.android.settings.ChooseLockSettingsHelper; @@ -31,6 +33,10 @@ public class FingerprintEnrollFindSensor extends FingerprintEnrollBase { private static final int CONFIRM_REQUEST = 1; private static final int ENROLLING = 2; + private static final int SENSOR_LOCATION_BACK = 0; + private static final int SENSOR_LOCATION_FRONT = 1; + private static final int SENSOR_LOCATION_LEFT = 2; + private static final int SENSOR_LOCATION_RIGHT = 3; public static final String EXTRA_KEY_LAUNCHED_CONFIRM = "launched_confirm_lock"; private FingerprintLocationAnimationView mAnimation; @@ -48,6 +54,21 @@ public class FingerprintEnrollFindSensor extends FingerprintEnrollBase { } mAnimation = (FingerprintLocationAnimationView) findViewById( R.id.fingerprint_sensor_location_animation); + + int sensorLocation = getResources().getInteger(R.integer.config_fingerprintSensorLocation); + if (sensorLocation < SENSOR_LOCATION_BACK || sensorLocation > SENSOR_LOCATION_RIGHT) { + sensorLocation = SENSOR_LOCATION_BACK; + } + final String location = getResources().getStringArray( + R.array.security_settings_fingerprint_sensor_locations)[sensorLocation]; + TextView message = (TextView) findViewById(R.id.find_sensor_message); + message.setText(getString( + R.string.security_settings_fingerprint_enroll_find_sensor_message_cm, + location)); + if (sensorLocation != SENSOR_LOCATION_BACK) { + findViewById(R.id.fingerprint_sensor_location_front_overlay) + .setVisibility(View.VISIBLE); + } } @Override diff --git a/src/com/android/settings/fingerprint/FingerprintEnrollIntroduction.java b/src/com/android/settings/fingerprint/FingerprintEnrollIntroduction.java index bc2757a..c3f2a5f 100644 --- a/src/com/android/settings/fingerprint/FingerprintEnrollIntroduction.java +++ b/src/com/android/settings/fingerprint/FingerprintEnrollIntroduction.java @@ -20,6 +20,7 @@ import android.app.admin.DevicePolicyManager; import android.content.Intent; import android.os.Bundle; import android.os.UserHandle; +import android.text.TextUtils; import android.provider.Settings.Global; import android.view.View; @@ -41,11 +42,21 @@ public class FingerprintEnrollIntroduction extends FingerprintEnrollBase { setContentView(R.layout.fingerprint_enroll_introduction); setHeaderText(R.string.security_settings_fingerprint_enroll_introduction_title); findViewById(R.id.cancel_button).setOnClickListener(this); + + final View learnMoreButton = findViewById(R.id.learn_more_button); + // If help url is not overlaid, remove the button. + if (TextUtils.isEmpty(getString(R.string.help_url_fingerprint))) { + learnMoreButton.setVisibility(View.GONE); + } else { + learnMoreButton.setOnClickListener(this); + } + final View learnMoreButton = findViewById(R.id.learn_more_button); learnMoreButton.setOnClickListener(this); if (Global.getInt(getContentResolver(), Global.DEVICE_PROVISIONED, 0) == 0) { learnMoreButton.setVisibility(View.GONE); } + final int passwordQuality = new ChooseLockSettingsHelper(this).utils() .getActivePasswordQuality(UserHandle.myUserId()); mHasPassword = passwordQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; diff --git a/src/com/android/settings/fingerprint/FingerprintUiHelper.java b/src/com/android/settings/fingerprint/FingerprintUiHelper.java index 245cbb4..7c519fe 100644 --- a/src/com/android/settings/fingerprint/FingerprintUiHelper.java +++ b/src/com/android/settings/fingerprint/FingerprintUiHelper.java @@ -18,6 +18,7 @@ package com.android.settings.fingerprint; import android.hardware.fingerprint.FingerprintManager; import android.os.CancellationSignal; +import android.text.TextUtils; import android.view.View; import android.widget.ImageView; import android.widget.TextView; @@ -38,29 +39,46 @@ public class FingerprintUiHelper extends FingerprintManager.AuthenticationCallba private Callback mCallback; private FingerprintManager mFingerprintManager; + private boolean mDark; + private String mIdleText; + + private boolean mCanceledBySelf; + public FingerprintUiHelper(ImageView icon, TextView errorTextView, Callback callback) { mFingerprintManager = icon.getContext().getSystemService(FingerprintManager.class); mIcon = icon; mErrorTextView = errorTextView; mCallback = callback; + mDark = false; } public void startListening() { if (mFingerprintManager.getEnrolledFingerprints().size() > 0) { + mCanceledBySelf = false; mCancellationSignal = new CancellationSignal(); mFingerprintManager.authenticate(null, mCancellationSignal, 0 /* flags */, this, null); setFingerprintIconVisibility(true); - mIcon.setImageResource(R.drawable.ic_fingerprint); + mIcon.setImageResource(mDark ? R.drawable.ic_fingerprint_dark + : R.drawable.ic_fingerprint); } } public void stopListening() { + mCanceledBySelf = true; if (mCancellationSignal != null) { mCancellationSignal.cancel(); mCancellationSignal = null; } } + public void setDarkIconography(boolean dark) { + mDark = dark; + } + + public void setIdleText(String idleText) { + mIdleText = idleText; + } + private boolean isListening() { return mCancellationSignal != null && !mCancellationSignal.isCanceled(); } @@ -72,8 +90,10 @@ public class FingerprintUiHelper extends FingerprintManager.AuthenticationCallba @Override public void onAuthenticationError(int errMsgId, CharSequence errString) { - showError(errString); - setFingerprintIconVisibility(false); + if (!mCanceledBySelf) { + showError(errString); + setFingerprintIconVisibility(false); + } } @Override @@ -107,8 +127,9 @@ public class FingerprintUiHelper extends FingerprintManager.AuthenticationCallba private Runnable mResetErrorTextRunnable = new Runnable() { @Override public void run() { - mErrorTextView.setText(""); - mIcon.setImageResource(R.drawable.ic_fingerprint); + mErrorTextView.setText(TextUtils.isEmpty(mIdleText) ? "" : mIdleText); + mIcon.setImageResource(mDark ? R.drawable.ic_fingerprint_dark + : R.drawable.ic_fingerprint); } }; diff --git a/src/com/android/settings/fuelgauge/BatteryEntry.java b/src/com/android/settings/fuelgauge/BatteryEntry.java index fbde228..fb211dc 100644 --- a/src/com/android/settings/fuelgauge/BatteryEntry.java +++ b/src/com/android/settings/fuelgauge/BatteryEntry.java @@ -32,7 +32,7 @@ import android.util.Log; import com.android.internal.os.BatterySipper; import com.android.settings.R; -import com.android.settings.Utils; +import com.android.settingslib.Utils; import java.util.ArrayList; import java.util.HashMap; @@ -157,7 +157,7 @@ public class BatteryEntry { break; case FLASHLIGHT: name = context.getResources().getString(R.string.power_flashlight); - iconId = R.drawable.ic_settings_display; + iconId = R.drawable.ic_power_flashlight; break; case APP: name = sipper.packageWithHighestDrain; diff --git a/src/com/android/settings/fuelgauge/BatteryHistoryChart.java b/src/com/android/settings/fuelgauge/BatteryHistoryChart.java index 76acf69..85813eb 100644 --- a/src/com/android/settings/fuelgauge/BatteryHistoryChart.java +++ b/src/com/android/settings/fuelgauge/BatteryHistoryChart.java @@ -128,6 +128,7 @@ public class BatteryHistoryChart extends View { final Paint mBatteryWarnPaint = new Paint(Paint.ANTI_ALIAS_FLAG); final Paint mBatteryCriticalPaint = new Paint(Paint.ANTI_ALIAS_FLAG); final Paint mTimeRemainPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + final Paint mDockBatteryBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); final Paint mChargingPaint = new Paint(); final Paint mScreenOnPaint = new Paint(); final Paint mGpsOnPaint = new Paint(); @@ -146,6 +147,7 @@ public class BatteryHistoryChart extends View { final Path mBatWarnPath = new Path(); final Path mBatCriticalPath = new Path(); final Path mTimeRemainPath = new Path(); + final Path mDockBatLevelPath = new Path(); final Path mChargingPath = new Path(); final Path mScreenOnPath = new Path(); final Path mGpsOnPath = new Path(); @@ -156,13 +158,17 @@ public class BatteryHistoryChart extends View { final Path mDateLinePath = new Path(); BatteryStats mStats; + BatteryStats mDockStats; Intent mBatteryBroadcast; long mStatsPeriod; int mBatteryLevel; + long mDockStatsPeriod; + int mDockBatteryLevel; String mMaxPercentLabelString; String mMinPercentLabelString; String mDurationString; String mChargeLabelString; + String mDockChargeLabelString; String mChargeDurationString; String mDrainString; String mChargingLabel; @@ -213,13 +219,21 @@ public class BatteryHistoryChart extends View { int mLevelRight; int mNumHist; + int mDockNumHist; long mHistStart; long mHistDataEnd; long mHistEnd; long mStartWallTime; long mEndDataWallTime; long mEndWallTime; + long mDockHistStart; + long mDockHistDataEnd; + long mDockHistEnd; + long mDockStartWallTime; + long mDockEndDataWallTime; + long mDockEndWallTime; boolean mDischarging; + boolean mDockDischarging; int mBatLow; int mBatHigh; boolean mHaveWifi; @@ -234,6 +248,8 @@ public class BatteryHistoryChart extends View { Bitmap mBitmap; Canvas mCanvas; + private final boolean mDockBatterySupported; + static class TextAttrs { ColorStateList textColor = null; int textSize = 15; @@ -356,6 +372,10 @@ public class BatteryHistoryChart extends View { if (DEBUG) Log.d(TAG, "New BatteryHistoryChart!"); + BatteryManager mBatteryService = (BatteryManager) context.getSystemService( + Context.BATTERY_SERVICE); + mDockBatterySupported = mBatteryService.isDockBatterySupported(); + mBatteryWarnLevel = mContext.getResources().getInteger( com.android.internal.R.integer.config_lowBatteryWarningLevel); mBatteryCriticalLevel = mContext.getResources().getInteger( @@ -374,6 +394,10 @@ public class BatteryHistoryChart extends View { mBatteryCriticalPaint.setStyle(Paint.Style.STROKE); mTimeRemainPaint.setColor(0xFFCED7BB); mTimeRemainPaint.setStyle(Paint.Style.FILL); + if (mDockBatterySupported) { + mDockBatteryBackgroundPaint.setColor(0x803CA8B8); + mDockBatteryBackgroundPaint.setStyle(Paint.Style.FILL); + } mChargingPaint.setStyle(Paint.Style.STROKE); mScreenOnPaint.setStyle(Paint.Style.STROKE); mGpsOnPaint.setStyle(Paint.Style.STROKE); @@ -629,6 +653,93 @@ public class BatteryHistoryChart extends View { if (mHistEnd <= mHistStart) mHistEnd = mHistStart+1; } + void setDockStats(BatteryStats dockStats, Intent broadcast) { + mDockStats = dockStats; + if (mDockBatterySupported && dockStats != null) { + final long elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000; + + long uSecTime = mDockStats.computeBatteryRealtime(elapsedRealtimeUs, + BatteryStats.STATS_SINCE_CHARGED); + mDockStatsPeriod = uSecTime; + + mDockBatteryLevel = com.android.settings.Utils.getDockBatteryLevel(mBatteryBroadcast); + String batteryPercentString = Utils.formatPercentage(mDockBatteryLevel); + long remainingTimeUs = 0; + mDockDischarging = true; + if (mBatteryBroadcast.getBooleanExtra(BatteryManager.EXTRA_DOCK_PRESENT, false)) { + // We need to add a extra check over the status because of dock batteries + // PlugType doesn't means that the dock battery is charging (some devices + // doesn't charge under dock usb) + int plugType = mBatteryBroadcast.getIntExtra(BatteryManager.EXTRA_DOCK_PLUGGED, 0); + int status = mBatteryBroadcast.getIntExtra(BatteryManager.EXTRA_DOCK_STATUS, + BatteryManager.BATTERY_STATUS_UNKNOWN); + boolean plugged = plugType != 0 && + (status == BatteryManager.BATTERY_STATUS_CHARGING || + status == BatteryManager.BATTERY_STATUS_FULL); + if (!plugged) { + mDockChargeLabelString = batteryPercentString; + } else { + final String statusLabel = com.android.settings.Utils.getDockBatteryStatus( + getResources(), mBatteryBroadcast); + mDockChargeLabelString = getContext().getResources().getString( + R.string.power_charging, batteryPercentString, statusLabel); + } + } else { + mDockChargeLabelString = getContext().getResources().getString( + R.string.dock_battery_not_present); + } + + int pos = 0; + int lastInteresting = 0; + byte lastLevel = -1; + long lastWallTime = 0; + long lastRealtime = 0; + boolean first = true; + if (dockStats.startIteratingHistoryLocked()) { + final HistoryItem rec = new HistoryItem(); + while (dockStats.getNextHistoryLocked(rec)) { + pos++; + if (first) { + first = false; + mDockHistStart = rec.time; + } + if (rec.cmd == HistoryItem.CMD_CURRENT_TIME + || rec.cmd == HistoryItem.CMD_RESET) { + // If there is a ridiculously large jump in time, then we won't be + // able to create a good chart with that data, so just ignore the + // times we got before and pretend like our data extends back from + // the time we have now. + // Also, if we are getting a time change and we are less than 5 minutes + // since the start of the history real time, then also use this new + // time to compute the base time, since whatever time we had before is + // pretty much just noise. + if (rec.currentTime > (lastWallTime+(180*24*60*60*1000L)) + || rec.time < (mDockHistStart+(5*60*1000L))) { + mDockStartWallTime = 0; + } + lastWallTime = rec.currentTime; + lastRealtime = rec.time; + if (mDockStartWallTime == 0) { + mDockStartWallTime = lastWallTime - (lastRealtime-mDockHistStart); + } + } + if (rec.isDeltaData()) { + if (rec.batteryLevel != lastLevel || pos == 1) { + lastLevel = rec.batteryLevel; + } + lastInteresting = pos; + mDockHistDataEnd = rec.time; + } + } + } + mDockHistEnd = mDockHistDataEnd + (remainingTimeUs/1000); + mDockEndDataWallTime = lastWallTime + mDockHistDataEnd - lastRealtime; + mDockEndWallTime = mDockEndDataWallTime + (remainingTimeUs/1000); + mDockNumHist = lastInteresting; + if (mDockHistEnd <= mDockHistStart) mDockHistEnd = mDockHistStart+1; + } + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { mMaxPercentLabelStringWidth = (int)mTextPaint.measureText(mMaxPercentLabelString); @@ -642,6 +753,9 @@ public class BatteryHistoryChart extends View { mHeaderTextDescent = (int)mHeaderTextPaint.descent(); int headerTextHeight = mHeaderTextDescent - mHeaderTextAscent; mHeaderHeight = headerTextHeight*2 - mTextAscent; + if (mDockBatterySupported) { + mHeaderHeight += headerTextHeight; + } setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(mChartMinHeight+mHeaderHeight, heightMeasureSpec)); } @@ -650,18 +764,7 @@ public class BatteryHistoryChart extends View { int lastX, boolean lastCharging, boolean lastScreenOn, boolean lastGpsOn, boolean lastFlashlightOn, boolean lastCameraOn, boolean lastWifiRunning, boolean lastCpuRunning, Path lastPath) { - if (curLevelPath != null) { - if (lastX >= 0 && lastX < w) { - if (lastPath != null) { - lastPath.lineTo(w, y); - } - curLevelPath.lineTo(w, y); - } - curLevelPath.lineTo(w, mLevelTop+levelh); - curLevelPath.lineTo(startX, mLevelTop+levelh); - curLevelPath.close(); - } - + finishCurLevelPath(w, levelh, startX, y, curLevelPath, lastX, lastPath); if (lastCharging) { mChargingPath.lineTo(w, h-mChargingOffset); } @@ -688,6 +791,21 @@ public class BatteryHistoryChart extends View { } } + void finishCurLevelPath(int w, int levelh, int startX, int y, + Path curLevelPath, int lastX, Path lastPath) { + if (curLevelPath != null) { + if (lastX >= 0 && lastX < w) { + if (lastPath != null) { + lastPath.lineTo(w, y); + } + curLevelPath.lineTo(w, y); + } + curLevelPath.lineTo(w, mLevelTop+levelh); + curLevelPath.lineTo(startX, mLevelTop+levelh); + curLevelPath.close(); + } + } + private boolean is24Hour() { return DateFormat.is24HourFormat(getContext()); } @@ -782,8 +900,11 @@ public class BatteryHistoryChart extends View { mBatLevelPath.reset(); mBatGoodPath.reset(); mBatWarnPath.reset(); - mTimeRemainPath.reset(); mBatCriticalPath.reset(); + mTimeRemainPath.reset(); + if (mDockBatterySupported) { + mDockBatLevelPath.reset(); + } mScreenOnPath.reset(); mGpsOnPath.reset(); mFlashlightOnPath.reset(); @@ -795,9 +916,14 @@ public class BatteryHistoryChart extends View { mTimeLabels.clear(); mDateLabels.clear(); - final long walltimeStart = mStartWallTime; - final long walltimeChange = mEndWallTime > walltimeStart - ? (mEndWallTime-walltimeStart) : 1; + final long walltimeStart = mDockBatterySupported + ? Math.min(mStartWallTime, mDockStartWallTime) : mStartWallTime; + long w1 = mEndWallTime > walltimeStart ? (mEndWallTime-walltimeStart) : 1; + long w2 = mEndWallTime > walltimeStart ? (mEndWallTime-walltimeStart) : 1; + final long walltimeChange = mDockBatterySupported + ? Math.max(w1, w2) : w1; + + long curWalltime = mStartWallTime; long lastRealtime = 0; @@ -815,7 +941,7 @@ public class BatteryHistoryChart extends View { boolean lastFlashlightOn = false, lastCameraOn = false; boolean lastWifiRunning = false, lastWifiSupplRunning = false, lastCpuRunning = false; int lastWifiSupplState = BatteryStats.WIFI_SUPPL_STATE_INVALID; - final int N = mNumHist; + int N = mNumHist; if (mEndDataWallTime > mStartWallTime && mStats.startIteratingHistoryLocked()) { final HistoryItem rec = new HistoryItem(); while (mStats.getNextHistoryLocked(rec) && i < N) { @@ -1052,6 +1178,7 @@ public class BatteryHistoryChart extends View { finishPaths(x, h, levelh, startX, lastY, curLevelPath, lastX, lastCharging, lastScreenOn, lastGpsOn, lastFlashlightOn, lastCameraOn, lastWifiRunning, lastCpuRunning, lastLinePath); + int lastBatX = x; if (x < w) { // If we reserved room for the remaining time, create a final path to draw @@ -1069,6 +1196,117 @@ public class BatteryHistoryChart extends View { mTimeRemainPath.close(); } + // And now set the dock battery if is supported + if (mDockBatterySupported) { + x = mLevelLeft; + y = 0; + startX = mLevelLeft; + lastX = -1; + lastY = -1; + i = 0; + curLevelPath = null; + lastLinePath = null; + curWalltime = mDockStartWallTime; + lastRealtime = 0; + lastCharging = false; + N = mDockNumHist; + if (mDockEndDataWallTime > mDockStartWallTime + && mDockStats.startIteratingHistoryLocked()) { + final HistoryItem rec = new HistoryItem(); + while (mDockStats.getNextHistoryLocked(rec) && i < N) { + if (rec.isDeltaData()) { + curWalltime += rec.time-lastRealtime; + lastRealtime = rec.time; + int pos = (int)(((curWalltime-walltimeStart)*levelWidth)/walltimeChange); + x = mLevelLeft + pos; + if (x < 0) { + x = 0; + } + if (false) { + StringBuilder sb = new StringBuilder(128); + sb.append("walloff="); + TimeUtils.formatDuration(curWalltime - walltimeStart, sb); + sb.append(" wallchange="); + TimeUtils.formatDuration(walltimeChange, sb); + sb.append(" x="); + sb.append(x); + Log.d("foo", sb.toString()); + } + y = mLevelTop + levelh - ((rec.batteryLevel-batLow)*(levelh-1))/batChange; + + if (lastX != x) { + // We have moved by at least a pixel. + if (lastY != y) { + if (curLevelPath == null) { + curLevelPath = mDockBatLevelPath; + curLevelPath.moveTo(x, y); + startX = x; + } else { + curLevelPath.lineTo(x, y); + } + lastX = x; + lastY = y; + } + } + + } else { + long lastWalltime = curWalltime; + if (rec.cmd == HistoryItem.CMD_CURRENT_TIME + || rec.cmd == HistoryItem.CMD_RESET) { + if (rec.currentTime >= mDockStartWallTime) { + curWalltime = rec.currentTime; + } else { + curWalltime = mDockStartWallTime + (rec.time-mDockHistStart); + } + lastRealtime = rec.time; + } + + if (rec.cmd != HistoryItem.CMD_OVERFLOW + && (rec.cmd != HistoryItem.CMD_CURRENT_TIME + || Math.abs(lastWalltime-curWalltime) > (60*60*1000))) { + if (curLevelPath != null) { + finishCurLevelPath(x+1,levelh, startX, lastY, curLevelPath, lastX, + lastLinePath); + lastX = lastY = -1; + curLevelPath = null; + lastLinePath = null; + lastCharging = lastScreenOn = lastGpsOn = lastCpuRunning = false; + } + } + } + + i++; + } + mDockStats.finishIteratingHistoryLocked(); + } + + if (lastY < 0 || lastX < 0) { + // Didn't get any data... + x = lastX = mLevelLeft; + y = lastY = mLevelTop + levelh - ((mDockBatteryLevel-batLow)*(levelh-1))/batChange; + mDockBatLevelPath.moveTo(x, y); + curLevelPath = mDockBatLevelPath; + x = w; + } else { + // Figure out where the actual data ends on the screen. + int pos = (int)(((mDockEndDataWallTime-walltimeStart)*levelWidth)/walltimeChange); + x = mLevelLeft + pos; + if (x < 0) { + x = 0; + } + } + + if (x < lastBatX) { + if (curLevelPath != null) { + curLevelPath.lineTo(x, lastY); + } + x = lastBatX; + } + + finishCurLevelPath(x,levelh, startX, lastY, curLevelPath, lastX, + lastLinePath); + } + if (mStartWallTime > 0 && mEndWallTime > mStartWallTime) { // Create the time labels at the bottom. boolean is24hr = is24Hour(); @@ -1202,6 +1440,9 @@ public class BatteryHistoryChart extends View { if (DEBUG) Log.d(TAG, "Drawing time remain path."); canvas.drawPath(mTimeRemainPath, mTimeRemainPaint); } + if (mDockBatterySupported && !mDockBatLevelPath.isEmpty()) { + canvas.drawPath(mDockBatLevelPath, mDockBatteryBackgroundPaint); + } if (mTimeLabels.size() > 1) { int y = mLevelBottom - mTextAscent + (mThinLineWidth*4); int ytick = mLevelBottom+mThinLineWidth+(mThinLineWidth/2); @@ -1253,6 +1494,11 @@ public class BatteryHistoryChart extends View { mHeaderTextPaint.setTextAlign(textAlignLeft); if (DEBUG) Log.d(TAG, "Drawing charge label string: " + mChargeLabelString); canvas.drawText(mChargeLabelString, textStartX, headerTop, mHeaderTextPaint); + if (mDockBatterySupported) { + if (DEBUG) Log.d(TAG, "Drawing dock charge label string: " + mDockChargeLabelString); + canvas.drawText(mDockChargeLabelString, textStartX, + headerTop + (mHeaderTextDescent - mHeaderTextAscent), mHeaderTextPaint); + } int stringHalfWidth = mChargeDurationStringWidth / 2; if (layoutRtl) stringHalfWidth = -stringHalfWidth; int headerCenter = ((width-mChargeDurationStringWidth-mDrainStringWidth)/2) diff --git a/src/com/android/settings/fuelgauge/BatteryHistoryDetail.java b/src/com/android/settings/fuelgauge/BatteryHistoryDetail.java index c977063..c1afe7b 100644 --- a/src/com/android/settings/fuelgauge/BatteryHistoryDetail.java +++ b/src/com/android/settings/fuelgauge/BatteryHistoryDetail.java @@ -30,9 +30,11 @@ import com.android.settings.R; public class BatteryHistoryDetail extends InstrumentedFragment { public static final String EXTRA_STATS = "stats"; + public static final String EXTRA_DOCK_STATS = "dock_stats"; public static final String EXTRA_BROADCAST = "broadcast"; private BatteryStats mStats; + private BatteryStats mDockStats; private Intent mBatteryBroadcast; @Override @@ -40,6 +42,12 @@ public class BatteryHistoryDetail extends InstrumentedFragment { super.onCreate(icicle); String histFile = getArguments().getString(EXTRA_STATS); mStats = BatteryStatsHelper.statsFromFile(getActivity(), histFile); + String dockHistFile = getArguments().getString(EXTRA_DOCK_STATS); + if (dockHistFile != null) { + mDockStats = BatteryStatsHelper.statsFromFile(getActivity(), dockHistFile); + } else { + mDockStats = null; + } mBatteryBroadcast = getArguments().getParcelable(EXTRA_BROADCAST); } @@ -49,6 +57,7 @@ public class BatteryHistoryDetail extends InstrumentedFragment { BatteryHistoryChart chart = (BatteryHistoryChart)view.findViewById( R.id.battery_history_chart); chart.setStats(mStats, mBatteryBroadcast); + chart.setDockStats(mDockStats, mBatteryBroadcast); return view; } diff --git a/src/com/android/settings/fuelgauge/BatteryHistoryPreference.java b/src/com/android/settings/fuelgauge/BatteryHistoryPreference.java index 0bf85b5..8c13a76 100644 --- a/src/com/android/settings/fuelgauge/BatteryHistoryPreference.java +++ b/src/com/android/settings/fuelgauge/BatteryHistoryPreference.java @@ -38,8 +38,10 @@ import com.android.settings.SettingsActivity; public class BatteryHistoryPreference extends Preference { protected static final String BATTERY_HISTORY_FILE = "tmp_bat_history.bin"; + protected static final String DOCK_BATTERY_HISTORY_FILE = "tmp_dock_bat_history.bin"; private BatteryStats mStats; + private BatteryStats mDockStats; private Intent mBatteryBroadcast; private BatteryHistoryChart mChart; @@ -57,6 +59,11 @@ public class BatteryHistoryPreference extends Preference { mHelper.storeStatsHistoryInFile(BATTERY_HISTORY_FILE); Bundle args = new Bundle(); args.putString(BatteryHistoryDetail.EXTRA_STATS, BATTERY_HISTORY_FILE); + if (mDockStats != null) { + mHelper.storeDockStatsHistoryInFile(DOCK_BATTERY_HISTORY_FILE); + args.putString(BatteryHistoryDetail.EXTRA_DOCK_STATS, DOCK_BATTERY_HISTORY_FILE); + } + args.putParcelable(BatteryHistoryDetail.EXTRA_BROADCAST, mHelper.getBatteryBroadcast()); if (getContext() instanceof SettingsActivity) { @@ -71,6 +78,7 @@ public class BatteryHistoryPreference extends Preference { mChart = null; mHelper = batteryStats; mStats = batteryStats.getStats(); + mDockStats = batteryStats.getDockStats(); mBatteryBroadcast = batteryStats.getBatteryBroadcast(); if (getLayoutResource() != R.layout.battery_history_chart) { // Now we should have some data, set the layout we want. @@ -79,10 +87,6 @@ public class BatteryHistoryPreference extends Preference { notifyChanged(); } - BatteryStats getStats() { - return mStats; - } - @Override protected void onBindView(View view) { super.onBindView(view); @@ -95,6 +99,7 @@ public class BatteryHistoryPreference extends Preference { if (mChart == null) { // First time: use and initialize this chart. chart.setStats(mStats, mBatteryBroadcast); + chart.setDockStats(mDockStats, mBatteryBroadcast); mChart = chart; } else { // All future times: forget the newly inflated chart, re-use the diff --git a/src/com/android/settings/fuelgauge/BatterySaverSettings.java b/src/com/android/settings/fuelgauge/BatterySaverSettings.java deleted file mode 100644 index d8a9ca0..0000000 --- a/src/com/android/settings/fuelgauge/BatterySaverSettings.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settings.fuelgauge; - -import static android.os.PowerManager.ACTION_POWER_SAVE_MODE_CHANGING; - -import android.content.BroadcastReceiver; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.res.Resources; -import android.database.ContentObserver; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.Handler; -import android.os.PowerManager; -import android.provider.Settings.Global; -import android.util.Log; -import android.widget.Switch; - -import com.android.internal.logging.MetricsLogger; -import com.android.settings.R; -import com.android.settings.SettingsActivity; -import com.android.settings.SettingsPreferenceFragment; -import com.android.settings.Utils; -import com.android.settings.notification.SettingPref; -import com.android.settings.widget.SwitchBar; - -public class BatterySaverSettings extends SettingsPreferenceFragment - implements SwitchBar.OnSwitchChangeListener { - private static final String TAG = "BatterySaverSettings"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - private static final String KEY_TURN_ON_AUTOMATICALLY = "turn_on_automatically"; - private static final long WAIT_FOR_SWITCH_ANIM = 500; - - private final Handler mHandler = new Handler(); - private final SettingsObserver mSettingsObserver = new SettingsObserver(mHandler); - private final Receiver mReceiver = new Receiver(); - - private Context mContext; - private boolean mCreated; - private SettingPref mTriggerPref; - private SwitchBar mSwitchBar; - private Switch mSwitch; - private boolean mValidListener; - private PowerManager mPowerManager; - - @Override - protected int getMetricsCategory() { - return MetricsLogger.FUELGAUGE_BATTERY_SAVER; - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - if (mCreated) { - mSwitchBar.show(); - return; - } - mCreated = true; - addPreferencesFromResource(R.xml.battery_saver_settings); - - mContext = getActivity(); - mSwitchBar = ((SettingsActivity) mContext).getSwitchBar(); - mSwitch = mSwitchBar.getSwitch(); - mSwitchBar.show(); - - mTriggerPref = new SettingPref(SettingPref.TYPE_GLOBAL, KEY_TURN_ON_AUTOMATICALLY, - Global.LOW_POWER_MODE_TRIGGER_LEVEL, - 0, /*default*/ - getResources().getIntArray(R.array.battery_saver_trigger_values)) { - @Override - protected String getCaption(Resources res, int value) { - if (value > 0 && value < 100) { - return res.getString(R.string.battery_saver_turn_on_automatically_pct, - Utils.formatPercentage(value)); - } - return res.getString(R.string.battery_saver_turn_on_automatically_never); - } - }; - mTriggerPref.init(this); - - mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - mSwitchBar.hide(); - } - - @Override - public void onResume() { - super.onResume(); - mSettingsObserver.setListening(true); - mReceiver.setListening(true); - if (!mValidListener) { - mSwitchBar.addOnSwitchChangeListener(this); - mValidListener = true; - } - updateSwitch(); - } - - @Override - public void onPause() { - super.onPause(); - mSettingsObserver.setListening(false); - mReceiver.setListening(false); - if (mValidListener) { - mSwitchBar.removeOnSwitchChangeListener(this); - mValidListener = false; - } - } - - @Override - public void onSwitchChanged(Switch switchView, boolean isChecked) { - mHandler.removeCallbacks(mStartMode); - if (isChecked) { - mHandler.postDelayed(mStartMode, WAIT_FOR_SWITCH_ANIM); - } else { - if (DEBUG) Log.d(TAG, "Stopping low power mode from settings"); - trySetPowerSaveMode(false); - } - } - - private void trySetPowerSaveMode(boolean mode) { - if (!mPowerManager.setPowerSaveMode(mode)) { - if (DEBUG) Log.d(TAG, "Setting mode failed, fallback to current value"); - mHandler.post(mUpdateSwitch); - } - } - - private void updateSwitch() { - final boolean mode = mPowerManager.isPowerSaveMode(); - if (DEBUG) Log.d(TAG, "updateSwitch: isChecked=" + mSwitch.isChecked() + " mode=" + mode); - if (mode == mSwitch.isChecked()) return; - - // set listener to null so that that code below doesn't trigger onCheckedChanged() - if (mValidListener) { - mSwitchBar.removeOnSwitchChangeListener(this); - } - mSwitch.setChecked(mode); - if (mValidListener) { - mSwitchBar.addOnSwitchChangeListener(this); - } - } - - private final Runnable mUpdateSwitch = new Runnable() { - @Override - public void run() { - updateSwitch(); - } - }; - - private final Runnable mStartMode = new Runnable() { - @Override - public void run() { - AsyncTask.execute(new Runnable() { - @Override - public void run() { - if (DEBUG) Log.d(TAG, "Starting low power mode from settings"); - trySetPowerSaveMode(true); - } - }); - } - }; - - private final class Receiver extends BroadcastReceiver { - private boolean mRegistered; - - @Override - public void onReceive(Context context, Intent intent) { - if (DEBUG) Log.d(TAG, "Received " + intent.getAction()); - mHandler.post(mUpdateSwitch); - } - - public void setListening(boolean listening) { - if (listening && !mRegistered) { - mContext.registerReceiver(this, new IntentFilter(ACTION_POWER_SAVE_MODE_CHANGING)); - mRegistered = true; - } else if (!listening && mRegistered) { - mContext.unregisterReceiver(this); - mRegistered = false; - } - } - } - - private final class SettingsObserver extends ContentObserver { - private final Uri LOW_POWER_MODE_TRIGGER_LEVEL_URI - = Global.getUriFor(Global.LOW_POWER_MODE_TRIGGER_LEVEL); - - public SettingsObserver(Handler handler) { - super(handler); - } - - @Override - public void onChange(boolean selfChange, Uri uri) { - if (LOW_POWER_MODE_TRIGGER_LEVEL_URI.equals(uri)) { - mTriggerPref.update(mContext); - } - } - - public void setListening(boolean listening) { - final ContentResolver cr = getContentResolver(); - if (listening) { - cr.registerContentObserver(LOW_POWER_MODE_TRIGGER_LEVEL_URI, false, this); - } else { - cr.unregisterContentObserver(this); - } - } - } -} diff --git a/src/com/android/settings/fuelgauge/PowerUsageBase.java b/src/com/android/settings/fuelgauge/PowerUsageBase.java index 269249a..5a6bd1b 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageBase.java +++ b/src/com/android/settings/fuelgauge/PowerUsageBase.java @@ -46,6 +46,7 @@ public abstract class PowerUsageBase extends SettingsPreferenceFragment { private String mBatteryLevel; private String mBatteryStatus; + private boolean mHideRefresh = false; @Override public void onAttach(Activity activity) { @@ -102,11 +103,13 @@ public abstract class PowerUsageBase extends SettingsPreferenceFragment { @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); - MenuItem refresh = menu.add(0, MENU_STATS_REFRESH, 0, R.string.menu_stats_refresh) - .setIcon(com.android.internal.R.drawable.ic_menu_refresh) - .setAlphabeticShortcut('r'); - refresh.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | - MenuItem.SHOW_AS_ACTION_WITH_TEXT); + if (!mHideRefresh) { + MenuItem refresh = menu.add(0, MENU_STATS_REFRESH, 0, R.string.menu_stats_refresh) + .setIcon(com.android.internal.R.drawable.ic_menu_refresh) + .setAlphabeticShortcut('r'); + refresh.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | + MenuItem.SHOW_AS_ACTION_WITH_TEXT); + } } public boolean onOptionsItemSelected(MenuItem item) { @@ -142,6 +145,10 @@ public abstract class PowerUsageBase extends SettingsPreferenceFragment { return false; } + void hideRefreshButton(boolean hide) { + mHideRefresh = hide; + } + static final int MSG_REFRESH_STATS = 100; private final Handler mHandler = new Handler() { diff --git a/src/com/android/settings/fuelgauge/PowerUsageDetail.java b/src/com/android/settings/fuelgauge/PowerUsageDetail.java index 129322b..135b70e 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageDetail.java +++ b/src/com/android/settings/fuelgauge/PowerUsageDetail.java @@ -29,6 +29,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.graphics.drawable.Drawable; +import android.graphics.PorterDuff; import android.net.Uri; import android.os.BatteryStats; import android.os.Bundle; @@ -86,9 +87,10 @@ public class PowerUsageDetail extends PowerUsageBase implements Button.OnClickLi SettingsActivity caller, BatteryStatsHelper helper, int statsType, BatteryEntry entry, boolean showLocationButton) { // Initialize mStats if necessary. - helper.getStats(); + final BatteryStats stats = helper.getStats(); + helper.getDockStats(); - final int dischargeAmount = helper.getStats().getDischargeAmount(statsType); + final int dischargeAmount = stats.getDischargeAmount(statsType); Bundle args = new Bundle(); args.putString(PowerUsageDetail.EXTRA_TITLE, entry.name); args.putInt(PowerUsageDetail.EXTRA_PERCENT, (int) @@ -151,15 +153,13 @@ public class PowerUsageDetail extends PowerUsageBase implements Button.OnClickLi if (entry.sipper.drainType == BatterySipper.DrainType.APP) { Writer result = new StringWriter(); PrintWriter printWriter = new FastPrintWriter(result, false, 1024); - helper.getStats().dumpLocked(caller, printWriter, "", helper.getStatsType(), - uid.getUid()); + stats.dumpLocked(caller, printWriter, "", helper.getStatsType(), uid.getUid()); printWriter.flush(); args.putString(PowerUsageDetail.EXTRA_REPORT_DETAILS, result.toString()); result = new StringWriter(); printWriter = new FastPrintWriter(result, false, 1024); - helper.getStats().dumpCheckinLocked(caller, printWriter, helper.getStatsType(), - uid.getUid()); + stats.dumpCheckinLocked(caller, printWriter, helper.getStatsType(), uid.getUid()); printWriter.flush(); args.putString(PowerUsageDetail.EXTRA_REPORT_CHECKIN_DETAILS, result.toString()); @@ -354,6 +354,7 @@ public class PowerUsageDetail extends PowerUsageBase implements Button.OnClickLi mControlsParent = (PreferenceCategory) findPreference(KEY_CONTROLS_PARENT); mMessagesParent = (PreferenceCategory) findPreference(KEY_MESSAGES_PARENT); mPackagesParent = (PreferenceCategory) findPreference(KEY_PACKAGES_PARENT); + hideRefreshButton(true); createDetails(); } @@ -479,13 +480,15 @@ public class PowerUsageDetail extends PowerUsageBase implements Button.OnClickLi if (appIcon == null) { appIcon = getActivity().getPackageManager().getDefaultActivityIcon(); } - + if (appIcon != null) { + appIcon.setTintMode(PorterDuff.Mode.SRC_ATOP); + } if (pkg == null && mPackages != null) { pkg = mPackages[0]; } AppHeader.createAppHeader(this, appIcon, title, pkg != null ? AppInfoWithHeader.getInfoIntent(this, pkg) : null, - mDrainType != DrainType.APP ? android.R.color.white : 0); + mDrainType != DrainType.APP ? R.color.power_usage_ab_icon_tint : 0); } public void onClick(View v) { diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/PowerUsageSummary.java index 445896d..fda2a7e 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageSummary.java +++ b/src/com/android/settings/fuelgauge/PowerUsageSummary.java @@ -18,16 +18,29 @@ package com.android.settings.fuelgauge; import android.app.Activity; import android.graphics.drawable.Drawable; +import android.app.AlertDialog; +import android.content.ContentResolver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.BatteryManager; import android.os.BatteryStats; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; +import android.os.PowerManager; import android.os.Process; import android.os.UserHandle; +import android.preference.ListPreference; import android.preference.Preference; import android.preference.PreferenceGroup; import android.preference.PreferenceScreen; +import android.preference.SwitchPreference; +import android.provider.Settings; import android.text.TextUtils; import android.util.SparseArray; import android.util.TypedValue; @@ -44,6 +57,10 @@ import com.android.settings.R; import com.android.settings.Settings.HighPowerApplicationsActivity; import com.android.settings.SettingsActivity; import com.android.settings.applications.ManageApplications; +import com.android.settings.Utils; + +import cyanogenmod.power.PerformanceManager; +import cyanogenmod.providers.CMSettings; import java.util.ArrayList; import java.util.Collections; @@ -54,7 +71,8 @@ import java.util.List; * Displays a list of apps and subsystems that consume power, ordered by how much power was * consumed since the last time it was unplugged. */ -public class PowerUsageSummary extends PowerUsageBase { +public class PowerUsageSummary extends PowerUsageBase + implements Preference.OnPreferenceChangeListener { private static final boolean DEBUG = false; @@ -63,15 +81,31 @@ public class PowerUsageSummary extends PowerUsageBase { static final String TAG = "PowerUsageSummary"; private static final String KEY_APP_LIST = "app_list"; + private static final String KEY_BATTERY_HISTORY = "battery_history"; + private static final String KEY_PERF_PROFILE = "pref_perf_profile"; + private static final String KEY_PER_APP_PROFILES = "app_perf_profiles_enabled"; + + private static final String KEY_BATTERY_SAVER = "low_power"; private static final int MENU_STATS_TYPE = Menu.FIRST; - private static final int MENU_BATTERY_SAVER = Menu.FIRST + 2; - private static final int MENU_HIGH_POWER_APPS = Menu.FIRST + 3; - private static final int MENU_HELP = Menu.FIRST + 4; + private static final int MENU_STATS_RESET = Menu.FIRST + 2; + private static final int MENU_BATTERY_SAVER = Menu.FIRST + 3; + private static final int MENU_HIGH_POWER_APPS = Menu.FIRST + 4; + private static final int MENU_HELP = Menu.FIRST + 5; private BatteryHistoryPreference mHistPref; private PreferenceGroup mAppListGroup; + private ListPreference mPerfProfilePref; + private SwitchPreference mPerAppProfiles; + private SwitchPreference mBatterySaverPref; + + private String[] mPerfProfileEntries; + private String[] mPerfProfileValues; + private PerformanceProfileObserver mPerformanceProfileObserver = null; + private int mNumPerfProfiles = 0; + + private boolean mBatteryPluggedIn; private int mStatsType = BatteryStats.STATS_SINCE_CHARGED; @@ -80,13 +114,67 @@ public class PowerUsageSummary extends PowerUsageBase { private static final int MIN_AVERAGE_POWER_THRESHOLD_MILLI_AMP = 10; private static final int SECONDS_IN_HOUR = 60 * 60; + private PowerManager mPowerManager; + private PerformanceManager mPerf; + + private class PerformanceProfileObserver extends ContentObserver { + public PerformanceProfileObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + updatePerformanceValue(); + } + } + @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); + mPowerManager = (PowerManager)getSystemService(Context.POWER_SERVICE); + mPerf = PerformanceManager.getInstance(getActivity()); + addPreferencesFromResource(R.xml.power_usage_summary); mHistPref = (BatteryHistoryPreference) findPreference(KEY_BATTERY_HISTORY); mAppListGroup = (PreferenceGroup) findPreference(KEY_APP_LIST); + mBatterySaverPref = (SwitchPreference) findPreference(KEY_BATTERY_SAVER); + + mNumPerfProfiles = mPerf.getNumberOfProfiles(); + mPerfProfilePref = (ListPreference) findPreference(KEY_PERF_PROFILE); + mPerAppProfiles = (SwitchPreference) findPreference(KEY_PER_APP_PROFILES); + if (mNumPerfProfiles < 1) { + removePreference(KEY_PERF_PROFILE); + removePreference(KEY_PER_APP_PROFILES); + mPerfProfilePref = null; + mPerAppProfiles = null; + } else { + // Remove the battery saver switch, power profiles have 3 modes + removePreference(KEY_BATTERY_SAVER); + mBatterySaverPref = null; + mPerfProfilePref.setOrder(-1); + mPerfProfileEntries = new String[mNumPerfProfiles]; + mPerfProfileValues = new String[mNumPerfProfiles]; + + // Filter out unsupported profiles + final String[] entries = getResources().getStringArray( + org.cyanogenmod.platform.internal.R.array.perf_profile_entries); + final int[] values = getResources().getIntArray( + org.cyanogenmod.platform.internal.R.array.perf_profile_values); + int i = 0; + for (int j = 0; j < values.length; j++) { + if (values[j] < mNumPerfProfiles) { + mPerfProfileEntries[i] = entries[j]; + mPerfProfileValues[i] = String.valueOf(values[j]); + i++; + } + } + mPerfProfilePref.setEntries(mPerfProfileEntries); + mPerfProfilePref.setEntryValues(mPerfProfileValues); + updatePerformanceValue(); + mPerfProfilePref.setOnPreferenceChangeListener(this); + } + mPerformanceProfileObserver = new PerformanceProfileObserver(new Handler()); } @Override @@ -98,6 +186,17 @@ public class PowerUsageSummary extends PowerUsageBase { public void onResume() { super.onResume(); refreshStats(); + + if (mBatterySaverPref != null) { + refreshBatterySaverOptions(); + } + + if (mPerfProfilePref != null) { + updatePerformanceValue(); + ContentResolver resolver = getActivity().getContentResolver(); + resolver.registerContentObserver(CMSettings.Secure.getUriFor( + CMSettings.Secure.PERFORMANCE_PROFILE), false, mPerformanceProfileObserver); + } } @Override @@ -105,6 +204,11 @@ public class PowerUsageSummary extends PowerUsageBase { BatteryEntry.stopRequestQueue(); mHandler.removeMessages(BatteryEntry.MSG_UPDATE_NAME_ICON); super.onPause(); + + if (mPerfProfilePref != null) { + ContentResolver resolver = getActivity().getContentResolver(); + resolver.unregisterContentObserver(mPerformanceProfileObserver); + } } @Override @@ -118,7 +222,7 @@ public class PowerUsageSummary extends PowerUsageBase { @Override public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { if (!(preference instanceof PowerGaugePreference)) { - return false; + return super.onPreferenceTreeClick(preferenceScreen, preference); } PowerGaugePreference pgp = (PowerGaugePreference) preference; BatteryEntry entry = pgp.getInfo(); @@ -128,6 +232,21 @@ public class PowerUsageSummary extends PowerUsageBase { } @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (newValue != null) { + if (preference == mPerfProfilePref) { + Integer value = Integer.valueOf((String) (newValue)); + boolean powerProfileUpdated = mPerf.setPowerProfile(value); + if (powerProfileUpdated) { + updatePerformanceSummary(); + } + return powerProfileUpdated; + } + } + return false; + } + + @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { if (DEBUG) { menu.add(0, MENU_STATS_TYPE, 0, R.string.menu_stats_total) @@ -135,6 +254,12 @@ public class PowerUsageSummary extends PowerUsageBase { .setAlphabeticShortcut('t'); } + MenuItem reset = menu.add(0, MENU_STATS_RESET, 0, R.string.menu_stats_reset) + .setIcon(R.drawable.ic_actionbar_delete) + .setAlphabeticShortcut('d'); + reset.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | + MenuItem.SHOW_AS_ACTION_WITH_TEXT); + MenuItem batterySaver = menu.add(0, MENU_BATTERY_SAVER, 0, R.string.battery_saver); batterySaver.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); @@ -143,11 +268,6 @@ public class PowerUsageSummary extends PowerUsageBase { } @Override - protected int getHelpResource() { - return R.string.help_url_battery; - } - - @Override public boolean onOptionsItemSelected(MenuItem item) { final SettingsActivity sa = (SettingsActivity) getActivity(); switch (item.getItemId()) { @@ -159,9 +279,46 @@ public class PowerUsageSummary extends PowerUsageBase { } refreshStats(); return true; + case MENU_STATS_RESET: + resetStats(); + return true; case MENU_BATTERY_SAVER: - sa.startPreferencePanel(BatterySaverSettings.class.getName(), null, - R.string.battery_saver, null, null, 0); + Resources res = getResources(); + + final int value = Settings.Global.getInt(getContentResolver(), + Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0); + + int selectedIndex = -1; + final int[] intVals = res.getIntArray(R.array.battery_saver_trigger_values); + String[] strVals = new String[intVals.length]; + for (int i = 0; i < intVals.length; i++) { + if (intVals[i] == value) { + selectedIndex = i; + } + if (intVals[i] > 0 && intVals[i] < 100) { + strVals[i] = res.getString(R.string.battery_saver_turn_on_automatically_pct, + Utils.formatPercentage(intVals[i])); + } else { + strVals[i] = + res.getString(R.string.battery_saver_turn_on_automatically_never); + } + } + + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) + .setTitle(R.string.battery_saver_turn_on_automatically_title) + .setSingleChoiceItems(strVals, + selectedIndex, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Settings.Global.putInt(getContentResolver(), + Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, + intVals[which]); + } + }) + .setPositiveButton(R.string.okay, null); + builder.create().show(); + return true; case MENU_HIGH_POWER_APPS: Bundle args = new Bundle(); @@ -181,6 +338,34 @@ public class PowerUsageSummary extends PowerUsageBase { mAppListGroup.addPreference(notAvailable); } + private void resetStats() { + AlertDialog dialog = new AlertDialog.Builder(getActivity()) + .setTitle(R.string.menu_stats_reset) + .setMessage(R.string.reset_stats_msg) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // Reset stats and request a refresh to initialize vars + mStatsHelper.resetStatistics(); + refreshStats(); + mHandler.removeMessages(MSG_REFRESH_STATS); + } + }) + .setNegativeButton(android.R.string.cancel, null) + .create(); + dialog.show(); + } + + private void refreshBatterySaverOptions() { + if (mBatterySaverPref != null) { + mBatterySaverPref.setEnabled(!mBatteryPluggedIn); + mBatterySaverPref.setChecked(!mBatteryPluggedIn && mPowerManager.isPowerSaveMode()); + mBatterySaverPref.setSummary(mBatteryPluggedIn + ? R.string.battery_saver_summary_unavailable + : R.string.battery_saver_summary); + } + } + private static boolean isSharedGid(int uid) { return UserHandle.getAppIdFromSharedAppGid(uid) > 0; } @@ -189,6 +374,13 @@ public class PowerUsageSummary extends PowerUsageBase { return uid >= Process.SYSTEM_UID && uid < Process.FIRST_APPLICATION_UID; } + private boolean isBatteryPluggedIn(Intent intent) { + int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, + BatteryManager.BATTERY_STATUS_UNKNOWN); + return status == BatteryManager.BATTERY_STATUS_CHARGING + || status == BatteryManager.BATTERY_STATUS_FULL; + } + /** * We want to coalesce some UIDs. For example, dex2oat runs under a shared gid that * exists for all users of the same app. We detect this case and merge the power use @@ -279,9 +471,40 @@ public class PowerUsageSummary extends PowerUsageBase { return results; } + private void updatePerformanceSummary() { + int value = mPerf.getPowerProfile(); + String summary = ""; + int count = mPerfProfileValues.length; + for (int i = 0; i < count; i++) { + try { + if (mPerfProfileValues[i].equals(String.valueOf(value))) { + summary = mPerfProfileEntries[i]; + } + } catch (IndexOutOfBoundsException ex) { + // Ignore + } + } + mPerfProfilePref.setSummary(String.format("%s", summary)); + } + + private void updatePerformanceValue() { + if (mPerfProfilePref == null) { + return; + } + mPerfProfilePref.setValue(String.valueOf(mPerf.getPowerProfile())); + mPerAppProfiles.setEnabled( + mPerf.getProfileHasAppProfiles(mPerf.getPowerProfile())); + updatePerformanceSummary(); + } + + private boolean sipperCanBePruned(BatterySipper sipper) { + return sipper.drainType != BatterySipper.DrainType.SCREEN; + } + protected void refreshStats() { super.refreshStats(); updatePreference(mHistPref); + mAppListGroup.removeAll(); mAppListGroup.setOrderingAsAdded(false); boolean addedSome = false; @@ -303,14 +526,16 @@ public class PowerUsageSummary extends PowerUsageBase { final int numSippers = usageList.size(); for (int i = 0; i < numSippers; i++) { final BatterySipper sipper = usageList.get(i); - if ((sipper.totalPowerMah * SECONDS_IN_HOUR) < MIN_POWER_THRESHOLD_MILLI_AMP) { - continue; - } double totalPower = USE_FAKE_DATA ? 4000 : mStatsHelper.getTotalPower(); final double percentOfTotal = ((sipper.totalPowerMah / totalPower) * dischargeAmount); - if (((int) (percentOfTotal + .5)) < 1) { - continue; + if (sipperCanBePruned(sipper)) { + if ((sipper.totalPowerMah * SECONDS_IN_HOUR) < MIN_POWER_THRESHOLD_MILLI_AMP) { + continue; + } + if (((int) (percentOfTotal + .5)) < 1) { + continue; + } } if (sipper.drainType == BatterySipper.DrainType.OVERCOUNTED) { // Don't show over-counted unless it is at least 2/3 the size of @@ -321,7 +546,7 @@ public class PowerUsageSummary extends PowerUsageBase { if (percentOfTotal < 10) { continue; } - if ("user".equals(Build.TYPE)) { + if ("user".equals(Build.TYPE) || "userdebug".equals(Build.TYPE)) { continue; } } @@ -334,7 +559,7 @@ public class PowerUsageSummary extends PowerUsageBase { if (percentOfTotal < 5) { continue; } - if ("user".equals(Build.TYPE)) { + if ("user".equals(Build.TYPE) || "userdebug".equals(Build.TYPE)) { continue; } } diff --git a/src/com/android/settings/hardware/VibratorIntensity.java b/src/com/android/settings/hardware/VibratorIntensity.java new file mode 100644 index 0000000..a778f36 --- /dev/null +++ b/src/com/android/settings/hardware/VibratorIntensity.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.hardware; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.graphics.Color; +import android.graphics.LightingColorFilter; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.os.Bundle; +import android.os.Vibrator; +import android.preference.DialogPreference; +import android.preference.PreferenceManager; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.widget.SeekBar; +import android.widget.TextView; +import android.widget.Button; + +import cyanogenmod.hardware.CMHardwareManager; +import cyanogenmod.providers.CMSettings; + +import com.android.settings.R; + +public class VibratorIntensity extends DialogPreference implements + SeekBar.OnSeekBarChangeListener, PreferenceManager.OnActivityStopListener { + private static final String PREF_NAME = "vibrator_intensity"; + private SeekBar mSeekBar; + private TextView mValue; + private TextView mWarning; + private int mOriginalValue; + private int mMinValue; + private int mMaxValue; + private int mDefaultValue; + private int mWarningValue; + private CMHardwareManager mHardware; + private final Vibrator mVibrator; + + private Drawable mProgressDrawable; + private Drawable mProgressThumb; + private LightingColorFilter mRedFilter; + + public VibratorIntensity(Context context, AttributeSet attrs) { + super(context, attrs); + + mHardware = CMHardwareManager.getInstance(context); + mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); + + if (!mHardware.isSupported(CMHardwareManager.FEATURE_VIBRATOR)) { + return; + } + + setDialogLayoutResource(R.layout.vibrator_intensity); + } + + public static boolean isSupported(Context context) { + CMHardwareManager hardware = CMHardwareManager.getInstance(context); + return hardware.isSupported(CMHardwareManager.FEATURE_VIBRATOR); + } + + @Override + protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { + builder.setNeutralButton(R.string.settings_reset_button, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + } + }); + } + + @Override + protected void onBindDialogView(View view) { + super.onBindDialogView(view); + + mSeekBar = (SeekBar) view.findViewById(com.android.internal.R.id.seekbar); + mValue = (TextView) view.findViewById(R.id.value); + mWarning = (TextView) view.findViewById(R.id.warning_text); + + // Read the current value in case user wants to dismiss his changes + mOriginalValue = mHardware.getVibratorIntensity(); + mWarningValue = mHardware.getVibratorWarningIntensity(); + mMinValue = mHardware.getVibratorMinIntensity(); + mMaxValue = mHardware.getVibratorMaxIntensity(); + mDefaultValue = mHardware.getVibratorDefaultIntensity(); + if (mWarningValue > 0) { + String message = getContext().getResources().getString( + R.string.vibrator_warning, intensityToPercent(mMinValue, mMaxValue, + mWarningValue)); + mWarning.setText(message); + } else if (mWarning != null) { + mWarning.setVisibility(View.GONE); + } + + Drawable progressDrawable = mSeekBar.getProgressDrawable(); + if (progressDrawable instanceof LayerDrawable) { + LayerDrawable ld = (LayerDrawable) progressDrawable; + mProgressDrawable = ld.findDrawableByLayerId(android.R.id.progress); + } + mProgressThumb = mSeekBar.getThumb(); + mRedFilter = new LightingColorFilter(Color.BLACK, + getContext().getResources().getColor(android.R.color.holo_red_light)); + + mSeekBar.setOnSeekBarChangeListener(this); + mSeekBar.setMax(mMaxValue - mMinValue); + mSeekBar.setProgress(mOriginalValue - mMinValue); + + getPreferenceManager().registerOnActivityStopListener(this); + } + + @Override + protected void showDialog(Bundle state) { + super.showDialog(state); + + // Can't use onPrepareDialogBuilder for this as we want the dialog + // to be kept open on click + AlertDialog d = (AlertDialog) getDialog(); + Button defaultsButton = d.getButton(DialogInterface.BUTTON_NEUTRAL); + defaultsButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mSeekBar.setProgress(mDefaultValue - mMinValue); + } + }); + } + + @Override + protected void onDialogClosed(boolean positiveResult) { + super.onDialogClosed(positiveResult); + + if (positiveResult) { + // Store percent value in SharedPreferences object + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); + int intensity = mSeekBar.getProgress() + mMinValue; + int percent = intensityToPercent(mMinValue, mMaxValue, intensity); + prefs.edit().putInt(PREF_NAME, percent).commit(); + CMSettings.Secure.putInt(getContext().getContentResolver(), + CMSettings.Secure.VIBRATOR_INTENSITY, intensity); + } else { + CMSettings.Secure.putInt(getContext().getContentResolver(), + CMSettings.Secure.VIBRATOR_INTENSITY, mOriginalValue); + } + + getPreferenceManager().unregisterOnActivityStopListener(this); + } + + @Override + public void onActivityStop() { + mHardware.setVibratorIntensity(mOriginalValue); + } + + public static void restore(Context context) { + CMHardwareManager hardware = CMHardwareManager.getInstance(context); + if (!hardware.isSupported(CMHardwareManager.FEATURE_VIBRATOR)) { + return; + } + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + int min = hardware.getVibratorMinIntensity(); + int max = hardware.getVibratorMaxIntensity(); + int defaultIntensity = hardware.getVibratorDefaultIntensity(); + int percent = prefs.getInt(PREF_NAME, intensityToPercent(min, max, defaultIntensity)); + + CMSettings.Secure.putInt(context.getContentResolver(), + CMSettings.Secure.VIBRATOR_INTENSITY, percentToIntensity(min, max, percent)); + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + int intensity = progress + mMinValue; + boolean shouldWarn = mWarningValue > 0 && intensity >= mWarningValue; + + if (mProgressDrawable != null) { + mProgressDrawable.setColorFilter(shouldWarn ? mRedFilter : null); + } + if (mProgressThumb != null) { + mProgressThumb.setColorFilter(shouldWarn ? mRedFilter : null); + } + + mValue.setText(String.format("%d%%", intensityToPercent(mMinValue, mMaxValue, intensity))); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + // Do nothing here + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + mHardware.setVibratorIntensity(seekBar.getProgress() + mMinValue); + Vibrator vib = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE); + vib.vibrate(200); + } + + private static int intensityToPercent(int minValue, int maxValue, int value) { + int percent = Math.round((value - minValue) * (100.f / (maxValue - minValue))); + + if (percent > 100) { + percent = 100; + } else if (percent < 0) { + percent = 0; + } + + return percent; + } + + private static int percentToIntensity(int minValue, int maxValue, int percent) { + int value = Math.round((((maxValue - minValue) * percent) / 100.f) + minValue); + + if (value > maxValue) { + value = maxValue; + } else if (value < minValue) { + value = minValue; + } + + return value; + } +} diff --git a/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java b/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java index e8ef5ef..7c531da 100644 --- a/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java +++ b/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java @@ -43,6 +43,7 @@ import android.provider.Settings; import android.provider.Settings.System; import android.speech.tts.TtsEngines; import android.text.TextUtils; +import android.util.Log; import android.view.InputDevice; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; @@ -64,6 +65,10 @@ import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.Indexable; import com.android.settings.search.SearchIndexableRaw; +import com.android.settings.voicewakeup.VoiceWakeupSettings; +import cyanogenmod.hardware.CMHardwareManager; +import cyanogenmod.providers.CMSettings; + import java.text.Collator; import java.util.ArrayList; import java.util.Collections; @@ -78,21 +83,43 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment implements Preference.OnPreferenceChangeListener, InputManager.InputDeviceListener, KeyboardLayoutDialogFragment.OnSetupKeyboardLayoutsListener, Indexable, InputMethodPreference.OnSavePreferenceListener { + + private static final String TAG = "InputMethodAndLanguageSettings"; + private static final String KEY_SPELL_CHECKERS = "spellcheckers_settings"; private static final String KEY_PHONE_LANGUAGE = "phone_language"; private static final String KEY_CURRENT_INPUT_METHOD = "current_input_method"; private static final String KEY_INPUT_METHOD_SELECTOR = "input_method_selector"; private static final String KEY_USER_DICTIONARY_SETTINGS = "key_user_dictionary_settings"; + private static final String KEY_POINTER_SETTINGS_CATEGORY = "pointer_settings_category"; private static final String KEY_PREVIOUSLY_ENABLED_SUBTYPES = "previously_enabled_subtypes"; + private static final String KEY_TOUCHSCREEN_HOVERING = "touchscreen_hovering"; + private static final String KEY_TRACKPAD_SETTINGS = "gesture_pad_settings"; + private static final String KEY_STYLUS_GESTURES = "stylus_gestures"; + private static final String KEY_STYLUS_ICON_ENABLED = "stylus_icon_enabled"; + private static final String KEY_VOICE_CATEGORY = "voice_category"; + private static final String KEY_VOICE_WAKEUP = "voice_wakeup"; + // false: on ICS or later private static final boolean SHOW_INPUT_METHOD_SWITCHER_SETTINGS = false; + private static final String[] sSystemSettingNames = { + System.TEXT_AUTO_REPLACE, System.TEXT_AUTO_CAPS, System.TEXT_AUTO_PUNCTUATE, + }; + + private static final String[] sHardKeyboardKeys = { + "auto_replace", "auto_caps", "auto_punctuate", + }; + private int mDefaultInputMethodSelectorVisibility = 0; private ListPreference mShowInputMethodSelectorPref; + private SwitchPreference mStylusIconEnabled; + private SwitchPreference mTouchscreenHovering; private PreferenceCategory mKeyboardSettingsCategory; private PreferenceCategory mHardKeyboardCategory; private PreferenceCategory mGameControllerCategory; private Preference mLanguagePref; + private PreferenceScreen mStylusGestures; private final ArrayList<InputMethodPreference> mInputMethodPreferenceList = new ArrayList<>(); private final ArrayList<PreferenceScreen> mHardKeyboardPreferenceList = new ArrayList<>(); private InputManager mIm; @@ -103,6 +130,7 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment private Intent mIntentWaitingForResult; private InputMethodSettingValuesWrapper mInputMethodSettingValues; private DevicePolicyManager mDpm; + private CMHardwareManager mHardware; @Override protected int getMetricsCategory() { @@ -119,6 +147,8 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment mImm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); mInputMethodSettingValues = InputMethodSettingValuesWrapper.getInstance(activity); + mHardware = CMHardwareManager.getInstance(activity); + try { mDefaultInputMethodSelectorVisibility = Integer.valueOf( getString(R.string.input_method_selector_visibility_default_value)); @@ -166,6 +196,45 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment mIm = (InputManager)activity.getSystemService(Context.INPUT_SERVICE); updateInputDevices(); + PreferenceCategory pointerSettingsCategory = (PreferenceCategory) + findPreference(KEY_POINTER_SETTINGS_CATEGORY); + + mStylusGestures = (PreferenceScreen) findPreference(KEY_STYLUS_GESTURES); + mStylusIconEnabled = (SwitchPreference) findPreference(KEY_STYLUS_ICON_ENABLED); + + mTouchscreenHovering = (SwitchPreference) findPreference(KEY_TOUCHSCREEN_HOVERING); + + if (pointerSettingsCategory != null) { + if (!getResources().getBoolean(com.android.internal.R.bool.config_stylusGestures)) { + pointerSettingsCategory.removePreference(mStylusGestures); + pointerSettingsCategory.removePreference(mStylusIconEnabled); + } + + if (!mHardware.isSupported(CMHardwareManager.FEATURE_TOUCH_HOVERING)) { + pointerSettingsCategory.removePreference(mTouchscreenHovering); + mTouchscreenHovering = null; + } else { + mTouchscreenHovering.setChecked( + mHardware.get(CMHardwareManager.FEATURE_TOUCH_HOVERING)); + } + + Utils.updatePreferenceToSpecificActivityFromMetaDataOrRemove(getActivity(), + pointerSettingsCategory, KEY_TRACKPAD_SETTINGS); + if (pointerSettingsCategory.getPreferenceCount() == 0) { + getPreferenceScreen().removePreference(pointerSettingsCategory); + } + } + + // Enable or disable mStatusBarImeSwitcher based on boolean: config_show_cmIMESwitcher + boolean showCmImeSwitcher = getResources().getBoolean( + com.android.internal.R.bool.config_show_cmIMESwitcher); + if (!showCmImeSwitcher) { + Preference pref = findPreference(CMSettings.System.STATUS_BAR_IME_SWITCHER); + if (pref != null) { + getPreferenceScreen().removePreference(pref); + } + } + // Spell Checker final Preference spellChecker = findPreference(KEY_SPELL_CHECKERS); if (spellChecker != null) { @@ -192,6 +261,20 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment if (mShowsOnlyFullImeAndKeyboardList && identifier != null) { showKeyboardLayoutDialog(identifier); } + + if (!Utils.isUserOwner() || + !Utils.isPackageInstalled(getActivity(), + VoiceWakeupSettings.VOICE_WAKEUP_PACKAGE, false)) { + PreferenceCategory voiceCategory = (PreferenceCategory) + findPreference(KEY_VOICE_CATEGORY); + if (voiceCategory != null) { + Preference wakeup = voiceCategory.findPreference(KEY_VOICE_WAKEUP); + if (wakeup != null) { + voiceCategory.removePreference(wakeup); + } + } + } + } private void updateInputMethodSelectorSummary(int value) { @@ -263,6 +346,11 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment } } + if (mStylusIconEnabled != null) { + mStylusIconEnabled.setChecked(Settings.System.getInt(getActivity().getContentResolver(), + Settings.System.STYLUS_ICON_ENABLED, 0) == 1); + } + if (!mShowsOnlyFullImeAndKeyboardList) { if (mLanguagePref != null) { String localeName = getLocaleName(getActivity()); @@ -275,6 +363,16 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment } } + // Hard keyboard + if (!mHardKeyboardPreferenceList.isEmpty()) { + for (int i = 0; i < sHardKeyboardKeys.length; ++i) { + SwitchPreference swPref = (SwitchPreference) + mHardKeyboardCategory.findPreference(sHardKeyboardKeys[i]); + swPref.setChecked( + System.getInt(getContentResolver(), sSystemSettingNames[i], 1) > 0); + } + } + updateInputDevices(); // Refresh internal states in mInputMethodSettingValues to keep the latest @@ -320,7 +418,16 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment if (Utils.isMonkeyRunning()) { return false; } - if (preference instanceof PreferenceScreen) { + if (preference == mStylusIconEnabled) { + Settings.System.putInt(getActivity().getContentResolver(), + Settings.System.STYLUS_ICON_ENABLED, mStylusIconEnabled.isChecked() ? 1 : 0); + } else if (preference == mTouchscreenHovering) { + boolean touchHoveringEnable = mTouchscreenHovering.isChecked(); + CMSettings.Secure.putInt(getActivity().getContentResolver(), + CMSettings.Secure.FEATURE_TOUCH_HOVERING, + touchHoveringEnable ? 1 : 0); + return true; + } else if (preference instanceof PreferenceScreen) { if (preference.getFragment() != null) { // Fragment will be handled correctly by the super class. } else if (KEY_CURRENT_INPUT_METHOD.equals(preference.getKey())) { @@ -335,6 +442,15 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment pref.isChecked() ? 1 : 0); return true; } + if (!mHardKeyboardPreferenceList.isEmpty()) { + for (int i = 0; i < sHardKeyboardKeys.length; ++i) { + if (pref == mHardKeyboardCategory.findPreference(sHardKeyboardKeys[i])) { + System.putInt(getContentResolver(), sSystemSettingNames[i], + pref.isChecked() ? 1 : 0); + return true; + } + } + } } return super.onPreferenceTreeClick(preferenceScreen, preference); } @@ -648,6 +764,18 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment } } + public static void restore(Context context) { + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + final CMHardwareManager hardware = CMHardwareManager.getInstance(context); + if (hardware.isSupported(CMHardwareManager.FEATURE_TOUCH_HOVERING)) { + final boolean enabled = prefs.getBoolean(KEY_TOUCHSCREEN_HOVERING, + hardware.get(CMHardwareManager.FEATURE_TOUCH_HOVERING)); + CMSettings.Secure.putInt(context.getContentResolver(), + CMSettings.Secure.FEATURE_TOUCH_HOVERING, + enabled ? 1 : 0); + } + } + public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new BaseSearchIndexProvider() { @Override @@ -789,6 +917,33 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment R.string.builtin_keyboard_settings_title); indexable.screenTitle = screenTitle; indexables.add(indexable); + + // Auto replace. + indexable = new SearchIndexableRaw(context); + indexable.key = "auto_replace"; + indexable.title = context.getString(R.string.auto_replace); + indexable.summaryOn = context.getString(R.string.auto_replace_summary); + indexable.summaryOff = context.getString(R.string.auto_replace_summary); + indexable.screenTitle = screenTitle; + indexables.add(indexable); + + // Auto caps. + indexable = new SearchIndexableRaw(context); + indexable.key = "auto_caps"; + indexable.title = context.getString(R.string.auto_caps); + indexable.summaryOn = context.getString(R.string.auto_caps_summary); + indexable.summaryOff = context.getString(R.string.auto_caps_summary); + indexable.screenTitle = screenTitle; + indexables.add(indexable); + + // Auto punctuate. + indexable = new SearchIndexableRaw(context); + indexable.key = "auto_punctuate"; + indexable.title = context.getString(R.string.auto_punctuate); + indexable.summaryOn = context.getString(R.string.auto_punctuate_summary); + indexable.summaryOff = context.getString(R.string.auto_punctuate_summary); + indexable.screenTitle = screenTitle; + indexables.add(indexable); } // Text-to-speech. diff --git a/src/com/android/settings/inputmethod/StylusGestures.java b/src/com/android/settings/inputmethod/StylusGestures.java new file mode 100644 index 0000000..1ca6ad3 --- /dev/null +++ b/src/com/android/settings/inputmethod/StylusGestures.java @@ -0,0 +1,208 @@ +/* Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.inputmethod; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.os.Bundle; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.PreferenceScreen; +import android.provider.Settings; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; + +import org.cyanogenmod.internal.logging.CMMetricsLogger; + +import java.util.Collections; +import java.util.List; + +public class StylusGestures extends SettingsPreferenceFragment implements + Preference.OnPreferenceChangeListener { + + public static final String TAG = "Stylus Gestures"; + public static final String KEY_SPEN_LEFT = "gestures_left"; + public static final String KEY_SPEN_RIGHT = "gestures_right"; + public static final String KEY_SPEN_UP = "gestures_up"; + public static final String KEY_SPEN_DOWN = "gestures_down"; + public static final String KEY_SPEN_LONG = "gestures_long"; + public static final String KEY_SPEN_DOUBLE = "gestures_double"; + public static final int KEY_NO_ACTION = 1000; + public static final String TEXT_NO_ACTION = "No Action"; + + private ListPreference mSwipeLeft; + private ListPreference mSwipeRight; + private ListPreference mSwipeUp; + private ListPreference mSwipeDown; + private ListPreference mSwipeLong; + private ListPreference mSwipeDouble; + + private Context mContext; + private ContentResolver mResolver; + + private String[] mActionNames; + private String[] mActionValues; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.gestures_prefs); + mContext = getActivity(); + mResolver = getContentResolver(); + + Resources resources = mContext.getResources(); + mActionNames = resources.getStringArray(R.array.gestures_entries); + mActionValues = resources.getStringArray(R.array.gestures_values); + + // Setup the gestures + mSwipeLeft = setupGesturePref(KEY_SPEN_LEFT, Settings.System.GESTURES_LEFT_SWIPE); + mSwipeRight = setupGesturePref(KEY_SPEN_RIGHT, Settings.System.GESTURES_RIGHT_SWIPE); + mSwipeUp = setupGesturePref(KEY_SPEN_UP, Settings.System.GESTURES_UP_SWIPE); + mSwipeDown = setupGesturePref(KEY_SPEN_DOWN, Settings.System.GESTURES_DOWN_SWIPE); + mSwipeLong = setupGesturePref(KEY_SPEN_LONG, Settings.System.GESTURES_LONG_PRESS); + mSwipeDouble = setupGesturePref(KEY_SPEN_DOUBLE, Settings.System.GESTURES_DOUBLE_TAP); + } + + @Override + protected int getMetricsCategory() { + return CMMetricsLogger.STYLUS_GESTURES; + } + + private ListPreference setupGesturePref(String key, String settingName) { + ListPreference pref = (ListPreference) findPreference(key); + String setting = Settings.System.getString(mResolver, settingName); + addApplicationEntries(pref, setting); + pref.setOnPreferenceChangeListener(this); + return pref; + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + String settingName = null; + + if (preference == mSwipeLeft) { + settingName = Settings.System.GESTURES_LEFT_SWIPE; + } else if (preference == mSwipeRight) { + settingName = Settings.System.GESTURES_RIGHT_SWIPE; + } else if (preference == mSwipeUp) { + settingName = Settings.System.GESTURES_UP_SWIPE; + } else if (preference == mSwipeDown) { + settingName = Settings.System.GESTURES_DOWN_SWIPE; + } else if (preference == mSwipeLong) { + settingName = Settings.System.GESTURES_LONG_PRESS; + } else if (preference == mSwipeDouble) { + settingName = Settings.System.GESTURES_DOUBLE_TAP; + } else { + return false; + } + + String packageName = newValue.toString(); + Settings.System.putString(mResolver, settingName, packageName); + setPrefValue((ListPreference)preference, packageName); + return true; + } + + private String mapUpdateValue(String time) { + for (int i = 0; i < mActionValues.length; i++) { + if (mActionValues[i].equalsIgnoreCase(time)) { + return mActionNames[i]; + } + } + return null; + } + + private void setPrefValue(ListPreference pref, String packageName) { + if (packageName == null) { + packageName = String.valueOf(KEY_NO_ACTION); + } + + String text = mapUpdateValue(packageName); + if (text != null) { + pref.setValue(packageName); + pref.setSummary(text); + } else { + CharSequence appName = getAppName(packageName); + if (appName != null) { + pref.setValue(packageName); + pref.setSummary(appName); + } else { + pref.setSummary(mContext.getString(R.string.stylus_app_not_installed, + packageName)); + } + } + + } + + private void addApplicationEntries(ListPreference pref, String packageName) { + PackageManager pm = getPackageManager(); + + Intent intent = new Intent(Intent.ACTION_MAIN, null); + intent.addCategory(Intent.CATEGORY_LAUNCHER); + List<ResolveInfo> list = pm.queryIntentActivities(intent, + PackageManager.PERMISSION_GRANTED); + + Collections.sort(list, new ResolveInfo.DisplayNameComparator(pm)); + + int count = list.size() + mActionValues.length; + CharSequence[] entries = new CharSequence[count]; + CharSequence[] values = new CharSequence[count]; + + // Step 1: copy in predefined actions + for (int i = 0; i < mActionValues.length; i++) { + entries[i] = mActionNames[i]; + values[i] = mActionValues[i]; + } + + // Step 2: copy in resolved activities + for (int i = mActionValues.length; i < count; i++) { + ResolveInfo info = list.get(i - mActionValues.length); + CharSequence label = info.loadLabel(pm); + if (label == null) { + label = info.activityInfo.name; + } + + entries[i] = label; + values[i] = info.activityInfo.applicationInfo.packageName; + } + + pref.setEntries(entries); + pref.setEntryValues(values); + setPrefValue(pref, packageName); + } + + private CharSequence getAppName(String packageName) { + final PackageManager pm = mContext.getPackageManager(); + ApplicationInfo ai; + + try { + ai = pm.getApplicationInfo(packageName, 0); + } catch (final NameNotFoundException e) { + ai = null; + } + + if (ai != null) { + return pm.getApplicationLabel(ai); + } + return null; + } +} diff --git a/src/com/android/settings/livedisplay/DisplayColor.java b/src/com/android/settings/livedisplay/DisplayColor.java new file mode 100644 index 0000000..f6fee82 --- /dev/null +++ b/src/com/android/settings/livedisplay/DisplayColor.java @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2013-2015 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.livedisplay; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.preference.DialogPreference; +import android.util.AttributeSet; +import android.view.View; +import android.widget.Button; +import android.widget.SeekBar; +import android.widget.TextView; + +import com.android.settings.IntervalSeekBar; +import com.android.settings.R; + +import cyanogenmod.hardware.LiveDisplayManager; + +/** + * Special preference type that allows configuration of Color settings + */ +public class DisplayColor extends DialogPreference { + private static final String TAG = "ColorCalibration"; + + private final Context mContext; + private final LiveDisplayManager mLiveDisplay; + + // These arrays must all match in length and order + private static final int[] SEEKBAR_ID = new int[] { + R.id.color_red_seekbar, + R.id.color_green_seekbar, + R.id.color_blue_seekbar + }; + + private static final int[] SEEKBAR_VALUE_ID = new int[] { + R.id.color_red_value, + R.id.color_green_value, + R.id.color_blue_value + }; + + private ColorSeekBar[] mSeekBars = new ColorSeekBar[SEEKBAR_ID.length]; + + private final float[] mCurrentColors = new float[3]; + private final float[] mOriginalColors = new float[3]; + + public DisplayColor(Context context, AttributeSet attrs) { + super(context, attrs); + + mContext = context; + mLiveDisplay = LiveDisplayManager.getInstance(mContext); + + setDialogLayoutResource(R.layout.display_color_calibration); + } + + @Override + protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { + builder.setNeutralButton(R.string.settings_reset_button, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + } + }); + } + + @Override + protected void onBindDialogView(View view) { + super.onBindDialogView(view); + + System.arraycopy(mLiveDisplay.getColorAdjustment(), 0, mOriginalColors, 0, 3); + System.arraycopy(mOriginalColors, 0, mCurrentColors, 0, 3); + + for (int i = 0; i < SEEKBAR_ID.length; i++) { + IntervalSeekBar seekBar = (IntervalSeekBar) view.findViewById(SEEKBAR_ID[i]); + TextView value = (TextView) view.findViewById(SEEKBAR_VALUE_ID[i]); + mSeekBars[i] = new ColorSeekBar(seekBar, value, i); + mSeekBars[i].mSeekBar.setMinimum(0.1f); + mSeekBars[i].mSeekBar.setMaximum(1.0f); + + mSeekBars[i].mSeekBar.setProgressFloat(mCurrentColors[i]); + int percent = Math.round(100F * mCurrentColors[i]); + value.setText(String.format("%d%%", percent)); + } + } + + @Override + protected void showDialog(Bundle state) { + super.showDialog(state); + + // Can't use onPrepareDialogBuilder for this as we want the dialog + // to be kept open on click + AlertDialog d = (AlertDialog) getDialog(); + Button defaultsButton = d.getButton(DialogInterface.BUTTON_NEUTRAL); + defaultsButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + for (int i = 0; i < mSeekBars.length; i++) { + mSeekBars[i].mSeekBar.setProgressFloat(1.0f); + mCurrentColors[i] = 1.0f; + } + updateColors(mCurrentColors); + } + }); + } + + @Override + protected void onDialogClosed(boolean positiveResult) { + super.onDialogClosed(positiveResult); + updateColors(positiveResult ? mCurrentColors : mOriginalColors); + } + + @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.currentColors = mCurrentColors; + myState.originalColors = mOriginalColors; + + // Restore the old state when the activity or dialog is being paused + updateColors(mOriginalColors); + + 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()); + + System.arraycopy(myState.originalColors, 0, mOriginalColors, 0, 3); + System.arraycopy(myState.currentColors, 0, mCurrentColors, 0, 3); + for (int i = 0; i < mSeekBars.length; i++) { + mSeekBars[i].mSeekBar.setProgressFloat(mCurrentColors[i]); + } + updateColors(mCurrentColors); + } + + private static class SavedState extends BaseSavedState { + float[] originalColors; + float[] currentColors; + + public SavedState(Parcelable superState) { + super(superState); + } + + public SavedState(Parcel source) { + super(source); + originalColors = source.createFloatArray(); + currentColors = source.createFloatArray(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeFloatArray(originalColors); + dest.writeFloatArray(currentColors); + } + + public static final Parcelable.Creator<SavedState> CREATOR = + new Parcelable.Creator<SavedState>() { + + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } + + private void updateColors(float[] colors) { + mLiveDisplay.setColorAdjustment(colors); + } + + private class ColorSeekBar implements SeekBar.OnSeekBarChangeListener { + private int mIndex; + private final IntervalSeekBar mSeekBar; + private TextView mValue; + + public ColorSeekBar(IntervalSeekBar seekBar, TextView value, int index) { + mSeekBar = seekBar; + mValue = value; + mIndex = index; + + mSeekBar.setOnSeekBarChangeListener(this); + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + IntervalSeekBar isb = (IntervalSeekBar)seekBar; + float fp = isb.getProgressFloat(); + if (fromUser) { + mCurrentColors[mIndex] = fp > 1.0f ? 1.0f : fp; + updateColors(mCurrentColors); + } + + int percent = Math.round(100F * fp); + mValue.setText(String.format("%d%%", percent)); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + // Do nothing here + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + // Do nothing here + } + } +} diff --git a/src/com/android/settings/livedisplay/DisplayTemperature.java b/src/com/android/settings/livedisplay/DisplayTemperature.java new file mode 100644 index 0000000..350ef62 --- /dev/null +++ b/src/com/android/settings/livedisplay/DisplayTemperature.java @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2015 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.livedisplay; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.preference.DialogPreference; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.SeekBar; +import android.widget.TextView; + +import com.android.settings.R; + +import org.cyanogenmod.internal.util.MathUtils; + +import cyanogenmod.hardware.LiveDisplayConfig; +import cyanogenmod.hardware.LiveDisplayManager; + +/** + * Preference for selection of color temperature range for LiveDisplay + */ +public class DisplayTemperature extends DialogPreference { + private static final String TAG = "DisplayTemperature"; + + private final Context mContext; + + private ColorTemperatureSeekBar mDayTemperature; + private ColorTemperatureSeekBar mNightTemperature; + + private int mOriginalDayTemperature; + private int mOriginalNightTemperature; + + private final LiveDisplayManager mLiveDisplay; + private final LiveDisplayConfig mConfig; + + private static final int STEP = 100; + + public DisplayTemperature(Context context, AttributeSet attrs) { + super(context, attrs); + mContext = context; + mLiveDisplay = LiveDisplayManager.getInstance(mContext); + mConfig = mLiveDisplay.getConfig(); + + setDialogLayoutResource(R.layout.display_temperature); + } + + @Override + protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { + builder.setNeutralButton(R.string.settings_reset_button, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + } + }); + } + + @Override + protected void onBindDialogView(View view) { + super.onBindDialogView(view); + + mOriginalDayTemperature = mLiveDisplay.getDayColorTemperature(); + mOriginalNightTemperature = mLiveDisplay.getNightColorTemperature(); + + SeekBar day = (SeekBar) view.findViewById(R.id.day_temperature_seekbar); + TextView dayText = (TextView) view.findViewById(R.id.day_temperature_value); + mDayTemperature = new ColorTemperatureSeekBar(day, dayText); + + SeekBar night = (SeekBar) view.findViewById(R.id.night_temperature_seekbar); + TextView nightText = (TextView) view.findViewById(R.id.night_temperature_value); + mNightTemperature = new ColorTemperatureSeekBar(night, nightText); + + mDayTemperature.setTemperature(mOriginalDayTemperature); + mNightTemperature.setTemperature(mOriginalNightTemperature); + } + + @Override + protected void showDialog(Bundle state) { + super.showDialog(state); + + // Can't use onPrepareDialogBuilder for this as we want the dialog + // to be kept open on click + AlertDialog d = (AlertDialog) getDialog(); + Button defaultsButton = d.getButton(DialogInterface.BUTTON_NEUTRAL); + defaultsButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mDayTemperature.setTemperature(mConfig.getDefaultDayTemperature()); + mNightTemperature.setTemperature(mConfig.getDefaultNightTemperature()); + updateTemperature(true); + } + }); + } + + @Override + protected void onDialogClosed(boolean positiveResult) { + super.onDialogClosed(positiveResult); + updateTemperature(positiveResult); + } + + @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.originalDayTemperature = mOriginalDayTemperature; + myState.originalNightTemperature = mOriginalNightTemperature; + myState.currentDayTemperature = mDayTemperature.getTemperature(); + myState.currentNightTemperature = mNightTemperature.getTemperature(); + + // Restore the old state when the activity or dialog is being paused + updateTemperature(false); + + 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()); + + mOriginalDayTemperature = myState.originalDayTemperature; + mOriginalNightTemperature = myState.originalNightTemperature; + mDayTemperature.setTemperature(myState.currentDayTemperature); + mNightTemperature.setTemperature(myState.currentNightTemperature);; + + updateTemperature(true); + } + + private static class SavedState extends BaseSavedState { + int originalDayTemperature; + int originalNightTemperature; + int currentDayTemperature; + int currentNightTemperature; + + public SavedState(Parcelable superState) { + super(superState); + } + + public SavedState(Parcel source) { + super(source); + originalDayTemperature = source.readInt(); + originalNightTemperature = source.readInt(); + currentDayTemperature = source.readInt(); + currentNightTemperature = source.readInt(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(originalDayTemperature); + dest.writeInt(originalNightTemperature); + dest.writeInt(currentDayTemperature); + dest.writeInt(currentNightTemperature); + } + + public static final Parcelable.Creator<SavedState> CREATOR = + new Parcelable.Creator<SavedState>() { + + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } + + private void updateTemperature(boolean accept) { + int day = accept ? mDayTemperature.getTemperature() : mOriginalDayTemperature; + int night = accept ? mNightTemperature.getTemperature() : mOriginalNightTemperature; + callChangeListener(new Integer[] { day, night }); + + mLiveDisplay.setDayColorTemperature(day); + mLiveDisplay.setNightColorTemperature(night); + } + + int roundUp(int value) { + return ((value + STEP / 2) / STEP) * STEP; + } + + private class ColorTemperatureSeekBar implements SeekBar.OnSeekBarChangeListener { + private final SeekBar mSeekBar; + private final TextView mValue; + + private final int mMin; + private final int mMax; + + private final int mBalanceMin; + private final int mBalanceMax; + + private final int mBarMax; + + private final boolean mUseBalance; + private final double[] mBalanceCurve; + + public ColorTemperatureSeekBar(SeekBar seekBar, TextView value) { + mSeekBar = seekBar; + mValue = value; + mMin = mConfig.getColorTemperatureRange().getLower(); + mMax = mConfig.getColorTemperatureRange().getUpper(); + mBalanceMin = mConfig.getColorBalanceRange().getLower(); + mBalanceMax = mConfig.getColorBalanceRange().getUpper(); + mUseBalance = mConfig.hasFeature(LiveDisplayManager.FEATURE_COLOR_BALANCE) && + ((mBalanceMin != 0) || (mBalanceMax != 0)); + + if (mUseBalance) { + mBalanceCurve = MathUtils.powerCurve(mMin, mConfig.getDefaultDayTemperature(), mMax); + mBarMax = mBalanceMax - mBalanceMin; + } else { + mBalanceCurve = null; + mBarMax = (mMax - mMin) / STEP; + } + mSeekBar.setMax(mBarMax); + mSeekBar.setOnSeekBarChangeListener(this); + + // init text value + int p = mSeekBar.getProgress(); + onProgressChanged(mSeekBar, p, false); + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser) { + updateTemperature(true); + } + + int displayValue; + if (mUseBalance) { + displayValue = roundUp(Math.round((float)MathUtils.linearToPowerCurve( + mBalanceCurve, (double)progress / (double)mBarMax))); + } else { + displayValue = progress * STEP + mMin; + } + Log.d(TAG, "onProgressChanged: progress=" + progress + " displayValue=" + displayValue); + + mValue.setText(mContext.getResources().getString( + R.string.live_display_color_temperature_label, displayValue)); + } + + public void setTemperature(int temperature) { + if (mUseBalance) { + double z = MathUtils.powerCurveToLinear(mBalanceCurve, (double)temperature); + mSeekBar.setProgress(Math.round((float)(z * (double)mBarMax))); + return; + } + int p = Math.max(temperature, mMin) - mMin; + mSeekBar.setProgress(Math.round((float) p / STEP)); + } + + public int getTemperature() { + if (mUseBalance) { + return Math.round((float)MathUtils.linearToPowerCurve( + mBalanceCurve, (double)mSeekBar.getProgress() / (double)mBarMax)); + } + return mSeekBar.getProgress() * STEP + mMin; + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + // Do nothing here + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + // Do nothing here + } + } +} diff --git a/src/com/android/settings/livedisplay/LiveDisplay.java b/src/com/android/settings/livedisplay/LiveDisplay.java new file mode 100644 index 0000000..a3f4636 --- /dev/null +++ b/src/com/android/settings/livedisplay/LiveDisplay.java @@ -0,0 +1,426 @@ +/* + * Copyright (C) 2015 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.livedisplay; + +import static cyanogenmod.hardware.LiveDisplayManager.FEATURE_CABC; +import static cyanogenmod.hardware.LiveDisplayManager.FEATURE_COLOR_ADJUSTMENT; +import static cyanogenmod.hardware.LiveDisplayManager.FEATURE_COLOR_ENHANCEMENT; +import static cyanogenmod.hardware.LiveDisplayManager.FEATURE_DISPLAY_MODES; +import static cyanogenmod.hardware.LiveDisplayManager.FEATURE_PICTURE_ADJUSTMENT; +import static cyanogenmod.hardware.LiveDisplayManager.MODE_OFF; +import static cyanogenmod.hardware.LiveDisplayManager.MODE_OUTDOOR; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.UserHandle; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.PreferenceCategory; +import android.preference.PreferenceScreen; +import android.preference.SwitchPreference; +import android.provider.SearchIndexableResource; +import android.util.Log; + +import com.android.internal.util.ArrayUtils; +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.Utils; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settings.search.Indexable; + +import org.cyanogenmod.internal.logging.CMMetricsLogger; + +import java.util.ArrayList; +import java.util.List; + +import cyanogenmod.hardware.CMHardwareManager; +import cyanogenmod.hardware.DisplayMode; +import cyanogenmod.hardware.LiveDisplayConfig; +import cyanogenmod.hardware.LiveDisplayManager; +import cyanogenmod.providers.CMSettings; + +public class LiveDisplay extends SettingsPreferenceFragment implements + Preference.OnPreferenceChangeListener, Indexable { + + private static final String TAG = "LiveDisplay"; + + private static final String KEY_CATEGORY_LIVE_DISPLAY = "live_display_options"; + private static final String KEY_CATEGORY_ADVANCED = "advanced"; + + private static final String KEY_LIVE_DISPLAY = "live_display"; + private static final String KEY_LIVE_DISPLAY_AUTO_OUTDOOR_MODE = + "display_auto_outdoor_mode"; + private static final String KEY_LIVE_DISPLAY_LOW_POWER = "display_low_power"; + private static final String KEY_LIVE_DISPLAY_COLOR_ENHANCE = "display_color_enhance"; + private static final String KEY_LIVE_DISPLAY_TEMPERATURE = "live_display_color_temperature"; + + private static final String KEY_DISPLAY_COLOR = "color_calibration"; + private static final String KEY_PICTURE_ADJUSTMENT = "picture_adjustment"; + + private static final String KEY_LIVE_DISPLAY_COLOR_PROFILE = "live_display_color_profile"; + + private final Handler mHandler = new Handler(); + private final SettingsObserver mObserver = new SettingsObserver(); + + private ListPreference mLiveDisplay; + + private SwitchPreference mColorEnhancement; + private SwitchPreference mLowPower; + private SwitchPreference mOutdoorMode; + + private PictureAdjustment mPictureAdjustment; + private DisplayTemperature mDisplayTemperature; + private DisplayColor mDisplayColor; + + private ListPreference mColorProfile; + private String[] mColorProfileSummaries; + + private String[] mModeEntries; + private String[] mModeValues; + private String[] mModeSummaries; + + private boolean mHasDisplayModes = false; + + private LiveDisplayManager mLiveDisplayManager; + private LiveDisplayConfig mConfig; + + private CMHardwareManager mHardware; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final Resources res = getResources(); + + mHardware = CMHardwareManager.getInstance(getActivity()); + mLiveDisplayManager = LiveDisplayManager.getInstance(getActivity()); + mConfig = mLiveDisplayManager.getConfig(); + + addPreferencesFromResource(R.xml.livedisplay); + + PreferenceCategory liveDisplayPrefs = (PreferenceCategory) + findPreference(KEY_CATEGORY_LIVE_DISPLAY); + PreferenceCategory advancedPrefs = (PreferenceCategory) + findPreference(KEY_CATEGORY_ADVANCED); + + int adaptiveMode = mLiveDisplayManager.getMode(); + + mLiveDisplay = (ListPreference) findPreference(KEY_LIVE_DISPLAY); + mLiveDisplay.setValue(String.valueOf(adaptiveMode)); + + mModeEntries = res.getStringArray( + org.cyanogenmod.platform.internal.R.array.live_display_entries); + mModeValues = res.getStringArray( + org.cyanogenmod.platform.internal.R.array.live_display_values); + mModeSummaries = res.getStringArray( + org.cyanogenmod.platform.internal.R.array.live_display_summaries); + + // Remove outdoor mode from lists if there is no support + if (!mConfig.hasFeature(LiveDisplayManager.MODE_OUTDOOR)) { + int idx = ArrayUtils.indexOf(mModeValues, String.valueOf(MODE_OUTDOOR)); + String[] entriesTemp = new String[mModeEntries.length - 1]; + String[] valuesTemp = new String[mModeValues.length - 1]; + String[] summariesTemp = new String[mModeSummaries.length - 1]; + int j = 0; + for (int i = 0; i < mModeEntries.length; i++) { + if (i == idx) { + continue; + } + entriesTemp[j] = mModeEntries[i]; + valuesTemp[j] = mModeValues[i]; + summariesTemp[j] = mModeSummaries[i]; + j++; + } + mModeEntries = entriesTemp; + mModeValues = valuesTemp; + mModeSummaries = summariesTemp; + } + + mLiveDisplay.setEntries(mModeEntries); + mLiveDisplay.setEntryValues(mModeValues); + mLiveDisplay.setOnPreferenceChangeListener(this); + + mDisplayTemperature = (DisplayTemperature) findPreference(KEY_LIVE_DISPLAY_TEMPERATURE); + + mColorProfile = (ListPreference) findPreference(KEY_LIVE_DISPLAY_COLOR_PROFILE); + if (liveDisplayPrefs != null && mColorProfile != null + && (!mConfig.hasFeature(FEATURE_DISPLAY_MODES) || !updateDisplayModes())) { + liveDisplayPrefs.removePreference(mColorProfile); + } else { + mHasDisplayModes = true; + mColorProfile.setOnPreferenceChangeListener(this); + } + + mOutdoorMode = (SwitchPreference) findPreference(KEY_LIVE_DISPLAY_AUTO_OUTDOOR_MODE); + if (liveDisplayPrefs != null && mOutdoorMode != null + && !mConfig.hasFeature(MODE_OUTDOOR)) { + liveDisplayPrefs.removePreference(mOutdoorMode); + mOutdoorMode = null; + } + + mLowPower = (SwitchPreference) findPreference(KEY_LIVE_DISPLAY_LOW_POWER); + if (advancedPrefs != null && mLowPower != null + && !mConfig.hasFeature(FEATURE_CABC)) { + advancedPrefs.removePreference(mLowPower); + mLowPower = null; + } + + mColorEnhancement = (SwitchPreference) findPreference(KEY_LIVE_DISPLAY_COLOR_ENHANCE); + if (advancedPrefs != null && mColorEnhancement != null + && !mConfig.hasFeature(FEATURE_COLOR_ENHANCEMENT)) { + advancedPrefs.removePreference(mColorEnhancement); + mColorEnhancement = null; + } + + mPictureAdjustment = (PictureAdjustment) findPreference(KEY_PICTURE_ADJUSTMENT); + if (advancedPrefs != null && mPictureAdjustment != null && + !mConfig.hasFeature(LiveDisplayManager.FEATURE_PICTURE_ADJUSTMENT)) { + advancedPrefs.removePreference(mPictureAdjustment); + mPictureAdjustment = null; + } + + mDisplayColor = (DisplayColor) findPreference(KEY_DISPLAY_COLOR); + if (advancedPrefs != null && mDisplayColor != null && + !mConfig.hasFeature(LiveDisplayManager.FEATURE_COLOR_ADJUSTMENT)) { + advancedPrefs.removePreference(mDisplayColor); + mDisplayColor = null; + } + } + + @Override + protected int getMetricsCategory() { + return CMMetricsLogger.LIVE_DISPLAY; + } + + @Override + public void onResume() { + super.onResume(); + updateModeSummary(); + updateTemperatureSummary(); + updateColorProfileSummary(null); + mObserver.register(true); + } + + @Override + public void onPause() { + super.onPause(); + mObserver.register(false); + } + + private String getStringForResourceName(String resourceName, String defaultValue) { + Resources res = getResources(); + int resId = res.getIdentifier(resourceName, "string", "com.android.settings"); + if (resId <= 0) { + Log.e(TAG, "No resource found for " + resourceName); + return defaultValue; + } else { + return res.getString(resId); + } + } + + private boolean updateDisplayModes() { + final DisplayMode[] modes = mHardware.getDisplayModes(); + if (modes == null || modes.length == 0) { + return false; + } + + final DisplayMode cur = mHardware.getCurrentDisplayMode() != null + ? mHardware.getCurrentDisplayMode() : mHardware.getDefaultDisplayMode(); + int curId = -1; + String[] entries = new String[modes.length]; + String[] values = new String[modes.length]; + mColorProfileSummaries = new String[modes.length]; + for (int i = 0; i < modes.length; i++) { + values[i] = String.valueOf(modes[i].id); + String name = modes[i].name.toLowerCase().replace(" ", "_"); + String nameRes = String.format("live_display_color_profile_%s_title", name); + entries[i] = getStringForResourceName(nameRes, modes[i].name); + + // Populate summary + String summaryRes = String.format("live_display_color_profile_%s_summary", name); + String summary = getStringForResourceName(summaryRes, null); + if (summary != null) { + summary = String.format("%s - %s", entries[i], summary); + } + mColorProfileSummaries[i] = summary; + + if (cur != null && modes[i].id == cur.id) { + curId = cur.id; + } + } + mColorProfile.setEntries(entries); + mColorProfile.setEntryValues(values); + if (curId >= 0) { + mColorProfile.setValue(String.valueOf(curId)); + } + + return true; + } + + private void updateColorProfileSummary(String value) { + if (!mHasDisplayModes) { + return; + } + + if (value == null) { + DisplayMode cur = mHardware.getCurrentDisplayMode() != null + ? mHardware.getCurrentDisplayMode() : mHardware.getDefaultDisplayMode(); + if (cur != null && cur.id >= 0) { + value = String.valueOf(cur.id); + } + } + + int idx = mColorProfile.findIndexOfValue(value); + if (idx < 0) { + Log.e(TAG, "No summary resource found for profile " + value); + mColorProfile.setSummary(null); + return; + } + + mColorProfile.setValue(value); + mColorProfile.setSummary(mColorProfileSummaries[idx]); + } + + private void updateModeSummary() { + int mode = mLiveDisplayManager.getMode(); + + int index = ArrayUtils.indexOf(mModeValues, String.valueOf(mode)); + if (index < 0) { + index = ArrayUtils.indexOf(mModeValues, String.valueOf(MODE_OFF)); + } + + mLiveDisplay.setSummary(mModeSummaries[index]); + mLiveDisplay.setValue(String.valueOf(mode)); + + if (mDisplayTemperature != null) { + mDisplayTemperature.setEnabled(mode != MODE_OFF); + } + if (mOutdoorMode != null) { + mOutdoorMode.setEnabled(mode != MODE_OFF); + } + } + + private void updateTemperatureSummary() { + int day = mLiveDisplayManager.getDayColorTemperature(); + int night = mLiveDisplayManager.getNightColorTemperature(); + + mDisplayTemperature.setSummary(getResources().getString( + R.string.live_display_color_temperature_summary, + mDisplayTemperature.roundUp(day), + mDisplayTemperature.roundUp(night))); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object objValue) { + if (preference == mLiveDisplay) { + mLiveDisplayManager.setMode(Integer.valueOf((String)objValue)); + } else if (preference == mColorProfile) { + int id = Integer.valueOf((String)objValue); + Log.i("LiveDisplay", "Setting mode: " + id); + for (DisplayMode mode : mHardware.getDisplayModes()) { + if (mode.id == id) { + mHardware.setDisplayMode(mode, true); + updateColorProfileSummary((String)objValue); + break; + } + } + } + return true; + } + + private static boolean isPostProcessingSupported(Context context) { + return Utils.isPackageInstalled(context, "com.qualcomm.display"); + } + + private final class SettingsObserver extends ContentObserver { + private final Uri DISPLAY_TEMPERATURE_DAY_URI = + CMSettings.System.getUriFor(CMSettings.System.DISPLAY_TEMPERATURE_DAY); + private final Uri DISPLAY_TEMPERATURE_NIGHT_URI = + CMSettings.System.getUriFor(CMSettings.System.DISPLAY_TEMPERATURE_NIGHT); + private final Uri DISPLAY_TEMPERATURE_MODE_URI = + CMSettings.System.getUriFor(CMSettings.System.DISPLAY_TEMPERATURE_MODE); + + public SettingsObserver() { + super(mHandler); + } + + public void register(boolean register) { + final ContentResolver cr = getContentResolver(); + if (register) { + cr.registerContentObserver(DISPLAY_TEMPERATURE_DAY_URI, false, this, UserHandle.USER_ALL); + cr.registerContentObserver(DISPLAY_TEMPERATURE_NIGHT_URI, false, this, UserHandle.USER_ALL); + cr.registerContentObserver(DISPLAY_TEMPERATURE_MODE_URI, false, this, UserHandle.USER_ALL); + } else { + cr.unregisterContentObserver(this); + } + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + super.onChange(selfChange, uri); + updateModeSummary(); + updateTemperatureSummary(); + } + } + + public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider() { + + @Override + public List<SearchIndexableResource> getXmlResourcesToIndex(Context context, + boolean enabled) { + ArrayList<SearchIndexableResource> result = + new ArrayList<SearchIndexableResource>(); + + SearchIndexableResource sir = new SearchIndexableResource(context); + sir.xmlResId = R.xml.livedisplay; + result.add(sir); + + return result; + } + + @Override + public List<String> getNonIndexableKeys(Context context) { + final CMHardwareManager hardware = CMHardwareManager.getInstance(context); + final LiveDisplayConfig config = LiveDisplayManager.getInstance(context).getConfig(); + + ArrayList<String> result = new ArrayList<String>(); + if (!hardware.isSupported(FEATURE_DISPLAY_MODES)) { + result.add(KEY_LIVE_DISPLAY_COLOR_PROFILE); + } + if (!config.hasFeature(MODE_OUTDOOR)) { + result.add(KEY_LIVE_DISPLAY_AUTO_OUTDOOR_MODE); + } + if (!config.hasFeature(FEATURE_COLOR_ENHANCEMENT)) { + result.add(KEY_LIVE_DISPLAY_COLOR_ENHANCE); + } + if (!config.hasFeature(FEATURE_CABC)) { + result.add(KEY_LIVE_DISPLAY_LOW_POWER); + } + if (!config.hasFeature(FEATURE_COLOR_ADJUSTMENT)) { + result.add(KEY_DISPLAY_COLOR); + } + if (!config.hasFeature(FEATURE_PICTURE_ADJUSTMENT)) { + result.add(KEY_PICTURE_ADJUSTMENT); + } + return result; + } + }; +} diff --git a/src/com/android/settings/livedisplay/PictureAdjustment.java b/src/com/android/settings/livedisplay/PictureAdjustment.java new file mode 100644 index 0000000..657d7d4 --- /dev/null +++ b/src/com/android/settings/livedisplay/PictureAdjustment.java @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.livedisplay; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.preference.DialogPreference; +import android.util.AttributeSet; +import android.util.Range; +import android.view.View; +import android.widget.Button; +import android.widget.SeekBar; +import android.widget.TextView; + +import com.android.settings.IntervalSeekBar; +import com.android.settings.R; + +import cyanogenmod.hardware.HSIC; +import cyanogenmod.hardware.LiveDisplayManager; + +import java.util.List; + +/** + * Special preference type that allows configuration of Color settings + */ +public class PictureAdjustment extends DialogPreference { + private static final String TAG = "PictureAdjustment"; + + private final Context mContext; + private final LiveDisplayManager mLiveDisplay; + private final List<Range<Float>> mRanges; + + // These arrays must all match in length and order + private static final int[] SEEKBAR_ID = new int[] { + R.id.adj_hue_seekbar, + R.id.adj_saturation_seekbar, + R.id.adj_intensity_seekbar, + R.id.adj_contrast_seekbar + }; + + private static final int[] SEEKBAR_VALUE_ID = new int[] { + R.id.adj_hue_value, + R.id.adj_saturation_value, + R.id.adj_intensity_value, + R.id.adj_contrast_value + }; + + private ColorSeekBar[] mSeekBars = new ColorSeekBar[SEEKBAR_ID.length]; + + private final float[] mCurrentAdj = new float[5]; + private final float[] mOriginalAdj = new float[5]; + + public PictureAdjustment(Context context, AttributeSet attrs) { + super(context, attrs); + + mContext = context; + mLiveDisplay = LiveDisplayManager.getInstance(mContext); + mRanges = mLiveDisplay.getConfig().getPictureAdjustmentRanges(); + + setDialogLayoutResource(R.layout.display_picture_adjustment); + } + + @Override + protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { + builder.setNeutralButton(R.string.settings_reset_button, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + } + }); + } + + private void updateBars() { + for (int i = 0; i < SEEKBAR_ID.length; i++) { + mSeekBars[i].setValue(mCurrentAdj[i]); + } + } + + @Override + protected void onBindDialogView(View view) { + super.onBindDialogView(view); + + System.arraycopy(mLiveDisplay.getPictureAdjustment().toFloatArray(), 0, mOriginalAdj, 0, 5); + System.arraycopy(mOriginalAdj, 0, mCurrentAdj, 0, 5); + + for (int i = 0; i < SEEKBAR_ID.length; i++) { + IntervalSeekBar seekBar = (IntervalSeekBar) view.findViewById(SEEKBAR_ID[i]); + TextView value = (TextView) view.findViewById(SEEKBAR_VALUE_ID[i]); + final Range<Float> range = mRanges.get(i); + mSeekBars[i] = new ColorSeekBar(seekBar, range, value, i); + } + updateBars(); + } + + @Override + protected void showDialog(Bundle state) { + super.showDialog(state); + + // Can't use onPrepareDialogBuilder for this as we want the dialog + // to be kept open on click + AlertDialog d = (AlertDialog) getDialog(); + Button defaultsButton = d.getButton(DialogInterface.BUTTON_NEUTRAL); + defaultsButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + System.arraycopy(mLiveDisplay.getDefaultPictureAdjustment().toFloatArray(), + 0, mCurrentAdj, 0, 5); + updateBars(); + updateAdjustment(mCurrentAdj); + } + }); + } + + @Override + protected void onDialogClosed(boolean positiveResult) { + super.onDialogClosed(positiveResult); + updateAdjustment(positiveResult ? mCurrentAdj : mOriginalAdj); + } + + @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.currentAdj = mCurrentAdj; + myState.originalAdj = mOriginalAdj; + + // Restore the old state when the activity or dialog is being paused + updateAdjustment(mOriginalAdj); + + 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()); + + System.arraycopy(myState.originalAdj, 0, mOriginalAdj, 0, 5); + System.arraycopy(myState.currentAdj, 0, mCurrentAdj, 0, 5); + + updateBars(); + updateAdjustment(mCurrentAdj); + } + + private static class SavedState extends BaseSavedState { + float[] originalAdj; + float[] currentAdj; + + public SavedState(Parcelable superState) { + super(superState); + } + + public SavedState(Parcel source) { + super(source); + originalAdj = source.createFloatArray(); + currentAdj = source.createFloatArray(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeFloatArray(originalAdj); + dest.writeFloatArray(currentAdj); + } + + public static final Creator<SavedState> CREATOR = + new Creator<PictureAdjustment.SavedState>() { + + public PictureAdjustment.SavedState createFromParcel(Parcel in) { + return new PictureAdjustment.SavedState(in); + } + + public PictureAdjustment.SavedState[] newArray(int size) { + return new PictureAdjustment.SavedState[size]; + } + }; + } + + private void updateAdjustment(final float[] adjustment) { + mLiveDisplay.setPictureAdjustment(HSIC.fromFloatArray(adjustment)); + } + + private class ColorSeekBar implements SeekBar.OnSeekBarChangeListener { + private int mIndex; + private final IntervalSeekBar mSeekBar; + private TextView mValue; + private Range<Float> mRange; + + public ColorSeekBar(IntervalSeekBar seekBar, Range<Float> range, TextView value, int index) { + mSeekBar = seekBar; + mValue = value; + mIndex = index; + mRange = range; + mSeekBar.setMinimum(range.getLower()); + mSeekBar.setMaximum(range.getUpper()); + + mSeekBar.setOnSeekBarChangeListener(this); + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + IntervalSeekBar isb = (IntervalSeekBar)seekBar; + float fp = isb.getProgressFloat(); + if (fromUser) { + mCurrentAdj[mIndex] = mRanges.get(mIndex).clamp(fp); + updateAdjustment(mCurrentAdj); + } + mValue.setText(getLabel(mCurrentAdj[mIndex])); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + // Do nothing here + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + // Do nothing here + } + + private String getLabel(float value) { + if (mRange.getUpper() == 1.0f) { + return String.format("%d%%", Math.round(100F * value)); + } + return String.format("%d", Math.round(value)); + } + + public void setValue(float value) { + mSeekBar.setProgressFloat(value); + mValue.setText(getLabel(value)); + } + } +} diff --git a/src/com/android/settings/location/IzatSettingsInjector.java b/src/com/android/settings/location/IzatSettingsInjector.java new file mode 100644 index 0000000..4f430bf --- /dev/null +++ b/src/com/android/settings/location/IzatSettingsInjector.java @@ -0,0 +1,114 @@ +/* Copyright (c) 2015, The Linux Foundation. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of The Linux Foundation nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + +package com.android.settings.location; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.graphics.drawable.Drawable; +import android.os.UserHandle; +import android.preference.Preference; +import android.preference.SwitchPreference; +import android.util.Log; + +import com.android.settings.R; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +class IzatSettingsInjector extends SettingsInjector { + static final String TAG = "IzatSettingsInjector"; + private static final boolean PRINT_DEBUG_LOG = Log.isLoggable(TAG, Log.DEBUG); + + private static final String[] GS_PACKAGE_NAMES = {"com.google.android.gms", + "com.google.android.location"}; + private final String IZAT_EULA_PACKAGE_NAME = "com.qualcomm.location.XT"; + + private static final int GS_PRESENCE_UNKNOWN = 0; + private static final int GS_PRESENT = 1; + private static final int GS_NOT_PRESENT = 2; + private static int mGsExists = GS_PRESENCE_UNKNOWN; + + public static SettingsInjector getSettingInjector(Context context) { + + if (mGsExists == GS_PRESENCE_UNKNOWN) { + checkGsPresence(context); + } + + if (mGsExists == GS_PRESENT) { + return new SettingsInjector(context); + } else { + return new IzatSettingsInjector(context); + } + } + + private IzatSettingsInjector(Context context) { + super(context); + } + + private static void checkGsPresence(Context context) { + mGsExists = GS_NOT_PRESENT; + + List<ApplicationInfo> packages; + PackageManager pm = context.getPackageManager(); + packages = pm.getInstalledApplications(0); + + for (ApplicationInfo packageInfo : packages) { + if (mGsExists != GS_PRESENT) { + for (String packageName : GS_PACKAGE_NAMES) { + if (packageInfo.packageName.equals(packageName)) { + if (PRINT_DEBUG_LOG) Log.d(TAG, "Found GS Packages"); + mGsExists = GS_PRESENT; + break; + } + } + } else { + break; + } + } + } + + @Override + protected InjectedSetting parseServiceInfo(ResolveInfo service, UserHandle userHandle, + PackageManager pm) throws XmlPullParserException, IOException { + + ServiceInfo si = service.serviceInfo; + if (si.packageName.equals(IZAT_EULA_PACKAGE_NAME)) { + return null; + } + + return super.parseServiceInfo(service, userHandle, pm); + } +}
\ No newline at end of file diff --git a/src/com/android/settings/location/LocationSettings.java b/src/com/android/settings/location/LocationSettings.java index 2628c7f..9783ff2 100644 --- a/src/com/android/settings/location/LocationSettings.java +++ b/src/com/android/settings/location/LocationSettings.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2011 The Android Open Source Project + * Copyright (C) 2015 The CyanogenMod Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,10 +17,16 @@ package com.android.settings.location; +import static cyanogenmod.hardware.CMHardwareManager.FEATURE_LONG_TERM_ORBITS; + +import android.app.AlarmManager; +import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.location.SettingInjectorService; import android.os.Bundle; import android.os.UserHandle; @@ -27,7 +34,10 @@ import android.os.UserManager; import android.preference.Preference; import android.preference.PreferenceCategory; import android.preference.PreferenceGroup; +import android.preference.PreferenceManager; import android.preference.PreferenceScreen; +import android.preference.SwitchPreference; +import android.preference.Preference.OnPreferenceChangeListener; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; @@ -37,8 +47,11 @@ import com.android.internal.logging.MetricsLogger; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.Utils; +import com.android.settings.cyanogenmod.LtoService; import com.android.settings.widget.SwitchBar; +import cyanogenmod.hardware.CMHardwareManager; + import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -69,7 +82,7 @@ import java.util.List; * implementation. */ public class LocationSettings extends LocationSettingsBase - implements SwitchBar.OnSwitchChangeListener { + implements SwitchBar.OnSwitchChangeListener, OnPreferenceChangeListener { private static final String TAG = "LocationSettings"; @@ -93,12 +106,16 @@ public class LocationSettings extends LocationSettingsBase private static final int MENU_SCANNING = Menu.FIRST; + /** Key for preference LTO over Wi-Fi only */ + public static final String KEY_LTO_DOWNLOAD_DATA_WIFI_ONLY = "lto_download_data_wifi_only"; + private SwitchBar mSwitchBar; private Switch mSwitch; private boolean mValidListener = false; private UserHandle mManagedProfile; private Preference mManagedProfilePreference; private Preference mLocationMode; + private SwitchPreference mLtoDownloadDataWifiOnly; private PreferenceCategory mCategoryRecentLocationRequests; /** Receives UPDATE_INTENT */ private BroadcastReceiver mReceiver; @@ -120,6 +137,7 @@ public class LocationSettings extends LocationSettingsBase mSwitchBar = activity.getSwitchBar(); mSwitch = mSwitchBar.getSwitch(); mSwitchBar.show(); + setHasOptionsMenu(true); } @Override @@ -191,6 +209,17 @@ public class LocationSettings extends LocationSettingsBase } }); + mLtoDownloadDataWifiOnly = + (SwitchPreference) root.findPreference(KEY_LTO_DOWNLOAD_DATA_WIFI_ONLY); + if (mLtoDownloadDataWifiOnly != null) { + if (!isLtoSupported(activity) || !checkGpsDownloadWiFiOnly(activity)) { + root.removePreference(mLtoDownloadDataWifiOnly); + mLtoDownloadDataWifiOnly = null; + } else { + mLtoDownloadDataWifiOnly.setOnPreferenceChangeListener(this); + } + } + mCategoryRecentLocationRequests = (PreferenceCategory) root.findPreference(KEY_RECENT_LOCATION_REQUESTS); RecentLocationApps recentApps = new RecentLocationApps(activity); @@ -253,7 +282,7 @@ public class LocationSettings extends LocationSettingsBase boolean lockdownOnLocationAccess) { PreferenceCategory categoryLocationServices = (PreferenceCategory) root.findPreference(KEY_LOCATION_SERVICES); - injector = new SettingsInjector(context); + injector = IzatSettingsInjector.getSettingInjector(context); // If location access is locked down by device policy then we only show injected settings // for the primary profile. List<Preference> locationServices = injector.getInjectedSettings(lockdownOnLocationAccess ? @@ -334,6 +363,9 @@ public class LocationSettings extends LocationSettingsBase // Disable the whole switch bar instead of the switch itself. If we disabled the switch // only, it would be re-enabled again if the switch bar is not disabled. mSwitchBar.setEnabled(!restricted); + if (mLtoDownloadDataWifiOnly != null) { + mLtoDownloadDataWifiOnly.setEnabled(enabled && !restricted); + } mLocationMode.setEnabled(enabled && !restricted); mCategoryRecentLocationRequests.setEnabled(enabled); @@ -377,4 +409,67 @@ public class LocationSettings extends LocationSettingsBase setLocationMode(android.provider.Settings.Secure.LOCATION_MODE_OFF); } } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (mLtoDownloadDataWifiOnly != null && preference.equals(mLtoDownloadDataWifiOnly)) { + updateLtoServiceStatus(getActivity(), isLocationModeEnabled(getActivity())); + } + return true; + } + + private static void updateLtoServiceStatus(Context context, boolean start) { + Intent intent = new Intent(context, LtoService.class); + if (start) { + context.startService(intent); + } else { + context.stopService(intent); + } + } + + private static boolean checkGpsDownloadWiFiOnly(Context context) { + PackageManager pm = context.getPackageManager(); + boolean supportsTelephony = pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY); + boolean supportsWifi = pm.hasSystemFeature(PackageManager.FEATURE_WIFI); + if (!supportsWifi || !supportsTelephony) { + SharedPreferences.Editor editor = + PreferenceManager.getDefaultSharedPreferences(context).edit(); + editor.putBoolean(KEY_LTO_DOWNLOAD_DATA_WIFI_ONLY, supportsWifi); + editor.apply(); + return false; + } + return true; + } + + public static boolean isLocationModeEnabled(Context context) { + int mode = android.provider.Settings.Secure.getInt(context.getContentResolver(), + android.provider.Settings.Secure.LOCATION_MODE, + android.provider.Settings.Secure.LOCATION_MODE_OFF); + return (mode != android.provider.Settings.Secure.LOCATION_MODE_OFF); + } + + /** + * Restore the properties associated with this preference on boot + * @param ctx A valid context + */ + public static void restore(final Context context) { + if (isLtoSupported(context) && isLocationModeEnabled(context)) { + // Check and adjust the value for Gps download data on wifi only + checkGpsDownloadWiFiOnly(context); + + // Starts the LtoService, but delayed 2 minutes after boot (this should give a + // proper time to start all device services) + AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + Intent intent = new Intent(context, LtoService.class); + PendingIntent pi = PendingIntent.getService(context, 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT); + long nextLtoDownload = System.currentTimeMillis() + (1000 * 60 * 2L); + am.set(AlarmManager.RTC, nextLtoDownload, pi); + } + } + + private static boolean isLtoSupported(Context context) { + final CMHardwareManager hardware = CMHardwareManager.getInstance(context); + return hardware != null && hardware.isSupported(FEATURE_LONG_TERM_ORBITS); + } } diff --git a/src/com/android/settings/location/SettingsInjector.java b/src/com/android/settings/location/SettingsInjector.java index 283430e..db1e011 100644 --- a/src/com/android/settings/location/SettingsInjector.java +++ b/src/com/android/settings/location/SettingsInjector.java @@ -150,7 +150,7 @@ class SettingsInjector { * * Duplicates some code from {@link android.content.pm.RegisteredServicesCache}. */ - private static InjectedSetting parseServiceInfo(ResolveInfo service, UserHandle userHandle, + protected InjectedSetting parseServiceInfo(ResolveInfo service, UserHandle userHandle, PackageManager pm) throws XmlPullParserException, IOException { ServiceInfo si = service.serviceInfo; diff --git a/src/com/android/settings/net/ChartDataLoader.java b/src/com/android/settings/net/ChartDataLoader.java deleted file mode 100644 index e0336b7..0000000 --- a/src/com/android/settings/net/ChartDataLoader.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * 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.net; - -import static android.net.NetworkStats.SET_DEFAULT; -import static android.net.NetworkStats.SET_FOREGROUND; -import static android.net.NetworkStats.TAG_NONE; -import static android.net.NetworkStatsHistory.FIELD_RX_BYTES; -import static android.net.NetworkStatsHistory.FIELD_TX_BYTES; -import static android.text.format.DateUtils.HOUR_IN_MILLIS; - -import android.content.AsyncTaskLoader; -import android.content.Context; -import android.net.INetworkStatsSession; -import android.net.NetworkStatsHistory; -import android.net.NetworkTemplate; -import android.os.Bundle; -import android.os.RemoteException; - -import com.android.settings.DataUsageSummary.AppItem; - -/** - * Loader for historical chart data for both network and UID details. - */ -public class ChartDataLoader extends AsyncTaskLoader<ChartData> { - private static final String KEY_TEMPLATE = "template"; - private static final String KEY_APP = "app"; - private static final String KEY_FIELDS = "fields"; - - private final INetworkStatsSession mSession; - private final Bundle mArgs; - - public static Bundle buildArgs(NetworkTemplate template, AppItem app) { - return buildArgs(template, app, FIELD_RX_BYTES | FIELD_TX_BYTES); - } - - public static Bundle buildArgs(NetworkTemplate template, AppItem app, int fields) { - final Bundle args = new Bundle(); - args.putParcelable(KEY_TEMPLATE, template); - args.putParcelable(KEY_APP, app); - args.putInt(KEY_FIELDS, fields); - return args; - } - - public ChartDataLoader(Context context, INetworkStatsSession session, Bundle args) { - super(context); - mSession = session; - mArgs = args; - } - - @Override - protected void onStartLoading() { - super.onStartLoading(); - forceLoad(); - } - - @Override - public ChartData loadInBackground() { - final NetworkTemplate template = mArgs.getParcelable(KEY_TEMPLATE); - final AppItem app = mArgs.getParcelable(KEY_APP); - final int fields = mArgs.getInt(KEY_FIELDS); - - try { - return loadInBackground(template, app, fields); - } catch (RemoteException e) { - // since we can't do much without history, and we don't want to - // leave with half-baked UI, we bail hard. - throw new RuntimeException("problem reading network stats", e); - } - } - - private ChartData loadInBackground(NetworkTemplate template, AppItem app, int fields) - throws RemoteException { - final ChartData data = new ChartData(); - data.network = mSession.getHistoryForNetwork(template, fields); - - if (app != null) { - // load stats for current uid and template - final int size = app.uids.size(); - for (int i = 0; i < size; i++) { - final int uid = app.uids.keyAt(i); - data.detailDefault = collectHistoryForUid( - template, uid, SET_DEFAULT, data.detailDefault); - data.detailForeground = collectHistoryForUid( - template, uid, SET_FOREGROUND, data.detailForeground); - } - - if (size > 0) { - data.detail = new NetworkStatsHistory(data.detailForeground.getBucketDuration()); - data.detail.recordEntireHistory(data.detailDefault); - data.detail.recordEntireHistory(data.detailForeground); - } else { - data.detailDefault = new NetworkStatsHistory(HOUR_IN_MILLIS); - data.detailForeground = new NetworkStatsHistory(HOUR_IN_MILLIS); - data.detail = new NetworkStatsHistory(HOUR_IN_MILLIS); - } - } - - return data; - } - - @Override - protected void onStopLoading() { - super.onStopLoading(); - cancelLoad(); - } - - @Override - protected void onReset() { - super.onReset(); - cancelLoad(); - } - - /** - * Collect {@link NetworkStatsHistory} for the requested UID, combining with - * an existing {@link NetworkStatsHistory} if provided. - */ - private NetworkStatsHistory collectHistoryForUid( - NetworkTemplate template, int uid, int set, NetworkStatsHistory existing) - throws RemoteException { - final NetworkStatsHistory history = mSession.getHistoryForUid( - template, uid, set, TAG_NONE, FIELD_RX_BYTES | FIELD_TX_BYTES); - - if (existing != null) { - existing.recordEntireHistory(history); - return existing; - } else { - return history; - } - } -} diff --git a/src/com/android/settings/net/DataUsageMeteredSettings.java b/src/com/android/settings/net/DataUsageMeteredSettings.java index ec1dd38..59a8b92 100644 --- a/src/com/android/settings/net/DataUsageMeteredSettings.java +++ b/src/com/android/settings/net/DataUsageMeteredSettings.java @@ -40,6 +40,7 @@ import com.android.settings.SettingsPreferenceFragment; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.Indexable; import com.android.settings.search.SearchIndexableRaw; +import com.android.settingslib.NetworkPolicyEditor; import java.util.ArrayList; import java.util.List; diff --git a/src/com/android/settings/net/NetworkPolicyEditor.java b/src/com/android/settings/net/NetworkPolicyEditor.java deleted file mode 100644 index 1268c3f..0000000 --- a/src/com/android/settings/net/NetworkPolicyEditor.java +++ /dev/null @@ -1,252 +0,0 @@ -/* - * 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.net; - -import static android.net.NetworkPolicy.CYCLE_NONE; -import static android.net.NetworkPolicy.LIMIT_DISABLED; -import static android.net.NetworkPolicy.SNOOZE_NEVER; -import static android.net.NetworkPolicy.WARNING_DISABLED; -import static android.net.NetworkTemplate.MATCH_WIFI; -import static com.android.internal.util.Preconditions.checkNotNull; - -import android.net.NetworkPolicy; -import android.net.NetworkPolicyManager; -import android.net.NetworkTemplate; -import android.net.wifi.WifiInfo; -import android.os.AsyncTask; -import android.text.TextUtils; -import android.text.format.Time; - -import com.google.android.collect.Lists; - -import java.util.ArrayList; - -/** - * Utility class to modify list of {@link NetworkPolicy}. Specifically knows - * about which policies can coexist. This editor offers thread safety when - * talking with {@link NetworkPolicyManager}. - */ -public class NetworkPolicyEditor { - // TODO: be more robust when missing policies from service - - public static final boolean ENABLE_SPLIT_POLICIES = false; - - private NetworkPolicyManager mPolicyManager; - private ArrayList<NetworkPolicy> mPolicies = Lists.newArrayList(); - - public NetworkPolicyEditor(NetworkPolicyManager policyManager) { - mPolicyManager = checkNotNull(policyManager); - } - - public void read() { - final NetworkPolicy[] policies = mPolicyManager.getNetworkPolicies(); - - boolean modified = false; - mPolicies.clear(); - for (NetworkPolicy policy : policies) { - // TODO: find better place to clamp these - if (policy.limitBytes < -1) { - policy.limitBytes = LIMIT_DISABLED; - modified = true; - } - if (policy.warningBytes < -1) { - policy.warningBytes = WARNING_DISABLED; - modified = true; - } - - mPolicies.add(policy); - } - - // when we cleaned policies above, write back changes - if (modified) writeAsync(); - } - - public void writeAsync() { - // TODO: consider making more robust by passing through service - final NetworkPolicy[] policies = mPolicies.toArray(new NetworkPolicy[mPolicies.size()]); - new AsyncTask<Void, Void, Void>() { - @Override - protected Void doInBackground(Void... params) { - write(policies); - return null; - } - }.execute(); - } - - public void write(NetworkPolicy[] policies) { - mPolicyManager.setNetworkPolicies(policies); - } - - public boolean hasLimitedPolicy(NetworkTemplate template) { - final NetworkPolicy policy = getPolicy(template); - return policy != null && policy.limitBytes != LIMIT_DISABLED; - } - - public NetworkPolicy getOrCreatePolicy(NetworkTemplate template) { - NetworkPolicy policy = getPolicy(template); - if (policy == null) { - policy = buildDefaultPolicy(template); - mPolicies.add(policy); - } - return policy; - } - - public NetworkPolicy getPolicy(NetworkTemplate template) { - for (NetworkPolicy policy : mPolicies) { - if (policy.template.equals(template)) { - return policy; - } - } - return null; - } - - public NetworkPolicy getPolicyMaybeUnquoted(NetworkTemplate template) { - NetworkPolicy policy = getPolicy(template); - if (policy != null) { - return policy; - } else { - return getPolicy(buildUnquotedNetworkTemplate(template)); - } - } - - @Deprecated - private static NetworkPolicy buildDefaultPolicy(NetworkTemplate template) { - // TODO: move this into framework to share with NetworkPolicyManagerService - final int cycleDay; - final String cycleTimezone; - final boolean metered; - - if (template.getMatchRule() == MATCH_WIFI) { - cycleDay = CYCLE_NONE; - cycleTimezone = Time.TIMEZONE_UTC; - metered = false; - } else { - final Time time = new Time(); - time.setToNow(); - cycleDay = time.monthDay; - cycleTimezone = time.timezone; - metered = true; - } - - return new NetworkPolicy(template, cycleDay, cycleTimezone, WARNING_DISABLED, - LIMIT_DISABLED, SNOOZE_NEVER, SNOOZE_NEVER, metered, true); - } - - public int getPolicyCycleDay(NetworkTemplate template) { - final NetworkPolicy policy = getPolicy(template); - return (policy != null) ? policy.cycleDay : -1; - } - - public void setPolicyCycleDay(NetworkTemplate template, int cycleDay, String cycleTimezone) { - final NetworkPolicy policy = getOrCreatePolicy(template); - policy.cycleDay = cycleDay; - policy.cycleTimezone = cycleTimezone; - policy.inferred = false; - policy.clearSnooze(); - writeAsync(); - } - - public long getPolicyWarningBytes(NetworkTemplate template) { - final NetworkPolicy policy = getPolicy(template); - return (policy != null) ? policy.warningBytes : WARNING_DISABLED; - } - - public void setPolicyWarningBytes(NetworkTemplate template, long warningBytes) { - final NetworkPolicy policy = getOrCreatePolicy(template); - policy.warningBytes = warningBytes; - policy.inferred = false; - policy.clearSnooze(); - writeAsync(); - } - - public long getPolicyLimitBytes(NetworkTemplate template) { - final NetworkPolicy policy = getPolicy(template); - return (policy != null) ? policy.limitBytes : LIMIT_DISABLED; - } - - public void setPolicyLimitBytes(NetworkTemplate template, long limitBytes) { - final NetworkPolicy policy = getOrCreatePolicy(template); - policy.limitBytes = limitBytes; - policy.inferred = false; - policy.clearSnooze(); - writeAsync(); - } - - public boolean getPolicyMetered(NetworkTemplate template) { - NetworkPolicy policy = getPolicy(template); - if (policy != null) { - return policy.metered; - } else { - return false; - } - } - - public void setPolicyMetered(NetworkTemplate template, boolean metered) { - boolean modified = false; - - NetworkPolicy policy = getPolicy(template); - if (metered) { - if (policy == null) { - policy = buildDefaultPolicy(template); - policy.metered = true; - policy.inferred = false; - mPolicies.add(policy); - modified = true; - } else if (!policy.metered) { - policy.metered = true; - policy.inferred = false; - modified = true; - } - - } else { - if (policy == null) { - // ignore when policy doesn't exist - } else if (policy.metered) { - policy.metered = false; - policy.inferred = false; - modified = true; - } - } - - // Remove legacy unquoted policies while we're here - final NetworkTemplate unquoted = buildUnquotedNetworkTemplate(template); - final NetworkPolicy unquotedPolicy = getPolicy(unquoted); - if (unquotedPolicy != null) { - mPolicies.remove(unquotedPolicy); - modified = true; - } - - if (modified) writeAsync(); - } - - /** - * Build a revised {@link NetworkTemplate} that matches the same rule, but - * with an unquoted {@link NetworkTemplate#getNetworkId()}. Used to work - * around legacy bugs. - */ - private static NetworkTemplate buildUnquotedNetworkTemplate(NetworkTemplate template) { - if (template == null) return null; - final String networkId = template.getNetworkId(); - final String strippedNetworkId = WifiInfo.removeDoubleQuotes(networkId); - if (!TextUtils.equals(strippedNetworkId, networkId)) { - return new NetworkTemplate( - template.getMatchRule(), template.getSubscriberId(), strippedNetworkId); - } else { - return null; - } - } -} diff --git a/src/com/android/settings/net/SummaryForAllUidLoader.java b/src/com/android/settings/net/SummaryForAllUidLoader.java deleted file mode 100644 index 68dc799..0000000 --- a/src/com/android/settings/net/SummaryForAllUidLoader.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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.net; - -import android.content.AsyncTaskLoader; -import android.content.Context; -import android.net.INetworkStatsSession; -import android.net.NetworkStats; -import android.net.NetworkTemplate; -import android.os.Bundle; -import android.os.RemoteException; - -public class SummaryForAllUidLoader extends AsyncTaskLoader<NetworkStats> { - private static final String KEY_TEMPLATE = "template"; - private static final String KEY_START = "start"; - private static final String KEY_END = "end"; - - private final INetworkStatsSession mSession; - private final Bundle mArgs; - - public static Bundle buildArgs(NetworkTemplate template, long start, long end) { - final Bundle args = new Bundle(); - args.putParcelable(KEY_TEMPLATE, template); - args.putLong(KEY_START, start); - args.putLong(KEY_END, end); - return args; - } - - public SummaryForAllUidLoader(Context context, INetworkStatsSession session, Bundle args) { - super(context); - mSession = session; - mArgs = args; - } - - @Override - protected void onStartLoading() { - super.onStartLoading(); - forceLoad(); - } - - @Override - public NetworkStats loadInBackground() { - final NetworkTemplate template = mArgs.getParcelable(KEY_TEMPLATE); - final long start = mArgs.getLong(KEY_START); - final long end = mArgs.getLong(KEY_END); - - try { - return mSession.getSummaryForAllUid(template, start, end, false); - } catch (RemoteException e) { - return null; - } - } - - @Override - protected void onStopLoading() { - super.onStopLoading(); - cancelLoad(); - } - - @Override - protected void onReset() { - super.onReset(); - cancelLoad(); - } -} diff --git a/src/com/android/settings/net/UidDetailProvider.java b/src/com/android/settings/net/UidDetailProvider.java deleted file mode 100644 index a08c7de..0000000 --- a/src/com/android/settings/net/UidDetailProvider.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * 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.net; - -import android.app.AppGlobals; -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.IPackageManager; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.UserInfo; -import android.content.res.Resources; -import android.graphics.drawable.Drawable; -import android.net.ConnectivityManager; -import android.net.TrafficStats; -import android.os.UserManager; -import android.os.UserHandle; -import android.os.RemoteException; -import android.text.TextUtils; -import android.util.Log; -import android.util.SparseArray; - -import com.android.settings.R; -import com.android.settings.Utils; - -/** - * Return details about a specific UID, handling special cases like - * {@link TrafficStats#UID_TETHERING} and {@link UserInfo}. - */ -public class UidDetailProvider { - private static final String TAG = "DataUsage"; - private final Context mContext; - private final SparseArray<UidDetail> mUidDetailCache; - - public static final int OTHER_USER_RANGE_START = -2000; - - public static int buildKeyForUser(int userHandle) { - return OTHER_USER_RANGE_START - userHandle; - } - - public static boolean isKeyForUser(int key) { - return key <= OTHER_USER_RANGE_START; - } - - public static int getUserIdForKey(int key) { - return OTHER_USER_RANGE_START - key; - } - - public UidDetailProvider(Context context) { - mContext = context.getApplicationContext(); - mUidDetailCache = new SparseArray<UidDetail>(); - } - - public void clearCache() { - synchronized (mUidDetailCache) { - mUidDetailCache.clear(); - } - } - - /** - * Resolve best descriptive label for the given UID. - */ - public UidDetail getUidDetail(int uid, boolean blocking) { - UidDetail detail; - - synchronized (mUidDetailCache) { - detail = mUidDetailCache.get(uid); - } - - if (detail != null) { - return detail; - } else if (!blocking) { - return null; - } - - detail = buildUidDetail(uid); - - synchronized (mUidDetailCache) { - mUidDetailCache.put(uid, detail); - } - - return detail; - } - - /** - * Build {@link UidDetail} object, blocking until all {@link Drawable} - * lookup is finished. - */ - private UidDetail buildUidDetail(int uid) { - final Resources res = mContext.getResources(); - final PackageManager pm = mContext.getPackageManager(); - - final UidDetail detail = new UidDetail(); - detail.label = pm.getNameForUid(uid); - detail.icon = pm.getDefaultActivityIcon(); - - // handle special case labels - switch (uid) { - case android.os.Process.SYSTEM_UID: - detail.label = res.getString(R.string.process_kernel_label); - detail.icon = pm.getDefaultActivityIcon(); - return detail; - case TrafficStats.UID_REMOVED: - detail.label = res.getString(UserManager.supportsMultipleUsers() - ? R.string.data_usage_uninstalled_apps_users - : R.string.data_usage_uninstalled_apps); - detail.icon = pm.getDefaultActivityIcon(); - return detail; - case TrafficStats.UID_TETHERING: - final ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService( - Context.CONNECTIVITY_SERVICE); - detail.label = res.getString(Utils.getTetheringLabel(cm)); - detail.icon = pm.getDefaultActivityIcon(); - return detail; - } - - final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); - - // Handle keys that are actually user handles - if (isKeyForUser(uid)) { - final int userHandle = getUserIdForKey(uid); - final UserInfo info = um.getUserInfo(userHandle); - if (info != null) { - detail.label = Utils.getUserLabel(mContext, info); - detail.icon = Utils.getUserIcon(mContext, um, info); - return detail; - } - } - - // otherwise fall back to using packagemanager labels - final String[] packageNames = pm.getPackagesForUid(uid); - final int length = packageNames != null ? packageNames.length : 0; - try { - final int userId = UserHandle.getUserId(uid); - UserHandle userHandle = new UserHandle(userId); - IPackageManager ipm = AppGlobals.getPackageManager(); - if (length == 1) { - final ApplicationInfo info = ipm.getApplicationInfo(packageNames[0], - 0 /* no flags */, userId); - if (info != null) { - detail.label = info.loadLabel(pm).toString(); - detail.icon = um.getBadgedIconForUser(info.loadIcon(pm), - new UserHandle(userId)); - } - } else if (length > 1) { - detail.detailLabels = new CharSequence[length]; - detail.detailContentDescriptions = new CharSequence[length]; - for (int i = 0; i < length; i++) { - final String packageName = packageNames[i]; - final PackageInfo packageInfo = pm.getPackageInfo(packageName, 0); - final ApplicationInfo appInfo = ipm.getApplicationInfo(packageName, - 0 /* no flags */, userId); - - if (appInfo != null) { - detail.detailLabels[i] = appInfo.loadLabel(pm).toString(); - detail.detailContentDescriptions[i] = um.getBadgedLabelForUser( - detail.detailLabels[i], userHandle); - if (packageInfo.sharedUserLabel != 0) { - detail.label = pm.getText(packageName, packageInfo.sharedUserLabel, - packageInfo.applicationInfo).toString(); - detail.icon = um.getBadgedIconForUser(appInfo.loadIcon(pm), userHandle); - } - } - } - } - detail.contentDescription = um.getBadgedLabelForUser(detail.label, userHandle); - } catch (NameNotFoundException e) { - Log.w(TAG, "Error while building UI detail for uid "+uid, e); - } catch (RemoteException e) { - Log.w(TAG, "Error while building UI detail for uid "+uid, e); - } - - if (TextUtils.isEmpty(detail.label)) { - detail.label = Integer.toString(uid); - } - - return detail; - } -} diff --git a/src/com/android/settings/nfc/NfcEnabler.java b/src/com/android/settings/nfc/NfcEnabler.java index ae61b13..f9dff46 100644 --- a/src/com/android/settings/nfc/NfcEnabler.java +++ b/src/com/android/settings/nfc/NfcEnabler.java @@ -37,6 +37,7 @@ public class NfcEnabler implements Preference.OnPreferenceChangeListener { private final Context mContext; private final SwitchPreference mSwitch; private final PreferenceScreen mAndroidBeam; + private final PreferenceScreen mNfcPayment; private final NfcAdapter mNfcAdapter; private final IntentFilter mIntentFilter; private boolean mBeamDisallowed; @@ -53,10 +54,11 @@ public class NfcEnabler implements Preference.OnPreferenceChangeListener { }; public NfcEnabler(Context context, SwitchPreference switchPreference, - PreferenceScreen androidBeam) { + PreferenceScreen androidBeam, PreferenceScreen nfcPayment) { mContext = context; mSwitch = switchPreference; mAndroidBeam = androidBeam; + mNfcPayment = nfcPayment; mNfcAdapter = NfcAdapter.getDefaultAdapter(context); mBeamDisallowed = ((UserManager) mContext.getSystemService(Context.USER_SERVICE)) .hasUserRestriction(UserManager.DISALLOW_OUTGOING_BEAM); @@ -65,6 +67,7 @@ public class NfcEnabler implements Preference.OnPreferenceChangeListener { // NFC is not supported mSwitch.setEnabled(false); mAndroidBeam.setEnabled(false); + mNfcPayment.setEnabled(false); mIntentFilter = null; return; } @@ -113,6 +116,7 @@ public class NfcEnabler implements Preference.OnPreferenceChangeListener { mSwitch.setEnabled(true); mAndroidBeam.setEnabled(false); mAndroidBeam.setSummary(R.string.android_beam_disabled_summary); + mNfcPayment.setEnabled(false); break; case NfcAdapter.STATE_ON: mSwitch.setChecked(true); @@ -123,16 +127,19 @@ public class NfcEnabler implements Preference.OnPreferenceChangeListener { } else { mAndroidBeam.setSummary(R.string.android_beam_off_summary); } + mNfcPayment.setEnabled(true); break; case NfcAdapter.STATE_TURNING_ON: mSwitch.setChecked(true); mSwitch.setEnabled(false); mAndroidBeam.setEnabled(false); + mNfcPayment.setEnabled(false); break; case NfcAdapter.STATE_TURNING_OFF: mSwitch.setChecked(false); mSwitch.setEnabled(false); mAndroidBeam.setEnabled(false); + mNfcPayment.setEnabled(false); break; } } diff --git a/src/com/android/settings/nfc/NfcPaymentPreference.java b/src/com/android/settings/nfc/NfcPaymentPreference.java index e8dcf0b..0eef242 100644 --- a/src/com/android/settings/nfc/NfcPaymentPreference.java +++ b/src/com/android/settings/nfc/NfcPaymentPreference.java @@ -29,6 +29,7 @@ import android.widget.BaseAdapter; import android.widget.CompoundButton; import android.widget.ImageView; import android.widget.RadioButton; +import android.content.pm.PackageManager; import com.android.settings.R; import com.android.settings.nfc.PaymentBackend.PaymentAppInfo; @@ -129,7 +130,7 @@ public class NfcPaymentPreference extends DialogPreference implements } class NfcPaymentAdapter extends BaseAdapter implements CompoundButton.OnCheckedChangeListener, - View.OnClickListener { + View.OnClickListener, View.OnLongClickListener { // Only modified on UI thread private PaymentAppInfo[] appInfos; @@ -175,6 +176,7 @@ public class NfcPaymentPreference extends DialogPreference implements holder.imageView.setTag(appInfo); holder.imageView.setContentDescription(appInfo.label); holder.imageView.setOnClickListener(this); + holder.imageView.setOnLongClickListener(this); // Prevent checked callback getting called on recycled views holder.radioButton.setOnCheckedChangeListener(null); @@ -202,6 +204,28 @@ public class NfcPaymentPreference extends DialogPreference implements makeDefault(appInfo); } + @Override + public boolean onLongClick(View view){ + PaymentAppInfo appInfo = (PaymentAppInfo) view.getTag(); + if (appInfo.componentName != null) { + Log.d(TAG, "LongClick: " + appInfo.componentName.toString()); + PackageManager pm = mContext.getPackageManager(); + Intent gsmaIntent = + pm.getLaunchIntentForPackage(appInfo.componentName.getPackageName()); + if (gsmaIntent != null) { + gsmaIntent.setAction("com.gsma.services.nfc.SELECT_DEFAULT_SERVICE"); + gsmaIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); + gsmaIntent.setPackage(gsmaIntent.getPackage()); + try { + mContext.startActivity(gsmaIntent); + } catch (ActivityNotFoundException e) { + Log.e(TAG, "Settings activity for " + appInfo.componentName.toString() + " not found."); + } + } + } + return true; + } + void makeDefault(PaymentAppInfo appInfo) { if (!appInfo.isDefault) { mPaymentBackend.setDefaultPaymentApp(appInfo.componentName); diff --git a/src/com/android/settings/notification/AppNotificationSettings.java b/src/com/android/settings/notification/AppNotificationSettings.java index 2ed6d85..2e76f92 100644 --- a/src/com/android/settings/notification/AppNotificationSettings.java +++ b/src/com/android/settings/notification/AppNotificationSettings.java @@ -27,6 +27,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.os.Bundle; import android.os.UserHandle; +import android.preference.ListPreference; import android.preference.Preference; import android.preference.Preference.OnPreferenceChangeListener; import android.preference.Preference.OnPreferenceClickListener; @@ -59,6 +60,9 @@ public class AppNotificationSettings extends SettingsPreferenceFragment { private static final String KEY_PEEKABLE = "peekable"; private static final String KEY_SENSITIVE = "sensitive"; private static final String KEY_APP_SETTINGS = "app_settings"; + private static final String KEY_SHOW_ON_KEYGUARD = "show_on_keyguard"; + private static final String KEY_NO_ONGOING_ON_KEYGUARD = "no_ongoing_on_keyguard"; + private static final String KEY_SOUND_TIMEOUT = "sound_timeout"; private static final Intent APP_NOTIFICATION_PREFS_CATEGORY_INTENT = new Intent(Intent.ACTION_MAIN) @@ -71,6 +75,9 @@ public class AppNotificationSettings extends SettingsPreferenceFragment { private SwitchPreference mPriority; private SwitchPreference mPeekable; private SwitchPreference mSensitive; + private SwitchPreference mShowOnKeyguard; + private SwitchPreference mShowNoOngoingOnKeyguard; + private ListPreference mSoundTimeout; private AppRow mAppRow; private boolean mCreated; private boolean mIsSystemPackage; @@ -137,6 +144,9 @@ public class AppNotificationSettings extends SettingsPreferenceFragment { mPriority = (SwitchPreference) findPreference(KEY_PRIORITY); mPeekable = (SwitchPreference) findPreference(KEY_PEEKABLE); mSensitive = (SwitchPreference) findPreference(KEY_SENSITIVE); + mShowOnKeyguard = (SwitchPreference) findPreference(KEY_SHOW_ON_KEYGUARD); + mShowNoOngoingOnKeyguard = (SwitchPreference) findPreference(KEY_NO_ONGOING_ON_KEYGUARD); + mSoundTimeout = (ListPreference) findPreference(KEY_SOUND_TIMEOUT); mAppRow = mBackend.loadAppRow(pm, info.applicationInfo); @@ -150,6 +160,8 @@ public class AppNotificationSettings extends SettingsPreferenceFragment { mPriority.setChecked(mAppRow.priority); mPeekable.setChecked(mAppRow.peekable); mSensitive.setChecked(mAppRow.sensitive); + mSoundTimeout.setValue(Long.toString(mAppRow.soundTimeout)); + updateSoundTimeoutSummary(mAppRow.soundTimeout); mBlock.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { @Override @@ -190,6 +202,18 @@ public class AppNotificationSettings extends SettingsPreferenceFragment { } }); + mSoundTimeout.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + long value = Long.valueOf((String) newValue); + if (!mBackend.setNotificationSoundTimeout(pkg, mUid, value)) { + return false; + } + updateSoundTimeoutSummary(value); + return true; + } + }); + if (mAppRow.settingsIntent != null) { findPreference(KEY_APP_SETTINGS).setOnPreferenceClickListener( new OnPreferenceClickListener() { @@ -202,6 +226,47 @@ public class AppNotificationSettings extends SettingsPreferenceFragment { } else { removePreference(KEY_APP_SETTINGS); } + + int keyguard = mBackend.getShowNotificationForPackageOnKeyguard(pkg, mUid); + mShowOnKeyguard.setChecked((keyguard & Notification.SHOW_ALL_NOTI_ON_KEYGUARD) != 0); + mShowNoOngoingOnKeyguard.setChecked( + (keyguard & Notification.SHOW_NO_ONGOING_NOTI_ON_KEYGUARD) != 0); + + mShowOnKeyguard.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + final boolean showOnKeyguard = (Boolean) newValue; + int keyguard = mBackend.getShowNotificationForPackageOnKeyguard(pkg, mUid); + + if (showOnKeyguard && (keyguard & Notification.SHOW_ALL_NOTI_ON_KEYGUARD) == 0) { + keyguard |= Notification.SHOW_ALL_NOTI_ON_KEYGUARD; + } else { + keyguard &= ~Notification.SHOW_ALL_NOTI_ON_KEYGUARD; + } + return mBackend.setShowNotificationForPackageOnKeyguard(pkg, mUid, keyguard); + } + }); + + mShowNoOngoingOnKeyguard.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + final boolean showNoOngoingOnKeyguard = (Boolean) newValue; + int keyguard = mBackend.getShowNotificationForPackageOnKeyguard(pkg, mUid); + if (showNoOngoingOnKeyguard + && (keyguard & Notification.SHOW_NO_ONGOING_NOTI_ON_KEYGUARD) == 0) { + keyguard |= Notification.SHOW_NO_ONGOING_NOTI_ON_KEYGUARD; + } else { + keyguard &= ~Notification.SHOW_NO_ONGOING_NOTI_ON_KEYGUARD; + } + return mBackend.setShowNotificationForPackageOnKeyguard(pkg, mUid, keyguard); + } + }); + + // Users cannot block notifications from system/signature packages + if (mIsSystemPackage || !getLockscreenNotificationsEnabled()) { + getPreferenceScreen().removePreference(mShowNoOngoingOnKeyguard); + getPreferenceScreen().removePreference(mShowOnKeyguard); + } } @Override @@ -213,17 +278,40 @@ public class AppNotificationSettings extends SettingsPreferenceFragment { } } + private void updateSoundTimeoutSummary(long value) { + if (value == 0) { + mSoundTimeout.setSummary(R.string.app_notification_sound_timeout_value_none); + } else { + final CharSequence[] entries = mSoundTimeout.getEntries(); + final CharSequence[] values = mSoundTimeout.getEntryValues(); + CharSequence summary = null; + for (int i = 0; i < values.length; i++) { + long timeout = Long.parseLong(values[i].toString()); + if (timeout == value) { + summary = getString(R.string.app_notification_sound_timeout_summary_template, + entries[i]); + break; + } + } + mSoundTimeout.setSummary(summary); + } + } + private void updateDependents(boolean banned) { final boolean lockscreenSecure = new LockPatternUtils(getActivity()).isSecure( UserHandle.myUserId()); final boolean lockscreenNotificationsEnabled = getLockscreenNotificationsEnabled(); final boolean allowPrivate = getLockscreenAllowPrivateNotifications(); + final boolean headsUpEnabled = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED, 0) != 0; setVisible(mBlock, !mIsSystemPackage); setVisible(mPriority, mIsSystemPackage || !banned); - setVisible(mPeekable, mIsSystemPackage || !banned); + setVisible(mPeekable, mIsSystemPackage || !banned && headsUpEnabled); setVisible(mSensitive, mIsSystemPackage || !banned && lockscreenSecure && lockscreenNotificationsEnabled && allowPrivate); + setVisible(mShowOnKeyguard, mIsSystemPackage || !banned); + setVisible(mShowNoOngoingOnKeyguard, mIsSystemPackage || !banned); } private void setVisible(Preference p, boolean visible) { diff --git a/src/com/android/settings/notification/IncreasingRingVolumePreference.java b/src/com/android/settings/notification/IncreasingRingVolumePreference.java new file mode 100644 index 0000000..8a55eaa --- /dev/null +++ b/src/com/android/settings/notification/IncreasingRingVolumePreference.java @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2014 CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notification; + +import android.content.ContentResolver; +import android.content.Context; +import android.media.AudioAttributes; +import android.media.AudioManager; +import android.media.Ringtone; +import android.media.RingtoneManager; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Message; +import android.preference.PreferenceManager; +import android.preference.Preference; +import android.provider.Settings; +import android.text.format.Formatter; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.widget.SeekBar; +import android.widget.TextView; + +import com.android.settings.R; +import cyanogenmod.providers.CMSettings; + +public class IncreasingRingVolumePreference extends Preference implements + PreferenceManager.OnActivityStopListener, Handler.Callback, + SeekBar.OnSeekBarChangeListener { + private static final String TAG = "IncreasingRingMinVolumePreference"; + + public interface Callback { + void onStartingSample(); + } + + private SeekBar mStartVolumeSeekBar; + private SeekBar mRampUpTimeSeekBar; + private TextView mRampUpTimeValue; + + private Ringtone mRingtone; + private Callback mCallback; + + private Handler mHandler; + private final Handler mMainHandler = new Handler(this); + + private static final int MSG_START_SAMPLE = 1; + private static final int MSG_STOP_SAMPLE = 2; + private static final int MSG_INIT_SAMPLE = 3; + private static final int MSG_SET_VOLUME = 4; + private static final int CHECK_RINGTONE_PLAYBACK_DELAY_MS = 1000; + + public IncreasingRingVolumePreference(Context context) { + this(context, null); + } + + public IncreasingRingVolumePreference(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public IncreasingRingVolumePreference(Context context, AttributeSet attrs, + int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public IncreasingRingVolumePreference(Context context, AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + setLayoutResource(R.layout.preference_increasing_ring); + initHandler(); + } + + public void setCallback(Callback callback) { + mCallback = callback; + } + + public void onActivityResume() { + initHandler(); + } + + @Override + public void onActivityStop() { + if (mHandler != null) { + postStopSample(); + mHandler.getLooper().quitSafely(); + mHandler = null; + } + } + + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case MSG_START_SAMPLE: + onStartSample((float) msg.arg1 / 1000F); + break; + case MSG_STOP_SAMPLE: + onStopSample(); + break; + case MSG_INIT_SAMPLE: + onInitSample(); + break; + case MSG_SET_VOLUME: + onSetVolume((float) msg.arg1 / 1000F); + break; + } + return true; + } + + @Override + protected void onBindView(View view) { + super.onBindView(view); + getPreferenceManager().registerOnActivityStopListener(this); + + initHandler(); + + final SeekBar seekBar = (SeekBar) view.findViewById(R.id.start_volume); + if (seekBar == mStartVolumeSeekBar) return; + + mStartVolumeSeekBar = seekBar; + mRampUpTimeSeekBar = (SeekBar) view.findViewById(R.id.ramp_up_time); + mRampUpTimeValue = (TextView) view.findViewById(R.id.ramp_up_time_value); + + final ContentResolver cr = getContext().getContentResolver(); + float startVolume = CMSettings.System.getFloat(cr, + CMSettings.System.INCREASING_RING_START_VOLUME, 0.1f); + int rampUpTime = CMSettings.System.getInt(cr, + CMSettings.System.INCREASING_RING_RAMP_UP_TIME, 10); + + mStartVolumeSeekBar.setProgress(Math.round(startVolume * 1000F)); + mStartVolumeSeekBar.setOnSeekBarChangeListener(this); + mRampUpTimeSeekBar.setOnSeekBarChangeListener(this); + mRampUpTimeSeekBar.setProgress((rampUpTime / 5) - 1); + mRampUpTimeValue.setText( + Formatter.formatShortElapsedTime(getContext(), rampUpTime * 1000)); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + if (seekBar == mStartVolumeSeekBar) { + postStartSample(seekBar.getProgress()); + } + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch) { + ContentResolver cr = getContext().getContentResolver(); + if (fromTouch && seekBar == mStartVolumeSeekBar) { + CMSettings.System.putFloat(cr, CMSettings.System.INCREASING_RING_START_VOLUME, + (float) progress / 1000F); + } else if (seekBar == mRampUpTimeSeekBar) { + int seconds = (progress + 1) * 5; + mRampUpTimeValue.setText( + Formatter.formatShortElapsedTime(getContext(), seconds * 1000)); + if (fromTouch) { + CMSettings.System.putInt(cr, + CMSettings.System.INCREASING_RING_RAMP_UP_TIME, seconds); + } + } + } + + private void initHandler() { + if (mHandler != null) return; + + HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler"); + thread.start(); + + mHandler = new Handler(thread.getLooper(), this); + mHandler.sendEmptyMessage(MSG_INIT_SAMPLE); + } + + private void onInitSample() { + mRingtone = RingtoneManager.getRingtone(getContext(), + Settings.System.DEFAULT_RINGTONE_URI); + if (mRingtone != null) { + mRingtone.setStreamType(AudioManager.STREAM_RING); + mRingtone.setAudioAttributes( + new AudioAttributes.Builder(mRingtone.getAudioAttributes()) + .setFlags(AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY | + AudioAttributes.FLAG_BYPASS_MUTE) + .build()); + } + } + + private void postStartSample(int progress) { + boolean playing = isSamplePlaying(); + mHandler.removeMessages(MSG_START_SAMPLE); + mHandler.removeMessages(MSG_SET_VOLUME); + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_SAMPLE, progress, 0), + playing ? CHECK_RINGTONE_PLAYBACK_DELAY_MS : 0); + if (playing) { + mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_VOLUME, progress, 0)); + } + } + + private void onStartSample(float volume) { + if (mRingtone == null) { + return; + } + if (!isSamplePlaying()) { + if (mCallback != null) { + mCallback.onStartingSample(); + } + try { + mRingtone.play(); + } catch (Throwable e) { + Log.w(TAG, "Error playing ringtone", e); + } + } + mRingtone.setVolume(volume); + } + + private void onSetVolume(float volume) { + if (mRingtone != null) { + mRingtone.setVolume(volume); + } + } + + private boolean isSamplePlaying() { + return mRingtone != null && mRingtone.isPlaying(); + } + + public void stopSample() { + if (mHandler != null) { + postStopSample(); + } + } + + private void postStopSample() { + // remove pending delayed start messages + mHandler.removeMessages(MSG_START_SAMPLE); + mHandler.removeMessages(MSG_STOP_SAMPLE); + mHandler.sendEmptyMessage(MSG_STOP_SAMPLE); + } + + private void onStopSample() { + if (mRingtone != null) { + mRingtone.stop(); + } + } +} diff --git a/src/com/android/settings/notification/NotificationBackend.java b/src/com/android/settings/notification/NotificationBackend.java index 2060719..ae83179 100644 --- a/src/com/android/settings/notification/NotificationBackend.java +++ b/src/com/android/settings/notification/NotificationBackend.java @@ -47,6 +47,7 @@ public class NotificationBackend { row.priority = getHighPriority(row.pkg, row.uid); row.peekable = getPeekable(row.pkg, row.uid); row.sensitive = getSensitive(row.pkg, row.uid); + row.soundTimeout = getNotificationSoundTimeout(row.pkg, row.uid); return row; } @@ -130,6 +131,44 @@ public class NotificationBackend { } } + public int getShowNotificationForPackageOnKeyguard(String pkg, int uid) { + try { + return sINM.getShowNotificationForPackageOnKeyguard(pkg, uid); + } catch (Exception e) { + Log.w(TAG, "Error calling NoMan", e); + return Notification.SHOW_ALL_NOTI_ON_KEYGUARD; + } + } + + public boolean setShowNotificationForPackageOnKeyguard(String pkg, int uid, int status) { + try { + sINM.setShowNotificationForPackageOnKeyguard(pkg, uid, status); + return true; + } catch (Exception e) { + Log.w(TAG, "Error calling NoMan", e); + return false; + } + } + + public long getNotificationSoundTimeout(String pkg, int uid) { + try { + return sINM.getPackageNotificationSoundTimeout(pkg, uid); + } catch (Exception e) { + Log.w(TAG, "Error calling NoMan", e); + return 0; + } + } + + public boolean setNotificationSoundTimeout(String pkg, int uid, long timeout) { + try { + sINM.setPackageNotificationSoundTimeout(pkg, uid, timeout); + return true; + } catch (Exception e) { + Log.w(TAG, "Error calling NoMan", e); + return false; + } + } + static class Row { public String section; } @@ -145,6 +184,7 @@ public class NotificationBackend { public boolean peekable; public boolean sensitive; public boolean first; // first app in section + public long soundTimeout; } } diff --git a/src/com/android/settings/notification/NotificationManagerSettings.java b/src/com/android/settings/notification/NotificationManagerSettings.java new file mode 100644 index 0000000..cb7cfc3 --- /dev/null +++ b/src/com/android/settings/notification/NotificationManagerSettings.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2015 The CyanogenMod project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notification; + +import android.content.Context; +import android.os.Bundle; +import android.os.UserHandle; +import android.preference.PreferenceCategory; +import android.provider.SearchIndexableResource; +import android.provider.Settings; +import android.util.Log; + +import com.android.internal.logging.MetricsLogger; +import com.android.internal.widget.LockPatternUtils; +import com.android.settings.DropDownPreference; +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settings.search.Indexable; +import org.cyanogenmod.internal.logging.CMMetricsLogger; + +import java.util.ArrayList; +import java.util.List; + +public class NotificationManagerSettings extends SettingsPreferenceFragment + implements Indexable { + + private static final String TAG = NotificationManagerSettings.class.getSimpleName(); + + private static final String KEY_LOCK_SCREEN_NOTIFICATIONS = "lock_screen_notifications"; + + private boolean mSecure; + private int mLockscreenSelectedValue; + private DropDownPreference mLockscreen; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + addPreferencesFromResource(R.xml.notification_manager_settings); + mSecure = new LockPatternUtils(getActivity()).isSecure(UserHandle.myUserId()); + initLockscreenNotifications(); + } + + // === Lockscreen (public / private) notifications === + + private void initLockscreenNotifications() { + mLockscreen = (DropDownPreference) findPreference(KEY_LOCK_SCREEN_NOTIFICATIONS); + if (mLockscreen == null) { + Log.i(TAG, "Preference not found: " + KEY_LOCK_SCREEN_NOTIFICATIONS); + return; + } + + mLockscreen.addItem(R.string.lock_screen_notifications_summary_show, + R.string.lock_screen_notifications_summary_show); + if (mSecure) { + mLockscreen.addItem(R.string.lock_screen_notifications_summary_hide, + R.string.lock_screen_notifications_summary_hide); + } + mLockscreen.addItem(R.string.lock_screen_notifications_summary_disable, + R.string.lock_screen_notifications_summary_disable); + updateLockscreenNotifications(); + mLockscreen.setCallback(new DropDownPreference.Callback() { + @Override + public boolean onItemSelected(int pos, Object value) { + final int val = (Integer) value; + if (val == mLockscreenSelectedValue) { + return true; + } + final boolean enabled = val != R.string.lock_screen_notifications_summary_disable; + final boolean show = val == R.string.lock_screen_notifications_summary_show; + Settings.Secure.putInt(getContentResolver(), + Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, show ? 1 : 0); + Settings.Secure.putInt(getContentResolver(), + Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, enabled ? 1 : 0); + mLockscreenSelectedValue = val; + return true; + } + }); + } + + private void updateLockscreenNotifications() { + if (mLockscreen == null) { + return; + } + final boolean enabled = getLockscreenNotificationsEnabled(); + final boolean allowPrivate = !mSecure || getLockscreenAllowPrivateNotifications(); + mLockscreenSelectedValue = !enabled ? R.string.lock_screen_notifications_summary_disable : + allowPrivate ? R.string.lock_screen_notifications_summary_show : + R.string.lock_screen_notifications_summary_hide; + mLockscreen.setSelectedValue(mLockscreenSelectedValue); + } + + private boolean getLockscreenNotificationsEnabled() { + return Settings.Secure.getInt(getContentResolver(), + Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0) != 0; + } + + private boolean getLockscreenAllowPrivateNotifications() { + return Settings.Secure.getInt(getContentResolver(), + Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0) != 0; + } + + public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider() { + @Override + public List<SearchIndexableResource> getXmlResourcesToIndex(Context context, + boolean enabled) { + ArrayList<SearchIndexableResource> result = + new ArrayList<SearchIndexableResource>(); + + SearchIndexableResource sir = new SearchIndexableResource(context); + sir.xmlResId = R.xml.notification_manager_settings; + result.add(sir); + + return result; + } + }; + + @Override + protected int getMetricsCategory() { + return CMMetricsLogger.NOTIFICATION_MANAGER_SETTINGS; + } +} diff --git a/src/com/android/settings/notification/NotificationStation.java b/src/com/android/settings/notification/NotificationStation.java index a54e3dd..fa6b171 100644 --- a/src/com/android/settings/notification/NotificationStation.java +++ b/src/com/android/settings/notification/NotificationStation.java @@ -27,6 +27,8 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Resources; import android.graphics.drawable.Drawable; +import android.graphics.ColorFilter; +import android.graphics.LightingColorFilter; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -74,6 +76,7 @@ public class NotificationStation extends SettingsPreferenceFragment { private PackageManager mPm; private INotificationManager mNoMan; + private ColorFilter mFilter; private Runnable mRefreshListRunnable = new Runnable() { @Override @@ -128,6 +131,15 @@ public class NotificationStation extends SettingsPreferenceFragment { } @Override + public void onCreate(Bundle savedInstanceState) { + logd("onCreate(%s)", savedInstanceState); + super.onCreate(savedInstanceState); + + int colorPrimaryDark = getResources().getColor(R.color.theme_primary_dark); + mFilter = new LightingColorFilter(colorPrimaryDark, colorPrimaryDark); + } + + @Override public void onDetach() { try { mListener.unregisterAsSystemService(); @@ -275,13 +287,16 @@ public class NotificationStation extends SettingsPreferenceFragment { private Drawable loadIconDrawable(String pkg, int userId, int resId) { Resources r = getResourcesForUserPackage(pkg, userId); + Drawable d; if (resId == 0) { return null; } try { - return r.getDrawable(resId, null); + d = r.getDrawable(resId, null).mutate(); + d.setColorFilter(mFilter); + return d; } catch (RuntimeException e) { Log.w(TAG, "Icon not found in " + (pkg != null ? resId : "<system>") diff --git a/src/com/android/settings/notification/OtherSoundSettings.java b/src/com/android/settings/notification/OtherSoundSettings.java index 969ec90..935aea5 100644 --- a/src/com/android/settings/notification/OtherSoundSettings.java +++ b/src/com/android/settings/notification/OtherSoundSettings.java @@ -19,15 +19,22 @@ package com.android.settings.notification; import static com.android.settings.notification.SettingPref.TYPE_GLOBAL; import static com.android.settings.notification.SettingPref.TYPE_SYSTEM; +import android.app.Activity; import android.content.ContentResolver; import android.content.Context; +import android.content.Intent; import android.content.res.Resources; import android.database.ContentObserver; import android.media.AudioManager; +import android.media.Ringtone; +import android.media.RingtoneManager; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Vibrator; +import android.preference.Preference; +import android.preference.PreferenceScreen; +import android.preference.SwitchPreference; import android.provider.SearchIndexableResource; import android.provider.Settings.Global; import android.provider.Settings.System; @@ -37,8 +44,10 @@ import com.android.internal.logging.MetricsLogger; import com.android.settings.R; import com.android.settings.SettingsPreferenceFragment; import com.android.settings.Utils; +import com.android.settings.hardware.VibratorIntensity; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.Indexable; +import cyanogenmod.providers.CMSettings; import java.util.ArrayList; import java.util.Arrays; @@ -47,6 +56,7 @@ import java.util.List; public class OtherSoundSettings extends SettingsPreferenceFragment implements Indexable { private static final String TAG = "OtherSoundSettings"; + private static final int DEFAULT_OFF = 0; private static final int DEFAULT_ON = 1; private static final int EMERGENCY_TONE_SILENT = 0; @@ -62,11 +72,23 @@ public class OtherSoundSettings extends SettingsPreferenceFragment implements In private static final String KEY_SCREEN_LOCKING_SOUNDS = "screen_locking_sounds"; private static final String KEY_CHARGING_SOUNDS = "charging_sounds"; private static final String KEY_DOCKING_SOUNDS = "docking_sounds"; + private static final String KEY_VOLUME_ADJUST_SOUNDS = "volume_adjust_sounds"; private static final String KEY_TOUCH_SOUNDS = "touch_sounds"; - private static final String KEY_VIBRATE_ON_TOUCH = "vibrate_on_touch"; private static final String KEY_DOCK_AUDIO_MEDIA = "dock_audio_media"; private static final String KEY_EMERGENCY_TONE = "emergency_tone"; + private static final String KEY_POWER_NOTIFICATIONS_VIBRATE = "power_notifications_vibrate"; + private static final String KEY_POWER_NOTIFICATIONS_RINGTONE = "power_notifications_ringtone"; + + // Request code for power notification ringtone picker + private static final int REQUEST_CODE_POWER_NOTIFICATIONS_RINGTONE = 1; + + // Used for power notification uri string if set to silent + private static final String POWER_NOTIFICATIONS_SILENT_URI = "silent"; + + private SwitchPreference mPowerSoundsVibrate; + private Preference mPowerSoundsRingtone; + private static final SettingPref PREF_DIAL_PAD_TONES = new SettingPref( TYPE_SYSTEM, KEY_DIAL_PAD_TONES, System.DTMF_TONE_WHEN_DIALING, DEFAULT_ON) { @Override @@ -79,7 +101,7 @@ public class OtherSoundSettings extends SettingsPreferenceFragment implements In TYPE_SYSTEM, KEY_SCREEN_LOCKING_SOUNDS, System.LOCKSCREEN_SOUNDS_ENABLED, DEFAULT_ON); private static final SettingPref PREF_CHARGING_SOUNDS = new SettingPref( - TYPE_GLOBAL, KEY_CHARGING_SOUNDS, Global.CHARGING_SOUNDS_ENABLED, DEFAULT_ON); + TYPE_GLOBAL, KEY_CHARGING_SOUNDS, Global.CHARGING_SOUNDS_ENABLED, DEFAULT_OFF); private static final SettingPref PREF_DOCKING_SOUNDS = new SettingPref( TYPE_GLOBAL, KEY_DOCKING_SOUNDS, Global.DOCK_SOUNDS_ENABLED, DEFAULT_ON) { @@ -89,6 +111,15 @@ public class OtherSoundSettings extends SettingsPreferenceFragment implements In } }; + private static final SettingPref PREF_VOLUME_ADJUST_SOUNDS = new SettingPref( + TYPE_SYSTEM, KEY_VOLUME_ADJUST_SOUNDS, CMSettings.System.VOLUME_ADJUST_SOUNDS_ENABLED, + DEFAULT_ON) { + @Override + public boolean isApplicable(Context context) { + return Utils.hasVolumeRocker(context); + } + }; + private static final SettingPref PREF_TOUCH_SOUNDS = new SettingPref( TYPE_SYSTEM, KEY_TOUCH_SOUNDS, System.SOUND_EFFECTS_ENABLED, DEFAULT_ON) { @Override @@ -103,14 +134,6 @@ public class OtherSoundSettings extends SettingsPreferenceFragment implements In } }; - private static final SettingPref PREF_VIBRATE_ON_TOUCH = new SettingPref( - TYPE_SYSTEM, KEY_VIBRATE_ON_TOUCH, System.HAPTIC_FEEDBACK_ENABLED, DEFAULT_ON) { - @Override - public boolean isApplicable(Context context) { - return hasHaptic(context); - } - }; - private static final SettingPref PREF_DOCK_AUDIO_MEDIA = new SettingPref( TYPE_GLOBAL, KEY_DOCK_AUDIO_MEDIA, Global.DOCK_AUDIO_MEDIA_ENABLED, DEFAULT_DOCK_AUDIO_MEDIA, DOCK_AUDIO_MEDIA_DISABLED, DOCK_AUDIO_MEDIA_ENABLED) { @@ -161,8 +184,8 @@ public class OtherSoundSettings extends SettingsPreferenceFragment implements In PREF_SCREEN_LOCKING_SOUNDS, PREF_CHARGING_SOUNDS, PREF_DOCKING_SOUNDS, + PREF_VOLUME_ADJUST_SOUNDS, PREF_TOUCH_SOUNDS, - PREF_VIBRATE_ON_TOUCH, PREF_DOCK_AUDIO_MEDIA, PREF_EMERGENCY_TONE, }; @@ -189,6 +212,37 @@ public class OtherSoundSettings extends SettingsPreferenceFragment implements In mContext = getActivity(); + // power state change notification sounds + mPowerSoundsVibrate = (SwitchPreference) findPreference(KEY_POWER_NOTIFICATIONS_VIBRATE); + mPowerSoundsVibrate.setChecked(CMSettings.Global.getInt(getContentResolver(), + CMSettings.Global.POWER_NOTIFICATIONS_VIBRATE, 0) != 0); + Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); + if (vibrator == null || !vibrator.hasVibrator()) { + removePreference(KEY_POWER_NOTIFICATIONS_VIBRATE); + } + + mPowerSoundsRingtone = findPreference(KEY_POWER_NOTIFICATIONS_RINGTONE); + String currentPowerRingtonePath = CMSettings.Global.getString(getContentResolver(), + CMSettings.Global.POWER_NOTIFICATIONS_RINGTONE); + + // set to default notification if we don't yet have one + if (currentPowerRingtonePath == null) { + currentPowerRingtonePath = System.DEFAULT_NOTIFICATION_URI.toString(); + CMSettings.Global.putString(getContentResolver(), + CMSettings.Global.POWER_NOTIFICATIONS_RINGTONE, currentPowerRingtonePath); + } + // is it silent ? + if (currentPowerRingtonePath.equals(POWER_NOTIFICATIONS_SILENT_URI)) { + mPowerSoundsRingtone.setSummary( + getString(R.string.power_notifications_ringtone_silent)); + } else { + final Ringtone ringtone = + RingtoneManager.getRingtone(getActivity(), Uri.parse(currentPowerRingtonePath)); + if (ringtone != null) { + mPowerSoundsRingtone.setSummary(ringtone.getTitle(getActivity())); + } + } + for (SettingPref pref : PREFS) { pref.init(this); } @@ -206,13 +260,27 @@ public class OtherSoundSettings extends SettingsPreferenceFragment implements In mSettingsObserver.register(false); } - private static boolean hasDockSettings(Context context) { - return context.getResources().getBoolean(R.bool.has_dock_settings); + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { + if (preference == mPowerSoundsVibrate) { + CMSettings.Global.putInt(getContentResolver(), + CMSettings.Global.POWER_NOTIFICATIONS_VIBRATE, + mPowerSoundsVibrate.isChecked() ? 1 : 0); + + } else if (preference == mPowerSoundsRingtone) { + launchNotificationSoundPicker(REQUEST_CODE_POWER_NOTIFICATIONS_RINGTONE, + CMSettings.Global.getString(getContentResolver(), + CMSettings.Global.POWER_NOTIFICATIONS_RINGTONE)); + } else { + // If we didn't handle it, let preferences handle it. + return super.onPreferenceTreeClick(preferenceScreen, preference); + } + + return true; } - private static boolean hasHaptic(Context context) { - final Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); - return vibrator != null && vibrator.hasVibrator(); + private static boolean hasDockSettings(Context context) { + return context.getResources().getBoolean(R.bool.has_dock_settings); } // === Callbacks === @@ -267,4 +335,58 @@ public class OtherSoundSettings extends SettingsPreferenceFragment implements In return rt; } }; + + private void launchNotificationSoundPicker(int code, String currentPowerRingtonePath) { + final Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); + + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, + getString(R.string.power_notifications_ringtone_title)); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, + RingtoneManager.TYPE_NOTIFICATION); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, + System.DEFAULT_NOTIFICATION_URI); + if (currentPowerRingtonePath != null && + !currentPowerRingtonePath.equals(POWER_NOTIFICATIONS_SILENT_URI)) { + Uri uri = Uri.parse(currentPowerRingtonePath); + if (uri != null) { + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, uri); + } + } + startActivityForResult(intent, code); + } + + private void setPowerNotificationRingtone(Intent intent) { + final Uri uri = intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); + + final String toneName; + final String toneUriPath; + + if ( uri != null ) { + final Ringtone ringtone = RingtoneManager.getRingtone(getActivity(), uri); + toneName = ringtone.getTitle(getActivity()); + toneUriPath = uri.toString(); + } else { + // silent + toneName = getString(R.string.power_notifications_ringtone_silent); + toneUriPath = POWER_NOTIFICATIONS_SILENT_URI; + } + + mPowerSoundsRingtone.setSummary(toneName); + CMSettings.Global.putString(getContentResolver(), + CMSettings.Global.POWER_NOTIFICATIONS_RINGTONE, toneUriPath); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case REQUEST_CODE_POWER_NOTIFICATIONS_RINGTONE: + if (resultCode == Activity.RESULT_OK) { + setPowerNotificationRingtone(data); + } + break; + default: + super.onActivityResult(requestCode, resultCode, data); + break; + } + } } diff --git a/src/com/android/settings/notification/NotificationSettings.java b/src/com/android/settings/notification/SoundSettings.java index e0d5e8c..f51a342 100644 --- a/src/com/android/settings/notification/NotificationSettings.java +++ b/src/com/android/settings/notification/SoundSettings.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The CyanogenMod Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,38 +38,46 @@ import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; -import android.os.UserHandle; import android.os.UserManager; import android.os.Vibrator; import android.preference.Preference; import android.preference.Preference.OnPreferenceChangeListener; import android.preference.PreferenceCategory; +import android.preference.PreferenceScreen; import android.preference.SeekBarVolumizer; +import android.preference.SwitchPreference; import android.preference.TwoStatePreference; import android.provider.MediaStore; import android.provider.OpenableColumns; import android.provider.SearchIndexableResource; import android.provider.Settings; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; import android.util.Log; import com.android.internal.logging.MetricsLogger; import com.android.internal.widget.LockPatternUtils; +import com.android.settings.DefaultRingtonePreference; import com.android.settings.DropDownPreference; import com.android.settings.R; import com.android.settings.SettingsPreferenceFragment; import com.android.settings.Utils; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.Indexable; +import cyanogenmod.hardware.CMHardwareManager; +import cyanogenmod.providers.CMSettings; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; -public class NotificationSettings extends SettingsPreferenceFragment implements Indexable { - private static final String TAG = "NotificationSettings"; +public class SoundSettings extends SettingsPreferenceFragment implements Indexable { + private static final String TAG = SoundSettings.class.getSimpleName(); - private static final String KEY_SOUND = "sound"; + private static final String KEY_SOUND = "sounds"; + private static final String KEY_VOLUMES = "volumes"; + private static final String KEY_VIBRATE = "vibrate"; private static final String KEY_MEDIA_VOLUME = "media_volume"; private static final String KEY_ALARM_VOLUME = "alarm_volume"; private static final String KEY_RING_VOLUME = "ring_volume"; @@ -79,23 +88,34 @@ public class NotificationSettings extends SettingsPreferenceFragment implements private static final String KEY_WIFI_DISPLAY = "wifi_display"; private static final String KEY_NOTIFICATION = "notification"; private static final String KEY_NOTIFICATION_PULSE = "notification_pulse"; - private static final String KEY_LOCK_SCREEN_NOTIFICATIONS = "lock_screen_notifications"; private static final String KEY_NOTIFICATION_ACCESS = "manage_notification_access"; - private static final String KEY_ZEN_ACCESS = "manage_zen_access"; + private static final String KEY_INCREASING_RING_VOLUME = "increasing_ring_volume"; + private static final String KEY_VIBRATION_INTENSITY = "vibration_intensity"; + private static final String KEY_VIBRATE_ON_TOUCH = "vibrate_on_touch"; private static final String KEY_ZEN_MODE = "zen_mode"; + private static final String KEY_VOLUME_LINK_NOTIFICATION = "volume_link_notification"; private static final String[] RESTRICTED_KEYS = { KEY_MEDIA_VOLUME, KEY_ALARM_VOLUME, KEY_RING_VOLUME, KEY_NOTIFICATION_VOLUME, - KEY_ZEN_ACCESS, KEY_ZEN_MODE, }; private static final int SAMPLE_CUTOFF = 2000; // manually cap sample playback at 2 seconds private final VolumePreferenceCallback mVolumeCallback = new VolumePreferenceCallback(); + private final IncreasingRingVolumePreference.Callback mIncreasingRingVolumeCallback = + new IncreasingRingVolumePreference.Callback() { + @Override + public void onStartingSample() { + mVolumeCallback.stopSample(); + mHandler.removeMessages(H.STOP_SAMPLE); + mHandler.sendEmptyMessageDelayed(H.STOP_SAMPLE, SAMPLE_CUTOFF); + } + }; + private final H mHandler = new H(); private final SettingsObserver mSettingsObserver = new SettingsObserver(); private final Receiver mReceiver = new Receiver(); @@ -106,20 +126,20 @@ public class NotificationSettings extends SettingsPreferenceFragment implements private boolean mVoiceCapable; private Vibrator mVibrator; private AudioManager mAudioManager; - private VolumeSeekBarPreference mRingOrNotificationPreference; + private VolumeSeekBarPreference mRingPreference; + private VolumeSeekBarPreference mNotificationPreference; - private Preference mPhoneRingtonePreference; + private TwoStatePreference mIncreasingRing; + private IncreasingRingVolumePreference mIncreasingRingVolume; + private ArrayList<DefaultRingtonePreference> mPhoneRingtonePreferences; private Preference mNotificationRingtonePreference; private TwoStatePreference mVibrateWhenRinging; - private TwoStatePreference mNotificationPulse; - private DropDownPreference mLockscreen; private Preference mNotificationAccess; - private Preference mZenAccess; private boolean mSecure; private int mLockscreenSelectedValue; private ComponentName mSuppressor; private int mRingerMode = -1; - + private SwitchPreference mVolumeLinkNotificationSwitch; private UserManager mUserManager; @Override @@ -134,7 +154,6 @@ public class NotificationSettings extends SettingsPreferenceFragment implements mPM = mContext.getPackageManager(); mUserManager = UserManager.get(getContext()); mVoiceCapable = Utils.isVoiceCapable(mContext); - mSecure = new LockPatternUtils(getActivity()).isSecure(UserHandle.myUserId()); mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); mVibrator = (Vibrator) getActivity().getSystemService(Context.VIBRATOR_SERVICE); @@ -142,36 +161,37 @@ public class NotificationSettings extends SettingsPreferenceFragment implements mVibrator = null; } - addPreferencesFromResource(R.xml.notification_settings); + addPreferencesFromResource(R.xml.sounds); - final PreferenceCategory sound = (PreferenceCategory) findPreference(KEY_SOUND); + final PreferenceCategory volumes = (PreferenceCategory) findPreference(KEY_VOLUMES); + final PreferenceCategory sounds = (PreferenceCategory) findPreference(KEY_SOUND); + final PreferenceCategory vibrate = (PreferenceCategory) findPreference(KEY_VIBRATE); initVolumePreference(KEY_MEDIA_VOLUME, AudioManager.STREAM_MUSIC, com.android.internal.R.drawable.ic_audio_media_mute); initVolumePreference(KEY_ALARM_VOLUME, AudioManager.STREAM_ALARM, com.android.internal.R.drawable.ic_audio_alarm_mute); if (mVoiceCapable) { - mRingOrNotificationPreference = + mRingPreference = initVolumePreference(KEY_RING_VOLUME, AudioManager.STREAM_RING, com.android.internal.R.drawable.ic_audio_ring_notif_mute); - sound.removePreference(sound.findPreference(KEY_NOTIFICATION_VOLUME)); + mVolumeLinkNotificationSwitch = (SwitchPreference) + volumes.findPreference(KEY_VOLUME_LINK_NOTIFICATION); } else { - mRingOrNotificationPreference = - initVolumePreference(KEY_NOTIFICATION_VOLUME, AudioManager.STREAM_NOTIFICATION, - com.android.internal.R.drawable.ic_audio_ring_notif_mute); - sound.removePreference(sound.findPreference(KEY_RING_VOLUME)); + volumes.removePreference(volumes.findPreference(KEY_RING_VOLUME)); + volumes.removePreference(volumes.findPreference(KEY_VOLUME_LINK_NOTIFICATION)); } - initRingtones(sound); - initVibrateWhenRinging(sound); - final PreferenceCategory notification = (PreferenceCategory) - findPreference(KEY_NOTIFICATION); - initPulse(notification); - initLockscreenNotifications(notification); + CMHardwareManager hardware = CMHardwareManager.getInstance(mContext); + if (!hardware.isSupported(CMHardwareManager.FEATURE_VIBRATOR)) { + vibrate.removePreference(vibrate.findPreference(KEY_VIBRATION_INTENSITY)); + } + + initRingtones(sounds); + initIncreasingRing(sounds); + initVibrateWhenRinging(vibrate); mNotificationAccess = findPreference(KEY_NOTIFICATION_ACCESS); refreshNotificationListeners(); - mZenAccess = findPreference(KEY_ZEN_ACCESS); - refreshZenAccess(); updateRingerMode(); updateEffectsSuppressor(); } @@ -180,15 +200,18 @@ public class NotificationSettings extends SettingsPreferenceFragment implements public void onResume() { super.onResume(); refreshNotificationListeners(); - refreshZenAccess(); lookupRingtoneNames(); + updateNotificationPreferenceState(); mSettingsObserver.register(true); mReceiver.register(true); - updateRingOrNotificationPreference(); + updateRingPreference(); updateEffectsSuppressor(); for (VolumeSeekBarPreference volumePref : mVolumePrefs) { volumePref.onActivityResume(); } + if (mIncreasingRingVolume != null) { + mIncreasingRingVolume.onActivityResume(); + } boolean isRestricted = mUserManager.hasUserRestriction(UserManager.DISALLOW_ADJUST_VOLUME); for (String key : RESTRICTED_KEYS) { Preference pref = findPreference(key); @@ -202,14 +225,23 @@ public class NotificationSettings extends SettingsPreferenceFragment implements public void onPause() { super.onPause(); mVolumeCallback.stopSample(); + if (mIncreasingRingVolume != null) { + mIncreasingRingVolume.stopSample(); + } mSettingsObserver.register(false); mReceiver.register(false); } + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { + return super.onPreferenceTreeClick(preferenceScreen, preference); + } + // === Volumes === private VolumeSeekBarPreference initVolumePreference(String key, int stream, int muteIcon) { final VolumeSeekBarPreference volumePref = (VolumeSeekBarPreference) findPreference(key); + if (volumePref == null) return null; volumePref.setCallback(mVolumeCallback); volumePref.setStream(stream); mVolumePrefs.add(volumePref); @@ -217,12 +249,30 @@ public class NotificationSettings extends SettingsPreferenceFragment implements return volumePref; } - private void updateRingOrNotificationPreference() { - mRingOrNotificationPreference.showIcon(mSuppressor != null - ? com.android.internal.R.drawable.ic_audio_ring_notif_mute - : mRingerMode == AudioManager.RINGER_MODE_VIBRATE || wasRingerModeVibrate() - ? com.android.internal.R.drawable.ic_audio_ring_notif_vibrate - : com.android.internal.R.drawable.ic_audio_ring_notif); + private void updateNotificationPreferenceState() { + if (mNotificationPreference == null) { + mNotificationPreference = initVolumePreference(KEY_NOTIFICATION_VOLUME, + AudioManager.STREAM_NOTIFICATION, + com.android.internal.R.drawable.ic_audio_ring_notif_mute); + } + + if (mVoiceCapable) { + final boolean enabled = Settings.Secure.getInt(getContentResolver(), + Settings.Secure.VOLUME_LINK_NOTIFICATION, 1) == 1; + if (mVolumeLinkNotificationSwitch != null) { + mVolumeLinkNotificationSwitch.setChecked(enabled); + } + } + } + + private void updateRingPreference() { + if (mRingPreference != null) { + mRingPreference.showIcon(mSuppressor != null + ? com.android.internal.R.drawable.ic_audio_ring_notif_mute + : mRingerMode == AudioManager.RINGER_MODE_VIBRATE || wasRingerModeVibrate() + ? com.android.internal.R.drawable.ic_audio_ring_notif_vibrate + : R.drawable.ic_audio_ring); + } } private boolean wasRingerModeVibrate() { @@ -234,20 +284,20 @@ public class NotificationSettings extends SettingsPreferenceFragment implements final int ringerMode = mAudioManager.getRingerModeInternal(); if (mRingerMode == ringerMode) return; mRingerMode = ringerMode; - updateRingOrNotificationPreference(); + updateRingPreference(); } private void updateEffectsSuppressor() { final ComponentName suppressor = NotificationManager.from(mContext).getEffectsSuppressor(); if (Objects.equals(suppressor, mSuppressor)) return; mSuppressor = suppressor; - if (mRingOrNotificationPreference != null) { + if (mRingPreference != null) { final String text = suppressor != null ? mContext.getString(com.android.internal.R.string.muted_by, getSuppressorCaption(suppressor)) : null; - mRingOrNotificationPreference.setSuppressionText(text); + mRingPreference.setSuppressionText(text); } - updateRingOrNotificationPreference(); + updateRingPreference(); } private String getSuppressorCaption(ComponentName suppressor) { @@ -277,6 +327,9 @@ public class NotificationSettings extends SettingsPreferenceFragment implements if (mCurrent != null && mCurrent != sbv) { mCurrent.stopSample(); } + if (mIncreasingRingVolume != null) { + mIncreasingRingVolume.stopSample(); + } mCurrent = sbv; if (mCurrent != null) { mHandler.removeMessages(H.STOP_SAMPLE); @@ -300,10 +353,32 @@ public class NotificationSettings extends SettingsPreferenceFragment implements // === Phone & notification ringtone === private void initRingtones(PreferenceCategory root) { - mPhoneRingtonePreference = root.findPreference(KEY_PHONE_RINGTONE); - if (mPhoneRingtonePreference != null && !mVoiceCapable) { - root.removePreference(mPhoneRingtonePreference); - mPhoneRingtonePreference = null; + DefaultRingtonePreference phoneRingtonePreference = + (DefaultRingtonePreference) root.findPreference(KEY_PHONE_RINGTONE); + if (mPhoneRingtonePreferences != null && !mVoiceCapable || !Utils.isUserOwner()) { + root.removePreference(phoneRingtonePreference); + mPhoneRingtonePreferences = null; + } else { + mPhoneRingtonePreferences = new ArrayList<DefaultRingtonePreference>(); + TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService( + Context.TELEPHONY_SERVICE); + if (telephonyManager.isMultiSimEnabled()){ + root.removePreference(phoneRingtonePreference); + PreferenceCategory soundCategory = (PreferenceCategory) findPreference(KEY_SOUND); + for (int i = 0; i < TelephonyManager.getDefault().getSimCount(); i++) { + DefaultRingtonePreference ringtonePreference = + new DefaultRingtonePreference(mContext, null); + String title = getString(R.string.sim_ringtone_title, i + 1); + ringtonePreference.setTitle(title); + ringtonePreference.setSubId(i); + ringtonePreference.setOrder(0); + ringtonePreference.setRingtoneType(RingtoneManager.TYPE_RINGTONE); + soundCategory.addPreference(ringtonePreference); + mPhoneRingtonePreferences.add(ringtonePreference); + } + } else { + mPhoneRingtonePreferences.add(phoneRingtonePreference); + } } mNotificationRingtonePreference = root.findPreference(KEY_NOTIFICATION_RINGTONE); } @@ -315,16 +390,20 @@ public class NotificationSettings extends SettingsPreferenceFragment implements private final Runnable mLookupRingtoneNames = new Runnable() { @Override public void run() { - if (mPhoneRingtonePreference != null) { - final CharSequence summary = updateRingtoneName( - mContext, RingtoneManager.TYPE_RINGTONE); - if (summary != null) { - mHandler.obtainMessage(H.UPDATE_PHONE_RINGTONE, summary).sendToTarget(); + if (mPhoneRingtonePreferences != null) { + ArrayList<CharSequence> summaries = new ArrayList<CharSequence>(); + for (DefaultRingtonePreference preference : mPhoneRingtonePreferences) { + CharSequence summary = updateRingtoneName( + mContext, RingtoneManager.TYPE_RINGTONE, preference.getSubId()); + summaries.add(summary); + } + if (!summaries.isEmpty()) { + mHandler.obtainMessage(H.UPDATE_PHONE_RINGTONE, summaries).sendToTarget(); } } if (mNotificationRingtonePreference != null) { final CharSequence summary = updateRingtoneName( - mContext, RingtoneManager.TYPE_NOTIFICATION); + mContext, RingtoneManager.TYPE_NOTIFICATION, -1); if (summary != null) { mHandler.obtainMessage(H.UPDATE_NOTIFICATION_RINGTONE, summary).sendToTarget(); } @@ -332,12 +411,17 @@ public class NotificationSettings extends SettingsPreferenceFragment implements } }; - private static CharSequence updateRingtoneName(Context context, int type) { + private static CharSequence updateRingtoneName(Context context, int type, int subId) { if (context == null) { Log.e(TAG, "Unable to update ringtone name, no context provided"); return null; } - Uri ringtoneUri = RingtoneManager.getActualDefaultRingtoneUri(context, type); + Uri ringtoneUri; + if (type != RingtoneManager.TYPE_RINGTONE || subId <= 0) { + ringtoneUri = RingtoneManager.getActualDefaultRingtoneUri(context, type); + } else { + ringtoneUri = RingtoneManager.getActualRingtoneUriBySubId(context, subId); + } CharSequence summary = context.getString(com.android.internal.R.string.ringtone_unknown); // Is it a silent ringtone? if (ringtoneUri == null) { @@ -371,6 +455,30 @@ public class NotificationSettings extends SettingsPreferenceFragment implements return summary; } + // === Increasing ringtone === + + private void initIncreasingRing(PreferenceCategory root) { + mIncreasingRing = (TwoStatePreference) + root.findPreference(CMSettings.System.INCREASING_RING); + mIncreasingRingVolume = (IncreasingRingVolumePreference) + root.findPreference(KEY_INCREASING_RING_VOLUME); + + if (!mVoiceCapable) { + if (mIncreasingRing != null) { + root.removePreference(mIncreasingRing); + mIncreasingRing = null; + } + if (mIncreasingRingVolume != null) { + root.removePreference(mIncreasingRingVolume); + mIncreasingRingVolume = null; + } + } else { + if (mIncreasingRingVolume != null) { + mIncreasingRingVolume.setCallback(mIncreasingRingVolumeCallback); + } + } + } + // === Vibrate when ringing === private void initVibrateWhenRinging(PreferenceCategory root) { @@ -379,7 +487,7 @@ public class NotificationSettings extends SettingsPreferenceFragment implements Log.i(TAG, "Preference not found: " + KEY_VIBRATE_WHEN_RINGING); return; } - if (!mVoiceCapable) { + if (!mVoiceCapable || !Utils.isUserOwner()) { root.removePreference(mVibrateWhenRinging); mVibrateWhenRinging = null; return; @@ -403,90 +511,6 @@ public class NotificationSettings extends SettingsPreferenceFragment implements Settings.System.VIBRATE_WHEN_RINGING, 0) != 0); } - // === Pulse notification light === - - private void initPulse(PreferenceCategory parent) { - mNotificationPulse = (TwoStatePreference) parent.findPreference(KEY_NOTIFICATION_PULSE); - if (mNotificationPulse == null) { - Log.i(TAG, "Preference not found: " + KEY_NOTIFICATION_PULSE); - return; - } - if (!getResources() - .getBoolean(com.android.internal.R.bool.config_intrusiveNotificationLed)) { - parent.removePreference(mNotificationPulse); - } else { - updatePulse(); - mNotificationPulse.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - final boolean val = (Boolean)newValue; - return Settings.System.putInt(getContentResolver(), - Settings.System.NOTIFICATION_LIGHT_PULSE, - val ? 1 : 0); - } - }); - } - } - - private void updatePulse() { - if (mNotificationPulse == null) { - return; - } - try { - mNotificationPulse.setChecked(Settings.System.getInt(getContentResolver(), - Settings.System.NOTIFICATION_LIGHT_PULSE) == 1); - } catch (Settings.SettingNotFoundException snfe) { - Log.e(TAG, Settings.System.NOTIFICATION_LIGHT_PULSE + " not found"); - } - } - - // === Lockscreen (public / private) notifications === - - private void initLockscreenNotifications(PreferenceCategory parent) { - mLockscreen = (DropDownPreference) parent.findPreference(KEY_LOCK_SCREEN_NOTIFICATIONS); - if (mLockscreen == null) { - Log.i(TAG, "Preference not found: " + KEY_LOCK_SCREEN_NOTIFICATIONS); - return; - } - - boolean isSecureNotificationsDisabled = isSecureNotificationsDisabled(); - boolean isUnredactedNotificationsDisabled = isUnredactedNotificationsDisabled(); - if (!isSecureNotificationsDisabled && !isUnredactedNotificationsDisabled) { - mLockscreen.addItem(R.string.lock_screen_notifications_summary_show, - R.string.lock_screen_notifications_summary_show); - } - if (mSecure && !isSecureNotificationsDisabled) { - mLockscreen.addItem(R.string.lock_screen_notifications_summary_hide, - R.string.lock_screen_notifications_summary_hide); - } - mLockscreen.addItem(R.string.lock_screen_notifications_summary_disable, - R.string.lock_screen_notifications_summary_disable); - updateLockscreenNotifications(); - if (mLockscreen.getItemCount() > 1) { - mLockscreen.setCallback(new DropDownPreference.Callback() { - @Override - public boolean onItemSelected(int pos, Object value) { - final int val = (Integer) value; - if (val == mLockscreenSelectedValue) { - return true; - } - final boolean enabled = - val != R.string.lock_screen_notifications_summary_disable; - final boolean show = val == R.string.lock_screen_notifications_summary_show; - Settings.Secure.putInt(getContentResolver(), - Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, show ? 1 : 0); - Settings.Secure.putInt(getContentResolver(), - Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, enabled ? 1 : 0); - mLockscreenSelectedValue = val; - return true; - } - }); - } else { - // There is one or less option for the user, disable the drop down. - mLockscreen.setEnabled(false); - } - } - private boolean isSecureNotificationsDisabled() { final DevicePolicyManager dpm = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE); @@ -501,28 +525,6 @@ public class NotificationSettings extends SettingsPreferenceFragment implements & DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS) != 0; } - private void updateLockscreenNotifications() { - if (mLockscreen == null) { - return; - } - final boolean enabled = getLockscreenNotificationsEnabled(); - final boolean allowPrivate = !mSecure || getLockscreenAllowPrivateNotifications(); - mLockscreenSelectedValue = !enabled ? R.string.lock_screen_notifications_summary_disable : - allowPrivate ? R.string.lock_screen_notifications_summary_show : - R.string.lock_screen_notifications_summary_hide; - mLockscreen.setSelectedValue(mLockscreenSelectedValue); - } - - private boolean getLockscreenNotificationsEnabled() { - return Settings.Secure.getInt(getContentResolver(), - Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0) != 0; - } - - private boolean getLockscreenAllowPrivateNotifications() { - return Settings.Secure.getInt(getContentResolver(), - Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0) != 0; - } - // === Notification listeners === private void refreshNotificationListeners() { @@ -539,12 +541,6 @@ public class NotificationSettings extends SettingsPreferenceFragment implements } } - // === Zen access === - - private void refreshZenAccess() { - // noop for now - } - // === Callbacks === private final class SettingsObserver extends ContentObserver { @@ -556,6 +552,8 @@ public class NotificationSettings extends SettingsPreferenceFragment implements Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); private final Uri LOCK_SCREEN_SHOW_URI = Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS); + private final Uri VOLUME_LINK_NOTIFICATION_URI = + Settings.Secure.getUriFor(Settings.Secure.VOLUME_LINK_NOTIFICATION); public SettingsObserver() { super(mHandler); @@ -568,6 +566,7 @@ public class NotificationSettings extends SettingsPreferenceFragment implements cr.registerContentObserver(NOTIFICATION_LIGHT_PULSE_URI, false, this); cr.registerContentObserver(LOCK_SCREEN_PRIVATE_URI, false, this); cr.registerContentObserver(LOCK_SCREEN_SHOW_URI, false, this); + cr.registerContentObserver(VOLUME_LINK_NOTIFICATION_URI, false, this); } else { cr.unregisterContentObserver(this); } @@ -579,11 +578,8 @@ public class NotificationSettings extends SettingsPreferenceFragment implements if (VIBRATE_WHEN_RINGING_URI.equals(uri)) { updateVibrateWhenRinging(); } - if (NOTIFICATION_LIGHT_PULSE_URI.equals(uri)) { - updatePulse(); - } - if (LOCK_SCREEN_PRIVATE_URI.equals(uri) || LOCK_SCREEN_SHOW_URI.equals(uri)) { - updateLockscreenNotifications(); + if (VOLUME_LINK_NOTIFICATION_URI.equals(uri)) { + updateNotificationPreferenceState(); } } } @@ -603,13 +599,20 @@ public class NotificationSettings extends SettingsPreferenceFragment implements public void handleMessage(Message msg) { switch (msg.what) { case UPDATE_PHONE_RINGTONE: - mPhoneRingtonePreference.setSummary((CharSequence) msg.obj); + ArrayList<CharSequence> summaries = (ArrayList<CharSequence>) msg.obj; + for (int i = 0; i < summaries.size(); i++) { + Preference preference = mPhoneRingtonePreferences.get(i); + preference.setSummary(summaries.get(i)); + } break; case UPDATE_NOTIFICATION_RINGTONE: mNotificationRingtonePreference.setSummary((CharSequence) msg.obj); break; case STOP_SAMPLE: mVolumeCallback.stopSample(); + if (mIncreasingRingVolume != null) { + mIncreasingRingVolume.stopSample(); + } break; case UPDATE_EFFECTS_SUPPRESSOR: updateEffectsSuppressor(); @@ -652,11 +655,17 @@ public class NotificationSettings extends SettingsPreferenceFragment implements public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new BaseSearchIndexProvider() { + private boolean mHasVibratorIntensity; + + @Override + public void prepare() { + super.prepare(); + } public List<SearchIndexableResource> getXmlResourcesToIndex( Context context, boolean enabled) { final SearchIndexableResource sir = new SearchIndexableResource(context); - sir.xmlResId = R.xml.notification_settings; + sir.xmlResId = R.xml.sounds; return Arrays.asList(sir); } @@ -670,6 +679,15 @@ public class NotificationSettings extends SettingsPreferenceFragment implements rt.add(KEY_WIFI_DISPLAY); rt.add(KEY_VIBRATE_WHEN_RINGING); } + Vibrator vib = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); + if (vib == null || !vib.hasVibrator()) { + rt.add(KEY_VIBRATE); + } + CMHardwareManager hardware = CMHardwareManager.getInstance(context); + if (!hardware.isSupported(CMHardwareManager.FEATURE_VIBRATOR)) { + rt.add(KEY_VIBRATION_INTENSITY); + } + return rt; } }; diff --git a/src/com/android/settings/notification/VolumeSeekBarPreference.java b/src/com/android/settings/notification/VolumeSeekBarPreference.java index 2603016..49bd815 100644 --- a/src/com/android/settings/notification/VolumeSeekBarPreference.java +++ b/src/com/android/settings/notification/VolumeSeekBarPreference.java @@ -90,6 +90,7 @@ public class VolumeSeekBarPreference extends SeekBarPreference mStopped = true; if (mVolumizer != null) { mVolumizer.stop(); + mVolumizer = null; } } @@ -137,7 +138,9 @@ public class VolumeSeekBarPreference extends SeekBarPreference mVolumizer.start(); mVolumizer.setSeekBar(mSeekBar); updateIconView(); - mCallback.onStreamValueChanged(mStream, mSeekBar.getProgress()); + if (mCallback != null) { + mCallback.onStreamValueChanged(mStream, mSeekBar.getProgress()); + } updateSuppressionText(); if (!isEnabled()) { mSeekBar.setEnabled(false); @@ -149,7 +152,9 @@ public class VolumeSeekBarPreference extends SeekBarPreference @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch) { super.onProgressChanged(seekBar, progress, fromTouch); - mCallback.onStreamValueChanged(mStream, progress); + if (mCallback != null) { + mCallback.onStreamValueChanged(mStream, progress); + } } private void updateIconView() { diff --git a/src/com/android/settings/notification/ZenModePrioritySettings.java b/src/com/android/settings/notification/ZenModePrioritySettings.java index 6e34bf7..08e0350 100644 --- a/src/com/android/settings/notification/ZenModePrioritySettings.java +++ b/src/com/android/settings/notification/ZenModePrioritySettings.java @@ -28,6 +28,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.settings.DropDownPreference; import com.android.settings.R; import com.android.settings.search.Indexable; +import cyanogenmod.providers.CMSettings; public class ZenModePrioritySettings extends ZenModeSettingsBase implements Indexable { private static final String KEY_REMINDERS = "reminders"; @@ -35,6 +36,8 @@ public class ZenModePrioritySettings extends ZenModeSettingsBase implements Inde private static final String KEY_MESSAGES = "messages"; private static final String KEY_CALLS = "calls"; private static final String KEY_REPEAT_CALLERS = "repeat_callers"; + private static final String KEY_VIBRATION = "vibration"; + private static final String KEY_ALLOW_LIGHTS = "zen_priority_allow_lights"; private static final int SOURCE_NONE = -1; @@ -145,6 +148,27 @@ public class ZenModePrioritySettings extends ZenModeSettingsBase implements Inde } }); + DropDownPreference vibration = (DropDownPreference) root.findPreference(KEY_VIBRATION); + vibration.addItem(R.string.zen_mode_vibration_never, null); + vibration.addItem(R.string.zen_mode_vibration_calls_only, null); + vibration.addItem(R.string.zen_mode_vibration_calls_and_notifications, null); + vibration.setSelectedItem(CMSettings.System.getInt(getContentResolver(), + CMSettings.System.ZEN_PRIORITY_VIBRATION_MODE, 0)); + vibration.setCallback(new DropDownPreference.Callback() { + @Override + public boolean onItemSelected(int pos, Object newValue) { + CMSettings.System.putInt(getContentResolver(), + CMSettings.System.ZEN_PRIORITY_VIBRATION_MODE, pos); + return true; + } + }); + + // Remove of the "Allow notification light" setting if LED is not supported + if (!getResources().getBoolean( + com.android.internal.R.bool.config_intrusiveNotificationLed)) { + root.removePreference(findPreference(KEY_ALLOW_LIGHTS)); + } + updateControls(); } diff --git a/src/com/android/settings/notification/ZenModeScheduleDaysSelection.java b/src/com/android/settings/notification/ZenModeScheduleDaysSelection.java index 3e88046..a0af03f 100644 --- a/src/com/android/settings/notification/ZenModeScheduleDaysSelection.java +++ b/src/com/android/settings/notification/ZenModeScheduleDaysSelection.java @@ -61,8 +61,9 @@ public class ZenModeScheduleDaysSelection extends ScrollView { } mLayout.setOrientation(LinearLayout.VERTICAL); final Calendar c = Calendar.getInstance(); + int i = c.getFirstDayOfWeek() - 1; final LayoutInflater inflater = LayoutInflater.from(context); - for (int i = 0; i < DAYS.length; i++) { + for (int d = 0; d < DAYS.length; d++) { final int day = DAYS[i]; final CheckBox checkBox = (CheckBox) inflater.inflate(R.layout.zen_schedule_rule_day, this, false); @@ -77,6 +78,7 @@ public class ZenModeScheduleDaysSelection extends ScrollView { } }); mLayout.addView(checkBox); + i = ++i % DAYS.length; } } diff --git a/src/com/android/settings/notification/ZenModeSettings.java b/src/com/android/settings/notification/ZenModeSettings.java index f76ee38..8d21269 100644 --- a/src/com/android/settings/notification/ZenModeSettings.java +++ b/src/com/android/settings/notification/ZenModeSettings.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The CyanogenMod Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +26,7 @@ import android.util.SparseArray; import com.android.internal.logging.MetricsLogger; import com.android.settings.R; +import com.android.settings.Utils; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.Indexable; import com.android.settings.search.SearchIndexableRaw; @@ -35,8 +37,10 @@ import java.util.List; public class ZenModeSettings extends ZenModeSettingsBase implements Indexable { private static final String KEY_PRIORITY_SETTINGS = "priority_settings"; private static final String KEY_AUTOMATION_SETTINGS = "automation_settings"; + private static final String KEY_ZEN_ACCESS = "manage_zen_access"; private Preference mPrioritySettings; + private Preference mZenAccess; @Override public void onCreate(Bundle savedInstanceState) { @@ -49,12 +53,16 @@ public class ZenModeSettings extends ZenModeSettingsBase implements Indexable { if (!isScheduleSupported(mContext)) { removePreference(KEY_AUTOMATION_SETTINGS); } + + mZenAccess = findPreference(KEY_ZEN_ACCESS); + refreshZenAccess(); } @Override public void onResume() { super.onResume(); updateControls(); + refreshZenAccess(); } @Override @@ -77,21 +85,21 @@ public class ZenModeSettings extends ZenModeSettingsBase implements Indexable { } private void updatePrioritySettingsSummary() { - final boolean callers = mConfig.allowCalls || mConfig.allowRepeatCallers; - String s = getResources().getString(R.string.zen_mode_alarms); - s = appendLowercase(s, mConfig.allowReminders, R.string.zen_mode_reminders); - s = appendLowercase(s, mConfig.allowEvents, R.string.zen_mode_events); - s = appendLowercase(s, callers, R.string.zen_mode_selected_callers); - s = appendLowercase(s, mConfig.allowMessages, R.string.zen_mode_selected_messages); - mPrioritySettings.setSummary(s); - } - - private String appendLowercase(String s, boolean condition, int resId) { - if (condition) { - return getResources().getString(R.string.join_many_items_middle, s, - getResources().getString(resId).toLowerCase()); + final ArrayList<String> items = new ArrayList<>(); + items.add(getString(R.string.zen_mode_alarms)); + if (mConfig.allowReminders) { + items.add(getString(R.string.zen_mode_summary_reminders)); + } + if (mConfig.allowEvents) { + items.add(getString(R.string.zen_mode_summary_events)); + } + if (mConfig.allowCalls || mConfig.allowRepeatCallers) { + items.add(getString(R.string.zen_mode_summary_selected_callers)); } - return s; + if (mConfig.allowMessages) { + items.add(getString(R.string.zen_mode_summary_selected_messages)); + } + mPrioritySettings.setSummary(Utils.join(getResources(), items)); } private static SparseArray<String> allKeyTitles(Context context) { @@ -135,4 +143,10 @@ public class ZenModeSettings extends ZenModeSettingsBase implements Indexable { return rt; } }; + + // === Zen access === + + private void refreshZenAccess() { + // noop for now + } } diff --git a/src/com/android/settings/notificationlight/AlphaPatternDrawable.java b/src/com/android/settings/notificationlight/AlphaPatternDrawable.java new file mode 100644 index 0000000..e77118d --- /dev/null +++ b/src/com/android/settings/notificationlight/AlphaPatternDrawable.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2010 Daniel Nilsson + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notificationlight; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.Bitmap.Config; +import android.graphics.drawable.Drawable; + +/** + * This drawable that draws a simple white and gray chess board pattern. It's + * pattern you will often see as a background behind a partly transparent image + * in many applications. + * + * @author Daniel Nilsson + */ +public class AlphaPatternDrawable extends Drawable { + + private int mRectangleSize = 10; + + private Paint mPaint = new Paint(); + private Paint mPaintWhite = new Paint(); + private Paint mPaintGray = new Paint(); + + private int numRectanglesHorizontal; + private int numRectanglesVertical; + + /** + * Bitmap in which the pattern will be cached. + */ + private Bitmap mBitmap; + + public AlphaPatternDrawable(int rectangleSize) { + mRectangleSize = rectangleSize; + mPaintWhite.setColor(0xffffffff); + mPaintGray.setColor(0xffcbcbcb); + } + + @Override + public void draw(Canvas canvas) { + if (mBitmap != null) { + canvas.drawBitmap(mBitmap, null, getBounds(), mPaint); + } + } + + @Override + public int getOpacity() { + return 0; + } + + @Override + public void setAlpha(int alpha) { + throw new UnsupportedOperationException("Alpha is not supported by this drawwable."); + } + + @Override + public void setColorFilter(ColorFilter cf) { + throw new UnsupportedOperationException("ColorFilter is not supported by this drawwable."); + } + + @Override + protected void onBoundsChange(Rect bounds) { + super.onBoundsChange(bounds); + + int height = bounds.height(); + int width = bounds.width(); + + numRectanglesHorizontal = (int) Math.ceil((width / mRectangleSize)); + numRectanglesVertical = (int) Math.ceil(height / mRectangleSize); + + generatePatternBitmap(); + } + + /** + * This will generate a bitmap with the pattern as big as the rectangle we + * were allow to draw on. We do this to cache the bitmap so we don't need + * to recreate it each time draw() is called since it takes a few + * milliseconds. + */ + private void generatePatternBitmap() { + + if (getBounds().width() <= 0 || getBounds().height() <= 0) { + return; + } + + mBitmap = Bitmap.createBitmap(getBounds().width(), getBounds().height(), Config.ARGB_8888); + Canvas canvas = new Canvas(mBitmap); + + Rect r = new Rect(); + boolean verticalStartWhite = true; + for (int i = 0; i <= numRectanglesVertical; i++) { + boolean isWhite = verticalStartWhite; + for (int j = 0; j <= numRectanglesHorizontal; j++) { + r.top = i * mRectangleSize; + r.left = j * mRectangleSize; + r.bottom = r.top + mRectangleSize; + r.right = r.left + mRectangleSize; + + canvas.drawRect(r, isWhite ? mPaintWhite : mPaintGray); + + isWhite = !isWhite; + } + + verticalStartWhite = !verticalStartWhite; + } + } +} diff --git a/src/com/android/settings/notificationlight/ApplicationLightPreference.java b/src/com/android/settings/notificationlight/ApplicationLightPreference.java new file mode 100644 index 0000000..a12f45b --- /dev/null +++ b/src/com/android/settings/notificationlight/ApplicationLightPreference.java @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notificationlight; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.Resources; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.shapes.OvalShape; +import android.os.Bundle; +import android.preference.DialogPreference; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import com.android.settings.R; + +public class ApplicationLightPreference extends DialogPreference { + + private static String TAG = "AppLightPreference"; + public static final int DEFAULT_TIME = 1000; + public static final int DEFAULT_COLOR = 0xffffff; + + private ImageView mLightColorView; + private TextView mOnValueView; + private TextView mOffValueView; + + private int mColorValue; + private int mOnValue; + private int mOffValue; + private boolean mOnOffChangeable; + + private Resources mResources; + + /** + * @param context + * @param attrs + */ + public ApplicationLightPreference(Context context, AttributeSet attrs) { + super(context, attrs); + mColorValue = DEFAULT_COLOR; + mOnValue = DEFAULT_TIME; + mOffValue = DEFAULT_TIME; + mOnOffChangeable = context.getResources().getBoolean( + com.android.internal.R.bool.config_ledCanPulse); + init(); + } + + /** + * @param context + * @param color + * @param onValue + * @param offValue + */ + public ApplicationLightPreference(Context context, int color, int onValue, int offValue) { + super(context, null); + mColorValue = color; + mOnValue = onValue; + mOffValue = offValue; + mOnOffChangeable = context.getResources().getBoolean( + com.android.internal.R.bool.config_ledCanPulse); + init(); + } + + /** + * @param context + * @param color + * @param onValue + * @param offValue + */ + public ApplicationLightPreference(Context context, int color, int onValue, int offValue, boolean onOffChangeable) { + super(context, null); + mColorValue = color; + mOnValue = onValue; + mOffValue = offValue; + mOnOffChangeable = onOffChangeable; + init(); + } + + private void init() { + setLayoutResource(R.layout.preference_application_light); + mResources = getContext().getResources(); + } + + public void onStart() { + LightSettingsDialog d = (LightSettingsDialog) getDialog(); + if (d != null) { + d.onStart(); + } + } + + public void onStop() { + LightSettingsDialog d = (LightSettingsDialog) getDialog(); + if (d != null) { + d.onStop(); + } + } + + @Override + protected void onBindView(View view) { + super.onBindView(view); + + mLightColorView = (ImageView) view.findViewById(R.id.light_color); + mOnValueView = (TextView) view.findViewById(R.id.textViewTimeOnValue); + mOffValueView = (TextView) view.findViewById(R.id.textViewTimeOffValue); + + // Hide the summary text - it takes up too much space on a low res device + // We use it for storing the package name for the longClickListener + TextView tView = (TextView) view.findViewById(android.R.id.summary); + tView.setVisibility(View.GONE); + + if (!mResources.getBoolean(com.android.internal.R.bool.config_multiColorNotificationLed)) { + mLightColorView.setVisibility(View.GONE); + } + + updatePreferenceViews(); + } + + private void updatePreferenceViews() { + final int size = (int) mResources.getDimension(R.dimen.oval_notification_size); + + if (mLightColorView != null) { + mLightColorView.setEnabled(true); + // adjust if necessary to prevent material whiteout + final int imageColor = ((mColorValue & 0xF0F0F0) == 0xF0F0F0) ? + (mColorValue - 0x101010) : mColorValue; + mLightColorView.setImageDrawable(createOvalShape(size, + 0xFF000000 + imageColor)); + } + if (mOnValueView != null) { + mOnValueView.setText(mapLengthValue(mOnValue)); + } + if (mOffValueView != null) { + if (mOnValue == 1 || !mOnOffChangeable) { + mOffValueView.setVisibility(View.GONE); + } else { + mOffValueView.setVisibility(View.VISIBLE); + } + mOffValueView.setText(mapSpeedValue(mOffValue)); + } + } + + @Override + protected void showDialog(Bundle state) { + super.showDialog(state); + + final LightSettingsDialog d = (LightSettingsDialog) getDialog(); + } + + @Override + protected Dialog createDialog() { + final LightSettingsDialog d = new LightSettingsDialog(getContext(), + 0xFF000000 + mColorValue, mOnValue, mOffValue, mOnOffChangeable); + d.setAlphaSliderVisible(false); + + d.setButton(AlertDialog.BUTTON_POSITIVE, mResources.getString(R.string.dlg_ok), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mColorValue = d.getColor() - 0xFF000000; // strip alpha, led does not support it + mOnValue = d.getPulseSpeedOn(); + mOffValue = d.getPulseSpeedOff(); + updatePreferenceViews(); + callChangeListener(this); + } + }); + d.setButton(AlertDialog.BUTTON_NEGATIVE, mResources.getString(R.string.cancel), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + } + }); + + return d; + } + + /** + * Getters and Setters + */ + + public int getColor() { + return mColorValue; + } + + public void setColor(int color) { + mColorValue = color; + updatePreferenceViews(); + } + + public void setOnValue(int value) { + mOnValue = value; + updatePreferenceViews(); + } + + public int getOnValue() { + return mOnValue; + } + + public void setOffValue(int value) { + mOffValue = value; + updatePreferenceViews(); + } + + public int getOffValue() { + return mOffValue; + } + + public void setAllValues(int color, int onValue, int offValue) { + mColorValue = color; + mOnValue = onValue; + mOffValue = offValue; + updatePreferenceViews(); + } + + public void setAllValues(int color, int onValue, int offValue, boolean onOffChangeable) { + mColorValue = color; + mOnValue = onValue; + mOffValue = offValue; + mOnOffChangeable = onOffChangeable; + updatePreferenceViews(); + } + + public void setOnOffValue(int onValue, int offValue) { + mOnValue = onValue; + mOffValue = offValue; + updatePreferenceViews(); + } + + public void setOnOffChangeable(boolean value) { + mOnOffChangeable = value; + } + + /** + * Utility methods + */ + private static ShapeDrawable createOvalShape(int size, int color) { + ShapeDrawable shape = new ShapeDrawable(new OvalShape()); + shape.setIntrinsicHeight(size); + shape.setIntrinsicWidth(size); + shape.getPaint().setColor(color); + return shape; + } + + private String mapLengthValue(Integer time) { + if (!mOnOffChangeable) { + return getContext().getString(R.string.pulse_length_always_on); + } + if (time == DEFAULT_TIME) { + return getContext().getString(R.string.default_time); + } + + String[] timeNames = mResources.getStringArray(R.array.notification_pulse_length_entries); + String[] timeValues = mResources.getStringArray(R.array.notification_pulse_length_values); + + for (int i = 0; i < timeValues.length; i++) { + if (Integer.decode(timeValues[i]).equals(time)) { + return timeNames[i]; + } + } + + return getContext().getString(R.string.custom_time); + } + + private String mapSpeedValue(Integer time) { + if (time == DEFAULT_TIME) { + return getContext().getString(R.string.default_time); + } + + String[] timeNames = mResources.getStringArray(R.array.notification_pulse_speed_entries); + String[] timeValues = mResources.getStringArray(R.array.notification_pulse_speed_values); + + for (int i = 0; i < timeValues.length; i++) { + if (Integer.decode(timeValues[i]).equals(time)) { + return timeNames[i]; + } + } + + return getContext().getString(R.string.custom_time); + } +} diff --git a/src/com/android/settings/notificationlight/BatteryLightSettings.java b/src/com/android/settings/notificationlight/BatteryLightSettings.java new file mode 100644 index 0000000..2ee884e --- /dev/null +++ b/src/com/android/settings/notificationlight/BatteryLightSettings.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notificationlight; + +import android.content.ContentResolver; +import android.content.res.Resources; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.PreferenceGroup; +import android.preference.PreferenceScreen; +import android.provider.Settings; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.cyanogenmod.CMSystemSettingSwitchPreference; + +import cyanogenmod.providers.CMSettings; + +import org.cyanogenmod.internal.logging.CMMetricsLogger; + +public class BatteryLightSettings extends SettingsPreferenceFragment implements + Preference.OnPreferenceChangeListener { + private static final String TAG = "BatteryLightSettings"; + + private static final String LOW_COLOR_PREF = "low_color"; + private static final String MEDIUM_COLOR_PREF = "medium_color"; + private static final String FULL_COLOR_PREF = "full_color"; + private static final String LIGHT_ENABLED_PREF = "battery_light_enabled"; + private static final String PULSE_ENABLED_PREF = "battery_light_pulse"; + + private PreferenceGroup mColorPrefs; + private ApplicationLightPreference mLowColorPref; + private ApplicationLightPreference mMediumColorPref; + private ApplicationLightPreference mFullColorPref; + private CMSystemSettingSwitchPreference mLightEnabledPref; + private CMSystemSettingSwitchPreference mPulseEnabledPref; + + private static final int MENU_RESET = Menu.FIRST; + + @Override + protected int getMetricsCategory() { + return CMMetricsLogger.BATTERY_LIGHT_SETTINGS; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.battery_light_settings); + + PreferenceScreen prefSet = getPreferenceScreen(); + + PreferenceGroup mGeneralPrefs = (PreferenceGroup) prefSet.findPreference("general_section"); + + mLightEnabledPref = (CMSystemSettingSwitchPreference) prefSet.findPreference(LIGHT_ENABLED_PREF); + mPulseEnabledPref = (CMSystemSettingSwitchPreference) prefSet.findPreference(PULSE_ENABLED_PREF); + + if (!getResources().getBoolean(com.android.internal.R.bool.config_ledCanPulse) || + getResources().getBoolean(org.cyanogenmod.platform.internal.R.bool.config_useSegmentedBatteryLed)) { + mGeneralPrefs.removePreference(mPulseEnabledPref); + } + + // Does the Device support changing battery LED colors? + if (getResources().getBoolean(com.android.internal.R.bool.config_multiColorBatteryLed)) { + setHasOptionsMenu(true); + + // Low, Medium and full color preferences + mLowColorPref = (ApplicationLightPreference) prefSet.findPreference(LOW_COLOR_PREF); + mLowColorPref.setOnPreferenceChangeListener(this); + + mMediumColorPref = (ApplicationLightPreference) prefSet.findPreference(MEDIUM_COLOR_PREF); + mMediumColorPref.setOnPreferenceChangeListener(this); + + mFullColorPref = (ApplicationLightPreference) prefSet.findPreference(FULL_COLOR_PREF); + mFullColorPref.setOnPreferenceChangeListener(this); + } else { + prefSet.removePreference(prefSet.findPreference("colors_list")); + resetColors(); + } + } + + @Override + public void onResume() { + super.onResume(); + refreshDefault(); + } + + private void refreshDefault() { + ContentResolver resolver = getContentResolver(); + Resources res = getResources(); + + if (mLowColorPref != null) { + int lowColor = CMSettings.System.getInt(resolver, CMSettings.System.BATTERY_LIGHT_LOW_COLOR, + res.getInteger(com.android.internal.R.integer.config_notificationsBatteryLowARGB)); + mLowColorPref.setAllValues(lowColor, 0, 0, false); + } + + if (mMediumColorPref != null) { + int mediumColor = CMSettings.System.getInt(resolver, CMSettings.System.BATTERY_LIGHT_MEDIUM_COLOR, + res.getInteger(com.android.internal.R.integer.config_notificationsBatteryMediumARGB)); + mMediumColorPref.setAllValues(mediumColor, 0, 0, false); + } + + if (mFullColorPref != null) { + int fullColor = CMSettings.System.getInt(resolver, CMSettings.System.BATTERY_LIGHT_FULL_COLOR, + res.getInteger(com.android.internal.R.integer.config_notificationsBatteryFullARGB)); + mFullColorPref.setAllValues(fullColor, 0, 0, false); + } + } + + /** + * Updates the default or application specific notification settings. + * + * @param key of the specific setting to update + * @param color + */ + protected void updateValues(String key, Integer color) { + ContentResolver resolver = getContentResolver(); + + if (key.equals(LOW_COLOR_PREF)) { + CMSettings.System.putInt(resolver, CMSettings.System.BATTERY_LIGHT_LOW_COLOR, color); + } else if (key.equals(MEDIUM_COLOR_PREF)) { + CMSettings.System.putInt(resolver, CMSettings.System.BATTERY_LIGHT_MEDIUM_COLOR, color); + } else if (key.equals(FULL_COLOR_PREF)) { + CMSettings.System.putInt(resolver, CMSettings.System.BATTERY_LIGHT_FULL_COLOR, color); + } + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + menu.add(0, MENU_RESET, 0, R.string.profile_reset_title) + .setIcon(R.drawable.ic_settings_backup_restore) + .setAlphabeticShortcut('r') + .setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case MENU_RESET: + resetToDefaults(); + return true; + } + return false; + } + + protected void resetColors() { + ContentResolver resolver = getContentResolver(); + Resources res = getResources(); + + // Reset to the framework default colors + CMSettings.System.putInt(resolver, CMSettings.System.BATTERY_LIGHT_LOW_COLOR, + res.getInteger(com.android.internal.R.integer.config_notificationsBatteryLowARGB)); + CMSettings.System.putInt(resolver, CMSettings.System.BATTERY_LIGHT_MEDIUM_COLOR, + res.getInteger(com.android.internal.R.integer.config_notificationsBatteryMediumARGB)); + CMSettings.System.putInt(resolver, CMSettings.System.BATTERY_LIGHT_FULL_COLOR, + res.getInteger(com.android.internal.R.integer.config_notificationsBatteryFullARGB)); + refreshDefault(); + } + + protected void resetToDefaults() { + final Resources res = getResources(); + final boolean batteryLightEnabled = res.getBoolean(R.bool.def_battery_light_enabled); + final boolean batteryLightPulseEnabled = res.getBoolean(R.bool.def_battery_light_pulse); + + if (mLightEnabledPref != null) mLightEnabledPref.setChecked(batteryLightEnabled); + if (mPulseEnabledPref != null) mPulseEnabledPref.setChecked(batteryLightPulseEnabled); + + resetColors(); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object objValue) { + ApplicationLightPreference lightPref = (ApplicationLightPreference) preference; + updateValues(lightPref.getKey(), lightPref.getColor()); + + return true; + } +} diff --git a/src/com/android/settings/notificationlight/ColorPanelView.java b/src/com/android/settings/notificationlight/ColorPanelView.java new file mode 100644 index 0000000..fcaa1b8 --- /dev/null +++ b/src/com/android/settings/notificationlight/ColorPanelView.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2010 Daniel Nilsson + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notificationlight; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; +import android.util.AttributeSet; +import android.view.View; + +/** + * This class draws a panel which which will be filled with a color which can be + * set. It can be used to show the currently selected color which you will get + * from the {@link ColorPickerView}. + * + * @author Daniel Nilsson + */ +public class ColorPanelView extends View { + + /** + * The width in pixels of the border surrounding the color panel. + */ + private final static float BORDER_WIDTH_PX = 1; + + private static float mDensity = 1f; + + private int mBorderColor = 0xff6E6E6E; + private int mColor = 0xff000000; + + private Paint mBorderPaint; + private Paint mColorPaint; + + private RectF mDrawingRect; + private RectF mColorRect; + + private AlphaPatternDrawable mAlphaPattern; + + public ColorPanelView(Context context) { + this(context, null); + } + + public ColorPanelView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ColorPanelView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + init(); + } + + private void init() { + mBorderPaint = new Paint(); + mColorPaint = new Paint(); + mDensity = getContext().getResources().getDisplayMetrics().density; + } + + @Override + protected void onDraw(Canvas canvas) { + + final RectF rect = mColorRect; + + if (BORDER_WIDTH_PX > 0) { + mBorderPaint.setColor(mBorderColor); + canvas.drawRect(mDrawingRect, mBorderPaint); + } + + if (mAlphaPattern != null) { + mAlphaPattern.draw(canvas); + } + + mColorPaint.setColor(mColor); + + canvas.drawRect(rect, mColorPaint); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + + setMeasuredDimension(width, height); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + + mDrawingRect = new RectF(); + mDrawingRect.left = getPaddingLeft(); + mDrawingRect.right = w - getPaddingRight(); + mDrawingRect.top = getPaddingTop(); + mDrawingRect.bottom = h - getPaddingBottom(); + + setUpColorRect(); + + } + + private void setUpColorRect() { + final RectF dRect = mDrawingRect; + + float left = dRect.left + BORDER_WIDTH_PX; + float top = dRect.top + BORDER_WIDTH_PX; + float bottom = dRect.bottom - BORDER_WIDTH_PX; + float right = dRect.right - BORDER_WIDTH_PX; + + mColorRect = new RectF(left, top, right, bottom); + + mAlphaPattern = new AlphaPatternDrawable((int) (5 * mDensity)); + + mAlphaPattern.setBounds(Math.round(mColorRect.left), + Math.round(mColorRect.top), + Math.round(mColorRect.right), + Math.round(mColorRect.bottom)); + + } + + /** + * Set the color that should be shown by this view. + * + * @param color + */ + public void setColor(int color) { + mColor = color; + invalidate(); + } + + /** + * Get the color currently show by this view. + * + * @return + */ + public int getColor() { + return mColor; + } + + /** + * Set the color of the border surrounding the panel. + * + * @param color + */ + public void setBorderColor(int color) { + mBorderColor = color; + invalidate(); + } + + /** + * Get the color of the border surrounding the panel. + */ + public int getBorderColor() { + return mBorderColor; + } + +} diff --git a/src/com/android/settings/notificationlight/ColorPickerView.java b/src/com/android/settings/notificationlight/ColorPickerView.java new file mode 100644 index 0000000..19becf2 --- /dev/null +++ b/src/com/android/settings/notificationlight/ColorPickerView.java @@ -0,0 +1,841 @@ +/* + * Copyright (C) 2010 Daniel Nilsson + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notificationlight; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ComposeShader; +import android.graphics.LinearGradient; +import android.graphics.Paint; +import android.graphics.Point; +import android.graphics.PorterDuff.Mode; +import android.graphics.RectF; +import android.graphics.Shader; +import android.graphics.Paint.Align; +import android.graphics.Paint.Style; +import android.graphics.Shader.TileMode; +import android.os.Build; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; + +/** + * Displays a color picker to the user and allow them to select a color. A + * slider for the alpha channel is also available. Enable it by setting + * setAlphaSliderVisible(boolean) to true. + * + * @author Daniel Nilsson + */ +public class ColorPickerView extends View { + + public interface OnColorChangedListener { + public void onColorChanged(int color); + } + + private final static int PANEL_SAT_VAL = 0; + private final static int PANEL_HUE = 1; + private final static int PANEL_ALPHA = 2; + + /** + * The width in pixels of the border surrounding all color panels. + */ + private final static float BORDER_WIDTH_PX = 1; + + /** + * The width in dp of the hue panel. + */ + private float HUE_PANEL_WIDTH = 30f; + /** + * The height in dp of the alpha panel + */ + private float ALPHA_PANEL_HEIGHT = 20f; + /** + * The distance in dp between the different color panels. + */ + private float PANEL_SPACING = 10f; + /** + * The radius in dp of the color palette tracker circle. + */ + private float PALETTE_CIRCLE_TRACKER_RADIUS = 5f; + /** + * The dp which the tracker of the hue or alpha panel will extend outside of + * its bounds. + */ + private float RECTANGLE_TRACKER_OFFSET = 2f; + + private static float mDensity = 1f; + + private OnColorChangedListener mListener; + + private Paint mSatValPaint; + private Paint mSatValTrackerPaint; + + private Paint mHuePaint; + private Paint mHueTrackerPaint; + + private Paint mAlphaPaint; + private Paint mAlphaTextPaint; + + private Paint mBorderPaint; + + private Shader mValShader; + private Shader mSatShader; + private Shader mHueShader; + private Shader mAlphaShader; + + private int mAlpha = 0xff; + private float mHue = 360f; + private float mSat = 0f; + private float mVal = 0f; + + private String mAlphaSliderText = "Alpha"; + private int mSliderTrackerColor = 0xff1c1c1c; + private int mBorderColor = 0xff6E6E6E; + private boolean mShowAlphaPanel = false; + + /* + * To remember which panel that has the "focus" when processing hardware + * button data. + */ + private int mLastTouchedPanel = PANEL_SAT_VAL; + + /** + * Offset from the edge we must have or else the finger tracker will get + * clipped when it is drawn outside of the view. + */ + private float mDrawingOffset; + + /* + * Distance form the edges of the view of where we are allowed to draw. + */ + private RectF mDrawingRect; + + private RectF mSatValRect; + private RectF mHueRect; + private RectF mAlphaRect; + + private AlphaPatternDrawable mAlphaPattern; + + private Point mStartTouchPoint = null; + + public ColorPickerView(Context context) { + this(context, null); + } + + public ColorPickerView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ColorPickerView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + private void init() { + mDensity = getContext().getResources().getDisplayMetrics().density; + PALETTE_CIRCLE_TRACKER_RADIUS *= mDensity; + RECTANGLE_TRACKER_OFFSET *= mDensity; + HUE_PANEL_WIDTH *= mDensity; + ALPHA_PANEL_HEIGHT *= mDensity; + PANEL_SPACING = PANEL_SPACING * mDensity; + + mDrawingOffset = calculateRequiredOffset(); + initPaintTools(); + + // Needed for receiving track ball motion events. + setFocusableInTouchMode(true); + setFocusable(true); + setClickable(true); + } + + private void initPaintTools() { + mSatValPaint = new Paint(); + mSatValTrackerPaint = new Paint(); + mHuePaint = new Paint(); + mHueTrackerPaint = new Paint(); + mAlphaPaint = new Paint(); + mAlphaTextPaint = new Paint(); + mBorderPaint = new Paint(); + + mSatValTrackerPaint.setStyle(Style.STROKE); + mSatValTrackerPaint.setStrokeWidth(2f * mDensity); + mSatValTrackerPaint.setAntiAlias(true); + + mHueTrackerPaint.setColor(mSliderTrackerColor); + mHueTrackerPaint.setStyle(Style.STROKE); + mHueTrackerPaint.setStrokeWidth(2f * mDensity); + mHueTrackerPaint.setAntiAlias(true); + + mAlphaTextPaint.setColor(0xff1c1c1c); + mAlphaTextPaint.setTextSize(14f * mDensity); + mAlphaTextPaint.setAntiAlias(true); + mAlphaTextPaint.setTextAlign(Align.CENTER); + mAlphaTextPaint.setFakeBoldText(true); + } + + private float calculateRequiredOffset() { + float offset = Math.max(PALETTE_CIRCLE_TRACKER_RADIUS, RECTANGLE_TRACKER_OFFSET); + offset = Math.max(offset, BORDER_WIDTH_PX * mDensity); + + return offset * 1.5f; + } + + private int[] buildHueColorArray() { + int[] hue = new int[361]; + + int count = 0; + for (int i = hue.length - 1; i >= 0; i--, count++) { + hue[count] = Color.HSVToColor(new float[] { + i, 1f, 1f + }); + } + return hue; + } + + @Override + protected void onDraw(Canvas canvas) { + if (mDrawingRect.width() <= 0 || mDrawingRect.height() <= 0) { + return; + } + drawSatValPanel(canvas); + drawHuePanel(canvas); + drawAlphaPanel(canvas); + } + + private void drawSatValPanel(Canvas canvas) { + final RectF rect = mSatValRect; + int rgb = Color.HSVToColor(new float[] { + mHue, 1f, 1f + }); + + if (BORDER_WIDTH_PX > 0) { + mBorderPaint.setColor(mBorderColor); + canvas.drawRect(mDrawingRect.left, mDrawingRect.top, rect.right + BORDER_WIDTH_PX, + rect.bottom + BORDER_WIDTH_PX, mBorderPaint); + } + + // On Honeycomb+ we need to use software rendering to create the shader properly + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + setLayerType(View.LAYER_TYPE_SOFTWARE, null); + } + + // Get the overlaying gradients ready and create the ComposeShader + if (mValShader == null) { + mValShader = new LinearGradient(rect.left, rect.top, rect.left, rect.bottom, + 0xffffffff, 0xff000000, TileMode.CLAMP); + } + mSatShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top, + 0xffffffff, rgb, TileMode.CLAMP); + ComposeShader mShader = new ComposeShader(mValShader, mSatShader, Mode.MULTIPLY); + mSatValPaint.setShader(mShader); + canvas.drawRect(rect, mSatValPaint); + + Point p = satValToPoint(mSat, mVal); + mSatValTrackerPaint.setColor(0xff000000); + canvas.drawCircle(p.x, p.y, PALETTE_CIRCLE_TRACKER_RADIUS - 1f * mDensity, + mSatValTrackerPaint); + + mSatValTrackerPaint.setColor(0xffdddddd); + canvas.drawCircle(p.x, p.y, PALETTE_CIRCLE_TRACKER_RADIUS, mSatValTrackerPaint); + } + + private void drawHuePanel(Canvas canvas) { + final RectF rect = mHueRect; + + if (BORDER_WIDTH_PX > 0) { + mBorderPaint.setColor(mBorderColor); + canvas.drawRect(rect.left - BORDER_WIDTH_PX, + rect.top - BORDER_WIDTH_PX, + rect.right + BORDER_WIDTH_PX, + rect.bottom + BORDER_WIDTH_PX, + mBorderPaint); + } + + if (mHueShader == null) { + mHueShader = new LinearGradient(rect.left, rect.top, rect.left, rect.bottom, + buildHueColorArray(), null, TileMode.CLAMP); + mHuePaint.setShader(mHueShader); + } + + canvas.drawRect(rect, mHuePaint); + + float rectHeight = 4 * mDensity / 2; + + Point p = hueToPoint(mHue); + + RectF r = new RectF(); + r.left = rect.left - RECTANGLE_TRACKER_OFFSET; + r.right = rect.right + RECTANGLE_TRACKER_OFFSET; + r.top = p.y - rectHeight; + r.bottom = p.y + rectHeight; + + canvas.drawRoundRect(r, 2, 2, mHueTrackerPaint); + + } + + private void drawAlphaPanel(Canvas canvas) { + if (!mShowAlphaPanel || mAlphaRect == null || mAlphaPattern == null) { + return; + } + + final RectF rect = mAlphaRect; + + if (BORDER_WIDTH_PX > 0) { + mBorderPaint.setColor(mBorderColor); + canvas.drawRect(rect.left - BORDER_WIDTH_PX, + rect.top - BORDER_WIDTH_PX, + rect.right + BORDER_WIDTH_PX, + rect.bottom + BORDER_WIDTH_PX, + mBorderPaint); + } + + mAlphaPattern.draw(canvas); + + float[] hsv = new float[] { + mHue, mSat, mVal + }; + int color = Color.HSVToColor(hsv); + int acolor = Color.HSVToColor(0, hsv); + + mAlphaShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top, + color, acolor, TileMode.CLAMP); + + mAlphaPaint.setShader(mAlphaShader); + + canvas.drawRect(rect, mAlphaPaint); + + if (mAlphaSliderText != null && mAlphaSliderText != "") { + canvas.drawText(mAlphaSliderText, rect.centerX(), rect.centerY() + 4 * mDensity, + mAlphaTextPaint); + } + + float rectWidth = 4 * mDensity / 2; + Point p = alphaToPoint(mAlpha); + + RectF r = new RectF(); + r.left = p.x - rectWidth; + r.right = p.x + rectWidth; + r.top = rect.top - RECTANGLE_TRACKER_OFFSET; + r.bottom = rect.bottom + RECTANGLE_TRACKER_OFFSET; + + canvas.drawRoundRect(r, 2, 2, mHueTrackerPaint); + } + + private Point hueToPoint(float hue) { + final RectF rect = mHueRect; + final float height = rect.height(); + + Point p = new Point(); + p.y = (int) (height - (hue * height / 360f) + rect.top); + p.x = (int) rect.left; + return p; + } + + private Point satValToPoint(float sat, float val) { + + final RectF rect = mSatValRect; + final float height = rect.height(); + final float width = rect.width(); + + Point p = new Point(); + + p.x = (int) (sat * width + rect.left); + p.y = (int) ((1f - val) * height + rect.top); + + return p; + } + + private Point alphaToPoint(int alpha) { + final RectF rect = mAlphaRect; + final float width = rect.width(); + + Point p = new Point(); + p.x = (int) (width - (alpha * width / 0xff) + rect.left); + p.y = (int) rect.top; + return p; + } + + private float[] pointToSatVal(float x, float y) { + final RectF rect = mSatValRect; + float[] result = new float[2]; + float width = rect.width(); + float height = rect.height(); + + if (x < rect.left) { + x = 0f; + } else if (x > rect.right) { + x = width; + } else { + x = x - rect.left; + } + + if (y < rect.top) { + y = 0f; + } else if (y > rect.bottom) { + y = height; + } else { + y = y - rect.top; + } + + result[0] = 1.f / width * x; + result[1] = 1.f - (1.f / height * y); + return result; + } + + private float pointToHue(float y) { + final RectF rect = mHueRect; + float height = rect.height(); + + if (y < rect.top) { + y = 0f; + } else if (y > rect.bottom) { + y = height; + } else { + y = y - rect.top; + } + return 360f - (y * 360f / height); + } + + private int pointToAlpha(int x) { + final RectF rect = mAlphaRect; + final int width = (int) rect.width(); + + if (x < rect.left) { + x = 0; + } else if (x > rect.right) { + x = width; + } else { + x = x - (int) rect.left; + } + return 0xff - (x * 0xff / width); + } + + @Override + public boolean onTrackballEvent(MotionEvent event) { + float x = event.getX(); + float y = event.getY(); + boolean update = false; + + if (event.getAction() == MotionEvent.ACTION_MOVE) { + switch (mLastTouchedPanel) { + case PANEL_SAT_VAL: + float sat, + val; + sat = mSat + x / 50f; + val = mVal - y / 50f; + if (sat < 0f) { + sat = 0f; + } else if (sat > 1f) { + sat = 1f; + } + + if (val < 0f) { + val = 0f; + } else if (val > 1f) { + val = 1f; + } + mSat = sat; + mVal = val; + update = true; + break; + case PANEL_HUE: + float hue = mHue - y * 10f; + if (hue < 0f) { + hue = 0f; + } else if (hue > 360f) { + hue = 360f; + } + mHue = hue; + update = true; + break; + case PANEL_ALPHA: + if (!mShowAlphaPanel || mAlphaRect == null) { + update = false; + } else { + int alpha = (int) (mAlpha - x * 10); + if (alpha < 0) { + alpha = 0; + } else if (alpha > 0xff) { + alpha = 0xff; + } + mAlpha = alpha; + update = true; + } + break; + } + } + + if (update) { + if (mListener != null) { + mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[] { + mHue, mSat, mVal + })); + } + invalidate(); + return true; + } + return super.onTrackballEvent(event); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + boolean update = false; + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + mStartTouchPoint = new Point((int) event.getX(), (int) event.getY()); + update = moveTrackersIfNeeded(event); + break; + case MotionEvent.ACTION_MOVE: + update = moveTrackersIfNeeded(event); + break; + case MotionEvent.ACTION_UP: + mStartTouchPoint = null; + update = moveTrackersIfNeeded(event); + break; + } + + if (update) { + requestFocus(); + if (mListener != null) { + mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[] { + mHue, mSat, mVal + })); + } + invalidate(); + return true; + } + + return super.onTouchEvent(event); + } + + private boolean moveTrackersIfNeeded(MotionEvent event) { + + if (mStartTouchPoint == null) + return false; + + boolean update = false; + int startX = mStartTouchPoint.x; + int startY = mStartTouchPoint.y; + + if (mHueRect.contains(startX, startY)) { + mLastTouchedPanel = PANEL_HUE; + mHue = pointToHue(event.getY()); + update = true; + } else if (mSatValRect.contains(startX, startY)) { + mLastTouchedPanel = PANEL_SAT_VAL; + float[] result = pointToSatVal(event.getX(), event.getY()); + mSat = result[0]; + mVal = result[1]; + update = true; + } else if (mAlphaRect != null && mAlphaRect.contains(startX, startY)) { + mLastTouchedPanel = PANEL_ALPHA; + mAlpha = pointToAlpha((int) event.getX()); + update = true; + } + + return update; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = 0; + int height = 0; + + int widthMode = MeasureSpec.getMode(widthMeasureSpec); + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + + int widthAllowed = MeasureSpec.getSize(widthMeasureSpec); + int heightAllowed = MeasureSpec.getSize(heightMeasureSpec); + + widthAllowed = chooseWidth(widthMode, widthAllowed); + heightAllowed = chooseHeight(heightMode, heightAllowed); + + if (!mShowAlphaPanel) { + height = (int) (widthAllowed - PANEL_SPACING - HUE_PANEL_WIDTH); + + // If calculated height (based on the width) is more than the + // allowed height. + if (height > heightAllowed && heightMode != MeasureSpec.UNSPECIFIED) { + height = heightAllowed; + width = (int) (height + PANEL_SPACING + HUE_PANEL_WIDTH); + } else { + width = widthAllowed; + } + } else { + + width = (int) (heightAllowed - ALPHA_PANEL_HEIGHT + HUE_PANEL_WIDTH); + + if (width > widthAllowed && widthMode != MeasureSpec.UNSPECIFIED) { + width = widthAllowed; + height = (int) (widthAllowed - HUE_PANEL_WIDTH + ALPHA_PANEL_HEIGHT); + } else { + height = heightAllowed; + } + } + setMeasuredDimension(width, height); + } + + private int chooseWidth(int mode, int size) { + if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) { + return size; + } else { // (mode == MeasureSpec.UNSPECIFIED) + return getPrefferedWidth(); + } + } + + private int chooseHeight(int mode, int size) { + if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) { + return size; + } else { // (mode == MeasureSpec.UNSPECIFIED) + return getPrefferedHeight(); + } + } + + private int getPrefferedWidth() { + int width = getPrefferedHeight(); + if (mShowAlphaPanel) { + width -= (PANEL_SPACING + ALPHA_PANEL_HEIGHT); + } + return (int) (width + HUE_PANEL_WIDTH + PANEL_SPACING); + } + + private int getPrefferedHeight() { + int height = (int) (200 * mDensity); + if (mShowAlphaPanel) { + height += PANEL_SPACING + ALPHA_PANEL_HEIGHT; + } + return height; + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + + mDrawingRect = new RectF(); + mDrawingRect.left = mDrawingOffset + getPaddingLeft(); + mDrawingRect.right = w - mDrawingOffset - getPaddingRight(); + mDrawingRect.top = mDrawingOffset + getPaddingTop(); + mDrawingRect.bottom = h - mDrawingOffset - getPaddingBottom(); + + setUpSatValRect(); + setUpHueRect(); + setUpAlphaRect(); + } + + private void setUpSatValRect() { + final RectF dRect = mDrawingRect; + float panelSide = dRect.height() - BORDER_WIDTH_PX * 2; + + if (mShowAlphaPanel) { + panelSide -= PANEL_SPACING + ALPHA_PANEL_HEIGHT; + } + + float left = dRect.left + BORDER_WIDTH_PX; + float top = dRect.top + BORDER_WIDTH_PX; + float bottom = top + panelSide; + float right = left + panelSide; + mSatValRect = new RectF(left, top, right, bottom); + } + + private void setUpHueRect() { + final RectF dRect = mDrawingRect; + + float left = dRect.right - HUE_PANEL_WIDTH + BORDER_WIDTH_PX; + float top = dRect.top + BORDER_WIDTH_PX; + float bottom = dRect.bottom - BORDER_WIDTH_PX + - (mShowAlphaPanel ? (PANEL_SPACING + ALPHA_PANEL_HEIGHT) : 0); + float right = dRect.right - BORDER_WIDTH_PX; + + mHueRect = new RectF(left, top, right, bottom); + } + + private void setUpAlphaRect() { + if (!mShowAlphaPanel) { + return; + } + + final RectF dRect = mDrawingRect; + float left = dRect.left + BORDER_WIDTH_PX; + float top = dRect.bottom - ALPHA_PANEL_HEIGHT + BORDER_WIDTH_PX; + float bottom = dRect.bottom - BORDER_WIDTH_PX; + float right = dRect.right - BORDER_WIDTH_PX; + + mAlphaRect = new RectF(left, top, right, bottom); + mAlphaPattern = new AlphaPatternDrawable((int) (5 * mDensity)); + mAlphaPattern.setBounds(Math.round(mAlphaRect.left), Math + .round(mAlphaRect.top), Math.round(mAlphaRect.right), Math + .round(mAlphaRect.bottom)); + } + + /** + * Set a OnColorChangedListener to get notified when the color selected by + * the user has changed. + * + * @param listener + */ + public void setOnColorChangedListener(OnColorChangedListener listener) { + mListener = listener; + } + + /** + * Set the color of the border surrounding all panels. + * + * @param color + */ + public void setBorderColor(int color) { + mBorderColor = color; + invalidate(); + } + + /** + * Get the color of the border surrounding all panels. + */ + public int getBorderColor() { + return mBorderColor; + } + + /** + * Get the current color this view is showing. + * + * @return the current color. + */ + public int getColor() { + return Color.HSVToColor(mAlpha, new float[] { + mHue, mSat, mVal + }); + } + + /** + * Set the color the view should show. + * + * @param color The color that should be selected. + */ + public void setColor(int color) { + setColor(color, false); + } + + /** + * Set the color this view should show. + * + * @param color The color that should be selected. + * @param callback If you want to get a callback to your + * OnColorChangedListener. + */ + public void setColor(int color, boolean callback) { + int alpha = Color.alpha(color); + int red = Color.red(color); + int blue = Color.blue(color); + int green = Color.green(color); + float[] hsv = new float[3]; + + Color.RGBToHSV(red, green, blue, hsv); + mAlpha = alpha; + mHue = hsv[0]; + mSat = hsv[1]; + mVal = hsv[2]; + + if (callback && mListener != null) { + mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[] { + mHue, mSat, mVal + })); + } + invalidate(); + } + + /** + * Get the drawing offset of the color picker view. The drawing offset is + * the distance from the side of a panel to the side of the view minus the + * padding. Useful if you want to have your own panel below showing the + * currently selected color and want to align it perfectly. + * + * @return The offset in pixels. + */ + public float getDrawingOffset() { + return mDrawingOffset; + } + + /** + * Set if the user is allowed to adjust the alpha panel. Default is false. + * If it is set to false no alpha will be set. + * + * @param visible + */ + public void setAlphaSliderVisible(boolean visible) { + if (mShowAlphaPanel != visible) { + mShowAlphaPanel = visible; + + /* + * Reset all shader to force a recreation. Otherwise they will not + * look right after the size of the view has changed. + */ + mValShader = null; + mSatShader = null; + mHueShader = null; + mAlphaShader = null; + requestLayout(); + } + + } + + public boolean isAlphaSliderVisible() { + return mShowAlphaPanel; + } + + public void setSliderTrackerColor(int color) { + mSliderTrackerColor = color; + mHueTrackerPaint.setColor(mSliderTrackerColor); + invalidate(); + } + + public int getSliderTrackerColor() { + return mSliderTrackerColor; + } + + /** + * Set the text that should be shown in the alpha slider. Set to null to + * disable text. + * + * @param res string resource id. + */ + public void setAlphaSliderText(int res) { + String text = getContext().getString(res); + setAlphaSliderText(text); + } + + /** + * Set the text that should be shown in the alpha slider. Set to null to + * disable text. + * + * @param text Text that should be shown. + */ + public void setAlphaSliderText(String text) { + mAlphaSliderText = text; + invalidate(); + } + + /** + * Get the current value of the text that will be shown in the alpha slider. + * + * @return + */ + public String getAlphaSliderText() { + return mAlphaSliderText; + } +} diff --git a/src/com/android/settings/notificationlight/LightSettingsDialog.java b/src/com/android/settings/notificationlight/LightSettingsDialog.java new file mode 100644 index 0000000..d2d4e84 --- /dev/null +++ b/src/com/android/settings/notificationlight/LightSettingsDialog.java @@ -0,0 +1,444 @@ +/* + * Copyright (C) 2010 Daniel Nilsson + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notificationlight; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Notification; +import android.app.NotificationManager; +import android.content.Context; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.text.Editable; +import android.text.TextWatcher; +import android.text.InputFilter; +import android.text.InputFilter.LengthFilter; +import android.util.Pair; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnFocusChangeListener; +import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.Spinner; +import android.widget.SpinnerAdapter; +import android.widget.TextView; + +import com.android.settings.R; +import com.android.settings.notificationlight.ColorPickerView.OnColorChangedListener; + +import java.util.ArrayList; +import java.util.IllegalFormatException; +import java.util.Locale; + +public class LightSettingsDialog extends AlertDialog implements + ColorPickerView.OnColorChangedListener, TextWatcher, OnFocusChangeListener { + + private final static String STATE_KEY_COLOR = "LightSettingsDialog:color"; + // Minimum delay between LED notification updates + private final static long LED_UPDATE_DELAY_MS = 250; + + private ColorPickerView mColorPicker; + private LinearLayout mColorPanel; + private View mLightsDialogDivider; + + private EditText mHexColorInput; + private ColorPanelView mNewColor; + private Spinner mPulseSpeedOn; + private Spinner mPulseSpeedOff; + private LayoutInflater mInflater; + + private OnColorChangedListener mListener; + + private NotificationManager mNotificationManager; + + private boolean mReadyForLed; + private int mLedLastColor; + private int mLedLastSpeedOn; + private int mLedLastSpeedOff; + + /** + * @param context + * @param initialColor + * @param initialSpeedOn + * @param initialSpeedOff + */ + protected LightSettingsDialog(Context context, int initialColor, int initialSpeedOn, + int initialSpeedOff) { + super(context); + + init(context, initialColor, initialSpeedOn, initialSpeedOff, true); + } + + /** + * @param context + * @param initialColor + * @param initialSpeedOn + * @param initialSpeedOff + * @param onOffChangeable + */ + protected LightSettingsDialog(Context context, int initialColor, int initialSpeedOn, + int initialSpeedOff, boolean onOffChangeable) { + super(context); + + init(context, initialColor, initialSpeedOn, initialSpeedOff, onOffChangeable); + } + + private void init(Context context, int color, int speedOn, int speedOff, + boolean onOffChangeable) { + mNotificationManager = + (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + + mReadyForLed = false; + mLedLastColor = 0; + + // To fight color banding. + getWindow().setFormat(PixelFormat.RGBA_8888); + setUp(color, speedOn, speedOff, onOffChangeable); + } + + /** + * This function sets up the dialog with the proper values. If the speedOff parameters + * has a -1 value disable both spinners + * + * @param color - the color to set + * @param speedOn - the flash time in ms + * @param speedOff - the flash length in ms + */ + private void setUp(int color, int speedOn, int speedOff, boolean onOffChangeable) { + mInflater = (LayoutInflater) getContext() + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View layout = mInflater.inflate(R.layout.dialog_light_settings, null); + + mColorPicker = (ColorPickerView) layout.findViewById(R.id.color_picker_view); + mColorPanel = (LinearLayout) layout.findViewById(R.id.color_panel_view); + mHexColorInput = (EditText) layout.findViewById(R.id.hex_color_input); + mNewColor = (ColorPanelView) layout.findViewById(R.id.color_panel); + mLightsDialogDivider = (View) layout.findViewById(R.id.lights_dialog_divider); + mPulseSpeedOn = (Spinner) layout.findViewById(R.id.on_spinner); + mPulseSpeedOff = (Spinner) layout.findViewById(R.id.off_spinner); + + mColorPicker.setOnColorChangedListener(this); + mColorPicker.setColor(color, true); + + mHexColorInput.setOnFocusChangeListener(this); + + if (onOffChangeable) { + PulseSpeedAdapter pulseSpeedAdapter = new PulseSpeedAdapter( + R.array.notification_pulse_length_entries, + R.array.notification_pulse_length_values, + speedOn); + mPulseSpeedOn.setAdapter(pulseSpeedAdapter); + mPulseSpeedOn.setSelection(pulseSpeedAdapter.getTimePosition(speedOn)); + mPulseSpeedOn.setOnItemSelectedListener(mPulseSelectionListener); + + pulseSpeedAdapter = new PulseSpeedAdapter(R.array.notification_pulse_speed_entries, + R.array.notification_pulse_speed_values, + speedOff); + mPulseSpeedOff.setAdapter(pulseSpeedAdapter); + mPulseSpeedOff.setSelection(pulseSpeedAdapter.getTimePosition(speedOff)); + mPulseSpeedOff.setOnItemSelectedListener(mPulseSelectionListener); + } else { + View speedSettingsGroup = layout.findViewById(R.id.speed_title_view); + speedSettingsGroup.setVisibility(View.GONE); + } + + mPulseSpeedOn.setEnabled(onOffChangeable); + mPulseSpeedOff.setEnabled((speedOn != 1) && onOffChangeable); + + setView(layout); + setTitle(R.string.edit_light_settings); + + if (!getContext().getResources().getBoolean( + com.android.internal.R.bool.config_multiColorNotificationLed)) { + mColorPicker.setVisibility(View.GONE); + mColorPanel.setVisibility(View.GONE); + mLightsDialogDivider.setVisibility(View.GONE); + } + + mReadyForLed = true; + updateLed(); + } + + private AdapterView.OnItemSelectedListener mPulseSelectionListener = + new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { + if (parent == mPulseSpeedOn) { + mPulseSpeedOff.setEnabled(mPulseSpeedOn.isEnabled() && getPulseSpeedOn() != 1); + } + updateLed(); + } + + @Override + public void onNothingSelected(AdapterView<?> parent) { + } + }; + + @Override + public Bundle onSaveInstanceState() { + Bundle state = super.onSaveInstanceState(); + state.putInt(STATE_KEY_COLOR, getColor()); + return state; + } + + @Override + public void onRestoreInstanceState(Bundle state) { + super.onRestoreInstanceState(state); + mColorPicker.setColor(state.getInt(STATE_KEY_COLOR), true); + } + + @Override + public void onStop() { + super.onStop(); + dismissLed(); + } + + @Override + public void onStart() { + super.onStart(); + updateLed(); + } + + @Override + public void onColorChanged(int color) { + final boolean hasAlpha = mColorPicker.isAlphaSliderVisible(); + final String format = hasAlpha ? "%08x" : "%06x"; + final int mask = hasAlpha ? 0xFFFFFFFF : 0x00FFFFFF; + + mNewColor.setColor(color); + mHexColorInput.setText(String.format(Locale.US, format, color & mask)); + + if (mListener != null) { + mListener.onColorChanged(color); + } + + updateLed(); + } + + public void setAlphaSliderVisible(boolean visible) { + mHexColorInput.setFilters(new InputFilter[] { new InputFilter.LengthFilter(visible ? 8 : 6) } ); + mColorPicker.setAlphaSliderVisible(visible); + } + + public int getColor() { + return mColorPicker.getColor(); + } + + @SuppressWarnings("unchecked") + public int getPulseSpeedOn() { + if (mPulseSpeedOn.isEnabled()) { + return ((Pair<String, Integer>) mPulseSpeedOn.getSelectedItem()).second; + } else { + return 1; + } + } + + @SuppressWarnings("unchecked") + public int getPulseSpeedOff() { + // return 0 if 'Always on' is selected + return getPulseSpeedOn() == 1 ? 0 : ((Pair<String, Integer>) mPulseSpeedOff.getSelectedItem()).second; + } + + private Handler mLedHandler = new Handler() { + public void handleMessage(Message msg) { + updateLed(); + } + }; + + private void updateLed() { + if (!mReadyForLed) { + return; + } + + final int color = getColor() & 0xFFFFFF; + final int speedOn, speedOff; + if (mPulseSpeedOn.isEnabled()) { + speedOn = getPulseSpeedOn(); + speedOff = getPulseSpeedOff(); + } else { + speedOn = 1; + speedOff = 0; + } + + if (mLedLastColor == color && mLedLastSpeedOn == speedOn + && mLedLastSpeedOff == speedOff) { + return; + } + + // Dampen rate of consecutive LED changes + if (mLedHandler.hasMessages(0)) { + return; + } + mLedHandler.sendEmptyMessageDelayed(0, LED_UPDATE_DELAY_MS); + + mLedLastColor = color; + mLedLastSpeedOn = speedOn; + mLedLastSpeedOff = speedOff; + + final Bundle b = new Bundle(); + b.putBoolean(Notification.EXTRA_FORCE_SHOW_LIGHTS, true); + + final Notification.Builder builder = new Notification.Builder(getContext()); + builder.setLights(color, speedOn, speedOff); + builder.setExtras(b); + + // Set a notification + builder.setSmallIcon(R.drawable.ic_settings_24dp); + builder.setContentTitle(getContext().getString(R.string.led_notification_title)); + builder.setContentText(getContext().getString(R.string.led_notification_text)); + builder.setOngoing(true); + + mNotificationManager.notify(1, builder.build()); + } + + public void dismissLed() { + mNotificationManager.cancel(1); + // ensure we later reset LED if dialog is + // hidden and then made visible + mLedLastColor = 0; + } + + class PulseSpeedAdapter extends BaseAdapter implements SpinnerAdapter { + private ArrayList<Pair<String, Integer>> times; + + public PulseSpeedAdapter(int timeNamesResource, int timeValuesResource) { + times = new ArrayList<Pair<String, Integer>>(); + + String[] time_names = getContext().getResources().getStringArray(timeNamesResource); + String[] time_values = getContext().getResources().getStringArray(timeValuesResource); + + for(int i = 0; i < time_values.length; ++i) { + times.add(new Pair<String, Integer>(time_names[i], Integer.decode(time_values[i]))); + } + + } + + /** + * This constructor apart from taking a usual time entry array takes the + * currently configured time value which might cause the addition of a + * "Custom" time entry in the spinner in case this time value does not + * match any of the predefined ones in the array. + * + * @param timeNamesResource The time entry names array + * @param timeValuesResource The time entry values array + * @param customTime Current time value that might be one of the + * predefined values or a totally custom value + */ + public PulseSpeedAdapter(int timeNamesResource, int timeValuesResource, Integer customTime) { + this(timeNamesResource, timeValuesResource); + + // Check if we also need to add the custom value entry + if (getTimePosition(customTime) == -1) { + times.add(new Pair<String, Integer>(getContext().getResources() + .getString(R.string.custom_time), customTime)); + } + } + + /** + * Will return the position of the spinner entry with the specified + * time. Returns -1 if there is no such entry. + * + * @param time Time in ms + * @return Position of entry with given time or -1 if not found. + */ + public int getTimePosition(Integer time) { + for (int position = 0; position < getCount(); ++position) { + if (getItem(position).second.equals(time)) { + return position; + } + } + + return -1; + } + + @Override + public int getCount() { + return times.size(); + } + + @Override + public Pair<String, Integer> getItem(int position) { + return times.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View view, ViewGroup parent) { + if (view == null) { + view = mInflater.inflate(R.layout.pulse_time_item, parent, false); + } + + Pair<String, Integer> entry = getItem(position); + ((TextView) view.findViewById(R.id.textViewName)).setText(entry.first); + + return view; + } + } + + @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 afterTextChanged(Editable s) { + String hexColor = mHexColorInput.getText().toString(); + if (!hexColor.isEmpty()) { + try { + int color = Color.parseColor('#' + hexColor); + if (!mColorPicker.isAlphaSliderVisible()) { + color |= 0xFF000000; // set opaque + } + mColorPicker.setColor(color); + mNewColor.setColor(color); + updateLed(); + if (mListener != null) { + mListener.onColorChanged(color); + } + } catch (IllegalArgumentException ex) { + // Number format is incorrect, ignore + } + } + } + + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (!hasFocus) { + mHexColorInput.removeTextChangedListener(this); + InputMethodManager inputMethodManager = (InputMethodManager) getContext() + .getSystemService(Activity.INPUT_METHOD_SERVICE); + inputMethodManager.hideSoftInputFromWindow(v.getWindowToken(), 0); + } else { + mHexColorInput.addTextChangedListener(this); + } + } +} diff --git a/src/com/android/settings/notificationlight/NotificationLightSettings.java b/src/com/android/settings/notificationlight/NotificationLightSettings.java new file mode 100644 index 0000000..b17918a --- /dev/null +++ b/src/com/android/settings/notificationlight/NotificationLightSettings.java @@ -0,0 +1,579 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notificationlight; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.ContentResolver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.pm.PackageInfo; +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.preference.Preference; +import android.preference.PreferenceGroup; +import android.preference.PreferenceScreen; +import android.provider.Settings; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ListView; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.cyanogenmod.CMSystemSettingSwitchPreference; +import com.android.settings.cyanogenmod.PackageListAdapter; +import com.android.settings.cyanogenmod.PackageListAdapter.PackageItem; +import com.android.settings.cyanogenmod.SystemSettingSwitchPreference; + +import org.cyanogenmod.internal.logging.CMMetricsLogger; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import cyanogenmod.providers.CMSettings; +import cyanogenmod.util.ColorUtils; + +public class NotificationLightSettings extends SettingsPreferenceFragment implements + Preference.OnPreferenceChangeListener, AdapterView.OnItemLongClickListener { + private static final String TAG = "NotificationLightSettings"; + private static final String DEFAULT_PREF = "default"; + private static final String MISSED_CALL_PREF = "missed_call"; + private static final String VOICEMAIL_PREF = "voicemail"; + public static final int ACTION_TEST = 0; + public static final int ACTION_DELETE = 1; + private static final int MENU_ADD = 0; + private static final int DIALOG_APPS = 0; + + private int mDefaultColor; + private int mDefaultLedOn; + private int mDefaultLedOff; + private PackageManager mPackageManager; + private PreferenceGroup mApplicationPrefList; + private PreferenceScreen mNotificationLedBrightnessPref; + private SystemSettingSwitchPreference mEnabledPref; + private CMSystemSettingSwitchPreference mCustomEnabledPref; + private CMSystemSettingSwitchPreference mMultipleLedsEnabledPref; + private CMSystemSettingSwitchPreference mScreenOnLightsPref; + private CMSystemSettingSwitchPreference mAutoGenerateColors; + private ApplicationLightPreference mDefaultPref; + private ApplicationLightPreference mCallPref; + private ApplicationLightPreference mVoicemailPref; + private Menu mMenu; + private PackageListAdapter mPackageAdapter; + private String mPackageList; + private Map<String, Package> mPackages; + private boolean mMultiColorLed; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.notification_light_settings); + + PreferenceScreen prefSet = getPreferenceScreen(); + Resources resources = getResources(); + + PreferenceGroup mAdvancedPrefs = (PreferenceGroup) prefSet.findPreference("advanced_section"); + + // Get the system defined default notification color + mDefaultColor = + resources.getColor(com.android.internal.R.color.config_defaultNotificationColor, null); + + mDefaultLedOn = resources.getInteger( + com.android.internal.R.integer.config_defaultNotificationLedOn); + mDefaultLedOff = resources.getInteger( + com.android.internal.R.integer.config_defaultNotificationLedOff); + + mEnabledPref = (SystemSettingSwitchPreference) + findPreference(Settings.System.NOTIFICATION_LIGHT_PULSE); + mEnabledPref.setOnPreferenceChangeListener(this); + + mDefaultPref = (ApplicationLightPreference) findPreference(DEFAULT_PREF); + mDefaultPref.setOnPreferenceChangeListener(this); + + mAutoGenerateColors = (CMSystemSettingSwitchPreference) + findPreference(CMSettings.System.NOTIFICATION_LIGHT_COLOR_AUTO); + + // Advanced light settings + mNotificationLedBrightnessPref = (PreferenceScreen) + findPreference(CMSettings.System.NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL); + mMultipleLedsEnabledPref = (CMSystemSettingSwitchPreference) + findPreference(CMSettings.System.NOTIFICATION_LIGHT_MULTIPLE_LEDS_ENABLE); + mScreenOnLightsPref = (CMSystemSettingSwitchPreference) + findPreference(CMSettings.System.NOTIFICATION_LIGHT_SCREEN_ON); + mScreenOnLightsPref.setOnPreferenceChangeListener(this); + mCustomEnabledPref = (CMSystemSettingSwitchPreference) + findPreference(CMSettings.System.NOTIFICATION_LIGHT_PULSE_CUSTOM_ENABLE); + mCustomEnabledPref.setOnPreferenceChangeListener(this); + if (!resources.getBoolean( + org.cyanogenmod.platform.internal.R.bool.config_adjustableNotificationLedBrightness)) { + mAdvancedPrefs.removePreference(mNotificationLedBrightnessPref); + } else { + mNotificationLedBrightnessPref.setOnPreferenceChangeListener(this); + } + if (!resources.getBoolean( + org.cyanogenmod.platform.internal.R.bool.config_multipleNotificationLeds)) { + mAdvancedPrefs.removePreference(mMultipleLedsEnabledPref); + } else { + mMultipleLedsEnabledPref.setOnPreferenceChangeListener(this); + } + + // Missed call and Voicemail preferences should only show on devices with a voice capabilities + TelephonyManager tm = (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE); + if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_NONE) { + removePreference("phone_list"); + } else { + mCallPref = (ApplicationLightPreference) findPreference(MISSED_CALL_PREF); + mCallPref.setOnPreferenceChangeListener(this); + + mVoicemailPref = (ApplicationLightPreference) findPreference(VOICEMAIL_PREF); + mVoicemailPref.setOnPreferenceChangeListener(this); + } + + mApplicationPrefList = (PreferenceGroup) findPreference("applications_list"); + mApplicationPrefList.setOrderingAsAdded(false); + + // Get launch-able applications + mPackageManager = getPackageManager(); + mPackageAdapter = new PackageListAdapter(getActivity()); + + mPackages = new HashMap<String, Package>(); + setHasOptionsMenu(true); + + mMultiColorLed = resources.getBoolean(com.android.internal.R.bool.config_multiColorNotificationLed); + if (!mMultiColorLed) { + resetColors(); + PreferenceGroup mGeneralPrefs = (PreferenceGroup) prefSet.findPreference("general_section"); + mGeneralPrefs.removePreference(mAutoGenerateColors); + } else { + mAutoGenerateColors.setOnPreferenceChangeListener(this); + } + } + + @Override + protected int getMetricsCategory() { + return CMMetricsLogger.NOTIFICATION_LIGHT_SETTINGS; + } + + @Override + public void onResume() { + super.onResume(); + refreshDefault(); + refreshCustomApplicationPrefs(); + getListView().setOnItemLongClickListener(this); + getActivity().invalidateOptionsMenu(); + } + + @Override + public void onStart() { + super.onStart(); + setChildrenStarted(getPreferenceScreen(), true); + } + + @Override + public void onStop() { + super.onStop(); + setChildrenStarted(getPreferenceScreen(), false); + } + + private void setChildrenStarted(PreferenceGroup group, boolean started) { + final int count = group.getPreferenceCount(); + for (int i = 0; i < count; i++) { + Preference pref = group.getPreference(i); + if (pref instanceof ApplicationLightPreference) { + ApplicationLightPreference ap = (ApplicationLightPreference) pref; + if (started) { + ap.onStart(); + } else { + ap.onStop(); + } + } else if (pref instanceof PreferenceGroup) { + setChildrenStarted((PreferenceGroup) pref, started); + } + } + } + + private void refreshDefault() { + ContentResolver resolver = getContentResolver(); + int color = CMSettings.System.getInt(resolver, + CMSettings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR, mDefaultColor); + int timeOn = CMSettings.System.getInt(resolver, + CMSettings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_ON, mDefaultLedOn); + int timeOff = CMSettings.System.getInt(resolver, + CMSettings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_OFF, mDefaultLedOff); + + mDefaultPref.setAllValues(color, timeOn, timeOff); + + // Get Missed call and Voicemail values + if (mCallPref != null) { + int callColor = CMSettings.System.getInt(resolver, + CMSettings.System.NOTIFICATION_LIGHT_PULSE_CALL_COLOR, mDefaultColor); + int callTimeOn = CMSettings.System.getInt(resolver, + CMSettings.System.NOTIFICATION_LIGHT_PULSE_CALL_LED_ON, mDefaultLedOn); + int callTimeOff = CMSettings.System.getInt(resolver, + CMSettings.System.NOTIFICATION_LIGHT_PULSE_CALL_LED_OFF, mDefaultLedOff); + + mCallPref.setAllValues(callColor, callTimeOn, callTimeOff); + } + + if (mVoicemailPref != null) { + int vmailColor = CMSettings.System.getInt(resolver, + CMSettings.System.NOTIFICATION_LIGHT_PULSE_VMAIL_COLOR, mDefaultColor); + int vmailTimeOn = CMSettings.System.getInt(resolver, + CMSettings.System.NOTIFICATION_LIGHT_PULSE_VMAIL_LED_ON, mDefaultLedOn); + int vmailTimeOff = CMSettings.System.getInt(resolver, + CMSettings.System.NOTIFICATION_LIGHT_PULSE_VMAIL_LED_OFF, mDefaultLedOff); + + mVoicemailPref.setAllValues(vmailColor, vmailTimeOn, vmailTimeOff); + } + + mApplicationPrefList = (PreferenceGroup) findPreference("applications_list"); + mApplicationPrefList.setOrderingAsAdded(false); + } + + private void refreshCustomApplicationPrefs() { + Context context = getActivity(); + + if (!parsePackageList()) { + return; + } + + // Add the Application Preferences + if (mApplicationPrefList != null) { + mApplicationPrefList.removeAll(); + + for (Package pkg : mPackages.values()) { + try { + PackageInfo info = mPackageManager.getPackageInfo(pkg.name, + PackageManager.GET_META_DATA); + ApplicationLightPreference pref = + new ApplicationLightPreference(context, pkg.color, pkg.timeon, pkg.timeoff); + + pref.setKey(pkg.name); + pref.setTitle(info.applicationInfo.loadLabel(mPackageManager)); + pref.setIcon(info.applicationInfo.loadIcon(mPackageManager)); + pref.setPersistent(false); + pref.setOnPreferenceChangeListener(this); + + mApplicationPrefList.addPreference(pref); + } catch (NameNotFoundException e) { + // Do nothing + } + } + + /* Display a pref explaining how to add apps */ + if (mApplicationPrefList.getPreferenceCount() == 0) { + String summary = getResources().getString( + R.string.notification_light_no_apps_summary); + String useCustom = getResources().getString( + R.string.notification_light_use_custom); + Preference pref = new Preference(context); + pref.setSummary(String.format(summary, useCustom)); + pref.setEnabled(false); + mApplicationPrefList.addPreference(pref); + } + } + } + + private int getInitialColorForPackage(String packageName) { + boolean autoColor = CMSettings.System.getInt(getContentResolver(), + CMSettings.System.NOTIFICATION_LIGHT_COLOR_AUTO, mMultiColorLed ? 1 : 0) == 1; + int color = mDefaultColor; + if (autoColor) { + try { + Drawable icon = mPackageManager.getApplicationIcon(packageName); + color = ColorUtils.generateAlertColorFromDrawable(icon); + } catch (NameNotFoundException e) { + // shouldn't happen, but just return default + } + } + return color; + } + + private void addCustomApplicationPref(String packageName) { + Package pkg = mPackages.get(packageName); + if (pkg == null) { + int color = getInitialColorForPackage(packageName); + pkg = new Package(packageName, color, mDefaultLedOn, mDefaultLedOff); + mPackages.put(packageName, pkg); + savePackageList(false); + refreshCustomApplicationPrefs(); + } + } + + private void removeCustomApplicationPref(String packageName) { + if (mPackages.remove(packageName) != null) { + savePackageList(false); + refreshCustomApplicationPrefs(); + } + } + + private boolean parsePackageList() { + final String baseString = CMSettings.System.getString(getContentResolver(), + CMSettings.System.NOTIFICATION_LIGHT_PULSE_CUSTOM_VALUES); + + if (TextUtils.equals(mPackageList, baseString)) { + return false; + } + + mPackageList = baseString; + mPackages.clear(); + + if (baseString != null) { + final String[] array = TextUtils.split(baseString, "\\|"); + for (String item : array) { + if (TextUtils.isEmpty(item)) { + continue; + } + Package pkg = Package.fromString(item); + if (pkg != null) { + mPackages.put(pkg.name, pkg); + } + } + } + + return true; + } + + private void savePackageList(boolean preferencesUpdated) { + List<String> settings = new ArrayList<String>(); + for (Package app : mPackages.values()) { + settings.add(app.toString()); + } + final String value = TextUtils.join("|", settings); + if (preferencesUpdated) { + mPackageList = value; + } + CMSettings.System.putString(getContentResolver(), + CMSettings.System.NOTIFICATION_LIGHT_PULSE_CUSTOM_VALUES, value); + } + + /** + * Updates the default or package specific notification settings. + * + * @param packageName Package name of application specific settings to update + * @param color + * @param timeon + * @param timeoff + */ + protected void updateValues(String packageName, Integer color, Integer timeon, Integer timeoff) { + ContentResolver resolver = getContentResolver(); + + if (packageName.equals(DEFAULT_PREF)) { + CMSettings.System.putInt(resolver, CMSettings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR, color); + CMSettings.System.putInt(resolver, CMSettings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_ON, timeon); + CMSettings.System.putInt(resolver, CMSettings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_OFF, timeoff); + refreshDefault(); + return; + } else if (packageName.equals(MISSED_CALL_PREF)) { + CMSettings.System.putInt(resolver, CMSettings.System.NOTIFICATION_LIGHT_PULSE_CALL_COLOR, color); + CMSettings.System.putInt(resolver, CMSettings.System.NOTIFICATION_LIGHT_PULSE_CALL_LED_ON, timeon); + CMSettings.System.putInt(resolver, CMSettings.System.NOTIFICATION_LIGHT_PULSE_CALL_LED_OFF, timeoff); + refreshDefault(); + return; + } else if (packageName.equals(VOICEMAIL_PREF)) { + CMSettings.System.putInt(resolver, CMSettings.System.NOTIFICATION_LIGHT_PULSE_VMAIL_COLOR, color); + CMSettings.System.putInt(resolver, CMSettings.System.NOTIFICATION_LIGHT_PULSE_VMAIL_LED_ON, timeon); + CMSettings.System.putInt(resolver, CMSettings.System.NOTIFICATION_LIGHT_PULSE_VMAIL_LED_OFF, timeoff); + refreshDefault(); + return; + } + + // Find the custom package and sets its new values + Package app = mPackages.get(packageName); + if (app != null) { + app.color = color; + app.timeon = timeon; + app.timeoff = timeoff; + savePackageList(true); + } + } + + protected void resetColors() { + ContentResolver resolver = getContentResolver(); + + // Reset to the framework default colors + CMSettings.System.putInt(resolver, CMSettings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR, mDefaultColor); + CMSettings.System.putInt(resolver, CMSettings.System.NOTIFICATION_LIGHT_PULSE_CALL_COLOR, mDefaultColor); + CMSettings.System.putInt(resolver, CMSettings.System.NOTIFICATION_LIGHT_PULSE_VMAIL_COLOR, mDefaultColor); + + refreshDefault(); + } + + public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { + final Preference pref = (Preference) getPreferenceScreen().getRootAdapter().getItem(position); + + if (mApplicationPrefList.findPreference(pref.getKey()) != pref) { + return false; + } + + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) + .setTitle(R.string.dialog_delete_title) + .setMessage(R.string.dialog_delete_message) + .setIconAttribute(android.R.attr.alertDialogIcon) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + removeCustomApplicationPref(pref.getKey()); + } + }) + .setNegativeButton(android.R.string.cancel, null); + + builder.show(); + return true; + } + + public boolean onPreferenceChange(Preference preference, Object objValue) { + if (preference == mEnabledPref || preference == mCustomEnabledPref || + preference == mMultipleLedsEnabledPref || + preference == mNotificationLedBrightnessPref || + preference == mScreenOnLightsPref || + preference == mAutoGenerateColors) { + getActivity().invalidateOptionsMenu(); + } else { + ApplicationLightPreference lightPref = (ApplicationLightPreference) preference; + updateValues(lightPref.getKey(), lightPref.getColor(), + lightPref.getOnValue(), lightPref.getOffValue()); + } + + return true; + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + mMenu = menu; + mMenu.add(0, MENU_ADD, 0, R.string.profiles_add) + .setIcon(R.drawable.ic_menu_add_white) + .setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + } + + @Override + public void onPrepareOptionsMenu(Menu menu) { + boolean enableAddButton = mEnabledPref.isChecked() && mCustomEnabledPref.isChecked(); + menu.findItem(MENU_ADD).setVisible(enableAddButton); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case MENU_ADD: + showDialog(DIALOG_APPS); + return true; + } + return false; + } + + /** + * Utility classes and supporting methods + */ + @Override + public Dialog onCreateDialog(int id) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + final Dialog dialog; + switch (id) { + case DIALOG_APPS: + final ListView list = new ListView(getActivity()); + list.setAdapter(mPackageAdapter); + + builder.setTitle(R.string.profile_choose_app); + builder.setView(list); + dialog = builder.create(); + + list.setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + // Add empty application definition, the user will be able to edit it later + PackageItem info = (PackageItem) parent.getItemAtPosition(position); + addCustomApplicationPref(info.packageName); + dialog.cancel(); + } + }); + break; + default: + dialog = null; + } + return dialog; + } + + /** + * Application class + */ + private static class Package { + public String name; + public Integer color; + public Integer timeon; + public Integer timeoff; + + /** + * Stores all the application values in one call + * @param name + * @param color + * @param timeon + * @param timeoff + */ + public Package(String name, Integer color, Integer timeon, Integer timeoff) { + this.name = name; + this.color = color; + this.timeon = timeon; + this.timeoff = timeoff; + } + + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(name); + builder.append("="); + builder.append(color); + builder.append(";"); + builder.append(timeon); + builder.append(";"); + builder.append(timeoff); + return builder.toString(); + } + + public static Package fromString(String value) { + if (TextUtils.isEmpty(value)) { + return null; + } + String[] app = value.split("=", -1); + if (app.length != 2) + return null; + + String[] values = app[1].split(";", -1); + if (values.length != 3) + return null; + + try { + Package item = new Package(app[0], Integer.parseInt(values[0]), Integer + .parseInt(values[1]), Integer.parseInt(values[2])); + return item; + } catch (NumberFormatException e) { + return null; + } + } + + } +} diff --git a/src/com/android/settings/privacyguard/AppInfoLoader.java b/src/com/android/settings/privacyguard/AppInfoLoader.java new file mode 100644 index 0000000..6efd7a5 --- /dev/null +++ b/src/com/android/settings/privacyguard/AppInfoLoader.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2015 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.privacyguard; + +import android.app.AppOpsManager; +import android.content.AsyncTaskLoader; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageInfo; + +import com.android.settings.privacyguard.PrivacyGuardManager.AppInfo; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * An asynchronous loader implementation that loads AppInfo structures. + */ +/* package */ class AppInfoLoader extends AsyncTaskLoader<List<AppInfo>> { + private PackageManager mPm; + private boolean mShowSystemApps; + private AppOpsManager mAppOps; + private static final String[] BLACKLISTED_PACKAGES = { + "com.android.systemui" + }; + + public AppInfoLoader(Context context, boolean showSystemApps) { + super(context); + mPm = context.getPackageManager(); + mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE); + mShowSystemApps = showSystemApps; + } + + @Override + public List<AppInfo> loadInBackground() { + return loadInstalledApps(); + } + + @Override + public void onStartLoading() { + forceLoad(); + } + + @Override + public void onStopLoading() { + cancelLoad(); + } + + @Override + protected void onReset() { + cancelLoad(); + } + + private boolean isBlacklisted(String packageName) { + for (String pkg : BLACKLISTED_PACKAGES) { + if (pkg.equals(packageName)) { + return true; + } + } + return false; + } + + /** + * Uses the package manager to query for all currently installed apps + * for the list. + * + * @return the complete List off installed applications (@code PrivacyGuardAppInfo) + */ + private List<AppInfo> loadInstalledApps() { + List<AppInfo> apps = new ArrayList<AppInfo>(); + List<PackageInfo> packages = mPm.getInstalledPackages( + PackageManager.GET_PERMISSIONS | PackageManager.GET_SIGNATURES); + + for (PackageInfo info : packages) { + final ApplicationInfo appInfo = info.applicationInfo; + + // skip all system apps if they shall not be included + if ((!mShowSystemApps && (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) + || (appInfo.uid == android.os.Process.SYSTEM_UID) + || isBlacklisted(appInfo.packageName)) { + continue; + } + + AppInfo app = new AppInfo(); + app.title = appInfo.loadLabel(mPm).toString(); + app.packageName = info.packageName; + app.enabled = appInfo.enabled; + app.uid = info.applicationInfo.uid; + app.privacyGuardEnabled = mAppOps.getPrivacyGuardSettingForPackage( + app.uid, app.packageName); + apps.add(app); + } + + // sort the apps by their enabled state, then by title + Collections.sort(apps, new Comparator<AppInfo>() { + @Override + public int compare(AppInfo lhs, AppInfo rhs) { + if (lhs.enabled != rhs.enabled) { + return lhs.enabled ? -1 : 1; + } + return lhs.title.compareToIgnoreCase(rhs.title); + } + }); + + return apps; + } + +} diff --git a/src/com/android/settings/privacyguard/PrivacyGuardAppListAdapter.java b/src/com/android/settings/privacyguard/PrivacyGuardAppListAdapter.java new file mode 100644 index 0000000..57047b9 --- /dev/null +++ b/src/com/android/settings/privacyguard/PrivacyGuardAppListAdapter.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2013 SlimRoms 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.privacyguard; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.os.AsyncTask; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.SectionIndexer; +import android.widget.TextView; + +import com.android.settings.R; +import com.android.settings.privacyguard.PrivacyGuardManager.AppInfo; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +public class PrivacyGuardAppListAdapter extends BaseAdapter implements SectionIndexer { + + private LayoutInflater mInflater; + private PackageManager mPm; + + private List<AppInfo> mApps; + private String[] mSections; + private int[] mPositions; + private ConcurrentHashMap<String, Drawable> mIcons; + private Drawable mDefaultImg; + + private Context mContext; + + //constructor + public PrivacyGuardAppListAdapter(Context context, List<AppInfo> apps, + List<String> sections, List<Integer> positions) { + mContext = context; + mInflater = LayoutInflater.from(mContext); + mPm = context.getPackageManager(); + + mApps = apps; + mSections = sections.toArray(new String[sections.size()]); + mPositions = new int[positions.size()]; + for (int i = 0; i < positions.size(); i++) { + mPositions[i] = positions.get(i); + } + + // set the default icon till the actual app icon is loaded in async task + mDefaultImg = mContext.getResources().getDrawable(android.R.mipmap.sym_def_app_icon); + mIcons = new ConcurrentHashMap<String, Drawable>(); + + new LoadIconsTask().execute(apps.toArray(new PrivacyGuardManager.AppInfo[]{})); + } + + @Override + public int getCount() { + return mApps.size(); + } + + @Override + public Object getItem(int position) { + return mApps.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + PrivacyGuardAppViewHolder appHolder; + + if (convertView == null) { + convertView = mInflater.inflate(R.layout.privacy_guard_manager_list_row, null); + + // creates a ViewHolder and children references + appHolder = new PrivacyGuardAppViewHolder(); + appHolder.title = (TextView) convertView.findViewById(R.id.app_title); + appHolder.icon = (ImageView) convertView.findViewById(R.id.app_icon); + appHolder.privacyGuardIcon = (ImageView) convertView.findViewById(R.id.app_privacy_guard_icon); + convertView.setTag(appHolder); + } else { + appHolder = (PrivacyGuardAppViewHolder) convertView.getTag(); + } + + PrivacyGuardManager.AppInfo app = mApps.get(position); + + appHolder.title.setText(app.title); + + Drawable icon = mIcons.get(app.packageName); + appHolder.icon.setImageDrawable(icon != null ? icon : mDefaultImg); + + int privacyGuardDrawableResId = app.privacyGuardEnabled + ? R.drawable.ic_privacy_guard_on : + R.drawable.ic_privacy_guard_off; + appHolder.privacyGuardIcon.setImageResource(privacyGuardDrawableResId); + + return convertView; + } + + @Override + public int getPositionForSection(int section) { + if (section < 0 || section >= mSections.length) { + return -1; + } + + return mPositions[section]; + } + + @Override + public int getSectionForPosition(int position) { + if (position < 0 || position >= getCount()) { + return -1; + } + + int index = Arrays.binarySearch(mPositions, position); + + /* + * Consider this example: section positions are 0, 3, 5; the supplied + * position is 4. The section corresponding to position 4 starts at + * position 3, so the expected return value is 1. Binary search will not + * find 4 in the array and thus will return -insertPosition-1, i.e. -3. + * To get from that number to the expected value of 1 we need to negate + * and subtract 2. + */ + return index >= 0 ? index : -index - 2; + } + + @Override + public Object[] getSections() { + return mSections; + } + + /** + * An asynchronous task to load the icons of the installed applications. + */ + private class LoadIconsTask extends AsyncTask<PrivacyGuardManager.AppInfo, Void, Void> { + @Override + protected Void doInBackground(PrivacyGuardManager.AppInfo... apps) { + for (PrivacyGuardManager.AppInfo app : apps) { + try { + Drawable icon = mPm.getApplicationIcon(app.packageName); + mIcons.put(app.packageName, icon); + publishProgress(); + } catch (PackageManager.NameNotFoundException e) { + // ignored; app will show up with default image + } + } + + return null; + } + + @Override + protected void onProgressUpdate(Void... progress) { + notifyDataSetChanged(); + } + } + + /** + * App view holder used to reuse the views inside the list. + */ + public static class PrivacyGuardAppViewHolder { + TextView title; + ImageView icon; + ImageView privacyGuardIcon; + } +} diff --git a/src/com/android/settings/privacyguard/PrivacyGuardManager.java b/src/com/android/settings/privacyguard/PrivacyGuardManager.java new file mode 100644 index 0000000..c2f485a --- /dev/null +++ b/src/com/android/settings/privacyguard/PrivacyGuardManager.java @@ -0,0 +1,409 @@ +/* + * Copyright (C) 2013 SlimRoms 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.privacyguard; + +import android.app.FragmentTransaction; +import android.os.Build; +import android.view.animation.AnimationUtils; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.AppOpsManager; +import android.app.Dialog; +import android.app.DialogFragment; +import android.app.Fragment; +import android.app.FragmentManager; +import android.app.LoaderManager; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.Loader; +import android.content.SharedPreferences; +import android.net.Uri; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.text.TextUtils; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.AdapterView.OnItemLongClickListener; +import android.widget.ListView; +import android.widget.TextView; + +import com.android.settings.R; +import com.android.settings.Settings.AppOpsSummaryActivity; +import com.android.settings.SubSettings; +import com.android.settings.applications.AppOpsDetails; +import com.android.settings.applications.AppOpsState; +import com.android.settings.applications.AppOpsState.OpsTemplate; +import com.android.settings.privacyguard.AppInfoLoader; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class PrivacyGuardManager extends Fragment + implements OnItemClickListener, OnItemLongClickListener, + LoaderManager.LoaderCallbacks<List<PrivacyGuardManager.AppInfo>> { + + private static final String TAG = "PrivacyGuardManager"; + + private TextView mNoUserAppsInstalled; + private ListView mAppsList; + private View mLoadingContainer; + private PrivacyGuardAppListAdapter mAdapter; + private List<AppInfo> mApps; + + private Activity mActivity; + + private SharedPreferences mPreferences; + private AppOpsManager mAppOps; + + private int mSavedFirstVisiblePosition = AdapterView.INVALID_POSITION; + private int mSavedFirstItemOffset; + + // keys for extras and icicles + private final static String LAST_LIST_POS = "last_list_pos"; + private final static String LAST_LIST_OFFSET = "last_list_offset"; + + // Privacy Guard Fragment + private final static String PRIVACY_GUARD_FRAGMENT_TAG = "privacy_guard_fragment"; + + // holder for package data passed into the adapter + public static final class AppInfo { + String title; + String packageName; + boolean enabled; + boolean privacyGuardEnabled; + int uid; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + mActivity = getActivity(); + mAppOps = (AppOpsManager)getActivity().getSystemService(Context.APP_OPS_SERVICE); + + View hostView = inflater.inflate(R.layout.privacy_guard_manager, container, false); + + Fragment privacyGuardPrefs = PrivacyGuardPrefs.newInstance(); + FragmentTransaction fragmentTransaction = getChildFragmentManager().beginTransaction(); + fragmentTransaction.replace(R.id.privacy_guard_prefs, privacyGuardPrefs, + PRIVACY_GUARD_FRAGMENT_TAG); + fragmentTransaction.commit(); + return hostView; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + mNoUserAppsInstalled = (TextView) mActivity.findViewById(R.id.error); + + mAppsList = (ListView) mActivity.findViewById(R.id.apps_list); + mAppsList.setOnItemClickListener(this); + mAppsList.setOnItemLongClickListener(this); + + mLoadingContainer = mActivity.findViewById(R.id.loading_container); + + // get shared preference + mPreferences = mActivity.getSharedPreferences("privacy_guard_manager", Activity.MODE_PRIVATE); + if (savedInstanceState == null && !mPreferences.getBoolean("first_help_shown", false)) { + showHelp(); + } + + if (savedInstanceState != null) { + mSavedFirstVisiblePosition = savedInstanceState.getInt(LAST_LIST_POS, + AdapterView.INVALID_POSITION); + mSavedFirstItemOffset = savedInstanceState.getInt(LAST_LIST_OFFSET, 0); + } else { + mSavedFirstVisiblePosition = AdapterView.INVALID_POSITION; + mSavedFirstItemOffset = 0; + } + + // load apps and construct the list + scheduleAppsLoad(); + + setHasOptionsMenu(true); + } + + @Override + public void onViewStateRestored(Bundle savedInstanceState) { + super.onViewStateRestored(savedInstanceState); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + outState.putInt(LAST_LIST_POS, mSavedFirstVisiblePosition); + outState.putInt(LAST_LIST_OFFSET, mSavedFirstItemOffset); + } + + @Override + public void onPause() { + super.onPause(); + + // Remember where the list is scrolled to so we can restore the scroll position + // when we come back to this activity and *after* we complete querying for the + // conversations. + mSavedFirstVisiblePosition = mAppsList.getFirstVisiblePosition(); + View firstChild = mAppsList.getChildAt(0); + mSavedFirstItemOffset = (firstChild == null) ? 0 : firstChild.getTop(); + } + + @Override + public void onResume() { + super.onResume(); + + // rebuild the list; the user might have changed settings inbetween + scheduleAppsLoad(); + } + + @Override + public Loader<List<AppInfo>> onCreateLoader(int id, Bundle args) { + mLoadingContainer.startAnimation(AnimationUtils.loadAnimation( + mActivity, android.R.anim.fade_in)); + mAppsList.startAnimation(AnimationUtils.loadAnimation( + mActivity, android.R.anim.fade_out)); + + mAppsList.setVisibility(View.INVISIBLE); + mLoadingContainer.setVisibility(View.VISIBLE); + return new AppInfoLoader(mActivity, shouldShowSystemApps()); + } + + @Override + public void onLoadFinished(Loader<List<AppInfo>> loader, List<AppInfo> apps) { + mApps = apps; + prepareAppAdapter(); + + mLoadingContainer.startAnimation(AnimationUtils.loadAnimation( + mActivity, android.R.anim.fade_out)); + mAppsList.startAnimation(AnimationUtils.loadAnimation( + mActivity, android.R.anim.fade_in)); + + if (mSavedFirstVisiblePosition != AdapterView.INVALID_POSITION) { + mAppsList.setSelectionFromTop(mSavedFirstVisiblePosition, mSavedFirstItemOffset); + mSavedFirstVisiblePosition = AdapterView.INVALID_POSITION; + } + + mLoadingContainer.setVisibility(View.INVISIBLE); + mAppsList.setVisibility(View.VISIBLE); + } + + @Override + public void onLoaderReset(Loader<List<AppInfo>> loader) { + } + + private void scheduleAppsLoad() { + getLoaderManager().restartLoader(0, null, this); + } + + private void prepareAppAdapter() { + // if app list is empty inform the user + // else go ahead and construct the list + if (mApps == null || mApps.isEmpty()) { + mNoUserAppsInstalled.setText(R.string.privacy_guard_no_user_apps); + mNoUserAppsInstalled.setVisibility(View.VISIBLE); + mAppsList.setVisibility(View.GONE); + mAppsList.setAdapter(null); + } else { + mNoUserAppsInstalled.setVisibility(View.GONE); + mAppsList.setVisibility(View.VISIBLE); + mAdapter = createAdapter(); + mAppsList.setAdapter(mAdapter); + mAppsList.setFastScrollEnabled(true); + } + } + + private PrivacyGuardAppListAdapter createAdapter() { + String lastSectionIndex = null; + ArrayList<String> sections = new ArrayList<String>(); + ArrayList<Integer> positions = new ArrayList<Integer>(); + int count = mApps.size(), offset = 0; + + for (int i = 0; i < count; i++) { + AppInfo app = mApps.get(i); + String sectionIndex; + + if (!app.enabled) { + sectionIndex = "--"; //XXX + } else if (app.title.isEmpty()) { + sectionIndex = ""; + } else { + sectionIndex = app.title.substring(0, 1).toUpperCase(); + } + + if (lastSectionIndex == null || + !TextUtils.equals(sectionIndex, lastSectionIndex)) { + sections.add(sectionIndex); + positions.add(offset); + lastSectionIndex = sectionIndex; + } + offset++; + } + + return new PrivacyGuardAppListAdapter(mActivity, mApps, sections, positions); + } + + private void resetPrivacyGuard() { + if (mApps == null || mApps.isEmpty()) { + return; + } + showResetDialog(); + } + + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + // on click change the privacy guard status for this item + final AppInfo app = (AppInfo) parent.getItemAtPosition(position); + + app.privacyGuardEnabled = !app.privacyGuardEnabled; + mAppOps.setPrivacyGuardSettingForPackage(app.uid, app.packageName, app.privacyGuardEnabled); + + mAdapter.notifyDataSetChanged(); + } + + @Override + public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { + // on long click open app details window + final AppInfo app = (AppInfo) parent.getItemAtPosition(position); + + Bundle args = new Bundle(); + args.putString(AppOpsDetails.ARG_PACKAGE_NAME, app.packageName); + + SubSettings ssa = (SubSettings) getActivity(); + ssa.startPreferencePanel(AppOpsDetails.class.getName(), args, + R.string.privacy_guard_manager_title, null, this, 2); + return true; + } + + private boolean shouldShowSystemApps() { + return mPreferences.getBoolean("show_system_apps", true) && + mActivity.getResources().getBoolean(R.bool.config_showBuiltInAppsForPG); + } + + public static class HelpDialogFragment extends DialogFragment { + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + return new AlertDialog.Builder(getActivity()) + .setTitle(R.string.privacy_guard_help_title) + .setMessage(R.string.privacy_guard_help_text) + .setNegativeButton(R.string.dlg_ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.cancel(); + } + }) + .create(); + } + + @Override + public void onCancel(DialogInterface dialog) { + getActivity().getSharedPreferences("privacy_guard_manager", Activity.MODE_PRIVATE) + .edit() + .putBoolean("first_help_shown", true) + .commit(); + } + } + + private void showHelp() { + HelpDialogFragment fragment = new HelpDialogFragment(); + fragment.show(getFragmentManager(), "help_dialog"); + } + + public static class ResetDialogFragment extends DialogFragment { + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + return new AlertDialog.Builder(getActivity()) + .setTitle(R.string.privacy_guard_reset_title) + .setMessage(R.string.privacy_guard_reset_text) + .setPositiveButton(R.string.dlg_ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + ((PrivacyGuardManager)getTargetFragment()).doReset(); + } + }) + .setNegativeButton(R.string.cancel, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + // Do nothing + } + }) + .create(); + } + } + + private void doReset() { + // turn off privacy guard for all apps shown in the current list + for (AppInfo app : mApps) { + app.privacyGuardEnabled = false; + } + mAppOps.resetAllModes(); + mAdapter.notifyDataSetChanged(); + } + + private void showResetDialog() { + ResetDialogFragment dialog = new ResetDialogFragment(); + dialog.setTargetFragment(this, 0); + dialog.show(getFragmentManager(), "reset_dialog"); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + inflater.inflate(R.menu.privacy_guard_manager, menu); + if (!mActivity.getResources().getBoolean(R.bool.config_showBuiltInAppsForPG)) { + menu.removeItem(R.id.show_system_apps); + } else { + menu.findItem(R.id.show_system_apps).setChecked(shouldShowSystemApps()); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.help: + showHelp(); + return true; + case R.id.reset: + resetPrivacyGuard(); + return true; + case R.id.show_system_apps: + final String prefName = "show_system_apps"; + // set the menu checkbox and save it in + // shared preference and rebuild the list + item.setChecked(!item.isChecked()); + mPreferences.edit().putBoolean(prefName, item.isChecked()).commit(); + scheduleAppsLoad(); + return true; + case R.id.advanced: + Intent i = new Intent(Intent.ACTION_MAIN); + i.setClass(mActivity, AppOpsSummaryActivity.class); + mActivity.startActivity(i); + return true; + default: + return super.onContextItemSelected(item); + } + } +} diff --git a/src/com/android/settings/privacyguard/PrivacyGuardPrefs.java b/src/com/android/settings/privacyguard/PrivacyGuardPrefs.java new file mode 100644 index 0000000..060e617 --- /dev/null +++ b/src/com/android/settings/privacyguard/PrivacyGuardPrefs.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2013 Slimroms + * + * 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.privacyguard; + +import android.os.Bundle; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceChangeListener; +import android.preference.SwitchPreference; +import android.provider.Settings; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ListView; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.Utils; +import cyanogenmod.providers.CMSettings; +import org.cyanogenmod.internal.logging.CMMetricsLogger; + +public class PrivacyGuardPrefs extends SettingsPreferenceFragment implements + OnPreferenceChangeListener { + + private static final String TAG = "PrivacyGuardPrefs"; + + private static final String KEY_PRIVACY_GUARD_DEFAULT = "privacy_guard_default"; + + private SwitchPreference mPrivacyGuardDefault; + + public static PrivacyGuardPrefs newInstance() { + PrivacyGuardPrefs privacyGuardFragment = new PrivacyGuardPrefs(); + return privacyGuardFragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + addPreferencesFromResource(R.xml.privacy_guard_prefs); + + mPrivacyGuardDefault = (SwitchPreference) findPreference(KEY_PRIVACY_GUARD_DEFAULT); + mPrivacyGuardDefault.setOnPreferenceChangeListener(this); + + mPrivacyGuardDefault.setChecked(CMSettings.Secure.getInt(getContentResolver(), + CMSettings.Secure.PRIVACY_GUARD_DEFAULT, 0) == 1); + } + + @Override + public View onCreateView(LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + final View view = super.onCreateView(inflater, container, savedInstanceState); + final ListView list = (ListView) view.findViewById(android.R.id.list); + // our container already takes care of the padding + int paddingTop = list.getPaddingTop(); + int paddingBottom = list.getPaddingBottom(); + list.setPadding(0, paddingTop, 0, paddingBottom); + return view; + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (preference == mPrivacyGuardDefault) { + boolean value = (Boolean) newValue; + CMSettings.Secure.putInt(getContentResolver(), + CMSettings.Secure.PRIVACY_GUARD_DEFAULT, value ? 1 : 0); + return true; + } + return false; + } + + @Override + protected int getMetricsCategory() { + return CMMetricsLogger.PRIVACY_GUARD_PREFS; + } +} diff --git a/src/com/android/settings/profiles/AppGroupConfig.java b/src/com/android/settings/profiles/AppGroupConfig.java new file mode 100644 index 0000000..0832151 --- /dev/null +++ b/src/com/android/settings/profiles/AppGroupConfig.java @@ -0,0 +1,334 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.profiles; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.NotificationGroup; +import android.content.DialogInterface; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.PreferenceGroup; +import android.preference.PreferenceScreen; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.AdapterContextMenuInfo; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ListView; +import android.widget.Toast; + +import cyanogenmod.app.ProfileManager; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.cyanogenmod.PackageListAdapter; +import com.android.settings.cyanogenmod.PackageListAdapter.PackageItem; + +import org.cyanogenmod.internal.logging.CMMetricsLogger; + +public class AppGroupConfig extends SettingsPreferenceFragment + implements Preference.OnPreferenceChangeListener { + + private static String TAG = "AppGroupConfig"; + + private static final int DIALOG_APPS = 0; + + private static final int DELETE_CONFIRM = 1; + + private static final int DELETE_GROUP_CONFIRM = 2; + + public static final String PROFILE_SERVICE = "profile"; + + private ListView mListView; + + private PackageManager mPackageManager; + + private NotificationGroup mNotificationGroup; + + private ProfileManager mProfileManager; + + private NamePreference mNamePreference; + + private static final int MENU_DELETE = Menu.FIRST; + + private static final int MENU_ADD = Menu.FIRST + 1; + + private PackageListAdapter mAppAdapter; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (savedInstanceState != null) { + mPackageToDelete = savedInstanceState.getString("package_delete"); + } + + mProfileManager = ProfileManager.getInstance(getActivity()); + addPreferencesFromResource(R.xml.application_list); + + final Bundle args = getArguments(); + if (args != null) { + mNotificationGroup = (NotificationGroup) args.getParcelable("NotificationGroup"); + mPackageManager = getPackageManager(); + mAppAdapter = new PackageListAdapter(getActivity()); + + updatePackages(); + + setHasOptionsMenu(true); + } + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + MenuItem delete = menu.add(0, MENU_DELETE, 0, R.string.profile_menu_delete_title) + .setIcon(R.drawable.ic_menu_trash_holo_dark); + delete.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | + MenuItem.SHOW_AS_ACTION_WITH_TEXT); + + MenuItem addApplication = menu.add(0, MENU_ADD, 0, R.string.profiles_add) + .setIcon(R.drawable.ic_menu_add) + .setAlphabeticShortcut('a'); + addApplication.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | + MenuItem.SHOW_AS_ACTION_WITH_TEXT); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return super.onCreateView(inflater, container, savedInstanceState); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case MENU_DELETE: + deleteNotificationGroup(); + return true; + case MENU_ADD: + addNewApp(); + return true; + default: + return false; + } + } + + Preference mAddPreference; + + Preference mDeletePreference; + + private void updatePackages() { + PreferenceScreen prefSet = getPreferenceScreen(); + + // Add the General section + PreferenceGroup generalPrefs = (PreferenceGroup) prefSet.findPreference("general_section"); + if (generalPrefs != null) { + generalPrefs.removeAll(); + + // Name preference + mNamePreference = new NamePreference(getActivity(), mNotificationGroup.getName()); + mNamePreference.setOnPreferenceChangeListener(this); + generalPrefs.addPreference(mNamePreference); + } + + PreferenceGroup applicationsList = (PreferenceGroup) prefSet.findPreference("applications_list"); + if (applicationsList != null) { + applicationsList.removeAll(); + for (String pkg : mNotificationGroup.getPackages()) { + Preference pref = new Preference(getActivity()); + try { + PackageInfo group = mPackageManager.getPackageInfo(pkg, 0); + pref.setKey(group.packageName); + pref.setTitle(group.applicationInfo.loadLabel(mPackageManager)); + Drawable icon = group.applicationInfo.loadIcon(mPackageManager); + pref.setIcon(icon); + pref.setSelectable(true); + pref.setPersistent(false); + applicationsList.addPreference(pref); + } catch (NameNotFoundException e) { + e.printStackTrace(); + } + } + } + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + menu.add(0, R.string.profile_menu_delete_title, 0, R.string.profile_menu_delete_title); + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + AdapterContextMenuInfo aMenuInfo = (AdapterContextMenuInfo) item.getMenuInfo(); + PackageItem selectedGroup = (PackageItem) mListView.getItemAtPosition(aMenuInfo.position); + switch (item.getItemId()) { + case R.string.profile_menu_delete_title: + deleteAppFromGroup(selectedGroup); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void deleteAppFromGroup(PackageItem selectedGroup) { + if (selectedGroup != null) { + mNotificationGroup.removePackage(selectedGroup.packageName); + updatePackages(); + } + } + + @Override + protected int getMetricsCategory() { + return CMMetricsLogger.APP_GROUP_CONFIG; + } + + @Override + public void onPause() { + if (mNotificationGroup != null) { + mProfileManager.addNotificationGroup(mNotificationGroup); + } + super.onPause(); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (preference == mNamePreference) { + String name = mNamePreference.getName().toString(); + if (!name.equals(mNotificationGroup.getName())) { + if (!mProfileManager.notificationGroupExists(name)) { + mNotificationGroup.setName(name); + } else { + mNamePreference.setName(mNotificationGroup.getName()); + Toast.makeText(getActivity(), R.string.duplicate_appgroup_name, Toast.LENGTH_LONG).show(); + } + } + } + return true; + } + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { + if (preference instanceof Preference) { + String deleteItem = preference.getKey(); + removeApp(deleteItem); + return true; + } + return super.onPreferenceTreeClick(preferenceScreen, preference); + } + + private void addNewApp() { + showDialog(DIALOG_APPS); + // TODO: switch to using the built in app list rather than dialog box? + } + + private void removeApp(String key) { + mPackageToDelete = key.toString(); + showDialog(DELETE_CONFIRM); + } + + private void deleteNotificationGroup() { + showDialog(DELETE_GROUP_CONFIRM); + } + + @Override + public Dialog onCreateDialog(int id) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + final Dialog dialog; + switch (id) { + case DIALOG_APPS: + final ListView list = new ListView(getActivity()); + list.setAdapter(mAppAdapter); + builder.setTitle(R.string.profile_choose_app); + builder.setView(list); + dialog = builder.create(); + list.setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + PackageItem info = (PackageItem) parent.getItemAtPosition(position); + mNotificationGroup.addPackage(info.packageName); + updatePackages(); + dialog.cancel(); + } + }); + break; + case DELETE_CONFIRM: + builder.setMessage(R.string.profile_app_delete_confirm); + builder.setTitle(R.string.profile_menu_delete_title); + builder.setIconAttribute(android.R.attr.alertDialogIcon); + builder.setPositiveButton(android.R.string.yes, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + doDelete(); + } + }); + builder.setNegativeButton(android.R.string.no, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + } + }); + dialog = builder.create(); + break; + case DELETE_GROUP_CONFIRM: + builder.setMessage(R.string.profile_delete_appgroup); + builder.setTitle(R.string.profile_menu_delete_title); + builder.setIconAttribute(android.R.attr.alertDialogIcon); + builder.setPositiveButton(android.R.string.yes, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mProfileManager.removeNotificationGroup(mNotificationGroup); + mNotificationGroup = null; + finish(); + } + }); + builder.setNegativeButton(android.R.string.no, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + } + }); + dialog = builder.create(); + break; + default: + dialog = null; + } + return dialog; + } + + String mPackageToDelete; + + private void doDelete() { + mNotificationGroup.removePackage(mPackageToDelete); + updatePackages(); + } + + @Override + public void onSaveInstanceState(Bundle in) { + super.onSaveInstanceState(in); + in.putString("package_delete", mPackageToDelete); + } +}
\ No newline at end of file diff --git a/src/com/android/settings/profiles/AppGroupList.java b/src/com/android/settings/profiles/AppGroupList.java new file mode 100644 index 0000000..35d3eeb --- /dev/null +++ b/src/com/android/settings/profiles/AppGroupList.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.profiles; + +import java.util.UUID; + +import android.annotation.Nullable; +import android.app.AlertDialog; +import android.app.NotificationGroup; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.PreferenceScreen; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; + +import cyanogenmod.app.ProfileManager; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; + +import org.cyanogenmod.internal.logging.CMMetricsLogger; +import org.cyanogenmod.internal.util.ScreenType; + +public class AppGroupList extends SettingsPreferenceFragment { + + private static final String TAG = "AppGroupSettings"; + + private ProfileManager mProfileManager; + + private View mFab; + + // constant value that can be used to check return code from sub activity. + private static final int APP_GROUP_CONFIG = 1; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + addPreferencesFromResource(R.xml.appgroup_list); + mProfileManager = ProfileManager.getInstance(getActivity()); + } + + @Override + protected int getMetricsCategory() { + return CMMetricsLogger.APP_GROUP_LIST; + } + + @Override + public void onResume() { + super.onResume(); + refreshList(); + + // On tablet devices remove the padding + if (ScreenType.isTablet(getActivity())) { + getListView().setPadding(0, 0, 0, 0); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.preference_list_with_fab, container, false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mFab = view.findViewById(R.id.floating_action_button); + mFab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + addAppGroup(); + } + }); + } + + public void refreshList() { + PreferenceScreen appgroupList = getPreferenceScreen(); + appgroupList.removeAll(); + + // Add the existing app groups + for (NotificationGroup group : mProfileManager.getNotificationGroups()) { + PreferenceScreen pref = new PreferenceScreen(getActivity(), null); + pref.setKey(group.getUuid().toString()); + pref.setTitle(group.getName()); + pref.setPersistent(false); + appgroupList.addPreference(pref); + } + } + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { + if (preference instanceof PreferenceScreen) { + NotificationGroup group = mProfileManager.getNotificationGroup( + UUID.fromString(preference.getKey())); + editGroup(group); + } + return super.onPreferenceTreeClick(preferenceScreen, preference); + } + + private void addAppGroup() { + LayoutInflater inflater = getActivity().getLayoutInflater(); + View content = inflater.inflate(R.layout.profile_name_dialog, null); + final TextView prompt = (TextView) content.findViewById(R.id.prompt); + final EditText entry = (EditText) content.findViewById(R.id.name); + + prompt.setText(R.string.profile_appgroup_name_prompt); + + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setTitle(R.string.profile_new_appgroup); + builder.setView(content); + + builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String name = entry.getText().toString(); + if (!mProfileManager.notificationGroupExists(name)) { + NotificationGroup newGroup = new NotificationGroup(name); + mProfileManager.addNotificationGroup(newGroup); + + refreshList(); + } else { + Toast.makeText(getActivity(), + R.string.duplicate_appgroup_name, Toast.LENGTH_LONG).show(); + } + } + }); + builder.setNegativeButton(android.R.string.cancel, null); + + AlertDialog dialog = builder.create(); + dialog.show(); + } + + private void editGroup(NotificationGroup group) { + Bundle args = new Bundle(); + args.putParcelable("NotificationGroup", group); + + startFragment(this, AppGroupConfig.class.getName(), R.string.profile_appgroup_manage, + APP_GROUP_CONFIG, args); + } +} diff --git a/src/com/android/settings/profiles/NFCProfile.java b/src/com/android/settings/profiles/NFCProfile.java new file mode 100644 index 0000000..74aec53 --- /dev/null +++ b/src/com/android/settings/profiles/NFCProfile.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.profiles; + +import java.util.UUID; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.nfc.NdefMessage; +import android.nfc.NdefRecord; +import android.nfc.NfcAdapter; +import android.os.Bundle; +import android.os.Parcelable; +import android.widget.Toast; + +import cyanogenmod.app.Profile; +import cyanogenmod.app.ProfileManager; +import cyanogenmod.providers.CMSettings; + +import com.android.settings.R; + +/** + * This activity handles NDEF_DISCOVERED intents with the cm/profile mime type. + * Tags should be encoded with the 16-byte UUID of the profile to be activated. + * Tapping a tag while that profile is already active will select the previously + * active profile. + */ +public class NFCProfile extends Activity { + + private static final String PREFS_NAME = "NFCProfile"; + + private static final String PREFS_PREVIOUS_PROFILE = "previous-profile"; + + static final String PROFILE_MIME_TYPE = "cm/profile"; + + private ProfileManager mProfileManager; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mProfileManager = ProfileManager.getInstance(this); + } + + @Override + protected void onResume() { + super.onResume(); + + Intent intent = getIntent(); + String action = intent.getAction(); + if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) { + Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES); + if (rawMsgs != null) { + NdefMessage[] msgs = new NdefMessage[rawMsgs.length]; + for (int i = 0; i < rawMsgs.length; i++) { + msgs[i] = (NdefMessage) rawMsgs[i]; + for (NdefRecord record : msgs[i].getRecords()) { + String type = new String(record.getType()); + byte[] payload = record.getPayload(); + if (PROFILE_MIME_TYPE.equals(type) && payload != null + && payload.length == 16) { + handleProfileMimeType(payload); + } + } + } + } + } + finish(); + } + + private void handleProfileMimeType(byte[] payload) { + UUID profileUuid = NFCProfileUtils.toUUID(payload); + + boolean enabled = CMSettings.System.getInt(getContentResolver(), + CMSettings.System.SYSTEM_PROFILES_ENABLED, 1) == 1; + + if (enabled) { + // Only do NFC profile changing if System Profile support is enabled + Profile currentProfile = mProfileManager.getActiveProfile(); + Profile targetProfile = mProfileManager.getProfile(profileUuid); + + if (targetProfile == null) { + // show profile selection for unknown tag + Intent i = new Intent(this, NFCProfileSelect.class); + i.putExtra(NFCProfileSelect.EXTRA_PROFILE_UUID, profileUuid.toString()); + i.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + this.startActivity(i); + } else { + // switch to profile + if (currentProfile == null || !currentProfile.getUuid().equals(profileUuid)) { + saveCurrentProfile(); + switchTo(profileUuid); + } else { + Profile lastProfile = getPreviouslySelectedProfile(); + if (lastProfile != null) { + switchTo(lastProfile.getUuid()); + clearPreviouslySelectedProfile(); + } + } + } + } + } + + private void switchTo(UUID uuid) { + Profile p = mProfileManager.getProfile(uuid); + if (p != null) { + mProfileManager.setActiveProfile(uuid); + + Toast.makeText( + this, + getString(R.string.profile_selected, p.getName()), + Toast.LENGTH_LONG).show(); + NFCProfileUtils.vibrate(this); + } + } + + private Profile getPreviouslySelectedProfile() { + Profile previous = null; + SharedPreferences prefs = getSharedPreferences(PREFS_NAME, 0); + String uuid = prefs.getString(PREFS_PREVIOUS_PROFILE, null); + if (uuid != null) { + previous = mProfileManager.getProfile(UUID.fromString(uuid)); + } + return previous; + } + + private void clearPreviouslySelectedProfile() { + SharedPreferences.Editor editor = getSharedPreferences(PREFS_NAME, 0).edit(); + editor.remove(PREFS_PREVIOUS_PROFILE); + editor.commit(); + } + + private void saveCurrentProfile() { + Profile currentProfile = mProfileManager.getActiveProfile(); + if (currentProfile != null) { + SharedPreferences.Editor editor = getSharedPreferences(PREFS_NAME, 0).edit(); + editor.putString(PREFS_PREVIOUS_PROFILE, currentProfile.getUuid().toString()); + editor.commit(); + } + } +} diff --git a/src/com/android/settings/profiles/NFCProfileSelect.java b/src/com/android/settings/profiles/NFCProfileSelect.java new file mode 100644 index 0000000..2c2fab8 --- /dev/null +++ b/src/com/android/settings/profiles/NFCProfileSelect.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.profiles; + +import java.util.UUID; + +import android.app.Activity; +import android.app.AlertDialog.Builder; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Toast; + +import cyanogenmod.app.Profile; +import cyanogenmod.app.ProfileManager; + +import com.android.settings.R; + +/** + * Activity to support attaching a unknown NFC tag to an existing profile. + */ +public class NFCProfileSelect extends Activity { + + private static final String TAG = "NFCProfileSelect"; + + static final String EXTRA_PROFILE_UUID = "PROFILE_UUID"; + + private ProfileManager mProfileManager; + + private UUID mProfileUuid; + + final static int defaultChoice = -1; + + private int currentChoice = defaultChoice; + + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mProfileManager = ProfileManager.getInstance(this); + + setContentView(R.layout.nfc_select); + setTitle(R.string.profile_unknown_nfc_tag); + + findViewById(R.id.add_tag).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + showProfileSelectionDialog(); + } + }); + } + + @Override + public void onResume() { + super.onResume(); + + String profileUuid = getIntent().getStringExtra(EXTRA_PROFILE_UUID); + if (profileUuid != null) { + mProfileUuid = UUID.fromString(profileUuid); + } else { + finish(); + } + } + + void showProfileSelectionDialog() { + final Profile[] profiles = mProfileManager.getProfiles(); + final String[] profileNames = new String[profiles.length]; + for (int i = 0; i < profiles.length; i++) { + profileNames[i] = profiles[i].getName(); + } + + Builder builder = new Builder(this); + builder.setTitle(R.string.profile_settings_title); + builder.setSingleChoiceItems(profileNames, currentChoice, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + currentChoice = which; + } + }); + builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (currentChoice != defaultChoice) { + Profile profile = profiles[currentChoice]; + profile.addSecondaryUuid(mProfileUuid); + mProfileManager.updateProfile(profile); + Toast.makeText(NFCProfileSelect.this, R.string.profile_write_success, Toast.LENGTH_LONG).show(); + } + finish(); + } + }); + builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }); + builder.show(); + } +} diff --git a/src/com/android/settings/net/ChartData.java b/src/com/android/settings/profiles/NFCProfileTagCallback.java index 0b8969e..e3fd5ef 100644 --- a/src/com/android/settings/net/ChartData.java +++ b/src/com/android/settings/profiles/NFCProfileTagCallback.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 The Android Open Source Project + * Copyright (C) 2014 The CyanogenMod Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,15 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package com.android.settings.profiles; -package com.android.settings.net; +import android.nfc.Tag; -import android.net.NetworkStatsHistory; - -public class ChartData { - public NetworkStatsHistory network; - - public NetworkStatsHistory detail; - public NetworkStatsHistory detailDefault; - public NetworkStatsHistory detailForeground; +public interface NFCProfileTagCallback { + public void onTagRead(Tag tag); } diff --git a/src/com/android/settings/profiles/NFCProfileUtils.java b/src/com/android/settings/profiles/NFCProfileUtils.java new file mode 100644 index 0000000..4ce8c80 --- /dev/null +++ b/src/com/android/settings/profiles/NFCProfileUtils.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.profiles; + +import android.content.Context; +import android.nfc.NdefMessage; +import android.nfc.NdefRecord; +import android.nfc.Tag; +import android.nfc.tech.Ndef; +import android.nfc.tech.NdefFormatable; +import android.os.Vibrator; +import android.util.Log; + +import cyanogenmod.app.Profile; + +import java.io.IOException; +import java.util.UUID; + +public class NFCProfileUtils { + + private static final String TAG = "NFCUtils"; + + private static final long[] VIBRATION_PATTERN = { + 0, 100, 10000 + }; + + public static void vibrate(Context context) { + Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); + vibrator.vibrate(VIBRATION_PATTERN, -1); + } + + /* + * Writes an NdefMessage to a NFC tag + */ + public static boolean writeTag(NdefMessage message, Tag tag) { + int size = message.toByteArray().length; + try { + Ndef ndef = Ndef.get(tag); + if (ndef != null) { + ndef.connect(); + if (!ndef.isWritable()) { + Log.e(TAG, "Tag is not writable!"); + return false; + } + if (ndef.getMaxSize() < size) { + Log.e(TAG, + "Tag exceeds max ndef message size! [" + size + " > " + + ndef.getMaxSize() + "]"); + return false; + } + ndef.writeNdefMessage(message); + return true; + } else { + NdefFormatable format = NdefFormatable.get(tag); + if (format != null) { + try { + format.connect(); + format.format(message); + return true; + } catch (IOException e) { + Log.e(TAG, "Write error!", e); + return false; + } + } else { + return false; + } + } + } catch (Exception e) { + Log.e(TAG, "Write error!", e); + return false; + } + } + + /* Convert a 16-byte array to a UUID */ + static UUID toUUID(byte[] byteArray) { + + long msb = 0; + long lsb = 0; + for (int i = 0; i < 8; i++) { + msb = (msb << 8) | (byteArray[i] & 0xff); + } + for (int i = 8; i < 16; i++) { + lsb = (lsb << 8) | (byteArray[i] & 0xff); + } + UUID result = new UUID(msb, lsb); + + return result; + } + + /* Convert a UUID to a 16-byte array */ + static byte[] asByteArray(UUID uuid) { + long msb = uuid.getMostSignificantBits(); + long lsb = uuid.getLeastSignificantBits(); + byte[] buffer = new byte[16]; + + for (int i = 0; i < 8; i++) { + buffer[i] = (byte) (msb >>> 8 * (7 - i)); + } + for (int i = 8; i < 16; i++) { + buffer[i] = (byte) (lsb >>> 8 * (7 - i)); + } + + return buffer; + } + + /* + * Convert a profiles into an NdefMessage. The profile UUID is 16 bytes and + * stored with the cm/profile mimetype + */ + public static NdefMessage getProfileAsNdef(Profile profile) { + byte[] profileBytes = NFCProfileUtils.asByteArray(profile.getUuid()); + + NdefRecord record = NdefRecord.createMime(NFCProfile.PROFILE_MIME_TYPE, profileBytes); + return new NdefMessage(new NdefRecord[] { record }); + } +} diff --git a/src/com/android/settings/profiles/NFCProfileWriter.java b/src/com/android/settings/profiles/NFCProfileWriter.java new file mode 100644 index 0000000..8396307 --- /dev/null +++ b/src/com/android/settings/profiles/NFCProfileWriter.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.profiles; + +import android.app.Activity; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.os.Bundle; +import android.util.Log; +import android.widget.Toast; + +import cyanogenmod.app.Profile; +import cyanogenmod.app.ProfileManager; + +import com.android.settings.R; + +import java.util.UUID; + +/** + * Activity to support writing a profile to an NFC tag. + * The mime type is "cm/profile" and the payload is the raw bytes of the profile's + * UUID. The payload was intentionally kept small to support writing on 46-byte tags. + */ +public class NFCProfileWriter extends Activity { + + private static final String TAG = "NFCProfileWriter"; + + static final String EXTRA_PROFILE_UUID = "PROFILE_UUID"; + + private NfcAdapter mNfcAdapter; + + private IntentFilter[] mWriteTagFilters; + + private Profile mProfile; + + private ProfileManager mProfileManager; + + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mNfcAdapter = NfcAdapter.getDefaultAdapter(this); + mProfileManager = ProfileManager.getInstance(this); + + setContentView(R.layout.nfc_writer); + setTitle(R.string.profile_write_nfc_tag); + } + + @Override + public void onResume() { + super.onResume(); + String profileUuid = getIntent().getStringExtra(EXTRA_PROFILE_UUID); + if (profileUuid != null) { + mProfile = mProfileManager.getProfile(UUID.fromString(profileUuid)); + Log.d(TAG, "Profile to write: " + mProfile.getName()); + enableTagWriteMode(); + } + } + + + @Override + protected void onPause() { + super.onPause(); + disableTagWriteMode(); + } + + private PendingIntent getPendingIntent() { + return PendingIntent.getActivity(this, 0, + new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0); + } + + private void disableTagWriteMode() { + mNfcAdapter.disableForegroundDispatch(this); + } + + private void enableTagWriteMode() { + IntentFilter tagDetected = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED); + mWriteTagFilters = new IntentFilter[] { + tagDetected + }; + mNfcAdapter.enableForegroundDispatch(this, getPendingIntent(), mWriteTagFilters, null); + } + + @Override + protected void onNewIntent(Intent intent) { + // Tag writing mode + if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) { + Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); + if (NFCProfileUtils.writeTag(NFCProfileUtils.getProfileAsNdef(mProfile), detectedTag)) { + Toast.makeText(this, R.string.profile_write_success, Toast.LENGTH_LONG).show(); + NFCProfileUtils.vibrate(this); + } else { + Toast.makeText(this, R.string.profile_write_failed, Toast.LENGTH_LONG).show(); + } + finish(); + } + } +} diff --git a/src/com/android/settings/profiles/NamePreference.java b/src/com/android/settings/profiles/NamePreference.java new file mode 100644 index 0000000..0a89974 --- /dev/null +++ b/src/com/android/settings/profiles/NamePreference.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.profiles; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.preference.Preference; +import android.view.View; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.settings.R; + +public class NamePreference extends Preference implements + View.OnClickListener, Preference.OnPreferenceChangeListener { + private static final String TAG = NamePreference.class.getSimpleName(); + + private TextView mNameView; + + private String mName; + + /** + * @param context + * @param title + */ + public NamePreference(Context context, String name) { + super(context); + mName = name.toString(); + init(); + } + + /** + * @param context + */ + public NamePreference(Context context) { + super(context); + init(); + } + + @Override + public void onBindView(View view) { + super.onBindView(view); + + View namePref = view.findViewById(R.id.name_pref); + if ((namePref != null) && namePref instanceof LinearLayout) { + namePref.setOnClickListener(this); + } + + mNameView = (TextView) view.findViewById(R.id.title); + + updatePreferenceViews(); + } + + private void init() { + setLayoutResource(R.layout.preference_name); + } + + public void setName(String name) { + mName = (name.toString()); + updatePreferenceViews(); + } + + public String getName() { + return(mName.toString()); + } + + private void updatePreferenceViews() { + if (mNameView != null) { + mNameView.setText(mName.toString()); + } + } + + @Override + public void onClick(android.view.View v) { + if (v != null) { + Context context = getContext(); + if (context != null) { + final EditText entry = new EditText(context); + entry.setSingleLine(); + entry.setText(mName.toString()); + + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(R.string.rename_dialog_title); + builder.setMessage(R.string.rename_dialog_message); + builder.setView(entry, 34, 16, 34, 16); + builder.setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String value = entry.getText().toString(); + mName = value.toString(); + mNameView.setText(value.toString()); + callChangeListener(this); + } + }); + builder.setNegativeButton(android.R.string.cancel, null); + AlertDialog dialog = builder.create(); + dialog.show(); + ((TextView)dialog.findViewById(android.R.id.message)).setTextAppearance(context, + android.R.style.TextAppearance_DeviceDefault_Small); + } + } + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + callChangeListener(preference); + return false; + } +}
\ No newline at end of file diff --git a/src/com/android/settings/profiles/ProfileGroupConfig.java b/src/com/android/settings/profiles/ProfileGroupConfig.java new file mode 100644 index 0000000..c960fb5 --- /dev/null +++ b/src/com/android/settings/profiles/ProfileGroupConfig.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.profiles; + +import java.util.UUID; + +import android.net.Uri; +import android.os.Bundle; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceChangeListener; + +import cyanogenmod.app.Profile; +import cyanogenmod.app.ProfileGroup; +import cyanogenmod.app.ProfileGroup.Mode; +import cyanogenmod.app.ProfileManager; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import org.cyanogenmod.internal.logging.CMMetricsLogger; + +public class ProfileGroupConfig extends SettingsPreferenceFragment implements + OnPreferenceChangeListener { + + private static final CharSequence KEY_SOUNDMODE = "sound_mode"; + private static final CharSequence KEY_VIBRATEMODE = "vibrate_mode"; + private static final CharSequence KEY_LIGHTSMODE = "lights_mode"; + private static final CharSequence KEY_RINGERMODE = "ringer_mode"; + private static final CharSequence KEY_SOUNDTONE = "soundtone"; + private static final CharSequence KEY_RINGTONE = "ringtone"; + + Profile mProfile; + ProfileGroup mProfileGroup; + + private ListPreference mSoundMode; + private ListPreference mRingerMode; + private ListPreference mVibrateMode; + private ListPreference mLightsMode; + private ProfileRingtonePreference mRingTone; + private ProfileRingtonePreference mSoundTone; + private ProfileManager mProfileManager; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + addPreferencesFromResource(R.xml.profile_settings); + + final Bundle args = getArguments(); + if (args != null) { + mProfile = (Profile) args.getParcelable("Profile"); + UUID uuid = UUID.fromString(args.getString("ProfileGroup")); + + mProfileManager = ProfileManager.getInstance(getActivity()); + mProfileGroup = mProfile.getProfileGroup(uuid); + + mRingerMode = (ListPreference) findPreference(KEY_RINGERMODE); + mSoundMode = (ListPreference) findPreference(KEY_SOUNDMODE); + mVibrateMode = (ListPreference) findPreference(KEY_VIBRATEMODE); + mLightsMode = (ListPreference) findPreference(KEY_LIGHTSMODE); + mRingTone = (ProfileRingtonePreference) findPreference(KEY_RINGTONE); + mSoundTone = (ProfileRingtonePreference) findPreference(KEY_SOUNDTONE); + + mRingTone.setShowSilent(false); + mSoundTone.setShowSilent(false); + + mSoundMode.setOnPreferenceChangeListener(this); + mRingerMode.setOnPreferenceChangeListener(this); + mVibrateMode.setOnPreferenceChangeListener(this); + mLightsMode.setOnPreferenceChangeListener(this); + mSoundTone.setOnPreferenceChangeListener(this); + mRingTone.setOnPreferenceChangeListener(this); + + updateState(); + } + } + + @Override + protected int getMetricsCategory() { + return CMMetricsLogger.PROFILE_GROUP_CONFIG; + } + + private void updateState() { + mVibrateMode.setValue(mProfileGroup.getVibrateMode().name()); + mSoundMode.setValue(mProfileGroup.getSoundMode().name()); + mRingerMode.setValue(mProfileGroup.getRingerMode().name()); + mLightsMode.setValue(mProfileGroup.getLightsMode().name()); + + mVibrateMode.setSummary(mVibrateMode.getEntry()); + mSoundMode.setSummary(mSoundMode.getEntry()); + mRingerMode.setSummary(mRingerMode.getEntry()); + mLightsMode.setSummary(mLightsMode.getEntry()); + + if (mProfileGroup.getSoundOverride() != null) { + mSoundTone.setRingtone(mProfileGroup.getSoundOverride()); + } + + if (mProfileGroup.getRingerOverride() != null) { + mRingTone.setRingtone(mProfileGroup.getRingerOverride()); + } + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (preference == mVibrateMode) { + mProfileGroup.setVibrateMode(Mode.valueOf((String) newValue)); + } else if (preference == mSoundMode) { + mProfileGroup.setSoundMode(Mode.valueOf((String) newValue)); + } else if (preference == mRingerMode) { + mProfileGroup.setRingerMode(Mode.valueOf((String) newValue)); + } else if (preference == mLightsMode) { + mProfileGroup.setLightsMode(Mode.valueOf((String) newValue)); + } else if (preference == mRingTone) { + Uri uri = Uri.parse((String) newValue); + mProfileGroup.setRingerOverride(uri); + } else if (preference == mSoundTone) { + Uri uri = Uri.parse((String) newValue); + mProfileGroup.setSoundOverride(uri); + } + + mProfileManager.updateProfile(mProfile); + + updateState(); + return true; + } +} diff --git a/src/com/android/settings/profiles/ProfileRingtonePreference.java b/src/com/android/settings/profiles/ProfileRingtonePreference.java new file mode 100644 index 0000000..91ccbe6 --- /dev/null +++ b/src/com/android/settings/profiles/ProfileRingtonePreference.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.profiles; + +import android.content.Context; +import android.content.Intent; +import android.media.RingtoneManager; +import android.net.Uri; +import android.preference.RingtonePreference; +import android.util.AttributeSet; + +public class ProfileRingtonePreference extends RingtonePreference { + private static final String TAG = "ProfileRingtonePreference"; + + public ProfileRingtonePreference(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); + } + + private Uri mRingtone; + + void setRingtone(Uri uri) { + mRingtone = uri; + } + + @Override + protected Uri onRestoreRingtone() { + if (mRingtone == null) { + return super.onRestoreRingtone(); + } else { + return mRingtone; + } + } +} diff --git a/src/com/android/settings/profiles/ProfilesPreference.java b/src/com/android/settings/profiles/ProfilesPreference.java new file mode 100644 index 0000000..b5462e1 --- /dev/null +++ b/src/com/android/settings/profiles/ProfilesPreference.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.profiles; + +import android.content.ActivityNotFoundException; +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settings.SettingsPreferenceFragment; + +public class ProfilesPreference extends CheckBoxPreference implements View.OnClickListener { + private static final String TAG = ProfilesPreference.class.getSimpleName(); + private static final float DISABLED_ALPHA = 0.4f; + private final SettingsPreferenceFragment mFragment; + private final Bundle mSettingsBundle; + + // constant value that can be used to check return code from sub activity. + private static final int PROFILE_DETAILS = 1; + + private ImageView mProfilesSettingsButton; + private TextView mTitleText; + private TextView mSummaryText; + private View mProfilesPref; + + public ProfilesPreference(SettingsPreferenceFragment fragment, Bundle settingsBundle) { + super(fragment.getActivity(), null, R.style.ProfilesPreferenceStyle); + setLayoutResource(R.layout.preference_profiles); + setWidgetLayoutResource(R.layout.preference_profiles_widget); + mFragment = fragment; + mSettingsBundle = settingsBundle; + } + + @Override + protected void onBindView(View view) { + super.onBindView(view); + + mProfilesPref = view.findViewById(R.id.profiles_pref); + mProfilesPref.setOnClickListener(this); + mProfilesSettingsButton = (ImageView)view.findViewById(R.id.profiles_settings); + mTitleText = (TextView)view.findViewById(android.R.id.title); + mSummaryText = (TextView)view.findViewById(android.R.id.summary); + + if (mSettingsBundle != null) { + mProfilesSettingsButton.setOnClickListener(this); + updatePreferenceViews(); + } else { + mProfilesSettingsButton.setVisibility(View.GONE); + } + } + + @Override + public void onClick(View view) { + if (view == mProfilesSettingsButton) { + try { + startProfileConfigActivity(); + } catch (ActivityNotFoundException e) { + // If the settings activity does not exist, we can just do nothing... + } + } else if (view == mProfilesPref) { + if (isEnabled() && !isChecked()) { + setChecked(true); + callChangeListener(getKey()); + } + } + } + + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + if (enabled) { + updatePreferenceViews(); + } else { + disablePreferenceViews(); + } + } + + private void disablePreferenceViews() { + if (mProfilesSettingsButton != null) { + mProfilesSettingsButton.setEnabled(false); + mProfilesSettingsButton.setAlpha(DISABLED_ALPHA); + } + if (mProfilesPref != null) { + mProfilesPref.setEnabled(false); + mProfilesPref.setBackgroundColor(0); + } + } + + private void updatePreferenceViews() { + final boolean checked = isChecked(); + if (mProfilesSettingsButton != null) { + mProfilesSettingsButton.setEnabled(true); + mProfilesSettingsButton.setClickable(true); + mProfilesSettingsButton.setFocusable(true); + } + if (mTitleText != null) { + mTitleText.setEnabled(true); + } + if (mSummaryText != null) { + mSummaryText.setEnabled(checked); + } + if (mProfilesPref != null) { + mProfilesPref.setEnabled(true); + mProfilesPref.setLongClickable(checked); + final boolean enabled = isEnabled(); + mProfilesPref.setOnClickListener(enabled ? this : null); + if (!enabled) { + mProfilesPref.setBackgroundColor(0); + } + } + } + + // utility method used to start sub activity + private void startProfileConfigActivity() { + SettingsActivity pa = (SettingsActivity) mFragment.getActivity(); + pa.startPreferencePanel(SetupActionsFragment.class.getCanonicalName(), mSettingsBundle, + R.string.profile_profile_manage, null, null, PROFILE_DETAILS); + } +} diff --git a/src/com/android/settings/profiles/ProfilesSettings.java b/src/com/android/settings/profiles/ProfilesSettings.java new file mode 100644 index 0000000..e2dcf04 --- /dev/null +++ b/src/com/android/settings/profiles/ProfilesSettings.java @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.profiles; + +import android.annotation.Nullable; +import android.app.Activity; +import android.app.AlertDialog; +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.os.UserHandle; +import android.preference.Preference; +import android.preference.PreferenceScreen; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ListView; +import android.widget.TextView; + +import cyanogenmod.app.Profile; +import cyanogenmod.app.ProfileManager; +import cyanogenmod.providers.CMSettings; + +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.cyanogenmod.CMBaseSystemSettingSwitchBar; +import org.cyanogenmod.internal.logging.CMMetricsLogger; + +import java.util.UUID; + +public class ProfilesSettings extends SettingsPreferenceFragment + implements CMBaseSystemSettingSwitchBar.SwitchBarChangeCallback, + Preference.OnPreferenceChangeListener { + private static final String TAG = "ProfilesSettings"; + + public static final String EXTRA_PROFILE = "Profile"; + public static final String EXTRA_NEW_PROFILE = "new_profile_mode"; + + private static final int MENU_RESET = Menu.FIRST; + private static final int MENU_APP_GROUPS = Menu.FIRST + 1; + + private final IntentFilter mFilter; + private final BroadcastReceiver mReceiver; + + private ProfileManager mProfileManager; + private CMBaseSystemSettingSwitchBar mProfileEnabler; + + private View mAddProfileFab; + private boolean mEnabled; + + ViewGroup mContainer; + + static Bundle mSavedState; + + public ProfilesSettings() { + mFilter = new IntentFilter(); + mFilter.addAction(ProfileManager.PROFILES_STATE_CHANGED_ACTION); + + mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (ProfileManager.PROFILES_STATE_CHANGED_ACTION.equals(action)) { + updateProfilesEnabledState(); + } + } + }; + + setHasOptionsMenu(true); + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + addPreferencesFromResource(R.xml.profiles_settings); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View view = super.onCreateView(inflater, container, savedInstanceState); + FrameLayout frameLayout = new FrameLayout(getActivity()); + mContainer = frameLayout; + frameLayout.addView(view); + return frameLayout; + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + // Add a footer to avoid a situation where the FAB would cover the last + // item's options in a non-scrollable listview. + ListView listView = getListView(); + View footer = LayoutInflater.from(getActivity()) + .inflate(R.layout.empty_list_entry_footer, listView, false); + listView.addFooterView(footer); + listView.setFooterDividersEnabled(false); + footer.setOnClickListener(null); + + View v = LayoutInflater.from(getActivity()) + .inflate(R.layout.empty_textview, (ViewGroup) view, true); + + TextView emptyTextView = (TextView) v.findViewById(R.id.empty); + listView.setEmptyView(emptyTextView); + + View fab = LayoutInflater.from(getActivity()) + .inflate(R.layout.fab, mContainer, true); + mAddProfileFab = fab.findViewById(R.id.floating_action_button); + mAddProfileFab.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + addProfile(); + } + }); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + mProfileManager = ProfileManager.getInstance(getActivity()); + // After confirming PreferenceScreen is available, we call super. + super.onActivityCreated(savedInstanceState); + } + + @Override + protected int getMetricsCategory() { + return CMMetricsLogger.PROFILES_SETTINGS; + } + + @Override + public void onResume() { + super.onResume(); + if (mProfileEnabler != null) { + mProfileEnabler.resume(getActivity()); + } + getActivity().registerReceiver(mReceiver, mFilter); + + // check if we are enabled + updateProfilesEnabledState(); + } + + @Override + public void onPause() { + super.onPause(); + if (mProfileEnabler != null) { + mProfileEnabler.pause(); + } + getActivity().unregisterReceiver(mReceiver); + } + + @Override + public void onStart() { + super.onStart(); + final SettingsActivity activity = (SettingsActivity) getActivity(); + mProfileEnabler = new CMBaseSystemSettingSwitchBar(activity, activity.getSwitchBar(), + CMSettings.System.SYSTEM_PROFILES_ENABLED, true, this); + } + + @Override + public void onDestroyView() { + if (mProfileEnabler != null) { + mProfileEnabler.teardownSwitchBar(); + } + super.onDestroyView(); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + menu.add(0, MENU_RESET, 0, R.string.profile_reset_title) + .setAlphabeticShortcut('r') + .setEnabled(mEnabled) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + menu.add(0, MENU_APP_GROUPS, 0, R.string.profile_appgroups_title) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case MENU_RESET: + resetAll(); + return true; + case MENU_APP_GROUPS: + startFragment(this, AppGroupList.class.getName(), + R.string.profile_appgroups_title, 0, null); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void addProfile() { + Bundle args = new Bundle(); + args.putBoolean(EXTRA_NEW_PROFILE, true); + args.putParcelable(EXTRA_PROFILE, new Profile(getString(R.string.new_profile_name))); + + SettingsActivity pa = (SettingsActivity) getActivity(); + pa.startPreferencePanel(SetupTriggersFragment.class.getCanonicalName(), args, + 0, null, this, 0); + } + + private void resetAll() { + new AlertDialog.Builder(getActivity()) + .setTitle(R.string.profile_reset_title) + .setIconAttribute(android.R.attr.alertDialogIcon) + .setMessage(R.string.profile_reset_message) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + mProfileManager.resetAll(); + mProfileManager.setActiveProfile( + mProfileManager.getActiveProfile().getUuid()); + dialog.dismiss(); + refreshList(); + + } + }) + .setNegativeButton(R.string.cancel, null) + .show(); + } + + private void updateProfilesEnabledState() { + Activity activity = getActivity(); + + mEnabled = CMSettings.System.getInt(activity.getContentResolver(), + CMSettings.System.SYSTEM_PROFILES_ENABLED, 1) == 1; + activity.invalidateOptionsMenu(); + + mAddProfileFab.setVisibility(mEnabled ? View.VISIBLE : View.GONE); + if (!mEnabled) { + getPreferenceScreen().removeAll(); // empty it + } else { + refreshList(); + } + } + + @Override + public void onEnablerChanged(boolean isEnabled) { + Intent intent = new Intent(ProfileManager.PROFILES_STATE_CHANGED_ACTION); + intent.putExtra(ProfileManager.EXTRA_PROFILES_STATE, + isEnabled ? + ProfileManager.PROFILES_STATE_ENABLED : + ProfileManager.PROFILES_STATE_DISABLED); + getActivity().sendBroadcast(intent); + } + + public void refreshList() { + PreferenceScreen plist = getPreferenceScreen(); + plist.removeAll(); + + // Get active profile, if null + Profile prof = mProfileManager.getActiveProfile(); + String selectedKey = prof != null ? prof.getUuid().toString() : null; + + for (Profile profile : mProfileManager.getProfiles()) { + Bundle args = new Bundle(); + args.putParcelable(ProfilesSettings.EXTRA_PROFILE, profile); + args.putBoolean(ProfilesSettings.EXTRA_NEW_PROFILE, false); + + ProfilesPreference ppref = new ProfilesPreference(this, args); + ppref.setKey(profile.getUuid().toString()); + ppref.setTitle(profile.getName()); + ppref.setPersistent(false); + ppref.setOnPreferenceChangeListener(this); + ppref.setSelectable(true); + ppref.setEnabled(true); + + if (TextUtils.equals(selectedKey, ppref.getKey())) { + ppref.setChecked(true); + } + + plist.addPreference(ppref); + } + } + + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (newValue instanceof String) { + setSelectedProfile((String) newValue); + refreshList(); + } + return true; + } + + private void setSelectedProfile(String key) { + try { + UUID selectedUuid = UUID.fromString(key); + mProfileManager.setActiveProfile(selectedUuid); + } catch (IllegalArgumentException ex) { + ex.printStackTrace(); + } + } +} diff --git a/src/com/android/settings/profiles/SetupActionsFragment.java b/src/com/android/settings/profiles/SetupActionsFragment.java new file mode 100644 index 0000000..b94632a --- /dev/null +++ b/src/com/android/settings/profiles/SetupActionsFragment.java @@ -0,0 +1,1166 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.profiles; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.NotificationGroup; +import android.app.admin.DevicePolicyManager; +import android.bluetooth.BluetoothAdapter; +import android.content.ContentResolver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.location.LocationManager; +import android.media.AudioManager; +import android.media.RingtoneManager; +import android.net.ConnectivityManager; +import android.net.wifi.WifiManager; +//import android.net.wimax.WimaxHelper; +import android.nfc.NfcManager; +import android.os.AsyncTask; +import android.os.Bundle; +import android.preference.SeekBarVolumizer; +import android.provider.Settings; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.SeekBar; +import android.widget.TextView; + +import cyanogenmod.app.Profile; +import cyanogenmod.app.ProfileGroup; +import cyanogenmod.app.ProfileManager; +import cyanogenmod.profiles.AirplaneModeSettings; +import cyanogenmod.profiles.BrightnessSettings; +import cyanogenmod.profiles.ConnectionSettings; +import cyanogenmod.profiles.LockSettings; +import cyanogenmod.profiles.RingModeSettings; +import cyanogenmod.profiles.StreamSettings; + +import com.android.settings.R; +import com.android.settings.SubSettings; +import com.android.settings.cyanogenmod.DeviceUtils; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.profiles.actions.ItemListAdapter; +import com.android.settings.profiles.actions.item.AirplaneModeItem; +import com.android.settings.profiles.actions.item.AppGroupItem; +import com.android.settings.profiles.actions.item.BrightnessItem; +import com.android.settings.profiles.actions.item.ConnectionOverrideItem; +import com.android.settings.profiles.actions.item.DisabledItem; +import com.android.settings.profiles.actions.item.DozeModeItem; +import com.android.settings.profiles.actions.item.Header; +import com.android.settings.profiles.actions.item.Item; +import com.android.settings.profiles.actions.item.LockModeItem; +import com.android.settings.profiles.actions.item.NotificationLightModeItem; +import com.android.settings.profiles.actions.item.ProfileNameItem; +import com.android.settings.profiles.actions.item.RingModeItem; +import com.android.settings.profiles.actions.item.TriggerItem; +import com.android.settings.profiles.actions.item.VolumeStreamItem; +import com.android.settings.Utils; +import com.android.settings.utils.TelephonyUtils; +import org.cyanogenmod.internal.logging.CMMetricsLogger; + +import java.util.ArrayList; +import java.util.List; + +import static cyanogenmod.profiles.ConnectionSettings.PROFILE_CONNECTION_2G3G4G; +import static cyanogenmod.profiles.ConnectionSettings.PROFILE_CONNECTION_BLUETOOTH; +import static cyanogenmod.profiles.ConnectionSettings.PROFILE_CONNECTION_GPS; +import static cyanogenmod.profiles.ConnectionSettings.PROFILE_CONNECTION_MOBILEDATA; +import static cyanogenmod.profiles.ConnectionSettings.PROFILE_CONNECTION_NFC; +import static cyanogenmod.profiles.ConnectionSettings.PROFILE_CONNECTION_SYNC; +import static cyanogenmod.profiles.ConnectionSettings.PROFILE_CONNECTION_WIFI; +import static cyanogenmod.profiles.ConnectionSettings.PROFILE_CONNECTION_WIFIAP; + +public class SetupActionsFragment extends SettingsPreferenceFragment + implements AdapterView.OnItemClickListener { + + private static final int RINGTONE_REQUEST_CODE = 1000; + private static final int NEW_TRIGGER_REQUEST_CODE = 1001; + private static final int SET_NETWORK_MODE_REQUEST_CODE = 1002; + + public static final String EXTRA_NETWORK_MODE_PICKED = "network_mode_picker::chosen_value"; + + private static final int MENU_REMOVE = Menu.FIRST; + private static final int MENU_FILL_PROFILE = Menu.FIRST + 1; + + private static final int DIALOG_FILL_FROM_SETTINGS = 1; + private static final int DIALOG_AIRPLANE_MODE = 2; + private static final int DIALOG_BRIGHTNESS = 3; + private static final int DIALOG_LOCK_MODE = 4; + private static final int DIALOG_DOZE_MODE = 5; + private static final int DIALOG_RING_MODE = 6; + private static final int DIALOG_CONNECTION_OVERRIDE = 7; + private static final int DIALOG_VOLUME_STREAM = 8; + private static final int DIALOG_PROFILE_NAME = 9; + + private static final String LAST_SELECTED_POSITION = "last_selected_position"; + private static final int DIALOG_REMOVE_PROFILE = 10; + + private static final int DIALOG_NOTIFICATION_LIGHT_MODE = 11; + + private int mLastSelectedPosition = -1; + private Item mSelectedItem; + + Profile mProfile; + ItemListAdapter mAdapter; + ProfileManager mProfileManager; + ListView mListView; + + boolean mNewProfileMode; + + private static final int[] LOCKMODE_MAPPING = new int[] { + Profile.LockMode.DEFAULT, Profile.LockMode.INSECURE, Profile.LockMode.DISABLE + }; + private static final int[] EXPANDED_DESKTOP_MAPPING = new int[] { + Profile.ExpandedDesktopMode.DEFAULT, + Profile.ExpandedDesktopMode.ENABLE, + Profile.ExpandedDesktopMode.DISABLE + }; + private static final int[] DOZE_MAPPING = new int[] { + Profile.DozeMode.DEFAULT, + Profile.DozeMode.ENABLE, + Profile.DozeMode.DISABLE + }; + private static final int[] NOTIFICATION_LIGHT_MAPPING = new int[] { + Profile.NotificationLightMode.DEFAULT, + Profile.NotificationLightMode.ENABLE, + Profile.NotificationLightMode.DISABLE + }; + private List<Item> mItems = new ArrayList<Item>(); + + public static SetupActionsFragment newInstance(Profile profile, boolean newProfile) { + SetupActionsFragment fragment = new SetupActionsFragment(); + Bundle args = new Bundle(); + args.putParcelable(ProfilesSettings.EXTRA_PROFILE, profile); + args.putBoolean(ProfilesSettings.EXTRA_NEW_PROFILE, newProfile); + + fragment.setArguments(args); + return fragment; + } + + public SetupActionsFragment() { + // Required empty public constructor + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + mProfile = getArguments().getParcelable(ProfilesSettings.EXTRA_PROFILE); + mNewProfileMode = getArguments().getBoolean(ProfilesSettings.EXTRA_NEW_PROFILE, false); + } + + mProfileManager = ProfileManager.getInstance(getActivity()); + mAdapter = new ItemListAdapter(getActivity(), mItems); + rebuildItemList(); + + setHasOptionsMenu(true); + if (mNewProfileMode && savedInstanceState == null) { + // only pop this up on first creation + showDialog(DIALOG_FILL_FROM_SETTINGS); + } else if (savedInstanceState != null) { + mLastSelectedPosition = savedInstanceState.getInt("last_selected_position", -1); + if (mLastSelectedPosition != -1) { + mSelectedItem = mAdapter.getItem(mLastSelectedPosition); + } + } + } + + private void rebuildItemList() { + mItems.clear(); + // general prefs + mItems.add(new Header(getString(R.string.profile_name_title))); + mItems.add(new ProfileNameItem(mProfile)); + + if (!mNewProfileMode) { + // triggers + mItems.add(new Header(getString(R.string.profile_triggers_header))); + mItems.add(generateTriggerItem(TriggerItem.WIFI)); + if (DeviceUtils.deviceSupportsBluetooth()) { + mItems.add(generateTriggerItem(TriggerItem.BLUETOOTH)); + } + if (DeviceUtils.deviceSupportsNfc(getActivity())) { + mItems.add(generateTriggerItem(TriggerItem.NFC)); + } + } + + // connection overrides + mItems.add(new Header(getString(R.string.wireless_networks_settings_title))); + if (DeviceUtils.deviceSupportsBluetooth()) { + mItems.add(new ConnectionOverrideItem(PROFILE_CONNECTION_BLUETOOTH, + mProfile.getSettingsForConnection(PROFILE_CONNECTION_BLUETOOTH))); + } + mItems.add(generateConnectionOverrideItem(PROFILE_CONNECTION_GPS)); + mItems.add(generateConnectionOverrideItem(PROFILE_CONNECTION_WIFI)); + mItems.add(generateConnectionOverrideItem(PROFILE_CONNECTION_SYNC)); + if (DeviceUtils.deviceSupportsMobileData(getActivity())) { + mItems.add(generateConnectionOverrideItem(PROFILE_CONNECTION_MOBILEDATA)); + mItems.add(generateConnectionOverrideItem(PROFILE_CONNECTION_WIFIAP)); + + final List<SubscriptionInfo> subs = SubscriptionManager.from(getContext()) + .getActiveSubscriptionInfoList(); + if (subs != null) { + for (SubscriptionInfo sub : subs) { + mItems.add(generatePreferredNetworkOverrideItem(sub.getSubscriptionId())); + } + } else { + if (TelephonyManager.from(getContext()).getPhoneCount() == 1) { + mItems.add(generatePreferredNetworkOverrideItem( + SubscriptionManager.INVALID_SUBSCRIPTION_ID)); + } + } + } + //if (WimaxHelper.isWimaxSupported(getActivity())) { + // mItems.add(generateConnectionOverrideItem(PROFILE_CONNECTION_WIMAX)); + //} + if (DeviceUtils.deviceSupportsNfc(getActivity())) { + mItems.add(generateConnectionOverrideItem(PROFILE_CONNECTION_NFC)); + } + + // add volume streams + mItems.add(new Header(getString(R.string.profile_volumeoverrides_title))); + mItems.add(generateVolumeStreamItem(AudioManager.STREAM_ALARM)); + mItems.add(generateVolumeStreamItem(AudioManager.STREAM_MUSIC)); + mItems.add(generateVolumeStreamItem(AudioManager.STREAM_RING)); + mItems.add(generateVolumeStreamItem(AudioManager.STREAM_NOTIFICATION)); + + // system settings + mItems.add(new Header(getString(R.string.profile_system_settings_title))); + mItems.add(new RingModeItem(mProfile.getRingMode())); + mItems.add(new AirplaneModeItem(mProfile.getAirplaneMode())); + DevicePolicyManager dpm = (DevicePolicyManager) getSystemService( + Context.DEVICE_POLICY_SERVICE); + if (!dpm.requireSecureKeyguard()) { + mItems.add(new LockModeItem(mProfile)); + } else { + mItems.add(new DisabledItem(R.string.profile_lockmode_title, + R.string.profile_lockmode_policy_disabled_summary)); + } + mItems.add(new BrightnessItem(mProfile.getBrightness())); + + final Activity activity = getActivity(); + if (Utils.isDozeAvailable(activity)) { + mItems.add(new DozeModeItem(mProfile)); + } + + if (getResources().getBoolean( + com.android.internal.R.bool.config_intrusiveNotificationLed)) { + mItems.add(new NotificationLightModeItem(mProfile)); + } + + // app groups + mItems.add(new Header(getString(R.string.profile_app_group_category_title))); + + int groupsAdded = 0; + ProfileGroup[] profileGroups = mProfile.getProfileGroups(); + if (profileGroups != null && profileGroups.length > 1) { // it will always have "other" + for (ProfileGroup profileGroup : profileGroups) { + // only display profile group if there's a matching notification group + // and don't' show the wildcard group + if (mProfileManager.getNotificationGroup(profileGroup.getUuid()) != null + && !mProfile.getDefaultGroup().getUuid().equals( + profileGroup.getUuid())) { + mItems.add(new AppGroupItem(mProfile, profileGroup, + mProfileManager.getNotificationGroup( + profileGroup.getUuid()))); + groupsAdded++; + } + } + if (groupsAdded > 0) { + // add "Other" at the end + mItems.add(new AppGroupItem(mProfile, mProfile.getDefaultGroup(), + mProfileManager.getNotificationGroup( + mProfile.getDefaultGroup().getUuid()))); + } + } + if (mProfileManager.getNotificationGroups().length > 0) { + // if there are notification groups available, allow them to be configured + mItems.add(new AppGroupItem()); + } else if (groupsAdded == 0) { + // no notification groups available at all, nothing to add/remove + mItems.remove(mItems.get(mItems.size() - 1)); + } + + mAdapter.notifyDataSetChanged(); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + if (!mNewProfileMode) { + menu.add(0, MENU_REMOVE, 0, R.string.profile_menu_delete_title) + .setIcon(R.drawable.ic_actionbar_delete) + .setAlphabeticShortcut('d') + .setEnabled(true) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | + MenuItem.SHOW_AS_ACTION_WITH_TEXT); + + menu.add(0, MENU_FILL_PROFILE, 0, R.string.profile_menu_fill_from_state) + .setAlphabeticShortcut('f') + .setEnabled(true) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case MENU_REMOVE: + mLastSelectedPosition = -1; // reset + showDialog(DIALOG_REMOVE_PROFILE); + return true; + case MENU_FILL_PROFILE: + showDialog(DIALOG_FILL_FROM_SETTINGS); + return true; + } + return super.onOptionsItemSelected(item); + } + + private ConnectionOverrideItem generatePreferredNetworkOverrideItem(int subId) { + ConnectionSettings settings = mProfile.getConnectionSettingWithSubId(subId); + if (settings == null) { + settings = new ConnectionSettings(ConnectionSettings.PROFILE_CONNECTION_2G3G4G); + settings.setSubId(subId); + mProfile.setConnectionSettings(settings); + } + return new ConnectionOverrideItem(settings.getConnectionId(), settings); + } + + private ConnectionOverrideItem generateConnectionOverrideItem(int connectionId) { + ConnectionSettings settings = mProfile.getSettingsForConnection(connectionId); + if (settings == null) { + settings = new ConnectionSettings(connectionId); + mProfile.setConnectionSettings(settings); + } + return new ConnectionOverrideItem(connectionId, settings); + } + + private VolumeStreamItem generateVolumeStreamItem(int stream) { + StreamSettings settings = mProfile.getSettingsForStream(stream); + if (settings == null) { + settings = new StreamSettings(stream); + mProfile.setStreamSettings(settings); + } + return new VolumeStreamItem(stream, settings); + } + + private TriggerItem generateTriggerItem(int whichTrigger) { + return new TriggerItem(mProfile, whichTrigger); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + if (mNewProfileMode) { + TextView desc = new TextView(getActivity()); + int descPadding = getResources().getDimensionPixelSize( + R.dimen.profile_instruction_padding); + desc.setPadding(descPadding, descPadding, descPadding, descPadding); + desc.setText(R.string.profile_setup_actions_description); + getListView().addHeaderView(desc, null, false); + } + } + + private void updateProfile() { + mProfileManager.updateProfile(mProfile); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + mListView.setAdapter(mAdapter); + if (mNewProfileMode) { + getActivity().getActionBar().setTitle(R.string.profile_setup_actions_title); + } else { + getActivity().getActionBar().setTitle(mProfile.getName()); + } + } + + private AlertDialog requestFillProfileFromSettingsDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setMessage(R.string.profile_populate_profile_from_state); + builder.setNegativeButton(R.string.no, null); + builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + fillProfileFromCurrentSettings(); + dialog.dismiss(); + } + }); + return builder.create(); + } + + private void fillProfileFromCurrentSettings() { + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... params) { + fillProfileWithCurrentSettings(getActivity(), mProfile); + updateProfile(); + return null; + } + + @Override + protected void onPostExecute(Void aVoid) { + super.onPostExecute(aVoid); + rebuildItemList(); + } + }.execute((Void) null); + } + + public static void fillProfileWithCurrentSettings(Context context, Profile profile) { + // bt + if (DeviceUtils.deviceSupportsBluetooth()) { + profile.setConnectionSettings( + new ConnectionSettings(ConnectionSettings.PROFILE_CONNECTION_BLUETOOTH, + BluetoothAdapter.getDefaultAdapter().isEnabled() ? 1 : 0, + true)); + } + + // gps + LocationManager locationManager = (LocationManager) + context.getSystemService(Context.LOCATION_SERVICE); + boolean gpsEnabled = locationManager. + isProviderEnabled(LocationManager.GPS_PROVIDER); + profile.setConnectionSettings( + new ConnectionSettings(ConnectionSettings.PROFILE_CONNECTION_GPS, + gpsEnabled ? 1 : 0, true)); + + // wifi + WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + profile.setConnectionSettings( + new ConnectionSettings(ConnectionSettings.PROFILE_CONNECTION_WIFI, + wifiManager.isWifiEnabled() ? 1 : 0, true)); + + // auto sync data + profile.setConnectionSettings( + new ConnectionSettings(ConnectionSettings.PROFILE_CONNECTION_SYNC, + ContentResolver.getMasterSyncAutomatically() ? 1 : 0, true)); + + // mobile data + if (DeviceUtils.deviceSupportsMobileData(context)) { + ConnectivityManager cm = (ConnectivityManager) + context.getSystemService(Context.CONNECTIVITY_SERVICE); + profile.setConnectionSettings( + new ConnectionSettings(ConnectionSettings.PROFILE_CONNECTION_MOBILEDATA, + cm.getMobileDataEnabled() ? 1 : 0, true)); + } + + // wifi hotspot + profile.setConnectionSettings( + new ConnectionSettings(ConnectionSettings.PROFILE_CONNECTION_WIFIAP, + wifiManager.isWifiApEnabled() ? 1 : 0, true)); + + // 2g/3g/4g + // skipping this one + + // nfc + if (DeviceUtils.deviceSupportsNfc(context)) { + NfcManager nfcManager = (NfcManager) context.getSystemService(Context.NFC_SERVICE); + profile.setConnectionSettings( + new ConnectionSettings(ConnectionSettings.PROFILE_CONNECTION_NFC, + nfcManager.getDefaultAdapter().isEnabled() ? 1 : 0, true)); + } + + // alarm volume + final AudioManager am = (AudioManager) context + .getSystemService(Context.AUDIO_SERVICE); + profile.setStreamSettings(new StreamSettings(AudioManager.STREAM_ALARM, + am.getStreamVolume(AudioManager.STREAM_ALARM), true)); + + // media volume + profile.setStreamSettings(new StreamSettings(AudioManager.STREAM_MUSIC, + am.getStreamVolume(AudioManager.STREAM_MUSIC), true)); + + // ringtone volume + profile.setStreamSettings(new StreamSettings(AudioManager.STREAM_RING, + am.getStreamVolume(AudioManager.STREAM_RING), true)); + + // notification volume + profile.setStreamSettings(new StreamSettings(AudioManager.STREAM_NOTIFICATION, + am.getStreamVolume(AudioManager.STREAM_NOTIFICATION), true)); + + // ring mode + String ringValue; + switch (am.getRingerMode()) { + default: + case AudioManager.RINGER_MODE_NORMAL: + ringValue = "normal"; + break; + case AudioManager.RINGER_MODE_SILENT: + ringValue = "mute"; + break; + case AudioManager.RINGER_MODE_VIBRATE: + ringValue = "vibrate"; + break; + } + profile.setRingMode(new RingModeSettings(ringValue, true)); + + // airplane mode + boolean airplaneMode = Settings.Global.getInt(context.getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, 0) != 0; + profile.setAirplaneMode(new AirplaneModeSettings(airplaneMode ? 1 : 0, true)); + + // lock screen mode + // populated only from profiles, so we can read the current profile, + // but let's skip this one + } + + @Override + public Dialog onCreateDialog(int dialogId) { + switch (dialogId) { + case DIALOG_FILL_FROM_SETTINGS: + return requestFillProfileFromSettingsDialog(); + + case DIALOG_AIRPLANE_MODE: + return requestAirplaneModeDialog(((AirplaneModeItem) mSelectedItem).getSettings()); + + case DIALOG_BRIGHTNESS: + return requestBrightnessDialog(((BrightnessItem) mSelectedItem).getSettings()); + + case DIALOG_LOCK_MODE: + return requestLockscreenModeDialog(); + + case DIALOG_DOZE_MODE: + return requestDozeModeDialog(); + + case DIALOG_NOTIFICATION_LIGHT_MODE: + return requestNotificationLightModeDialog(); + + case DIALOG_RING_MODE: + return requestRingModeDialog(((RingModeItem) mSelectedItem).getSettings()); + + case DIALOG_CONNECTION_OVERRIDE: + ConnectionOverrideItem connItem = (ConnectionOverrideItem) mSelectedItem; + return requestConnectionOverrideDialog(connItem.getSettings()); + + case DIALOG_VOLUME_STREAM: + VolumeStreamItem volumeItem = (VolumeStreamItem) mSelectedItem; + return requestVolumeDialog(volumeItem.getStreamType(), volumeItem.getSettings()); + + case DIALOG_PROFILE_NAME: + return requestProfileName(); + + case DIALOG_REMOVE_PROFILE: + return requestRemoveProfileDialog(); + + } + return super.onCreateDialog(dialogId); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + if (mLastSelectedPosition != -1) { + outState.putInt(LAST_SELECTED_POSITION, mLastSelectedPosition); + } + } + + private AlertDialog requestRemoveProfileDialog() { + Profile current = mProfileManager.getActiveProfile(); + if (mProfile.getUuid().equals(current.getUuid())) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setMessage(getString(R.string.profile_remove_current_profile)); + builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }); + return builder.create(); + } + + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setMessage(getString(R.string.profile_remove_dialog_message, mProfile.getName())); + builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + mProfileManager.removeProfile(mProfile); + finishFragment(); + } + }); + builder.setNegativeButton(R.string.no, null); + return builder.create(); + } + + private AlertDialog requestLockscreenModeDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + final String[] lockEntries = + getResources().getStringArray(R.array.profile_lockmode_entries); + + int defaultIndex = 0; // no action + for (int i = 0; i < LOCKMODE_MAPPING.length; i++) { + if (LOCKMODE_MAPPING[i] == mProfile.getScreenLockMode().getValue()) { + defaultIndex = i; + break; + } + } + + builder.setTitle(R.string.profile_lockmode_title); + builder.setSingleChoiceItems(lockEntries, defaultIndex, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int item) { + mProfile.setScreenLockMode(new LockSettings(LOCKMODE_MAPPING[item])); + updateProfile(); + mAdapter.notifyDataSetChanged(); + dialog.dismiss(); + } + }); + + builder.setNegativeButton(android.R.string.cancel, null); + return builder.create(); + } + + private AlertDialog requestDozeModeDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + final String[] dozeEntries = + getResources().getStringArray(R.array.profile_doze_entries); + + int defaultIndex = 0; // no action + for (int i = 0; i < DOZE_MAPPING.length; i++) { + if (DOZE_MAPPING[i] == mProfile.getDozeMode()) { + defaultIndex = i; + break; + } + } + + builder.setTitle(R.string.doze_title); + builder.setSingleChoiceItems(dozeEntries, defaultIndex, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int item) { + mProfile.setDozeMode(DOZE_MAPPING[item]); + updateProfile(); + mAdapter.notifyDataSetChanged(); + dialog.dismiss(); + } + }); + + builder.setNegativeButton(android.R.string.cancel, null); + return builder.create(); + } + + private AlertDialog requestNotificationLightModeDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + final String[] notificationLightEntries = + getResources().getStringArray(R.array.profile_notification_light_entries); + + int defaultIndex = 0; // no action + for (int i = 0; i < NOTIFICATION_LIGHT_MAPPING.length; i++) { + if (NOTIFICATION_LIGHT_MAPPING[i] == mProfile.getNotificationLightMode()) { + defaultIndex = i; + break; + } + } + + builder.setTitle(R.string.notification_light_title); + builder.setSingleChoiceItems(notificationLightEntries, defaultIndex, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int item) { + mProfile.setNotificationLightMode(NOTIFICATION_LIGHT_MAPPING[item]); + updateProfile(); + mAdapter.notifyDataSetChanged(); + dialog.dismiss(); + } + }); + + builder.setNegativeButton(android.R.string.cancel, null); + return builder.create(); + } + + private AlertDialog requestAirplaneModeDialog(final AirplaneModeSettings setting) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + final String[] connectionNames = + getResources().getStringArray(R.array.profile_action_generic_connection_entries); + + int defaultIndex = 0; // no action + if (setting.isOverride()) { + if (setting.getValue() == 1) { + defaultIndex = 2; // enabled + } else { + defaultIndex = 1; // disabled + } + } + + builder.setTitle(R.string.profile_airplanemode_title); + builder.setSingleChoiceItems(connectionNames, defaultIndex, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int item) { + switch (item) { + case 0: // disable override + setting.setOverride(false); + break; + case 1: // enable override, disable + setting.setOverride(true); + setting.setValue(0); + break; + case 2: // enable override, enable + setting.setOverride(true); + setting.setValue(1); + break; + } + mProfile.setAirplaneMode(setting); + mAdapter.notifyDataSetChanged(); + updateProfile(); + dialog.dismiss(); + } + }); + + builder.setNegativeButton(android.R.string.cancel, null); + return builder.create(); + } + + private void requestProfileRingMode() { + // Launch the ringtone picker + Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, false); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_RINGTONE); + startActivityForResult(intent, RINGTONE_REQUEST_CODE); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == NEW_TRIGGER_REQUEST_CODE) { + mProfile = mProfileManager.getProfile(mProfile.getUuid()); + rebuildItemList(); + + } else if (requestCode == SET_NETWORK_MODE_REQUEST_CODE + && resultCode == Activity.RESULT_OK) { + + int selectedMode = Integer.parseInt(data.getStringExtra( + TelephonyUtils.EXTRA_NETWORK_PICKER_PICKED_VALUE)); + int subId = data.getIntExtra(TelephonyUtils.EXTRA_SUBID, + SubscriptionManager.getDefaultDataSubId()); + ConnectionOverrideItem connItem = (ConnectionOverrideItem) mSelectedItem; + final ConnectionSettings setting = connItem.getSettings(); +// final ConnectionSettings setting = mProfile.getConnectionSettingWithSubId(subId); + + switch (selectedMode) { + case ConnectionOverrideItem.CM_MODE_SYSTEM_DEFAULT: + setting.setOverride(false); + break; + default: + setting.setOverride(true); + setting.setValue(selectedMode); + } + mProfile.setConnectionSettings(setting); + mAdapter.notifyDataSetChanged(); + updateProfile(); + } + } + + private AlertDialog requestRingModeDialog(final RingModeSettings setting) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + final String[] values = getResources().getStringArray(R.array.ring_mode_values); + final String[] names = getResources().getStringArray(R.array.ring_mode_entries); + + int defaultIndex = 0; // normal by default + if (setting.isOverride()) { + if (setting.getValue().equals(values[0] /* normal */)) { + defaultIndex = 0; + } else if (setting.getValue().equals(values[1] /* vibrate */)) { + defaultIndex = 1; // enabled + } else if (setting.getValue().equals(values[2] /* mute */)) { + defaultIndex = 2; // mute + } + } else { + defaultIndex = 3; + } + + builder.setTitle(R.string.ring_mode_title); + builder.setSingleChoiceItems(names, defaultIndex, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int item) { + switch (item) { + case 0: // enable override, normal + setting.setOverride(true); + setting.setValue(values[0]); + break; + case 1: // enable override, vibrate + setting.setOverride(true); + setting.setValue(values[1]); + break; + case 2: // enable override, mute + setting.setOverride(true); + setting.setValue(values[2]); + break; + case 3: + setting.setOverride(false); + break; + } + mProfile.setRingMode(setting); + mAdapter.notifyDataSetChanged(); + updateProfile(); + dialog.dismiss(); + } + }); + + builder.setNegativeButton(android.R.string.cancel, null); + return builder.create(); + } + + private AlertDialog requestConnectionOverrideDialog(final ConnectionSettings setting) { + if (setting == null) { + throw new UnsupportedOperationException("connection setting cannot be null"); + } + if (setting.getConnectionId() == PROFILE_CONNECTION_2G3G4G) { + throw new UnsupportedOperationException("dialog must be requested from Telephony"); + } + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + final String[] connectionNames = + getResources().getStringArray(R.array.profile_action_generic_connection_entries); + + int defaultIndex = 0; // no action + if (setting.isOverride()) { + if (setting.getValue() == 1) { + defaultIndex = 2; // enabled + } else { + defaultIndex = 1; // disabled + } + } + + builder.setTitle(ConnectionOverrideItem.getConnectionTitle(getContext(), setting)); + builder.setSingleChoiceItems(connectionNames, defaultIndex, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int item) { + switch (item) { + case 0: // disable override + setting.setOverride(false); + break; + case 1: // enable override, disable + setting.setOverride(true); + setting.setValue(0); + break; + case 2: // enable override, enable + setting.setOverride(true); + setting.setValue(1); + break; + } + mProfile.setConnectionSettings(setting); + mAdapter.notifyDataSetChanged(); + updateProfile(); + dialog.dismiss(); + } + }); + + builder.setNegativeButton(android.R.string.cancel, null); + return builder.create(); + } + + public AlertDialog requestVolumeDialog(int streamId, + final StreamSettings streamSettings) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setTitle(VolumeStreamItem.getNameForStream(streamId)); + + final AudioManager am = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE); + final LayoutInflater inflater = LayoutInflater.from(getActivity()); + final View view = inflater.inflate(R.layout.dialog_profiles_volume_override, null); + final SeekBar seekBar = (SeekBar) view.findViewById(R.id.seekbar); + final CheckBox override = (CheckBox) view.findViewById(R.id.checkbox); + override.setChecked(streamSettings.isOverride()); + override.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + seekBar.setEnabled(isChecked); + } + }); + final SeekBarVolumizer volumizer = new SeekBarVolumizer(getActivity(), streamId, null, + null); + volumizer.start(); + volumizer.setSeekBar(seekBar); + seekBar.setEnabled(streamSettings.isOverride()); + + builder.setView(view); + builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + int value = seekBar.getProgress(); + streamSettings.setOverride(override.isChecked()); + streamSettings.setValue(value); + mProfile.setStreamSettings(streamSettings); + mAdapter.notifyDataSetChanged(); + updateProfile(); + } + }); + builder.setNegativeButton(android.R.string.cancel, null); + setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialogInterface) { + if (volumizer != null) { + volumizer.stop(); + } + setOnDismissListener(null); // re-set this for next dialog + } + }); + return builder.create(); + } + + public AlertDialog requestBrightnessDialog(final BrightnessSettings brightnessSettings) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setTitle(R.string.profile_brightness_title); + + final LayoutInflater inflater = LayoutInflater.from(getActivity()); + final View view = inflater.inflate(R.layout.dialog_profiles_brightness_override, null); + final SeekBar seekBar = (SeekBar) view.findViewById(R.id.seekbar); + final CheckBox override = (CheckBox) view.findViewById(R.id.checkbox); + override.setChecked(brightnessSettings.isOverride()); + override.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + seekBar.setEnabled(isChecked); + } + }); + seekBar.setEnabled(brightnessSettings.isOverride()); + seekBar.setMax(255); + seekBar.setProgress(brightnessSettings.getValue()); + builder.setView(view); + builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + int value = seekBar.getProgress(); + brightnessSettings.setValue(value); + brightnessSettings.setOverride(override.isChecked()); + mProfile.setBrightness(brightnessSettings); + mAdapter.notifyDataSetChanged(); + updateProfile(); + dialog.dismiss(); + } + }); + builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }); + return builder.create(); + } + + private AlertDialog requestProfileName() { + LayoutInflater inflater = LayoutInflater.from(getActivity()); + View dialogView = inflater.inflate(R.layout.profile_name_dialog, null); + + final EditText entry = (EditText) dialogView.findViewById(R.id.name); + entry.setText(mProfile.getName()); + entry.setSelectAllOnFocus(true); + + final AlertDialog alertDialog = new AlertDialog.Builder(getActivity()) + .setTitle(R.string.rename_dialog_title) + .setView(dialogView) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String value = entry.getText().toString(); + mProfile.setName(value); + mAdapter.notifyDataSetChanged(); + updateProfile(); + } + }) + .setNegativeButton(android.R.string.cancel, null) + .create(); + + entry.addTextChangedListener(new TextWatcher() { + @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 afterTextChanged(Editable s) { + final String str = s.toString(); + final boolean empty = TextUtils.isEmpty(str) + || TextUtils.getTrimmedLength(str) == 0; + alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(!empty); + } + }); + alertDialog.setOnShowListener(new DialogInterface.OnShowListener() { + @Override + public void onShow(DialogInterface dialog) { + InputMethodManager imm = (InputMethodManager) + getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(entry, InputMethodManager.SHOW_IMPLICIT); + } + }); + return alertDialog; + } + + private void requestActiveAppGroupsDialog() { + final NotificationGroup[] notificationGroups = mProfileManager.getNotificationGroups(); + + CharSequence[] items = new CharSequence[notificationGroups.length]; + boolean[] checked = new boolean[notificationGroups.length]; + + for (int i = 0; i < notificationGroups.length; i++) { + items[i] = notificationGroups[i].getName(); + checked[i] = mProfile.getProfileGroup(notificationGroups[i].getUuid()) != null; + } + DialogInterface.OnMultiChoiceClickListener listener = + new DialogInterface.OnMultiChoiceClickListener() { + @Override + public void onClick(DialogInterface dialog, int which, boolean isChecked) { + if (isChecked) { + mProfile.addProfileGroup(new ProfileGroup(notificationGroups[which].getUuid(), false)); + } else { + mProfile.removeProfileGroup(notificationGroups[which].getUuid()); + } + updateProfile(); + rebuildItemList(); + } + }; + + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) + .setMultiChoiceItems(items, checked, listener) + .setTitle(R.string.profile_appgroups_title) + .setPositiveButton(android.R.string.ok, null); + builder.show(); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_setup_actions, container, false); + + mListView = (ListView) view.findViewById(android.R.id.list); + mListView.setOnItemClickListener(this); + if (mNewProfileMode) { + view.findViewById(R.id.back).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + getActivity().setResult(Activity.RESULT_CANCELED); + finishFragment(); + } + }); + + view.findViewById(R.id.finish).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + mProfileManager.addProfile(mProfile); + + getActivity().setResult(Activity.RESULT_OK); + finishFragment(); + } + }); + } else { + view.findViewById(R.id.bottom_buttons).setVisibility(View.GONE); + } + return view; + } + + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + final Item itemAtPosition = (Item) parent.getItemAtPosition(position); + mSelectedItem = itemAtPosition; + mLastSelectedPosition = mAdapter.getPosition(itemAtPosition); + + if (itemAtPosition instanceof AirplaneModeItem) { + showDialog(DIALOG_AIRPLANE_MODE); + } else if (itemAtPosition instanceof BrightnessItem) { + showDialog(DIALOG_BRIGHTNESS); + } else if (itemAtPosition instanceof LockModeItem) { + showDialog(DIALOG_LOCK_MODE); + } else if (itemAtPosition instanceof DozeModeItem) { + showDialog(DIALOG_DOZE_MODE); + } else if (itemAtPosition instanceof NotificationLightModeItem) { + showDialog(DIALOG_NOTIFICATION_LIGHT_MODE); + } else if (itemAtPosition instanceof RingModeItem) { + showDialog(DIALOG_RING_MODE); + } else if (itemAtPosition instanceof ConnectionOverrideItem) { + + ConnectionOverrideItem connItem = (ConnectionOverrideItem) mSelectedItem; + if (connItem.getConnectionType() == ConnectionSettings.PROFILE_CONNECTION_2G3G4G) { + final Intent intent = new Intent(TelephonyUtils.ACTION_PICK_NETWORK_MODE); + intent.putExtra(TelephonyUtils.EXTRA_NONE_TEXT, + getString(R.string.profile_action_none)); + intent.putExtra(TelephonyUtils.EXTRA_SHOW_NONE, true); + intent.putExtra(TelephonyUtils.EXTRA_SUBID, connItem.getSettings().getSubId()); + intent.putExtra(TelephonyUtils.EXTRA_INITIAL_NETWORK_VALUE, + connItem.getSettings().isOverride() + ? connItem.getSettings().getValue() : -1); + startActivityForResult(intent, SET_NETWORK_MODE_REQUEST_CODE); + } else { + showDialog(DIALOG_CONNECTION_OVERRIDE); + } + } else if (itemAtPosition instanceof VolumeStreamItem) { + showDialog(DIALOG_VOLUME_STREAM); + } else if (itemAtPosition instanceof ProfileNameItem) { + showDialog(DIALOG_PROFILE_NAME); + } else if (itemAtPosition instanceof TriggerItem) { + TriggerItem item = (TriggerItem) itemAtPosition; + openTriggersFragment(item.getTriggerType()); + } else if (itemAtPosition instanceof AppGroupItem) { + AppGroupItem item = (AppGroupItem) itemAtPosition; + if (item.getGroupUuid() == null) { + requestActiveAppGroupsDialog(); + } else { + startProfileGroupActivity(item); + } + } + } + + private void startProfileGroupActivity(AppGroupItem item) { + Bundle args = new Bundle(); + args.putString("ProfileGroup", item.getGroupUuid().toString()); + args.putParcelable("Profile", mProfile); + + startFragment(this, ProfileGroupConfig.class.getName(), 0, 0, args); + } + + private void openTriggersFragment(int openTo) { + Bundle args = new Bundle(); + args.putParcelable(ProfilesSettings.EXTRA_PROFILE, mProfile); + args.putBoolean(ProfilesSettings.EXTRA_NEW_PROFILE, false); + args.putInt(SetupTriggersFragment.EXTRA_INITIAL_PAGE, openTo); + + SubSettings pa = (SubSettings) getActivity(); + pa.startPreferencePanel(SetupTriggersFragment.class.getCanonicalName(), args, + R.string.profile_profile_manage, null, this, NEW_TRIGGER_REQUEST_CODE); + } + + @Override + protected int getMetricsCategory() { + return CMMetricsLogger.SETUP_ACTIONS_FRAGMENT; + } +} diff --git a/src/com/android/settings/profiles/SetupDefaultProfileReceiver.java b/src/com/android/settings/profiles/SetupDefaultProfileReceiver.java new file mode 100644 index 0000000..fc45cce --- /dev/null +++ b/src/com/android/settings/profiles/SetupDefaultProfileReceiver.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2015 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.profiles; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import cyanogenmod.app.Profile; +import cyanogenmod.app.ProfileManager; +import cyanogenmod.providers.CMSettings; + +import java.util.UUID; + +public class SetupDefaultProfileReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + if (CMSettings.System.getInt(context.getContentResolver(), + CMSettings.System.SYSTEM_PROFILES_ENABLED, 1) == 1) { + ProfileManager profileManager = ProfileManager.getInstance(context); + Profile defaultProfile = profileManager.getProfile( + UUID.fromString("0230226d-0d05-494a-a9bd-d222a1117655")); + if (defaultProfile != null) { + SetupActionsFragment.fillProfileWithCurrentSettings(context, defaultProfile); + profileManager.updateProfile(defaultProfile); + } + } + } +} diff --git a/src/com/android/settings/profiles/SetupTriggersFragment.java b/src/com/android/settings/profiles/SetupTriggersFragment.java new file mode 100644 index 0000000..ffb6146 --- /dev/null +++ b/src/com/android/settings/profiles/SetupTriggersFragment.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.profiles; + +import android.annotation.Nullable; +import android.app.ActionBar; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.support.v4.view.PagerTabStrip; +import android.support.v4.view.ViewPager; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; + +import cyanogenmod.app.Profile; +import cyanogenmod.app.ProfileManager; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.SubSettings; +import com.android.settings.profiles.triggers.NfcTriggerFragment; +import org.cyanogenmod.internal.logging.CMMetricsLogger; + +public class SetupTriggersFragment extends SettingsPreferenceFragment { + + ViewPager mPager; + Profile mProfile; + ProfileManager mProfileManager; + TriggerPagerAdapter mAdapter; + boolean mNewProfileMode; + int mPreselectedItem; + + public static final String EXTRA_INITIAL_PAGE = "current_item"; + + private static final int REQUEST_SETUP_ACTIONS = 5; + + public static SetupTriggersFragment newInstance(Profile profile, boolean newProfile) { + SetupTriggersFragment fragment = new SetupTriggersFragment(); + Bundle args = new Bundle(); + args.putParcelable(ProfilesSettings.EXTRA_PROFILE, profile); + args.putBoolean(ProfilesSettings.EXTRA_NEW_PROFILE, newProfile); + + fragment.setArguments(args); + return fragment; + } + + public SetupTriggersFragment() { + // Required empty public constructor + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + mProfile = getArguments().getParcelable(ProfilesSettings.EXTRA_PROFILE); + mNewProfileMode = getArguments().getBoolean(ProfilesSettings.EXTRA_NEW_PROFILE, false); + mPreselectedItem = getArguments().getInt(EXTRA_INITIAL_PAGE, 0); + } + mProfileManager = ProfileManager.getInstance(getActivity()); + } + + @Override + protected int getMetricsCategory() { + return CMMetricsLogger.SETUP_TRIGGERS_FRAGMENT; + } + + @Override + public void onResume() { + super.onResume(); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + final ActionBar actionBar = getActivity().getActionBar(); + if (mNewProfileMode) { + actionBar.setTitle(R.string.profile_setup_setup_triggers_title); + } else { + String title = getString(R.string.profile_setup_setup_triggers_title_config, + mProfile.getName()); + actionBar.setTitle(title); + } + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mPager.setCurrentItem(mPreselectedItem); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + View root = inflater.inflate(R.layout.fragment_setup_triggers, container, false); + + mPager = (ViewPager) root.findViewById(R.id.view_pager); + mAdapter = new TriggerPagerAdapter(getActivity(), getChildFragmentManager()); + + Bundle profileArgs = new Bundle(); + profileArgs.putParcelable(ProfilesSettings.EXTRA_PROFILE, mProfile); + + final TriggerPagerAdapter.TriggerFragments[] fragments = + TriggerPagerAdapter.TriggerFragments.values(); + + for (final TriggerPagerAdapter.TriggerFragments fragment : fragments) { + if (fragment.getFragmentClass() == NfcTriggerFragment.class) { + if (!getActivity().getPackageManager().hasSystemFeature( + PackageManager.FEATURE_NFC)) { + // device doesn't have NFC + continue; + } + } + mAdapter.add(fragment.getFragmentClass(), profileArgs, fragment.getTitleRes()); + } + + mPager.setAdapter(mAdapter); + + PagerTabStrip tabs = (PagerTabStrip) root.findViewById(R.id.tabs); + tabs.setTabIndicatorColorResource(R.color.theme_accent); + + if (mNewProfileMode) { + Button nextButton = (Button) root.findViewById(R.id.next); + nextButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Bundle args = new Bundle(); + args.putParcelable(ProfilesSettings.EXTRA_PROFILE, mProfile); + args.putBoolean(ProfilesSettings.EXTRA_NEW_PROFILE, mNewProfileMode); + + SubSettings pa = (SubSettings) getActivity(); + pa.startPreferencePanel(SetupActionsFragment.class.getCanonicalName(), args, + R.string.profile_profile_manage, null, + SetupTriggersFragment.this, REQUEST_SETUP_ACTIONS); + } + }); + + // back button + root.findViewById(R.id.back).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + finishFragment(); + } + }); + } else { + root.findViewById(R.id.bottom_buttons).setVisibility(View.GONE); + } + return root; + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == REQUEST_SETUP_ACTIONS) { + if (resultCode == Activity.RESULT_OK) { + // exit out of the wizard! + finishFragment(); + } + } + } + + +} diff --git a/src/com/android/settings/profiles/TriggerPagerAdapter.java b/src/com/android/settings/profiles/TriggerPagerAdapter.java new file mode 100644 index 0000000..046bf49 --- /dev/null +++ b/src/com/android/settings/profiles/TriggerPagerAdapter.java @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.profiles; + +import android.app.Activity; +import android.app.Fragment; +import android.app.FragmentManager; +import android.os.Bundle; +import android.support.v13.app.FragmentPagerAdapter; +import android.util.SparseArray; +import android.view.ViewGroup; + +import com.android.settings.R; +import com.android.settings.profiles.triggers.BluetoothTriggerFragment; +import com.android.settings.profiles.triggers.NfcTriggerFragment; +import com.android.settings.profiles.triggers.WifiTriggerFragment; +import com.google.android.collect.Lists; + +import java.lang.ref.WeakReference; +import java.util.List; + +/** + * A {@link android.support.v4.app.FragmentPagerAdapter} class for swiping between playlists, recent, + * artists, albums, songs, and genre {@link android.support.v4.app.Fragment}s on phones.<br/> + */ +public class TriggerPagerAdapter extends FragmentPagerAdapter { + + private final SparseArray<WeakReference<Fragment>> mFragmentArray = + new SparseArray<WeakReference<Fragment>>(); + + private final List<Holder> mHolderList = Lists.newArrayList(); + + private final Activity mFragmentActivity; + + private int mCurrentPage; + + /** + * Constructor of <code>PagerAdatper<code> + * + * @param activity The {@link android.support.v4.app.FragmentActivity} of the + * {@link android.support.v4.app.Fragment}. + * @param fm the FragmentManager to use. + */ + public TriggerPagerAdapter(Activity activity, FragmentManager fm) { + super(fm); + mFragmentActivity = activity; + } + + /** + * Method that adds a new fragment class to the viewer (the fragment is + * internally instantiate) + * + * @param className The full qualified name of fragment class. + * @param params The instantiate params. + */ + @SuppressWarnings("synthetic-access") + public void add(final Class<? extends Fragment> className, final Bundle params, + final int titleResId) { + final Holder mHolder = new Holder(); + mHolder.mClassName = className.getName(); + mHolder.mParams = params; + mHolder.mTitleResId = titleResId; + + final int mPosition = mHolderList.size(); + mHolderList.add(mPosition, mHolder); + notifyDataSetChanged(); + } + + /** + * Method that returns the {@link android.support.v4.app.Fragment} in the argument + * position. + * + * @param position The position of the fragment to return. + * @return Fragment The {@link android.support.v4.app.Fragment} in the argument position. + */ + public Fragment getFragment(final int position) { + final WeakReference<Fragment> mWeakFragment = mFragmentArray.get(position); + if (mWeakFragment != null && mWeakFragment.get() != null) { + return mWeakFragment.get(); + } + return getItem(position); + } + + /** + * {@inheritDoc} + */ + @Override + public Object instantiateItem(final ViewGroup container, final int position) { + final Fragment mFragment = (Fragment)super.instantiateItem(container, position); + final WeakReference<Fragment> mWeakFragment = mFragmentArray.get(position); + if (mWeakFragment != null) { + mWeakFragment.clear(); + } + mFragmentArray.put(position, new WeakReference<Fragment>(mFragment)); + return mFragment; + } + + /** + * {@inheritDoc} + */ + @Override + public Fragment getItem(final int position) { + final Holder mCurrentHolder = mHolderList.get(position); + final Fragment mFragment = Fragment.instantiate(mFragmentActivity, + mCurrentHolder.mClassName, mCurrentHolder.mParams); + return mFragment; + } + + /** + * {@inheritDoc} + */ + @Override + public void destroyItem(final ViewGroup container, final int position, final Object object) { + super.destroyItem(container, position, object); + final WeakReference<Fragment> mWeakFragment = mFragmentArray.get(position); + if (mWeakFragment != null) { + mWeakFragment.clear(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public int getCount() { + return mHolderList.size(); + } + + /** + * {@inheritDoc} + */ + @Override + public CharSequence getPageTitle(final int position) { + return mFragmentActivity.getString(mHolderList.get(position).mTitleResId); + } + + /** + * Method that returns the current page position. + * + * @return int The current page. + */ + public int getCurrentPage() { + return mCurrentPage; + } + + /** + * Method that sets the current page position. + * + * @param currentPage The current page. + */ + protected void setCurrentPage(final int currentPage) { + mCurrentPage = currentPage; + } + + /** + * An enumeration of all the main fragments supported. + */ + public enum TriggerFragments { + /** + * The artist fragment + */ + WIFI(WifiTriggerFragment.class, R.string.profile_tabs_wifi), + /** + * The album fragment + */ + BLUETOOTH(BluetoothTriggerFragment.class, R.string.profile_tabs_bluetooth), + /** + * The song fragment + */ + NFC(NfcTriggerFragment.class, R.string.profile_tabs_nfc); + + private Class<? extends Fragment> mFragmentClass; + private int mNameRes; + + /** + * Constructor of <code>MusicFragments</code> + * + * @param fragmentClass The fragment class + */ + private TriggerFragments(final Class<? extends Fragment> fragmentClass, int nameRes) { + mFragmentClass = fragmentClass; + mNameRes = nameRes; + } + + /** + * Method that returns the fragment class. + * + * @return Class<? extends Fragment> The fragment class. + */ + public Class<? extends Fragment> getFragmentClass() { + return mFragmentClass; + } + + public int getTitleRes() { return mNameRes; } + } + + /** + * A private class with information about fragment initialization + */ + private final static class Holder { + String mClassName; + int mTitleResId; + Bundle mParams; + } +} diff --git a/src/com/android/settings/profiles/actions/ItemListAdapter.java b/src/com/android/settings/profiles/actions/ItemListAdapter.java new file mode 100644 index 0000000..79cf22c --- /dev/null +++ b/src/com/android/settings/profiles/actions/ItemListAdapter.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.profiles.actions; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; + +import com.android.settings.profiles.actions.item.Item; + +import java.util.List; + +public class ItemListAdapter extends ArrayAdapter<Item> { + private LayoutInflater mInflater; + + public enum RowType { + HEADER_ITEM, + DISABLED_ITEM, + CONNECTION_ITEM, + VOLUME_STREAM_ITEM, + NAME_ITEM, + RINGMODE_ITEM, + AIRPLANEMODE_ITEM, + LOCKSCREENMODE_ITEM, + TRIGGER_ITEM, + APP_GROUP_ITEM, + BRIGHTNESS_ITEM, + DOZEMODE_ITEM, + NOTIFICATIONLIGHTMODE_ITEM + } + + public ItemListAdapter(Context context, List<Item> items) { + super(context, 0, items); + mInflater = LayoutInflater.from(context); + } + + @Override + public int getViewTypeCount() { + return RowType.values().length; + + } + + @Override + public int getItemViewType(int position) { + return getItem(position).getRowType().ordinal(); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + return getItem(position).getView(mInflater, convertView, parent); + } + + @Override + public boolean areAllItemsEnabled() { + return false; + } + + @Override + public boolean isEnabled(int position) { + return getItem(position).isEnabled(); + } +} diff --git a/src/com/android/settings/profiles/actions/item/AirplaneModeItem.java b/src/com/android/settings/profiles/actions/item/AirplaneModeItem.java new file mode 100644 index 0000000..fbd9620 --- /dev/null +++ b/src/com/android/settings/profiles/actions/item/AirplaneModeItem.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.profiles.actions.item; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import cyanogenmod.profiles.AirplaneModeSettings; + +import com.android.settings.R; +import com.android.settings.profiles.actions.ItemListAdapter; + +public class AirplaneModeItem implements Item { + AirplaneModeSettings mSettings; + + public AirplaneModeItem(AirplaneModeSettings airplaneModeSettings) { + if (airplaneModeSettings == null) { + airplaneModeSettings = new AirplaneModeSettings(); + } + mSettings = airplaneModeSettings; + } + + @Override + public ItemListAdapter.RowType getRowType() { + return ItemListAdapter.RowType.AIRPLANEMODE_ITEM; + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public View getView(LayoutInflater inflater, View convertView, ViewGroup parent) { + View view; + if (convertView == null) { + view = inflater.inflate(R.layout.list_two_line_item, parent, false); + // Do some initialization + } else { + view = convertView; + } + + TextView text = (TextView) view.findViewById(R.id.title); + text.setText(R.string.profile_airplanemode_title); + + TextView desc = (TextView) view.findViewById(R.id.summary); + desc.setText(getModeString(mSettings)); + + return view; + } + + public AirplaneModeSettings getSettings() { + return mSettings; + } + + public static int getModeString(AirplaneModeSettings settings) { + if (settings.isOverride()) { + if (settings.getValue() == 1) { + return R.string.profile_action_enable; + } else { + return R.string.profile_action_disable; + } + } else { + return R.string.profile_action_none; + } + } +} diff --git a/src/com/android/settings/profiles/actions/item/AppGroupItem.java b/src/com/android/settings/profiles/actions/item/AppGroupItem.java new file mode 100644 index 0000000..5558900 --- /dev/null +++ b/src/com/android/settings/profiles/actions/item/AppGroupItem.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.profiles.actions.item; + +import android.app.NotificationGroup; +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import cyanogenmod.app.Profile; +import cyanogenmod.app.ProfileGroup; +import cyanogenmod.app.ProfileManager; + +import com.android.settings.R; +import com.android.settings.profiles.actions.ItemListAdapter; + +import java.util.UUID; + +public class AppGroupItem implements Item { + Profile mProfile; + ProfileGroup mGroup; + NotificationGroup mNotifGroup; + + public AppGroupItem() { + // empty app group will act as a "Add/remove app groups" item + } + + public AppGroupItem(Profile profile, ProfileGroup group, NotificationGroup nGroup) { + mProfile = profile; + if (group == null) { + throw new UnsupportedOperationException("profile group can't be null"); + } + mGroup = group; + mNotifGroup = nGroup; + } + + @Override + public ItemListAdapter.RowType getRowType() { + return ItemListAdapter.RowType.APP_GROUP_ITEM; + } + + @Override + public boolean isEnabled() { + return true; + } + + public UUID getGroupUuid() { + if (mGroup != null) { + return mGroup.getUuid(); + } + return null; + } + + @Override + public View getView(LayoutInflater inflater, View convertView, ViewGroup parent) { + View view; + if (convertView == null) { + view = inflater.inflate(R.layout.list_two_line_item, parent, false); + } else { + view = convertView; + } + TextView text = (TextView) view.findViewById(R.id.title); + TextView desc = (TextView) view.findViewById(R.id.summary); + + if (mGroup != null) { + if (mNotifGroup != null) { + text.setText(mNotifGroup.getName()); + } else { + text.setText("<unknown>"); + } + desc.setVisibility(View.GONE); + } else { + text.setText(R.string.profile_app_group_item_instructions); + desc.setText(R.string.profile_app_group_item_instructions_summary); + + desc.setVisibility(View.VISIBLE); + } + + return view; + } +} diff --git a/src/com/android/settings/profiles/actions/item/BrightnessItem.java b/src/com/android/settings/profiles/actions/item/BrightnessItem.java new file mode 100644 index 0000000..49f0ec9 --- /dev/null +++ b/src/com/android/settings/profiles/actions/item/BrightnessItem.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2015 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.profiles.actions.item; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import cyanogenmod.profiles.BrightnessSettings; + +import com.android.settings.R; +import com.android.settings.profiles.actions.ItemListAdapter; + +public class BrightnessItem implements Item { + BrightnessSettings mSettings; + + public BrightnessItem(BrightnessSettings brightnessSettings) { + if (brightnessSettings == null) { + brightnessSettings = new BrightnessSettings(); + } + mSettings = brightnessSettings; + } + + @Override + public ItemListAdapter.RowType getRowType() { + return ItemListAdapter.RowType.BRIGHTNESS_ITEM; + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public View getView(LayoutInflater inflater, View convertView, ViewGroup parent) { + View view; + if (convertView == null) { + view = inflater.inflate(R.layout.list_two_line_item, parent, false); + // Do some initialization + } else { + view = convertView; + } + + TextView text = (TextView) view.findViewById(R.id.title); + text.setText(R.string.profile_brightness_title); + + Context context = inflater.getContext(); + TextView desc = (TextView) view.findViewById(R.id.summary); + if (mSettings.isOverride()) { + desc.setText(context.getResources().getString( + R.string.profile_brightness_override_summary, + (int)((mSettings.getValue() * 100f)/255))); + } else { + desc.setText(context.getString(R.string.profile_action_none)); + } + + return view; + } + + public BrightnessSettings getSettings() { + return mSettings; + } +} diff --git a/src/com/android/settings/profiles/actions/item/ConnectionOverrideItem.java b/src/com/android/settings/profiles/actions/item/ConnectionOverrideItem.java new file mode 100644 index 0000000..00c8542 --- /dev/null +++ b/src/com/android/settings/profiles/actions/item/ConnectionOverrideItem.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.profiles.actions.item; + +import android.content.Context; +import android.telephony.SubscriptionManager; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import cyanogenmod.profiles.ConnectionSettings; + +import com.android.settings.R; +import com.android.settings.profiles.actions.ItemListAdapter; + +import com.android.settings.utils.TelephonyUtils; + +public class ConnectionOverrideItem implements Item { + int mConnectionId; + ConnectionSettings mConnectionSettings; + + public static final int CM_MODE_SYSTEM_DEFAULT = -1; + + public ConnectionOverrideItem(int connectionId, ConnectionSettings settings) { + mConnectionId = connectionId; + if (settings == null) { + settings = new ConnectionSettings(connectionId); + } + this.mConnectionSettings = settings; + } + + @Override + public ItemListAdapter.RowType getRowType() { + return ItemListAdapter.RowType.CONNECTION_ITEM; + } + + @Override + public View getView(LayoutInflater inflater, View convertView, ViewGroup parent) { + View view; + if (convertView == null) { + view = inflater.inflate(R.layout.list_two_line_item, parent, false); + // Do some initialization + } else { + view = convertView; + } + + TextView title = (TextView) view.findViewById(R.id.title); + TextView summary = (TextView) view.findViewById(R.id.summary); + + title.setText(getConnectionTitle(view.getContext(), mConnectionSettings)); + summary.setText(getSummary(view.getContext())); + + return view; + } + + @Override + public boolean isEnabled() { + return true; + } + + public static String getConnectionTitle(Context context, ConnectionSettings settings) { + int r = 0; + switch (settings.getConnectionId()) { + case ConnectionSettings.PROFILE_CONNECTION_BLUETOOTH: + r = R.string.toggleBluetooth; + break; + case ConnectionSettings.PROFILE_CONNECTION_MOBILEDATA: + r =R.string.toggleData; + break; + case ConnectionSettings.PROFILE_CONNECTION_2G3G4G: + if (settings.getSubId() != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { + final String displayName = SubscriptionManager.from(context) + .getActiveSubscriptionInfo(settings.getSubId()) + .getDisplayName() + .toString(); + return context.getString(R.string.toggle2g3g4g_msim, displayName); + } + r = R.string.toggle2g3g4g; + break; + case ConnectionSettings.PROFILE_CONNECTION_GPS: + r = R.string.toggleGPS; + break; + case ConnectionSettings.PROFILE_CONNECTION_NFC: + r = R.string.toggleNfc; + break; + case ConnectionSettings.PROFILE_CONNECTION_SYNC: + r = R.string.toggleSync; + break; + case ConnectionSettings.PROFILE_CONNECTION_WIFI: + r = R.string.toggleWifi; + break; + case ConnectionSettings.PROFILE_CONNECTION_WIFIAP: + r = R.string.toggleWifiAp; + break; + } + return context.getString(r); + } + + public CharSequence getSummary(Context context) { + int resId = -1; + if (mConnectionSettings != null) { + if (mConnectionId == ConnectionSettings.PROFILE_CONNECTION_2G3G4G) { // different options + if (mConnectionSettings.isOverride()) { + return TelephonyUtils.getNetworkModeString(context, + mConnectionSettings.getValue(), SubscriptionManager.getDefaultDataSubId()); + } else { + resId = R.string.profile_action_none; + } + } else if (mConnectionSettings.isOverride()) { // enabled, disabled, or none + if (mConnectionSettings.getValue() == 1) { + resId = R.string.profile_action_enable; + } else { + resId = R.string.profile_action_disable; + } + } else { + resId = R.string.profile_action_none; + } + } else { + resId = R.string.profile_action_none; + } + return context.getString(resId); + } + + public ConnectionSettings getSettings() { + return mConnectionSettings; + } + + public int getConnectionType() { + return mConnectionId; + } +} diff --git a/src/com/android/settings/profiles/actions/item/DisabledItem.java b/src/com/android/settings/profiles/actions/item/DisabledItem.java new file mode 100644 index 0000000..f6328af --- /dev/null +++ b/src/com/android/settings/profiles/actions/item/DisabledItem.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2015 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.profiles.actions.item; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.android.settings.R; +import com.android.settings.profiles.actions.ItemListAdapter; + +public class DisabledItem implements Item { + + private final int mResTitle; + private final int mResSummary; + + public DisabledItem(int resTitle, int resSummary) { + mResTitle = resTitle; + mResSummary = resSummary; + } + + @Override + public ItemListAdapter.RowType getRowType() { + return ItemListAdapter.RowType.DISABLED_ITEM; + } + + @Override + public boolean isEnabled() { + return false; + } + + @Override + public View getView(LayoutInflater inflater, View convertView, ViewGroup parent) { + View view; + if (convertView == null) { + view = inflater.inflate(R.layout.list_two_line_item, parent, false); + // Do some initialization + } else { + view = convertView; + } + + TextView text = (TextView) view.findViewById(R.id.title); + text.setText(mResTitle); + text.setEnabled(false); + + TextView desc = (TextView) view.findViewById(R.id.summary); + desc.setText(mResSummary); + desc.setEnabled(false); + + return view; + } +} diff --git a/src/com/android/settings/profiles/actions/item/DozeModeItem.java b/src/com/android/settings/profiles/actions/item/DozeModeItem.java new file mode 100644 index 0000000..a1918ae --- /dev/null +++ b/src/com/android/settings/profiles/actions/item/DozeModeItem.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.profiles.actions.item; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import cyanogenmod.app.Profile; + +import com.android.settings.R; +import com.android.settings.profiles.actions.ItemListAdapter; + +public class DozeModeItem implements Item { + Profile mProfile; + + public DozeModeItem(Profile profile) { + mProfile = profile; + } + + @Override + public ItemListAdapter.RowType getRowType() { + return ItemListAdapter.RowType.DOZEMODE_ITEM; + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public View getView(LayoutInflater inflater, View convertView, ViewGroup parent) { + View view; + if (convertView == null) { + view = inflater.inflate(R.layout.list_two_line_item, parent, false); + // Do some initialization + } else { + view = convertView; + } + + TextView text = (TextView) view.findViewById(R.id.title); + text.setText(R.string.doze_title); + + TextView desc = (TextView) view.findViewById(R.id.summary); + desc.setText(getSummaryString(mProfile)); + + return view; + } + + public static int getSummaryString(Profile profile) { + switch (profile.getDozeMode()) { + case Profile.DozeMode.DEFAULT: + return R.string.profile_action_none; //"leave unchanged" + case Profile.DozeMode.ENABLE: + return R.string.profile_action_enable; + case Profile.DozeMode.DISABLE: + return R.string.profile_action_disable; + default: return 0; + } + } +} diff --git a/src/com/android/settings/profiles/actions/item/Header.java b/src/com/android/settings/profiles/actions/item/Header.java new file mode 100644 index 0000000..7d08508 --- /dev/null +++ b/src/com/android/settings/profiles/actions/item/Header.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.profiles.actions.item; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.android.settings.R; +import com.android.settings.profiles.actions.ItemListAdapter; + +public class Header implements Item { + private final String name; + + public Header(String name) { + this.name = name; + } + + @Override + public ItemListAdapter.RowType getRowType() { + return ItemListAdapter.RowType.HEADER_ITEM; + } + + @Override + public View getView(LayoutInflater inflater, View convertView, ViewGroup parent) { + View view; + if (convertView == null) { + view = inflater.inflate(R.layout.profile_list_header, parent, false); + // Do some initialization + } else { + view = convertView; + } + + TextView text = (TextView) view.findViewById(R.id.title); + text.setText(name); + + return view; + } + + @Override + public boolean isEnabled() { + return false; + } +} diff --git a/src/com/android/settings/profiles/actions/item/Item.java b/src/com/android/settings/profiles/actions/item/Item.java new file mode 100644 index 0000000..c468e11 --- /dev/null +++ b/src/com/android/settings/profiles/actions/item/Item.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.profiles.actions.item; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.android.settings.profiles.actions.ItemListAdapter; + +public interface Item { + public ItemListAdapter.RowType getRowType(); + public View getView(LayoutInflater inflater, View convertView, ViewGroup parent); + public boolean isEnabled(); +} diff --git a/src/com/android/settings/profiles/actions/item/LockModeItem.java b/src/com/android/settings/profiles/actions/item/LockModeItem.java new file mode 100644 index 0000000..e04cf19 --- /dev/null +++ b/src/com/android/settings/profiles/actions/item/LockModeItem.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.profiles.actions.item; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import cyanogenmod.app.Profile; + +import com.android.settings.R; +import com.android.settings.profiles.actions.ItemListAdapter; + +public class LockModeItem implements Item { + Profile mProfile; + + public LockModeItem(Profile profile) { + mProfile = profile; + } + + @Override + public ItemListAdapter.RowType getRowType() { + return ItemListAdapter.RowType.LOCKSCREENMODE_ITEM; + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public View getView(LayoutInflater inflater, View convertView, ViewGroup parent) { + View view; + if (convertView == null) { + view = inflater.inflate(R.layout.list_two_line_item, parent, false); + // Do some initialization + } else { + view = convertView; + } + + TextView text = (TextView) view.findViewById(R.id.title); + text.setText(R.string.profile_lockmode_title); + + TextView desc = (TextView) view.findViewById(R.id.summary); + desc.setText(getSummaryString(mProfile)); + + return view; + } + + public static int getSummaryString(Profile profile) { + switch (profile.getScreenLockMode().getValue()) { + case Profile.LockMode.DEFAULT: + return R.string.profile_action_system; //"leave unchanged" + case Profile.LockMode.DISABLE: + return R.string.profile_lockmode_disabled_summary; + case Profile.LockMode.INSECURE: + return R.string.profile_lockmode_insecure_summary; + default: return 0; + } + } +} diff --git a/src/com/android/settings/profiles/actions/item/NotificationLightModeItem.java b/src/com/android/settings/profiles/actions/item/NotificationLightModeItem.java new file mode 100644 index 0000000..b2c9132 --- /dev/null +++ b/src/com/android/settings/profiles/actions/item/NotificationLightModeItem.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.profiles.actions.item; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import cyanogenmod.app.Profile; + +import com.android.settings.R; +import com.android.settings.profiles.actions.ItemListAdapter; + +public class NotificationLightModeItem implements Item { + Profile mProfile; + + public NotificationLightModeItem(Profile profile) { + mProfile = profile; + } + + @Override + public ItemListAdapter.RowType getRowType() { + return ItemListAdapter.RowType.NOTIFICATIONLIGHTMODE_ITEM; + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public View getView(LayoutInflater inflater, View convertView, ViewGroup parent) { + View view; + if (convertView == null) { + view = inflater.inflate(R.layout.list_two_line_item, parent, false); + // Do some initialization + } else { + view = convertView; + } + + TextView text = (TextView) view.findViewById(R.id.title); + text.setText(R.string.notification_light_title); + + TextView desc = (TextView) view.findViewById(R.id.summary); + desc.setText(getSummaryString(mProfile)); + + return view; + } + + public static int getSummaryString(Profile profile) { + switch (profile.getNotificationLightMode()) { + case Profile.NotificationLightMode.DEFAULT: + return R.string.profile_action_none; //"leave unchanged" + case Profile.NotificationLightMode.ENABLE: + return R.string.profile_action_enable; + case Profile.NotificationLightMode.DISABLE: + return R.string.profile_action_disable; + default: return 0; + } + } +} diff --git a/src/com/android/settings/profiles/actions/item/ProfileNameItem.java b/src/com/android/settings/profiles/actions/item/ProfileNameItem.java new file mode 100644 index 0000000..6ea133b --- /dev/null +++ b/src/com/android/settings/profiles/actions/item/ProfileNameItem.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.profiles.actions.item; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import cyanogenmod.app.Profile; + +import com.android.settings.R; +import com.android.settings.profiles.actions.ItemListAdapter; + +public class ProfileNameItem implements Item { + Profile mProfile; + + public ProfileNameItem(Profile profile) { + this.mProfile = profile; + } + + @Override + public ItemListAdapter.RowType getRowType() { + return ItemListAdapter.RowType.NAME_ITEM; + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public View getView(LayoutInflater inflater, View convertView, ViewGroup parent) { + View view; + if (convertView == null) { + view = inflater.inflate(R.layout.profile_list_item, parent, false); + // Do some initialization + } else { + view = convertView; + } + + TextView text = (TextView) view.findViewById(R.id.title); + text.setText(mProfile.getName()); + + return view; + } +} diff --git a/src/com/android/settings/profiles/actions/item/RingModeItem.java b/src/com/android/settings/profiles/actions/item/RingModeItem.java new file mode 100644 index 0000000..42fdb91 --- /dev/null +++ b/src/com/android/settings/profiles/actions/item/RingModeItem.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.profiles.actions.item; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import cyanogenmod.profiles.RingModeSettings; + +import com.android.settings.R; +import com.android.settings.profiles.actions.ItemListAdapter; + +public class RingModeItem implements Item { + RingModeSettings mSettings; + + public RingModeItem(RingModeSettings ringModeSettings) { + if (ringModeSettings == null) { + ringModeSettings = new RingModeSettings(); + } + mSettings = ringModeSettings; + } + + @Override + public ItemListAdapter.RowType getRowType() { + return ItemListAdapter.RowType.RINGMODE_ITEM; + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public View getView(LayoutInflater inflater, View convertView, ViewGroup parent) { + View view; + if (convertView == null) { + view = inflater.inflate(R.layout.list_two_line_item, parent, false); + // Do some initialization + } else { + view = convertView; + } + + TextView text = (TextView) view.findViewById(R.id.title); + text.setText(R.string.ring_mode_title); + + TextView desc = (TextView) view.findViewById(R.id.summary); + desc.setText(getModeString(mSettings)); + + return view; + } + + public static int getModeString(RingModeSettings settings) { + if (settings == null) { + return R.string.ring_mode_normal; + } + if (settings.isOverride()) { + if (settings.getValue().equals("vibrate")) { + return R.string.ring_mode_vibrate; + } else if (settings.getValue().equals("normal")) { + return R.string.ring_mode_normal; + } else { + return R.string.ring_mode_mute; + } + } else { + return R.string.profile_action_none; //"leave unchanged" + } + } + + public RingModeSettings getSettings() { + return mSettings; + } +} diff --git a/src/com/android/settings/profiles/actions/item/TriggerItem.java b/src/com/android/settings/profiles/actions/item/TriggerItem.java new file mode 100644 index 0000000..dd5686a --- /dev/null +++ b/src/com/android/settings/profiles/actions/item/TriggerItem.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.profiles.actions.item; + +import android.util.StringBuilderPrinter; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import cyanogenmod.app.Profile; + +import com.android.settings.R; +import com.android.settings.profiles.actions.ItemListAdapter; + +import java.util.ArrayList; + +public class TriggerItem implements Item { + + // from Profile.TriggerType + public static final int WIFI = 0; + public static final int BLUETOOTH = 1; + // not in Profile.TriggerType, but we need it. + public static final int NFC = 2; + + Profile mProfile; + int mTriggerType; + + public TriggerItem(Profile profile, int whichTrigger) { + mProfile = profile; + mTriggerType = whichTrigger; + } + + @Override + public ItemListAdapter.RowType getRowType() { + return ItemListAdapter.RowType.TRIGGER_ITEM; + } + + @Override + public boolean isEnabled() { + return true; + } + + public int getTriggerType() { + return mTriggerType; + } + + @Override + public View getView(LayoutInflater inflater, View convertView, ViewGroup parent) { + View view; + if (convertView == null) { + view = inflater.inflate(R.layout.list_two_line_item, parent, false); + // Do some initialization + } else { + view = convertView; + } + + TextView text = (TextView) view.findViewById(R.id.title); + text.setText(getTitleString(mTriggerType)); + + + StringBuilder sb = new StringBuilder(); + ArrayList<Profile.ProfileTrigger> triggers = mProfile.getTriggersFromType(mTriggerType); + + for (int i = 0; i < triggers.size(); i++) { + sb.append(triggers.get(i).getName()); + if (i < (triggers.size() - 1)) { + sb.append("\n"); + } + } + + TextView desc = (TextView) view.findViewById(R.id.summary); + if (sb.length() == 0) { + if (mTriggerType == NFC) { + desc.setText(R.string.no_triggers_configured_nfc); + } else { + desc.setText(R.string.no_triggers_configured); + } + } else { + desc.setText(sb.toString()); + } + + return view; + } + + public static int getTitleString(int triggerType) { + switch (triggerType) { + case WIFI: + return R.string.profile_tabs_wifi; + case BLUETOOTH: + return R.string.profile_tabs_bluetooth; + case NFC: + return R.string.profile_tabs_nfc; + default: return 0; + } + } +} diff --git a/src/com/android/settings/profiles/actions/item/VolumeStreamItem.java b/src/com/android/settings/profiles/actions/item/VolumeStreamItem.java new file mode 100644 index 0000000..f4d76c7 --- /dev/null +++ b/src/com/android/settings/profiles/actions/item/VolumeStreamItem.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.profiles.actions.item; + +import android.content.Context; +import android.media.AudioManager; +import android.provider.Settings; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import cyanogenmod.profiles.StreamSettings; + +import com.android.settings.R; +import com.android.settings.profiles.actions.ItemListAdapter; + +public class VolumeStreamItem implements Item { + private int mStreamId; + private StreamSettings mStreamSettings; + private boolean mEnabled; + + public VolumeStreamItem(int streamId, StreamSettings streamSettings) { + mStreamId = streamId; + mStreamSettings = streamSettings; + } + + @Override + public ItemListAdapter.RowType getRowType() { + return ItemListAdapter.RowType.VOLUME_STREAM_ITEM; + } + + @Override + public boolean isEnabled() { + return mEnabled; + } + + @Override + public View getView(LayoutInflater inflater, View convertView, ViewGroup parent) { + View view; + if (convertView == null) { + view = inflater.inflate(R.layout.list_two_line_item, parent, false); + // Do some initialization + } else { + view = convertView; + } + + Context context = inflater.getContext(); + final AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + + TextView text = (TextView) view.findViewById(R.id.title); + text.setText(getNameForStream(mStreamId)); + + TextView desc = (TextView) view.findViewById(R.id.summary); + int denominator = mStreamSettings.getValue(); + int numerator = am.getStreamMaxVolume(mStreamId); + if (mStreamSettings.isOverride()) { + desc.setText(context.getResources().getString(R.string.volume_override_summary, + denominator, numerator)); + } else { + desc.setText(context.getString(R.string.profile_action_none)); + } + + final boolean volumeLinkNotification = Settings.Secure.getInt(context + .getContentResolver(), Settings.Secure.VOLUME_LINK_NOTIFICATION, 1) == 1; + mEnabled = true; + if (mStreamId == AudioManager.STREAM_NOTIFICATION && volumeLinkNotification) { + mEnabled = false; + text.setEnabled(false); + desc.setEnabled(false); + } + + return view; + } + + public static int getNameForStream(int stream) { + switch (stream) { + case AudioManager.STREAM_ALARM: + return R.string.alarm_volume_title; + case AudioManager.STREAM_MUSIC: + return R.string.media_volume_title; + case AudioManager.STREAM_RING: + return R.string.incoming_call_volume_title; + case AudioManager.STREAM_NOTIFICATION: + return R.string.notification_volume_title; + default: return 0; + } + } + + public int getStreamType() { + return mStreamId; + } + + public StreamSettings getSettings() { + return mStreamSettings; + } +} diff --git a/src/com/android/settings/profiles/triggers/AbstractTriggerItem.java b/src/com/android/settings/profiles/triggers/AbstractTriggerItem.java new file mode 100644 index 0000000..432b55a --- /dev/null +++ b/src/com/android/settings/profiles/triggers/AbstractTriggerItem.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.profiles.triggers; + +import cyanogenmod.app.Profile; + +public class AbstractTriggerItem { + private int mIcon; + private String mSummary; + private String mTitle; + + private int mTriggerState = Profile.TriggerState.DISABLED; + + public void setTriggerState(int trigger) { + mTriggerState = trigger; + } + + public int getTriggerState() { + return mTriggerState; + } + + public void setSummary(String summary) { + mSummary = summary; + } + + public String getTitle() { + return mTitle; + } + + public void setTitle(String title) { + mTitle = title; + } + + public String getSummary() { + return mSummary; + } + + public void setIcon(int icon) { + mIcon = icon; + } + + public int getIcon() { + return mIcon; + } +} diff --git a/src/com/android/settings/profiles/triggers/BluetoothTriggerFragment.java b/src/com/android/settings/profiles/triggers/BluetoothTriggerFragment.java new file mode 100644 index 0000000..5da8e44 --- /dev/null +++ b/src/com/android/settings/profiles/triggers/BluetoothTriggerFragment.java @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.profiles.triggers; + +import android.app.AlertDialog; +import android.app.ListFragment; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Resources; +import android.os.Bundle; +import android.provider.Settings; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; + +import cyanogenmod.app.Profile; +import cyanogenmod.app.ProfileManager; + +import com.android.settings.R; +import com.android.settings.Utils; +import com.android.settings.profiles.ProfilesSettings; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class BluetoothTriggerFragment extends ListFragment { + + private BluetoothAdapter mBluetoothAdapter; + + Profile mProfile; + ProfileManager mProfileManager; + + private View mEmptyView; + + private List<BluetoothTrigger> mTriggers = new ArrayList<BluetoothTrigger>(); + private BluetoothTriggerAdapter mListAdapter; + + public static BluetoothTriggerFragment newInstance(Profile profile) { + BluetoothTriggerFragment fragment = new BluetoothTriggerFragment(); + + Bundle extras = new Bundle(); + extras.putParcelable(ProfilesSettings.EXTRA_PROFILE, profile); + + fragment.setArguments(extras); + return fragment; + } + + public BluetoothTriggerFragment() { + // Required empty public constructor + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mProfileManager = ProfileManager.getInstance(getActivity()); + if (getArguments() != null) { + mProfile = getArguments().getParcelable(ProfilesSettings.EXTRA_PROFILE); + } + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + } + + @Override + public void onResume() { + super.onResume(); + reloadTriggerListItems(); + } + + private void initPreference(AbstractTriggerItem pref, int state, Resources res, int icon) { + String[] values = res.getStringArray(R.array.profile_trigger_wifi_options_values); + for (int i = 0; i < values.length; i++) { + if (Integer.parseInt(values[i]) == state) { + pref.setSummary(res.getStringArray(R.array.profile_trigger_wifi_options)[i]); + break; + } + } + pref.setTriggerState(state); + pref.setIcon(icon); + } + + @Override + public void onListItemClick(ListView l, View v, int position, long id) { + super.onListItemClick(l, v, position, id); + + final String triggerId; + final String triggerName; + final int triggerType; + + String[] entries = getResources().getStringArray(R.array.profile_trigger_wifi_options); + String[] values = + getResources().getStringArray(R.array.profile_trigger_wifi_options_values); + + List<Trigger> triggers = new ArrayList<Trigger>(entries.length); + for (int i = 0; i < entries.length; i++) { + Trigger toAdd = new Trigger(); + toAdd.value = Integer.parseInt(values[i]); + toAdd.name = entries[i]; + triggers.add(toAdd); + } + + BluetoothTrigger btpref = mListAdapter.getItem(position); + triggerName = btpref.getTitle(); + triggerId = btpref.getAddress(); + triggerType = Profile.TriggerType.BLUETOOTH; + BluetoothDevice dev = mBluetoothAdapter.getRemoteDevice(triggerId); + if (!dev.getBluetoothClass().doesClassMatch(BluetoothClass.PROFILE_A2DP)) { + removeTrigger(triggers, Profile.TriggerState.ON_A2DP_CONNECT); + removeTrigger(triggers, Profile.TriggerState.ON_A2DP_DISCONNECT); + } + + entries = new String[triggers.size()]; + final int[] valueInts = new int[triggers.size()]; + int currentTriggerState = mProfile.getTriggerState(triggerType, triggerId); + int currentItem = -1; + for (int i = 0; i < triggers.size(); i++) { + Trigger t = triggers.get(i); + entries[i] = t.name; + valueInts[i] = t.value; + if (valueInts[i] == currentTriggerState) { + currentItem = i; + } + } + + new AlertDialog.Builder(getActivity()) + .setTitle(R.string.profile_trigger_configure) + .setSingleChoiceItems(entries, currentItem, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mProfile.setTrigger(triggerType, triggerId, valueInts[which], triggerName); + mProfileManager.updateProfile(mProfile); + reloadTriggerListItems(); + dialog.dismiss(); + } + }) + .show(); + } + + @Override + public void onStart() { + super.onStart(); + getListView().setEmptyView(mEmptyView); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + mEmptyView = inflater.inflate(R.layout.profile_bluetooth_empty_view, container, false); + mEmptyView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent bluetoothSettings = new Intent(); + bluetoothSettings.setAction( + Settings.ACTION_BLUETOOTH_SETTINGS); + startActivity(bluetoothSettings); + } + }); + + ViewGroup view = (ViewGroup) super.onCreateView(inflater, container, savedInstanceState); + view.addView(mEmptyView); + return view; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + reloadTriggerListItems(); + mListAdapter = new BluetoothTriggerAdapter(getActivity()); + setListAdapter(mListAdapter); + } + + private void removeTrigger(List<Trigger> triggers, int value) { + for (Trigger t : triggers) { + if (t.value == value) { + triggers.remove(t); + return; + } + } + } + + private void reloadTriggerListItems() { + mTriggers.clear(); + final Resources res = getResources(); + + Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices(); + if (!pairedDevices.isEmpty()) { + for (BluetoothDevice device : pairedDevices) { + BluetoothTrigger bt = + new BluetoothTrigger(device); + int state = mProfile.getTriggerState( + Profile.TriggerType.BLUETOOTH, bt.getAddress()); + initPreference(bt, state, res, R.drawable.ic_settings_bluetooth); + mTriggers.add(bt); + } + } else { + final List<Profile.ProfileTrigger> triggers = + mProfile.getTriggersFromType(Profile.TriggerType.BLUETOOTH); + for (Profile.ProfileTrigger trigger : triggers) { + BluetoothTrigger bt = new BluetoothTrigger(trigger.getName(), trigger.getId()); + initPreference(bt, trigger.getState(), res, R.drawable.ic_settings_bluetooth); + mTriggers.add(bt); + } + } + + if (mListAdapter != null) { + mListAdapter.notifyDataSetChanged(); + } + } + + private class Trigger { + int value; + String name; + } + + private class BluetoothTriggerAdapter extends ArrayAdapter<BluetoothTrigger> { + public BluetoothTriggerAdapter(Context context) { + super(context, R.layout.abstract_trigger_row, R.id.title, mTriggers); + } + + @Override + public View getView(int i, View view, ViewGroup viewGroup) { + LayoutInflater inflater = LayoutInflater.from(getContext()); + View rowView = inflater.inflate(R.layout.abstract_trigger_row, viewGroup, false); + TextView title = (TextView) rowView.findViewById(R.id.title); + TextView desc = (TextView) rowView.findViewById(R.id.desc); + ImageView imageView = (ImageView) rowView.findViewById(R.id.icon); + + BluetoothTrigger trigger = getItem(i); + + title.setText(trigger.getTitle()); + desc.setText(trigger.getSummary()); + imageView.setImageResource(trigger.getIcon()); + + return rowView; + } + } + + public static class BluetoothTrigger extends AbstractTriggerItem { + private String mAddress; + + public BluetoothTrigger(BluetoothDevice device) { + mAddress = device.getAddress(); + if (device.getAlias() != null) { + setTitle(device.getAlias()); + } else { + setTitle(device.getName()); + } + } + + public BluetoothTrigger(String name, String address) { + mAddress = address; + setTitle(name); + } + + public String getAddress() { + return mAddress; + } + } +} diff --git a/src/com/android/settings/profiles/triggers/NfcTriggerFragment.java b/src/com/android/settings/profiles/triggers/NfcTriggerFragment.java new file mode 100644 index 0000000..363162d --- /dev/null +++ b/src/com/android/settings/profiles/triggers/NfcTriggerFragment.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.profiles.triggers; + +import android.app.Fragment; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Toast; + +import cyanogenmod.app.Profile; + +import com.android.settings.R; +import com.android.settings.Settings; +import com.android.settings.SubSettings; +import com.android.settings.profiles.NFCProfileTagCallback; +import com.android.settings.profiles.NFCProfileUtils; +import com.android.settings.profiles.ProfilesSettings; + +public class NfcTriggerFragment extends Fragment implements NFCProfileTagCallback { + Profile mProfile; + + private NfcAdapter mNfcAdapter; + private IntentFilter[] mWriteTagFilters; + + public static NfcTriggerFragment newInstance(Profile profile) { + NfcTriggerFragment fragment = new NfcTriggerFragment(); + + Bundle extras = new Bundle(); + extras.putParcelable(ProfilesSettings.EXTRA_PROFILE, profile); + + fragment.setArguments(extras); + return fragment; + } + + public NfcTriggerFragment() { + // Required empty public constructor + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mNfcAdapter = NfcAdapter.getDefaultAdapter(getActivity()); + if (getArguments() != null) { + mProfile = getArguments().getParcelable(ProfilesSettings.EXTRA_PROFILE); + } + ((SubSettings) getActivity()).setNfcProfileCallback(this); + } + + @Override + public void onDestroy() { + super.onDestroy(); + ((SubSettings) getActivity()).setNfcProfileCallback(null); + } + + @Override + public void onResume() { + super.onResume(); + if (mProfile != null) { + enableTagWriteMode(); + } + } + + @Override + public void onPause() { + super.onPause(); + disableTagWriteMode(); + } + + private PendingIntent getPendingIntent() { + Intent intent = new Intent(getActivity(), getActivity().getClass()) + .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + return PendingIntent.getActivity(getActivity(), 0, intent, 0); + } + + private void disableTagWriteMode() { + mNfcAdapter.disableForegroundDispatch(getActivity()); + } + + private void enableTagWriteMode() { + IntentFilter tagDetected = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED); + mWriteTagFilters = new IntentFilter[] { + tagDetected + }; + mNfcAdapter.enableForegroundDispatch(getActivity(), getPendingIntent(), mWriteTagFilters, null); + } + + @Override + public void onTagRead(Tag tag) { + if (NFCProfileUtils.writeTag(NFCProfileUtils.getProfileAsNdef(mProfile), tag)) { + Toast.makeText(getActivity(), R.string.profile_write_success, Toast.LENGTH_LONG).show(); + NFCProfileUtils.vibrate(getActivity()); + } else { + Toast.makeText(getActivity(), R.string.profile_write_failed, Toast.LENGTH_LONG).show(); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.nfc_writer, container, false); + } +} diff --git a/src/com/android/settings/profiles/triggers/WifiTriggerFragment.java b/src/com/android/settings/profiles/triggers/WifiTriggerFragment.java new file mode 100644 index 0000000..e951c4c --- /dev/null +++ b/src/com/android/settings/profiles/triggers/WifiTriggerFragment.java @@ -0,0 +1,288 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.profiles.triggers; + +import android.app.AlertDialog; +import android.app.ListFragment; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Resources; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiManager; +import android.os.Bundle; +import android.provider.Settings; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; + +import cyanogenmod.app.Profile; +import cyanogenmod.app.ProfileManager; + +import com.android.settings.R; +import com.android.settings.profiles.ProfilesSettings; + +import java.util.ArrayList; +import java.util.List; + +public class WifiTriggerFragment extends ListFragment { + WifiManager mWifiManager; + Profile mProfile; + private ProfileManager mProfileManager; + + private View mEmptyView; + + private List<WifiTrigger> mTriggers = new ArrayList<WifiTrigger>(); + private WifiTriggerAdapter mListAdapter; + + public static WifiTriggerFragment newInstance(Profile profile) { + WifiTriggerFragment fragment = new WifiTriggerFragment(); + + Bundle extras = new Bundle(); + extras.putParcelable(ProfilesSettings.EXTRA_PROFILE, profile); + + fragment.setArguments(extras); + return fragment; + } + + public WifiTriggerFragment() { + // Required empty public constructor + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + mProfile = getArguments().getParcelable(ProfilesSettings.EXTRA_PROFILE); + } else { + throw new UnsupportedOperationException("no profile!"); + } + mProfileManager = ProfileManager.getInstance(getActivity()); + mWifiManager = (WifiManager) getActivity().getSystemService(Context.WIFI_SERVICE); + } + + + @Override + public void onStart() { + super.onStart(); + getListView().setEmptyView(mEmptyView); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + mEmptyView = inflater.inflate(R.layout.profile_wifi_empty_view, container, false); + mEmptyView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent wifiSettings = new Intent(); + wifiSettings.setAction( + Settings.ACTION_WIFI_SETTINGS); + startActivity(wifiSettings); + } + }); + + ViewGroup view = (ViewGroup) super.onCreateView(inflater, container, savedInstanceState); + view.addView(mEmptyView); + return view; + } + + @Override + public void onResume() { + super.onResume(); + reloadTriggerListItems(); + } + + private void initPreference(AbstractTriggerItem pref, int state, Resources res, int icon) { + String[] values = res.getStringArray(R.array.profile_trigger_wifi_options_values); + for (int i = 0; i < values.length; i++) { + if (Integer.parseInt(values[i]) == state) { + pref.setSummary(res.getStringArray(R.array.profile_trigger_wifi_options)[i]); + break; + } + } + pref.setTriggerState(state); + pref.setIcon(icon); + } + + @Override + public void onListItemClick(ListView l, View v, int position, long id) { + super.onListItemClick(l, v, position, id); + + final String triggerId; + final String triggerName; + final int triggerType; + + String[] entries = getResources().getStringArray(R.array.profile_trigger_wifi_options); + String[] values = + getResources().getStringArray(R.array.profile_trigger_wifi_options_values); + + List<Trigger> triggers = new ArrayList<Trigger>(entries.length); + for (int i = 0; i < entries.length; i++) { + Trigger toAdd = new Trigger(); + toAdd.value = Integer.parseInt(values[i]); + toAdd.name = entries[i]; + triggers.add(toAdd); + } + + WifiTrigger pref = (WifiTrigger) l.getAdapter().getItem(position); + triggerName = pref.getTitle(); + triggerId = pref.getSSID(); + triggerType = Profile.TriggerType.WIFI; + removeTrigger(triggers, Profile.TriggerState.ON_A2DP_CONNECT); + removeTrigger(triggers, Profile.TriggerState.ON_A2DP_DISCONNECT); + + entries = new String[triggers.size()]; + final int[] valueInts = new int[triggers.size()]; + int currentTriggerState = mProfile.getTriggerState(triggerType, triggerId); + int currentItem = -1; + for (int i = 0; i < triggers.size(); i++) { + Trigger t = triggers.get(i); + entries[i] = t.name; + valueInts[i] = t.value; + if (valueInts[i] == currentTriggerState) { + currentItem = i; + } + } + + new AlertDialog.Builder(getActivity()) + .setTitle(R.string.profile_trigger_configure) + .setSingleChoiceItems(entries, currentItem, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mProfile.setTrigger(triggerType, triggerId, valueInts[which], triggerName); + mProfileManager.updateProfile(mProfile); + reloadTriggerListItems(); + dialog.dismiss(); + } + }) + .show(); + } + + private void removeTrigger(List<Trigger> triggers, int value) { + for (Trigger t : triggers) { + if (t.value == value) { + triggers.remove(t); + return; + } + } + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + reloadTriggerListItems(); + mListAdapter = new WifiTriggerAdapter(getActivity()); + setListAdapter(mListAdapter); + } + + private void reloadTriggerListItems() { + mTriggers.clear(); + final Resources res = getResources(); + final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks(); + + if (configs != null) { + for (WifiConfiguration config : configs) { + WifiTrigger accessPoint = new WifiTrigger(config); + int state = mProfile.getTriggerState( + Profile.TriggerType.WIFI, accessPoint.getSSID()); + initPreference(accessPoint, state, res, R.drawable.ic_wifi_signal_4); + mTriggers.add(accessPoint); + } + } else { + final List<Profile.ProfileTrigger> triggers = + mProfile.getTriggersFromType(Profile.TriggerType.WIFI); + for (Profile.ProfileTrigger trigger : triggers) { + WifiTrigger accessPoint = new WifiTrigger(trigger.getName()); + initPreference(accessPoint, trigger.getState(), res, + R.drawable.ic_wifi_signal_4); + mTriggers.add(accessPoint); + } + } + if (mListAdapter != null) { + mListAdapter.notifyDataSetChanged(); + } + } + + private class Trigger { + int value; + String name; + } + + private class WifiTriggerAdapter extends ArrayAdapter<WifiTrigger> { + public WifiTriggerAdapter(Context context) { + super(context, R.layout.abstract_trigger_row, R.id.title, mTriggers); + } + + @Override + public View getView(int i, View view, ViewGroup viewGroup) { + LayoutInflater inflater = LayoutInflater.from(getContext()); + View rowView = inflater.inflate(R.layout.abstract_trigger_row, viewGroup, false); + TextView title = (TextView) rowView.findViewById(R.id.title); + TextView desc = (TextView) rowView.findViewById(R.id.desc); + ImageView imageView = (ImageView) rowView.findViewById(R.id.icon); + + WifiTrigger trigger = getItem(i); + + title.setText(trigger.getTitle()); + desc.setText(trigger.getSummary()); + imageView.setImageResource(trigger.getIcon()); + + return rowView; + } + } + + public static class WifiTrigger extends AbstractTriggerItem { + public String mSSID; + public WifiConfiguration mConfig; + + public WifiTrigger(WifiConfiguration config) { + mConfig = config; + loadConfig(config); + } + + public WifiTrigger(String ssid) { + mSSID = ssid; + } + + public String getSSID() { + return mSSID; + } + + @Override + public String getTitle() { + return mSSID; + } + + private void loadConfig(WifiConfiguration config) { + mSSID = (config.SSID == null ? "" : removeDoubleQuotes(config.SSID)); + mConfig = config; + } + + public static String removeDoubleQuotes(String string) { + final int length = string.length(); + if (length >= 2) { + if (string.startsWith("\"") && string.endsWith("\"")) { + return string.substring(1, length - 1); + } + } + return string; + } + } +} diff --git a/src/com/android/settings/search/BaseSearchIndexProvider.java b/src/com/android/settings/search/BaseSearchIndexProvider.java index 0fe1944..49e2803 100644 --- a/src/com/android/settings/search/BaseSearchIndexProvider.java +++ b/src/com/android/settings/search/BaseSearchIndexProvider.java @@ -33,6 +33,10 @@ public class BaseSearchIndexProvider implements Indexable.SearchIndexProvider { } @Override + public void prepare() { + } + + @Override public List<SearchIndexableResource> getXmlResourcesToIndex(Context context, boolean enabled) { return null; } diff --git a/src/com/android/settings/search/Index.java b/src/com/android/settings/search/Index.java index 267c6c0..055c2d8 100644 --- a/src/com/android/settings/search/Index.java +++ b/src/com/android/settings/search/Index.java @@ -31,6 +31,7 @@ import android.database.DatabaseUtils; import android.database.MergeCursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; +import android.database.sqlite.SQLiteFullException; import android.net.Uri; import android.os.AsyncTask; import android.provider.SearchIndexableData; @@ -529,6 +530,18 @@ public class Index { synchronized (mDataToProcess) { final UpdateIndexTask task = new UpdateIndexTask(); UpdateData copy = mDataToProcess.copy(); + for (SearchIndexableData data : copy.dataToUpdate) { + if (data instanceof SearchIndexableResource) { + SearchIndexableResource sir = (SearchIndexableResource) data; + final Class<?> clazz = sir.className != null + ? getIndexableClass(sir.className) : null; + final Indexable.SearchIndexProvider provider = clazz != null + ? getSearchIndexProvider(clazz) : null; + if (provider != null) { + provider.prepare(); + } + } + } task.execute(copy); mDataToProcess.clear(); } @@ -1177,14 +1190,15 @@ public class Index { final boolean forceUpdate = params[0].forceUpdate; - final SQLiteDatabase database = getWritableDatabase(); - if (database == null) { - Log.e(LOG_TAG, "Cannot update Index as I cannot get a writable database"); - return null; - } - final String localeStr = Locale.getDefault().toString(); + SQLiteDatabase database = null; try { + database = getWritableDatabase(); + if (database == null) { + Log.e(LOG_TAG, "Cannot update Index as I cannot get a writable database"); + return null; + } + final String localeStr = Locale.getDefault().toString(); database.beginTransaction(); if (dataToDelete.size() > 0) { processDataToDelete(database, localeStr, dataToDelete); @@ -1194,8 +1208,19 @@ public class Index { forceUpdate); } database.setTransactionSuccessful(); + } catch (SQLiteFullException e) { + Log.e(LOG_TAG, "SQLite database is full." + e.toString()); + } catch (SQLiteException e) { + Log.e(LOG_TAG, e.toString()); } finally { - database.endTransaction(); + try { + if (database != null) + database.endTransaction(); + } catch (SQLiteFullException e) { + Log.e(LOG_TAG, "SQLite database is full." + e.toString()); + } catch (SQLiteException e) { + Log.e(LOG_TAG, e.toString()); + } } return null; diff --git a/src/com/android/settings/search/IndexDatabaseHelper.java b/src/com/android/settings/search/IndexDatabaseHelper.java index 152cbf3..146c640 100644 --- a/src/com/android/settings/search/IndexDatabaseHelper.java +++ b/src/com/android/settings/search/IndexDatabaseHelper.java @@ -28,7 +28,7 @@ public class IndexDatabaseHelper extends SQLiteOpenHelper { private static final String TAG = "IndexDatabaseHelper"; private static final String DATABASE_NAME = "search_index.db"; - private static final int DATABASE_VERSION = 115; + private static final int DATABASE_VERSION = 121; public interface Tables { public static final String TABLE_PREFS_INDEX = "prefs_index"; diff --git a/src/com/android/settings/search/Indexable.java b/src/com/android/settings/search/Indexable.java index 19f88ae..c1c5b08 100644 --- a/src/com/android/settings/search/Indexable.java +++ b/src/com/android/settings/search/Indexable.java @@ -65,5 +65,10 @@ public interface Indexable { * @return a list of {@link SearchIndexableRaw} references. Can be null. */ List<String> getNonIndexableKeys(Context context); + + /** + * Prepare for indexing. Guaranteed to be called from the main thread. + */ + void prepare(); } } diff --git a/src/com/android/settings/search/Ranking.java b/src/com/android/settings/search/Ranking.java index 3edfee7..4d80f14 100644 --- a/src/com/android/settings/search/Ranking.java +++ b/src/com/android/settings/search/Ranking.java @@ -35,14 +35,14 @@ import com.android.settings.applications.AdvancedAppSettings; import com.android.settings.applications.ManageDefaultApps; import com.android.settings.bluetooth.BluetoothSettings; import com.android.settings.deviceinfo.StorageSettings; -import com.android.settings.fuelgauge.BatterySaverSettings; import com.android.settings.fuelgauge.PowerUsageSummary; import com.android.settings.inputmethod.InputMethodAndLanguageSettings; import com.android.settings.location.LocationSettings; import com.android.settings.location.ScanningSettings; import com.android.settings.net.DataUsageMeteredSettings; -import com.android.settings.notification.NotificationSettings; +import com.android.settings.notification.NotificationManagerSettings; import com.android.settings.notification.OtherSoundSettings; +import com.android.settings.notification.SoundSettings; import com.android.settings.notification.ZenModeAutomationSettings; import com.android.settings.notification.ZenModePrioritySettings; import com.android.settings.notification.ZenModeSettings; @@ -53,6 +53,9 @@ import com.android.settings.wifi.AdvancedWifiSettings; import com.android.settings.wifi.SavedAccessPointsWifiSettings; import com.android.settings.wifi.WifiSettings; +import com.android.settings.ButtonSettings; +import com.android.settings.cyanogenmod.StatusBarSettings; + import java.util.HashMap; /** @@ -117,12 +120,15 @@ public final class Ranking { // Display sRankMap.put(DisplaySettings.class.getName(), RANK_DISPLAY); + sRankMap.put(ButtonSettings.class.getName(), RANK_DISPLAY); + sRankMap.put(StatusBarSettings.class.getName(), RANK_DISPLAY); // Wallpapers sRankMap.put(WallpaperTypeSettings.class.getName(), RANK_WALLPAPER); // Notifications - sRankMap.put(NotificationSettings.class.getName(), RANK_NOTIFICATIONS); + sRankMap.put(SoundSettings.class.getName(), RANK_NOTIFICATIONS); + sRankMap.put(NotificationManagerSettings.class.getName(), RANK_NOTIFICATIONS); sRankMap.put(OtherSoundSettings.class.getName(), RANK_NOTIFICATIONS); sRankMap.put(ZenModeSettings.class.getName(), RANK_NOTIFICATIONS); sRankMap.put(ZenModePrioritySettings.class.getName(), RANK_NOTIFICATIONS); @@ -133,7 +139,6 @@ public final class Ranking { // Battery sRankMap.put(PowerUsageSummary.class.getName(), RANK_POWER_USAGE); - sRankMap.put(BatterySaverSettings.class.getName(), RANK_POWER_USAGE); // Advanced app settings sRankMap.put(AdvancedAppSettings.class.getName(), RANK_APPS); @@ -156,6 +161,7 @@ public final class Ranking { // Privacy sRankMap.put(PrivacySettings.class.getName(), RANK_PRIVACY); + sRankMap.put(com.android.settings.cyanogenmod.PrivacySettings.class.getName(), RANK_PRIVACY); // Date / Time sRankMap.put(DateTimeSettings.class.getName(), RANK_DATE_TIME); diff --git a/src/com/android/settings/search/SearchIndexableResources.java b/src/com/android/settings/search/SearchIndexableResources.java index f3c0b42..683aaec 100644 --- a/src/com/android/settings/search/SearchIndexableResources.java +++ b/src/com/android/settings/search/SearchIndexableResources.java @@ -37,14 +37,14 @@ import com.android.settings.applications.AdvancedAppSettings; import com.android.settings.applications.ManageDefaultApps; import com.android.settings.bluetooth.BluetoothSettings; import com.android.settings.deviceinfo.StorageSettings; -import com.android.settings.fuelgauge.BatterySaverSettings; import com.android.settings.fuelgauge.PowerUsageSummary; import com.android.settings.inputmethod.InputMethodAndLanguageSettings; import com.android.settings.location.LocationSettings; import com.android.settings.location.ScanningSettings; import com.android.settings.net.DataUsageMeteredSettings; -import com.android.settings.notification.NotificationSettings; +import com.android.settings.notification.NotificationManagerSettings; import com.android.settings.notification.OtherSoundSettings; +import com.android.settings.notification.SoundSettings; import com.android.settings.notification.ZenModePrioritySettings; import com.android.settings.notification.ZenModeSettings; import com.android.settings.print.PrintSettingsFragment; @@ -54,6 +54,9 @@ import com.android.settings.wifi.AdvancedWifiSettings; import com.android.settings.wifi.SavedAccessPointsWifiSettings; import com.android.settings.wifi.WifiSettings; +import com.android.settings.ButtonSettings; +import com.android.settings.cyanogenmod.StatusBarSettings; + import java.util.Collection; import java.util.HashMap; @@ -82,7 +85,7 @@ public final class SearchIndexableResources { sResMap.put(SavedAccessPointsWifiSettings.class.getName(), new SearchIndexableResource( Ranking.getRankForClassName(SavedAccessPointsWifiSettings.class.getName()), - R.xml.wifi_display_saved_access_points, + NO_DATA_RES_ID, SavedAccessPointsWifiSettings.class.getName(), R.drawable.ic_settings_wireless)); @@ -98,7 +101,7 @@ public final class SearchIndexableResources { Ranking.getRankForClassName(SimSettings.class.getName()), NO_DATA_RES_ID, SimSettings.class.getName(), - R.drawable.ic_sim_sd)); + R.drawable.ic_settings_sim)); sResMap.put(DataUsageSummary.class.getName(), new SearchIndexableResource( @@ -121,6 +124,13 @@ public final class SearchIndexableResources { WirelessSettings.class.getName(), R.drawable.ic_settings_more)); + sResMap.put(SecuritySettings.class.getName(), + new SearchIndexableResource( + Ranking.getRankForClassName(SecuritySettings.class.getName()), + NO_DATA_RES_ID, + SecuritySettings.class.getName(), + R.drawable.ic_settings_security)); + sResMap.put(HomeSettings.class.getName(), new SearchIndexableResource( Ranking.getRankForClassName(HomeSettings.class.getName()), @@ -142,11 +152,18 @@ public final class SearchIndexableResources { WallpaperTypeSettings.class.getName(), R.drawable.ic_settings_display)); - sResMap.put(NotificationSettings.class.getName(), + sResMap.put(SoundSettings.class.getName(), + new SearchIndexableResource( + Ranking.getRankForClassName(SoundSettings.class.getName()), + NO_DATA_RES_ID, + SoundSettings.class.getName(), + R.drawable.ic_settings_notifications)); + + sResMap.put(NotificationManagerSettings.class.getName(), new SearchIndexableResource( - Ranking.getRankForClassName(NotificationSettings.class.getName()), + Ranking.getRankForClassName(NotificationManagerSettings.class.getName()), NO_DATA_RES_ID, - NotificationSettings.class.getName(), + NotificationManagerSettings.class.getName(), R.drawable.ic_settings_notifications)); sResMap.put(OtherSoundSettings.class.getName(), @@ -184,13 +201,6 @@ public final class SearchIndexableResources { PowerUsageSummary.class.getName(), R.drawable.ic_settings_battery)); - sResMap.put(BatterySaverSettings.class.getName(), - new SearchIndexableResource( - Ranking.getRankForClassName(BatterySaverSettings.class.getName()), - R.xml.battery_saver_settings, - BatterySaverSettings.class.getName(), - R.drawable.ic_settings_battery)); - sResMap.put(AdvancedAppSettings.class.getName(), new SearchIndexableResource( Ranking.getRankForClassName(AdvancedAppSettings.class.getName()), @@ -302,6 +312,37 @@ public final class SearchIndexableResources { R.xml.wifi_calling_settings, WifiCallingSettings.class.getName(), R.drawable.ic_settings_wireless)); + + // CyanogenMod Settings + sResMap.put(ButtonSettings.class.getName(), + new SearchIndexableResource( + Ranking.getRankForClassName(ButtonSettings.class.getName()), + R.xml.button_settings, + ButtonSettings.class.getName(), + R.drawable.ic_settings_buttons)); + + sResMap.put(StatusBarSettings.class.getName(), + new SearchIndexableResource( + Ranking.getRankForClassName(StatusBarSettings.class.getName()), + R.xml.status_bar_settings, + StatusBarSettings.class.getName(), + R.drawable.ic_settings_statusbar)); + + sResMap.put(com.android.settings.cyanogenmod.PrivacySettings.class.getName(), + new SearchIndexableResource( + Ranking.getRankForClassName( + com.android.settings.cyanogenmod.PrivacySettings.class.getName()), + R.xml.privacy_settings_cyanogenmod, + com.android.settings.cyanogenmod.PrivacySettings.class.getName(), + R.drawable.ic_settings_privacy)); + + sResMap.put(com.android.settings.cyanogenmod.LockscreenSettingsAlias.class.getName(), + new SearchIndexableResource( + Ranking.getRankForClassName( + com.android.settings.cyanogenmod.LockscreenSettingsAlias.class.getName()), + NO_DATA_RES_ID, + com.android.settings.cyanogenmod.LockscreenSettingsAlias.class.getName(), + R.drawable.ic_settings_lockscreen)); } private SearchIndexableResources() { diff --git a/src/com/android/settings/sim/SimDialogActivity.java b/src/com/android/settings/sim/SimDialogActivity.java index 03a9daf..b4c7f03 100644 --- a/src/com/android/settings/sim/SimDialogActivity.java +++ b/src/com/android/settings/sim/SimDialogActivity.java @@ -21,11 +21,16 @@ import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.content.res.Resources; import android.os.Bundle; +import android.os.RemoteException; +import android.os.ServiceManager; import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; +import android.telephony.SmsManager; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; @@ -33,12 +38,16 @@ import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.util.Log; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.ListAdapter; +import android.widget.RadioButton; import android.widget.TextView; import android.widget.Toast; +import com.android.internal.telephony.IExtTelephony; +import com.android.internal.telephony.SmsApplication; import com.android.settings.R; import com.android.settings.Utils; import java.util.ArrayList; @@ -56,11 +65,17 @@ public class SimDialogActivity extends Activity { public static final int SMS_PICK = 2; public static final int PREFERRED_PICK = 3; + private boolean mHideAlwaysAsk = false; + + private IExtTelephony mExtTelephony = IExtTelephony.Stub. + asInterface(ServiceManager.getService("extphone")); + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); final Bundle extras = getIntent().getExtras(); final int dialogType = extras.getInt(DIALOG_TYPE_KEY, INVALID_PICK); + mHideAlwaysAsk = !SmsApplication.canSmsAppHandleAlwaysAsk(this) && dialogType == SMS_PICK; switch (dialogType) { case DATA_PICK: @@ -152,6 +167,7 @@ public class SimDialogActivity extends Activity { public Dialog createDialog(final Context context, final int id) { final ArrayList<String> list = new ArrayList<String>(); final SubscriptionManager subscriptionManager = SubscriptionManager.from(context); + final ArrayList<SubscriptionInfo> smsSubInfoList = new ArrayList<SubscriptionInfo>(); final List<SubscriptionInfo> subInfoList = subscriptionManager.getActiveSubscriptionInfoList(); final int selectableSubInfoLength = subInfoList == null ? 0 : subInfoList.size(); @@ -166,7 +182,12 @@ public class SimDialogActivity extends Activity { switch (id) { case DATA_PICK: sir = subInfoList.get(value); - setDefaultDataSubId(context, sir.getSubscriptionId()); + SubscriptionInfo defaultSub = subscriptionManager + .getDefaultDataSubscriptionInfo(); + if (defaultSub == null || defaultSub.getSubscriptionId() + != sir.getSubscriptionId()) { + setDefaultDataSubId(context, sir.getSubscriptionId()); + } break; case CALLS_PICK: final TelecomManager telecomManager = @@ -177,8 +198,32 @@ public class SimDialogActivity extends Activity { value < 1 ? null : phoneAccountsList.get(value - 1)); break; case SMS_PICK: - sir = subInfoList.get(value); - setDefaultSmsSubId(context, sir.getSubscriptionId()); + boolean isSmsPrompt = false; + if (value < 1) { + isSmsPrompt = false; // user knows best + setDefaultSmsSubId(context, SubscriptionManager.INVALID_SUBSCRIPTION_ID); + } else { + sir = smsSubInfoList.get(value); + if ( sir != null) { + setDefaultSmsSubId(context, sir.getSubscriptionId()); + } else { + isSmsPrompt = true; + } + Log.d(TAG, "SubscriptionInfo:" + sir); + } + Log.d(TAG, "isSmsPrompt: " + isSmsPrompt); + try { + mExtTelephony.setSMSPromptEnabled(isSmsPrompt); + } catch (RemoteException ex) { + Log.e(TAG, "RemoteException @setSMSPromptEnabled" + ex); + } catch (NullPointerException ex) { + Log.e(TAG, "NullPointerException @setSMSPromptEnabled" + ex); + } + + //Regardless, ignore the secondary telephony framework + if (mExtTelephony == null) { + SmsManager.getDefault().setSMSPromptEnabled(isSmsPrompt); + } break; default: throw new IllegalArgumentException("Invalid dialog type " @@ -200,19 +245,25 @@ public class SimDialogActivity extends Activity { } }; + int currentIndex = 0; ArrayList<SubscriptionInfo> callsSubInfoList = new ArrayList<SubscriptionInfo>(); if (id == CALLS_PICK) { final TelecomManager telecomManager = TelecomManager.from(context); final TelephonyManager telephonyManager = TelephonyManager.from(context); final Iterator<PhoneAccountHandle> phoneAccounts = telecomManager.getCallCapablePhoneAccounts().listIterator(); - + PhoneAccountHandle defaultPhoneAccount = + telecomManager.getUserSelectedOutgoingPhoneAccount(); list.add(getResources().getString(R.string.sim_calls_ask_first_prefs_title)); callsSubInfoList.add(null); while (phoneAccounts.hasNext()) { final PhoneAccount phoneAccount = telecomManager.getPhoneAccount(phoneAccounts.next()); list.add((String)phoneAccount.getLabel()); + if (defaultPhoneAccount != null && defaultPhoneAccount.equals( + phoneAccount.getAccountHandle())) { + currentIndex = list.size() - 1; + } int subId = telephonyManager.getSubIdForPhoneAccount(phoneAccount); if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { final SubscriptionInfo sir = SubscriptionManager.from(context) @@ -222,7 +273,41 @@ public class SimDialogActivity extends Activity { callsSubInfoList.add(null); } } + } else if ((id == SMS_PICK)){ + list.add(getResources().getString(R.string.sim_calls_ask_first_prefs_title)); + smsSubInfoList.add(null); + SubscriptionInfo defaultSub = subscriptionManager.getActiveSubscriptionInfo( + SubscriptionManager.getDefaultSmsSubId()); + boolean isSMSPrompt = false; + try { + isSMSPrompt = mExtTelephony.isSMSPromptEnabled(); + } catch (RemoteException | NullPointerException e) { + // Assume sms prompt is disabled + } + // External telephony interfaces may not exist, fall back to our impl + if (mExtTelephony == null) { + isSMSPrompt = SmsManager.getDefault().isSMSPromptEnabled(); + } + for (int i = 0; i < selectableSubInfoLength; ++i) { + final SubscriptionInfo sir = subInfoList.get(i); + smsSubInfoList.add(sir); + CharSequence displayName = sir.getDisplayName(); + if (displayName == null) { + displayName = ""; + } + list.add(displayName.toString()); + if (!isSMSPrompt && defaultSub != null && sir.getSubscriptionId() + == defaultSub.getSubscriptionId()) { + currentIndex = list.size() - 1; + } + } + if (mHideAlwaysAsk && currentIndex == 0) { + // unselect always ask because user can't select it. + currentIndex = -1; + } } else { + currentIndex = -1; + final int defaultDataSubId = SubscriptionManager.getDefaultDataSubId(); for (int i = 0; i < selectableSubInfoLength; ++i) { final SubscriptionInfo sir = subInfoList.get(i); CharSequence displayName = sir.getDisplayName(); @@ -230,6 +315,9 @@ public class SimDialogActivity extends Activity { displayName = ""; } list.add(displayName.toString()); + if (defaultDataSubId == sir.getSubscriptionId()) { + currentIndex = list.size() - 1; + } } } @@ -237,12 +325,6 @@ public class SimDialogActivity extends Activity { AlertDialog.Builder builder = new AlertDialog.Builder(context); - ListAdapter adapter = new SelectAccountListAdapter( - id == CALLS_PICK ? callsSubInfoList : subInfoList, - builder.getContext(), - R.layout.select_account_list_item, - arr, id); - switch (id) { case DATA_PICK: builder.setTitle(R.string.select_sim_for_data); @@ -258,6 +340,12 @@ public class SimDialogActivity extends Activity { + id + " in SIM dialog."); } + ListAdapter adapter = new SelectAccountListAdapter( + id == CALLS_PICK ? callsSubInfoList : + (id == SMS_PICK ? smsSubInfoList: subInfoList), + builder.getContext(), + R.layout.select_account_list_item, + arr, id, currentIndex); Dialog dialog = builder.setAdapter(adapter, selectionListener).create(); dialog.setOnKeyListener(keyListener); @@ -267,6 +355,11 @@ public class SimDialogActivity extends Activity { finish(); } }); + if (mHideAlwaysAsk) { + // make sure the user doesn't click out accidentally and we keep spamming them + // with dialogs + dialog.setCancelable(false); + } return dialog; @@ -278,14 +371,26 @@ public class SimDialogActivity extends Activity { private int mDialogId; private final float OPACITY = 0.54f; private List<SubscriptionInfo> mSubInfoList; + private final int mSelectionIndex; + + @Override + public boolean areAllItemsEnabled() { + return false; + } + + @Override + public boolean isEnabled(int position) { + return !(mHideAlwaysAsk && mSubInfoList.get(position) == null); + } public SelectAccountListAdapter(List<SubscriptionInfo> subInfoList, - Context context, int resource, String[] arr, int dialogId) { + Context context, int resource, String[] arr, int dialogId, int selectionIndex) { super(context, resource, arr); mContext = context; mResId = resource; mDialogId = dialogId; mSubInfoList = subInfoList; + mSelectionIndex = selectionIndex; } @Override @@ -302,31 +407,54 @@ public class SimDialogActivity extends Activity { holder.title = (TextView) rowView.findViewById(R.id.title); holder.summary = (TextView) rowView.findViewById(R.id.summary); holder.icon = (ImageView) rowView.findViewById(R.id.icon); + holder.radio = (RadioButton) rowView.findViewById(R.id.radio); rowView.setTag(holder); } else { rowView = convertView; holder = (ViewHolder) rowView.getTag(); } + final boolean enabled = isEnabled(position); final SubscriptionInfo sir = mSubInfoList.get(position); if (sir == null) { holder.title.setText(getItem(position)); - holder.summary.setText(""); + holder.summary.setText(mHideAlwaysAsk + ? getString(R.string.not_available_with_app, getCurrentSmsAppName()) + : null); + holder.summary.setVisibility(View.VISIBLE); holder.icon.setImageDrawable(getResources() .getDrawable(R.drawable.ic_live_help)); holder.icon.setAlpha(OPACITY); } else { holder.title.setText(sir.getDisplayName()); holder.summary.setText(sir.getNumber()); + holder.summary.setVisibility(View.VISIBLE); holder.icon.setImageBitmap(sir.createIconBitmap(mContext)); } + holder.radio.setChecked(position == mSelectionIndex); + holder.radio.setEnabled(enabled); + holder.title.setEnabled(enabled); + holder.summary.setEnabled(enabled); return rowView; } + private String getCurrentSmsAppName() { + try { + final ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo( + SmsApplication.getDefaultMmsApplication(getApplicationContext(), false) + .getPackageName(), 0); + return getPackageManager().getApplicationLabel(applicationInfo).toString(); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + return null; + } + private class ViewHolder { TextView title; TextView summary; ImageView icon; + RadioButton radio; } } } diff --git a/src/com/android/settings/sim/SimPreferenceDialog.java b/src/com/android/settings/sim/SimPreferenceDialog.java index f03b452..e2a6551 100644 --- a/src/com/android/settings/sim/SimPreferenceDialog.java +++ b/src/com/android/settings/sim/SimPreferenceDialog.java @@ -169,7 +169,6 @@ public class SimPreferenceDialog extends Activity { mSubInfoRecord.setIconTint(tint); mSubscriptionManager.setIconTint(tint, subscriptionId); dialog.dismiss(); - finish(); } }); @@ -177,6 +176,12 @@ public class SimPreferenceDialog extends Activity { @Override public void onClick(DialogInterface dialog, int whichButton) { dialog.dismiss(); + } + }); + + mBuilder.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { finish(); } }); diff --git a/src/com/android/settings/sim/SimSelectNotification.java b/src/com/android/settings/sim/SimSelectNotification.java index fd54e9b..16f2508 100644 --- a/src/com/android/settings/sim/SimSelectNotification.java +++ b/src/com/android/settings/sim/SimSelectNotification.java @@ -26,6 +26,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.res.Resources; +import android.os.SystemProperties; import android.provider.Settings; import android.support.v4.app.NotificationCompat; import android.telephony.SubscriptionInfo; @@ -48,8 +49,11 @@ public class SimSelectNotification extends BroadcastReceiver { final boolean isInProvisioning = Settings.Global.getInt(context.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) == 0; - // Do not create notifications on single SIM devices or when provisiong i.e. Setup Wizard. - if (numSlots < 2 || isInProvisioning) { + // Do not create notifications on single SIM devices or when provisiong i.e. Setup Wizard + // or User selection of fallback user preference is disabled. + if (numSlots < 2 || isInProvisioning || + !SystemProperties.getBoolean("persist.radio.aosp_usr_pref_sel", false)) { + Log.d(TAG, " no of slots " + numSlots + " provision = " + isInProvisioning); return; } diff --git a/src/com/android/settings/sim/SimSettings.java b/src/com/android/settings/sim/SimSettings.java index 23e6275..b97ee03 100644 --- a/src/com/android/settings/sim/SimSettings.java +++ b/src/com/android/settings/sim/SimSettings.java @@ -16,29 +16,54 @@ package com.android.settings.sim; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; +import android.content.IntentFilter; import android.content.res.Resources; import android.graphics.drawable.BitmapDrawable; +import android.os.AsyncTask; import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemProperties; import android.preference.Preference; import android.preference.PreferenceScreen; +import android.preference.PreferenceCategory; import android.provider.SearchIndexableResource; +import android.provider.Settings; import android.telephony.PhoneStateListener; +import android.telephony.SmsManager; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; import android.text.TextUtils; +import android.util.AttributeSet; import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; + import com.android.internal.logging.MetricsLogger; +import com.android.internal.telephony.PhoneConstants; import com.android.settings.RestrictedSettingsFragment; import com.android.settings.Utils; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.Indexable; import com.android.settings.R; -import android.os.SystemProperties; +import com.android.internal.telephony.IExtTelephony; import com.android.internal.telephony.TelephonyProperties; import java.util.ArrayList; @@ -48,12 +73,25 @@ public class SimSettings extends RestrictedSettingsFragment implements Indexable private static final String TAG = "SimSettings"; private static final boolean DBG = false; + // These are the list of possible values that + // IExtTelephony.getCurrentUiccCardProvisioningStatus() can return + private static final int PROVISIONED = 1; + private static final int NOT_PROVISIONED = 0; + private static final int INVALID_STATE = -1; + private static final int CARD_NOT_PRESENT = -2; + private static final String DISALLOW_CONFIG_SIM = "no_config_sim"; private static final String SIM_CARD_CATEGORY = "sim_cards"; private static final String KEY_CELLULAR_DATA = "sim_cellular_data"; private static final String KEY_CALLS = "sim_calls"; private static final String KEY_SMS = "sim_sms"; public static final String EXTRA_SLOT_ID = "slot_id"; + private static final String SIM_ACTIVITIES_CATEGORY = "sim_activities"; + private static final String MOBILE_NETWORK_CATEGORY = "mobile_network"; + private static final String KEY_PRIMARY_SUB_SELECT = "select_primary_sub"; + + private IExtTelephony mExtTelephony = IExtTelephony.Stub. + asInterface(ServiceManager.getService("extphone")); /** * By UX design we use only one Subscription Information(SubInfo) record per SIM slot. @@ -64,11 +102,31 @@ public class SimSettings extends RestrictedSettingsFragment implements Indexable private List<SubscriptionInfo> mAvailableSubInfos = null; private List<SubscriptionInfo> mSubInfoList = null; private List<SubscriptionInfo> mSelectableSubInfos = null; - private PreferenceScreen mSimCards = null; + private PreferenceCategory mSimCards = null; + private PreferenceCategory mMobileNetwork; private SubscriptionManager mSubscriptionManager; private int mNumSlots; private Context mContext; + private static AlertDialog sAlertDialog = null; + private static ProgressDialog sProgressDialog = null; + private boolean needUpdate = false; + private int mPhoneCount = TelephonyManager.getDefault().getPhoneCount(); + private int[] mUiccProvisionStatus = new int[mPhoneCount]; + private Preference mPrimarySubSelect = null; + private int[] mCallState = new int[mPhoneCount]; + private PhoneStateListener[] mPhoneStateListener = new PhoneStateListener[mPhoneCount]; + + private static final String ACTION_UICC_MANUAL_PROVISION_STATUS_CHANGED = + "org.codeaurora.intent.action.ACTION_UICC_MANUAL_PROVISION_STATUS_CHANGED"; + private static final String EXTRA_NEW_PROVISION_STATE = "newProvisionState"; + private static final String CONFIG_LTE_SUB_SELECT_MODE = "config_lte_sub_select_mode"; + private static final String CONFIG_PRIMARY_SUB_SETABLE = "config_primary_sub_setable"; + private static final String CONFIG_CURRENT_PRIMARY_SUB = "config_current_primary_sub"; + // If this config set to '1' DDS option would be greyed out on UI. + // For more info pls refere framework code. + private static final String CONFIG_DISABLE_DDS_PREFERENCE = "config_disable_dds_preference"; + public SimSettings() { super(DISALLOW_CONFIG_SIM); } @@ -88,11 +146,23 @@ public class SimSettings extends RestrictedSettingsFragment implements Indexable (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE); addPreferencesFromResource(R.xml.sim_settings); + mPrimarySubSelect = (Preference) findPreference(KEY_PRIMARY_SUB_SELECT); mNumSlots = tm.getSimCount(); - mSimCards = (PreferenceScreen)findPreference(SIM_CARD_CATEGORY); + mSimCards = (PreferenceCategory)findPreference(SIM_CARD_CATEGORY); + mMobileNetwork = (PreferenceCategory) findPreference(MOBILE_NETWORK_CATEGORY); mAvailableSubInfos = new ArrayList<SubscriptionInfo>(mNumSlots); mSelectableSubInfos = new ArrayList<SubscriptionInfo>(); SimSelectNotification.cancelNotification(getActivity()); + + IntentFilter intentFilter = new IntentFilter(ACTION_UICC_MANUAL_PROVISION_STATUS_CHANGED); + mContext.registerReceiver(mReceiver, intentFilter); + } + + @Override + public void onDestroy() { + mContext.unregisterReceiver(mReceiver); + Log.d(TAG,"on onDestroy"); + super.onDestroy(); } private final SubscriptionManager.OnSubscriptionsChangedListener mOnSubscriptionsChangeListener @@ -100,7 +170,10 @@ public class SimSettings extends RestrictedSettingsFragment implements Indexable @Override public void onSubscriptionsChanged() { if (DBG) log("onSubscriptionsChanged:"); - updateSubscriptions(); + Activity activity = getActivity(); + if (activity != null && !activity.isFinishing()) { + updateSubscriptions(); + } } }; @@ -112,19 +185,34 @@ public class SimSettings extends RestrictedSettingsFragment implements Indexable mSimCards.removePreference(pref); } } + mMobileNetwork.removeAll(); mAvailableSubInfos.clear(); mSelectableSubInfos.clear(); for (int i = 0; i < mNumSlots; ++i) { final SubscriptionInfo sir = mSubscriptionManager .getActiveSubscriptionInfoForSimSlotIndex(i); - SimPreference simPreference = new SimPreference(mContext, sir, i); + int subscriptionId = sir != null ? + sir.getSubscriptionId() : + SubscriptionManager.INVALID_SUBSCRIPTION_ID; + SimPreference simPreference = new SimEnablerPreference(mContext, sir, i); simPreference.setOrder(i-mNumSlots); mSimCards.addPreference(simPreference); mAvailableSubInfos.add(sir); - if (sir != null) { + if (sir != null && (isSubProvisioned(i))) { mSelectableSubInfos.add(sir); } + Intent mobileNetworkIntent = new Intent(); + mobileNetworkIntent.setComponent(new ComponentName( + "com.android.phone", "com.android.phone.MobileNetworkSettings")); + SubscriptionManager.putPhoneIdAndSubIdExtra(mobileNetworkIntent, i, subscriptionId); + Preference mobileNetworkPref = new Preference(getActivity()); + mobileNetworkPref.setTitle( + getString(R.string.sim_mobile_network_settings_title, (i + 1))); + mobileNetworkPref.setIntent(mobileNetworkIntent); + mobileNetworkPref.setEnabled( + subscriptionId != SubscriptionManager.INVALID_SUBSCRIPTION_ID); + mMobileNetwork.addPreference(mobileNetworkPref); } updateAllOptions(); } @@ -152,16 +240,29 @@ public class SimSettings extends RestrictedSettingsFragment implements Indexable private void updateSmsValues() { final Preference simPref = findPreference(KEY_SMS); - final SubscriptionInfo sir = mSubscriptionManager.getDefaultSmsSubscriptionInfo(); simPref.setTitle(R.string.sms_messages_title); - if (DBG) log("[updateSmsValues] mSubInfoList=" + mSubInfoList); - - if (sir != null) { + boolean isSMSPrompt = false; + SubscriptionInfo sir = mSubscriptionManager.getActiveSubscriptionInfo( + mSubscriptionManager.getDefaultSmsSubId()); + try { + isSMSPrompt = mExtTelephony.isSMSPromptEnabled(); + } catch (RemoteException ex) { + loge("RemoteException @isSMSPromptEnabled" + ex); + } catch (NullPointerException ex) { + loge("NullPointerException @isSMSPromptEnabled" + ex); + } + // External telephony interfaces may not exist, fall back to our impl + if (mExtTelephony == null) { + isSMSPrompt = SmsManager.getDefault().isSMSPromptEnabled(); + } + log("[updateSmsValues] isSMSPrompt: " + isSMSPrompt); + if (isSMSPrompt || sir == null) { + simPref.setSummary(mContext.getResources().getString( + R.string.sim_calls_ask_first_prefs_title)); + } else { simPref.setSummary(sir.getDisplayName()); - } else if (sir == null) { - simPref.setSummary(R.string.sim_selection_required_pref); } - simPref.setEnabled(mSelectableSubInfos.size() >= 1); + simPref.setEnabled(mSelectableSubInfos.size() > 1); } private void updateCellularDataValues() { @@ -175,7 +276,13 @@ public class SimSettings extends RestrictedSettingsFragment implements Indexable } else if (sir == null) { simPref.setSummary(R.string.sim_selection_required_pref); } - simPref.setEnabled(mSelectableSubInfos.size() >= 1); + + boolean callStateIdle = isCallStateIdle(); + final boolean ecbMode = SystemProperties.getBoolean( + TelephonyProperties.PROPERTY_INECM_MODE, false); + // Enable data preference in msim mode and call state idle + simPref.setEnabled((mSelectableSubInfos.size() > 1) && !disableDds() + && callStateIdle && !ecbMode); } private void updateCallValues() { @@ -197,36 +304,25 @@ public class SimSettings extends RestrictedSettingsFragment implements Indexable public void onResume() { super.onResume(); mSubscriptionManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangeListener); - final TelephonyManager tm = - (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE); - tm.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); + initLTEPreference(); updateSubscriptions(); + listen(); } @Override public void onPause() { super.onPause(); mSubscriptionManager.removeOnSubscriptionsChangedListener(mOnSubscriptionsChangeListener); - final TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); - tm.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE); - } + unRegisterPhoneStateListener(); - private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() { - // Disable Sim selection for Data when voice call is going on as changing the default data - // sim causes a modem reset currently and call gets disconnected - // ToDo : Add subtext on disabled preference to let user know that default data sim cannot - // be changed while call is going on - @Override - public void onCallStateChanged(int state, String incomingNumber) { - if (DBG) log("PhoneStateListener.onCallStateChanged: state=" + state); - final Preference pref = findPreference(KEY_CELLULAR_DATA); - if (pref != null) { - final boolean ecbMode = SystemProperties.getBoolean( - TelephonyProperties.PROPERTY_INECM_MODE, false); - pref.setEnabled((state == TelephonyManager.CALL_STATE_IDLE) && !ecbMode); + for (int i = 0; i < mSimCards.getPreferenceCount(); ++i) { + Preference pref = mSimCards.getPreference(i); + if (pref instanceof SimEnablerPreference) { + // Calling cleanUp() here to dismiss/cleanup any pending dialog exists. + ((SimEnablerPreference)pref).cleanUpPendingDialogs(); } } - }; + } @Override public boolean onPreferenceTreeClick(final PreferenceScreen preferenceScreen, @@ -239,25 +335,39 @@ public class SimSettings extends RestrictedSettingsFragment implements Indexable Intent newIntent = new Intent(context, SimPreferenceDialog.class); newIntent.putExtra(EXTRA_SLOT_ID, ((SimPreference)preference).getSlotId()); startActivity(newIntent); + return true; } else if (findPreference(KEY_CELLULAR_DATA) == preference) { intent.putExtra(SimDialogActivity.DIALOG_TYPE_KEY, SimDialogActivity.DATA_PICK); context.startActivity(intent); + return true; } else if (findPreference(KEY_CALLS) == preference) { intent.putExtra(SimDialogActivity.DIALOG_TYPE_KEY, SimDialogActivity.CALLS_PICK); context.startActivity(intent); + return true; } else if (findPreference(KEY_SMS) == preference) { intent.putExtra(SimDialogActivity.DIALOG_TYPE_KEY, SimDialogActivity.SMS_PICK); context.startActivity(intent); + return true; } - return true; + return false; + } + private void loge(String msg) { + if (DBG) Log.e(TAG + "message", msg); + } + + private void simEnablerUpdate() { + if (isAdded()) { + updateAllOptions(); + } else { + needUpdate = true; + } } private class SimPreference extends Preference { - private SubscriptionInfo mSubInfoRecord; - private int mSlotId; + SubscriptionInfo mSubInfoRecord; + int mSlotId; Context mContext; - public SimPreference(Context context, SubscriptionInfo subInfoRecord, int slotId) { super(context); @@ -267,6 +377,9 @@ public class SimSettings extends RestrictedSettingsFragment implements Indexable setKey("sim" + mSlotId); update(); } + public SimPreference (Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } public void update() { final Resources res = mContext.getResources(); @@ -292,6 +405,507 @@ public class SimSettings extends RestrictedSettingsFragment implements Indexable private int getSlotId() { return mSlotId; } + + @Override + protected void onAttachedToActivity() { + super.onAttachedToActivity(); + if (needUpdate) { + needUpdate = false; + updateAllOptions(); + } + } + } + + // This is to show SIM Enable options on/off on UI for user selection. + // User can activate/de-activate through SIM on/off options. + private class SimEnablerPreference extends SimPreference implements OnCheckedChangeListener { + + private String TAG = "SimEnablerPreference"; + private static final boolean DBG = true; + + private static final int EVT_UPDATE = 1; + private static final int EVT_SHOW_RESULT_DLG = 2; + private static final int EVT_SHOW_PROGRESS_DLG = 3; + private static final int EVT_PROGRESS_DLG_TIME_OUT = 4; + + private static final int CONFIRM_ALERT_DLG_ID = 1; + private static final int ERROR_ALERT_DLG_ID = 2; + private static final int RESULT_ALERT_DLG_ID = 3; + + private static final int REQUEST_SUCCESS = 0; + private static final int GENERIC_FAILURE = -1; + private static final int INVALID_INPUT = -2; + private static final int REQUEST_IN_PROGRESS = -3; + + + + private static final String DISPLAY_NUMBERS_TYPE = "display_numbers_type"; + + private SubscriptionInfo mSir; + private boolean mCurrentUiccProvisionState; + private boolean mIsChecked; + + private boolean mCmdInProgress = false; + private int mSwitchVisibility = View.VISIBLE; + private CompoundButton mSwitch; + //Delay for progress dialog to dismiss + private static final int PROGRESS_DLG_TIME_OUT = 30000; + private static final int MSG_DELAY_TIME = 2000; + + private IExtTelephony mExtTelephony; + + + public SimEnablerPreference(Context context, SubscriptionInfo sir, int slotId) { + super(context, (AttributeSet)null, + com.android.internal.R.attr.checkBoxPreferenceStyle); + logd("Contructor..Enter"); + mContext = context; + mSlotId = slotId; + mSir = sir; + mSubInfoRecord = sir; + if (mContext.getResources().getBoolean(R.bool.config_custom_multi_sim_checkbox)) { + setWidgetLayoutResource(R.layout.custom_sim_checkbox); + } else { + setWidgetLayoutResource(R.layout.custom_sim_switch); + } + + mExtTelephony = IExtTelephony.Stub.asInterface(ServiceManager.getService("extphone")); + + setSwitchVisibility(View.VISIBLE); + setKey("sim" + mSlotId); + update(); + } + + private void sendMessage(int event, Handler handler, int delay) { + Message message = handler.obtainMessage(event); + handler.sendMessageDelayed(message, delay); + } + + private void sendMessage(int event, Handler handler, int delay, int arg1, int arg2) { + Message message = handler.obtainMessage(event, arg1, arg2); + handler.sendMessageDelayed(message, delay); + } + + private boolean hasCard() { + return TelephonyManager.getDefault().hasIccCard(mSlotId); + } + + private boolean isAirplaneModeOn() { + return (Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, 0) != 0); + } + + private int getProvisionStatus(int slotId) { + return mUiccProvisionStatus[slotId]; + } + + @Override + protected void onBindView(View view) { + super.onBindView(view); + logd("onBindView...."); + mSwitch = (CompoundButton) view.findViewById(R.id.sub_switch_widget); + mSwitch.setOnCheckedChangeListener(this); + update(); + // now use other config screen to active/deactive sim card\ + mSwitch.setVisibility(mSwitchVisibility); + + // Disable manual provisioning option to user when + // device is in Airplane mode. Hide it if the extphone framework + // is not present, as the operation relies on said framework. + if (mExtTelephony == null || + !mContext.getResources().getBoolean(R.bool.config_enableManualSubProvisioning)) { + mSwitch.setVisibility(View.GONE); + } else { + mSwitch.setVisibility(View.VISIBLE); + mSwitch.setEnabled(!isAirplaneModeOn() && isCurrentSubValid()); + } + } + + @Override + public void update() { + final Resources res = mContext.getResources(); + logd("update()" + mSir); + try { + //get current provision state of the SIM. + mUiccProvisionStatus[mSlotId] = + mExtTelephony.getCurrentUiccCardProvisioningStatus(mSlotId); + } catch (RemoteException ex) { + mUiccProvisionStatus[mSlotId] = INVALID_STATE; + loge("Failed to get pref, slotId: "+ mSlotId +" Exception: " + ex); + } catch (NullPointerException ex) { + mUiccProvisionStatus[mSlotId] = INVALID_STATE; + loge("Failed to get pref, slotId: "+ mSlotId +" Exception: " + ex); + } + + if (mUiccProvisionStatus[mSlotId] == INVALID_STATE) { + mUiccProvisionStatus[mSlotId] = PROVISIONED; + } + + boolean isSubValid = isCurrentSubValid(); + setEnabled(isSubValid); + + logd("update: isSubValid " + isSubValid + " provision status[" + + mSlotId + "] = " + mUiccProvisionStatus[mSlotId]); + setTitle(res.getString(R.string.sim_card_number_title, mSlotId + 1)); + if (isSubValid) { + updateSummary(); + setIcon(new BitmapDrawable(res, (mSir.createIconBitmap(mContext)))); + } else { + setSummary(res.getString(R.string.sim_slot_empty)); + } + } + + // This method returns true if SubScription record corresponds to this + // Preference screen has a valid SIM and slot index/SubId. + private boolean isCurrentSubValid() { + boolean isSubValid = false; + if (hasCard()) { + List<SubscriptionInfo> sirList = + mSubscriptionManager.getActiveSubscriptionInfoList(); + if (sirList != null ) { + for (SubscriptionInfo sir : sirList) { + if (sir != null && mSlotId == sir.getSimSlotIndex()) { + mSir = sir; + break; + } + } + if (mSir != null && + SubscriptionManager.isValidSubscriptionId(mSir.getSubscriptionId()) && + mSir.getSimSlotIndex() >= 0 && + getProvisionStatus(mSir.getSimSlotIndex()) >= 0) { + isSubValid = true; + } + } + } + return isSubValid; + } + + public void setSwitchVisibility (int visibility) { + mSwitchVisibility = visibility; + } + + // Based on the received SIM provision state this method + // sets the check box on Sim Preference UI and updates new + // state to mCurrentUiccProvisionState. + private void setChecked(boolean uiccProvisionState) { + logd("setChecked: uiccProvisionState " + uiccProvisionState + "sir:" + mSir); + if (mSwitch != null) { + mSwitch.setOnCheckedChangeListener(null); + // Do not update update checkstatus again in progress + if (!mCmdInProgress) { + mSwitch.setChecked(uiccProvisionState); + } + mSwitch.setOnCheckedChangeListener(this); + mCurrentUiccProvisionState = uiccProvisionState; + } + } + + private void updateSummary() { + Resources res = mContext.getResources(); + String summary; + boolean isActivated = (getProvisionStatus(mSir.getSimSlotIndex()) == PROVISIONED); + logd("updateSummary: subId " + mSir.getSubscriptionId() + " isActivated = " + + isActivated + " slot id = " + mSlotId); + + String displayName = mSir == null ? "SIM" : (String)mSir.getDisplayName(); + if (isActivated) { + summary = displayName; + if (!TextUtils.isEmpty(mSir.getNumber())) { + summary = displayName + " - " + mSir.getNumber(); + } + } else { + summary = mContext.getString(R.string.sim_enabler_summary, displayName, + res.getString(hasCard() ? R.string.sim_disabled : R.string.sim_missing)); + } + + setSummary(summary); + setChecked(isActivated); + } + + + /** + * get number of Subs provisioned on the device + * @param context + * @return + */ + public int getNumOfSubsProvisioned() { + int activeSubInfoCount = 0; + List<SubscriptionInfo> subInfoLists = + mSubscriptionManager.getActiveSubscriptionInfoList(); + if (subInfoLists != null) { + for (SubscriptionInfo subInfo : subInfoLists) { + if (getProvisionStatus(subInfo.getSimSlotIndex()) + == PROVISIONED) activeSubInfoCount++; + } + } + return activeSubInfoCount; + } + + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + mIsChecked = isChecked; + logd("onClick: " + isChecked); + + handleUserRequest(); + } + + // This internal method called when user changes preference from UI + // 1. For activation/deactivation request from User, if device in APM mode + // OR if voice call active on any SIM it dispay error dialog and returns. + // 2. For deactivation request it returns error dialog if only one SUB in + // active state. + // 3. In other cases it sends user request to framework. + synchronized private void handleUserRequest() { + if (isAirplaneModeOn()) { + // do nothing but warning + logd("APM is on, EXIT!"); + showAlertDialog(ERROR_ALERT_DLG_ID, R.string.sim_enabler_airplane_on); + return; + } + for (int i = 0; i < mPhoneCount; i++) { + int[] subId = SubscriptionManager.getSubId(i); + //when voice call in progress, subscription can't be activate/deactivate. + if (TelephonyManager.getDefault().getCallState(subId[0]) + != TelephonyManager.CALL_STATE_IDLE) { + logd("Call state for phoneId: " + i + " is not idle, EXIT!"); + showAlertDialog(ERROR_ALERT_DLG_ID, R.string.sim_enabler_in_call); + return; + } + } + + if (!mIsChecked) { + if (getNumOfSubsProvisioned() > 1) { + logd("More than one sub is active, Deactivation possible."); + showAlertDialog(CONFIRM_ALERT_DLG_ID, 0); + } else { + logd("Only one sub is active. Deactivation not possible."); + showAlertDialog(ERROR_ALERT_DLG_ID, R.string.sim_enabler_both_inactive); + return; + } + } else { + logd("Activate the sub"); + sendUiccProvisioningRequest(); + } + } + + private void sendUiccProvisioningRequest() { + if (!mSwitch.isEnabled()) { + return; + } + new SimEnablerDisabler().execute(); + } + + private class SimEnablerDisabler extends AsyncTask<Void, Void, Integer> { + + int newProvisionedState = NOT_PROVISIONED; + + @Override + protected void onPreExecute() { + super.onPreExecute(); + mCmdInProgress = true; + showProgressDialog(); + setEnabled(false); + } + + @Override + protected Integer doInBackground(Void... params) { + int result = -1; + newProvisionedState = NOT_PROVISIONED; + try { + if (mIsChecked) { + result = mExtTelephony.activateUiccCard(mSir.getSimSlotIndex()); + newProvisionedState = PROVISIONED; + } else { + result = mExtTelephony.deactivateUiccCard(mSir.getSimSlotIndex()); + } + } catch (RemoteException ex) { + loge("Activate sub failed " + result + " phoneId " + mSir.getSimSlotIndex()); + } catch (NullPointerException ex) { + loge("Failed to activate sub Exception: " + ex); + } + return result; + } + + @Override + protected void onPostExecute(Integer result) { + processSetUiccDone(result.intValue(), newProvisionedState); + } + } + + private void processSetUiccDone(int result, int newProvisionedState) { + sendMessage(EVT_UPDATE, mHandler, MSG_DELAY_TIME); + sendMessage(EVT_SHOW_RESULT_DLG, mHandler, MSG_DELAY_TIME, result, newProvisionedState); + mCmdInProgress = false; + } + + private void showAlertDialog(int dialogId, int msgId) { + + String title = mSir == null ? "SUB" : mSir.getDisplayName().toString(); + // Confirm only one AlertDialog instance to show. + dismissDialog(sAlertDialog); + dismissDialog(sProgressDialog); + AlertDialog.Builder builder = new AlertDialog.Builder(mContext) + .setTitle(title); + + switch(dialogId) { + case CONFIRM_ALERT_DLG_ID: + String message; + if (mContext.getResources().getBoolean( + R.bool.confirm_to_switch_data_service)) { + if (SubscriptionManager.getDefaultDataSubId() == + mSir.getSubscriptionId()) { + message = mContext.getString( + R.string.sim_enabler_need_switch_data_service, + getProvisionedSlotId(mContext)); + } else { + message = mContext.getString(R.string.sim_enabler_need_disable_sim); + } + builder.setTitle(R.string.sim_enabler_will_disable_sim_title); + } else { + message = mContext.getString(R.string.sim_enabler_need_disable_sim); + } + builder.setMessage(message); + builder.setPositiveButton(android.R.string.ok, mDialogClickListener); + builder.setNegativeButton(android.R.string.no, mDialogClickListener); + builder.setOnCancelListener(mDialogCanceListener); + break; + + case ERROR_ALERT_DLG_ID: + builder.setMessage(mContext.getString(msgId)); + builder.setNeutralButton(android.R.string.ok, mDialogClickListener); + builder.setCancelable(false); + break; + + case RESULT_ALERT_DLG_ID: + String msg = mCurrentUiccProvisionState ? + mContext.getString(R.string.sub_activate_success) : + mContext.getString(R.string.sub_deactivate_success); + builder.setMessage(msg); + builder.setNeutralButton(android.R.string.ok, null); + break; + default: + break; + } + + sAlertDialog = builder.create(); + sAlertDialog.setCanceledOnTouchOutside(false); + sAlertDialog.show(); + } + + private int getProvisionedSlotId(Context context) { + int activeSlotId = -1; + List<SubscriptionInfo> subInfoLists = + mSubscriptionManager.getActiveSubscriptionInfoList(); + if (subInfoLists != null) { + for (SubscriptionInfo subInfo : subInfoLists) { + if (getProvisionStatus(subInfo.getSimSlotIndex()) == PROVISIONED + && subInfo.getSubscriptionId() != mSir.getSubscriptionId()) + activeSlotId = subInfo.getSimSlotIndex() + 1; + } + } + return activeSlotId; + } + + private void showProgressDialog() { + String title = mSir == null ? "SUB" : mSir.getDisplayName().toString(); + + String msg = mContext.getString(mIsChecked ? R.string.sim_enabler_enabling + : R.string.sim_enabler_disabling); + dismissDialog(sProgressDialog); + sProgressDialog = new ProgressDialog(mContext); + sProgressDialog.setIndeterminate(true); + sProgressDialog.setTitle(title); + sProgressDialog.setMessage(msg); + sProgressDialog.setCancelable(false); + sProgressDialog.setCanceledOnTouchOutside(false); + sProgressDialog.show(); + + sendMessage(EVT_PROGRESS_DLG_TIME_OUT, mHandler, PROGRESS_DLG_TIME_OUT); + } + + private void dismissDialog(Dialog dialog) { + if((dialog != null) && (dialog.isShowing())) { + dialog.dismiss(); + dialog = null; + } + } + + public void cleanUpPendingDialogs() { + dismissDialog(sProgressDialog); + dismissDialog(sAlertDialog); + } + + private DialogInterface.OnClickListener mDialogClickListener = new DialogInterface + .OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + if (which == DialogInterface.BUTTON_POSITIVE) { + dismissDialog(sAlertDialog); + sendUiccProvisioningRequest(); + } else if (which == DialogInterface.BUTTON_NEGATIVE) { + update(); + } else if (which == DialogInterface.BUTTON_NEUTRAL) { + update(); + } + } + }; + + private DialogInterface.OnCancelListener mDialogCanceListener = new DialogInterface + .OnCancelListener() { + public void onCancel(DialogInterface dialog) { + update(); + } + }; + + + private Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + + switch(msg.what) { + case EVT_UPDATE: + simEnablerUpdate(); + + case EVT_SHOW_RESULT_DLG: + int result = msg.arg1; + int newProvisionedState = msg.arg2; + logd("EVT_SHOW_RESULT_DLG result: " + result + + " new provisioned state " + newProvisionedState); + update(); + if (result != REQUEST_SUCCESS) { + int msgId = (newProvisionedState == PROVISIONED) ? + R.string.sub_activate_failed : + R.string.sub_deactivate_failed; + showAlertDialog(ERROR_ALERT_DLG_ID, msgId); + } else { + showAlertDialog(RESULT_ALERT_DLG_ID, 0); + } + mHandler.removeMessages(EVT_PROGRESS_DLG_TIME_OUT); + break; + + case EVT_SHOW_PROGRESS_DLG: + logd("EVT_SHOW_PROGRESS_DLG"); + showProgressDialog(); + break; + + case EVT_PROGRESS_DLG_TIME_OUT: + logd("EVT_PROGRESS_DLG_TIME_OUT"); + dismissDialog(sProgressDialog); + // Must update UI when time out + update(); + break; + + default: + break; + } + } + }; + + private void logd(String msg) { + if (DBG) Log.d(TAG + "(" + mSlotId + ")", msg); + } + + private void loge(String msg) { + Log.e(TAG + "(" + mSlotId + ")", msg); + } } // Returns the line1Number. Line1number should always be read from TelephonyManager since it can @@ -326,4 +940,125 @@ public class SimSettings extends RestrictedSettingsFragment implements Indexable return result; } }; + + // Internal utility, returns true if Uicc card + // corresponds to given slotId is provisioned. + private boolean isSubProvisioned(int slotId) { + boolean retVal = false; + + if (mUiccProvisionStatus[slotId] == PROVISIONED) retVal = true; + return retVal; + } + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + Log.d(TAG, "Intent received: " + action); + if (ACTION_UICC_MANUAL_PROVISION_STATUS_CHANGED.equals(action)) { + int phoneId = intent.getIntExtra(PhoneConstants.PHONE_KEY, + SubscriptionManager.INVALID_SUBSCRIPTION_ID); + int newProvisionedState = intent.getIntExtra(EXTRA_NEW_PROVISION_STATE, + NOT_PROVISIONED); + updateSubscriptions(); + Log.d(TAG, "Received ACTION_UICC_MANUAL_PROVISION_STATUS_CHANGED on phoneId: " + + phoneId + " new sub state " + newProvisionedState); + } + } + }; + + // When primarycard feature enabled this provides menu option for user + // to view/select current primary slot. + private void initLTEPreference() { + boolean isPrimarySubFeatureEnable = + SystemProperties.getBoolean("persist.radio.primarycard", false); + boolean primarySetable = Settings.Global.getInt(mContext.getContentResolver(), + CONFIG_PRIMARY_SUB_SETABLE, 0) == 1; + + log("isPrimarySubFeatureEnable :" + isPrimarySubFeatureEnable + + " primarySetable :" + primarySetable); + + if (!isPrimarySubFeatureEnable || !primarySetable) { + final PreferenceCategory simActivities = + (PreferenceCategory) findPreference(SIM_ACTIVITIES_CATEGORY); + simActivities.removePreference(mPrimarySubSelect); + return; + } + int currentPrimarySlot = Settings.Global.getInt(mContext.getContentResolver(), + CONFIG_CURRENT_PRIMARY_SUB, SubscriptionManager.INVALID_SIM_SLOT_INDEX); + boolean isManualMode = Settings.Global.getInt(mContext.getContentResolver(), + CONFIG_LTE_SUB_SELECT_MODE, 1) == 0; + + log("init LTE primary slot : " + currentPrimarySlot + " isManualMode :" + isManualMode); + + if (SubscriptionManager.isValidSlotId(currentPrimarySlot)) { + final SubscriptionInfo subInfo = mSubscriptionManager + .getActiveSubscriptionInfoForSimSlotIndex(currentPrimarySlot); + CharSequence lteSummary = (subInfo == null ) ? null : subInfo.getDisplayName(); + mPrimarySubSelect.setSummary(lteSummary); + } else { + mPrimarySubSelect.setSummary(""); + } + mPrimarySubSelect.setEnabled(isManualMode); + } + + private boolean disableDds() { + boolean disableDds = Settings.Global.getInt(mContext.getContentResolver(), + CONFIG_DISABLE_DDS_PREFERENCE, 0) == 1; + + log(" config disable dds = " + disableDds); + return disableDds; + } + + private void listen() { + TelephonyManager tm = + (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE); + if (mSelectableSubInfos.size() > 1) { + Log.d(TAG, "Register for call state change"); + for (int i = 0; i < mPhoneCount; i++) { + int subId = mSelectableSubInfos.get(i).getSubscriptionId(); + tm.listen(getPhoneStateListener(i, subId), + PhoneStateListener.LISTEN_CALL_STATE); + } + } + } + + private void unRegisterPhoneStateListener() { + TelephonyManager tm = + (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE); + for (int i = 0; i < mPhoneCount; i++) { + if (mPhoneStateListener[i] != null) { + tm.listen(mPhoneStateListener[i], PhoneStateListener.LISTEN_NONE); + mPhoneStateListener[i] = null; + } + } + } + + private PhoneStateListener getPhoneStateListener(int phoneId, int subId) { + // Disable Sim selection for Data when voice call is going on as changing the default data + // sim causes a modem reset currently and call gets disconnected + // ToDo : Add subtext on disabled preference to let user know that default data sim cannot + // be changed while call is going on + final int i = phoneId; + mPhoneStateListener[phoneId] = new PhoneStateListener(subId) { + @Override + public void onCallStateChanged(int state, String incomingNumber) { + if (DBG) log("PhoneStateListener.onCallStateChanged: state=" + state); + mCallState[i] = state; + updateCellularDataValues(); + } + }; + return mPhoneStateListener[phoneId]; + } + + private boolean isCallStateIdle() { + boolean callStateIdle = true; + for (int i = 0; i < mCallState.length; i++) { + if (TelephonyManager.CALL_STATE_IDLE != mCallState[i]) { + callStateIdle = false; + } + } + Log.d(TAG, "isCallStateIdle " + callStateIdle); + return callStateIdle; + } } diff --git a/src/com/android/settings/users/EditUserInfoController.java b/src/com/android/settings/users/EditUserInfoController.java index ab77101..a4c07bd 100644 --- a/src/com/android/settings/users/EditUserInfoController.java +++ b/src/com/android/settings/users/EditUserInfoController.java @@ -37,8 +37,8 @@ import android.widget.EditText; import android.widget.ImageView; import com.android.settings.R; -import com.android.settings.Utils; -import com.android.settings.drawable.CircleFramedDrawable; +import com.android.settingslib.Utils; +import com.android.settingslib.drawable.CircleFramedDrawable; /** * This class encapsulates a Dialog for editing the user nickname and photo. diff --git a/src/com/android/settings/users/EditUserPhotoController.java b/src/com/android/settings/users/EditUserPhotoController.java index 82e550e..f9f867d 100644 --- a/src/com/android/settings/users/EditUserPhotoController.java +++ b/src/com/android/settings/users/EditUserPhotoController.java @@ -45,7 +45,7 @@ import android.widget.ListAdapter; import android.widget.ListPopupWindow; import com.android.settings.R; -import com.android.settings.drawable.CircleFramedDrawable; +import com.android.settingslib.drawable.CircleFramedDrawable; import java.io.File; import java.io.FileNotFoundException; @@ -343,4 +343,4 @@ public class EditUserPhotoController { return title; } } -}
\ No newline at end of file +} diff --git a/src/com/android/settings/users/RestrictedProfileSettings.java b/src/com/android/settings/users/RestrictedProfileSettings.java index b0b86e4..2531aaa 100644 --- a/src/com/android/settings/users/RestrictedProfileSettings.java +++ b/src/com/android/settings/users/RestrictedProfileSettings.java @@ -86,7 +86,7 @@ public class RestrictedProfileSettings extends AppRestrictionsFragment } else { ((TextView) mHeaderView.findViewById(android.R.id.title)).setText(info.name); ((ImageView) mHeaderView.findViewById(android.R.id.icon)).setImageDrawable( - Utils.getUserIcon(getActivity(), mUserManager, info)); + com.android.settingslib.Utils.getUserIcon(getActivity(), mUserManager, info)); } } diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java index 1278d54..a172766 100644 --- a/src/com/android/settings/users/UserSettings.java +++ b/src/com/android/settings/users/UserSettings.java @@ -65,10 +65,10 @@ import com.android.settings.SelectableEditTextPreference; import com.android.settings.SettingsActivity; import com.android.settings.SettingsPreferenceFragment; import com.android.settings.Utils; -import com.android.settings.drawable.CircleFramedDrawable; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.Indexable; import com.android.settings.search.SearchIndexableRaw; +import com.android.settingslib.drawable.CircleFramedDrawable; import java.util.ArrayList; import java.util.Collections; diff --git a/src/com/android/settings/utils/TelephonyUtils.java b/src/com/android/settings/utils/TelephonyUtils.java new file mode 100644 index 0000000..35aab79 --- /dev/null +++ b/src/com/android/settings/utils/TelephonyUtils.java @@ -0,0 +1,231 @@ +package com.android.settings.utils; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.os.PersistableBundle; +import android.telephony.CarrierConfigManager; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.Log; + +import com.android.internal.telephony.PhoneConstants; +import com.android.internal.telephony.RILConstants; + +/** + * Helper class which has the same logic as MobileNetworkSettings to display the same + * network modes and strings as it does. + */ +public class TelephonyUtils { + + private static final String TAG = TelephonyUtils.class.getSimpleName(); + + // from MobileNetworkSettings + public static final String ACTION_PICK_NETWORK_MODE = + "cyanogenmod.platform.intent.action.NETWORK_MODE_PICKER"; + public static final String EXTRA_NONE_TEXT = "network_mode_picker::neutral_text"; + public static final String EXTRA_SHOW_NONE = "network_mode_picker::show_none"; + public static final String EXTRA_INITIAL_NETWORK_VALUE = "network_mode_picker::selected_mode"; + public static final String EXTRA_NETWORK_PICKER_PICKED_VALUE = + "network_mode_picker::chosen_value"; + public static final String EXTRA_SUBID = "network_mode_picker::sub_id"; + + public static String getNetworkModeString(Context context, int networkMode, int subId) { + return getNetworkModeString(context, + networkMode, + TelephonyManager.from(context).getCurrentPhoneType(subId) /* phone type */, + show4GForLTE(context)/* show 4G for lte */, + isSupportTdscdma(context, subId)/* supports TDS CDMA*/, + isGlobalCDMA(context, subId, isLteOnCdma(context, subId))/* is Global cdma */, + isWorldMode(context)/* is worldwide */); + } + + public static String getNetworkModeString(Context context, int networkMode, + int phoneType, boolean show4GForLTE, boolean isSupportTdsCdma, boolean isGlobalCdma, + boolean isWorldMode) { + String r = null; + switch (networkMode) { + case RILConstants.NETWORK_MODE_TDSCDMA_WCDMA: + case RILConstants.NETWORK_MODE_TDSCDMA_GSM_WCDMA: + case RILConstants.NETWORK_MODE_TDSCDMA_GSM: + r = "network_3G"; + break; + case RILConstants.NETWORK_MODE_WCDMA_ONLY: + r = "network_wcdma_only"; + break; + case RILConstants.NETWORK_MODE_GSM_UMTS: + r = "network_gsm_umts"; + break; + case RILConstants.NETWORK_MODE_WCDMA_PREF: + r = "network_wcdma_pref"; + break; + case RILConstants.NETWORK_MODE_GSM_ONLY: + r = "network_gsm_only"; + break; + case RILConstants.NETWORK_MODE_LTE_GSM_WCDMA: + r = (show4GForLTE) + ? "network_4G" : "network_lte_gsm_wcdma"; + break; + case RILConstants.NETWORK_MODE_LTE_WCDMA: + r = (show4GForLTE) + ? "network_4G" : "network_lte_cdma"; + break; + case RILConstants.NETWORK_MODE_LTE_ONLY: + r = (show4GForLTE) + ? "network_4G_only" : "network_lte_only"; + break; + case RILConstants.NETWORK_MODE_LTE_CDMA_EVDO: + r = (show4GForLTE) + ? "network_4G" : "network_lte_cdma_and_evdo"; + break; + case RILConstants.NETWORK_MODE_TDSCDMA_CDMA_EVDO_GSM_WCDMA: + r = "network_3G"; + break; + case RILConstants.NETWORK_MODE_CDMA: + r = "network_cdma"; + break; + case RILConstants.NETWORK_MODE_EVDO_NO_CDMA: + r = "network_evdo_no_cdma"; + break; + case RILConstants.NETWORK_MODE_GLOBAL: + r = "network_3g_global"; + break; + case RILConstants.NETWORK_MODE_CDMA_NO_EVDO: + r = "network_cdma_no_evdo"; + break; + case RILConstants.NETWORK_MODE_TDSCDMA_ONLY: + r = "network_tdscdma"; + break; + case RILConstants.NETWORK_MODE_LTE_TDSCDMA_GSM: + case RILConstants.NETWORK_MODE_LTE_TDSCDMA_GSM_WCDMA: + case RILConstants.NETWORK_MODE_LTE_TDSCDMA: + case RILConstants.NETWORK_MODE_LTE_TDSCDMA_WCDMA: + case RILConstants.NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA: + case RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA: + if (isSupportTdsCdma) { + r = "network_lte"; + } else { + if (phoneType == RILConstants.CDMA_PHONE || isGlobalCdma || isWorldMode) { + r = "network_global"; + } else { + r = (show4GForLTE) + ? "network_4G" : "network_lte"; + } + } + break; + default: + Log.w(TAG, "unknown phone mode: " + networkMode); + } + + if (r != null) { + // grab the phone resources + final Resources phoneResources = getPhoneResources(context); + if (phoneResources != null) { + int id = phoneResources.getIdentifier(r, "string", "com.android.phone"); + if (id > 0) { + return phoneResources.getString(id); + } else { + Log.w(TAG, "couldn't find resource id with name: " + r); + } + } + } + return null; + } + + private static boolean isSupportTdscdma(Context context, int subId) { + final Resources phoneResources = getPhoneResources(context); + if (phoneResources != null) { + int id = phoneResources.getIdentifier("config_support_tdscdma", + "bool", "com.android.phone"); + if (phoneResources.getBoolean(id)) { + return true; + } + + final String operatorNumeric = TelephonyManager.from(context) + .getSimOperatorNumericForSubscription(subId); + + int tdcdmaArrId = phoneResources.getIdentifier("config_support_tdscdma_roaming_on_networks", + "string-array", "com.android.phone"); + + if (tdcdmaArrId > 0) { + String[] numericArray = phoneResources.getStringArray(tdcdmaArrId); + if (numericArray.length == 0 || operatorNumeric == null) { + return false; + } + for (String numeric : numericArray) { + if (operatorNumeric.equals(numeric)) { + return true; + } + } + } + } + return false; + } + + private static boolean show4GForLTE(Context context) { + try { + Context con = context.createPackageContext("com.android.systemui", 0); + int id = con.getResources().getIdentifier("config_show4GForLTE", + "bool", "com.android.systemui"); + return con.getResources().getBoolean(id); + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } + + private static boolean isGlobalCDMA(Context context, int subId, boolean isLteOnCdma) { + final CarrierConfigManager carrierConfigMan = (CarrierConfigManager) + context.getSystemService(Context.CARRIER_CONFIG_SERVICE); + final PersistableBundle carrierConfig = carrierConfigMan.getConfigForSubId(subId); + return isLteOnCdma + && carrierConfig.getBoolean(CarrierConfigManager.KEY_SHOW_CDMA_CHOICES_BOOL); + } + + private static boolean isLteOnCdma(Context context, int subId) { + return TelephonyManager.from(context).getLteOnCdmaMode(subId) + == PhoneConstants.LTE_ON_CDMA_TRUE; + } + + private static boolean isWorldMode(Context context) { + boolean worldModeOn = false; + final TelephonyManager tm = (TelephonyManager) + context.getSystemService(Context.TELEPHONY_SERVICE); + + Resources phoneResources = getPhoneResources(context); + if (phoneResources != null) { + int id = phoneResources.getIdentifier("config_world_mode", + "string", "com.android.phone"); + + if (id > 0) { + final String configString = phoneResources.getString(id); + + if (!TextUtils.isEmpty(configString)) { + String[] configArray = configString.split(";"); + // Check if we have World mode configuration set to True only or config is set to True + // and SIM GID value is also set and matches to the current SIM GID. + if (configArray != null && + ((configArray.length == 1 && configArray[0].equalsIgnoreCase("true")) || + (configArray.length == 2 && !TextUtils.isEmpty(configArray[1]) && + tm != null && configArray[1].equalsIgnoreCase(tm.getGroupIdLevel1())))) { + worldModeOn = true; + } + } + } else { + Log.w(TAG, "couldn't find resource of config_world_mode"); + } + } + + return worldModeOn; + } + + private static Resources getPhoneResources(Context context) { + try { + final Context packageContext = context.createPackageContext("com.android.phone", 0); + return packageContext.getResources(); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + Log.w(TAG, "couldn't locate resources for com.android.phone!"); + return null; + } +} diff --git a/src/com/android/settings/voicewakeup/VoiceWakeupSettings.java b/src/com/android/settings/voicewakeup/VoiceWakeupSettings.java new file mode 100644 index 0000000..645437a --- /dev/null +++ b/src/com/android/settings/voicewakeup/VoiceWakeupSettings.java @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.voicewakeup; + +import android.Manifest; +import android.app.Activity; +import android.content.ComponentName; +import android.content.Intent; +import android.content.Intent.ShortcutIconResource; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceChangeListener; +import android.preference.PreferenceScreen; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Toast; +import com.android.internal.logging.MetricsLogger; +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.cyanogenmod.BaseSystemSettingSwitchBar; +import com.android.settings.cyanogenmod.ShortcutPickHelper; +import org.cyanogenmod.internal.util.ScreenType; + +import java.net.URISyntaxException; + +public class VoiceWakeupSettings extends SettingsPreferenceFragment implements + OnPreferenceChangeListener, ShortcutPickHelper.OnPickListener, + BaseSystemSettingSwitchBar.SwitchBarChangeCallback { + private static final String TAG = "VoiceWakeupSettings"; + + public static final int REQUEST_CALL_PERMS = 111; + + private static final String KEY_RETRAIN = "retrain"; + private static final String KEY_SHORTCUT_PICKER = "voice_wakeup_launch_intent"; + + public static final String VOICE_WAKEUP_PACKAGE = "com.cyanogenmod.voicewakeup"; + private static final ComponentName VOICE_TRAINING_COMPONENT = new ComponentName( + "com.cyanogenmod.voicewakeup", "com.cyanogenmod.voicewakeup.VoiceTrainingActivity"); + private static final ComponentName VOICE_TRAINING_SERVICE = new ComponentName( + "com.cyanogenmod.voicewakeup", "com.cyanogenmod.voicewakeup.VoiceWakeupEngine"); + private static final String ACTION_REQUEST_DIAL_PERMISSION + = "com.cyanogenmod.voicewakeup.ACTION_REQUEST_DIAL_PERMISSION"; + + private BaseSystemSettingSwitchBar mVoiceWakeupEnabler; + + private ShortcutPickHelper mPicker; + private String mDefaultActivityString; + private String mLaunchIntentString; + + ViewGroup mContainer; + + private Preference mRetrainPreference; + private Preference mPickShortcutPreference; + + private void log(String s) { + Log.d(TAG, s); + } + + private void retrain() { + Intent retrain = new Intent(Intent.ACTION_MAIN); + retrain.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP + | Intent.FLAG_ACTIVITY_CLEAR_TASK); + retrain.setComponent(VOICE_TRAINING_COMPONENT); + startActivity(retrain); + } + + private void restartService() { + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.setComponent(VOICE_TRAINING_SERVICE); + getActivity().startService(intent); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + addPreferencesFromResource(R.xml.voice_wakeup_settings); + + mRetrainPreference = findPreference(KEY_RETRAIN); + mPickShortcutPreference = findPreference(KEY_SHORTCUT_PICKER); + mPicker = new ShortcutPickHelper(getActivity(), this); + mDefaultActivityString = getResources().getString(R.string.voice_wakeup_default_activity); + } + + @Override + public void onStart() { + super.onStart(); + final SettingsActivity activity = (SettingsActivity) getActivity(); + mVoiceWakeupEnabler = new BaseSystemSettingSwitchBar(activity, activity.getSwitchBar(), + Settings.System.VOICE_WAKEUP, false, this); + } + + @Override + public void onDestroyView() { + if (mVoiceWakeupEnabler != null) { + mVoiceWakeupEnabler.teardownSwitchBar(); + } + super.onDestroyView(); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + mContainer = container; + return super.onCreateView(inflater, container, savedInstanceState); + } + + @Override + public void onPause() { + super.onPause(); + if (mVoiceWakeupEnabler != null) { + mVoiceWakeupEnabler.pause(); + } + } + + @Override + public void onResume() { + super.onResume(); + if (mVoiceWakeupEnabler != null) { + mVoiceWakeupEnabler.resume(getActivity()); + } + + // If running on a phone, remove padding around tabs + if (!ScreenType.isTablet(getActivity())) { + mContainer.setPadding(0, 0, 0, 0); + } + } + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, + Preference preference) { + if (preference == mRetrainPreference) { + retrain(); + return true; + } else if (preference == mPickShortcutPreference) { + final Activity activity = getActivity(); + String[] names = new String[] { + mDefaultActivityString + }; + ShortcutIconResource[] icons = new ShortcutIconResource[] { + ShortcutIconResource.fromContext(activity, R.drawable.ic_settings_voice_wakeup) + }; + mPicker.pickShortcut(names, icons, getId()); + return true; + } + return super.onPreferenceTreeClick(preferenceScreen, preference); + }; + + private boolean voiceWakeupHasCallPerms() { + return getPackageManager().checkPermission(Manifest.permission.CALL_PHONE, + VOICE_WAKEUP_PACKAGE) == PackageManager.PERMISSION_GRANTED; + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == REQUEST_CALL_PERMS) { + if (resultCode != Activity.RESULT_OK || !voiceWakeupHasCallPerms()) { + Toast.makeText(getActivity(), R.string.voice_wakeup_needs_dial_permission_warning, + Toast.LENGTH_SHORT).show(); + // reset to default + shortcutPicked("", mDefaultActivityString, true); + } + return; + } + String shortcutName = null; + if (data != null) { + shortcutName = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); + } + + if (TextUtils.equals(shortcutName, mDefaultActivityString)) { + shortcutPicked("", mDefaultActivityString, true); + } else if (requestCode != Activity.RESULT_CANCELED + && resultCode != Activity.RESULT_CANCELED) { + mPicker.onActivityResult(requestCode, resultCode, data); + } + } + + @Override + public void shortcutPicked(String uri, String friendlyName, boolean isApplication) { + try { + final Intent intent = Intent.parseUri(uri, 0); + if (intent.getAction().equals(Intent.ACTION_CALL)) { + Intent requestCallPerms = new Intent(ACTION_REQUEST_DIAL_PERMISSION); + requestCallPerms.setPackage(VOICE_WAKEUP_PACKAGE); + startActivityForResult(requestCallPerms, REQUEST_CALL_PERMS); + } + } catch (URISyntaxException e) { + e.printStackTrace(); + } + Settings.System.putString(getContentResolver(), Settings.System.VOICE_LAUNCH_INTENT, uri); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + return false; + } + + @Override + public void onEnablerChanged(boolean isEnabled) { + Activity activity = getActivity(); + mLaunchIntentString = Settings.System.getString(activity.getContentResolver(), + Settings.System.VOICE_LAUNCH_INTENT); + + activity.invalidateOptionsMenu(); + + mRetrainPreference.setEnabled(isEnabled); + mPickShortcutPreference.setEnabled(isEnabled); + + if (mLaunchIntentString == null || mLaunchIntentString.isEmpty()) { + mPickShortcutPreference.setSummary(mDefaultActivityString); + } else { + mPickShortcutPreference.setSummary(mPicker.getFriendlyNameForUri(mLaunchIntentString)); + } + if (isEnabled) { + restartService(); + } + } + + @Override + public int getMetricsCategory() { + return MetricsLogger.VOICE_INPUT; + } +} diff --git a/src/com/android/settings/widget/ChartDataUsageView.java b/src/com/android/settings/widget/ChartDataUsageView.java index 6fb805b..f5c2768 100644 --- a/src/com/android/settings/widget/ChartDataUsageView.java +++ b/src/com/android/settings/widget/ChartDataUsageView.java @@ -167,27 +167,29 @@ public class ChartDataUsageView extends ChartView { } public void bindNetworkPolicy(NetworkPolicy policy) { + final long warningBytes, limitBytes; + if (policy == null) { - mSweepLimit.setVisibility(View.INVISIBLE); - mSweepLimit.setValue(-1); - mSweepWarning.setVisibility(View.INVISIBLE); - mSweepWarning.setValue(-1); - return; + warningBytes = NetworkPolicy.LIMIT_DISABLED; + limitBytes = NetworkPolicy.LIMIT_DISABLED; + } else { + warningBytes = policy.warningBytes; + limitBytes = policy.limitBytes; } - if (policy.limitBytes != NetworkPolicy.LIMIT_DISABLED) { + if (limitBytes != NetworkPolicy.LIMIT_DISABLED) { mSweepLimit.setVisibility(View.VISIBLE); mSweepLimit.setEnabled(true); - mSweepLimit.setValue(policy.limitBytes); + mSweepLimit.setValue(limitBytes); } else { mSweepLimit.setVisibility(View.INVISIBLE); mSweepLimit.setEnabled(false); mSweepLimit.setValue(-1); } - if (policy.warningBytes != NetworkPolicy.WARNING_DISABLED) { + if (warningBytes != NetworkPolicy.WARNING_DISABLED) { mSweepWarning.setVisibility(View.VISIBLE); - mSweepWarning.setValue(policy.warningBytes); + mSweepWarning.setValue(warningBytes); } else { mSweepWarning.setVisibility(View.INVISIBLE); mSweepWarning.setValue(-1); @@ -223,7 +225,7 @@ public class ChartDataUsageView extends ChartView { final long maxSweep = Math.max(mSweepWarning.getValue(), mSweepLimit.getValue()); final long maxSeries = Math.max(mSeries.getMaxVisible(), mDetailSeries.getMaxVisible()); final long maxVisible = Math.max(maxSeries, maxSweep) * 12 / 10; - final long maxDefault = Math.max(maxVisible, 50 * MB_IN_BYTES); + final long maxDefault = Math.max(maxVisible, 20 * MB_IN_BYTES); newMax = Math.max(maxDefault, newMax); // only invalidate when vertMax actually changed diff --git a/src/com/android/settings/widget/CheckableLinearLayout.java b/src/com/android/settings/widget/CheckableLinearLayout.java new file mode 100644 index 0000000..a4b9a7d --- /dev/null +++ b/src/com/android/settings/widget/CheckableLinearLayout.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.CheckBox; +import android.widget.Checkable; +import android.widget.LinearLayout; +import com.android.settings.R; + +/* + * This class is useful for using inside of ListView that needs to have checkable items. + */ +public class CheckableLinearLayout extends LinearLayout implements Checkable { + private CheckBox mCheckBox; + + public CheckableLinearLayout(Context context) { + super(context); + } + + public CheckableLinearLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public CheckableLinearLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + mCheckBox = (CheckBox) findViewById(R.id.checkbox); + } + + @Override + public boolean isChecked() { + return mCheckBox.isChecked(); + } + + @Override + public void setChecked(boolean checked) { + mCheckBox.setChecked(checked); + } + + @Override + public void toggle() { + mCheckBox.toggle(); + } +}
\ No newline at end of file diff --git a/src/com/android/settings/widget/InertCheckBox.java b/src/com/android/settings/widget/InertCheckBox.java new file mode 100644 index 0000000..82a376f --- /dev/null +++ b/src/com/android/settings/widget/InertCheckBox.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.widget.CheckBox; + + +// CheckBox that does not react to any user event in order to let the container handle them. +public class InertCheckBox extends CheckBox { + + @SuppressWarnings("unused") + public InertCheckBox(Context context) { + super(context); + } + + @SuppressWarnings("unused") + public InertCheckBox(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @SuppressWarnings("unused") + public InertCheckBox(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + // Make the checkbox not respond to any user event + return false; + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + // Make the checkbox not respond to any user event + return false; + } + + @Override + public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { + // Make the checkbox not respond to any user event + return false; + } + + @Override + public boolean onKeyPreIme(int keyCode, KeyEvent event) { + // Make the checkbox not respond to any user event + return false; + } + + @Override + public boolean onKeyShortcut(int keyCode, KeyEvent event) { + // Make the checkbox not respond to any user event + return false; + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + // Make the checkbox not respond to any user event + return false; + } + + @Override + public boolean onTrackballEvent(MotionEvent event) { + // Make the checkbox not respond to any user event + return false; + } +}
\ No newline at end of file diff --git a/src/com/android/settings/widget/SwitchBar.java b/src/com/android/settings/widget/SwitchBar.java index 094b95f..8cf064d 100644 --- a/src/com/android/settings/widget/SwitchBar.java +++ b/src/com/android/settings/widget/SwitchBar.java @@ -56,6 +56,9 @@ public class SwitchBar extends LinearLayout implements CompoundButton.OnCheckedC private String mLabel; private String mSummary; + private int mStateOnLabel = R.string.switch_on_text; + private int mStateOffLabel = R.string.switch_off_text; + private ArrayList<OnSwitchChangeListener> mSwitchChangeListeners = new ArrayList<OnSwitchChangeListener>(); @@ -111,6 +114,14 @@ public class SwitchBar extends LinearLayout implements CompoundButton.OnCheckedC setVisibility(View.GONE); } + public void setOnStateOnLabel(int stringRes) { + mStateOnLabel = stringRes; + } + + public void setOnStateOffLabel(int stringRes) { + mStateOffLabel = stringRes; + } + public void setTextViewLabel(boolean isChecked) { mLabel = getResources() .getString(isChecked ? R.string.switch_on_text : R.string.switch_off_text); @@ -211,6 +222,8 @@ public class SwitchBar extends LinearLayout implements CompoundButton.OnCheckedC static class SavedState extends BaseSavedState { boolean checked; boolean visible; + int resOnLabel; + int resOffLabel; SavedState(Parcelable superState) { super(superState); @@ -223,6 +236,8 @@ public class SwitchBar extends LinearLayout implements CompoundButton.OnCheckedC super(in); checked = (Boolean)in.readValue(null); visible = (Boolean)in.readValue(null); + resOnLabel = in.readInt(); + resOffLabel = in.readInt(); } @Override @@ -230,6 +245,8 @@ public class SwitchBar extends LinearLayout implements CompoundButton.OnCheckedC super.writeToParcel(out, flags); out.writeValue(checked); out.writeValue(visible); + out.writeInt(resOnLabel); + out.writeInt(resOffLabel); } @Override @@ -237,7 +254,10 @@ public class SwitchBar extends LinearLayout implements CompoundButton.OnCheckedC return "SwitchBar.SavedState{" + Integer.toHexString(System.identityHashCode(this)) + " checked=" + checked - + " visible=" + visible + "}"; + + " visible=" + visible + + " resOnLabel = " + resOnLabel + + " resOffLabel = " + resOffLabel + + "}"; } public static final Parcelable.Creator<SavedState> CREATOR @@ -259,6 +279,8 @@ public class SwitchBar extends LinearLayout implements CompoundButton.OnCheckedC SavedState ss = new SavedState(superState); ss.checked = mSwitch.isChecked(); ss.visible = isShowing(); + ss.resOnLabel = mStateOnLabel; + ss.resOffLabel = mStateOffLabel; return ss; } @@ -269,6 +291,8 @@ public class SwitchBar extends LinearLayout implements CompoundButton.OnCheckedC super.onRestoreInstanceState(ss.getSuperState()); mSwitch.setCheckedInternal(ss.checked); + setOnStateOnLabel(ss.resOnLabel); + setOnStateOffLabel(ss.resOffLabel); setTextViewLabel(ss.checked); setVisibility(ss.visible ? View.VISIBLE : View.GONE); mSwitch.setOnCheckedChangeListener(ss.visible ? this : null); diff --git a/src/com/android/settings/wifi/AccessPointPreference.java b/src/com/android/settings/wifi/AccessPointPreference.java index 264f681..d9cb34a 100644 --- a/src/com/android/settings/wifi/AccessPointPreference.java +++ b/src/com/android/settings/wifi/AccessPointPreference.java @@ -40,6 +40,7 @@ public class AccessPointPreference extends Preference { private static final int[] STATE_NONE = {}; private static int[] wifi_signal_attributes = { R.attr.wifi_signal }; + private static int[] wifi_no_signal_attributes = { R.attr.wifi_no_signal }; private final StateListDrawable mWifiSld; private final int mBadgePadding; @@ -52,6 +53,9 @@ public class AccessPointPreference extends Preference { private int mLevel; private CharSequence mContentDescription; + private boolean mShowNoSignalIcon; + private boolean mNoSignalLoaded; + static final int[] WIFI_CONNECTION_STRENGTH = { R.string.accessibility_wifi_one_bar, R.string.accessibility_wifi_two_bars, @@ -68,7 +72,7 @@ public class AccessPointPreference extends Preference { } public AccessPointPreference(AccessPoint accessPoint, Context context, UserBadgeCache cache, - boolean forSavedNetworks) { + boolean forSavedNetworks, boolean showNoSignal) { super(context); mBadgeCache = cache; mAccessPoint = accessPoint; @@ -82,6 +86,7 @@ public class AccessPointPreference extends Preference { // Distance from the end of the title at which this AP's user badge should sit. mBadgePadding = context.getResources() .getDimensionPixelSize(R.dimen.wifi_preference_badge_padding); + mShowNoSignalIcon = showNoSignal; refresh(); } @@ -112,9 +117,24 @@ public class AccessPointPreference extends Preference { protected void updateIcon(int level, Context context) { if (level == -1) { - setIcon(null); + if (mShowNoSignalIcon) { + Drawable drawable = getIcon(); + if (drawable == null || !mNoSignalLoaded) { + StateListDrawable sld = (StateListDrawable) context.getTheme() + .obtainStyledAttributes(wifi_no_signal_attributes).getDrawable(0); + if (sld != null) { + sld.setState((getAccessPoint().getSecurity() != AccessPoint.SECURITY_NONE) + ? STATE_SECURED : STATE_NONE); + setIcon(sld.getCurrent()); + mNoSignalLoaded = true; + } + } + } + if (!mNoSignalLoaded) { + setIcon(null); + } } else { - if (getIcon() == null) { + if (getIcon() == null || mNoSignalLoaded) { // To avoid a drawing race condition, we first set the state (SECURE/NONE) and then // set the icon (drawable) to that state's drawable. // If sld is null then we are indexing and therefore do not have access to @@ -124,7 +144,7 @@ public class AccessPointPreference extends Preference { ? STATE_SECURED : STATE_NONE); Drawable drawable = mWifiSld.getCurrent(); - if (!mForSavedNetworks) { + if (!mForSavedNetworks || mShowNoSignalIcon) { setIcon(drawable); } else { setIcon(null); @@ -156,7 +176,7 @@ public class AccessPointPreference extends Preference { final Context context = getContext(); int level = mAccessPoint.getLevel(); - if (level != mLevel) { + if (level != mLevel || mShowNoSignalIcon) { mLevel = level; updateIcon(mLevel, context); notifyChanged(); diff --git a/src/com/android/settings/wifi/AdvancedWifiSettings.java b/src/com/android/settings/wifi/AdvancedWifiSettings.java index 4f5884e..b933a28 100644 --- a/src/com/android/settings/wifi/AdvancedWifiSettings.java +++ b/src/com/android/settings/wifi/AdvancedWifiSettings.java @@ -57,6 +57,7 @@ public class AdvancedWifiSettings extends SettingsPreferenceFragment private static final String KEY_MAC_ADDRESS = "mac_address"; private static final String KEY_CURRENT_IP_ADDRESS = "current_ip_address"; private static final String KEY_FREQUENCY_BAND = "frequency_band"; + private static final String KEY_COUNTRY_CODE = "wifi_countrycode"; private static final String KEY_NOTIFY_OPEN_NETWORKS = "notify_open_networks"; private static final String KEY_SLEEP_POLICY = "sleep_policy"; private static final String KEY_INSTALL_CREDENTIALS = "install_credentials"; @@ -69,6 +70,9 @@ public class AdvancedWifiSettings extends SettingsPreferenceFragment private NetworkScoreManager mNetworkScoreManager; private AppListSwitchPreference mWifiAssistantPreference; + private Preference mWpsPushPref; + private Preference mWpsPinPref; + private IntentFilter mFilter; private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override @@ -77,6 +81,11 @@ public class AdvancedWifiSettings extends SettingsPreferenceFragment if (action.equals(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION) || action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { refreshWifiInfo(); + } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { + int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, + WifiManager.WIFI_STATE_UNKNOWN); + mWpsPushPref.setEnabled(WifiManager.WIFI_STATE_ENABLED == state); + mWpsPinPref.setEnabled(WifiManager.WIFI_STATE_ENABLED == state); } } }; @@ -99,6 +108,7 @@ public class AdvancedWifiSettings extends SettingsPreferenceFragment mFilter = new IntentFilter(); mFilter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION); mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); + mFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); mNetworkScoreManager = (NetworkScoreManager) getSystemService(Context.NETWORK_SCORE_SERVICE); } @@ -147,25 +157,28 @@ public class AdvancedWifiSettings extends SettingsPreferenceFragment Preference wifiDirectPref = findPreference(KEY_WIFI_DIRECT); wifiDirectPref.setIntent(wifiDirectIntent); + final int wifiState = mWifiManager.getWifiState(); // WpsDialog: Create the dialog like WifiSettings does. - Preference wpsPushPref = findPreference(KEY_WPS_PUSH); - wpsPushPref.setOnPreferenceClickListener(new OnPreferenceClickListener() { + mWpsPushPref = findPreference(KEY_WPS_PUSH); + mWpsPushPref.setOnPreferenceClickListener(new OnPreferenceClickListener() { public boolean onPreferenceClick(Preference arg0) { WpsFragment wpsFragment = new WpsFragment(WpsInfo.PBC); wpsFragment.show(getFragmentManager(), KEY_WPS_PUSH); return true; } }); + mWpsPushPref.setEnabled(WifiManager.WIFI_STATE_ENABLED == wifiState); // WpsDialog: Create the dialog like WifiSettings does. - Preference wpsPinPref = findPreference(KEY_WPS_PIN); - wpsPinPref.setOnPreferenceClickListener(new OnPreferenceClickListener(){ + mWpsPinPref = findPreference(KEY_WPS_PIN); + mWpsPinPref.setOnPreferenceClickListener(new OnPreferenceClickListener(){ public boolean onPreferenceClick(Preference arg0) { WpsFragment wpsFragment = new WpsFragment(WpsInfo.DISPLAY); wpsFragment.show(getFragmentManager(), KEY_WPS_PIN); return true; } }); + mWpsPinPref.setEnabled(WifiManager.WIFI_STATE_ENABLED == wifiState); ListPreference frequencyPref = (ListPreference) findPreference(KEY_FREQUENCY_BAND); @@ -185,6 +198,23 @@ public class AdvancedWifiSettings extends SettingsPreferenceFragment } } + ListPreference ccodePref = (ListPreference) findPreference(KEY_COUNTRY_CODE); + if (ccodePref != null) { + boolean hideWifiRegion = getResources() + .getBoolean(R.bool.config_hide_wifi_region_code); + if (hideWifiRegion) { + removePreference(KEY_COUNTRY_CODE); + } else { + ccodePref.setOnPreferenceChangeListener(this); + String value = mWifiManager.getCountryCode(); + if (value != null) { + ccodePref.setValue(value); + } else { + Log.e(TAG, "Failed to fetch country code"); + } + } + } + ListPreference sleepPolicyPref = (ListPreference) findPreference(KEY_SLEEP_POLICY); if (sleepPolicyPref != null) { if (Utils.isWifiOnly(context)) { @@ -294,6 +324,16 @@ public class AdvancedWifiSettings extends SettingsPreferenceFragment return false; } + if (KEY_COUNTRY_CODE.equals(key)) { + try { + mWifiManager.setCountryCode((String) newValue, true); + } catch (IllegalArgumentException e) { + Toast.makeText(getActivity(), R.string.wifi_setting_countrycode_error, + Toast.LENGTH_SHORT).show(); + return false; + } + } + if (KEY_SLEEP_POLICY.equals(key)) { try { String stringValue = (String) newValue; diff --git a/src/com/android/settings/wifi/SavedAccessPointsWifiSettings.java b/src/com/android/settings/wifi/SavedAccessPointsWifiSettings.java index 45aafdf..e7166da 100644 --- a/src/com/android/settings/wifi/SavedAccessPointsWifiSettings.java +++ b/src/com/android/settings/wifi/SavedAccessPointsWifiSettings.java @@ -16,18 +16,33 @@ package com.android.settings.wifi; +import static android.os.UserManager.DISALLOW_CONFIG_WIFI; + import android.app.Dialog; +import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; import android.content.res.Resources; +import android.database.ContentObserver; +import android.net.Uri; +import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; import android.os.Bundle; +import android.os.Handler; import android.preference.Preference; import android.preference.PreferenceScreen; import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; import com.android.internal.logging.MetricsLogger; +import com.android.settings.DraggableSortListView; import com.android.settings.R; +import com.android.settings.RestrictedSettingsFragment; import com.android.settings.SettingsPreferenceFragment; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.Indexable; @@ -35,6 +50,7 @@ import com.android.settings.search.SearchIndexableRaw; import com.android.settings.wifi.AccessPointPreference.UserBadgeCache; import com.android.settingslib.wifi.AccessPoint; import com.android.settingslib.wifi.WifiTracker; +import cyanogenmod.providers.CMSettings; import java.util.ArrayList; import java.util.Collections; @@ -44,20 +60,74 @@ import java.util.List; /** * UI to manage saved networks/access points. */ -public class SavedAccessPointsWifiSettings extends SettingsPreferenceFragment +public class SavedAccessPointsWifiSettings extends RestrictedSettingsFragment implements DialogInterface.OnClickListener, Indexable { private static final String TAG = "SavedAccessPointsWifiSettings"; + private DraggableSortListView.DropListener mDropListener = + new DraggableSortListView.DropListener() { + @Override + public void drop(int from, int to) { + if (from == to) return; + + PreferenceScreen preferences = getPreferenceScreen(); + int count = preferences.getPreferenceCount(); + + // Sort the new list + List<AccessPointPreference> aps = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + aps.add((AccessPointPreference) preferences.getPreference(i)); + } + AccessPointPreference o = aps.remove(from); + aps.add(to, o); + + // Update the priorities + for (int i = 0; i < count; i++) { + AccessPoint ap = aps.get(i).getAccessPoint(); + WifiConfiguration config = ap.getConfig(); + config.priority = count - i; + + mWifiManager.updateNetwork(config); + } + + // Now, save all the Wi-Fi configuration with its new priorities + mWifiManager.saveConfiguration(); + mPrioritiesOrderChanged = true; + + // Redraw the listview + initPreferences(); + } + }; + + private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) { + @Override + public void onChange(boolean selfChange, Uri uri) { + mNetworksListView.setDropListener(isAutoConfigPriorities() ? null : mDropListener); + getActivity().invalidateOptionsMenu(); + } + }; + + + private static final int MENU_ID_AUTO_CONFIG_PRIORITIES = Menu.FIRST; + private WifiDialog mDialog; private WifiManager mWifiManager; private AccessPoint mDlgAccessPoint; private Bundle mAccessPointSavedState; private AccessPoint mSelectedAccessPoint; + private boolean mPrioritiesOrderChanged; private UserBadgeCache mUserBadgeCache; + private DraggableSortListView mNetworksListView; + // Instance state key private static final String SAVE_DIALOG_ACCESS_POINT_STATE = "wifi_ap_state"; + private static final String PRIORITIES_ORDER_CHANGED_STATE = "priorities_order_changed"; + + public SavedAccessPointsWifiSettings() { + super(DISALLOW_CONFIG_WIFI); + } @Override protected int getMetricsCategory() { @@ -75,6 +145,27 @@ public class SavedAccessPointsWifiSettings extends SettingsPreferenceFragment public void onResume() { super.onResume(); initPreferences(); + + mNetworksListView.setDropListener(isAutoConfigPriorities() ? null : mDropListener); + getActivity().invalidateOptionsMenu(); + ContentResolver resolver = getContentResolver(); + resolver.registerContentObserver(CMSettings.Global.getUriFor( + CMSettings.Global.WIFI_AUTO_PRIORITIES_CONFIGURATION), false, mSettingsObserver); + } + + @Override + public void onPause() { + super.onResume(); + getContentResolver().unregisterContentObserver(mSettingsObserver); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + mNetworksListView = new DraggableSortListView(getActivity()); + mNetworksListView.setId(android.R.id.list); + mNetworksListView.setDropListener(mDropListener); + return mNetworksListView; } @Override @@ -86,36 +177,113 @@ public class SavedAccessPointsWifiSettings extends SettingsPreferenceFragment if (savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) { mAccessPointSavedState = savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE); + mDlgAccessPoint = new AccessPoint(getActivity(), mAccessPointSavedState); + mSelectedAccessPoint = mDlgAccessPoint; } + mPrioritiesOrderChanged = savedInstanceState.getBoolean( + PRIORITIES_ORDER_CHANGED_STATE, false); + } + + registerForContextMenu(getListView()); + setHasOptionsMenu(true); + } + + @Override + public void onDetach() { + super.onDetach(); + + if (mPrioritiesOrderChanged) { + // Send a disconnect to ensure the new wifi priorities are detected + mWifiManager.disconnect(); } } + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + // If the user is not allowed to configure wifi, do not show the menu. + if (isUiRestricted()) return; + + addOptionsMenuItems(menu); + super.onCreateOptionsMenu(menu, inflater); + } + + void addOptionsMenuItems(Menu menu) { + menu.add(Menu.NONE, MENU_ID_AUTO_CONFIG_PRIORITIES, 0, R.string.wifi_auto_config_priorities) + .setCheckable(true) + .setChecked(isAutoConfigPriorities()) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // If the user is not allowed to configure wifi, do not handle menu selections. + if (isUiRestricted()) return false; + + switch (item.getItemId()) { + case MENU_ID_AUTO_CONFIG_PRIORITIES: + boolean autoConfig = !item.isChecked(); + + // Set the system settings and refresh the listview + CMSettings.Global.putInt(getActivity().getContentResolver(), + CMSettings.Global.WIFI_AUTO_PRIORITIES_CONFIGURATION, autoConfig ? 1 : 0); + mNetworksListView.setDropListener(autoConfig ? null : mDropListener); + item.setChecked(autoConfig); + + if (!autoConfig) { + // Reenable all the entries + PreferenceScreen preferences = getPreferenceScreen(); + int count = preferences.getPreferenceCount(); + for (int i = 0; i < count; i++) { + AccessPoint ap = ((AccessPointPreference) + preferences.getPreference(i)).getAccessPoint(); + WifiConfiguration config = ap.getConfig(); + mWifiManager.enableNetwork(config.networkId, false); + } + } + return true; + } + return super.onOptionsItemSelected(item); + } + private void initPreferences() { PreferenceScreen preferenceScreen = getPreferenceScreen(); final Context context = getActivity(); final List<AccessPoint> accessPoints = WifiTracker.getCurrentAccessPoints(context, true, false, true); + // Sort network list by priority (or by network id if the priority is the same) Collections.sort(accessPoints, new Comparator<AccessPoint>() { - public int compare(AccessPoint ap1, AccessPoint ap2) { - if (ap1.getConfigName() != null) { - return ap1.getConfigName().compareTo(ap2.getConfigName()); - } else { - return -1; - } + @Override + public int compare(AccessPoint lhs, AccessPoint rhs) { + WifiConfiguration lwc = lhs.getConfig(); + WifiConfiguration rwc = rhs.getConfig(); + + // > priority -- > lower position + if (lwc.priority < rwc.priority) return 1; + if (lwc.priority > rwc.priority) return -1; + // < network id -- > lower position + if (lhs.getNetworkId() < rhs.getNetworkId()) return -1; + if (lhs.getNetworkId() > rhs.getNetworkId()) return 1; + return 0; } }); + preferenceScreen.setOrderingAsAdded(false); preferenceScreen.removeAll(); final int accessPointsSize = accessPoints.size(); for (int i = 0; i < accessPointsSize; ++i){ - AccessPointPreference preference = new AccessPointPreference(accessPoints.get(i), - context, mUserBadgeCache, true); - preference.setIcon(null); + AccessPoint accessPoint = accessPoints.get(i); + AccessPointPreference preference = new AccessPointPreference(accessPoint, + context, mUserBadgeCache, true, true); + if (mSelectedAccessPoint != null && + mSelectedAccessPoint.getNetworkId() == accessPoint.getNetworkId()) { + mSelectedAccessPoint = accessPoint; + } + preference.setOrder(i); preferenceScreen.addPreference(preference); } - if(getPreferenceScreen().getPreferenceCount() < 1) { + if (getPreferenceScreen().getPreferenceCount() < 1) { Log.w(TAG, "Saved networks activity loaded, but there are no saved networks!"); } } @@ -136,11 +304,6 @@ public class SavedAccessPointsWifiSettings extends SettingsPreferenceFragment public Dialog onCreateDialog(int dialogId) { switch (dialogId) { case WifiSettings.WIFI_DIALOG_ID: - if (mDlgAccessPoint == null) { // For re-launch from saved state - mDlgAccessPoint = new AccessPoint(getActivity(), mAccessPointSavedState); - // Reset the saved access point data - mAccessPointSavedState = null; - } mSelectedAccessPoint = mDlgAccessPoint; // Hide forget button if config editing is locked down @@ -167,6 +330,7 @@ public class SavedAccessPointsWifiSettings extends SettingsPreferenceFragment outState.putBundle(SAVE_DIALOG_ACCESS_POINT_STATE, mAccessPointSavedState); } } + outState.putBoolean(PRIORITIES_ORDER_CHANGED_STATE, mPrioritiesOrderChanged); } @Override @@ -188,6 +352,11 @@ public class SavedAccessPointsWifiSettings extends SettingsPreferenceFragment } } + private boolean isAutoConfigPriorities() { + return CMSettings.Global.getInt(getActivity().getContentResolver(), + CMSettings.Global.WIFI_AUTO_PRIORITIES_CONFIGURATION, 1) != 0; + } + /** * For search. */ diff --git a/src/com/android/settings/wifi/WifiApEnabler.java b/src/com/android/settings/wifi/WifiApEnabler.java index 741c4a7..92d605a 100644 --- a/src/com/android/settings/wifi/WifiApEnabler.java +++ b/src/com/android/settings/wifi/WifiApEnabler.java @@ -41,6 +41,8 @@ public class WifiApEnabler { ConnectivityManager mCm; private String[] mWifiRegexs; + /* Indicates if we have to wait for WIFI_STATE_CHANGED intent */ + private boolean mWaitForWifiStateChange = false; private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override @@ -56,6 +58,11 @@ public class WifiApEnabler { } else { handleWifiApStateChanged(state, WifiManager.SAP_START_FAILURE_GENERAL); } + } else if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) { + if (mWaitForWifiStateChange == true) { + handleWifiStateChanged(intent.getIntExtra( + WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN)); + } } else if (ConnectivityManager.ACTION_TETHER_STATE_CHANGED.equals(action)) { ArrayList<String> available = intent.getStringArrayListExtra( ConnectivityManager.EXTRA_AVAILABLE_TETHER); @@ -84,6 +91,7 @@ public class WifiApEnabler { mIntentFilter = new IntentFilter(WifiManager.WIFI_AP_STATE_CHANGED_ACTION); mIntentFilter.addAction(ConnectivityManager.ACTION_TETHER_STATE_CHANGED); mIntentFilter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); + mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); } public void resume() { @@ -107,6 +115,27 @@ public class WifiApEnabler { } public void setSoftapEnabled(boolean enable) { + int wifiSavedState = 0; + /** + * Check if we have to wait for the WIFI_STATE_CHANGED intent + * before we re-enable the Checkbox. + */ + if (!enable) { + try { + wifiSavedState = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.WIFI_SAVED_STATE); + } catch (Settings.SettingNotFoundException e) { + ; + } + /** + * If Wi-Fi is turned of as part of SoftAp turn on process, + * we need to restore, Wi-Fi state after SoftAp turn Off. + * WIFI_SAVED_STATE inficates the state. + */ + if (wifiSavedState == 1) { + mWaitForWifiStateChange = true; + } + } if (TetherUtil.setWifiTethering(enable, mContext)) { /* Disable here, enabled on receiving success broadcast */ mSwitch.setEnabled(false); @@ -172,7 +201,9 @@ public class WifiApEnabler { case WifiManager.WIFI_AP_STATE_DISABLED: mSwitch.setChecked(false); mSwitch.setSummary(mOriginalSummary); - enableWifiSwitch(); + if (mWaitForWifiStateChange == false) { + enableWifiSwitch(); + } break; default: mSwitch.setChecked(false); @@ -184,4 +215,15 @@ public class WifiApEnabler { enableWifiSwitch(); } } + + private void handleWifiStateChanged(int state) { + switch (state) { + case WifiManager.WIFI_STATE_ENABLED: + case WifiManager.WIFI_STATE_UNKNOWN: + enableWifiSwitch(); + mWaitForWifiStateChange = false; + break; + default: + } + } } diff --git a/src/com/android/settings/wifi/WifiConfigController.java b/src/com/android/settings/wifi/WifiConfigController.java index 774c54b..b4a8967 100644 --- a/src/com/android/settings/wifi/WifiConfigController.java +++ b/src/com/android/settings/wifi/WifiConfigController.java @@ -35,8 +35,10 @@ import android.net.wifi.WifiEnterpriseConfig.Eap; import android.net.wifi.WifiEnterpriseConfig.Phase2; import android.net.wifi.WifiInfo; import android.os.Handler; +import android.provider.Settings; import android.security.Credentials; import android.security.KeyStore; +import android.telephony.TelephonyManager; import android.text.Editable; import android.text.InputType; import android.text.TextWatcher; @@ -54,11 +56,16 @@ import android.widget.EditText; import android.widget.Spinner; import android.widget.TextView; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; import com.android.settings.ProxySelector; import com.android.settings.R; import com.android.settingslib.wifi.AccessPoint; import com.android.settings.Utils; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Collections; import java.net.InetAddress; import java.net.Inet4Address; import java.util.Iterator; @@ -116,6 +123,8 @@ public class WifiConfigController implements TextWatcher, private Spinner mEapMethodSpinner; private Spinner mEapCaCertSpinner; private Spinner mPhase2Spinner; + private Spinner mSimCardSpinner; + private ArrayList<String> mSimDisplayNames; // Associated with mPhase2Spinner, one of PHASE2_FULL_ADAPTER or PHASE2_PEAP_ADAPTER private ArrayAdapter<String> mPhase2Adapter; private Spinner mEapUserCertSpinner; @@ -146,6 +155,10 @@ public class WifiConfigController implements TextWatcher, private TextView mSsidView; private Context mContext; + private TelephonyManager mTelephonyManager; + private SubscriptionManager mSubscriptionManager = null; + private int selectedSimCardNumber; + public WifiConfigController( WifiConfigUiBase parent, View view, AccessPoint accessPoint, boolean edit, @@ -163,6 +176,8 @@ public class WifiConfigController implements TextWatcher, mContext = mConfigUi.getContext(); final Resources res = mContext.getResources(); + mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); + mSimDisplayNames = new ArrayList<String>(); mLevels = res.getStringArray(R.array.wifi_signal); PHASE2_PEAP_ADAPTER = new ArrayAdapter<String>( mContext, android.R.layout.simple_spinner_item, @@ -447,6 +462,12 @@ public class WifiConfigController implements TextWatcher, break; } break; + case Eap.SIM: + case Eap.AKA: + case Eap.AKA_PRIME: + selectedSimCardNumber = mSimCardSpinner.getSelectedItemPosition() + 1; + config.SIMNum = selectedSimCardNumber; + break; default: // The default index from PHASE2_FULL_ADAPTER maps to the API config.enterpriseConfig.setPhase2Method(phase2Method); @@ -642,6 +663,7 @@ public class WifiConfigController implements TextWatcher, mView.findViewById(R.id.eap).setVisibility(View.VISIBLE); if (mEapMethodSpinner == null) { + getSIMInfo(); mEapMethodSpinner = (Spinner) mView.findViewById(R.id.method); mEapMethodSpinner.setOnItemSelectedListener(this); if (Utils.isWifiOnly(mContext) || !mContext.getResources().getBoolean( @@ -657,6 +679,7 @@ public class WifiConfigController implements TextWatcher, mPhase2Spinner = (Spinner) mView.findViewById(R.id.phase2); mEapCaCertSpinner = (Spinner) mView.findViewById(R.id.ca_cert); mEapUserCertSpinner = (Spinner) mView.findViewById(R.id.user_cert); + mSimCardSpinner = (Spinner) mView.findViewById(R.id.sim_card); mEapIdentityView = (TextView) mView.findViewById(R.id.identity); mEapAnonymousView = (TextView) mView.findViewById(R.id.anonymous); @@ -687,6 +710,12 @@ public class WifiConfigController implements TextWatcher, break; } break; + case Eap.SIM: + case Eap.AKA: + case Eap.AKA_PRIME: + WifiConfiguration config = mAccessPoint.getConfig(); + mSimCardSpinner.setSelection(config.SIMNum-1); + break; default: mPhase2Spinner.setSelection(phase2Method); break; @@ -742,12 +771,14 @@ public class WifiConfigController implements TextWatcher, setCaCertInvisible(); setAnonymousIdentInvisible(); setUserCertInvisible(); + setSimCardInvisible(); break; case WIFI_EAP_METHOD_TLS: mView.findViewById(R.id.l_user_cert).setVisibility(View.VISIBLE); setPhase2Invisible(); setAnonymousIdentInvisible(); setPasswordInvisible(); + setSimCardInvisible(); break; case WIFI_EAP_METHOD_PEAP: // Reset adapter if needed @@ -758,6 +789,7 @@ public class WifiConfigController implements TextWatcher, mView.findViewById(R.id.l_phase2).setVisibility(View.VISIBLE); mView.findViewById(R.id.l_anonymous).setVisibility(View.VISIBLE); setUserCertInvisible(); + setSimCardInvisible(); break; case WIFI_EAP_METHOD_TTLS: // Reset adapter if needed @@ -768,10 +800,26 @@ public class WifiConfigController implements TextWatcher, mView.findViewById(R.id.l_phase2).setVisibility(View.VISIBLE); mView.findViewById(R.id.l_anonymous).setVisibility(View.VISIBLE); setUserCertInvisible(); + setSimCardInvisible(); break; case WIFI_EAP_METHOD_SIM: case WIFI_EAP_METHOD_AKA: case WIFI_EAP_METHOD_AKA_PRIME: + WifiConfiguration config = null; + if (mAccessPoint != null) { + config = mAccessPoint.getConfig(); + } + ArrayAdapter<String> eapSimAdapter = new ArrayAdapter<String>( + mContext, android.R.layout.simple_spinner_item, + mSimDisplayNames.toArray(new String[mSimDisplayNames.size()]) + ); + eapSimAdapter.setDropDownViewResource( + android.R.layout.simple_spinner_dropdown_item); + mSimCardSpinner.setAdapter(eapSimAdapter); + mView.findViewById(R.id.l_sim_card).setVisibility(View.VISIBLE); + if(config != null){ + mSimCardSpinner.setSelection(config.SIMNum-1); + } setPhase2Invisible(); setAnonymousIdentInvisible(); setCaCertInvisible(); @@ -782,6 +830,10 @@ public class WifiConfigController implements TextWatcher, } } + private void setSimCardInvisible() { + mView.findViewById(R.id.l_sim_card).setVisibility(View.GONE); + } + private void setIdentityInvisible() { mView.findViewById(R.id.l_identity).setVisibility(View.GONE); mPhase2Spinner.setSelection(Phase2.NONE); @@ -1033,4 +1085,20 @@ public class WifiConfigController implements TextWatcher, InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD : InputType.TYPE_TEXT_VARIATION_PASSWORD)); } + + private void getSIMInfo() { + int numOfSims; + String displayname; + mSubscriptionManager = SubscriptionManager.from(mContext); + for(int i = 0; i < mTelephonyManager.getSimCount(); i++) { + final SubscriptionInfo sir = mSubscriptionManager. + getActiveSubscriptionInfoForSimSlotIndex(i); + if (sir != null) { + displayname = String.valueOf(sir.getDisplayName()); + } else { + displayname = mContext.getString(R.string.sim_editor_title, i + 1); + } + mSimDisplayNames.add(displayname); + } + } } diff --git a/src/com/android/settings/wifi/WifiEnabler.java b/src/com/android/settings/wifi/WifiEnabler.java index fe52cf2..108405a 100644 --- a/src/com/android/settings/wifi/WifiEnabler.java +++ b/src/com/android/settings/wifi/WifiEnabler.java @@ -27,26 +27,25 @@ import android.net.wifi.WifiManager; import android.os.Handler; import android.os.Message; import android.provider.Settings; +import android.widget.CompoundButton; import android.widget.Switch; import android.widget.Toast; import com.android.internal.logging.MetricsLogger; import com.android.settings.R; +import com.android.settings.dashboard.GenericSwitchToggle; import com.android.settings.search.Index; import com.android.settings.widget.SwitchBar; import com.android.settingslib.WirelessUtils; import java.util.concurrent.atomic.AtomicBoolean; -public class WifiEnabler implements SwitchBar.OnSwitchChangeListener { - private Context mContext; - private SwitchBar mSwitchBar; - private boolean mListeningToOnSwitchChange = false; +public class WifiEnabler extends GenericSwitchToggle { private AtomicBoolean mConnected = new AtomicBoolean(false); - private final WifiManager mWifiManager; - private boolean mStateMachineEvent; - private final IntentFilter mIntentFilter; + private WifiManager mWifiManager; + private IntentFilter mIntentFilter; + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -85,76 +84,71 @@ public class WifiEnabler implements SwitchBar.OnSwitchChangeListener { }; public WifiEnabler(Context context, SwitchBar switchBar) { - mContext = context; - mSwitchBar = switchBar; + super(context, switchBar); + + init(); + setupSwitches(); + } - mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + public WifiEnabler(Context context, Switch switch_) { + super(context, switch_); + + init(); + setupSwitches(); + } + + private void init() { + mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); mIntentFilter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION); // The order matters! We really should not depend on this. :( mIntentFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); - - setupSwitchBar(); } - public void setupSwitchBar() { + private void setupSwitches() { final int state = mWifiManager.getWifiState(); handleWifiStateChanged(state); - if (!mListeningToOnSwitchChange) { - mSwitchBar.addOnSwitchChangeListener(this); - mListeningToOnSwitchChange = true; + if (mSwitchBar != null) { + mSwitchBar.show(); } - mSwitchBar.show(); } - public void teardownSwitchBar() { - if (mListeningToOnSwitchChange) { - mSwitchBar.removeOnSwitchChangeListener(this); - mListeningToOnSwitchChange = false; - } - mSwitchBar.hide(); - } + @Override public void resume(Context context) { - mContext = context; + super.resume(context); // Wi-Fi state is sticky, so just let the receiver update UI mContext.registerReceiver(mReceiver, mIntentFilter); - if (!mListeningToOnSwitchChange) { - mSwitchBar.addOnSwitchChangeListener(this); - mListeningToOnSwitchChange = true; - } } + @Override public void pause() { + super.pause(); mContext.unregisterReceiver(mReceiver); - if (mListeningToOnSwitchChange) { - mSwitchBar.removeOnSwitchChangeListener(this); - mListeningToOnSwitchChange = false; - } } private void handleWifiStateChanged(int state) { switch (state) { case WifiManager.WIFI_STATE_ENABLING: - mSwitchBar.setEnabled(false); + setEnabled(false); break; case WifiManager.WIFI_STATE_ENABLED: - setSwitchBarChecked(true); - mSwitchBar.setEnabled(true); + setChecked(true); + setEnabled(true); updateSearchIndex(true); break; case WifiManager.WIFI_STATE_DISABLING: - mSwitchBar.setEnabled(false); + setEnabled(false); break; case WifiManager.WIFI_STATE_DISABLED: - setSwitchBarChecked(false); - mSwitchBar.setEnabled(true); + setChecked(false); + setEnabled(true); updateSearchIndex(false); break; default: - setSwitchBarChecked(false); - mSwitchBar.setEnabled(true); + setChecked(false); + setEnabled(false); updateSearchIndex(false); } } @@ -168,11 +162,6 @@ public class WifiEnabler implements SwitchBar.OnSwitchChangeListener { mHandler.sendMessage(msg); } - private void setSwitchBarChecked(boolean checked) { - mStateMachineEvent = true; - mSwitchBar.setChecked(checked); - mStateMachineEvent = false; - } private void handleStateChanged(@SuppressWarnings("unused") NetworkInfo.DetailedState state) { // After the refactoring from a CheckBoxPreference to a Switch, this method is useless since @@ -199,8 +188,7 @@ public class WifiEnabler implements SwitchBar.OnSwitchChangeListener { // Show toast message if Wi-Fi is not allowed in airplane mode if (isChecked && !WirelessUtils.isRadioAllowed(mContext, Settings.Global.RADIO_WIFI)) { Toast.makeText(mContext, R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show(); - // Reset switch to off. No infinite check/listenenr loop. - mSwitchBar.setChecked(false); + setChecked(false); return; } @@ -214,8 +202,14 @@ public class WifiEnabler implements SwitchBar.OnSwitchChangeListener { isChecked ? MetricsLogger.ACTION_WIFI_ON : MetricsLogger.ACTION_WIFI_OFF); if (!mWifiManager.setWifiEnabled(isChecked)) { // Error - mSwitchBar.setEnabled(true); + setEnabled(true); + Toast.makeText(mContext, R.string.wifi_error, Toast.LENGTH_SHORT).show(); } } + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + super.onCheckedChanged(buttonView, isChecked); + } } diff --git a/src/com/android/settings/wifi/WifiSettings.java b/src/com/android/settings/wifi/WifiSettings.java index 7eded24..5fc70e5 100644 --- a/src/com/android/settings/wifi/WifiSettings.java +++ b/src/com/android/settings/wifi/WifiSettings.java @@ -52,7 +52,6 @@ import android.os.UserManager; import android.preference.Preference; import android.preference.PreferenceScreen; import android.provider.Settings; -import android.text.Spannable; import android.text.style.TextAppearanceSpan; import android.util.Log; import android.view.ContextMenu; @@ -667,6 +666,10 @@ public class WifiSettings extends RestrictedSettingsFragment for (AccessPoint accessPoint : accessPoints) { // Ignore access points that are out of range. if (accessPoint.getLevel() != -1) { + if (accessPoint.isSaved() && (!accessPoint.foundInScanResult) + && (accessPoint.getDetailedState() == null)) { + continue; + } hasAvailableAccessPoints = true; if (accessPoint.getTag() != null) { final Preference pref = (Preference) accessPoint.getTag(); @@ -675,7 +678,7 @@ public class WifiSettings extends RestrictedSettingsFragment continue; } AccessPointPreference preference = new AccessPointPreference(accessPoint, - getActivity(), mUserBadgeCache, false); + getActivity(), mUserBadgeCache, false, false); preference.setOrder(index++); if (mOpenSsid != null && mOpenSsid.equals(accessPoint.getSsidStr()) @@ -720,7 +723,6 @@ public class WifiSettings extends RestrictedSettingsFragment protected TextView initEmptyView() { TextView emptyView = (TextView) getActivity().findViewById(android.R.id.empty); - emptyView.setGravity(Gravity.START | Gravity.CENTER_VERTICAL); getListView().setEmptyView(emptyView); return emptyView; } @@ -759,11 +761,6 @@ public class WifiSettings extends RestrictedSettingsFragment } }); } - // Embolden and enlarge the brief description anyway. - Spannable boldSpan = (Spannable) mEmptyView.getText(); - boldSpan.setSpan( - new TextAppearanceSpan(getActivity(), android.R.style.TextAppearance_Medium), 0, - briefText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); getPreferenceScreen().removeAll(); } diff --git a/src/com/android/settings/wifi/p2p/WifiP2pSettings.java b/src/com/android/settings/wifi/p2p/WifiP2pSettings.java index 81815ae..e4decb5 100644 --- a/src/com/android/settings/wifi/p2p/WifiP2pSettings.java +++ b/src/com/android/settings/wifi/p2p/WifiP2pSettings.java @@ -42,6 +42,7 @@ import android.preference.Preference; import android.preference.PreferenceCategory; import android.preference.PreferenceGroup; import android.preference.PreferenceScreen; +import android.provider.Settings; import android.text.InputFilter; import android.text.TextUtils; import android.util.Log; @@ -101,6 +102,8 @@ public class WifiP2pSettings extends SettingsPreferenceFragment private String mSavedDeviceName; + private int mStaDisconnectedScanIntervalWhenP2pConnected = 180000; + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -313,7 +316,9 @@ public class WifiP2pSettings extends SettingsPreferenceFragment mPersistentGroup = new PreferenceCategory(getActivity()); mPersistentGroup.setTitle(R.string.wifi_p2p_remembered_groups); preferenceScreen.addPreference(mPersistentGroup); - + Settings.Global.putInt(getContentResolver(), + Settings.Global.WIFI_SCAN_INTERVAL_WHEN_P2P_CONNECTED_MS, + mStaDisconnectedScanIntervalWhenP2pConnected); super.onActivityCreated(savedInstanceState); } |