summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/com/android/settings/AnimationScalePreference.java94
-rw-r--r--src/com/android/settings/ApnEditor.java138
-rw-r--r--src/com/android/settings/ApnPreference.java9
-rw-r--r--src/com/android/settings/ApnSettings.java59
-rw-r--r--src/com/android/settings/BandMode.java65
-rw-r--r--src/com/android/settings/BugreportPreference.java9
-rw-r--r--src/com/android/settings/ButtonSettings.java745
-rw-r--r--src/com/android/settings/CarrierSelection.java34
-rw-r--r--src/com/android/settings/ChooseLockGeneric.java25
-rw-r--r--src/com/android/settings/ChooseLockPattern.java45
-rw-r--r--src/com/android/settings/ChooseLockPatternSize.java112
-rw-r--r--src/com/android/settings/ChooseLockSettingsHelper.java8
-rw-r--r--src/com/android/settings/CmRadioInfo.java217
-rw-r--r--src/com/android/settings/ConfirmDeviceCredentialBaseActivity.java10
-rw-r--r--src/com/android/settings/ConfirmLockPattern.java4
-rw-r--r--src/com/android/settings/CryptKeeper.java173
-rw-r--r--src/com/android/settings/DataUsageSummary.java549
-rw-r--r--src/com/android/settings/DataUsageUtils.java121
-rw-r--r--src/com/android/settings/DateChangeReceiver.java (renamed from src/com/android/settings/net/UidDetail.java)19
-rw-r--r--src/com/android/settings/DateTimeSettings.java13
-rw-r--r--src/com/android/settings/DefaultRingtonePreference.java16
-rw-r--r--src/com/android/settings/DevelopmentSettings.java568
-rw-r--r--src/com/android/settings/DeviceAdminSettings.java1
-rw-r--r--src/com/android/settings/DeviceInfoSettings.java151
-rw-r--r--src/com/android/settings/Display.java138
-rw-r--r--src/com/android/settings/DisplaySettings.java584
-rw-r--r--src/com/android/settings/DraggableSortListView.java361
-rw-r--r--src/com/android/settings/DreamSettings.java7
-rw-r--r--src/com/android/settings/FontDialogPreference.java156
-rw-r--r--src/com/android/settings/HostnamePreference.java129
-rw-r--r--src/com/android/settings/IccLockSettings.java101
-rw-r--r--src/com/android/settings/IntervalSeekBar.java87
-rw-r--r--src/com/android/settings/LegalSettings.java24
-rw-r--r--src/com/android/settings/MasterClear.java198
-rw-r--r--src/com/android/settings/MasterClearConfirm.java246
-rw-r--r--src/com/android/settings/RegulatoryInfoDisplayActivity.java48
-rw-r--r--src/com/android/settings/ResetNetworkConfirm.java3
-rw-r--r--src/com/android/settings/SecuritySettings.java571
-rw-r--r--src/com/android/settings/Settings.java17
-rw-r--r--src/com/android/settings/SettingsActivity.java111
-rw-r--r--src/com/android/settings/SettingsPreferenceFragment.java17
-rw-r--r--src/com/android/settings/SubSettings.java2
-rw-r--r--src/com/android/settings/TetherSettings.java55
-rw-r--r--src/com/android/settings/TrustAgentUtils.java2
-rw-r--r--src/com/android/settings/UserAdapter.java4
-rw-r--r--src/com/android/settings/UserDictionarySettings.java2
-rw-r--r--src/com/android/settings/Utils.java364
-rw-r--r--src/com/android/settings/WifiCallingSettings.java26
-rw-r--r--src/com/android/settings/WirelessSettings.java39
-rw-r--r--src/com/android/settings/accessibility/AccessibilitySettings.java81
-rw-r--r--src/com/android/settings/accessibility/SettingsContentObserver.java2
-rw-r--r--src/com/android/settings/accounts/AccountPreferenceBase.java8
-rw-r--r--src/com/android/settings/applications/AppInfoBase.java5
-rw-r--r--src/com/android/settings/applications/AppOpsCategory.java2
-rw-r--r--src/com/android/settings/applications/AppOpsDetails.java197
-rw-r--r--src/com/android/settings/applications/AppOpsDetailsTop.java37
-rw-r--r--src/com/android/settings/applications/AppOpsState.java148
-rw-r--r--src/com/android/settings/applications/AppOpsSummary.java200
-rw-r--r--src/com/android/settings/applications/ExpandedDesktopExtraPrefs.java165
-rw-r--r--src/com/android/settings/applications/ExpandedDesktopPreferenceFragment.java613
-rwxr-xr-xsrc/com/android/settings/applications/InstalledAppDetails.java84
-rw-r--r--src/com/android/settings/applications/LockPatternActivity.java466
-rw-r--r--src/com/android/settings/applications/ManageApplications.java39
-rw-r--r--src/com/android/settings/applications/ManageDefaultApps.java4
-rw-r--r--src/com/android/settings/applications/ProcessStatsSummary.java18
-rw-r--r--src/com/android/settings/applications/ProtectedAppsActivity.java512
-rw-r--r--src/com/android/settings/applications/ResetAppsHelper.java9
-rw-r--r--src/com/android/settings/applications/RunningState.java2
-rw-r--r--src/com/android/settings/blacklist/BlacklistPreferences.java151
-rw-r--r--src/com/android/settings/blacklist/BlacklistSettings.java396
-rw-r--r--src/com/android/settings/blacklist/EntryEditDialogFragment.java401
-rw-r--r--src/com/android/settings/blacklist/ToggleImageView.java92
-rw-r--r--src/com/android/settings/bluetooth/BluetoothDevicePreference.java48
-rw-r--r--src/com/android/settings/bluetooth/BluetoothEnabler.java81
-rw-r--r--src/com/android/settings/bluetooth/BluetoothNameDialogFragment.java7
-rwxr-xr-xsrc/com/android/settings/bluetooth/BluetoothPairingDialog.java14
-rw-r--r--src/com/android/settings/bluetooth/BluetoothPairingRequest.java15
-rw-r--r--src/com/android/settings/bluetooth/BluetoothSettings.java105
-rw-r--r--src/com/android/settings/bluetooth/DevicePickerFragment.java21
-rw-r--r--[-rwxr-xr-x]src/com/android/settings/bluetooth/DeviceProfilesSettings.java46
-rwxr-xr-xsrc/com/android/settings/bluetooth/Utils.java2
-rw-r--r--src/com/android/settings/cmstats/AnonymousStats.java107
-rw-r--r--src/com/android/settings/cmstats/PreviewData.java55
-rw-r--r--src/com/android/settings/cmstats/ReportingService.java106
-rw-r--r--src/com/android/settings/cmstats/ReportingServiceManager.java123
-rw-r--r--src/com/android/settings/cmstats/StatsUploadJobService.java291
-rw-r--r--src/com/android/settings/cmstats/Utilities.java102
-rw-r--r--src/com/android/settings/contributors/ContributorsCloudFragment.java770
-rw-r--r--src/com/android/settings/contributors/ContributorsCloudViewController.java1304
-rw-r--r--src/com/android/settings/cyanogenmod/BacklightTimeoutSeekBar.java72
-rw-r--r--src/com/android/settings/cyanogenmod/BaseSystemSettingSwitchBar.java155
-rw-r--r--src/com/android/settings/cyanogenmod/BootReceiver.java68
-rw-r--r--src/com/android/settings/cyanogenmod/ButtonBacklightBrightness.java460
-rw-r--r--src/com/android/settings/cyanogenmod/CMBaseSystemSettingSwitchBar.java156
-rw-r--r--src/com/android/settings/cyanogenmod/CMGlobalSettingSwitchPreference.java66
-rw-r--r--src/com/android/settings/cyanogenmod/CMSecureSettingSwitchPreference.java66
-rw-r--r--src/com/android/settings/cyanogenmod/CMSystemSettingSwitchPreference.java66
-rw-r--r--src/com/android/settings/cyanogenmod/DeviceUtils.java26
-rw-r--r--src/com/android/settings/cyanogenmod/DisplayRotation.java180
-rw-r--r--src/com/android/settings/cyanogenmod/GlobalSettingSwitchPreference.java65
-rw-r--r--src/com/android/settings/cyanogenmod/LiveLockScreenSettings.java497
-rw-r--r--src/com/android/settings/cyanogenmod/LockscreenSettingsAlias.java142
-rw-r--r--src/com/android/settings/cyanogenmod/LtoService.java338
-rw-r--r--src/com/android/settings/cyanogenmod/NavBar.java127
-rw-r--r--src/com/android/settings/cyanogenmod/PackageListAdapter.java186
-rw-r--r--src/com/android/settings/cyanogenmod/PowerMenuActions.java316
-rw-r--r--src/com/android/settings/cyanogenmod/PrivacySettings.java82
-rw-r--r--src/com/android/settings/cyanogenmod/ProtectedAccountView.java281
-rw-r--r--src/com/android/settings/cyanogenmod/ProtectedAppsReceiver.java118
-rw-r--r--src/com/android/settings/cyanogenmod/SecureSettingSwitchPreference.java65
-rw-r--r--src/com/android/settings/cyanogenmod/ShortcutPickHelper.java324
-rw-r--r--src/com/android/settings/cyanogenmod/SpamList.java307
-rw-r--r--src/com/android/settings/cyanogenmod/StatusBarSettings.java222
-rw-r--r--src/com/android/settings/cyanogenmod/SystemSettingSwitchPreference.java65
-rw-r--r--src/com/android/settings/cyanogenmod/WeatherServiceSettings.java468
-rw-r--r--src/com/android/settings/dashboard/DashboardSummary.java35
-rw-r--r--src/com/android/settings/dashboard/DashboardTile.java16
-rw-r--r--src/com/android/settings/dashboard/DashboardTileView.java54
-rw-r--r--src/com/android/settings/dashboard/GenericSwitchToggle.java92
-rw-r--r--src/com/android/settings/dashboard/MobileNetworksEnabler.java138
-rw-r--r--src/com/android/settings/dashboard/SearchResultsSummary.java4
-rw-r--r--src/com/android/settings/deviceinfo/ImeiInformation.java38
-rw-r--r--src/com/android/settings/deviceinfo/PublicVolumeSettings.java2
-rw-r--r--src/com/android/settings/deviceinfo/SimStatus.java125
-rw-r--r--src/com/android/settings/deviceinfo/Status.java79
-rw-r--r--src/com/android/settings/deviceinfo/StorageSettings.java29
-rw-r--r--src/com/android/settings/deviceinfo/StorageSummaryPreference.java4
-rw-r--r--src/com/android/settings/deviceinfo/StorageVolumePreference.java11
-rw-r--r--src/com/android/settings/deviceinfo/UsbBackend.java9
-rw-r--r--src/com/android/settings/deviceinfo/UsbModeChooserActivity.java32
-rw-r--r--src/com/android/settings/drawable/CircleFramedDrawable.java136
-rw-r--r--src/com/android/settings/fingerprint/FingerprintEnrollFindSensor.java21
-rw-r--r--src/com/android/settings/fingerprint/FingerprintEnrollIntroduction.java11
-rw-r--r--src/com/android/settings/fingerprint/FingerprintUiHelper.java31
-rw-r--r--src/com/android/settings/fuelgauge/BatteryEntry.java4
-rw-r--r--src/com/android/settings/fuelgauge/BatteryHistoryChart.java280
-rw-r--r--src/com/android/settings/fuelgauge/BatteryHistoryDetail.java9
-rw-r--r--src/com/android/settings/fuelgauge/BatteryHistoryPreference.java13
-rw-r--r--src/com/android/settings/fuelgauge/BatterySaverSettings.java228
-rw-r--r--src/com/android/settings/fuelgauge/PowerUsageBase.java17
-rw-r--r--src/com/android/settings/fuelgauge/PowerUsageDetail.java19
-rw-r--r--src/com/android/settings/fuelgauge/PowerUsageSummary.java263
-rw-r--r--src/com/android/settings/hardware/VibratorIntensity.java235
-rw-r--r--src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java157
-rw-r--r--src/com/android/settings/inputmethod/StylusGestures.java208
-rw-r--r--src/com/android/settings/livedisplay/DisplayColor.java240
-rw-r--r--src/com/android/settings/livedisplay/DisplayTemperature.java300
-rw-r--r--src/com/android/settings/livedisplay/LiveDisplay.java426
-rw-r--r--src/com/android/settings/livedisplay/PictureAdjustment.java262
-rw-r--r--src/com/android/settings/location/IzatSettingsInjector.java114
-rw-r--r--src/com/android/settings/location/LocationSettings.java99
-rw-r--r--src/com/android/settings/location/SettingsInjector.java2
-rw-r--r--src/com/android/settings/net/ChartDataLoader.java145
-rw-r--r--src/com/android/settings/net/DataUsageMeteredSettings.java1
-rw-r--r--src/com/android/settings/net/NetworkPolicyEditor.java252
-rw-r--r--src/com/android/settings/net/SummaryForAllUidLoader.java79
-rw-r--r--src/com/android/settings/net/UidDetailProvider.java194
-rw-r--r--src/com/android/settings/nfc/NfcEnabler.java9
-rw-r--r--src/com/android/settings/nfc/NfcPaymentPreference.java26
-rw-r--r--src/com/android/settings/notification/AppNotificationSettings.java90
-rw-r--r--src/com/android/settings/notification/IncreasingRingVolumePreference.java257
-rw-r--r--src/com/android/settings/notification/NotificationBackend.java40
-rw-r--r--src/com/android/settings/notification/NotificationManagerSettings.java137
-rw-r--r--src/com/android/settings/notification/NotificationStation.java17
-rw-r--r--src/com/android/settings/notification/OtherSoundSettings.java154
-rw-r--r--src/com/android/settings/notification/SoundSettings.java (renamed from src/com/android/settings/notification/NotificationSettings.java)366
-rw-r--r--src/com/android/settings/notification/VolumeSeekBarPreference.java9
-rw-r--r--src/com/android/settings/notification/ZenModePrioritySettings.java24
-rw-r--r--src/com/android/settings/notification/ZenModeScheduleDaysSelection.java4
-rw-r--r--src/com/android/settings/notification/ZenModeSettings.java42
-rw-r--r--src/com/android/settings/notificationlight/AlphaPatternDrawable.java125
-rw-r--r--src/com/android/settings/notificationlight/ApplicationLightPreference.java297
-rw-r--r--src/com/android/settings/notificationlight/BatteryLightSettings.java195
-rw-r--r--src/com/android/settings/notificationlight/ColorPanelView.java171
-rw-r--r--src/com/android/settings/notificationlight/ColorPickerView.java841
-rw-r--r--src/com/android/settings/notificationlight/LightSettingsDialog.java444
-rw-r--r--src/com/android/settings/notificationlight/NotificationLightSettings.java579
-rw-r--r--src/com/android/settings/privacyguard/AppInfoLoader.java124
-rw-r--r--src/com/android/settings/privacyguard/PrivacyGuardAppListAdapter.java185
-rw-r--r--src/com/android/settings/privacyguard/PrivacyGuardManager.java409
-rw-r--r--src/com/android/settings/privacyguard/PrivacyGuardPrefs.java89
-rw-r--r--src/com/android/settings/profiles/AppGroupConfig.java334
-rw-r--r--src/com/android/settings/profiles/AppGroupList.java162
-rw-r--r--src/com/android/settings/profiles/NFCProfile.java156
-rw-r--r--src/com/android/settings/profiles/NFCProfileSelect.java116
-rw-r--r--src/com/android/settings/profiles/NFCProfileTagCallback.java (renamed from src/com/android/settings/net/ChartData.java)15
-rw-r--r--src/com/android/settings/profiles/NFCProfileUtils.java130
-rw-r--r--src/com/android/settings/profiles/NFCProfileWriter.java116
-rw-r--r--src/com/android/settings/profiles/NamePreference.java126
-rw-r--r--src/com/android/settings/profiles/ProfileGroupConfig.java140
-rw-r--r--src/com/android/settings/profiles/ProfileRingtonePreference.java58
-rw-r--r--src/com/android/settings/profiles/ProfilesPreference.java137
-rw-r--r--src/com/android/settings/profiles/ProfilesSettings.java315
-rw-r--r--src/com/android/settings/profiles/SetupActionsFragment.java1166
-rw-r--r--src/com/android/settings/profiles/SetupDefaultProfileReceiver.java43
-rw-r--r--src/com/android/settings/profiles/SetupTriggersFragment.java180
-rw-r--r--src/com/android/settings/profiles/TriggerPagerAdapter.java218
-rw-r--r--src/com/android/settings/profiles/actions/ItemListAdapter.java77
-rw-r--r--src/com/android/settings/profiles/actions/item/AirplaneModeItem.java82
-rw-r--r--src/com/android/settings/profiles/actions/item/AppGroupItem.java96
-rw-r--r--src/com/android/settings/profiles/actions/item/BrightnessItem.java78
-rw-r--r--src/com/android/settings/profiles/actions/item/ConnectionOverrideItem.java145
-rw-r--r--src/com/android/settings/profiles/actions/item/DisabledItem.java66
-rw-r--r--src/com/android/settings/profiles/actions/item/DozeModeItem.java75
-rw-r--r--src/com/android/settings/profiles/actions/item/Header.java58
-rw-r--r--src/com/android/settings/profiles/actions/item/Item.java28
-rw-r--r--src/com/android/settings/profiles/actions/item/LockModeItem.java75
-rw-r--r--src/com/android/settings/profiles/actions/item/NotificationLightModeItem.java75
-rw-r--r--src/com/android/settings/profiles/actions/item/ProfileNameItem.java60
-rw-r--r--src/com/android/settings/profiles/actions/item/RingModeItem.java87
-rw-r--r--src/com/android/settings/profiles/actions/item/TriggerItem.java110
-rw-r--r--src/com/android/settings/profiles/actions/item/VolumeStreamItem.java110
-rw-r--r--src/com/android/settings/profiles/triggers/AbstractTriggerItem.java58
-rw-r--r--src/com/android/settings/profiles/triggers/BluetoothTriggerFragment.java278
-rw-r--r--src/com/android/settings/profiles/triggers/NfcTriggerFragment.java125
-rw-r--r--src/com/android/settings/profiles/triggers/WifiTriggerFragment.java288
-rw-r--r--src/com/android/settings/search/BaseSearchIndexProvider.java4
-rw-r--r--src/com/android/settings/search/Index.java39
-rw-r--r--src/com/android/settings/search/IndexDatabaseHelper.java2
-rw-r--r--src/com/android/settings/search/Indexable.java5
-rw-r--r--src/com/android/settings/search/Ranking.java14
-rw-r--r--src/com/android/settings/search/SearchIndexableResources.java69
-rw-r--r--src/com/android/settings/sim/SimDialogActivity.java152
-rw-r--r--src/com/android/settings/sim/SimPreferenceDialog.java7
-rw-r--r--src/com/android/settings/sim/SimSelectNotification.java8
-rw-r--r--src/com/android/settings/sim/SimSettings.java811
-rw-r--r--src/com/android/settings/users/EditUserInfoController.java4
-rw-r--r--src/com/android/settings/users/EditUserPhotoController.java4
-rw-r--r--src/com/android/settings/users/RestrictedProfileSettings.java2
-rw-r--r--src/com/android/settings/users/UserSettings.java2
-rw-r--r--src/com/android/settings/utils/TelephonyUtils.java231
-rw-r--r--src/com/android/settings/voicewakeup/VoiceWakeupSettings.java243
-rw-r--r--src/com/android/settings/widget/ChartDataUsageView.java22
-rw-r--r--src/com/android/settings/widget/CheckableLinearLayout.java64
-rw-r--r--src/com/android/settings/widget/InertCheckBox.java84
-rw-r--r--src/com/android/settings/widget/SwitchBar.java26
-rw-r--r--src/com/android/settings/wifi/AccessPointPreference.java30
-rw-r--r--src/com/android/settings/wifi/AdvancedWifiSettings.java48
-rw-r--r--src/com/android/settings/wifi/SavedAccessPointsWifiSettings.java201
-rw-r--r--src/com/android/settings/wifi/WifiApEnabler.java44
-rw-r--r--src/com/android/settings/wifi/WifiConfigController.java68
-rw-r--r--src/com/android/settings/wifi/WifiEnabler.java92
-rw-r--r--src/com/android/settings/wifi/WifiSettings.java13
-rw-r--r--src/com/android/settings/wifi/p2p/WifiP2pSettings.java7
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);
}