summaryrefslogtreecommitdiffstats
path: root/src/com
diff options
context:
space:
mode:
Diffstat (limited to 'src/com')
-rw-r--r--src/com/android/settings/AccountPreference.java2
-rw-r--r--src/com/android/settings/ActiveNetworkScorerDialog.java105
-rw-r--r--src/com/android/settings/AirplaneModeEnabler.java17
-rw-r--r--src/com/android/settings/AirplaneModeVoiceActivity.java40
-rw-r--r--src/com/android/settings/AllowBindAppWidgetActivity.java40
-rw-r--r--src/com/android/settings/ApnEditor.java12
-rw-r--r--src/com/android/settings/ApnPreference.java1
-rw-r--r--src/com/android/settings/ApnSettings.java106
-rw-r--r--src/com/android/settings/AppListPreference.java119
-rw-r--r--src/com/android/settings/BandMode.java5
-rw-r--r--src/com/android/settings/BrightnessPreference.java4
-rw-r--r--src/com/android/settings/ButtonBarHandler.java2
-rw-r--r--src/com/android/settings/ChooseLockGeneric.java150
-rw-r--r--src/com/android/settings/ChooseLockPassword.java61
-rw-r--r--src/com/android/settings/ChooseLockPattern.java31
-rw-r--r--src/com/android/settings/ChooseLockSettingsHelper.java41
-rw-r--r--src/com/android/settings/ConfirmDeviceCredentialActivity.java65
-rw-r--r--src/com/android/settings/ConfirmLockPassword.java52
-rw-r--r--src/com/android/settings/ConfirmLockPattern.java18
-rw-r--r--src/com/android/settings/CreateShortcut.java1
-rw-r--r--src/com/android/settings/CredentialStorage.java31
-rw-r--r--src/com/android/settings/CryptKeeper.java410
-rw-r--r--src/com/android/settings/CryptKeeperConfirm.java41
-rw-r--r--src/com/android/settings/CryptKeeperSettings.java44
-rw-r--r--src/com/android/settings/DataUsageSummary.java697
-rw-r--r--src/com/android/settings/DateTimeSettings.java24
-rw-r--r--src/com/android/settings/DevelopmentSettings.java508
-rw-r--r--src/com/android/settings/DeviceAdminAdd.java236
-rw-r--r--src/com/android/settings/DeviceAdminSettings.java414
-rw-r--r--src/com/android/settings/DeviceInfoSettings.java192
-rw-r--r--src/com/android/settings/DisplaySettings.java245
-rw-r--r--src/com/android/settings/DreamSettings.java68
-rw-r--r--src/com/android/settings/EncryptionInterstitial.java212
-rw-r--r--src/com/android/settings/EventLogTags.logtags6
-rw-r--r--src/com/android/settings/HighlightingFragment.java126
-rw-r--r--src/com/android/settings/HomeSettings.java174
-rw-r--r--src/com/android/settings/KeyguardAppWidgetPickActivity.java634
-rw-r--r--src/com/android/settings/LocalePicker.java9
-rw-r--r--src/com/android/settings/ManagedProfileSetup.java86
-rw-r--r--src/com/android/settings/MasterClear.java45
-rw-r--r--src/com/android/settings/MasterClearConfirm.java80
-rw-r--r--src/com/android/settings/OwnerInfoSettings.java25
-rw-r--r--src/com/android/settings/PinnedHeaderListFragment.java47
-rw-r--r--src/com/android/settings/PrivacySettings.java142
-rw-r--r--src/com/android/settings/ProgressCategory.java24
-rw-r--r--src/com/android/settings/ProgressCategoryBase.java15
-rw-r--r--src/com/android/settings/ProxySelector.java69
-rw-r--r--src/com/android/settings/RadioInfo.java23
-rw-r--r--src/com/android/settings/RestrictedSettingsFragment.java198
-rw-r--r--src/com/android/settings/RingerVolumePreference.java388
-rw-r--r--src/com/android/settings/ScreenPinningSettings.java118
-rw-r--r--src/com/android/settings/SecuritySettings.java599
-rw-r--r--src/com/android/settings/Settings.java1161
-rw-r--r--src/com/android/settings/SettingsActivity.java1358
-rw-r--r--src/com/android/settings/SettingsPreferenceFragment.java239
-rw-r--r--src/com/android/settings/SmsDefaultDialog.java2
-rw-r--r--src/com/android/settings/SmsListPreference.java86
-rw-r--r--src/com/android/settings/SoundSettings.java445
-rw-r--r--src/com/android/settings/SubSettings.java5
-rw-r--r--src/com/android/settings/TetherSettings.java83
-rw-r--r--src/com/android/settings/TrustAgentSettings.java157
-rw-r--r--src/com/android/settings/TrustAgentUtils.java119
-rw-r--r--src/com/android/settings/TrustedCredentialsSettings.java562
-rw-r--r--src/com/android/settings/UsageAccessSettings.java394
-rwxr-xr-xsrc/com/android/settings/UsageStatsActivity.java (renamed from src/com/android/settings/UsageStats.java)209
-rw-r--r--src/com/android/settings/UserDictionarySettings.java5
-rw-r--r--src/com/android/settings/UserSpinnerAdapter.java148
-rw-r--r--src/com/android/settings/Utils.java609
-rw-r--r--src/com/android/settings/VoiceInputOutputSettings.java191
-rw-r--r--src/com/android/settings/VoiceSettingsActivity.java51
-rw-r--r--src/com/android/settings/WallpaperTypeSettings.java43
-rw-r--r--src/com/android/settings/WirelessSettings.java258
-rw-r--r--src/com/android/settings/accessibility/AccessibilitySettings.java283
-rw-r--r--src/com/android/settings/accessibility/CaptionPropertiesFragment.java135
-rw-r--r--src/com/android/settings/accessibility/ColorPreference.java2
-rw-r--r--src/com/android/settings/accessibility/EdgeTypePreference.java6
-rw-r--r--src/com/android/settings/accessibility/ListDialogPreference.java14
-rw-r--r--src/com/android/settings/accessibility/LocalePreference.java100
-rw-r--r--src/com/android/settings/accessibility/PresetPreference.java8
-rw-r--r--src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java161
-rw-r--r--src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java103
-rw-r--r--src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java98
-rw-r--r--src/com/android/settings/accessibility/ToggleGlobalGesturePreferenceFragment.java9
-rw-r--r--src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java9
-rw-r--r--src/com/android/settings/accounts/AccountPreferenceBase.java82
-rw-r--r--src/com/android/settings/accounts/AccountSettings.java588
-rw-r--r--src/com/android/settings/accounts/AccountSyncSettings.java153
-rw-r--r--src/com/android/settings/accounts/AddAccountSettings.java22
-rw-r--r--src/com/android/settings/accounts/AuthenticatorHelper.java137
-rw-r--r--src/com/android/settings/accounts/ChooseAccountActivity.java32
-rw-r--r--src/com/android/settings/accounts/ManageAccountsSettings.java145
-rw-r--r--src/com/android/settings/accounts/SyncSettings.java179
-rw-r--r--src/com/android/settings/accounts/SyncSettingsActivity.java43
-rw-r--r--src/com/android/settings/applications/AppOpsCategory.java6
-rw-r--r--src/com/android/settings/applications/AppOpsDetails.java6
-rw-r--r--src/com/android/settings/applications/AppOpsState.java15
-rw-r--r--src/com/android/settings/applications/AppOpsSummary.java3
-rwxr-xr-x[-rw-r--r--]src/com/android/settings/applications/InstalledAppDetails.java128
-rw-r--r--src/com/android/settings/applications/InstalledAppDetailsTop.java25
-rw-r--r--src/com/android/settings/applications/LinearColorBar.java6
-rw-r--r--src/com/android/settings/applications/LinearColorPreference.java12
-rw-r--r--src/com/android/settings/applications/ManageApplications.java105
-rw-r--r--src/com/android/settings/applications/ProcStatsEntry.java35
-rw-r--r--src/com/android/settings/applications/ProcessStatsDetail.java18
-rw-r--r--src/com/android/settings/applications/ProcessStatsMemDetail.java152
-rw-r--r--src/com/android/settings/applications/ProcessStatsPreference.java29
-rw-r--r--src/com/android/settings/applications/ProcessStatsUi.java327
-rw-r--r--src/com/android/settings/applications/RunningProcessesView.java158
-rw-r--r--src/com/android/settings/applications/RunningServiceDetails.java1
-rw-r--r--src/com/android/settings/applications/RunningState.java9
-rw-r--r--src/com/android/settings/bluetooth/BluetoothDevicePreference.java21
-rwxr-xr-xsrc/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java14
-rw-r--r--src/com/android/settings/bluetooth/BluetoothEnabler.java109
-rwxr-xr-xsrc/com/android/settings/bluetooth/BluetoothEventManager.java2
-rw-r--r--src/com/android/settings/bluetooth/BluetoothNameDialogFragment.java4
-rwxr-xr-xsrc/com/android/settings/bluetooth/BluetoothPairingDialog.java108
-rw-r--r--src/com/android/settings/bluetooth/BluetoothPairingRequest.java17
-rwxr-xr-xsrc/com/android/settings/bluetooth/BluetoothPermissionActivity.java68
-rw-r--r--src/com/android/settings/bluetooth/BluetoothPermissionRequest.java121
-rw-r--r--src/com/android/settings/bluetooth/BluetoothProgressCategory.java20
-rwxr-xr-xsrc/com/android/settings/bluetooth/BluetoothSettings.java363
-rwxr-xr-xsrc/com/android/settings/bluetooth/CachedBluetoothDevice.java218
-rwxr-xr-xsrc/com/android/settings/bluetooth/CachedBluetoothDeviceManager.java17
-rw-r--r--src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java14
-rw-r--r--src/com/android/settings/bluetooth/DevicePickerFragment.java29
-rwxr-xr-xsrc/com/android/settings/bluetooth/DeviceProfilesSettings.java182
-rwxr-xr-xsrc/com/android/settings/bluetooth/HeadsetProfile.java33
-rwxr-xr-xsrc/com/android/settings/bluetooth/HidProfile.java4
-rw-r--r--src/com/android/settings/bluetooth/LocalBluetoothProfileManager.java4
-rw-r--r--src/com/android/settings/bluetooth/MapProfile.java2
-rwxr-xr-xsrc/com/android/settings/bluetooth/PbapServerProfile.java7
-rwxr-xr-xsrc/com/android/settings/bluetooth/Utils.java18
-rw-r--r--src/com/android/settings/dashboard/DashboardCategory.java145
-rw-r--r--src/com/android/settings/dashboard/DashboardContainerView.java142
-rw-r--r--src/com/android/settings/dashboard/DashboardSummary.java174
-rw-r--r--src/com/android/settings/dashboard/DashboardTile.java177
-rw-r--r--src/com/android/settings/dashboard/DashboardTileView.java99
-rw-r--r--src/com/android/settings/dashboard/NoHomeDialogFragment.java39
-rw-r--r--src/com/android/settings/dashboard/SearchResultsSummary.java629
-rw-r--r--src/com/android/settings/deviceinfo/Memory.java92
-rw-r--r--src/com/android/settings/deviceinfo/Status.java30
-rw-r--r--src/com/android/settings/deviceinfo/StorageVolumePreferenceCategory.java7
-rw-r--r--src/com/android/settings/deviceinfo/UsageBarPreference.java19
-rw-r--r--src/com/android/settings/drawable/CircleFramedDrawable.java (renamed from src/com/android/settings/users/CircleFramedDrawable.java)6
-rw-r--r--src/com/android/settings/fuelgauge/BatteryEntry.java333
-rw-r--r--src/com/android/settings/fuelgauge/BatteryHistoryChart.java1151
-rw-r--r--src/com/android/settings/fuelgauge/BatteryHistoryDetail.java22
-rw-r--r--src/com/android/settings/fuelgauge/BatteryHistoryPreference.java39
-rw-r--r--src/com/android/settings/fuelgauge/BatterySaverSettings.java217
-rw-r--r--src/com/android/settings/fuelgauge/BatterySipper.java242
-rw-r--r--src/com/android/settings/fuelgauge/BatteryStatsHelper.java836
-rw-r--r--src/com/android/settings/fuelgauge/PowerGaugePreference.java20
-rw-r--r--src/com/android/settings/fuelgauge/PowerUsageDetail.java283
-rw-r--r--src/com/android/settings/fuelgauge/PowerUsageSummary.java246
-rw-r--r--src/com/android/settings/fuelgauge/Utils.java84
-rw-r--r--src/com/android/settings/inputmethod/CheckBoxAndSettingsPreference.java117
-rw-r--r--src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java596
-rw-r--r--src/com/android/settings/inputmethod/InputMethodAndSubtypeEnabler.java489
-rw-r--r--src/com/android/settings/inputmethod/InputMethodAndSubtypeEnablerActivity.java33
-rw-r--r--src/com/android/settings/inputmethod/InputMethodAndSubtypeUtil.java233
-rwxr-xr-x[-rw-r--r--]src/com/android/settings/inputmethod/InputMethodPreference.java422
-rw-r--r--src/com/android/settings/inputmethod/InputMethodSettingValuesWrapper.java40
-rw-r--r--src/com/android/settings/inputmethod/InputMethodSubtypePreference.java93
-rw-r--r--src/com/android/settings/inputmethod/KeyboardLayoutDialogFragment.java2
-rw-r--r--src/com/android/settings/inputmethod/SingleSpellCheckerPreference.java218
-rw-r--r--src/com/android/settings/inputmethod/SpellCheckerPreference.java151
-rw-r--r--src/com/android/settings/inputmethod/SpellCheckerUtils.java47
-rw-r--r--src/com/android/settings/inputmethod/SpellCheckersPreference.java39
-rw-r--r--src/com/android/settings/inputmethod/SpellCheckersSettings.java235
-rw-r--r--src/com/android/settings/inputmethod/SwitchWithNoTextPreference.java (renamed from src/com/android/settings/wifi/AccessPointCategoryForSetupWizardXL.java)25
-rw-r--r--src/com/android/settings/inputmethod/UserDictionaryAddWordContents.java4
-rw-r--r--src/com/android/settings/inputmethod/UserDictionaryAddWordFragment.java6
-rw-r--r--src/com/android/settings/inputmethod/UserDictionaryList.java27
-rw-r--r--src/com/android/settings/location/DimmableIconPreference.java63
-rw-r--r--src/com/android/settings/location/InjectedSetting.java2
-rw-r--r--src/com/android/settings/location/LocationSettings.java101
-rw-r--r--src/com/android/settings/location/LocationSettingsBase.java55
-rw-r--r--src/com/android/settings/location/RecentLocationApps.java130
-rw-r--r--src/com/android/settings/location/SettingsInjector.java12
-rw-r--r--src/com/android/settings/net/DataUsageMeteredSettings.java91
-rw-r--r--src/com/android/settings/net/UidDetail.java2
-rw-r--r--src/com/android/settings/net/UidDetailProvider.java74
-rw-r--r--src/com/android/settings/nfc/AndroidBeam.java70
-rw-r--r--src/com/android/settings/nfc/NfcEnabler.java43
-rw-r--r--src/com/android/settings/nfc/PaymentBackend.java15
-rw-r--r--src/com/android/settings/nfc/PaymentDefaultDialog.java30
-rw-r--r--src/com/android/settings/nfc/PaymentSettings.java29
-rw-r--r--src/com/android/settings/notification/AppNotificationSettings.java230
-rw-r--r--src/com/android/settings/notification/ConditionProviderSettings.java55
-rw-r--r--src/com/android/settings/notification/DropDownPreference.java131
-rw-r--r--src/com/android/settings/notification/ManagedServiceSettings.java (renamed from src/com/android/settings/NotificationAccessSettings.java)158
-rw-r--r--src/com/android/settings/notification/NotificationAccessSettings.java55
-rw-r--r--src/com/android/settings/notification/NotificationAppList.java594
-rw-r--r--src/com/android/settings/notification/NotificationSettings.java515
-rw-r--r--src/com/android/settings/notification/NotificationStation.java (renamed from src/com/android/settings/NotificationStation.java)170
-rw-r--r--src/com/android/settings/notification/OtherSoundSettings.java254
-rw-r--r--src/com/android/settings/notification/RedactionInterstitial.java102
-rw-r--r--src/com/android/settings/notification/RedactionSettingsStandalone.java43
-rw-r--r--src/com/android/settings/notification/SettingPref.java151
-rw-r--r--src/com/android/settings/notification/VolumeSeekBarPreference.java139
-rw-r--r--src/com/android/settings/notification/ZenModeAutomaticConditionSelection.java157
-rw-r--r--src/com/android/settings/notification/ZenModeConditionSelection.java153
-rw-r--r--src/com/android/settings/notification/ZenModeDowntimeDaysSelection.java96
-rw-r--r--src/com/android/settings/notification/ZenModeSettings.java725
-rw-r--r--src/com/android/settings/print/PrintJobSettingsFragment.java38
-rw-r--r--src/com/android/settings/print/PrintServiceSettingsFragment.java149
-rw-r--r--src/com/android/settings/print/PrintSettingsFragment.java146
-rw-r--r--src/com/android/settings/print/PrintSettingsUtils.java (renamed from src/com/android/settings/print/SettingsUtils.java)4
-rw-r--r--src/com/android/settings/quicklaunch/BookmarkPicker.java2
-rw-r--r--src/com/android/settings/quicklaunch/QuickLaunchSettings.java68
-rw-r--r--src/com/android/settings/search/BaseSearchIndexProvider.java49
-rw-r--r--src/com/android/settings/search/DynamicIndexableContentMonitor.java307
-rw-r--r--src/com/android/settings/search/Index.java1320
-rw-r--r--src/com/android/settings/search/IndexDatabaseHelper.java223
-rw-r--r--src/com/android/settings/search/Indexable.java69
-rw-r--r--src/com/android/settings/search/Ranking.java180
-rw-r--r--src/com/android/settings/search/SearchIndexableRaw.java64
-rw-r--r--src/com/android/settings/search/SearchIndexableResources.java281
-rw-r--r--src/com/android/settings/search/SettingsSearchIndexablesProvider.java75
-rw-r--r--src/com/android/settings/sim/SimSettings.java393
-rw-r--r--src/com/android/settings/tts/TextToSpeechSettings.java104
-rw-r--r--src/com/android/settings/tts/TtsEnginePreference.java25
-rw-r--r--src/com/android/settings/tts/TtsEngineSettingsFragment.java134
-rw-r--r--src/com/android/settings/users/AppRestrictionsFragment.java44
-rw-r--r--src/com/android/settings/users/EditUserInfoController.java197
-rw-r--r--src/com/android/settings/users/EditUserPhotoController.java346
-rw-r--r--src/com/android/settings/users/RestrictedProfileSettings.java457
-rw-r--r--src/com/android/settings/users/RestrictionUtils.java4
-rw-r--r--src/com/android/settings/users/UserDetailsSettings.java173
-rw-r--r--src/com/android/settings/users/UserPreference.java14
-rw-r--r--src/com/android/settings/users/UserSettings.java411
-rw-r--r--src/com/android/settings/users/UserUtils.java34
-rw-r--r--src/com/android/settings/voice/VoiceInputHelper.java211
-rw-r--r--src/com/android/settings/voice/VoiceInputPreference.java233
-rw-r--r--src/com/android/settings/voice/VoiceInputSettings.java242
-rw-r--r--src/com/android/settings/vpn2/VpnSettings.java31
-rwxr-xr-xsrc/com/android/settings/wfd/WifiDisplaySettings.java6
-rw-r--r--src/com/android/settings/widget/ChartDataUsageView.java93
-rw-r--r--src/com/android/settings/widget/ChartGridView.java94
-rw-r--r--src/com/android/settings/widget/ChartNetworkSeriesView.java42
-rw-r--r--src/com/android/settings/widget/ChartSweepView.java9
-rw-r--r--src/com/android/settings/widget/ChartView.java9
-rw-r--r--src/com/android/settings/widget/PieChartView.java244
-rw-r--r--src/com/android/settings/widget/SetupWizardIllustration.java161
-rw-r--r--src/com/android/settings/widget/StickyHeaderListView.java139
-rw-r--r--src/com/android/settings/widget/SwitchBar.java251
-rw-r--r--src/com/android/settings/widget/ToggleSwitch.java (renamed from src/com/android/settings/accessibility/ToggleSwitch.java)16
-rw-r--r--src/com/android/settings/wifi/AccessPoint.java364
-rw-r--r--src/com/android/settings/wifi/AdvancedWifiSettings.java159
-rw-r--r--src/com/android/settings/wifi/SavedAccessPointsWifiSettings.java253
-rw-r--r--src/com/android/settings/wifi/WifiApDialog.java21
-rw-r--r--src/com/android/settings/wifi/WifiApEnabler.java56
-rw-r--r--src/com/android/settings/wifi/WifiConfigController.java355
-rw-r--r--src/com/android/settings/wifi/WifiDialog.java19
-rw-r--r--src/com/android/settings/wifi/WifiEnabler.java172
-rw-r--r--src/com/android/settings/wifi/WifiPickerActivity.java79
-rw-r--r--src/com/android/settings/wifi/WifiSettings.java834
-rw-r--r--src/com/android/settings/wifi/WifiSettingsForSetupWizard.java177
-rw-r--r--src/com/android/settings/wifi/WifiSetupActivity.java282
-rw-r--r--src/com/android/settings/wifi/WpsDialog.java36
-rw-r--r--src/com/android/settings/wifi/WriteWifiConfigToNfcDialog.java283
-rw-r--r--src/com/android/settings/wifi/p2p/WifiP2pSettings.java60
262 files changed, 28131 insertions, 12011 deletions
diff --git a/src/com/android/settings/AccountPreference.java b/src/com/android/settings/AccountPreference.java
index 2cc013c..7547721 100644
--- a/src/com/android/settings/AccountPreference.java
+++ b/src/com/android/settings/AccountPreference.java
@@ -140,6 +140,8 @@ public class AccountPreference extends Preference {
return getContext().getString(R.string.accessibility_sync_disabled);
case SYNC_ERROR:
return getContext().getString(R.string.accessibility_sync_error);
+ case SYNC_IN_PROGRESS:
+ return getContext().getString(R.string.accessibility_sync_in_progress);
default:
Log.e(TAG, "Unknown sync status: " + status);
return getContext().getString(R.string.accessibility_sync_error);
diff --git a/src/com/android/settings/ActiveNetworkScorerDialog.java b/src/com/android/settings/ActiveNetworkScorerDialog.java
new file mode 100644
index 0000000..e47f8e3
--- /dev/null
+++ b/src/com/android/settings/ActiveNetworkScorerDialog.java
@@ -0,0 +1,105 @@
+/*
+ * 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.content.DialogInterface;
+import android.content.Intent;
+import android.net.NetworkScoreManager;
+import android.net.NetworkScorerAppManager;
+import android.net.NetworkScorerAppManager.NetworkScorerAppData;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+
+/**
+ * Dialog to allow a user to select a new network scorer.
+ *
+ * <p>Finishes with {@link #RESULT_CANCELED} in all circumstances unless the scorer is successfully
+ * changed or was already set to the new value (in which case it finishes with {@link #RESULT_OK}).
+ */
+public final class ActiveNetworkScorerDialog extends AlertActivity implements
+ DialogInterface.OnClickListener {
+ private static final String TAG = "ActiveNetScorerDlg";
+
+ private String mNewPackageName;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Intent intent = getIntent();
+ mNewPackageName = intent.getStringExtra(NetworkScoreManager.EXTRA_PACKAGE_NAME);
+
+ if (!buildDialog()) {
+ finish();
+ }
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ switch (which) {
+ case BUTTON_POSITIVE:
+ NetworkScoreManager nsm =
+ (NetworkScoreManager) getSystemService(Context.NETWORK_SCORE_SERVICE);
+ if (nsm.setActiveScorer(mNewPackageName)) {
+ setResult(RESULT_OK);
+ }
+ break;
+ case BUTTON_NEGATIVE:
+ break;
+ }
+ }
+
+ private boolean buildDialog() {
+ NetworkScorerAppData newScorer = NetworkScorerAppManager.getScorer(this, mNewPackageName);
+ if (newScorer == null) {
+ Log.e(TAG, "New package " + mNewPackageName + " is not a valid scorer.");
+ return false;
+ }
+
+ NetworkScorerAppData oldScorer = NetworkScorerAppManager.getActiveScorer(this);
+ if (oldScorer != null && TextUtils.equals(oldScorer.mPackageName, mNewPackageName)) {
+ Log.i(TAG, "New package " + mNewPackageName + " is already the active scorer.");
+ // Set RESULT_OK to indicate to the caller that the "switch" was successful.
+ setResult(RESULT_OK);
+ return false;
+ }
+
+ // Compose dialog.
+ CharSequence newName = newScorer.mScorerName;
+ final AlertController.AlertParams p = mAlertParams;
+ p.mTitle = getString(R.string.network_scorer_change_active_dialog_title);
+ if (oldScorer != null) {
+ p.mMessage = getString(R.string.network_scorer_change_active_dialog_text, newName,
+ oldScorer.mScorerName);
+ } else {
+ p.mMessage = getString(R.string.network_scorer_change_active_no_previous_dialog_text,
+ newName);
+ }
+ p.mPositiveButtonText = getString(R.string.yes);
+ p.mNegativeButtonText = getString(R.string.no);
+ p.mPositiveButtonListener = this;
+ p.mNegativeButtonListener = this;
+ setupAlert();
+
+ return true;
+ }
+}
diff --git a/src/com/android/settings/AirplaneModeEnabler.java b/src/com/android/settings/AirplaneModeEnabler.java
index d1c591e..4ce5198 100644
--- a/src/com/android/settings/AirplaneModeEnabler.java
+++ b/src/com/android/settings/AirplaneModeEnabler.java
@@ -25,6 +25,7 @@ import android.os.SystemProperties;
import android.os.UserHandle;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
+import android.preference.SwitchPreference;
import android.provider.Settings;
import com.android.internal.telephony.PhoneStateIntentReceiver;
@@ -36,7 +37,7 @@ public class AirplaneModeEnabler implements Preference.OnPreferenceChangeListene
private PhoneStateIntentReceiver mPhoneStateReceiver;
- private final CheckBoxPreference mCheckBoxPref;
+ private final SwitchPreference mSwitchPref;
private static final int EVENT_SERVICE_STATE_CHANGED = 3;
@@ -58,10 +59,10 @@ public class AirplaneModeEnabler implements Preference.OnPreferenceChangeListene
}
};
- public AirplaneModeEnabler(Context context, CheckBoxPreference airplaneModeCheckBoxPreference) {
+ public AirplaneModeEnabler(Context context, SwitchPreference airplaneModeCheckBoxPreference) {
mContext = context;
- mCheckBoxPref = airplaneModeCheckBoxPreference;
+ mSwitchPref = airplaneModeCheckBoxPreference;
airplaneModeCheckBoxPreference.setPersistent(false);
@@ -71,10 +72,10 @@ public class AirplaneModeEnabler implements Preference.OnPreferenceChangeListene
public void resume() {
- mCheckBoxPref.setChecked(isAirplaneModeOn(mContext));
+ mSwitchPref.setChecked(isAirplaneModeOn(mContext));
mPhoneStateReceiver.registerIntent();
- mCheckBoxPref.setOnPreferenceChangeListener(this);
+ mSwitchPref.setOnPreferenceChangeListener(this);
mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true,
mAirplaneModeObserver);
@@ -82,7 +83,7 @@ public class AirplaneModeEnabler implements Preference.OnPreferenceChangeListene
public void pause() {
mPhoneStateReceiver.unregisterIntent();
- mCheckBoxPref.setOnPreferenceChangeListener(null);
+ mSwitchPref.setOnPreferenceChangeListener(null);
mContext.getContentResolver().unregisterContentObserver(mAirplaneModeObserver);
}
@@ -96,7 +97,7 @@ public class AirplaneModeEnabler implements Preference.OnPreferenceChangeListene
Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON,
enabling ? 1 : 0);
// Update the UI to reflect system setting
- mCheckBoxPref.setChecked(enabling);
+ mSwitchPref.setChecked(enabling);
// Post the intent
Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
@@ -113,7 +114,7 @@ public class AirplaneModeEnabler implements Preference.OnPreferenceChangeListene
* - mobile does not send failure notification, fail on timeout.
*/
private void onAirplaneModeChanged() {
- mCheckBoxPref.setChecked(isAirplaneModeOn(mContext));
+ mSwitchPref.setChecked(isAirplaneModeOn(mContext));
}
/**
diff --git a/src/com/android/settings/AirplaneModeVoiceActivity.java b/src/com/android/settings/AirplaneModeVoiceActivity.java
new file mode 100644
index 0000000..3ab0c37
--- /dev/null
+++ b/src/com/android/settings/AirplaneModeVoiceActivity.java
@@ -0,0 +1,40 @@
+/*
+ * 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.Intent;
+import android.provider.Settings;
+import android.util.Log;
+
+/**
+ * Activity for modifying the {@link Settings.Global#AIRPLANE_MODE_ON AIRPLANE_MODE_ON}
+ * setting using the Voice Interaction API.
+ */
+public class AirplaneModeVoiceActivity extends VoiceSettingsActivity {
+ private static final String TAG = "AirplaneModeVoiceActivity";
+
+ protected void onVoiceSettingInteraction(Intent intent) {
+ if (intent.hasExtra(Settings.EXTRA_AIRPLANE_MODE_ENABLED)) {
+ boolean enabled =
+ intent.getBooleanExtra(Settings.EXTRA_AIRPLANE_MODE_ENABLED, false);
+ Settings.Global.putInt(getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_ON, enabled ? 1 : 0);
+ } else {
+ Log.v(TAG, "Missing airplane mode extra");
+ }
+ }
+}
diff --git a/src/com/android/settings/AllowBindAppWidgetActivity.java b/src/com/android/settings/AllowBindAppWidgetActivity.java
index f05c1c2..c3bf78a 100644
--- a/src/com/android/settings/AllowBindAppWidgetActivity.java
+++ b/src/com/android/settings/AllowBindAppWidgetActivity.java
@@ -25,7 +25,7 @@ import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
-import android.util.DisplayMetrics;
+import android.os.UserHandle;
import android.util.Log;
import android.view.LayoutInflater;
import android.widget.CheckBox;
@@ -42,6 +42,7 @@ public class AllowBindAppWidgetActivity extends AlertActivity implements
private CheckBox mAlwaysUse;
private int mAppWidgetId;
+ private UserHandle mProfile;
private ComponentName mComponentName;
private String mCallingPackage;
private AppWidgetManager mAppWidgetManager;
@@ -55,27 +56,32 @@ public class AllowBindAppWidgetActivity extends AlertActivity implements
setResult(RESULT_CANCELED);
if (mAppWidgetId != -1 && mComponentName != null && mCallingPackage != null) {
try {
- mAppWidgetManager.bindAppWidgetId(mAppWidgetId, mComponentName);
- Intent result = new Intent();
- result.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
- setResult(RESULT_OK, result);
+ final boolean bound = mAppWidgetManager.bindAppWidgetIdIfAllowed(mAppWidgetId,
+ mProfile, mComponentName, null);
+ if (bound) {
+ Intent result = new Intent();
+ result.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
+ setResult(RESULT_OK, result);
+ }
} catch (Exception e) {
Log.v("BIND_APPWIDGET", "Error binding widget with id "
+ mAppWidgetId + " and component " + mComponentName);
}
- }
- boolean alwaysAllowBind = mAlwaysUse.isChecked();
- if (alwaysAllowBind != mAppWidgetManager.hasBindAppWidgetPermission(mCallingPackage)) {
- mAppWidgetManager.setBindAppWidgetPermission(mCallingPackage, alwaysAllowBind);
+
+ final boolean alwaysAllowBind = mAlwaysUse.isChecked();
+ if (alwaysAllowBind != mAppWidgetManager.hasBindAppWidgetPermission(
+ mCallingPackage)) {
+ mAppWidgetManager.setBindAppWidgetPermission(mCallingPackage,
+ alwaysAllowBind);
+ }
}
}
finish();
}
- protected void onDestroy() {
- if (!mClicked) {
+ protected void onPause() {
+ if (isDestroyed() && !mClicked) {
setResult(RESULT_CANCELED);
- finish();
}
super.onDestroy();
}
@@ -87,7 +93,12 @@ public class AllowBindAppWidgetActivity extends AlertActivity implements
if (intent != null) {
try {
mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
- mComponentName = (ComponentName)
+ mProfile = intent.getParcelableExtra(
+ AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE);
+ if (mProfile == null) {
+ mProfile = android.os.Process.myUserHandle();
+ }
+ mComponentName =
intent.getParcelableExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER);
mCallingPackage = getCallingPackage();
PackageManager pm = getPackageManager();
@@ -123,7 +134,8 @@ public class AllowBindAppWidgetActivity extends AlertActivity implements
getResources().getDimension(R.dimen.bind_app_widget_dialog_checkbox_bottom_padding)));
mAppWidgetManager = AppWidgetManager.getInstance(this);
- mAlwaysUse.setChecked(mAppWidgetManager.hasBindAppWidgetPermission(mCallingPackage));
+ mAlwaysUse.setChecked(mAppWidgetManager.hasBindAppWidgetPermission(mCallingPackage,
+ mProfile.getIdentifier()));
setupAlert();
}
diff --git a/src/com/android/settings/ApnEditor.java b/src/com/android/settings/ApnEditor.java
index 2da2d76..8cfee92 100644
--- a/src/com/android/settings/ApnEditor.java
+++ b/src/com/android/settings/ApnEditor.java
@@ -327,6 +327,13 @@ public class ApnEditor extends PreferenceActivity
mMvnoType.setSummary(
checkNull(mvnoDescription(mMvnoType.getValue())));
mMvnoMatchData.setSummary(checkNull(mMvnoMatchData.getText()));
+ // allow user to edit carrier_enabled for some APN
+ boolean ceEditable = getResources().getBoolean(R.bool.config_allow_edit_carrier_enabled);
+ if (ceEditable) {
+ mCarrierEnabled.setEnabled(true);
+ } else {
+ mCarrierEnabled.setEnabled(false);
+ }
}
/**
@@ -446,7 +453,7 @@ public class ApnEditor extends PreferenceActivity
// 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)
- .setIcon(R.drawable.ic_menu_delete_holo_dark);
+ .setIcon(R.drawable.ic_menu_delete);
}
menu.add(0, MENU_SAVE, 0, R.string.menu_save)
.setIcon(android.R.drawable.ic_menu_save);
@@ -571,6 +578,7 @@ public class ApnEditor extends PreferenceActivity
values.put(Telephony.Carriers.MVNO_TYPE, checkNotSet(mMvnoType.getValue()));
values.put(Telephony.Carriers.MVNO_MATCH_DATA, checkNotSet(mMvnoMatchData.getText()));
+ values.put(Telephony.Carriers.CARRIER_ENABLED, mCarrierEnabled.isChecked() ? 1 : 0);
getContentResolver().update(mUri, values, null, null);
return true;
@@ -664,6 +672,8 @@ public class ApnEditor extends PreferenceActivity
if (pref != null) {
if (pref.equals(mPassword)){
pref.setSummary(starify(sharedPreferences.getString(key, "")));
+ } else if (pref.equals(mCarrierEnabled)) {
+ // do nothing
} else {
pref.setSummary(checkNull(sharedPreferences.getString(key, "")));
}
diff --git a/src/com/android/settings/ApnPreference.java b/src/com/android/settings/ApnPreference.java
index addb695..1e29d22 100644
--- a/src/com/android/settings/ApnPreference.java
+++ b/src/com/android/settings/ApnPreference.java
@@ -71,6 +71,7 @@ public class ApnPreference extends Preference implements
mProtectFromCheckedChange = true;
rb.setChecked(isChecked);
mProtectFromCheckedChange = false;
+ rb.setVisibility(View.VISIBLE);
} else {
rb.setVisibility(View.GONE);
}
diff --git a/src/com/android/settings/ApnSettings.java b/src/com/android/settings/ApnSettings.java
index 3fbb5e3..0c0e53c 100644
--- a/src/com/android/settings/ApnSettings.java
+++ b/src/com/android/settings/ApnSettings.java
@@ -16,6 +16,7 @@
package com.android.settings;
+import android.app.Activity;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
@@ -32,14 +33,19 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
+import android.os.UserManager;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
import android.provider.Telephony;
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.widget.TextView;
import android.widget.Toast;
import com.android.internal.telephony.Phone;
@@ -49,7 +55,7 @@ import com.android.internal.telephony.TelephonyProperties;
import java.util.ArrayList;
-public class ApnSettings extends PreferenceActivity implements
+public class ApnSettings extends SettingsPreferenceFragment implements
Preference.OnPreferenceChangeListener {
static final String TAG = "ApnSettings";
@@ -83,10 +89,14 @@ public class ApnSettings extends PreferenceActivity implements
private RestoreApnProcessHandler mRestoreApnProcessHandler;
private HandlerThread mRestoreDefaultApnThread;
+ private UserManager mUm;
+
private String mSelectedKey;
private IntentFilter mMobileStateFilter;
+ private boolean mUnavailable;
+
private final BroadcastReceiver mMobileStateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -116,38 +126,68 @@ public class ApnSettings extends PreferenceActivity implements
}
@Override
- protected void onCreate(Bundle icicle) {
+ public void onCreate(Bundle icicle) {
super.onCreate(icicle);
- addPreferencesFromResource(R.xml.apn_settings);
- getListView().setItemsCanFocus(true);
+ mUm = (UserManager) getSystemService(Context.USER_SERVICE);
mMobileStateFilter = new IntentFilter(
TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
+
+ if (!mUm.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)) {
+ setHasOptionsMenu(true);
+ }
}
@Override
- protected void onResume() {
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ TextView empty = (TextView) getView().findViewById(android.R.id.empty);
+ if (empty != null) {
+ empty.setText(R.string.apn_settings_not_available);
+ getListView().setEmptyView(empty);
+ }
+
+ if (mUm.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)) {
+ mUnavailable = true;
+ setPreferenceScreen(new PreferenceScreen(getActivity(), null));
+ return;
+ }
+
+ addPreferencesFromResource(R.xml.apn_settings);
+
+ getListView().setItemsCanFocus(true);
+ }
+
+ @Override
+ public void onResume() {
super.onResume();
- registerReceiver(mMobileStateReceiver, mMobileStateFilter);
+ if (mUnavailable) {
+ return;
+ }
+
+ getActivity().registerReceiver(mMobileStateReceiver, mMobileStateFilter);
if (!mRestoreDefaultApnMode) {
fillList();
- } else {
- showDialog(DIALOG_RESTORE_DEFAULTAPN);
}
}
@Override
- protected void onPause() {
+ public void onPause() {
super.onPause();
- unregisterReceiver(mMobileStateReceiver);
+ if (mUnavailable) {
+ return;
+ }
+
+ getActivity().unregisterReceiver(mMobileStateReceiver);
}
@Override
- protected void onDestroy() {
+ public void onDestroy() {
super.onDestroy();
if (mRestoreDefaultApnThread != null) {
@@ -178,7 +218,7 @@ public class ApnSettings extends PreferenceActivity implements
String key = cursor.getString(ID_INDEX);
String type = cursor.getString(TYPES_INDEX);
- ApnPreference pref = new ApnPreference(this);
+ ApnPreference pref = new ApnPreference(getActivity());
pref.setKey(key);
pref.setTitle(name);
@@ -207,16 +247,18 @@ public class ApnSettings extends PreferenceActivity implements
}
@Override
- public boolean onCreateOptionsMenu(Menu menu) {
- super.onCreateOptionsMenu(menu);
- menu.add(0, MENU_NEW, 0,
- getResources().getString(R.string.menu_new))
- .setIcon(android.R.drawable.ic_menu_add)
- .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
- menu.add(0, MENU_RESTORE, 0,
- getResources().getString(R.string.menu_restore))
- .setIcon(android.R.drawable.ic_menu_upload);
- return true;
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ if (!mUnavailable) {
+ menu.add(0, MENU_NEW, 0,
+ getResources().getString(R.string.menu_new))
+ .setIcon(android.R.drawable.ic_menu_add)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ menu.add(0, MENU_RESTORE, 0,
+ getResources().getString(R.string.menu_restore))
+ .setIcon(android.R.drawable.ic_menu_upload);
+ }
+
+ super.onCreateOptionsMenu(menu, inflater);
}
@Override
@@ -305,12 +347,17 @@ public class ApnSettings extends PreferenceActivity implements
public void handleMessage(Message msg) {
switch (msg.what) {
case EVENT_RESTORE_DEFAULTAPN_COMPLETE:
+ Activity activity = getActivity();
+ if (activity == null) {
+ mRestoreDefaultApnMode = false;
+ return;
+ }
fillList();
getPreferenceScreen().setEnabled(true);
mRestoreDefaultApnMode = false;
- dismissDialog(DIALOG_RESTORE_DEFAULTAPN);
+ removeDialog(DIALOG_RESTORE_DEFAULTAPN);
Toast.makeText(
- ApnSettings.this,
+ activity,
getResources().getString(
R.string.restore_default_apn_completed),
Toast.LENGTH_LONG).show();
@@ -341,20 +388,13 @@ public class ApnSettings extends PreferenceActivity implements
}
@Override
- protected Dialog onCreateDialog(int id) {
+ public Dialog onCreateDialog(int id) {
if (id == DIALOG_RESTORE_DEFAULTAPN) {
- ProgressDialog dialog = new ProgressDialog(this);
+ ProgressDialog dialog = new ProgressDialog(getActivity());
dialog.setMessage(getResources().getString(R.string.restore_default_apn));
dialog.setCancelable(false);
return dialog;
}
return null;
}
-
- @Override
- protected void onPrepareDialog(int id, Dialog dialog) {
- if (id == DIALOG_RESTORE_DEFAULTAPN) {
- getPreferenceScreen().setEnabled(false);
- }
- }
}
diff --git a/src/com/android/settings/AppListPreference.java b/src/com/android/settings/AppListPreference.java
new file mode 100644
index 0000000..2180983
--- /dev/null
+++ b/src/com/android/settings/AppListPreference.java
@@ -0,0 +1,119 @@
+/*
+ * 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;
+
+import android.app.Activity;
+import android.app.AlertDialog.Builder;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.graphics.drawable.Drawable;
+import android.preference.ListPreference;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.CheckedTextView;
+import android.widget.ImageView;
+import android.widget.ListAdapter;
+
+/**
+ * Extends ListPreference to allow us to show the icons for a given list of applications. We do this
+ * because the names of applications are very similar and the user may not be able to determine what
+ * app they are selecting without an icon.
+ */
+public class AppListPreference extends ListPreference {
+ private Drawable[] mEntryDrawables;
+
+ public class AppArrayAdapter extends ArrayAdapter<CharSequence> {
+ private Drawable[] mImageDrawables = null;
+ private int mSelectedIndex = 0;
+
+ public AppArrayAdapter(Context context, int textViewResourceId,
+ CharSequence[] objects, Drawable[] imageDrawables, int selectedIndex) {
+ super(context, textViewResourceId, objects);
+ mSelectedIndex = selectedIndex;
+ mImageDrawables = imageDrawables;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ LayoutInflater inflater = ((Activity)getContext()).getLayoutInflater();
+ View view = inflater.inflate(R.layout.app_preference_item, parent, false);
+ CheckedTextView checkedTextView = (CheckedTextView)view.findViewById(R.id.app_label);
+ checkedTextView.setText(getItem(position));
+ if (position == mSelectedIndex) {
+ checkedTextView.setChecked(true);
+ }
+ ImageView imageView = (ImageView)view.findViewById(R.id.app_image);
+ imageView.setImageDrawable(mImageDrawables[position]);
+ return view;
+ }
+ }
+
+ public AppListPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public void setPackageNames(String[] packageNames, String defaultPackageName) {
+ // Look up all package names in PackageManager. Skip ones we can't find.
+ int foundPackages = 0;
+ PackageManager pm = getContext().getPackageManager();
+ ApplicationInfo[] appInfos = new ApplicationInfo[packageNames.length];
+ for (int i = 0; i < packageNames.length; i++) {
+ try {
+ appInfos[i] = pm.getApplicationInfo(packageNames[i], 0);
+ foundPackages++;
+ } catch (NameNotFoundException e) {
+ // Leave appInfos[i] uninitialized; it will be skipped in the list.
+ }
+ }
+
+ // Show the label and icon for each application package.
+ CharSequence[] applicationNames = new CharSequence[foundPackages];
+ mEntryDrawables = new Drawable[foundPackages];
+ int index = 0;
+ int selectedIndex = -1;
+ for (ApplicationInfo appInfo : appInfos) {
+ if (appInfo != null) {
+ applicationNames[index] = appInfo.loadLabel(pm);
+ mEntryDrawables[index] = appInfo.loadIcon(pm);
+ if (defaultPackageName != null &&
+ appInfo.packageName.contentEquals(defaultPackageName)) {
+ selectedIndex = index;
+ }
+ index++;
+ }
+ }
+ setEntries(applicationNames);
+ setEntryValues(packageNames);
+ if (selectedIndex != -1) {
+ setValueIndex(selectedIndex);
+ }
+ }
+
+ @Override
+ protected void onPrepareDialogBuilder(Builder builder) {
+ int selectedIndex = findIndexOfValue(getValue());
+ ListAdapter adapter = new AppArrayAdapter(getContext(),
+ R.layout.app_preference_item, getEntries(), mEntryDrawables, selectedIndex);
+ builder.setAdapter(adapter, this);
+ super.onPrepareDialogBuilder(builder);
+ }
+}
diff --git a/src/com/android/settings/BandMode.java b/src/com/android/settings/BandMode.java
index 0a0f77f..81e8b49 100644
--- a/src/com/android/settings/BandMode.java
+++ b/src/com/android/settings/BandMode.java
@@ -58,7 +58,7 @@ public class BandMode extends Activity {
super.onCreate(icicle);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
-
+
setContentView(R.layout.band_mode);
setTitle(getString(R.string.band_mode_title));
@@ -73,8 +73,6 @@ public class BandMode extends Activity {
mBandList.setAdapter(mBandListAdapter);
mBandList.setOnItemClickListener(mBandSelectionHandler);
-
-
loadBandList();
}
@@ -109,6 +107,7 @@ public class BandMode extends Activity {
}
public String toString() {
+ if (mBandMode >= BAND_NAMES.length) return "Band mode " + mBandMode;
return BAND_NAMES[mBandMode];
}
}
diff --git a/src/com/android/settings/BrightnessPreference.java b/src/com/android/settings/BrightnessPreference.java
index 6d3b81b..7d60e7f 100644
--- a/src/com/android/settings/BrightnessPreference.java
+++ b/src/com/android/settings/BrightnessPreference.java
@@ -30,7 +30,7 @@ public class BrightnessPreference extends Preference {
@Override
protected void onClick() {
- Intent intent = new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG);
- getContext().sendBroadcastAsUser(intent, UserHandle.CURRENT_OR_SELF);
+ getContext().startActivityAsUser(new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG),
+ UserHandle.CURRENT_OR_SELF);
}
}
diff --git a/src/com/android/settings/ButtonBarHandler.java b/src/com/android/settings/ButtonBarHandler.java
index d61da13..85e39d1 100644
--- a/src/com/android/settings/ButtonBarHandler.java
+++ b/src/com/android/settings/ButtonBarHandler.java
@@ -19,7 +19,7 @@ import android.widget.Button;
/**
* Interface letting {@link SettingsPreferenceFragment} access to bottom bar inside
- * {@link android.preference.PreferenceActivity}.
+ * {@link SettingsActivity}.
*/
public interface ButtonBarHandler {
public boolean hasNextButton();
diff --git a/src/com/android/settings/ChooseLockGeneric.java b/src/com/android/settings/ChooseLockGeneric.java
index 49de366..e3a9932 100644
--- a/src/com/android/settings/ChooseLockGeneric.java
+++ b/src/com/android/settings/ChooseLockGeneric.java
@@ -16,8 +16,9 @@
package com.android.settings;
+import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Activity;
-import android.app.Fragment;
+import android.app.ActivityManagerNative;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
@@ -25,32 +26,31 @@ import android.content.Intent;
import android.content.pm.UserInfo;
import android.os.Bundle;
import android.os.Process;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.preference.Preference;
-import android.preference.PreferenceActivity;
import android.preference.PreferenceScreen;
import android.security.KeyStore;
import android.util.EventLog;
+import android.util.MutableBoolean;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityManager;
import android.widget.ListView;
import com.android.internal.widget.LockPatternUtils;
-import com.android.settings.ConfirmLockPattern.ConfirmLockPatternFragment;
import java.util.List;
-import libcore.util.MutableBoolean;
-
-public class ChooseLockGeneric extends PreferenceActivity {
+public class ChooseLockGeneric extends SettingsActivity {
+ public static final String CONFIRM_CREDENTIALS = "confirm_credentials";
@Override
public Intent getIntent() {
Intent modIntent = new Intent(super.getIntent());
modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ChooseLockGenericFragment.class.getName());
- modIntent.putExtra(EXTRA_NO_HEADERS, true);
return modIntent;
}
@@ -74,11 +74,14 @@ public class ChooseLockGeneric extends PreferenceActivity {
private static final String KEY_UNLOCK_SET_PATTERN = "unlock_set_pattern";
private static final int CONFIRM_EXISTING_REQUEST = 100;
private static final int FALLBACK_REQUEST = 101;
+ private static final int ENABLE_ENCRYPTION_REQUEST = 102;
private static final String PASSWORD_CONFIRMED = "password_confirmed";
- private static final String CONFIRM_CREDENTIALS = "confirm_credentials";
+
private static final String WAITING_FOR_CONFIRMATION = "waiting_for_confirmation";
private static final String FINISH_PENDING = "finish_pending";
public static final String MINIMUM_QUALITY_KEY = "minimum_quality";
+ public static final String ENCRYPT_REQUESTED_QUALITY = "encrypt_requested_quality";
+ public static final String ENCRYPT_REQUESTED_DISABLED = "encrypt_requested_disabled";
private static final boolean ALWAY_SHOW_TUTORIAL = true;
@@ -88,6 +91,10 @@ public class ChooseLockGeneric extends PreferenceActivity {
private boolean mPasswordConfirmed = false;
private boolean mWaitingForConfirmation = false;
private boolean mFinishPending = false;
+ private int mEncryptionRequestQuality;
+ private boolean mEncryptionRequestDisabled;
+ private boolean mRequirePassword;
+ private LockPatternUtils mLockPatternUtils;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -96,6 +103,7 @@ public class ChooseLockGeneric extends PreferenceActivity {
mDPM = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
mKeyStore = KeyStore.getInstance();
mChooseLockSettingsHelper = new ChooseLockSettingsHelper(this.getActivity());
+ mLockPatternUtils = new LockPatternUtils(getActivity());
// Defaults to needing to confirm credentials
final boolean confirmCredentials = getActivity().getIntent()
@@ -108,6 +116,9 @@ public class ChooseLockGeneric extends PreferenceActivity {
mPasswordConfirmed = savedInstanceState.getBoolean(PASSWORD_CONFIRMED);
mWaitingForConfirmation = savedInstanceState.getBoolean(WAITING_FOR_CONFIRMATION);
mFinishPending = savedInstanceState.getBoolean(FINISH_PENDING);
+ mEncryptionRequestQuality = savedInstanceState.getInt(ENCRYPT_REQUESTED_QUALITY);
+ mEncryptionRequestDisabled = savedInstanceState.getBoolean(
+ ENCRYPT_REQUESTED_DISABLED);
}
if (mPasswordConfirmed) {
@@ -148,16 +159,16 @@ public class ChooseLockGeneric extends PreferenceActivity {
updateUnlockMethodAndFinish(
DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, false);
} else if (KEY_UNLOCK_SET_BIOMETRIC_WEAK.equals(key)) {
- updateUnlockMethodAndFinish(
+ maybeEnableEncryption(
DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK, false);
}else if (KEY_UNLOCK_SET_PATTERN.equals(key)) {
- updateUnlockMethodAndFinish(
+ maybeEnableEncryption(
DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, false);
} else if (KEY_UNLOCK_SET_PIN.equals(key)) {
- updateUnlockMethodAndFinish(
+ maybeEnableEncryption(
DevicePolicyManager.PASSWORD_QUALITY_NUMERIC, false);
} else if (KEY_UNLOCK_SET_PASSWORD.equals(key)) {
- updateUnlockMethodAndFinish(
+ maybeEnableEncryption(
DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, false);
} else {
handled = false;
@@ -165,6 +176,31 @@ public class ChooseLockGeneric extends PreferenceActivity {
return handled;
}
+ /**
+ * If the device has encryption already enabled, then ask the user if they
+ * also want to encrypt the phone with this password.
+ *
+ * @param quality
+ * @param disabled
+ */
+ private void maybeEnableEncryption(int quality, boolean disabled) {
+ if (Process.myUserHandle().isOwner() && LockPatternUtils.isDeviceEncryptionEnabled()) {
+ mEncryptionRequestQuality = quality;
+ mEncryptionRequestDisabled = disabled;
+ // If accessibility is enabled and the user hasn't seen this dialog before, set the
+ // default state to agree with that which is compatible with accessibility
+ // (password not required).
+ final boolean accEn = AccessibilityManager.getInstance(getActivity()).isEnabled();
+ final boolean required = mLockPatternUtils.isCredentialRequiredToDecrypt(!accEn);
+ Intent intent = EncryptionInterstitial.createStartIntent(
+ getActivity(), quality, required);
+ startActivityForResult(intent, ENABLE_ENCRYPTION_REQUEST);
+ } else {
+ mRequirePassword = false; // device encryption not enabled or not device owner.
+ updateUnlockMethodAndFinish(quality, disabled);
+ }
+ }
+
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -187,10 +223,15 @@ public class ChooseLockGeneric extends PreferenceActivity {
if (requestCode == CONFIRM_EXISTING_REQUEST && resultCode == Activity.RESULT_OK) {
mPasswordConfirmed = true;
updatePreferencesOrFinish();
- } else if(requestCode == FALLBACK_REQUEST) {
+ } else if (requestCode == FALLBACK_REQUEST) {
mChooseLockSettingsHelper.utils().deleteTempGallery();
getActivity().setResult(resultCode);
finish();
+ } else if (requestCode == ENABLE_ENCRYPTION_REQUEST
+ && resultCode == Activity.RESULT_OK) {
+ mRequirePassword = data.getBooleanExtra(
+ EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true);
+ updateUnlockMethodAndFinish(mEncryptionRequestQuality, mEncryptionRequestDisabled);
} else {
getActivity().setResult(Activity.RESULT_CANCELED);
finish();
@@ -204,6 +245,8 @@ public class ChooseLockGeneric extends PreferenceActivity {
outState.putBoolean(PASSWORD_CONFIRMED, mPasswordConfirmed);
outState.putBoolean(WAITING_FOR_CONFIRMATION, mWaitingForConfirmation);
outState.putBoolean(FINISH_PENDING, mFinishPending);
+ outState.putInt(ENCRYPT_REQUESTED_QUALITY, mEncryptionRequestQuality);
+ outState.putBoolean(ENCRYPT_REQUESTED_DISABLED, mEncryptionRequestDisabled);
}
private void updatePreferencesOrFinish() {
@@ -220,6 +263,7 @@ public class ChooseLockGeneric extends PreferenceActivity {
}
addPreferencesFromResource(R.xml.security_settings_picker);
disableUnusablePreferences(quality, allowBiometric);
+ updatePreferenceSummaryIfNeeded();
} else {
updateUnlockMethodAndFinish(quality, false);
}
@@ -229,20 +273,7 @@ public class ChooseLockGeneric extends PreferenceActivity {
private int upgradeQuality(int quality, MutableBoolean allowBiometric) {
quality = upgradeQualityForDPM(quality);
quality = upgradeQualityForKeyStore(quality);
- int encryptionQuality = upgradeQualityForEncryption(quality);
- if (encryptionQuality > quality) {
- //The first case checks whether biometric is allowed, prior to the user making
- //their selection from the list
- if (allowBiometric != null) {
- allowBiometric.value = quality <=
- DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK;
- } else if (quality == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK) {
- //When the user has selected biometric we shouldn't change that due to
- //encryption
- return quality;
- }
- }
- return encryptionQuality;
+ return quality;
}
private int upgradeQualityForDPM(int quality) {
@@ -254,27 +285,6 @@ public class ChooseLockGeneric extends PreferenceActivity {
return quality;
}
- /**
- * Mix in "encryption minimums" to any given quality value. This prevents users
- * from downgrading the pattern/pin/password to a level below the minimums.
- *
- * ASSUMPTION: Setting quality is sufficient (e.g. minimum lengths will be set
- * appropriately.)
- */
- private int upgradeQualityForEncryption(int quality) {
- // Don't upgrade quality for secondary users. Encryption requirements don't apply.
- if (!Process.myUserHandle().equals(UserHandle.OWNER)) return quality;
- int encryptionStatus = mDPM.getStorageEncryptionStatus();
- boolean encrypted = (encryptionStatus == DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE)
- || (encryptionStatus == DevicePolicyManager.ENCRYPTION_STATUS_ACTIVATING);
- if (encrypted) {
- if (quality < CryptKeeperSettings.MIN_PASSWORD_QUALITY) {
- quality = CryptKeeperSettings.MIN_PASSWORD_QUALITY;
- }
- }
- return quality;
- }
-
private int upgradeQualityForKeyStore(int quality) {
if (!mKeyStore.isEmpty()) {
if (quality < CredentialStorage.MIN_PASSWORD_QUALITY) {
@@ -319,7 +329,7 @@ public class ChooseLockGeneric extends PreferenceActivity {
} else if (KEY_UNLOCK_SET_PATTERN.equals(key)) {
enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
} else if (KEY_UNLOCK_SET_PIN.equals(key)) {
- enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+ enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
} else if (KEY_UNLOCK_SET_PASSWORD.equals(key)) {
enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
}
@@ -333,6 +343,32 @@ public class ChooseLockGeneric extends PreferenceActivity {
}
}
+ private void updatePreferenceSummaryIfNeeded() {
+ if (LockPatternUtils.isDeviceEncrypted()) {
+ return;
+ }
+
+ if (AccessibilityManager.getInstance(getActivity()).getEnabledAccessibilityServiceList(
+ AccessibilityServiceInfo.FEEDBACK_ALL_MASK).isEmpty()) {
+ return;
+ }
+
+ CharSequence summary = getString(R.string.secure_lock_encryption_warning);
+
+ PreferenceScreen screen = getPreferenceScreen();
+ final int preferenceCount = screen.getPreferenceCount();
+ for (int i = 0; i < preferenceCount; i++) {
+ Preference preference = screen.getPreference(i);
+ switch (preference.getKey()) {
+ case KEY_UNLOCK_SET_PATTERN:
+ case KEY_UNLOCK_SET_PIN:
+ case KEY_UNLOCK_SET_PASSWORD: {
+ preference.setSummary(summary);
+ } break;
+ }
+ }
+ }
+
/**
* Check whether the key is allowed for fallback (e.g. bio sensor). Returns true if it's
* supported as a backup.
@@ -389,13 +425,8 @@ public class ChooseLockGeneric extends PreferenceActivity {
minLength = MIN_PASSWORD_LENGTH;
}
final int maxLength = mDPM.getPasswordMaximumLength(quality);
- Intent intent = new Intent().setClass(getActivity(), ChooseLockPassword.class);
- intent.putExtra(LockPatternUtils.PASSWORD_TYPE_KEY, quality);
- intent.putExtra(ChooseLockPassword.PASSWORD_MIN_KEY, minLength);
- intent.putExtra(ChooseLockPassword.PASSWORD_MAX_KEY, maxLength);
- intent.putExtra(CONFIRM_CREDENTIALS, false);
- intent.putExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK,
- isFallback);
+ Intent intent = ChooseLockPassword.createIntent(getActivity(), quality, isFallback,
+ minLength, maxLength, mRequirePassword, false /* confirm credentials */);
if (isFallback) {
startActivityForResult(intent, FALLBACK_REQUEST);
return;
@@ -405,11 +436,8 @@ public class ChooseLockGeneric extends PreferenceActivity {
startActivity(intent);
}
} else if (quality == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING) {
- Intent intent = new Intent(getActivity(), ChooseLockPattern.class);
- intent.putExtra("key_lock_method", "pattern");
- intent.putExtra(CONFIRM_CREDENTIALS, false);
- intent.putExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK,
- isFallback);
+ Intent intent = ChooseLockPattern.createIntent(getActivity(),
+ isFallback, mRequirePassword, false /* confirm credentials */);
if (isFallback) {
startActivityForResult(intent, FALLBACK_REQUEST);
return;
diff --git a/src/com/android/settings/ChooseLockPassword.java b/src/com/android/settings/ChooseLockPassword.java
index f43738f..b72d5c5 100644
--- a/src/com/android/settings/ChooseLockPassword.java
+++ b/src/com/android/settings/ChooseLockPassword.java
@@ -19,17 +19,19 @@ package com.android.settings;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.PasswordEntryKeyboardHelper;
import com.android.internal.widget.PasswordEntryKeyboardView;
-import com.android.settings.ChooseLockGeneric.ChooseLockGenericFragment;
+import com.android.settings.notification.RedactionInterstitial;
import android.app.Activity;
import android.app.Fragment;
import android.app.admin.DevicePolicyManager;
+import android.content.ContentResolver;
+import android.content.Context;
import android.content.Intent;
import android.inputmethodservice.KeyboardView;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
-import android.preference.PreferenceActivity;
+import android.provider.Settings;
import android.text.Editable;
import android.text.InputType;
import android.text.Selection;
@@ -41,13 +43,12 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
-import android.view.accessibility.AccessibilityEvent;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
-public class ChooseLockPassword extends PreferenceActivity {
+public class ChooseLockPassword extends SettingsActivity {
public static final String PASSWORD_MIN_KEY = "lockscreen.password_min";
public static final String PASSWORD_MAX_KEY = "lockscreen.password_max";
public static final String PASSWORD_MIN_LETTERS_KEY = "lockscreen.password_min_letters";
@@ -61,10 +62,22 @@ public class ChooseLockPassword extends PreferenceActivity {
public Intent getIntent() {
Intent modIntent = new Intent(super.getIntent());
modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ChooseLockPasswordFragment.class.getName());
- modIntent.putExtra(EXTRA_NO_HEADERS, true);
return modIntent;
}
+ public static Intent createIntent(Context context, int quality, final boolean isFallback,
+ int minLength, final int maxLength, boolean requirePasswordToDecrypt,
+ boolean confirmCredentials) {
+ Intent intent = new Intent().setClass(context, ChooseLockPassword.class);
+ intent.putExtra(LockPatternUtils.PASSWORD_TYPE_KEY, quality);
+ intent.putExtra(PASSWORD_MIN_KEY, minLength);
+ intent.putExtra(PASSWORD_MAX_KEY, maxLength);
+ intent.putExtra(ChooseLockGeneric.CONFIRM_CREDENTIALS, confirmCredentials);
+ intent.putExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, isFallback);
+ intent.putExtra(EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, requirePasswordToDecrypt);
+ return intent;
+ }
+
@Override
protected boolean isValidFragment(String fragmentName) {
if (ChooseLockPasswordFragment.class.getName().equals(fragmentName)) return true;
@@ -79,7 +92,7 @@ public class ChooseLockPassword extends PreferenceActivity {
//WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
super.onCreate(savedInstanceState);
CharSequence msg = getText(R.string.lockpassword_choose_your_password_header);
- showBreadCrumbs(msg, msg);
+ setTitle(msg);
}
public static class ChooseLockPasswordFragment extends Fragment
@@ -99,6 +112,7 @@ public class ChooseLockPassword extends PreferenceActivity {
private int mRequestedQuality = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
private ChooseLockSettingsHelper mChooseLockSettingsHelper;
private Stage mUiStage = Stage.Introduction;
+ private boolean mDone = false;
private TextView mHeaderText;
private String mFirstPin;
private KeyboardView mKeyboardView;
@@ -137,9 +151,6 @@ public class ChooseLockPassword extends PreferenceActivity {
R.string.lockpassword_confirm_pins_dont_match,
R.string.lockpassword_continue_label);
- /**
- * @param headerMessage The message displayed at the top.
- */
Stage(int hintInAlpha, int hintInNumeric, int nextButtonText) {
this.alphaHint = hintInAlpha;
this.numericHint = hintInNumeric;
@@ -235,13 +246,13 @@ public class ChooseLockPassword extends PreferenceActivity {
updateStage(mUiStage);
}
}
- // Update the breadcrumb (title) if this is embedded in a PreferenceActivity
- if (activity instanceof PreferenceActivity) {
- final PreferenceActivity preferenceActivity = (PreferenceActivity) activity;
+ mDone = false;
+ if (activity instanceof SettingsActivity) {
+ final SettingsActivity sa = (SettingsActivity) activity;
int id = mIsAlphaMode ? R.string.lockpassword_choose_your_password_header
: R.string.lockpassword_choose_your_pin_header;
CharSequence title = getText(id);
- preferenceActivity.showBreadCrumbs(title, title);
+ sa.setTitle(title);
}
return view;
@@ -337,10 +348,18 @@ public class ChooseLockPassword extends PreferenceActivity {
}
}
if (DevicePolicyManager.PASSWORD_QUALITY_NUMERIC == mRequestedQuality
- && (letters > 0 || symbols > 0)) {
- // This shouldn't be possible unless user finds some way to bring up
- // soft keyboard
- return getString(R.string.lockpassword_pin_contains_non_digits);
+ || DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX == mRequestedQuality) {
+ if (letters > 0 || symbols > 0) {
+ // This shouldn't be possible unless user finds some way to bring up
+ // soft keyboard
+ return getString(R.string.lockpassword_pin_contains_non_digits);
+ }
+ // Check for repeated characters or sequences (e.g. '1234', '0000', '2468')
+ final int sequence = LockPatternUtils.maxLengthSequence(password);
+ if (DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX == mRequestedQuality
+ && sequence > LockPatternUtils.MAX_ALLOWED_SEQUENCE) {
+ return getString(R.string.lockpassword_pin_no_sequential_digits);
+ }
} else if (DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == mRequestedQuality) {
if (letters < mPasswordMinLetters) {
return String.format(getResources().getQuantityString(
@@ -383,10 +402,13 @@ public class ChooseLockPassword extends PreferenceActivity {
return getString(mIsAlphaMode ? R.string.lockpassword_password_recently_used
: R.string.lockpassword_pin_recently_used);
}
+
return null;
}
private void handleNext() {
+ if (mDone) return;
+
final String pin = mPasswordEntry.getText().toString();
if (TextUtils.isEmpty(pin)) {
return;
@@ -404,9 +426,14 @@ public class ChooseLockPassword extends PreferenceActivity {
final boolean isFallback = getActivity().getIntent().getBooleanExtra(
LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, false);
mLockPatternUtils.clearLock(isFallback);
+ final boolean required = getActivity().getIntent().getBooleanExtra(
+ EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true);
+ mLockPatternUtils.setCredentialRequiredToDecrypt(required);
mLockPatternUtils.saveLockPassword(pin, mRequestedQuality, isFallback);
getActivity().setResult(RESULT_FINISHED);
getActivity().finish();
+ mDone = true;
+ startActivity(RedactionInterstitial.createStartIntent(getActivity()));
} else {
CharSequence tmp = mPasswordEntry.getText();
if (tmp != null) {
diff --git a/src/com/android/settings/ChooseLockPattern.java b/src/com/android/settings/ChooseLockPattern.java
index 328312c..3d3ef16 100644
--- a/src/com/android/settings/ChooseLockPattern.java
+++ b/src/com/android/settings/ChooseLockPattern.java
@@ -17,20 +17,21 @@
package com.android.settings;
import com.google.android.collect.Lists;
-
import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternView;
import com.android.internal.widget.LockPatternView.Cell;
-import com.android.settings.ChooseLockGeneric.ChooseLockGenericFragment;
+import com.android.settings.notification.RedactionInterstitial;
import static com.android.internal.widget.LockPatternView.DisplayMode;
import android.app.Activity;
import android.app.Fragment;
+import android.content.ContentResolver;
+import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
-import android.preference.PreferenceActivity;
+import android.provider.Settings;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
@@ -49,7 +50,7 @@ import java.util.List;
* - asks for confirmation / restart
* - saves chosen password when confirmed
*/
-public class ChooseLockPattern extends PreferenceActivity {
+public class ChooseLockPattern extends SettingsActivity {
/**
* Used by the choose lock pattern wizard to indicate the wizard is
* finished, and each activity in the wizard should finish.
@@ -65,10 +66,19 @@ public class ChooseLockPattern extends PreferenceActivity {
public Intent getIntent() {
Intent modIntent = new Intent(super.getIntent());
modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ChooseLockPatternFragment.class.getName());
- modIntent.putExtra(EXTRA_NO_HEADERS, true);
return modIntent;
}
+ public static Intent createIntent(Context context, final boolean isFallback,
+ boolean requirePassword, boolean confirmCredentials) {
+ Intent intent = new Intent(context, ChooseLockPattern.class);
+ intent.putExtra("key_lock_method", "pattern");
+ intent.putExtra(ChooseLockGeneric.CONFIRM_CREDENTIALS, confirmCredentials);
+ intent.putExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, isFallback);
+ intent.putExtra(EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, requirePassword);
+ return intent;
+ }
+
@Override
protected boolean isValidFragment(String fragmentName) {
if (ChooseLockPatternFragment.class.getName().equals(fragmentName)) return true;
@@ -80,7 +90,7 @@ public class ChooseLockPattern extends PreferenceActivity {
// requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
CharSequence msg = getText(R.string.lockpassword_choose_your_pattern_header);
- showBreadCrumbs(msg, msg);
+ setTitle(msg);
}
@Override
@@ -292,6 +302,7 @@ public class ChooseLockPattern extends PreferenceActivity {
}
private Stage mUiStage = Stage.Introduction;
+ private boolean mDone = false;
private Runnable mClearPatternRunnable = new Runnable() {
public void run() {
@@ -365,6 +376,7 @@ public class ChooseLockPattern extends PreferenceActivity {
}
updateStage(Stage.values()[savedInstanceState.getInt(KEY_UI_STAGE)]);
}
+ mDone = false;
return view;
}
@@ -521,11 +533,16 @@ public class ChooseLockPattern extends PreferenceActivity {
}
private void saveChosenPatternAndFinish() {
+ if (mDone) return;
LockPatternUtils utils = mChooseLockSettingsHelper.utils();
final boolean lockVirgin = !utils.isPatternEverChosen();
final boolean isFallback = getActivity().getIntent()
.getBooleanExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, false);
+
+ final boolean required = getActivity().getIntent().getBooleanExtra(
+ EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true);
+ utils.setCredentialRequiredToDecrypt(required);
utils.saveLockPattern(mChosenPattern, isFallback);
utils.setLockPatternEnabled(true);
@@ -535,6 +552,8 @@ public class ChooseLockPattern extends PreferenceActivity {
getActivity().setResult(RESULT_FINISHED);
getActivity().finish();
+ mDone = true;
+ startActivity(RedactionInterstitial.createStartIntent(getActivity()));
}
}
}
diff --git a/src/com/android/settings/ChooseLockSettingsHelper.java b/src/com/android/settings/ChooseLockSettingsHelper.java
index a069712..3086a7a 100644
--- a/src/com/android/settings/ChooseLockSettingsHelper.java
+++ b/src/com/android/settings/ChooseLockSettingsHelper.java
@@ -25,6 +25,7 @@ import android.content.Intent;
public final class ChooseLockSettingsHelper {
+ static final String EXTRA_KEY_TYPE = "type";
static final String EXTRA_KEY_PASSWORD = "password";
private LockPatternUtils mLockPatternUtils;
@@ -53,17 +54,32 @@ public final class ChooseLockSettingsHelper {
* @see #onActivityResult(int, int, android.content.Intent)
*/
boolean launchConfirmationActivity(int request, CharSequence message, CharSequence details) {
+ return launchConfirmationActivity(request, message, details, false);
+ }
+
+ /**
+ * If a pattern, password or PIN exists, prompt the user before allowing them to change it.
+ * @param message optional message to display about the action about to be done
+ * @param details optional detail message to display
+ * @param returnCredentials if true, put credentials into intent. Note that if this is true,
+ this can only be called internally.
+ * @return true if one exists and we launched an activity to confirm it
+ * @see #onActivityResult(int, int, android.content.Intent)
+ */
+ boolean launchConfirmationActivity(int request, CharSequence message, CharSequence details,
+ boolean returnCredentials) {
boolean launched = false;
switch (mLockPatternUtils.getKeyguardStoredPasswordQuality()) {
case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
- launched = confirmPattern(request, message, details);
+ launched = confirmPattern(request, message, details, returnCredentials);
break;
case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
+ case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX:
case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
// TODO: update UI layout for ConfirmPassword to show message and details
- launched = confirmPassword(request);
+ launched = confirmPassword(request, message, returnCredentials);
break;
}
return launched;
@@ -73,10 +89,12 @@ public final class ChooseLockSettingsHelper {
* Launch screen to confirm the existing lock pattern.
* @param message shown in header of ConfirmLockPattern if not null
* @param details shown in footer of ConfirmLockPattern if not null
+ * @param returnCredentials if true, put credentials into intent.
* @see #onActivityResult(int, int, android.content.Intent)
* @return true if we launched an activity to confirm pattern
*/
- private boolean confirmPattern(int request, CharSequence message, CharSequence details) {
+ private boolean confirmPattern(int request, CharSequence message,
+ CharSequence details, boolean returnCredentials) {
if (!mLockPatternUtils.isLockPatternEnabled() || !mLockPatternUtils.savedPatternExists()) {
return false;
}
@@ -84,7 +102,10 @@ public final class ChooseLockSettingsHelper {
// supply header and footer text in the intent
intent.putExtra(ConfirmLockPattern.HEADER_TEXT, message);
intent.putExtra(ConfirmLockPattern.FOOTER_TEXT, details);
- intent.setClassName("com.android.settings", "com.android.settings.ConfirmLockPattern");
+ intent.setClassName("com.android.settings",
+ returnCredentials
+ ? ConfirmLockPattern.InternalActivity.class.getName()
+ : ConfirmLockPattern.class.getName());
if (mFragment != null) {
mFragment.startActivityForResult(intent, request);
} else {
@@ -95,13 +116,21 @@ public final class ChooseLockSettingsHelper {
/**
* Launch screen to confirm the existing lock password.
+ * @param message shown in header of ConfirmLockPassword if not null
+ * @param returnCredentials if true, put credentials into intent.
* @see #onActivityResult(int, int, android.content.Intent)
* @return true if we launched an activity to confirm password
*/
- private boolean confirmPassword(int request) {
+ private boolean confirmPassword(int request, CharSequence message,
+ boolean returnCredentials) {
if (!mLockPatternUtils.isLockPasswordEnabled()) return false;
final Intent intent = new Intent();
- intent.setClassName("com.android.settings", "com.android.settings.ConfirmLockPassword");
+ // supply header text in the intent
+ intent.putExtra(ConfirmLockPattern.HEADER_TEXT, message);
+ intent.setClassName("com.android.settings",
+ returnCredentials
+ ? ConfirmLockPassword.InternalActivity.class.getName()
+ : ConfirmLockPassword.class.getName());
if (mFragment != null) {
mFragment.startActivityForResult(intent, request);
} else {
diff --git a/src/com/android/settings/ConfirmDeviceCredentialActivity.java b/src/com/android/settings/ConfirmDeviceCredentialActivity.java
new file mode 100644
index 0000000..beb2d97
--- /dev/null
+++ b/src/com/android/settings/ConfirmDeviceCredentialActivity.java
@@ -0,0 +1,65 @@
+
+/*
+ * 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.app.Activity;
+import android.app.KeyguardManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * Launch this when you want to confirm the user is present by asking them to enter their
+ * PIN/password/pattern.
+ */
+public class ConfirmDeviceCredentialActivity extends Activity {
+ public static final String TAG = ConfirmDeviceCredentialActivity.class.getSimpleName();
+
+ public static Intent createIntent(CharSequence title, CharSequence details) {
+ Intent intent = new Intent();
+ intent.setClassName("com.android.settings",
+ ConfirmDeviceCredentialActivity.class.getName());
+ intent.putExtra(KeyguardManager.EXTRA_TITLE, title);
+ intent.putExtra(KeyguardManager.EXTRA_DESCRIPTION, details);
+ return intent;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Intent intent = getIntent();
+ String title = intent.getStringExtra(KeyguardManager.EXTRA_TITLE);
+ String details = intent.getStringExtra(KeyguardManager.EXTRA_DESCRIPTION);
+
+ ChooseLockSettingsHelper helper = new ChooseLockSettingsHelper(this);
+ if (!helper.launchConfirmationActivity(0 /* request code */, title, details)) {
+ Log.d(TAG, "No pattern, password or PIN set.");
+ setResult(Activity.RESULT_OK);
+ finish();
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ boolean credentialsConfirmed = (resultCode == Activity.RESULT_OK);
+ Log.d(TAG, "Device credentials confirmed: " + credentialsConfirmed);
+ setResult(credentialsConfirmed ? Activity.RESULT_OK : Activity.RESULT_CANCELED);
+ finish();
+ }
+}
diff --git a/src/com/android/settings/ConfirmLockPassword.java b/src/com/android/settings/ConfirmLockPassword.java
index 38d4a89..c74e861 100644
--- a/src/com/android/settings/ConfirmLockPassword.java
+++ b/src/com/android/settings/ConfirmLockPassword.java
@@ -16,10 +16,10 @@
package com.android.settings;
+import android.text.TextUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.PasswordEntryKeyboardHelper;
import com.android.internal.widget.PasswordEntryKeyboardView;
-import com.android.settings.ChooseLockGeneric.ChooseLockGenericFragment;
import android.app.Activity;
import android.app.Fragment;
@@ -28,8 +28,8 @@ import android.content.Intent;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.os.Handler;
-import android.preference.PreferenceActivity;
import android.os.SystemClock;
+import android.os.storage.StorageManager;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
@@ -38,19 +38,23 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
-public class ConfirmLockPassword extends PreferenceActivity {
+public class ConfirmLockPassword extends SettingsActivity {
+
+ public static final String PACKAGE = "com.android.settings";
+ public static final String HEADER_TEXT = PACKAGE + ".ConfirmLockPattern.header";
+
+ public static class InternalActivity extends ConfirmLockPassword {
+ }
@Override
public Intent getIntent() {
Intent modIntent = new Intent(super.getIntent());
modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ConfirmLockPasswordFragment.class.getName());
- modIntent.putExtra(EXTRA_NO_HEADERS, true);
return modIntent;
}
@@ -67,11 +71,13 @@ public class ConfirmLockPassword extends PreferenceActivity {
//WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
super.onCreate(savedInstanceState);
CharSequence msg = getText(R.string.lockpassword_confirm_your_password_header);
- showBreadCrumbs(msg, msg);
+ setTitle(msg);
}
public static class ConfirmLockPasswordFragment extends Fragment implements OnClickListener,
OnEditorActionListener, TextWatcher {
+ private static final String KEY_NUM_WRONG_CONFIRM_ATTEMPTS
+ = "confirm_lock_password_fragment.key_num_wrong_confirm_attempts";
private static final long ERROR_MESSAGE_TIMEOUT = 3000;
private TextView mPasswordEntry;
private LockPatternUtils mLockPatternUtils;
@@ -93,6 +99,10 @@ public class ConfirmLockPassword extends PreferenceActivity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mLockPatternUtils = new LockPatternUtils(getActivity());
+ if (savedInstanceState != null) {
+ mNumWrongConfirmAttempts = savedInstanceState.getInt(
+ KEY_NUM_WRONG_CONFIRM_ATTEMPTS, 0);
+ }
}
@Override
@@ -116,7 +126,15 @@ public class ConfirmLockPassword extends PreferenceActivity {
mIsAlpha = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == storedQuality
|| DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == storedQuality
|| DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == storedQuality;
- mHeaderText.setText(getDefaultHeader());
+
+ Intent intent = getActivity().getIntent();
+ if (intent != null) {
+ CharSequence headerMessage = intent.getCharSequenceExtra(HEADER_TEXT);
+ if (TextUtils.isEmpty(headerMessage)) {
+ headerMessage = getString(getDefaultHeader());
+ }
+ mHeaderText.setText(headerMessage);
+ }
final Activity activity = getActivity();
mKeyboardHelper = new PasswordEntryKeyboardHelper(activity,
@@ -130,12 +148,11 @@ public class ConfirmLockPassword extends PreferenceActivity {
mPasswordEntry.setInputType(mIsAlpha ? currentType
: (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD));
- // Update the breadcrumb (title) if this is embedded in a PreferenceActivity
- if (activity instanceof PreferenceActivity) {
- final PreferenceActivity preferenceActivity = (PreferenceActivity) activity;
+ if (activity instanceof SettingsActivity) {
+ final SettingsActivity sa = (SettingsActivity) activity;
int id = getDefaultHeader();
CharSequence title = getText(id);
- preferenceActivity.showBreadCrumbs(title, title);
+ sa.setTitle(title);
}
return view;
@@ -167,12 +184,23 @@ public class ConfirmLockPassword extends PreferenceActivity {
}
}
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(KEY_NUM_WRONG_CONFIRM_ATTEMPTS, mNumWrongConfirmAttempts);
+ }
+
private void handleNext() {
final String pin = mPasswordEntry.getText().toString();
if (mLockPatternUtils.checkPassword(pin)) {
Intent intent = new Intent();
- intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, pin);
+ if (getActivity() instanceof ConfirmLockPassword.InternalActivity) {
+ intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_TYPE,
+ mIsAlpha ? StorageManager.CRYPT_TYPE_PASSWORD
+ : StorageManager.CRYPT_TYPE_PIN);
+ intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, pin);
+ }
getActivity().setResult(RESULT_OK, intent);
getActivity().finish();
diff --git a/src/com/android/settings/ConfirmLockPattern.java b/src/com/android/settings/ConfirmLockPattern.java
index 3a1f06c..caf691d 100644
--- a/src/com/android/settings/ConfirmLockPattern.java
+++ b/src/com/android/settings/ConfirmLockPattern.java
@@ -27,7 +27,7 @@ import android.content.Intent;
import android.os.CountDownTimer;
import android.os.SystemClock;
import android.os.Bundle;
-import android.preference.PreferenceActivity;
+import android.os.storage.StorageManager;
import android.widget.TextView;
import android.view.LayoutInflater;
import android.view.View;
@@ -41,7 +41,10 @@ import java.util.List;
* Sets an activity result of {@link Activity#RESULT_OK} when the user
* successfully confirmed their pattern.
*/
-public class ConfirmLockPattern extends PreferenceActivity {
+public class ConfirmLockPattern extends SettingsActivity {
+
+ public static class InternalActivity extends ConfirmLockPattern {
+ }
/**
* Names of {@link CharSequence} fields within the originating {@link Intent}
@@ -65,14 +68,13 @@ public class ConfirmLockPattern extends PreferenceActivity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
CharSequence msg = getText(R.string.lockpassword_confirm_your_pattern_header);
- showBreadCrumbs(msg, msg);
+ setTitle(msg);
}
@Override
public Intent getIntent() {
Intent modIntent = new Intent(super.getIntent());
modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ConfirmLockPatternFragment.class.getName());
- modIntent.putExtra(EXTRA_NO_HEADERS, true);
return modIntent;
}
@@ -267,8 +269,12 @@ public class ConfirmLockPattern extends PreferenceActivity {
if (mLockPatternUtils.checkPattern(pattern)) {
Intent intent = new Intent();
- intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD,
- LockPatternUtils.patternToString(pattern));
+ if (getActivity() instanceof ConfirmLockPattern.InternalActivity) {
+ intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_TYPE,
+ StorageManager.CRYPT_TYPE_PATTERN);
+ intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD,
+ LockPatternUtils.patternToString(pattern));
+ }
getActivity().setResult(Activity.RESULT_OK, intent);
getActivity().finish();
diff --git a/src/com/android/settings/CreateShortcut.java b/src/com/android/settings/CreateShortcut.java
index f71df1d..0bf265f 100644
--- a/src/com/android/settings/CreateShortcut.java
+++ b/src/com/android/settings/CreateShortcut.java
@@ -19,7 +19,6 @@ package com.android.settings;
import android.app.LauncherActivity;
import android.content.Intent;
import android.content.pm.ResolveInfo;
-import android.os.Bundle;
import android.view.View;
import android.widget.ListView;
diff --git a/src/com/android/settings/CredentialStorage.java b/src/com/android/settings/CredentialStorage.java
index fcf208a..57b5384 100644
--- a/src/com/android/settings/CredentialStorage.java
+++ b/src/com/android/settings/CredentialStorage.java
@@ -19,6 +19,7 @@ package com.android.settings;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.admin.DevicePolicyManager;
+import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
@@ -26,6 +27,7 @@ import android.os.AsyncTask;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.Process;
+import android.os.UserManager;
import android.security.Credentials;
import android.security.KeyChain.KeyChainConnection;
import android.security.KeyChain;
@@ -38,8 +40,8 @@ import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
-import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternUtils;
import com.android.org.bouncycastle.asn1.ASN1InputStream;
import com.android.org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
@@ -119,16 +121,20 @@ public final class CredentialStorage extends Activity {
Intent intent = getIntent();
String action = intent.getAction();
-
- if (ACTION_RESET.equals(action)) {
- new ResetDialog();
- } else {
- if (ACTION_INSTALL.equals(action)
- && "com.android.certinstaller".equals(getCallingPackage())) {
- mInstallBundle = intent.getExtras();
+ UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
+ if (!userManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_CREDENTIALS)) {
+ if (ACTION_RESET.equals(action)) {
+ new ResetDialog();
+ } else {
+ if (ACTION_INSTALL.equals(action)
+ && "com.android.certinstaller".equals(getCallingPackage())) {
+ mInstallBundle = intent.getExtras();
+ }
+ // ACTION_UNLOCK also handled here in addition to ACTION_INSTALL
+ handleUnlockOrInstall();
}
- // ACTION_UNLOCK also handled here in addition to ACTION_INSTALL
- handleUnlockOrInstall();
+ } else {
+ finish();
}
}
@@ -270,7 +276,6 @@ public final class CredentialStorage extends Activity {
private ResetDialog() {
AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this)
.setTitle(android.R.string.dialog_alert_title)
- .setIconAttribute(android.R.attr.alertDialogIcon)
.setMessage(R.string.credentials_reset_hint)
.setPositiveButton(android.R.string.ok, this)
.setNegativeButton(android.R.string.cancel, this)
@@ -340,7 +345,6 @@ public final class CredentialStorage extends Activity {
private ConfigureKeyGuardDialog() {
AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this)
.setTitle(android.R.string.dialog_alert_title)
- .setIconAttribute(android.R.attr.alertDialogIcon)
.setMessage(R.string.credentials_configure_lock_screen_hint)
.setPositiveButton(android.R.string.ok, this)
.setNegativeButton(android.R.string.cancel, this)
@@ -374,7 +378,8 @@ public final class CredentialStorage extends Activity {
boolean launched = new ChooseLockSettingsHelper(this)
.launchConfirmationActivity(CONFIRM_KEY_GUARD_REQUEST,
res.getText(R.string.credentials_install_gesture_prompt),
- res.getText(R.string.credentials_install_gesture_explanation));
+ res.getText(R.string.credentials_install_gesture_explanation),
+ true);
return launched;
}
diff --git a/src/com/android/settings/CryptKeeper.java b/src/com/android/settings/CryptKeeper.java
index 23ec70e..2242fad 100644
--- a/src/com/android/settings/CryptKeeper.java
+++ b/src/com/android/settings/CryptKeeper.java
@@ -21,7 +21,9 @@ import android.app.StatusBarManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
+import android.content.res.Resources.NotFoundException;
import android.media.AudioManager;
import android.os.AsyncTask;
import android.os.Bundle;
@@ -34,11 +36,14 @@ import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.storage.IMountService;
+import android.os.storage.StorageManager;
import android.provider.Settings;
+import android.telecom.TelecomManager;
import android.telephony.TelephonyManager;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
+import android.text.format.DateUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -57,9 +62,13 @@ import android.widget.ProgressBar;
import android.widget.TextView;
import com.android.internal.statusbar.StatusBarIcon;
-import com.android.internal.telephony.ITelephony;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternView;
+import com.android.internal.widget.LockPatternView.Cell;
+
+import static com.android.internal.widget.LockPatternView.DisplayMode;
import java.util.List;
@@ -108,13 +117,42 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
private boolean mValidationRequested;
/** A flag to indicate that the volume is in a bad state (e.g. partially encrypted). */
private boolean mEncryptionGoneBad;
+ /** If gone bad, should we show encryption failed (false) or corrupt (true)*/
+ private boolean mCorrupt;
/** A flag to indicate when the back event should be ignored */
private boolean mIgnoreBack = false;
private int mCooldown;
PowerManager.WakeLock mWakeLock;
private EditText mPasswordEntry;
+ private LockPatternView mLockPatternView;
/** 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 */
+ private int mReleaseWakeLockCountdown = 0;
+ private int mStatusString = R.string.enter_password;
+
+ // how long we wait to clear a wrong pattern
+ private static final int WRONG_PATTERN_CLEAR_TIMEOUT_MS = 1500;
+
+ // how long we wait to clear a right pattern
+ private static final int RIGHT_PATTERN_CLEAR_TIMEOUT_MS = 500;
+
+ // When the user enters a short pin/password, run this to show an error,
+ // but don't count it against attempts.
+ private final Runnable mFakeUnlockAttemptRunnable = new Runnable() {
+ public void run() {
+ handleBadAttempt(1 /* failedAttempt */);
+ }
+ };
+
+ // TODO: this should be tuned to match minimum decryption timeout
+ private static final int FAKE_ATTEMPT_DELAY = 1000;
+
+ private Runnable mClearPatternRunnable = new Runnable() {
+ public void run() {
+ mLockPatternView.clearPattern();
+ }
+ };
/**
* Used to propagate state through configuration changes (e.g. screen rotation)
@@ -127,23 +165,14 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
}
}
- /**
- * Activity used to fade the screen to black after the password is entered.
- */
- public static class FadeToBlack extends Activity {
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.crypt_keeper_blank);
- }
- /** Ignore all back events. */
- @Override
- public void onBackPressed() {
- return;
+ private class DecryptTask extends AsyncTask<String, Void, Integer> {
+ private void hide(int id) {
+ View view = findViewById(id);
+ if (view != null) {
+ view.setVisibility(View.GONE);
+ }
}
- }
- private class DecryptTask extends AsyncTask<String, Void, Integer> {
@Override
protected Integer doInBackground(String... params) {
final IMountService service = getMountService();
@@ -158,35 +187,84 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
@Override
protected void onPostExecute(Integer failedAttempts) {
if (failedAttempts == 0) {
- // The password was entered successfully. Start the Blank activity
- // so this activity animates to black before the devices starts. Note
- // It has 1 second to complete the animation or it will be frozen
- // until the boot animation comes back up.
- Intent intent = new Intent(CryptKeeper.this, FadeToBlack.class);
- finish();
- startActivity(intent);
+ // The password was entered successfully. Simply do nothing
+ // and wait for the service restart to switch to surfacefligner
+ if (mLockPatternView != null) {
+ mLockPatternView.removeCallbacks(mClearPatternRunnable);
+ mLockPatternView.postDelayed(mClearPatternRunnable, RIGHT_PATTERN_CLEAR_TIMEOUT_MS);
+ }
+ hide(R.id.passwordEntry);
+ hide(R.id.switch_ime_button);
+ hide(R.id.lockPattern);
+ hide(R.id.status);
+ hide(R.id.owner_info);
+ hide(R.id.emergencyCallButton);
} else if (failedAttempts == MAX_FAILED_ATTEMPTS) {
// Factory reset the device.
- sendBroadcast(new Intent("android.intent.action.MASTER_CLEAR"));
- } else if ((failedAttempts % COOL_DOWN_ATTEMPTS) == 0) {
- mCooldown = COOL_DOWN_INTERVAL;
- cooldown();
+ 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);
+ return;
+ } else {
+ handleBadAttempt(failedAttempts);
+ }
+ }
+ }
+
+ private void handleBadAttempt(Integer failedAttempts) {
+ // Wrong entry. Handle pattern case.
+ if (mLockPatternView != null) {
+ mLockPatternView.setDisplayMode(DisplayMode.Wrong);
+ mLockPatternView.removeCallbacks(mClearPatternRunnable);
+ mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS);
+ }
+ if ((failedAttempts % COOL_DOWN_ATTEMPTS) == 0) {
+ mCooldown = COOL_DOWN_INTERVAL;
+ 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);
} else {
- final TextView status = (TextView) findViewById(R.id.status);
status.setText(R.string.try_again);
- // Reenable the password entry
+ }
+
+ if (mLockPatternView != null) {
+ mLockPatternView.setDisplayMode(DisplayMode.Wrong);
+ }
+ // Reenable the password entry
+ if (mPasswordEntry != null) {
mPasswordEntry.setEnabled(true);
+ final InputMethodManager imm = (InputMethodManager) getSystemService(
+ Context.INPUT_METHOD_SERVICE);
+ imm.showSoftInput(mPasswordEntry, 0);
+ setBackFunctionality(true);
+ }
+ if (mLockPatternView != null) {
+ mLockPatternView.setEnabled(true);
}
}
}
private class ValidationTask extends AsyncTask<Void, Void, Boolean> {
+ int state;
+
@Override
protected Boolean doInBackground(Void... params) {
final IMountService service = getMountService();
try {
Log.d(TAG, "Validating encryption state.");
- int state = service.getEncryptionState();
+ state = service.getEncryptionState();
if (state == IMountService.ENCRYPTION_STATE_NONE) {
Log.w(TAG, "Unexpectedly in CryptKeeper even though there is no encryption.");
return true; // Unexpected, but fine, I guess...
@@ -204,6 +282,7 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
if (Boolean.FALSE.equals(result)) {
Log.w(TAG, "Incomplete, or corrupted encryption detected. Prompting user to wipe.");
mEncryptionGoneBad = true;
+ mCorrupt = state == IMountService.ENCRYPTION_STATE_ERROR_CORRUPT;
} else {
Log.d(TAG, "Encryption state validated. Proceeding to configure UI");
}
@@ -243,6 +322,8 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
| StatusBarManager.DISABLE_SEARCH
| StatusBarManager.DISABLE_RECENT;
+ protected static final int MIN_LENGTH_BEFORE_REPORT = LockPatternUtils.MIN_LOCK_PATTERN_SIZE;
+
/** @return whether or not this Activity was started for debugging the UI only. */
private boolean isDebugView() {
return getIntent().hasExtra(EXTRA_FORCE_VIEW);
@@ -274,6 +355,14 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
// Notify the user again in 5 seconds.
mHandler.removeMessages(MESSAGE_NOTIFY);
mHandler.sendEmptyMessageDelayed(MESSAGE_NOTIFY, 5 * 1000);
+
+ if (mWakeLock.isHeld()) {
+ if (mReleaseWakeLockCountdown > 0) {
+ --mReleaseWakeLockCountdown;
+ } else {
+ mWakeLock.release();
+ }
+ }
}
/**
@@ -311,6 +400,13 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
return;
}
+ try {
+ if (getResources().getBoolean(R.bool.crypt_keeper_allow_rotation)) {
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+ }
+ } catch (NotFoundException e) {
+ }
+
// Disable the status bar, but do NOT disable back because the user needs a way to go
// from keyboard settings and back to the password screen.
mStatusBar = (StatusBarManager) getSystemService(Context.STATUS_BAR_SERVICE);
@@ -345,7 +441,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();
+ showFactoryReset(mCorrupt);
return;
}
@@ -354,8 +450,57 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
setContentView(R.layout.crypt_keeper_progress);
encryptionProgressInit();
} else if (mValidationComplete || isDebugView(FORCE_VIEW_PASSWORD)) {
- setContentView(R.layout.crypt_keeper_password_entry);
- passwordEntryInit();
+ new AsyncTask<Void, Void, Void>() {
+ int type = StorageManager.CRYPT_TYPE_PASSWORD;
+ String owner_info;
+ boolean pattern_visible;
+
+ @Override
+ public Void doInBackground(Void... v) {
+ try {
+ final IMountService service = getMountService();
+ type = service.getPasswordType();
+ owner_info = service.getField("OwnerInfo");
+ pattern_visible = !("0".equals(service.getField("PatternVisible")));
+ } catch (Exception e) {
+ Log.e(TAG, "Error calling mount service " + e);
+ }
+
+ return null;
+ }
+
+ @Override
+ public void onPostExecute(java.lang.Void v) {
+ if(type == StorageManager.CRYPT_TYPE_PIN) {
+ setContentView(R.layout.crypt_keeper_pin_entry);
+ mStatusString = R.string.enter_pin;
+ } else if (type == StorageManager.CRYPT_TYPE_PATTERN) {
+ setContentView(R.layout.crypt_keeper_pattern_entry);
+ setBackFunctionality(false);
+ mStatusString = R.string.enter_pattern;
+ } else {
+ setContentView(R.layout.crypt_keeper_password_entry);
+ mStatusString = R.string.enter_password;
+ }
+ final TextView status = (TextView) findViewById(R.id.status);
+ status.setText(mStatusString);
+
+ final TextView ownerInfo = (TextView) findViewById(R.id.owner_info);
+ ownerInfo.setText(owner_info);
+ ownerInfo.setSelected(true); // Required for marquee'ing to work
+
+ passwordEntryInit();
+
+ if (mLockPatternView != null) {
+ mLockPatternView.setInStealthMode(!pattern_visible);
+ }
+
+ if (mCooldown > 0) {
+ setBackFunctionality(false);
+ cooldown(); // in case we are cooling down and coming back from emergency dialler
+ }
+ }
+ }.execute();
} else if (!mValidationRequested) {
// We're supposed to be encrypted, but no validation has been done.
new ValidationTask().execute((Void[]) null);
@@ -418,7 +563,13 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
updateProgress();
}
- private void showFactoryReset() {
+ /**
+ * Show factory reset screen allowing the user to reset their phone when
+ * there is nothing else we can do
+ * @param corrupt true if userdata is corrupt, false if encryption failed
+ * partway through
+ */
+ private void showFactoryReset(final boolean corrupt) {
// Hide the encryption-bot to make room for the "factory reset" button
findViewById(R.id.encroid).setVisibility(View.GONE);
@@ -429,13 +580,22 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
@Override
public void onClick(View v) {
// Factory reset the device.
- sendBroadcast(new Intent("android.intent.action.MASTER_CLEAR"));
+ 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);
}
});
// Alert the user of the failure.
- ((TextView) findViewById(R.id.title)).setText(R.string.crypt_keeper_failed_title);
- ((TextView) findViewById(R.id.status)).setText(R.string.crypt_keeper_failed_summary);
+ if (corrupt) {
+ ((TextView) findViewById(R.id.title)).setText(R.string.crypt_keeper_data_corrupt_title);
+ ((TextView) findViewById(R.id.status)).setText(R.string.crypt_keeper_data_corrupt_summary);
+ } else {
+ ((TextView) findViewById(R.id.title)).setText(R.string.crypt_keeper_failed_title);
+ ((TextView) findViewById(R.id.status)).setText(R.string.crypt_keeper_failed_summary);
+ }
final View view = findViewById(R.id.bottom_divider);
// TODO(viki): Why would the bottom divider be missing in certain layouts? Investigate.
@@ -448,27 +608,44 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
final String state = SystemProperties.get("vold.encrypt_progress");
if ("error_partially_encrypted".equals(state)) {
- showFactoryReset();
+ showFactoryReset(false);
return;
}
- int progress = 0;
+ // Get status as percentage first
+ CharSequence status = getText(R.string.crypt_keeper_setup_description);
+ int percent = 0;
try {
// Force a 50% progress state when debugging the view.
- progress = isDebugView() ? 50 : Integer.parseInt(state);
+ percent = isDebugView() ? 50 : Integer.parseInt(state);
} catch (Exception e) {
Log.w(TAG, "Error parsing progress: " + e.toString());
}
+ String progress = Integer.toString(percent);
- final CharSequence status = getText(R.string.crypt_keeper_setup_description);
+ // Now try to get status as time remaining and replace as appropriate
Log.v(TAG, "Encryption progress: " + progress);
+ try {
+ final String timeProperty = SystemProperties.get("vold.encrypt_time_remaining");
+ int time = Integer.parseInt(timeProperty);
+ if (time >= 0) {
+ // Round up to multiple of 10 - this way display is less jerky
+ time = (time + 9) / 10 * 10;
+ progress = DateUtils.formatElapsedTime(time);
+ status = getText(R.string.crypt_keeper_setup_time_remaining);
+ }
+ } catch (Exception e) {
+ // Will happen if no time etc - show percentage
+ }
+
final TextView tv = (TextView) findViewById(R.id.status);
if (tv != null) {
- tv.setText(TextUtils.expandTemplate(status, Integer.toString(progress)));
+ tv.setText(TextUtils.expandTemplate(status, progress));
}
- // Check the progress every 5 seconds
+
+ // Check the progress every 1 seconds
mHandler.removeMessages(MESSAGE_UPDATE_PROGRESS);
- mHandler.sendEmptyMessageDelayed(MESSAGE_UPDATE_PROGRESS, 5000);
+ mHandler.sendEmptyMessageDelayed(MESSAGE_UPDATE_PROGRESS, 1000);
}
/** Disable password input for a while to force the user to waste time between retries */
@@ -477,10 +654,26 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
if (mCooldown <= 0) {
// Re-enable the password entry and back presses.
- mPasswordEntry.setEnabled(true);
- setBackFunctionality(true);
- status.setText(R.string.enter_password);
+ if (mPasswordEntry != null) {
+ mPasswordEntry.setEnabled(true);
+ final InputMethodManager imm = (InputMethodManager) getSystemService(
+ Context.INPUT_METHOD_SERVICE);
+ imm.showSoftInput(mPasswordEntry, 0);
+ setBackFunctionality(true);
+ }
+ if (mLockPatternView != null) {
+ mLockPatternView.setEnabled(true);
+ }
+ status.setText(mStatusString);
} else {
+ // Disable the password entry and back presses.
+ if (mPasswordEntry != null) {
+ mPasswordEntry.setEnabled(false);
+ }
+ if (mLockPatternView != null) {
+ mLockPatternView.setEnabled(false);
+ }
+
CharSequence template = getText(R.string.crypt_keeper_cooldown);
status.setText(TextUtils.expandTemplate(template, Integer.toString(mCooldown)));
@@ -503,18 +696,58 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
}
}
- private void passwordEntryInit() {
+ private void fakeUnlockAttempt(View postingView) {
+ postingView.postDelayed(mFakeUnlockAttemptRunnable, FAKE_ATTEMPT_DELAY);
+ }
+
+ protected LockPatternView.OnPatternListener mChooseNewLockPatternListener =
+ new LockPatternView.OnPatternListener() {
+
+ @Override
+ public void onPatternStart() {
+ mLockPatternView.removeCallbacks(mClearPatternRunnable);
+ }
+
+ @Override
+ public void onPatternCleared() {
+ }
+
+ @Override
+ public void onPatternDetected(List<LockPatternView.Cell> pattern) {
+ mLockPatternView.setEnabled(false);
+ if (pattern.size() >= MIN_LENGTH_BEFORE_REPORT) {
+ new DecryptTask().execute(LockPatternUtils.patternToString(pattern));
+ } else {
+ // Allow user to make as many of these as they want.
+ fakeUnlockAttempt(mLockPatternView);
+ }
+ }
+
+ @Override
+ public void onPatternCellAdded(List<Cell> pattern) {
+ }
+ };
+
+ private void passwordEntryInit() {
+ // Password/pin case
mPasswordEntry = (EditText) findViewById(R.id.passwordEntry);
- mPasswordEntry.setOnEditorActionListener(this);
- mPasswordEntry.requestFocus();
- // Become quiet when the user interacts with the Edit text screen.
- mPasswordEntry.setOnKeyListener(this);
- mPasswordEntry.setOnTouchListener(this);
- mPasswordEntry.addTextChangedListener(this);
+ if (mPasswordEntry != null){
+ mPasswordEntry.setOnEditorActionListener(this);
+ mPasswordEntry.requestFocus();
+ // Become quiet when the user interacts with the Edit text screen.
+ mPasswordEntry.setOnKeyListener(this);
+ mPasswordEntry.setOnTouchListener(this);
+ mPasswordEntry.addTextChangedListener(this);
+ }
+
+ // Pattern case
+ mLockPatternView = (LockPatternView) findViewById(R.id.lockPattern);
+ if (mLockPatternView != null) {
+ mLockPatternView.setOnPatternListener(mChooseNewLockPatternListener);
+ }
// Disable the Emergency call button if the device has no voice telephone capability
- final TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
- if (!tm.isVoiceCapable()) {
+ if (!getTelephonyManager().isVoiceCapable()) {
final View emergencyCall = findViewById(R.id.emergencyCallButton);
if (emergencyCall != null) {
Log.d(TAG, "Removing the emergency Call button");
@@ -544,23 +777,30 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
if (pm != null) {
mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
mWakeLock.acquire();
+ // Keep awake for 10 minutes - if the user hasn't been alerted by then
+ // best not to just drain their battery
+ mReleaseWakeLockCountdown = 96; // 96 * 5 secs per click + 120 secs before we show this = 600
}
}
+
// Asynchronously throw up the IME, since there are issues with requesting it to be shown
// immediately.
- mHandler.postDelayed(new Runnable() {
- @Override public void run() {
- imm.showSoftInputUnchecked(0, null);
- }
- }, 0);
+ if (mLockPatternView == null && mCooldown <= 0) {
+ mHandler.postDelayed(new Runnable() {
+ @Override public void run() {
+ imm.showSoftInputUnchecked(0, null);
+ }
+ }, 0);
+ }
updateEmergencyCallButtonState();
// Notify the user in 120 seconds that we are waiting for him to enter the password.
mHandler.removeMessages(MESSAGE_NOTIFY);
mHandler.sendEmptyMessageDelayed(MESSAGE_NOTIFY, 120 * 1000);
- // Dismiss keyguard while this screen is showing.
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
+ // Dismiss secure & non-secure keyguards while this screen is showing.
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
}
/**
@@ -637,8 +877,13 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
mPasswordEntry.setEnabled(false);
setBackFunctionality(false);
- Log.d(TAG, "Attempting to send command to decrypt");
- new DecryptTask().execute(password);
+ if (password.length() >= LockPatternUtils.MIN_LOCK_PATTERN_SIZE) {
+ Log.d(TAG, "Attempting to send command to decrypt");
+ new DecryptTask().execute(password);
+ } else {
+ // Allow user to make as many of these as they want.
+ fakeUnlockAttempt(mPasswordEntry);
+ }
return true;
}
@@ -662,7 +907,7 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
*/
private final void setAirplaneModeIfNecessary() {
final boolean isLteDevice =
- TelephonyManager.getDefault().getLteOnCdmaMode() == PhoneConstants.LTE_ON_CDMA_TRUE;
+ getTelephonyManager().getLteOnCdmaMode() == PhoneConstants.LTE_ON_CDMA_TRUE;
if (!isLteDevice) {
Log.d(TAG, "Going into airplane mode.");
Settings.Global.putInt(getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
@@ -698,17 +943,12 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
return;
}
- final int newState = TelephonyManager.getDefault().getCallState();
int textId;
- if (newState == TelephonyManager.CALL_STATE_OFFHOOK) {
- // Show "return to call" text and show phone icon
+ if (getTelecomManager().isInCall()) {
+ // Show "return to call"
textId = R.string.cryptkeeper_return_to_call;
- final int phoneCallIcon = R.drawable.stat_sys_phone_call;
- emergencyCall.setCompoundDrawablesWithIntrinsicBounds(phoneCallIcon, 0, 0, 0);
} else {
textId = R.string.cryptkeeper_emergency_call;
- final int emergencyIcon = R.drawable.ic_emergency;
- emergencyCall.setCompoundDrawablesWithIntrinsicBounds(emergencyIcon, 0, 0, 0);
}
emergencyCall.setText(textId);
}
@@ -718,31 +958,31 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
}
private void takeEmergencyCallAction() {
- if (TelephonyManager.getDefault().getCallState() == TelephonyManager.CALL_STATE_OFFHOOK) {
- resumeCall();
+ TelecomManager telecomManager = getTelecomManager();
+ if (telecomManager.isInCall()) {
+ telecomManager.showInCallScreen(false /* showDialpad */);
} else {
launchEmergencyDialer();
}
}
- private void resumeCall() {
- final ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
- if (phone != null) {
- try {
- phone.showCallScreen();
- } catch (RemoteException e) {
- Log.e(TAG, "Error calling ITelephony service: " + e);
- }
- }
- }
private void launchEmergencyDialer() {
final Intent intent = new Intent(ACTION_EMERGENCY_DIAL);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ setBackFunctionality(true);
startActivity(intent);
}
+ private TelephonyManager getTelephonyManager() {
+ return (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
+ }
+
+ private TelecomManager getTelecomManager() {
+ return (TelecomManager) getSystemService(Context.TELECOM_SERVICE);
+ }
+
/**
* Listen to key events so we can disable sounds when we get a keyinput in EditText.
*/
diff --git a/src/com/android/settings/CryptKeeperConfirm.java b/src/com/android/settings/CryptKeeperConfirm.java
index 4822a83..4d6f26b 100644
--- a/src/com/android/settings/CryptKeeperConfirm.java
+++ b/src/com/android/settings/CryptKeeperConfirm.java
@@ -25,6 +25,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.os.storage.IMountService;
import android.util.Log;
import android.view.LayoutInflater;
@@ -32,8 +33,14 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
+import com.android.internal.widget.LockPatternUtils;
+
+import java.util.Locale;
+
public class CryptKeeperConfirm extends Fragment {
+ private static final String TAG = "CryptKeeperConfirm";
+
public static class Blank extends Activity {
private Handler mHandler = new Handler();
@@ -72,7 +79,7 @@ public class CryptKeeperConfirm extends Fragment {
IMountService mountService = IMountService.Stub.asInterface(service);
try {
Bundle args = getIntent().getExtras();
- mountService.encryptStorage(args.getString("password"));
+ mountService.encryptStorage(args.getInt("type", -1), args.getString("password"));
} catch (Exception e) {
Log.e("CryptKeeper", "Error while encrypting...", e);
}
@@ -90,10 +97,40 @@ public class CryptKeeperConfirm extends Fragment {
return;
}
+ /* WARNING - nasty hack!
+ Settings for the lock screen are not available to the crypto
+ screen (CryptKeeper) at boot. We duplicate the ones that
+ CryptKeeper needs to the crypto key/value store when they are
+ modified (see LockPatternUtils).
+ However, prior to encryption, the crypto key/value store is not
+ persisted across reboots, thus modified settings are lost to
+ CryptKeeper.
+ In order to make sure CryptKeeper had the correct settings after
+ first encrypting, we thus need to rewrite them, which ensures the
+ crypto key/value store is up to date. On encryption, this store
+ is then persisted, and the settings will be there on future
+ reboots.
+ */
+
+ // 1. The owner info.
+ LockPatternUtils utils = new LockPatternUtils(getActivity());
+ utils.setVisiblePatternEnabled(utils.isVisiblePatternEnabled());
+ if (utils.isOwnerInfoEnabled()) {
+ utils.setOwnerInfo(utils.getOwnerInfo(UserHandle.USER_OWNER),
+ UserHandle.USER_OWNER);
+ }
Intent intent = new Intent(getActivity(), Blank.class);
intent.putExtras(getArguments());
-
startActivity(intent);
+
+ // 2. The system locale.
+ try {
+ IBinder service = ServiceManager.getService("mount");
+ IMountService mountService = IMountService.Stub.asInterface(service);
+ mountService.setField("SystemLocale", Locale.getDefault().toLanguageTag());
+ } catch (Exception e) {
+ Log.e(TAG, "Error storing locale for decryption UI", e);
+ }
}
};
diff --git a/src/com/android/settings/CryptKeeperSettings.java b/src/com/android/settings/CryptKeeperSettings.java
index 58d97a8..c572738 100644
--- a/src/com/android/settings/CryptKeeperSettings.java
+++ b/src/com/android/settings/CryptKeeperSettings.java
@@ -29,8 +29,8 @@ import android.content.IntentFilter;
import android.content.res.Resources;
import android.os.BatteryManager;
import android.os.Bundle;
+import android.os.storage.StorageManager;
import android.preference.Preference;
-import android.preference.PreferenceActivity;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
@@ -42,10 +42,6 @@ public class CryptKeeperSettings extends Fragment {
private static final int KEYGUARD_REQUEST = 55;
- // This is the minimum acceptable password quality. If the current password quality is
- // lower than this, encryption should not be activated.
- static final int MIN_PASSWORD_QUALITY = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
-
// Minimum battery charge level (in percent) to launch encryption. If the battery charge is
// lower than this, encryption should not be activated.
private static final int MIN_BATTERY_LEVEL = 80;
@@ -91,7 +87,6 @@ public class CryptKeeperSettings extends Fragment {
// TODO replace (or follow) this dialog with an explicit launch into password UI
new AlertDialog.Builder(getActivity())
.setTitle(R.string.crypt_keeper_dialog_need_password_title)
- .setIconAttribute(android.R.attr.alertDialogIcon)
.setMessage(R.string.crypt_keeper_dialog_need_password_message)
.setPositiveButton(android.R.string.ok, null)
.create()
@@ -158,24 +153,19 @@ public class CryptKeeperSettings extends Fragment {
* @return true if confirmation launched
*/
private boolean runKeyguardConfirmation(int request) {
- // 1. Confirm that we have a sufficient PIN/Password to continue
- LockPatternUtils lockPatternUtils = new LockPatternUtils(getActivity());
- int quality = lockPatternUtils.getActivePasswordQuality();
- if (quality == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK
- && lockPatternUtils.isLockPasswordEnabled()) {
- // Use the alternate as the quality. We expect this to be
- // PASSWORD_QUALITY_SOMETHING(pattern) or PASSWORD_QUALITY_NUMERIC(PIN).
- quality = lockPatternUtils.getKeyguardStoredPasswordQuality();
- }
- if (quality < MIN_PASSWORD_QUALITY) {
- return false;
- }
- // 2. Ask the user to confirm the current PIN/Password
Resources res = getActivity().getResources();
- return new ChooseLockSettingsHelper(getActivity(), this)
- .launchConfirmationActivity(request,
- res.getText(R.string.master_clear_gesture_prompt),
- res.getText(R.string.master_clear_gesture_explanation));
+ ChooseLockSettingsHelper helper = new ChooseLockSettingsHelper(getActivity(), this);
+
+ if (helper.utils().getKeyguardStoredPasswordQuality()
+ == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
+ showFinalConfirmation(StorageManager.CRYPT_TYPE_DEFAULT, "");
+ return true;
+ }
+
+ return helper.launchConfirmationActivity(request,
+ res.getText(R.string.master_clear_gesture_prompt),
+ res.getText(R.string.crypt_keeper_confirm_encrypt),
+ true);
}
@Override
@@ -189,18 +179,20 @@ public class CryptKeeperSettings extends Fragment {
// If the user entered a valid keyguard trace, present the final
// confirmation prompt; otherwise, go back to the initial state.
if (resultCode == Activity.RESULT_OK && data != null) {
+ int type = data.getIntExtra(ChooseLockSettingsHelper.EXTRA_KEY_TYPE, -1);
String password = data.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
if (!TextUtils.isEmpty(password)) {
- showFinalConfirmation(password);
+ showFinalConfirmation(type, password);
}
}
}
- private void showFinalConfirmation(String password) {
+ private void showFinalConfirmation(int type, String password) {
Preference preference = new Preference(getActivity());
preference.setFragment(CryptKeeperConfirm.class.getName());
preference.setTitle(R.string.crypt_keeper_confirm_title);
+ preference.getExtras().putInt("type", type);
preference.getExtras().putString("password", password);
- ((PreferenceActivity) getActivity()).onPreferenceStartFragment(null, preference);
+ ((SettingsActivity) getActivity()).onPreferenceStartFragment(null, preference);
}
}
diff --git a/src/com/android/settings/DataUsageSummary.java b/src/com/android/settings/DataUsageSummary.java
index db1ae29..872de1a 100644
--- a/src/com/android/settings/DataUsageSummary.java
+++ b/src/com/android/settings/DataUsageSummary.java
@@ -53,10 +53,9 @@ import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.Fragment;
-import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.app.LoaderManager.LoaderCallbacks;
-import android.content.ContentResolver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -64,6 +63,7 @@ import android.content.Loader;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
@@ -86,9 +86,8 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.os.UserManager;
import android.preference.Preference;
-import android.preference.PreferenceActivity;
-import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.text.format.DateUtils;
@@ -110,9 +109,6 @@ import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.Button;
-import android.widget.CheckBox;
-import android.widget.CompoundButton;
-import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
@@ -136,9 +132,11 @@ 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.PieChartView;
import com.google.android.collect.Lists;
import libcore.util.Objects;
@@ -152,7 +150,7 @@ import java.util.Locale;
* Panel showing data usage history across various networks, including options
* to inspect based on usage cycle and control through {@link NetworkPolicy}.
*/
-public class DataUsageSummary extends Fragment {
+public class DataUsageSummary extends HighlightingFragment implements Indexable {
private static final String TAG = "DataUsage";
private static final boolean LOGD = false;
@@ -170,7 +168,6 @@ public class DataUsageSummary extends Fragment {
private static final String TAB_ETHERNET = "ethernet";
private static final String TAG_CONFIRM_DATA_DISABLE = "confirmDataDisable";
- private static final String TAG_CONFIRM_DATA_ROAMING = "confirmDataRoaming";
private static final String TAG_CONFIRM_LIMIT = "confirmLimit";
private static final String TAG_CYCLE_EDITOR = "cycleEditor";
private static final String TAG_WARNING_EDITOR = "warningEditor";
@@ -178,16 +175,20 @@ public class DataUsageSummary extends Fragment {
private static final String TAG_CONFIRM_RESTRICT = "confirmRestrict";
private static final String TAG_DENIED_RESTRICT = "deniedRestrict";
private static final String TAG_CONFIRM_APP_RESTRICT = "confirmAppRestrict";
- private static final String TAG_CONFIRM_AUTO_SYNC_CHANGE = "confirmAutoSyncChange";
private static final String TAG_APP_DETAILS = "appDetails";
+ private static final String DATA_USAGE_ENABLE_MOBILE_KEY = "data_usage_enable_mobile";
+ private static final String DATA_USAGE_DISABLE_MOBILE_LIMIT_KEY =
+ "data_usage_disable_mobile_limit";
+ private static final String DATA_USAGE_CYCLE_KEY = "data_usage_cycle";
+
private static final int LOADER_CHART_DATA = 2;
private static final int LOADER_SUMMARY = 3;
private INetworkManagementService mNetworkService;
private INetworkStatsService mStatsService;
private NetworkPolicyManager mPolicyManager;
- private ConnectivityManager mConnService;
+ private TelephonyManager mTelephonyManager;
private INetworkStatsSession mStatsSession;
@@ -210,29 +211,33 @@ public class DataUsageSummary extends Fragment {
private ViewGroup mNetworkSwitchesContainer;
private LinearLayout mNetworkSwitches;
+ private boolean mDataEnabledSupported;
private Switch mDataEnabled;
private View mDataEnabledView;
- private CheckBox mDisableAtLimit;
+ private boolean mDisableAtLimitSupported;
+ private Switch mDisableAtLimit;
private View mDisableAtLimitView;
private View mCycleView;
private Spinner mCycleSpinner;
private CycleAdapter mCycleAdapter;
+ private TextView mCycleSummary;
private ChartDataUsageView mChart;
- private TextView mUsageSummary;
+ private View mDisclaimer;
private TextView mEmpty;
+ private View mStupidPadding;
private View mAppDetail;
private ImageView mAppIcon;
private ViewGroup mAppTitles;
- private PieChartView mAppPieChart;
+ private TextView mAppTotal;
private TextView mAppForeground;
private TextView mAppBackground;
private Button mAppSettings;
private LinearLayout mAppSwitches;
- private CheckBox mAppRestrict;
+ private Switch mAppRestrict;
private View mAppRestrictView;
private boolean mShowWifi = false;
@@ -250,9 +255,11 @@ public class DataUsageSummary extends Fragment {
private String mCurrentTab = null;
private String mIntentTab = null;
- private MenuItem mMenuDataRoaming;
private MenuItem mMenuRestrictBackground;
- private MenuItem mMenuAutoSync;
+ private MenuItem mMenuShowWifi;
+ private MenuItem mMenuShowEthernet;
+ private MenuItem mMenuSimCards;
+ private MenuItem mMenuCellularNetworks;
/** Flag used to ignore listeners during binding. */
private boolean mBinding;
@@ -269,7 +276,7 @@ public class DataUsageSummary extends Fragment {
mStatsService = INetworkStatsService.Stub.asInterface(
ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
mPolicyManager = NetworkPolicyManager.from(context);
- mConnService = ConnectivityManager.from(context);
+ mTelephonyManager = TelephonyManager.from(context);
mPrefs = getActivity().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
@@ -350,26 +357,36 @@ public class DataUsageSummary extends Fragment {
mNetworkSwitches = (LinearLayout) mHeader.findViewById(R.id.network_switches);
mDataEnabled = new Switch(inflater.getContext());
+ mDataEnabled.setClickable(false);
+ mDataEnabled.setFocusable(false);
mDataEnabledView = inflatePreference(inflater, mNetworkSwitches, mDataEnabled);
- mDataEnabled.setOnCheckedChangeListener(mDataEnabledListener);
+ mDataEnabledView.setTag(R.id.preference_highlight_key,
+ DATA_USAGE_ENABLE_MOBILE_KEY);
+ mDataEnabledView.setClickable(true);
+ mDataEnabledView.setFocusable(true);
+ mDataEnabledView.setOnClickListener(mDataEnabledListener);
mNetworkSwitches.addView(mDataEnabledView);
- mDisableAtLimit = new CheckBox(inflater.getContext());
+ mDisableAtLimit = new Switch(inflater.getContext());
mDisableAtLimit.setClickable(false);
mDisableAtLimit.setFocusable(false);
mDisableAtLimitView = inflatePreference(inflater, mNetworkSwitches, mDisableAtLimit);
+ mDisableAtLimitView.setTag(R.id.preference_highlight_key,
+ DATA_USAGE_DISABLE_MOBILE_LIMIT_KEY);
mDisableAtLimitView.setClickable(true);
mDisableAtLimitView.setFocusable(true);
mDisableAtLimitView.setOnClickListener(mDisableAtLimitListener);
mNetworkSwitches.addView(mDisableAtLimitView);
- }
- // bind cycle dropdown
- mCycleView = mHeader.findViewById(R.id.cycles);
- mCycleSpinner = (Spinner) mCycleView.findViewById(R.id.cycles_spinner);
- mCycleAdapter = new CycleAdapter(context);
- mCycleSpinner.setAdapter(mCycleAdapter);
- mCycleSpinner.setOnItemSelectedListener(mCycleListener);
+ mCycleView = inflater.inflate(R.layout.data_usage_cycles, mNetworkSwitches, false);
+ mCycleView.setTag(R.id.preference_highlight_key, DATA_USAGE_CYCLE_KEY);
+ mCycleSpinner = (Spinner) mCycleView.findViewById(R.id.cycles_spinner);
+ mCycleAdapter = new CycleAdapter(context);
+ mCycleSpinner.setAdapter(mCycleAdapter);
+ mCycleSpinner.setOnItemSelectedListener(mCycleListener);
+ mCycleSummary = (TextView) mCycleView.findViewById(R.id.cycle_summary);
+ mNetworkSwitches.addView(mCycleView);
+ }
mChart = (ChartDataUsageView) mHeader.findViewById(R.id.chart);
mChart.setListener(mChartListener);
@@ -380,15 +397,13 @@ public class DataUsageSummary extends Fragment {
mAppDetail = mHeader.findViewById(R.id.app_detail);
mAppIcon = (ImageView) mAppDetail.findViewById(R.id.app_icon);
mAppTitles = (ViewGroup) mAppDetail.findViewById(R.id.app_titles);
- mAppPieChart = (PieChartView) mAppDetail.findViewById(R.id.app_pie_chart);
mAppForeground = (TextView) mAppDetail.findViewById(R.id.app_foreground);
mAppBackground = (TextView) mAppDetail.findViewById(R.id.app_background);
mAppSwitches = (LinearLayout) mAppDetail.findViewById(R.id.app_switches);
mAppSettings = (Button) mAppDetail.findViewById(R.id.app_settings);
- mAppSettings.setOnClickListener(mAppSettingsListener);
- mAppRestrict = new CheckBox(inflater.getContext());
+ mAppRestrict = new Switch(inflater.getContext());
mAppRestrict.setClickable(false);
mAppRestrict.setFocusable(false);
mAppRestrictView = inflatePreference(inflater, mAppSwitches, mAppRestrict);
@@ -398,10 +413,12 @@ public class DataUsageSummary extends Fragment {
mAppSwitches.addView(mAppRestrictView);
}
- mUsageSummary = (TextView) mHeader.findViewById(R.id.usage_summary);
+ mDisclaimer = mHeader.findViewById(R.id.disclaimer);
mEmpty = (TextView) mHeader.findViewById(android.R.id.empty);
+ mStupidPadding = mHeader.findViewById(R.id.stupid_padding);
- mAdapter = new DataUsageAdapter(mUidDetailProvider, mInsetSide);
+ final UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ mAdapter = new DataUsageAdapter(um, mUidDetailProvider, mInsetSide);
mListView.setOnItemClickListener(mListListener);
mListView.setAdapter(mAdapter);
@@ -409,8 +426,8 @@ public class DataUsageSummary extends Fragment {
}
@Override
- public void onResume() {
- super.onResume();
+ public void onViewStateRestored(Bundle savedInstanceState) {
+ super.onViewStateRestored(savedInstanceState);
// pick default tab based on incoming intent
final Intent intent = getActivity().getIntent();
@@ -419,6 +436,18 @@ public class DataUsageSummary extends Fragment {
// this kicks off chain reaction which creates tabs, binds the body to
// selected network, and binds chart, cycles and detail list.
updateTabs();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ getView().post(new Runnable() {
+ @Override
+ public void run() {
+ highlightViewIfNeeded();
+ }
+ });
// kick off background task to update stats
new AsyncTask<Void, Void, Void>() {
@@ -454,39 +483,24 @@ public class DataUsageSummary extends Fragment {
final boolean appDetailMode = isAppDetailMode();
final boolean isOwner = ActivityManager.getCurrentUser() == UserHandle.USER_OWNER;
- mMenuDataRoaming = menu.findItem(R.id.data_usage_menu_roaming);
- mMenuDataRoaming.setVisible(hasReadyMobileRadio(context) && !appDetailMode);
- mMenuDataRoaming.setChecked(getDataRoaming());
-
- mMenuRestrictBackground = menu.findItem(R.id.data_usage_menu_restrict_background);
- mMenuRestrictBackground.setVisible(
- hasReadyMobileRadio(context) && isOwner && !appDetailMode);
- mMenuRestrictBackground.setChecked(mPolicyManager.getRestrictBackground());
-
- mMenuAutoSync = menu.findItem(R.id.data_usage_menu_auto_sync);
- mMenuAutoSync.setChecked(ContentResolver.getMasterSyncAutomatically());
- mMenuAutoSync.setVisible(!appDetailMode);
-
- final MenuItem split4g = menu.findItem(R.id.data_usage_menu_split_4g);
- split4g.setVisible(hasReadyMobile4gRadio(context) && isOwner && !appDetailMode);
- split4g.setChecked(isMobilePolicySplit());
-
- final MenuItem showWifi = menu.findItem(R.id.data_usage_menu_show_wifi);
+ mMenuShowWifi = menu.findItem(R.id.data_usage_menu_show_wifi);
if (hasWifiRadio(context) && hasReadyMobileRadio(context)) {
- showWifi.setVisible(!appDetailMode);
- showWifi.setChecked(mShowWifi);
+ mMenuShowWifi.setVisible(!appDetailMode);
} else {
- showWifi.setVisible(false);
+ mMenuShowWifi.setVisible(false);
}
- final MenuItem showEthernet = menu.findItem(R.id.data_usage_menu_show_ethernet);
+ mMenuShowEthernet = menu.findItem(R.id.data_usage_menu_show_ethernet);
if (hasEthernet(context) && hasReadyMobileRadio(context)) {
- showEthernet.setVisible(!appDetailMode);
- showEthernet.setChecked(mShowEthernet);
+ mMenuShowEthernet.setVisible(!appDetailMode);
} else {
- showEthernet.setVisible(false);
+ mMenuShowEthernet.setVisible(false);
}
+ mMenuRestrictBackground = menu.findItem(R.id.data_usage_menu_restrict_background);
+ mMenuRestrictBackground.setVisible(
+ hasReadyMobileRadio(context) && isOwner && !appDetailMode);
+
final MenuItem metered = menu.findItem(R.id.data_usage_menu_metered);
if (hasReadyMobileRadio(context) || hasWifiRadio(context)) {
metered.setVisible(!appDetailMode);
@@ -494,6 +508,14 @@ public class DataUsageSummary extends Fragment {
metered.setVisible(false);
}
+ // TODO: show when multiple sims available
+ mMenuSimCards = menu.findItem(R.id.data_usage_menu_sim_cards);
+ mMenuSimCards.setVisible(false);
+
+ mMenuCellularNetworks = menu.findItem(R.id.data_usage_menu_cellular_networks);
+ mMenuCellularNetworks.setVisible(hasReadyMobileRadio(context)
+ && !appDetailMode && isOwner);
+
final MenuItem help = menu.findItem(R.id.data_usage_menu_help);
String helpUrl;
if (!TextUtils.isEmpty(helpUrl = getResources().getString(R.string.help_url_data_usage))) {
@@ -501,23 +523,35 @@ public class DataUsageSummary extends Fragment {
} else {
help.setVisible(false);
}
+
+ updateMenuTitles();
+ }
+
+ private void updateMenuTitles() {
+ if (mPolicyManager.getRestrictBackground()) {
+ mMenuRestrictBackground.setTitle(R.string.data_usage_menu_allow_background);
+ } else {
+ mMenuRestrictBackground.setTitle(R.string.data_usage_menu_restrict_background);
+ }
+
+ if (mShowWifi) {
+ mMenuShowWifi.setTitle(R.string.data_usage_menu_hide_wifi);
+ } else {
+ mMenuShowWifi.setTitle(R.string.data_usage_menu_show_wifi);
+ }
+
+ if (mShowEthernet) {
+ mMenuShowEthernet.setTitle(R.string.data_usage_menu_hide_ethernet);
+ } else {
+ mMenuShowEthernet.setTitle(R.string.data_usage_menu_show_ethernet);
+ }
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
- case R.id.data_usage_menu_roaming: {
- final boolean dataRoaming = !item.isChecked();
- if (dataRoaming) {
- ConfirmDataRoamingFragment.show(this);
- } else {
- // no confirmation to disable roaming
- setDataRoaming(false);
- }
- return true;
- }
case R.id.data_usage_menu_restrict_background: {
- final boolean restrictBackground = !item.isChecked();
+ final boolean restrictBackground = !mPolicyManager.getRestrictBackground();
if (restrictBackground) {
ConfirmRestrictFragment.show(this);
} else {
@@ -526,39 +560,35 @@ public class DataUsageSummary extends Fragment {
}
return true;
}
- case R.id.data_usage_menu_split_4g: {
- final boolean mobileSplit = !item.isChecked();
- setMobilePolicySplit(mobileSplit);
- item.setChecked(isMobilePolicySplit());
- updateTabs();
- return true;
- }
case R.id.data_usage_menu_show_wifi: {
- mShowWifi = !item.isChecked();
+ mShowWifi = !mShowWifi;
mPrefs.edit().putBoolean(PREF_SHOW_WIFI, mShowWifi).apply();
- item.setChecked(mShowWifi);
+ updateMenuTitles();
updateTabs();
return true;
}
case R.id.data_usage_menu_show_ethernet: {
- mShowEthernet = !item.isChecked();
+ mShowEthernet = !mShowEthernet;
mPrefs.edit().putBoolean(PREF_SHOW_ETHERNET, mShowEthernet).apply();
- item.setChecked(mShowEthernet);
+ updateMenuTitles();
updateTabs();
return true;
}
- case R.id.data_usage_menu_metered: {
- final PreferenceActivity activity = (PreferenceActivity) getActivity();
- activity.startPreferencePanel(DataUsageMeteredSettings.class.getCanonicalName(), null,
- R.string.data_usage_metered_title, null, this, 0);
+ case R.id.data_usage_menu_sim_cards: {
+ // TODO: hook up to sim cards
return true;
}
- case R.id.data_usage_menu_auto_sync: {
- if (ActivityManager.isUserAMonkey()) {
- Log.d("SyncState", "ignoring monkey's attempt to flip global sync state");
- } else {
- ConfirmAutoSyncChangeFragment.show(this, !item.isChecked());
- }
+ case R.id.data_usage_menu_cellular_networks: {
+ final Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setComponent(new ComponentName("com.android.phone",
+ "com.android.phone.MobileNetworkSettings"));
+ startActivity(intent);
+ return true;
+ }
+ case R.id.data_usage_menu_metered: {
+ final SettingsActivity sa = (SettingsActivity) getActivity();
+ sa.startPreferencePanel(DataUsageMeteredSettings.class.getCanonicalName(), null,
+ R.string.data_usage_metered_title, null, this, 0);
return true;
}
}
@@ -575,11 +605,6 @@ public class DataUsageSummary extends Fragment {
TrafficStats.closeQuietly(mStatsSession);
- if (this.isRemoving()) {
- getFragmentManager()
- .popBackStack(TAG_APP_DETAILS, FragmentManager.POP_BACK_STACK_INCLUSIVE);
- }
-
super.onDestroy();
}
@@ -699,15 +724,14 @@ public class DataUsageSummary extends Fragment {
mListView.setVisibility(View.VISIBLE);
}
- final boolean tabChanged = !currentTab.equals(mCurrentTab);
mCurrentTab = currentTab;
if (LOGD) Log.d(TAG, "updateBody() with currentTab=" + currentTab);
- mDataEnabledView.setVisibility(isOwner ? View.VISIBLE : View.GONE);
+ mDataEnabledSupported = isOwner;
+ mDisableAtLimitSupported = true;
// TODO: remove mobile tabs when SIM isn't ready
- final TelephonyManager tele = TelephonyManager.from(context);
if (TAB_MOBILE.equals(currentTab)) {
setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_mobile);
@@ -728,14 +752,14 @@ public class DataUsageSummary extends Fragment {
} else if (TAB_WIFI.equals(currentTab)) {
// wifi doesn't have any controls
- mDataEnabledView.setVisibility(View.GONE);
- mDisableAtLimitView.setVisibility(View.GONE);
+ mDataEnabledSupported = false;
+ mDisableAtLimitSupported = false;
mTemplate = buildTemplateWifiWildcard();
} else if (TAB_ETHERNET.equals(currentTab)) {
// ethernet doesn't have any controls
- mDataEnabledView.setVisibility(View.GONE);
- mDisableAtLimitView.setVisibility(View.GONE);
+ mDataEnabledSupported = false;
+ mDisableAtLimitSupported = false;
mTemplate = buildTemplateEthernet();
} else {
@@ -788,12 +812,32 @@ public class DataUsageSummary extends Fragment {
mAppIcon.setImageDrawable(detail.icon);
mAppTitles.removeAllViews();
+
+ View title = null;
if (detail.detailLabels != null) {
- for (CharSequence label : detail.detailLabels) {
- mAppTitles.addView(inflateAppTitle(inflater, mAppTitles, label));
+ final int n = detail.detailLabels.length;
+ for (int i = 0; i < n; ++i) {
+ CharSequence label = detail.detailLabels[i];
+ CharSequence contentDescription = detail.detailContentDescriptions[i];
+ title = inflater.inflate(R.layout.data_usage_app_title, mAppTitles, false);
+ TextView appTitle = (TextView) title.findViewById(R.id.app_title);
+ appTitle.setText(label);
+ appTitle.setContentDescription(contentDescription);
+ mAppTitles.addView(title);
}
} else {
- mAppTitles.addView(inflateAppTitle(inflater, mAppTitles, detail.label));
+ title = inflater.inflate(R.layout.data_usage_app_title, mAppTitles, false);
+ TextView appTitle = (TextView) title.findViewById(R.id.app_title);
+ appTitle.setText(detail.label);
+ appTitle.setContentDescription(detail.contentDescription);
+ mAppTitles.addView(title);
+ }
+
+ // Remember last slot for summary
+ if (title != null) {
+ mAppTotal = (TextView) title.findViewById(R.id.app_summary);
+ } else {
+ mAppTotal = null;
}
// enable settings button when package provides it
@@ -812,11 +856,24 @@ public class DataUsageSummary extends Fragment {
}
}
+ mAppSettings.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (!isAdded()) {
+ return;
+ }
+
+ // TODO: target towards entire UID instead of just first package
+ getActivity().startActivityAsUser(mAppSettingsIntent,
+ new UserHandle(UserHandle.getUserId(uid)));
+ }
+ });
mAppSettings.setEnabled(matchFound);
mAppSettings.setVisibility(View.VISIBLE);
} else {
mAppSettingsIntent = null;
+ mAppSettings.setOnClickListener(null);
mAppSettings.setVisibility(View.GONE);
}
@@ -859,13 +916,13 @@ public class DataUsageSummary extends Fragment {
// TODO: deprecate and remove this once enabled flag is on policy
return mMobileDataEnabled;
} else {
- return mConnService.getMobileDataEnabled();
+ return mTelephonyManager.getDataEnabled();
}
}
private void setMobileDataEnabled(boolean enabled) {
if (LOGD) Log.d(TAG, "setMobileDataEnabled()");
- mConnService.setMobileDataEnabled(enabled);
+ mTelephonyManager.setDataEnabled(enabled);
mMobileDataEnabled = enabled;
updatePolicy(false);
}
@@ -884,22 +941,9 @@ public class DataUsageSummary extends Fragment {
}
}
- private boolean getDataRoaming() {
- final ContentResolver resolver = getActivity().getContentResolver();
- return Settings.Global.getInt(resolver, Settings.Global.DATA_ROAMING, 0) != 0;
- }
-
- private void setDataRoaming(boolean enabled) {
- // TODO: teach telephony DataConnectionTracker to watch and apply
- // updates when changed.
- final ContentResolver resolver = getActivity().getContentResolver();
- Settings.Global.putInt(resolver, Settings.Global.DATA_ROAMING, enabled ? 1 : 0);
- mMenuDataRoaming.setChecked(enabled);
- }
-
public void setRestrictBackground(boolean restrictBackground) {
mPolicyManager.setRestrictBackground(restrictBackground);
- mMenuRestrictBackground.setChecked(restrictBackground);
+ updateMenuTitles();
}
private boolean getAppRestrictBackground() {
@@ -921,10 +965,12 @@ public class DataUsageSummary extends Fragment {
* current {@link #mTemplate}.
*/
private void updatePolicy(boolean refreshCycle) {
+ boolean dataEnabledVisible = mDataEnabledSupported;
+ boolean disableAtLimitVisible = mDisableAtLimitSupported;
+
if (isAppDetailMode()) {
- mNetworkSwitches.setVisibility(View.GONE);
- } else {
- mNetworkSwitches.setVisibility(View.VISIBLE);
+ dataEnabledVisible = false;
+ disableAtLimitVisible = false;
}
// TODO: move enabled state directly into policy
@@ -936,7 +982,6 @@ public class DataUsageSummary extends Fragment {
final NetworkPolicy policy = mPolicyEditor.getPolicy(mTemplate);
if (isNetworkPolicyModifiable(policy)) {
- mDisableAtLimitView.setVisibility(View.VISIBLE);
mDisableAtLimit.setChecked(policy != null && policy.limitBytes != LIMIT_DISABLED);
if (!isAppDetailMode()) {
mChart.bindNetworkPolicy(policy);
@@ -944,10 +989,13 @@ public class DataUsageSummary extends Fragment {
} else {
// controls are disabled; don't bind warning/limit sweeps
- mDisableAtLimitView.setVisibility(View.GONE);
+ disableAtLimitVisible = false;
mChart.bindNetworkPolicy(null);
}
+ mDataEnabledView.setVisibility(dataEnabledVisible ? View.VISIBLE : View.GONE);
+ mDisableAtLimitView.setVisibility(disableAtLimitVisible ? View.VISIBLE : View.GONE);
+
if (refreshCycle) {
// generate cycle list based on policy and available history
updateCycleList(policy);
@@ -1027,12 +1075,12 @@ public class DataUsageSummary extends Fragment {
}
}
- private OnCheckedChangeListener mDataEnabledListener = new OnCheckedChangeListener() {
+ private View.OnClickListener mDataEnabledListener = new View.OnClickListener() {
@Override
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ public void onClick(View v) {
if (mBinding) return;
- final boolean dataEnabled = isChecked;
+ final boolean dataEnabled = !mDataEnabled.isChecked();
final String currentTab = mCurrentTab;
if (TAB_MOBILE.equals(currentTab)) {
if (dataEnabled) {
@@ -1078,16 +1126,6 @@ public class DataUsageSummary extends Fragment {
}
};
- private OnClickListener mAppSettingsListener = new OnClickListener() {
- @Override
- public void onClick(View v) {
- if (!isAdded()) return;
-
- // TODO: target torwards entire UID instead of just first package
- startActivity(mAppSettingsIntent);
- }
- };
-
private OnItemClickListener mListListener = new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
@@ -1156,15 +1194,11 @@ public class DataUsageSummary extends Fragment {
final long defaultBytes = entry.rxBytes + entry.txBytes;
entry = mChartData.detailForeground.getValues(start, end, now, entry);
final long foregroundBytes = entry.rxBytes + entry.txBytes;
+ final long totalBytes = defaultBytes + foregroundBytes;
- mAppPieChart.setOriginAngle(175);
-
- mAppPieChart.removeAllSlices();
- mAppPieChart.addSlice(foregroundBytes, Color.parseColor("#d88d3a"));
- mAppPieChart.addSlice(defaultBytes, Color.parseColor("#666666"));
-
- mAppPieChart.generatePath();
-
+ if (mAppTotal != null) {
+ mAppTotal.setText(Formatter.formatFileSize(context, totalBytes));
+ }
mAppBackground.setText(Formatter.formatFileSize(context, defaultBytes));
mAppForeground.setText(Formatter.formatFileSize(context, foregroundBytes));
@@ -1173,11 +1207,15 @@ public class DataUsageSummary extends Fragment {
getLoaderManager().destroyLoader(LOADER_SUMMARY);
+ mCycleSummary.setVisibility(View.GONE);
+
} else {
if (mChartData != null) {
entry = mChartData.network.getValues(start, end, now, null);
}
+ mCycleSummary.setVisibility(View.VISIBLE);
+
// kick off loader for detailed stats
getLoaderManager().restartLoader(LOADER_SUMMARY,
SummaryForAllUidLoader.buildArgs(mTemplate, start, end), mSummaryCallbacks);
@@ -1185,18 +1223,19 @@ public class DataUsageSummary extends Fragment {
final long totalBytes = entry != null ? entry.rxBytes + entry.txBytes : 0;
final String totalPhrase = Formatter.formatFileSize(context, totalBytes);
- final String rangePhrase = formatDateRange(context, start, end);
+ mCycleSummary.setText(totalPhrase);
- final int summaryRes;
if (TAB_MOBILE.equals(mCurrentTab) || TAB_3G.equals(mCurrentTab)
|| TAB_4G.equals(mCurrentTab)) {
- summaryRes = R.string.data_usage_total_during_range_mobile;
+ if (isAppDetailMode()) {
+ mDisclaimer.setVisibility(View.GONE);
+ } else {
+ mDisclaimer.setVisibility(View.VISIBLE);
+ }
} else {
- summaryRes = R.string.data_usage_total_during_range;
+ mDisclaimer.setVisibility(View.GONE);
}
- mUsageSummary.setText(getString(summaryRes, totalPhrase, rangePhrase));
-
// initial layout is finished above, ensure we have transitions
ensureLayoutTransitions();
}
@@ -1256,6 +1295,7 @@ public class DataUsageSummary extends Fragment {
private void updateEmptyVisible() {
final boolean isEmpty = mAdapter.isEmpty() && !isAppDetailMode();
mEmpty.setVisibility(isEmpty ? View.VISIBLE : View.GONE);
+ mStupidPadding.setVisibility(isEmpty ? View.VISIBLE : View.GONE);
}
};
@@ -1287,12 +1327,6 @@ public class DataUsageSummary extends Fragment {
private DataUsageChartListener mChartListener = new DataUsageChartListener() {
@Override
- public void onInspectRangeChanged() {
- if (LOGD) Log.d(TAG, "onInspectRangeChanged()");
- updateDetailData();
- }
-
- @Override
public void onWarningChanged() {
setPolicyWarningBytes(mChart.getWarningBytes());
}
@@ -1382,8 +1416,8 @@ public class DataUsageSummary extends Fragment {
private final CycleChangeItem mChangeItem;
public CycleAdapter(Context context) {
- super(context, android.R.layout.simple_spinner_item);
- setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ super(context, R.layout.data_usage_cycle_item);
+ setDropDownViewResource(R.layout.data_usage_cycle_item_dropdown);
mChangeItem = new CycleChangeItem(context);
}
@@ -1425,11 +1459,21 @@ public class DataUsageSummary extends Fragment {
}
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;
}
@@ -1458,7 +1502,11 @@ public class DataUsageSummary extends Fragment {
@Override
public int compareTo(AppItem another) {
- return Long.compare(another.total, total);
+ 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>() {
@@ -1480,13 +1528,15 @@ public class DataUsageSummary extends Fragment {
public static class DataUsageAdapter extends BaseAdapter {
private final UidDetailProvider mProvider;
private final int mInsetSide;
+ private final UserManager mUm;
private ArrayList<AppItem> mItems = Lists.newArrayList();
private long mLargest;
- public DataUsageAdapter(UidDetailProvider provider, int insetSide) {
+ public DataUsageAdapter(final UserManager userManager, UidDetailProvider provider, int insetSide) {
mProvider = checkNotNull(provider);
mInsetSide = insetSide;
+ mUm = userManager;
}
/**
@@ -1494,8 +1544,10 @@ public class DataUsageSummary extends Fragment {
*/
public void bindStats(NetworkStats stats, int[] restrictedUids) {
mItems.clear();
+ mLargest = 0;
final int currentUserId = ActivityManager.getCurrentUser();
+ final List<UserHandle> profiles = mUm.getUserProfiles();
final SparseArray<AppItem> knownItems = new SparseArray<AppItem>();
NetworkStats.Entry entry = null;
@@ -1505,32 +1557,43 @@ public class DataUsageSummary extends Fragment {
// Decide how to collapse items together
final int uid = entry.uid;
+
final int collapseKey;
+ final int category;
+ final int userId = UserHandle.getUserId(uid);
if (UserHandle.isApp(uid)) {
- if (UserHandle.getUserId(uid) == currentUserId) {
+ if (profiles.contains(new UserHandle(userId))) {
+ if (userId != currentUserId) {
+ // Add to a managed user item.
+ final int managedKey = UidDetailProvider.buildKeyForUser(userId);
+ accumulate(managedKey, knownItems, entry,
+ AppItem.CATEGORY_USER);
+ }
+ // Add to app item.
collapseKey = uid;
+ category = AppItem.CATEGORY_APP;
} else {
- collapseKey = UidDetailProvider.buildKeyForUser(UserHandle.getUserId(uid));
+ // Add to other user item.
+ collapseKey = UidDetailProvider.buildKeyForUser(userId);
+ category = AppItem.CATEGORY_USER;
}
} else if (uid == UID_REMOVED || uid == UID_TETHERING) {
collapseKey = uid;
+ category = AppItem.CATEGORY_APP;
} else {
collapseKey = android.os.Process.SYSTEM_UID;
+ category = AppItem.CATEGORY_APP;
}
-
- AppItem item = knownItems.get(collapseKey);
- if (item == null) {
- item = new AppItem(collapseKey);
- mItems.add(item);
- knownItems.put(item.key, item);
- }
- item.addUid(uid);
- item.total += entry.rxBytes + entry.txBytes;
+ accumulate(collapseKey, knownItems, entry, category);
}
- for (int uid : restrictedUids) {
- // Only splice in restricted state for current user
- if (UserHandle.getUserId(uid) != currentUserId) continue;
+ final int restrictedUidsMax = restrictedUids.length;
+ for (int i = 0; i < restrictedUidsMax; ++i) {
+ final int uid = restrictedUids[i];
+ // Only splice in restricted state for current user or managed users
+ if (!profiles.contains(new UserHandle(UserHandle.getUserId(uid)))) {
+ continue;
+ }
AppItem item = knownItems.get(uid);
if (item == null) {
@@ -1542,11 +1605,43 @@ public class DataUsageSummary extends Fragment {
item.restricted = true;
}
+ if (!mItems.isEmpty()) {
+ final AppItem title = new AppItem();
+ title.category = AppItem.CATEGORY_APP_TITLE;
+ mItems.add(title);
+ }
+
Collections.sort(mItems);
- mLargest = (mItems.size() > 0) ? mItems.get(0).total : 0;
notifyDataSetChanged();
}
+ /**
+ * Accumulate data usage of a network stats entry for the item mapped by the collapse key.
+ * Creates the item if needed.
+ *
+ * @param collapseKey the collapse key used to map the item.
+ * @param knownItems collection of known (already existing) items.
+ * @param entry the network stats entry to extract data usage from.
+ * @param itemCategory the item is categorized on the list view by this category. Must be
+ * either AppItem.APP_ITEM_CATEGORY or AppItem.MANAGED_USER_ITEM_CATEGORY
+ */
+ private void accumulate(int collapseKey, final SparseArray<AppItem> knownItems,
+ NetworkStats.Entry entry, int itemCategory) {
+ final int uid = entry.uid;
+ AppItem item = knownItems.get(collapseKey);
+ if (item == null) {
+ item = new AppItem(collapseKey);
+ item.category = itemCategory;
+ mItems.add(item);
+ knownItems.put(item.key, item);
+ }
+ item.addUid(uid);
+ item.total += entry.rxBytes + entry.txBytes;
+ if (mLargest < item.total) {
+ mLargest = item.total;
+ }
+ }
+
@Override
public int getCount() {
return mItems.size();
@@ -1562,37 +1657,82 @@ public class DataUsageSummary extends Fragment {
return mItems.get(position).key;
}
+ /**
+ * See {@link #getItemViewType} for the view types.
+ */
@Override
- public View getView(int position, View convertView, ViewGroup parent) {
- if (convertView == null) {
- convertView = LayoutInflater.from(parent.getContext()).inflate(
- R.layout.data_usage_item, parent, false);
+ public int getViewTypeCount() {
+ return 2;
+ }
- if (mInsetSide > 0) {
- convertView.setPaddingRelative(mInsetSide, 0, mInsetSide, 0);
- }
+ /**
+ * Returns 1 for separator items and 0 for anything else.
+ */
+ @Override
+ public int getItemViewType(int position) {
+ final AppItem item = mItems.get(position);
+ if (item.category == AppItem.CATEGORY_APP_TITLE) {
+ return 1;
+ } else {
+ return 0;
}
+ }
- final Context context = parent.getContext();
+ @Override
+ public boolean areAllItemsEnabled() {
+ return false;
+ }
- final TextView text1 = (TextView) convertView.findViewById(android.R.id.text1);
- final ProgressBar progress = (ProgressBar) convertView.findViewById(
- android.R.id.progress);
+ @Override
+ public boolean isEnabled(int position) {
+ if (position > mItems.size()) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ return getItemViewType(position) == 0;
+ }
- // kick off async load of app details
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
final AppItem item = mItems.get(position);
- UidDetailTask.bindView(mProvider, item, convertView);
+ if (getItemViewType(position) == 1) {
+ if (convertView == null) {
+ convertView = inflateCategoryHeader(LayoutInflater.from(parent.getContext()),
+ parent);
+ }
+
+ final TextView title = (TextView) convertView.findViewById(android.R.id.title);
+ title.setText(R.string.data_usage_app);
- if (item.restricted && item.total <= 0) {
- text1.setText(R.string.data_usage_app_restricted);
- progress.setVisibility(View.GONE);
} else {
- text1.setText(Formatter.formatFileSize(context, item.total));
- progress.setVisibility(View.VISIBLE);
- }
+ if (convertView == null) {
+ convertView = LayoutInflater.from(parent.getContext()).inflate(
+ R.layout.data_usage_item, parent, false);
+
+ if (mInsetSide > 0) {
+ convertView.setPaddingRelative(mInsetSide, 0, mInsetSide, 0);
+ }
+ }
+
+ final Context context = parent.getContext();
+
+ final TextView text1 = (TextView) convertView.findViewById(android.R.id.text1);
+ final ProgressBar progress = (ProgressBar) convertView.findViewById(
+ android.R.id.progress);
+
+ // kick off async load of app details
+ UidDetailTask.bindView(mProvider, item, convertView);
+
+ if (item.restricted && item.total <= 0) {
+ text1.setText(R.string.data_usage_app_restricted);
+ progress.setVisibility(View.GONE);
+ } else {
+ text1.setText(Formatter.formatFileSize(context, item.total));
+ progress.setVisibility(View.VISIBLE);
+ }
- final int percentTotal = mLargest != 0 ? (int) (item.total * 100 / mLargest) : 0;
- progress.setProgress(percentTotal);
+ final int percentTotal = mLargest != 0 ? (int) (item.total * 100 / mLargest) : 0;
+ progress.setProgress(percentTotal);
+ }
return convertView;
}
@@ -1617,7 +1757,8 @@ public class DataUsageSummary extends Fragment {
final FragmentTransaction ft = parent.getFragmentManager().beginTransaction();
ft.add(fragment, TAG_APP_DETAILS);
ft.addToBackStack(TAG_APP_DETAILS);
- ft.setBreadCrumbTitle(label);
+ ft.setBreadCrumbTitle(
+ parent.getResources().getString(R.string.data_usage_app_summary_title));
ft.commitAllowingStateLoss();
}
@@ -1649,10 +1790,12 @@ public class DataUsageSummary extends Fragment {
public static void show(DataUsageSummary parent) {
if (!parent.isAdded()) return;
+ final NetworkPolicy policy = parent.mPolicyEditor.getPolicy(parent.mTemplate);
+ if (policy == null) return;
+
final Resources res = parent.getResources();
final CharSequence message;
- final long minLimitBytes = (long) (
- parent.mPolicyEditor.getPolicy(parent.mTemplate).warningBytes * 1.2f);
+ final long minLimitBytes = (long) (policy.warningBytes * 1.2f);
final long limitBytes;
// TODO: customize default limits based on network template
@@ -1926,46 +2069,6 @@ public class DataUsageSummary extends Fragment {
/**
* Dialog to request user confirmation before setting
- * {@link android.provider.Settings.Global#DATA_ROAMING}.
- */
- public static class ConfirmDataRoamingFragment extends DialogFragment {
- public static void show(DataUsageSummary parent) {
- if (!parent.isAdded()) return;
-
- final ConfirmDataRoamingFragment dialog = new ConfirmDataRoamingFragment();
- dialog.setTargetFragment(parent, 0);
- dialog.show(parent.getFragmentManager(), TAG_CONFIRM_DATA_ROAMING);
- }
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- final Context context = getActivity();
-
- final AlertDialog.Builder builder = new AlertDialog.Builder(context);
- builder.setTitle(R.string.roaming_reenable_title);
- if (Utils.hasMultipleUsers(context)) {
- builder.setMessage(R.string.roaming_warning_multiuser);
- } else {
- builder.setMessage(R.string.roaming_warning);
- }
-
- builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
- if (target != null) {
- target.setDataRoaming(true);
- }
- }
- });
- builder.setNegativeButton(android.R.string.cancel, null);
-
- return builder.create();
- }
- }
-
- /**
- * Dialog to request user confirmation before setting
* {@link INetworkPolicyManager#setRestrictBackground(boolean)}.
*/
public static class ConfirmRestrictFragment extends DialogFragment {
@@ -2068,56 +2171,6 @@ public class DataUsageSummary extends Fragment {
}
/**
- * Dialog to inform user about changing auto-sync setting
- */
- public static class ConfirmAutoSyncChangeFragment extends DialogFragment {
- private static final String SAVE_ENABLING = "enabling";
- private boolean mEnabling;
-
- public static void show(DataUsageSummary parent, boolean enabling) {
- if (!parent.isAdded()) return;
-
- final ConfirmAutoSyncChangeFragment dialog = new ConfirmAutoSyncChangeFragment();
- dialog.mEnabling = enabling;
- dialog.setTargetFragment(parent, 0);
- dialog.show(parent.getFragmentManager(), TAG_CONFIRM_AUTO_SYNC_CHANGE);
- }
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- final Context context = getActivity();
- if (savedInstanceState != null) {
- mEnabling = savedInstanceState.getBoolean(SAVE_ENABLING);
- }
-
- final AlertDialog.Builder builder = new AlertDialog.Builder(context);
- if (!mEnabling) {
- builder.setTitle(R.string.data_usage_auto_sync_off_dialog_title);
- builder.setMessage(R.string.data_usage_auto_sync_off_dialog);
- } else {
- builder.setTitle(R.string.data_usage_auto_sync_on_dialog_title);
- builder.setMessage(R.string.data_usage_auto_sync_on_dialog);
- }
-
- builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- ContentResolver.setMasterSyncAutomatically(mEnabling);
- }
- });
- builder.setNegativeButton(android.R.string.cancel, null);
-
- return builder.create();
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putBoolean(SAVE_ENABLING, mEnabling);
- }
- }
-
- /**
* Compute default tab that should be selected, based on
* {@link NetworkPolicyManager#EXTRA_NETWORK_TEMPLATE} extra.
*/
@@ -2177,6 +2230,7 @@ public class DataUsageSummary extends Fragment {
if (detail != null) {
icon.setImageDrawable(detail.icon);
title.setText(detail.label);
+ title.setContentDescription(detail.contentDescription);
} else {
icon.setImageDrawable(null);
title.setText(null);
@@ -2286,12 +2340,12 @@ public class DataUsageSummary extends Fragment {
return view;
}
- private static View inflateAppTitle(
- LayoutInflater inflater, ViewGroup root, CharSequence label) {
- final TextView view = (TextView) inflater.inflate(
- R.layout.data_usage_app_title, root, false);
- view.setText(label);
- return view;
+ private static View inflateCategoryHeader(LayoutInflater inflater, ViewGroup root) {
+ final TypedArray a = inflater.getContext().obtainStyledAttributes(null,
+ com.android.internal.R.styleable.Preference,
+ com.android.internal.R.attr.preferenceCategoryStyle, 0);
+ final int resId = a.getResourceId(com.android.internal.R.styleable.Preference_layout, 0);
+ return inflater.inflate(resId, root, false);
}
/**
@@ -2388,4 +2442,47 @@ public class DataUsageSummary extends Fragment {
summary.setVisibility(View.VISIBLE);
summary.setText(string);
}
+
+ /**
+ * For search
+ */
+ public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider() {
+ @Override
+ public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
+ final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>();
+
+ final Resources res = context.getResources();
+
+ // Add fragment title
+ SearchIndexableRaw data = new SearchIndexableRaw(context);
+ data.title = res.getString(R.string.data_usage_summary_title);
+ data.screenTitle = res.getString(R.string.data_usage_summary_title);
+ result.add(data);
+
+ // Mobile data
+ data = new SearchIndexableRaw(context);
+ data.key = DATA_USAGE_ENABLE_MOBILE_KEY;
+ data.title = res.getString(R.string.data_usage_enable_mobile);
+ data.screenTitle = res.getString(R.string.data_usage_summary_title);
+ result.add(data);
+
+ // Set mobile data limit
+ data = new SearchIndexableRaw(context);
+ data.key = DATA_USAGE_DISABLE_MOBILE_LIMIT_KEY;
+ data.title = res.getString(R.string.data_usage_disable_mobile_limit);
+ data.screenTitle = res.getString(R.string.data_usage_summary_title);
+ result.add(data);
+
+ // Data usage cycle
+ data = new SearchIndexableRaw(context);
+ data.key = DATA_USAGE_CYCLE_KEY;
+ data.title = res.getString(R.string.data_usage_cycle);
+ data.screenTitle = res.getString(R.string.data_usage_summary_title);
+ result.add(data);
+
+ return result;
+ }
+ };
+
}
diff --git a/src/com/android/settings/DateTimeSettings.java b/src/com/android/settings/DateTimeSettings.java
index f02f838..8eb9c52 100644
--- a/src/com/android/settings/DateTimeSettings.java
+++ b/src/com/android/settings/DateTimeSettings.java
@@ -16,6 +16,7 @@
package com.android.settings;
+import android.app.admin.DevicePolicyManager;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.DatePickerDialog;
@@ -28,7 +29,6 @@ import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Bundle;
-import android.os.SystemClock;
import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
import android.preference.Preference;
@@ -42,7 +42,6 @@ import android.text.format.DateFormat;
import android.view.View;
import android.widget.DatePicker;
import android.widget.TimePicker;
-
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
@@ -91,12 +90,23 @@ public class DateTimeSettings extends SettingsPreferenceFragment
boolean autoTimeEnabled = getAutoState(Settings.Global.AUTO_TIME);
boolean autoTimeZoneEnabled = getAutoState(Settings.Global.AUTO_TIME_ZONE);
+ mAutoTimePref = (CheckBoxPreference) findPreference(KEY_AUTO_TIME);
+
+ DevicePolicyManager dpm = (DevicePolicyManager) getSystemService(Context
+ .DEVICE_POLICY_SERVICE);
+ if (dpm.getAutoTimeRequired()) {
+ mAutoTimePref.setEnabled(false);
+
+ // If Settings.Global.AUTO_TIME is false it will be set to true
+ // by the device policy manager very soon.
+ // Note that this app listens to that change.
+ }
+
Intent intent = getActivity().getIntent();
boolean isFirstRun = intent.getBooleanExtra(EXTRA_IS_FIRST_RUN, false);
mDummyDate = Calendar.getInstance();
- mAutoTimePref = (CheckBoxPreference) findPreference(KEY_AUTO_TIME);
mAutoTimePref.setChecked(autoTimeEnabled);
mAutoTimeZonePref = (CheckBoxPreference) findPreference(KEY_AUTO_TIME_ZONE);
// Override auto-timezone if it's a wifi-only device or if we're still in setup wizard.
@@ -308,9 +318,10 @@ public class DateTimeSettings extends SettingsPreferenceFragment
removeDialog(DIALOG_TIMEPICKER);
showDialog(DIALOG_TIMEPICKER);
} else if (preference == mTime24Pref) {
- set24Hour(((CheckBoxPreference)mTime24Pref).isChecked());
+ final boolean is24Hour = ((CheckBoxPreference)mTime24Pref).isChecked();
+ set24Hour(is24Hour);
updateTimeAndDateDisplay(getActivity());
- timeUpdated();
+ timeUpdated(is24Hour);
}
return super.onPreferenceTreeClick(preferenceScreen, preference);
}
@@ -321,8 +332,9 @@ public class DateTimeSettings extends SettingsPreferenceFragment
updateTimeAndDateDisplay(getActivity());
}
- private void timeUpdated() {
+ private void timeUpdated(boolean is24Hour) {
Intent timeChanged = new Intent(Intent.ACTION_TIME_CHANGED);
+ timeChanged.putExtra(Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT, is24Hour);
getActivity().sendBroadcast(timeChanged);
}
diff --git a/src/com/android/settings/DevelopmentSettings.java b/src/com/android/settings/DevelopmentSettings.java
index f3a22ca..f5704c1 100644
--- a/src/com/android/settings/DevelopmentSettings.java
+++ b/src/com/android/settings/DevelopmentSettings.java
@@ -16,7 +16,6 @@
package com.android.settings;
-import android.app.ActionBar;
import android.app.Activity;
import android.app.ActivityManagerNative;
import android.app.AlertDialog;
@@ -27,55 +26,58 @@ import android.bluetooth.BluetoothAdapter;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.hardware.usb.IUsbManager;
+import android.net.wifi.WifiManager;
import android.os.AsyncTask;
import android.os.BatteryManager;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
-import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StrictMode;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.os.UserManager;
import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
+import android.provider.SearchIndexableResource;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
-import android.view.Gravity;
import android.view.HardwareRenderer;
import android.view.IWindowManager;
import android.view.View;
-import android.widget.CompoundButton;
+import android.view.accessibility.AccessibilityManager;
import android.widget.Switch;
import android.widget.TextView;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.search.Indexable;
+import com.android.settings.widget.SwitchBar;
import dalvik.system.VMRuntime;
-import java.io.File;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
/*
* Displays preferences for application developers.
*/
-public class DevelopmentSettings extends RestrictedSettingsFragment
+public class DevelopmentSettings extends SettingsPreferenceFragment
implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener,
- OnPreferenceChangeListener, CompoundButton.OnCheckedChangeListener {
+ OnPreferenceChangeListener, SwitchBar.OnSwitchChangeListener, Indexable {
private static final String TAG = "DevelopmentSettings";
/**
@@ -93,8 +95,7 @@ public class DevelopmentSettings extends RestrictedSettingsFragment
private static final String ENABLE_TERMINAL = "enable_terminal";
private static final String KEEP_SCREEN_ON = "keep_screen_on";
private static final String BT_HCI_SNOOP_LOG = "bt_hci_snoop_log";
- private static final String SELECT_RUNTIME_KEY = "select_runtime";
- private static final String SELECT_RUNTIME_PROPERTY = "persist.sys.dalvik.vm.lib";
+ private static final String ENABLE_OEM_UNLOCK = "oem_unlock_enable";
private static final String ALLOW_MOCK_LOCATION = "allow_mock_location";
private static final String HDCP_CHECKING_KEY = "hdcp_checking";
private static final String HDCP_CHECKING_PROPERTY = "persist.sys.hdcp_checking";
@@ -108,11 +109,16 @@ public class DevelopmentSettings extends RestrictedSettingsFragment
private static final String DEBUG_APP_KEY = "debug_app";
private static final String WAIT_FOR_DEBUGGER_KEY = "wait_for_debugger";
private static final String VERIFY_APPS_OVER_USB_KEY = "verify_apps_over_usb";
+ private static final String DEBUG_VIEW_ATTRIBUTES = "debug_view_attributes";
private static final String STRICT_MODE_KEY = "strict_mode";
private static final String POINTER_LOCATION_KEY = "pointer_location";
private static final String SHOW_TOUCHES_KEY = "show_touches";
private static final String SHOW_SCREEN_UPDATES_KEY = "show_screen_updates";
private static final String DISABLE_OVERLAYS_KEY = "disable_overlays";
+ private static final String SIMULATE_COLOR_SPACE = "simulate_color_space";
+ private static final String USE_NUPLAYER_KEY = "use_nuplayer";
+ private static final String USB_AUDIO_KEY = "usb_audio";
+ private static final String USE_AWESOMEPLAYER_PROPERTY = "persist.sys.media.use-awesome";
private static final String SHOW_CPU_USAGE_KEY = "show_cpu_usage";
private static final String FORCE_HARDWARE_UI_KEY = "force_hw_ui";
private static final String FORCE_MSAA_KEY = "force_msaa";
@@ -130,6 +136,12 @@ public class DevelopmentSettings extends RestrictedSettingsFragment
private static final String DEBUG_DEBUGGING_CATEGORY_KEY = "debug_debugging_category";
private static final String DEBUG_APPLICATIONS_CATEGORY_KEY = "debug_applications_category";
private static final String WIFI_DISPLAY_CERTIFICATION_KEY = "wifi_display_certification";
+ private static final String WIFI_VERBOSE_LOGGING_KEY = "wifi_verbose_logging";
+ private static final String WIFI_AGGRESSIVE_HANDOVER_KEY = "wifi_aggressive_handover";
+ private static final String WIFI_ALLOW_SCAN_WITH_TRAFFIC_KEY = "wifi_allow_scan_with_traffic";
+ private static final String SELECT_LOGD_SIZE_KEY = "select_logd_size";
+ private static final String SELECT_LOGD_SIZE_PROPERTY = "persist.logd.size";
+ private static final String SELECT_LOGD_DEFAULT_SIZE_PROPERTY = "ro.logd.size";
private static final String OPENGL_TRACES_KEY = "enable_opengl_traces";
@@ -139,6 +151,8 @@ public class DevelopmentSettings extends RestrictedSettingsFragment
private static final String SHOW_ALL_ANRS_KEY = "show_all_anrs";
+ private static final String PROCESS_STATS = "proc_stats";
+
private static final String TAG_CONFIRM_ENFORCE = "confirm_enforce";
private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
@@ -147,11 +161,17 @@ public class DevelopmentSettings extends RestrictedSettingsFragment
private static final int RESULT_DEBUG_APP = 1000;
+ private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
+
+ private static String DEFAULT_LOG_RING_BUFFER_SIZE_IN_BYTES = "262144"; // 256K
+
private IWindowManager mWindowManager;
private IBackupManager mBackupManager;
private DevicePolicyManager mDpm;
+ private UserManager mUm;
+ private WifiManager mWifiManager;
- private Switch mEnabledSwitch;
+ private SwitchBar mSwitchBar;
private boolean mLastEnabledState;
private boolean mHaveDebugSettings;
private boolean mDontPokeProperties;
@@ -163,15 +183,20 @@ public class DevelopmentSettings extends RestrictedSettingsFragment
private CheckBoxPreference mBugreportInPower;
private CheckBoxPreference mKeepScreenOn;
private CheckBoxPreference mBtHciSnoopLog;
+ private CheckBoxPreference mEnableOemUnlock;
private CheckBoxPreference mAllowMockLocation;
- private PreferenceScreen mPassword;
+ private CheckBoxPreference mDebugViewAttributes;
+ private PreferenceScreen mPassword;
private String mDebugApp;
private Preference mDebugAppPref;
private CheckBoxPreference mWaitForDebugger;
private CheckBoxPreference mVerifyAppsOverUsb;
private CheckBoxPreference mWifiDisplayCertification;
+ private CheckBoxPreference mWifiVerboseLogging;
+ private CheckBoxPreference mWifiAggressiveHandover;
+ private CheckBoxPreference mWifiAllowScansWithTraffic;
private CheckBoxPreference mStrictMode;
private CheckBoxPreference mPointerLocation;
private CheckBoxPreference mShowTouches;
@@ -185,6 +210,7 @@ public class DevelopmentSettings extends RestrictedSettingsFragment
private CheckBoxPreference mDebugLayout;
private CheckBoxPreference mForceRtlLayout;
private ListPreference mDebugHwOverdraw;
+ private ListPreference mLogdSize;
private ListPreference mTrackFrameTime;
private ListPreference mShowNonRectClip;
private ListPreference mWindowAnimationScale;
@@ -193,29 +219,31 @@ public class DevelopmentSettings extends RestrictedSettingsFragment
private ListPreference mOverlayDisplayDevices;
private ListPreference mOpenGLTraces;
+ private ListPreference mSimulateColorSpace;
+
+ private CheckBoxPreference mUseNuplayer;
+ private CheckBoxPreference mUSBAudio;
private CheckBoxPreference mImmediatelyDestroyActivities;
+
private ListPreference mAppProcessLimit;
private CheckBoxPreference mShowAllANRs;
+ private PreferenceScreen mProcessStats;
private final ArrayList<Preference> mAllPrefs = new ArrayList<Preference>();
+
private final ArrayList<CheckBoxPreference> mResetCbPrefs
= new ArrayList<CheckBoxPreference>();
private final HashSet<Preference> mDisabledPrefs = new HashSet<Preference>();
-
// To track whether a confirmation dialog was clicked.
private boolean mDialogClicked;
private Dialog mEnableDialog;
private Dialog mAdbDialog;
- private Dialog mAdbKeysDialog;
+ private Dialog mAdbKeysDialog;
private boolean mUnavailable;
- public DevelopmentSettings() {
- super(RESTRICTIONS_PIN_SET);
- }
-
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
@@ -224,8 +252,12 @@ public class DevelopmentSettings extends RestrictedSettingsFragment
mBackupManager = IBackupManager.Stub.asInterface(
ServiceManager.getService(Context.BACKUP_SERVICE));
mDpm = (DevicePolicyManager)getActivity().getSystemService(Context.DEVICE_POLICY_SERVICE);
+ mUm = (UserManager) getSystemService(Context.USER_SERVICE);
+
+ mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
- if (android.os.Process.myUserHandle().getIdentifier() != UserHandle.USER_OWNER) {
+ if (android.os.Process.myUserHandle().getIdentifier() != UserHandle.USER_OWNER
+ || mUm.hasUserRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES)) {
mUnavailable = true;
setPreferenceScreen(new PreferenceScreen(getActivity(), null));
return;
@@ -243,6 +275,7 @@ public class DevelopmentSettings extends RestrictedSettingsFragment
debugDebuggingCategory.removePreference(mClearAdbKeys);
}
}
+ mAllPrefs.add(mClearAdbKeys);
mEnableTerminal = findAndInitCheckboxPref(ENABLE_TERMINAL);
if (!isPackageInstalled(getActivity(), TERMINAL_APP_PACKAGE)) {
debugDebuggingCategory.removePreference(mEnableTerminal);
@@ -253,10 +286,17 @@ public class DevelopmentSettings extends RestrictedSettingsFragment
mBugreportInPower = findAndInitCheckboxPref(BUGREPORT_IN_POWER_KEY);
mKeepScreenOn = findAndInitCheckboxPref(KEEP_SCREEN_ON);
mBtHciSnoopLog = findAndInitCheckboxPref(BT_HCI_SNOOP_LOG);
+ mEnableOemUnlock = findAndInitCheckboxPref(ENABLE_OEM_UNLOCK);
+ if (!showEnableOemUnlockPreference()) {
+ removePreference(mEnableOemUnlock);
+ mEnableOemUnlock = null;
+ }
mAllowMockLocation = findAndInitCheckboxPref(ALLOW_MOCK_LOCATION);
+ mDebugViewAttributes = findAndInitCheckboxPref(DEBUG_VIEW_ATTRIBUTES);
mPassword = (PreferenceScreen) findPreference(LOCAL_BACKUP_PASSWORD);
mAllPrefs.add(mPassword);
+
if (!android.os.Process.myUserHandle().equals(UserHandle.OWNER)) {
disableForUser(mEnableAdb);
disableForUser(mClearAdbKeys);
@@ -291,16 +331,25 @@ public class DevelopmentSettings extends RestrictedSettingsFragment
mForceRtlLayout = findAndInitCheckboxPref(FORCE_RTL_LAYOUT_KEY);
mDebugHwOverdraw = addListPreference(DEBUG_HW_OVERDRAW_KEY);
mWifiDisplayCertification = findAndInitCheckboxPref(WIFI_DISPLAY_CERTIFICATION_KEY);
+ mWifiVerboseLogging = findAndInitCheckboxPref(WIFI_VERBOSE_LOGGING_KEY);
+ mWifiAggressiveHandover = findAndInitCheckboxPref(WIFI_AGGRESSIVE_HANDOVER_KEY);
+ mWifiAllowScansWithTraffic = findAndInitCheckboxPref(WIFI_ALLOW_SCAN_WITH_TRAFFIC_KEY);
+ mLogdSize = addListPreference(SELECT_LOGD_SIZE_KEY);
+
mWindowAnimationScale = addListPreference(WINDOW_ANIMATION_SCALE_KEY);
mTransitionAnimationScale = addListPreference(TRANSITION_ANIMATION_SCALE_KEY);
mAnimatorDurationScale = addListPreference(ANIMATOR_DURATION_SCALE_KEY);
mOverlayDisplayDevices = addListPreference(OVERLAY_DISPLAY_DEVICES_KEY);
mOpenGLTraces = addListPreference(OPENGL_TRACES_KEY);
+ mSimulateColorSpace = addListPreference(SIMULATE_COLOR_SPACE);
+ mUseNuplayer = findAndInitCheckboxPref(USE_NUPLAYER_KEY);
+ mUSBAudio = findAndInitCheckboxPref(USB_AUDIO_KEY);
mImmediatelyDestroyActivities = (CheckBoxPreference) findPreference(
IMMEDIATELY_DESTROY_ACTIVITIES_KEY);
mAllPrefs.add(mImmediatelyDestroyActivities);
mResetCbPrefs.add(mImmediatelyDestroyActivities);
+
mAppProcessLimit = addListPreference(APP_PROCESS_LIMIT_KEY);
mShowAllANRs = (CheckBoxPreference) findPreference(
@@ -308,17 +357,14 @@ public class DevelopmentSettings extends RestrictedSettingsFragment
mAllPrefs.add(mShowAllANRs);
mResetCbPrefs.add(mShowAllANRs);
- Preference selectRuntime = findPreference(SELECT_RUNTIME_KEY);
- if (selectRuntime != null) {
- mAllPrefs.add(selectRuntime);
- filterRuntimeOptions(selectRuntime);
- }
-
Preference hdcpChecking = findPreference(HDCP_CHECKING_KEY);
if (hdcpChecking != null) {
mAllPrefs.add(hdcpChecking);
removePreferenceForProduction(hdcpChecking);
}
+
+ mProcessStats = (PreferenceScreen) findPreference(PROCESS_STATS);
+ mAllPrefs.add(mProcessStats);
}
private ListPreference addListPreference(String prefKey) {
@@ -349,37 +395,15 @@ public class DevelopmentSettings extends RestrictedSettingsFragment
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
- final Activity activity = getActivity();
- mEnabledSwitch = new Switch(activity);
+ final SettingsActivity activity = (SettingsActivity) getActivity();
- final int padding = activity.getResources().getDimensionPixelSize(
- R.dimen.action_bar_switch_padding);
- mEnabledSwitch.setPaddingRelative(0, 0, padding, 0);
- if (mUnavailable) {
- mEnabledSwitch.setEnabled(false);
+ mSwitchBar = activity.getSwitchBar();
+ if (mUnavailable) {
+ mSwitchBar.setEnabled(false);
return;
}
- mEnabledSwitch.setOnCheckedChangeListener(this);
- }
- @Override
- public void onStart() {
- super.onStart();
- final Activity activity = getActivity();
- activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
- ActionBar.DISPLAY_SHOW_CUSTOM);
- activity.getActionBar().setCustomView(mEnabledSwitch, new ActionBar.LayoutParams(
- ActionBar.LayoutParams.WRAP_CONTENT,
- ActionBar.LayoutParams.WRAP_CONTENT,
- Gravity.CENTER_VERTICAL | Gravity.END));
- }
-
- @Override
- public void onStop() {
- super.onStop();
- final Activity activity = getActivity();
- activity.getActionBar().setDisplayOptions(0, ActionBar.DISPLAY_SHOW_CUSTOM);
- activity.getActionBar().setCustomView(null);
+ mSwitchBar.addOnSwitchChangeListener(this);
}
private boolean removePreferenceForProduction(Preference preference) {
@@ -430,7 +454,7 @@ public class DevelopmentSettings extends RestrictedSettingsFragment
final ContentResolver cr = getActivity().getContentResolver();
mLastEnabledState = Settings.Global.getInt(cr,
Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
- mEnabledSwitch.setChecked(mLastEnabledState);
+ mSwitchBar.setChecked(mLastEnabledState);
setPrefsEnabledState(mLastEnabledState);
if (mHaveDebugSettings && !mLastEnabledState) {
@@ -441,9 +465,21 @@ public class DevelopmentSettings extends RestrictedSettingsFragment
Settings.Global.putInt(getActivity().getContentResolver(),
Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1);
mLastEnabledState = true;
- mEnabledSwitch.setChecked(mLastEnabledState);
+ mSwitchBar.setChecked(mLastEnabledState);
setPrefsEnabledState(mLastEnabledState);
}
+ mSwitchBar.show();
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+
+ if (mUnavailable) {
+ return;
+ }
+ mSwitchBar.removeOnSwitchChangeListener(this);
+ mSwitchBar.hide();
}
void updateCheckBox(CheckBoxPreference checkBox, boolean value) {
@@ -468,9 +504,13 @@ public class DevelopmentSettings extends RestrictedSettingsFragment
Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0) != 0);
updateCheckBox(mBtHciSnoopLog, Settings.Secure.getInt(cr,
Settings.Secure.BLUETOOTH_HCI_LOG, 0) != 0);
+ if (mEnableOemUnlock != null) {
+ updateCheckBox(mEnableOemUnlock, Utils.isOemUnlockEnabled(getActivity()));
+ }
updateCheckBox(mAllowMockLocation, Settings.Secure.getInt(cr,
Settings.Secure.ALLOW_MOCK_LOCATION, 0) != 0);
- updateRuntimeValue();
+ updateCheckBox(mDebugViewAttributes, Settings.Global.getInt(cr,
+ Settings.Global.DEBUG_VIEW_ATTRIBUTES, 0) != 0);
updateHdcpValues();
updatePasswordSummary();
updateDebuggerOptions();
@@ -496,7 +536,14 @@ public class DevelopmentSettings extends RestrictedSettingsFragment
updateVerifyAppsOverUsbOptions();
updateBugreportOptions();
updateForceRtlOptions();
+ updateLogdSizeValues();
updateWifiDisplayCertificationOptions();
+ updateWifiVerboseLoggingOptions();
+ updateWifiAggressiveHandoverOptions();
+ updateWifiAllowScansWithTrafficOptions();
+ updateSimulateColorSpace();
+ updateUseNuplayerOptions();
+ updateUSBAudioOptions();
}
private void resetDangerousOptions() {
@@ -509,9 +556,14 @@ public class DevelopmentSettings extends RestrictedSettingsFragment
}
}
resetDebuggerOptions();
+ writeLogdSizeOption(null);
writeAnimationScaleOption(0, mWindowAnimationScale, null);
writeAnimationScaleOption(1, mTransitionAnimationScale, null);
writeAnimationScaleOption(2, mAnimatorDurationScale, null);
+ // Only poke the color space setting if we control it.
+ if (usingDevelopmentColorSpace()) {
+ writeSimulateColorSpace(-1);
+ }
writeOverlayDisplayDevicesOptions(null);
writeAppProcessLimitOptions(null);
mHaveDebugSettings = false;
@@ -520,53 +572,6 @@ public class DevelopmentSettings extends RestrictedSettingsFragment
pokeSystemProperties();
}
- void filterRuntimeOptions(Preference selectRuntime) {
- ListPreference pref = (ListPreference) selectRuntime;
- ArrayList<String> validValues = new ArrayList<String>();
- ArrayList<String> validSummaries = new ArrayList<String>();
- String[] values = getResources().getStringArray(R.array.select_runtime_values);
- String[] summaries = getResources().getStringArray(R.array.select_runtime_summaries);
- for (int i = 0; i < values.length; i++) {
- String value = values[i];
- String summary = summaries[i];
- if (new File("/system/lib/" + value).exists()) {
- validValues.add(value);
- validSummaries.add(summary);
- }
- }
- int count = validValues.size();
- if (count <= 1) {
- // no choices, so remove preference
- removePreference(selectRuntime);
- } else {
- pref.setEntryValues(validValues.toArray(new String[count]));
- pref.setEntries(validSummaries.toArray(new String[count]));
- }
- }
-
- private String currentRuntimeValue() {
- return SystemProperties.get(SELECT_RUNTIME_PROPERTY, VMRuntime.getRuntime().vmLibrary());
- }
-
- private void updateRuntimeValue() {
- ListPreference selectRuntime = (ListPreference) findPreference(SELECT_RUNTIME_KEY);
- if (selectRuntime != null) {
- String currentValue = currentRuntimeValue();
- String[] values = getResources().getStringArray(R.array.select_runtime_values);
- String[] summaries = getResources().getStringArray(R.array.select_runtime_summaries);
- int index = 0;
- for (int i = 0; i < values.length; i++) {
- if (currentValue.equals(values[i])) {
- index = i;
- break;
- }
- }
- selectRuntime.setValue(values[index]);
- selectRuntime.setSummary(summaries[index]);
- selectRuntime.setOnPreferenceChangeListener(this);
- }
- }
-
private void updateHdcpValues() {
ListPreference hdcpChecking = (ListPreference) findPreference(HDCP_CHECKING_KEY);
if (hdcpChecking != null) {
@@ -682,6 +687,10 @@ public class DevelopmentSettings extends RestrictedSettingsFragment
Settings.Global.PACKAGE_VERIFIER_SETTING_VISIBLE, 1) > 0;
}
+ private static boolean showEnableOemUnlockPreference() {
+ return !SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP).equals("");
+ }
+
private void updateBugreportOptions() {
if ("user".equals(Build.TYPE)) {
final ContentResolver resolver = getActivity().getContentResolver();
@@ -933,6 +942,83 @@ public class DevelopmentSettings extends RestrictedSettingsFragment
pokeSystemProperties();
}
+ private void updateSimulateColorSpace() {
+ final ContentResolver cr = getContentResolver();
+ final boolean enabled = Settings.Secure.getInt(
+ cr, Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, 0) != 0;
+ if (enabled) {
+ final String mode = Integer.toString(Settings.Secure.getInt(
+ cr, Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER,
+ AccessibilityManager.DALTONIZER_DISABLED));
+ mSimulateColorSpace.setValue(mode);
+ final int index = mSimulateColorSpace.findIndexOfValue(mode);
+ if (index < 0) {
+ // We're using a mode controlled by accessibility preferences.
+ mSimulateColorSpace.setSummary(getString(R.string.daltonizer_type_overridden,
+ getString(R.string.accessibility_display_daltonizer_preference_title)));
+ } else {
+ mSimulateColorSpace.setSummary("%s");
+ }
+ } else {
+ mSimulateColorSpace.setValue(
+ Integer.toString(AccessibilityManager.DALTONIZER_DISABLED));
+ }
+ }
+
+ /**
+ * @return <code>true</code> if the color space preference is currently
+ * controlled by development settings
+ */
+ private boolean usingDevelopmentColorSpace() {
+ final ContentResolver cr = getContentResolver();
+ final boolean enabled = Settings.Secure.getInt(
+ cr, Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, 0) != 0;
+ if (enabled) {
+ final String mode = Integer.toString(Settings.Secure.getInt(
+ cr, Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER,
+ AccessibilityManager.DALTONIZER_DISABLED));
+ final int index = mSimulateColorSpace.findIndexOfValue(mode);
+ if (index >= 0) {
+ // We're using a mode controlled by developer preferences.
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void writeSimulateColorSpace(Object value) {
+ final ContentResolver cr = getContentResolver();
+ final int newMode = Integer.parseInt(value.toString());
+ if (newMode < 0) {
+ Settings.Secure.putInt(cr, Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, 0);
+ } else {
+ Settings.Secure.putInt(cr, Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, 1);
+ Settings.Secure.putInt(cr, Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER, newMode);
+ }
+ }
+
+ private void updateUseNuplayerOptions() {
+ updateCheckBox(
+ mUseNuplayer, !SystemProperties.getBoolean(USE_AWESOMEPLAYER_PROPERTY, false));
+ }
+
+ private void writeUseNuplayerOptions() {
+ SystemProperties.set(
+ USE_AWESOMEPLAYER_PROPERTY, mUseNuplayer.isChecked() ? "false" : "true");
+ pokeSystemProperties();
+ }
+
+ private void updateUSBAudioOptions() {
+ updateCheckBox(mUSBAudio, Settings.Secure.getInt(getContentResolver(),
+ Settings.Secure.USB_AUDIO_AUTOMATIC_ROUTING_DISABLED, 0) != 0);
+ }
+
+ private void writeUSBAudioOptions() {
+ Settings.Secure.putInt(getContentResolver(),
+ Settings.Secure.USB_AUDIO_AUTOMATIC_ROUTING_DISABLED,
+ mUSBAudio.isChecked() ? 1 : 0);
+ }
+
private void updateForceRtlOptions() {
updateCheckBox(mForceRtlLayout, Settings.Global.getInt(getActivity().getContentResolver(),
Settings.Global.DEVELOPMENT_FORCE_RTL, 0) != 0);
@@ -958,6 +1044,82 @@ public class DevelopmentSettings extends RestrictedSettingsFragment
mWifiDisplayCertification.isChecked() ? 1 : 0);
}
+ private void updateWifiVerboseLoggingOptions() {
+ boolean enabled = mWifiManager.getVerboseLoggingLevel() > 0;
+ updateCheckBox(mWifiVerboseLogging, enabled);
+ }
+
+ private void writeWifiVerboseLoggingOptions() {
+ mWifiManager.enableVerboseLogging(mWifiVerboseLogging.isChecked() ? 1 : 0);
+ }
+
+ private void updateWifiAggressiveHandoverOptions() {
+ boolean enabled = mWifiManager.getAggressiveHandover() > 0;
+ updateCheckBox(mWifiAggressiveHandover, enabled);
+ }
+
+ private void writeWifiAggressiveHandoverOptions() {
+ mWifiManager.enableAggressiveHandover(mWifiAggressiveHandover.isChecked() ? 1 : 0);
+ }
+
+ private void updateWifiAllowScansWithTrafficOptions() {
+ boolean enabled = mWifiManager.getAllowScansWithTraffic() > 0;
+ updateCheckBox(mWifiAllowScansWithTraffic, enabled);
+ }
+
+ private void writeWifiAllowScansWithTrafficOptions() {
+ mWifiManager.setAllowScansWithTraffic(mWifiAllowScansWithTraffic.isChecked() ? 1 : 0);
+ }
+
+ private void updateLogdSizeValues() {
+ if (mLogdSize != null) {
+ String currentValue = SystemProperties.get(SELECT_LOGD_SIZE_PROPERTY);
+ if (currentValue == null) {
+ currentValue = SystemProperties.get(SELECT_LOGD_DEFAULT_SIZE_PROPERTY);
+ if (currentValue == null) {
+ currentValue = "256K";
+ }
+ }
+ String[] values = getResources().getStringArray(R.array.select_logd_size_values);
+ String[] titles = getResources().getStringArray(R.array.select_logd_size_titles);
+ if (SystemProperties.get("ro.config.low_ram").equals("true")) {
+ mLogdSize.setEntries(R.array.select_logd_size_lowram_titles);
+ titles = getResources().getStringArray(R.array.select_logd_size_lowram_titles);
+ }
+ String[] summaries = getResources().getStringArray(R.array.select_logd_size_summaries);
+ int index = 1; // punt to second entry if not found
+ for (int i = 0; i < titles.length; i++) {
+ if (currentValue.equals(values[i])
+ || currentValue.equals(titles[i])) {
+ index = i;
+ break;
+ }
+ }
+ mLogdSize.setValue(values[index]);
+ mLogdSize.setSummary(summaries[index]);
+ mLogdSize.setOnPreferenceChangeListener(this);
+ }
+ }
+
+ private void writeLogdSizeOption(Object newValue) {
+ String currentValue = SystemProperties.get(SELECT_LOGD_DEFAULT_SIZE_PROPERTY);
+ if (currentValue != null) {
+ DEFAULT_LOG_RING_BUFFER_SIZE_IN_BYTES = currentValue;
+ }
+ final String size = (newValue != null) ?
+ newValue.toString() : DEFAULT_LOG_RING_BUFFER_SIZE_IN_BYTES;
+ SystemProperties.set(SELECT_LOGD_SIZE_PROPERTY, size);
+ pokeSystemProperties();
+ try {
+ Process p = Runtime.getRuntime().exec("logcat -b all -G " + size);
+ p.waitFor();
+ Log.i(TAG, "Logcat ring buffer sizes set to: " + size);
+ } catch (Exception e) {
+ Log.w(TAG, "Cannot set logcat ring buffer sizes", e);
+ }
+ updateLogdSizeValues();
+ }
+
private void updateCpuUsageOptions() {
updateCheckBox(mShowCpuUsage, Settings.Global.getInt(getActivity().getContentResolver(),
Settings.Global.SHOW_PROCESSES, 0) != 0);
@@ -1116,28 +1278,28 @@ public class DevelopmentSettings extends RestrictedSettingsFragment
}
@Override
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- if (buttonView == mEnabledSwitch) {
- if (isChecked != mLastEnabledState) {
- if (isChecked) {
- mDialogClicked = false;
- if (mEnableDialog != null) dismissDialogs();
- mEnableDialog = new AlertDialog.Builder(getActivity()).setMessage(
- getActivity().getResources().getString(
- R.string.dev_settings_warning_message))
- .setTitle(R.string.dev_settings_warning_title)
- .setIconAttribute(android.R.attr.alertDialogIcon)
- .setPositiveButton(android.R.string.yes, this)
- .setNegativeButton(android.R.string.no, this)
- .show();
- mEnableDialog.setOnDismissListener(this);
- } else {
- resetDangerousOptions();
- Settings.Global.putInt(getActivity().getContentResolver(),
- Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0);
- mLastEnabledState = isChecked;
- setPrefsEnabledState(mLastEnabledState);
- }
+ public void onSwitchChanged(Switch switchView, boolean isChecked) {
+ if (switchView != mSwitchBar.getSwitch()) {
+ return;
+ }
+ if (isChecked != mLastEnabledState) {
+ if (isChecked) {
+ mDialogClicked = false;
+ if (mEnableDialog != null) dismissDialogs();
+ mEnableDialog = new AlertDialog.Builder(getActivity()).setMessage(
+ getActivity().getResources().getString(
+ R.string.dev_settings_warning_message))
+ .setTitle(R.string.dev_settings_warning_title)
+ .setPositiveButton(android.R.string.yes, this)
+ .setNegativeButton(android.R.string.no, this)
+ .show();
+ mEnableDialog.setOnDismissListener(this);
+ } else {
+ resetDangerousOptions();
+ Settings.Global.putInt(getActivity().getContentResolver(),
+ Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0);
+ mLastEnabledState = isChecked;
+ setPrefsEnabledState(mLastEnabledState);
}
}
}
@@ -1168,7 +1330,6 @@ public class DevelopmentSettings extends RestrictedSettingsFragment
mAdbDialog = new AlertDialog.Builder(getActivity()).setMessage(
getActivity().getResources().getString(R.string.adb_warning_message))
.setTitle(R.string.adb_warning_title)
- .setIconAttribute(android.R.attr.alertDialogIcon)
.setPositiveButton(android.R.string.yes, this)
.setNegativeButton(android.R.string.no, this)
.show();
@@ -1200,13 +1361,19 @@ public class DevelopmentSettings extends RestrictedSettingsFragment
Settings.Global.putInt(getActivity().getContentResolver(),
Settings.Global.STAY_ON_WHILE_PLUGGED_IN,
mKeepScreenOn.isChecked() ?
- (BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB) : 0);
+ (BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB) : 0);
} else if (preference == mBtHciSnoopLog) {
writeBtHciSnoopLogOptions();
+ } else if (preference == mEnableOemUnlock) {
+ Utils.setOemUnlockEnabled(getActivity(), mEnableOemUnlock.isChecked());
} else if (preference == mAllowMockLocation) {
Settings.Secure.putInt(getActivity().getContentResolver(),
Settings.Secure.ALLOW_MOCK_LOCATION,
mAllowMockLocation.isChecked() ? 1 : 0);
+ } else if (preference == mDebugViewAttributes) {
+ Settings.Global.putInt(getActivity().getContentResolver(),
+ Settings.Global.DEBUG_VIEW_ATTRIBUTES,
+ mDebugViewAttributes.isChecked() ? 1 : 0);
} else if (preference == mDebugAppPref) {
startActivityForResult(new Intent(getActivity(), AppPicker.class), RESULT_DEBUG_APP);
} else if (preference == mWaitForDebugger) {
@@ -1243,6 +1410,16 @@ public class DevelopmentSettings extends RestrictedSettingsFragment
writeForceRtlOptions();
} else if (preference == mWifiDisplayCertification) {
writeWifiDisplayCertificationOptions();
+ } else if (preference == mWifiVerboseLogging) {
+ writeWifiVerboseLoggingOptions();
+ } else if (preference == mWifiAggressiveHandover) {
+ writeWifiAggressiveHandoverOptions();
+ } else if (preference == mWifiAllowScansWithTraffic) {
+ writeWifiAllowScansWithTrafficOptions();
+ } else if (preference == mUseNuplayer) {
+ writeUseNuplayerOptions();
+ } else if (preference == mUSBAudio) {
+ writeUSBAudioOptions();
} else {
return super.onPreferenceTreeClick(preferenceScreen, preference);
}
@@ -1252,38 +1429,14 @@ public class DevelopmentSettings extends RestrictedSettingsFragment
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
- if (SELECT_RUNTIME_KEY.equals(preference.getKey())) {
- final String oldRuntimeValue = VMRuntime.getRuntime().vmLibrary();
- final String newRuntimeValue = newValue.toString();
- if (!newRuntimeValue.equals(oldRuntimeValue)) {
- final Context context = getActivity();
- final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- builder.setMessage(context.getResources().getString(R.string.select_runtime_warning_message,
- oldRuntimeValue, newRuntimeValue));
- builder.setPositiveButton(android.R.string.ok, new OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- SystemProperties.set(SELECT_RUNTIME_PROPERTY, newRuntimeValue);
- pokeSystemProperties();
- PowerManager pm = (PowerManager)
- context.getSystemService(Context.POWER_SERVICE);
- pm.reboot(null);
- }
- });
- builder.setNegativeButton(android.R.string.cancel, new OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- updateRuntimeValue();
- }
- });
- builder.show();
- }
- return true;
- } else if (HDCP_CHECKING_KEY.equals(preference.getKey())) {
+ if (HDCP_CHECKING_KEY.equals(preference.getKey())) {
SystemProperties.set(HDCP_CHECKING_PROPERTY, newValue.toString());
updateHdcpValues();
pokeSystemProperties();
return true;
+ } else if (preference == mLogdSize) {
+ writeLogdSizeOption(newValue);
+ return true;
} else if (preference == mWindowAnimationScale) {
writeAnimationScaleOption(0, mWindowAnimationScale, newValue);
return true;
@@ -1311,6 +1464,9 @@ public class DevelopmentSettings extends RestrictedSettingsFragment
} else if (preference == mAppProcessLimit) {
writeAppProcessLimitOptions(newValue);
return true;
+ } else if (preference == mSimulateColorSpace) {
+ writeSimulateColorSpace(newValue);
+ return true;
}
return false;
}
@@ -1362,7 +1518,7 @@ public class DevelopmentSettings extends RestrictedSettingsFragment
setPrefsEnabledState(mLastEnabledState);
} else {
// Reset the toggle
- mEnabledSwitch.setChecked(false);
+ mSwitchBar.setChecked(false);
}
}
}
@@ -1376,7 +1532,7 @@ public class DevelopmentSettings extends RestrictedSettingsFragment
mAdbDialog = null;
} else if (dialog == mEnableDialog) {
if (!mDialogClicked) {
- mEnabledSwitch.setChecked(false);
+ mSwitchBar.setChecked(false);
}
mEnableDialog = null;
}
@@ -1429,4 +1585,44 @@ public class DevelopmentSettings extends RestrictedSettingsFragment
return false;
}
}
+
+ /**
+ * For Search.
+ */
+ public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider() {
+
+ private boolean isShowingDeveloperOptions(Context context) {
+ return context.getSharedPreferences(DevelopmentSettings.PREF_FILE,
+ Context.MODE_PRIVATE).getBoolean(
+ DevelopmentSettings.PREF_SHOW,
+ android.os.Build.TYPE.equals("eng"));
+ }
+
+ @Override
+ public List<SearchIndexableResource> getXmlResourcesToIndex(
+ Context context, boolean enabled) {
+
+ if (!isShowingDeveloperOptions(context)) {
+ return null;
+ }
+
+ final SearchIndexableResource sir = new SearchIndexableResource(context);
+ sir.xmlResId = R.xml.development_prefs;
+ return Arrays.asList(sir);
+ }
+
+ @Override
+ public List<String> getNonIndexableKeys(Context context) {
+ if (!isShowingDeveloperOptions(context)) {
+ return null;
+ }
+
+ final List<String> keys = new ArrayList<String>();
+ if (!showEnableOemUnlockPreference()) {
+ keys.add(ENABLE_OEM_UNLOCK);
+ }
+ return keys;
+ }
+ };
}
diff --git a/src/com/android/settings/DeviceAdminAdd.java b/src/com/android/settings/DeviceAdminAdd.java
index 6234038..ed95500 100644
--- a/src/com/android/settings/DeviceAdminAdd.java
+++ b/src/com/android/settings/DeviceAdminAdd.java
@@ -16,6 +16,8 @@
package com.android.settings;
+import android.app.AppOpsManager;
+
import org.xmlpull.v1.XmlPullParserException;
import android.app.Activity;
@@ -30,14 +32,19 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ActivityInfo;
+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.content.res.Resources;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteCallback;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.text.TextUtils.TruncateAt;
+import android.util.EventLog;
import android.util.Log;
import android.view.Display;
import android.view.View;
@@ -50,68 +57,103 @@ import android.widget.TextView;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.HashSet;
import java.util.List;
public class DeviceAdminAdd extends Activity {
static final String TAG = "DeviceAdminAdd";
-
+
static final int DIALOG_WARNING = 1;
private static final int MAX_ADD_MSG_LINES_PORTRAIT = 5;
private static final int MAX_ADD_MSG_LINES_LANDSCAPE = 2;
private static final int MAX_ADD_MSG_LINES = 15;
-
+
Handler mHandler;
-
+
DevicePolicyManager mDPM;
+ AppOpsManager mAppOps;
DeviceAdminInfo mDeviceAdmin;
CharSequence mAddMsgText;
-
+ String mProfileOwnerName;
+
ImageView mAdminIcon;
TextView mAdminName;
TextView mAdminDescription;
TextView mAddMsg;
+ TextView mProfileOwnerWarning;
ImageView mAddMsgExpander;
boolean mAddMsgEllipsized = true;
TextView mAdminWarning;
ViewGroup mAdminPolicies;
Button mActionButton;
Button mCancelButton;
-
+
final ArrayList<View> mAddingPolicies = new ArrayList<View>();
final ArrayList<View> mActivePolicies = new ArrayList<View>();
-
+
boolean mAdding;
boolean mRefreshing;
-
+ boolean mWaitingForRemoveMsg;
+ boolean mAddingProfileOwner;
+ int mCurSysAppOpMode;
+ int mCurToastAppOpMode;
+
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
mHandler = new Handler(getMainLooper());
-
+
mDPM = (DevicePolicyManager)getSystemService(Context.DEVICE_POLICY_SERVICE);
+ mAppOps = (AppOpsManager)getSystemService(Context.APP_OPS_SERVICE);
+ PackageManager packageManager = getPackageManager();
if ((getIntent().getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
Log.w(TAG, "Cannot start ADD_DEVICE_ADMIN as a new task");
finish();
return;
}
-
- ComponentName cn = (ComponentName)getIntent().getParcelableExtra(
+
+ String action = getIntent().getAction();
+ ComponentName who = (ComponentName)getIntent().getParcelableExtra(
DevicePolicyManager.EXTRA_DEVICE_ADMIN);
- if (cn == null) {
- Log.w(TAG, "No component specified in " + getIntent().getAction());
+ if (who == null) {
+ Log.w(TAG, "No component specified in " + action);
finish();
return;
}
+ if (action != null && action.equals(DevicePolicyManager.ACTION_SET_PROFILE_OWNER)) {
+ setResult(RESULT_CANCELED);
+ setFinishOnTouchOutside(true);
+ mAddingProfileOwner = true;
+ mProfileOwnerName =
+ getIntent().getStringExtra(DevicePolicyManager.EXTRA_PROFILE_OWNER_NAME);
+ String callingPackage = getCallingPackage();
+ if (callingPackage == null || !callingPackage.equals(who.getPackageName())) {
+ Log.e(TAG, "Unknown or incorrect caller");
+ finish();
+ return;
+ }
+ try {
+ PackageInfo packageInfo = packageManager.getPackageInfo(callingPackage, 0);
+ if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+ Log.e(TAG, "Cannot set a non-system app as a profile owner");
+ finish();
+ return;
+ }
+ } catch (NameNotFoundException nnfe) {
+ Log.e(TAG, "Cannot find the package " + callingPackage);
+ finish();
+ return;
+ }
+ }
+
ActivityInfo ai;
try {
- ai = getPackageManager().getReceiverInfo(cn, PackageManager.GET_META_DATA);
+ ai = packageManager.getReceiverInfo(who, PackageManager.GET_META_DATA);
} catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "Unable to retrieve device policy " + cn, e);
+ Log.w(TAG, "Unable to retrieve device policy " + who, e);
finish();
return;
}
@@ -119,8 +161,8 @@ public class DeviceAdminAdd extends Activity {
// When activating, make sure the given component name is actually a valid device admin.
// No need to check this when deactivating, because it is safe to deactivate an active
// invalid device admin.
- if (!mDPM.isAdminActive(cn)) {
- List<ResolveInfo> avail = getPackageManager().queryBroadcastReceivers(
+ if (!mDPM.isAdminActive(who)) {
+ List<ResolveInfo> avail = packageManager.queryBroadcastReceivers(
new Intent(DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED),
PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS);
int count = avail == null ? 0 : avail.size();
@@ -144,7 +186,7 @@ public class DeviceAdminAdd extends Activity {
}
}
if (!found) {
- Log.w(TAG, "Request to add invalid device admin: " + cn);
+ Log.w(TAG, "Request to add invalid device admin: " + who);
finish();
return;
}
@@ -155,25 +197,25 @@ public class DeviceAdminAdd extends Activity {
try {
mDeviceAdmin = new DeviceAdminInfo(this, ri);
} catch (XmlPullParserException e) {
- Log.w(TAG, "Unable to retrieve device policy " + cn, e);
+ Log.w(TAG, "Unable to retrieve device policy " + who, e);
finish();
return;
} catch (IOException e) {
- Log.w(TAG, "Unable to retrieve device policy " + cn, e);
+ Log.w(TAG, "Unable to retrieve device policy " + who, e);
finish();
return;
}
-
+
// This admin already exists, an we have two options at this point. If new policy
// bits are set, show the user the new list. If nothing has changed, simply return
// "OK" immediately.
if (DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN.equals(getIntent().getAction())) {
mRefreshing = false;
- if (mDPM.isAdminActive(cn)) {
+ if (mDPM.isAdminActive(who)) {
ArrayList<DeviceAdminInfo.PolicyInfo> newPolicies = mDeviceAdmin.getUsedPolicies();
for (int i = 0; i < newPolicies.size(); i++) {
DeviceAdminInfo.PolicyInfo pi = newPolicies.get(i);
- if (!mDPM.hasGrantedPolicy(cn, pi.ident)) {
+ if (!mDPM.hasGrantedPolicy(who, pi.ident)) {
mRefreshing = true;
break;
}
@@ -186,13 +228,22 @@ public class DeviceAdminAdd extends Activity {
}
}
}
+
+ // If we're trying to add a profile owner and user setup hasn't completed yet, no
+ // need to prompt for permission. Just add and finish.
+ if (mAddingProfileOwner && !mDPM.hasUserSetupCompleted()) {
+ addAndFinish();
+ return;
+ }
+
mAddMsgText = getIntent().getCharSequenceExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION);
setContentView(R.layout.device_admin_add);
-
+
mAdminIcon = (ImageView)findViewById(R.id.admin_icon);
mAdminName = (TextView)findViewById(R.id.admin_name);
mAdminDescription = (TextView)findViewById(R.id.admin_description);
+ mProfileOwnerWarning = (TextView) findViewById(R.id.profile_owner_warning);
mAddMsg = (TextView)findViewById(R.id.add_msg);
mAddMsgExpander = (ImageView) findViewById(R.id.add_msg_expander);
@@ -210,6 +261,8 @@ public class DeviceAdminAdd extends Activity {
mCancelButton = (Button) findViewById(R.id.cancel_button);
mCancelButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
+ EventLog.writeEvent(EventLogTags.EXP_DET_DEVICE_ADMIN_DECLINED_BY_USER,
+ mDeviceAdmin.getActivityInfo().applicationInfo.uid);
finish();
}
});
@@ -217,26 +270,15 @@ public class DeviceAdminAdd extends Activity {
mActionButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if (mAdding) {
- try {
- mDPM.setActiveAdmin(mDeviceAdmin.getComponent(), mRefreshing);
- setResult(Activity.RESULT_OK);
- } catch (RuntimeException e) {
- // Something bad happened... could be that it was
- // already set, though.
- Log.w(TAG, "Exception trying to activate admin "
- + mDeviceAdmin.getComponent(), e);
- if (mDPM.isAdminActive(mDeviceAdmin.getComponent())) {
- setResult(Activity.RESULT_OK);
- }
- }
- finish();
- } else {
+ addAndFinish();
+ } else if (!mWaitingForRemoveMsg) {
try {
// Don't allow the admin to put a dialog up in front
// of us while we interact with the user.
ActivityManagerNative.getDefault().stopAppSwitches();
} catch (RemoteException e) {
}
+ mWaitingForRemoveMsg = true;
mDPM.getRemoveWarning(mDeviceAdmin.getComponent(),
new RemoteCallback(mHandler) {
@Override
@@ -245,32 +287,98 @@ public class DeviceAdminAdd extends Activity {
? bundle.getCharSequence(
DeviceAdminReceiver.EXTRA_DISABLE_WARNING)
: null;
- if (msg == null) {
- try {
- ActivityManagerNative.getDefault().resumeAppSwitches();
- } catch (RemoteException e) {
- }
- mDPM.removeActiveAdmin(mDeviceAdmin.getComponent());
- finish();
- } else {
- Bundle args = new Bundle();
- args.putCharSequence(
- DeviceAdminReceiver.EXTRA_DISABLE_WARNING, msg);
- showDialog(DIALOG_WARNING, args);
- }
+ continueRemoveAction(msg);
}
});
+ // Don't want to wait too long.
+ getWindow().getDecorView().getHandler().postDelayed(new Runnable() {
+ @Override public void run() {
+ continueRemoveAction(null);
+ }
+ }, 2*1000);
}
}
});
}
-
+
+ void addAndFinish() {
+ try {
+ mDPM.setActiveAdmin(mDeviceAdmin.getComponent(), mRefreshing);
+ EventLog.writeEvent(EventLogTags.EXP_DET_DEVICE_ADMIN_ACTIVATED_BY_USER,
+ mDeviceAdmin.getActivityInfo().applicationInfo.uid);
+ setResult(Activity.RESULT_OK);
+ } catch (RuntimeException e) {
+ // Something bad happened... could be that it was
+ // already set, though.
+ Log.w(TAG, "Exception trying to activate admin "
+ + mDeviceAdmin.getComponent(), e);
+ if (mDPM.isAdminActive(mDeviceAdmin.getComponent())) {
+ setResult(Activity.RESULT_OK);
+ }
+ }
+ if (mAddingProfileOwner) {
+ try {
+ mDPM.setProfileOwner(mDeviceAdmin.getComponent(),
+ mProfileOwnerName, UserHandle.myUserId());
+ } catch (RuntimeException re) {
+ setResult(Activity.RESULT_CANCELED);
+ }
+ }
+ finish();
+ }
+
+ void continueRemoveAction(CharSequence msg) {
+ if (!mWaitingForRemoveMsg) {
+ return;
+ }
+ mWaitingForRemoveMsg = false;
+ if (msg == null) {
+ try {
+ ActivityManagerNative.getDefault().resumeAppSwitches();
+ } catch (RemoteException e) {
+ }
+ mDPM.removeActiveAdmin(mDeviceAdmin.getComponent());
+ finish();
+ } else {
+ try {
+ // Continue preventing anything from coming in front.
+ ActivityManagerNative.getDefault().stopAppSwitches();
+ } catch (RemoteException e) {
+ }
+ Bundle args = new Bundle();
+ args.putCharSequence(
+ DeviceAdminReceiver.EXTRA_DISABLE_WARNING, msg);
+ showDialog(DIALOG_WARNING, args);
+ }
+ }
+
@Override
protected void onResume() {
super.onResume();
updateInterface();
+ // As long as we are running, don't let this admin overlay stuff on top of the screen.
+ final int uid = mDeviceAdmin.getActivityInfo().applicationInfo.uid;
+ final String pkg = mDeviceAdmin.getActivityInfo().applicationInfo.packageName;
+ mCurSysAppOpMode = mAppOps.checkOp(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, uid, pkg);
+ mCurToastAppOpMode = mAppOps.checkOp(AppOpsManager.OP_TOAST_WINDOW, uid, pkg);
+ mAppOps.setMode(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, uid, pkg, AppOpsManager.MODE_IGNORED);
+ mAppOps.setMode(AppOpsManager.OP_TOAST_WINDOW, uid, pkg, AppOpsManager.MODE_IGNORED);
}
-
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ // As long as we are running, don't let this admin overlay stuff on top of the screen.
+ final int uid = mDeviceAdmin.getActivityInfo().applicationInfo.uid;
+ final String pkg = mDeviceAdmin.getActivityInfo().applicationInfo.packageName;
+ mAppOps.setMode(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, uid, pkg, mCurSysAppOpMode);
+ mAppOps.setMode(AppOpsManager.OP_TOAST_WINDOW, uid, pkg, mCurToastAppOpMode);
+ try {
+ ActivityManagerNative.getDefault().resumeAppSwitches();
+ } catch (RemoteException e) {
+ }
+ }
+
@Override
protected Dialog onCreateDialog(int id, Bundle args) {
switch (id) {
@@ -282,6 +390,10 @@ public class DeviceAdminAdd extends Activity {
builder.setPositiveButton(R.string.dlg_ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
+ try {
+ ActivityManagerNative.getDefault().resumeAppSwitches();
+ } catch (RemoteException e) {
+ }
mDPM.removeActiveAdmin(mDeviceAdmin.getComponent());
finish();
}
@@ -291,17 +403,17 @@ public class DeviceAdminAdd extends Activity {
}
default:
return super.onCreateDialog(id, args);
-
+
}
}
-
+
static void setViewVisibility(ArrayList<View> views, int visibility) {
final int N = views.size();
for (int i=0; i<N; i++) {
views.get(i).setVisibility(visibility);
}
}
-
+
void updateInterface() {
mAdminIcon.setImageDrawable(mDeviceAdmin.loadIcon(getPackageManager()));
mAdminName.setText(mDeviceAdmin.loadLabel(getPackageManager()));
@@ -312,6 +424,9 @@ public class DeviceAdminAdd extends Activity {
} catch (Resources.NotFoundException e) {
mAdminDescription.setVisibility(View.GONE);
}
+ if (mAddingProfileOwner) {
+ mProfileOwnerWarning.setVisibility(View.VISIBLE);
+ }
if (mAddMsgText != null) {
mAddMsg.setText(mAddMsgText);
mAddMsg.setVisibility(View.VISIBLE);
@@ -319,7 +434,8 @@ public class DeviceAdminAdd extends Activity {
mAddMsg.setVisibility(View.GONE);
mAddMsgExpander.setVisibility(View.GONE);
}
- if (!mRefreshing && mDPM.isAdminActive(mDeviceAdmin.getComponent())) {
+ if (!mRefreshing && !mAddingProfileOwner
+ && mDPM.isAdminActive(mDeviceAdmin.getComponent())) {
if (mActivePolicies.size() == 0) {
ArrayList<DeviceAdminInfo.PolicyInfo> policies = mDeviceAdmin.getUsedPolicies();
for (int i=0; i<policies.size(); i++) {
@@ -352,7 +468,11 @@ public class DeviceAdminAdd extends Activity {
setViewVisibility(mActivePolicies, View.GONE);
mAdminWarning.setText(getString(R.string.device_admin_warning,
mDeviceAdmin.getActivityInfo().applicationInfo.loadLabel(getPackageManager())));
- setTitle(getText(R.string.add_device_admin_msg));
+ if (mAddingProfileOwner) {
+ setTitle(getText(R.string.profile_owner_add_title));
+ } else {
+ setTitle(getText(R.string.add_device_admin_msg));
+ }
mActionButton.setText(getText(R.string.add_device_admin));
mAdding = true;
}
diff --git a/src/com/android/settings/DeviceAdminSettings.java b/src/com/android/settings/DeviceAdminSettings.java
index fa1c7f4..bc22637 100644
--- a/src/com/android/settings/DeviceAdminSettings.java
+++ b/src/com/android/settings/DeviceAdminSettings.java
@@ -20,6 +20,7 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.app.Activity;
+import android.app.AlertDialog;
import android.app.ListFragment;
import android.app.admin.DeviceAdminInfo;
import android.app.admin.DeviceAdminReceiver;
@@ -30,8 +31,13 @@ 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.graphics.drawable.Drawable;
import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.util.Log;
+import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -44,19 +50,25 @@ import android.widget.TextView;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashSet;
import java.util.List;
-import java.util.Set;
+import java.util.Collection;
public class DeviceAdminSettings extends ListFragment {
static final String TAG = "DeviceAdminSettings";
-
+
static final int DIALOG_WARNING = 1;
-
- DevicePolicyManager mDPM;
- final HashSet<ComponentName> mActiveAdmins = new HashSet<ComponentName>();
- final ArrayList<DeviceAdminInfo> mAvailableAdmins = new ArrayList<DeviceAdminInfo>();
- String mDeviceOwnerPkg;
+ private DevicePolicyManager mDPM;
+ private UserManager mUm;
+
+ /**
+ * Internal collection of device admin info objects for all profiles associated with the current
+ * user.
+ */
+ private final SparseArray<ArrayList<DeviceAdminInfo>>
+ mAdminsByProfile = new SparseArray<ArrayList<DeviceAdminInfo>>();
+
+ private String mDeviceOwnerPkg;
+ private SparseArray<ComponentName> mProfileOwnerComponents = new SparseArray<ComponentName>();
@Override
public void onCreate(Bundle icicle) {
@@ -67,82 +79,73 @@ public class DeviceAdminSettings extends ListFragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mDPM = (DevicePolicyManager) getActivity().getSystemService(Context.DEVICE_POLICY_SERVICE);
+ mUm = (UserManager) getActivity().getSystemService(Context.USER_SERVICE);
return inflater.inflate(R.layout.device_admin_settings, container, false);
}
@Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ Utils.forceCustomPadding(getListView(), true /* additive padding */);
+ }
+
+ @Override
public void onResume() {
super.onResume();
mDeviceOwnerPkg = mDPM.getDeviceOwner();
if (mDeviceOwnerPkg != null && !mDPM.isDeviceOwner(mDeviceOwnerPkg)) {
mDeviceOwnerPkg = null;
}
+ mProfileOwnerComponents.clear();
+ final List<UserHandle> profiles = mUm.getUserProfiles();
+ final int profilesSize = profiles.size();
+ for (int i = 0; i < profilesSize; ++i) {
+ final int profileId = profiles.get(i).getIdentifier();
+ mProfileOwnerComponents.put(profileId, mDPM.getProfileOwnerAsUser(profileId));
+ }
updateList();
}
+ /**
+ * Update the internal collection of available admins for all profiles associated with the
+ * current user.
+ */
void updateList() {
- mActiveAdmins.clear();
- List<ComponentName> cur = mDPM.getActiveAdmins();
- if (cur != null) {
- for (int i=0; i<cur.size(); i++) {
- mActiveAdmins.add(cur.get(i));
- }
- }
-
- mAvailableAdmins.clear();
- List<ResolveInfo> avail = getActivity().getPackageManager().queryBroadcastReceivers(
- new Intent(DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED),
- PackageManager.GET_META_DATA | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS);
- if (avail == null) {
- avail = Collections.emptyList();
- }
+ mAdminsByProfile.clear();
- // Some admins listed in mActiveAdmins may not have been found by the above query.
- // We thus add them separately.
- Set<ComponentName> activeAdminsNotInAvail = new HashSet<ComponentName>(mActiveAdmins);
- for (ResolveInfo ri : avail) {
- ComponentName riComponentName =
- new ComponentName(ri.activityInfo.packageName, ri.activityInfo.name);
- activeAdminsNotInAvail.remove(riComponentName);
- }
- if (!activeAdminsNotInAvail.isEmpty()) {
- avail = new ArrayList<ResolveInfo>(avail);
- PackageManager packageManager = getActivity().getPackageManager();
- for (ComponentName unlistedActiveAdmin : activeAdminsNotInAvail) {
- List<ResolveInfo> resolved = packageManager.queryBroadcastReceivers(
- new Intent().setComponent(unlistedActiveAdmin),
- PackageManager.GET_META_DATA
- | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS);
- if (resolved != null) {
- avail.addAll(resolved);
- }
- }
+ final List<UserHandle> profiles = mUm.getUserProfiles();
+ final int profilesSize = profiles.size();
+ for (int i = 0; i < profilesSize; ++i) {
+ final int profileId = profiles.get(i).getIdentifier();
+ updateAvailableAdminsForProfile(profileId);
}
- for (int i = 0, count = avail.size(); i < count; i++) {
- ResolveInfo ri = avail.get(i);
- try {
- DeviceAdminInfo dpi = new DeviceAdminInfo(getActivity(), ri);
- if (dpi.isVisible() || mActiveAdmins.contains(dpi.getComponent())) {
- mAvailableAdmins.add(dpi);
- }
- } catch (XmlPullParserException e) {
- Log.w(TAG, "Skipping " + ri.activityInfo, e);
- } catch (IOException e) {
- Log.w(TAG, "Skipping " + ri.activityInfo, e);
- }
- }
-
getListView().setAdapter(new PolicyListAdapter());
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
- DeviceAdminInfo dpi = (DeviceAdminInfo)l.getAdapter().getItem(position);
- Intent intent = new Intent();
- intent.setClass(getActivity(), DeviceAdminAdd.class);
- intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, dpi.getComponent());
- startActivity(intent);
+ Object o = l.getAdapter().getItem(position);
+ if (!(o instanceof DeviceAdminInfo)) {
+ // race conditions may cause this
+ return;
+ }
+ DeviceAdminInfo dpi = (DeviceAdminInfo) o;
+ final Activity activity = getActivity();
+ final int userId = getUserId(dpi);
+ if (userId == UserHandle.myUserId() || !isProfileOwner(dpi)) {
+ Intent intent = new Intent();
+ intent.setClass(activity, DeviceAdminAdd.class);
+ intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, dpi.getComponent());
+ activity.startActivityAsUser(intent, new UserHandle(userId));
+ } else {
+ AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+ builder.setMessage(getString(R.string.managed_profile_device_admin_info,
+ dpi.loadLabel(activity.getPackageManager())));
+ builder.setPositiveButton(android.R.string.ok, null);
+ builder.create().show();
+ }
}
static class ViewHolder {
@@ -151,57 +154,138 @@ public class DeviceAdminSettings extends ListFragment {
CheckBox checkbox;
TextView description;
}
-
+
class PolicyListAdapter extends BaseAdapter {
final LayoutInflater mInflater;
-
+
PolicyListAdapter() {
mInflater = (LayoutInflater)
getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
+ @Override
public boolean hasStableIds() {
- return true;
+ return false;
}
-
+
+ @Override
public int getCount() {
- return mAvailableAdmins.size();
+ int adminCount = 0;
+ final int profileCount = mAdminsByProfile.size();
+ for (int i = 0; i < profileCount; ++i) {
+ adminCount += mAdminsByProfile.valueAt(i).size();
+ }
+ // Add 'profileCount' for title items.
+ return adminCount + profileCount;
}
+ /**
+ * The item for the given position in the list.
+ *
+ * @return a String object for title items and a DeviceAdminInfo object for actual device
+ * admins.
+ */
+ @Override
public Object getItem(int position) {
- return mAvailableAdmins.get(position);
+ if (position < 0) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ // The position of the item in the list of admins.
+ // We start from the given position and discount the length of the upper lists until we
+ // get the one for the right profile
+ int adminPosition = position;
+ final int n = mAdminsByProfile.size();
+ int i = 0;
+ for (; i < n; ++i) {
+ // The elements in that section including the title item (that's why adding one).
+ final int listSize = mAdminsByProfile.valueAt(i).size() + 1;
+ if (adminPosition < listSize) {
+ break;
+ }
+ adminPosition -= listSize;
+ }
+ if (i == n) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ // If countdown == 0 that means the title item
+ if (adminPosition == 0) {
+ Resources res = getActivity().getResources();
+ if (mAdminsByProfile.keyAt(i) == UserHandle.myUserId()) {
+ return res.getString(R.string.personal_device_admin_title);
+ } else {
+ return res.getString(R.string.managed_device_admin_title);
+ }
+ } else {
+ // Subtracting one for the title.
+ return mAdminsByProfile.valueAt(i).get(adminPosition - 1);
+ }
}
+ @Override
public long getItemId(int position) {
return position;
}
+ @Override
public boolean areAllItemsEnabled() {
return false;
}
+ /**
+ * See {@link #getItemViewType} for the view types.
+ */
+ @Override
+ public int getViewTypeCount() {
+ return 2;
+ }
+
+ /**
+ * Returns 1 for title items and 0 for anything else.
+ */
+ @Override
+ public int getItemViewType(int position) {
+ Object o = getItem(position);
+ return (o instanceof String) ? 1 : 0;
+ }
+
+ @Override
public boolean isEnabled(int position) {
- DeviceAdminInfo info = mAvailableAdmins.get(position);
- if (mActiveAdmins.contains(info.getComponent())
- && info.getPackageName().equals(mDeviceOwnerPkg)) {
+ Object o = getItem(position);
+ return isEnabled(o);
+ }
+
+ private boolean isEnabled(Object o) {
+ if (!(o instanceof DeviceAdminInfo)) {
+ // Title item
+ return false;
+ }
+ DeviceAdminInfo info = (DeviceAdminInfo) o;
+ if (isActiveAdmin(info) && getUserId(info) == UserHandle.myUserId()
+ && (isDeviceOwner(info) || isProfileOwner(info))) {
return false;
- } else {
- return true;
}
+ return true;
}
+ @Override
public View getView(int position, View convertView, ViewGroup parent) {
- View v;
- if (convertView == null) {
- v = newView(parent);
+ Object o = getItem(position);
+ if (o instanceof DeviceAdminInfo) {
+ if (convertView == null) {
+ convertView = newDeviceAdminView(parent);
+ }
+ bindView(convertView, (DeviceAdminInfo) o);
} else {
- v = convertView;
+ if (convertView == null) {
+ convertView = newTitleView(parent);
+ }
+ final TextView title = (TextView) convertView.findViewById(android.R.id.title);
+ title.setText((String)o);
}
- bindView(v, position);
- return v;
+ return convertView;
}
-
- public View newView(ViewGroup parent) {
+
+ private View newDeviceAdminView(ViewGroup parent) {
View v = mInflater.inflate(R.layout.device_admin_item, parent, false);
ViewHolder h = new ViewHolder();
h.icon = (ImageView)v.findViewById(R.id.icon);
@@ -211,24 +295,170 @@ public class DeviceAdminSettings extends ListFragment {
v.setTag(h);
return v;
}
-
- public void bindView(View view, int position) {
+
+ private View newTitleView(ViewGroup parent) {
+ final TypedArray a = mInflater.getContext().obtainStyledAttributes(null,
+ com.android.internal.R.styleable.Preference,
+ com.android.internal.R.attr.preferenceCategoryStyle, 0);
+ final int resId = a.getResourceId(com.android.internal.R.styleable.Preference_layout,
+ 0);
+ return mInflater.inflate(resId, parent, false);
+ }
+
+ private void bindView(View view, DeviceAdminInfo item) {
final Activity activity = getActivity();
ViewHolder vh = (ViewHolder) view.getTag();
- DeviceAdminInfo item = mAvailableAdmins.get(position);
- vh.icon.setImageDrawable(item.loadIcon(activity.getPackageManager()));
+ Drawable activityIcon = item.loadIcon(activity.getPackageManager());
+ Drawable badgedIcon = activity.getPackageManager().getUserBadgedIcon(
+ activityIcon, new UserHandle(getUserId(item)));
+ vh.icon.setImageDrawable(badgedIcon);
vh.name.setText(item.loadLabel(activity.getPackageManager()));
- vh.checkbox.setChecked(mActiveAdmins.contains(item.getComponent()));
- final boolean activeOwner = vh.checkbox.isChecked()
- && item.getPackageName().equals(mDeviceOwnerPkg);
+ vh.checkbox.setChecked(isActiveAdmin(item));
+ final boolean enabled = isEnabled(item);
try {
vh.description.setText(item.loadDescription(activity.getPackageManager()));
} catch (Resources.NotFoundException e) {
}
- vh.checkbox.setEnabled(!activeOwner);
- vh.name.setEnabled(!activeOwner);
- vh.description.setEnabled(!activeOwner);
- vh.icon.setEnabled(!activeOwner);
+ vh.checkbox.setEnabled(enabled);
+ vh.name.setEnabled(enabled);
+ vh.description.setEnabled(enabled);
+ vh.icon.setEnabled(enabled);
+ }
+ }
+
+ private boolean isDeviceOwner(DeviceAdminInfo item) {
+ return getUserId(item) == UserHandle.myUserId()
+ && item.getPackageName().equals(mDeviceOwnerPkg);
+ }
+
+ private boolean isProfileOwner(DeviceAdminInfo item) {
+ ComponentName profileOwner = mProfileOwnerComponents.get(getUserId(item));
+ return item.getComponent().equals(profileOwner);
+ }
+
+ private boolean isActiveAdmin(DeviceAdminInfo item) {
+ return mDPM.isAdminActiveAsUser(item.getComponent(), getUserId(item));
+ }
+
+ /**
+ * Add device admins to the internal collection that belong to a profile.
+ *
+ * @param profileId the profile identifier.
+ */
+ private void updateAvailableAdminsForProfile(final int profileId) {
+ // We are adding the union of two sets 'A' and 'B' of device admins to mAvailableAdmins.
+ // Set 'A' is the set of active admins for the profile whereas set 'B' is the set of
+ // listeners to DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED for the profile.
+
+ // Add all of set 'A' to mAvailableAdmins.
+ List<ComponentName> activeAdminsListForProfile = mDPM.getActiveAdminsAsUser(profileId);
+ addActiveAdminsForProfile(activeAdminsListForProfile, profileId);
+
+ // Collect set 'B' and add B-A to mAvailableAdmins.
+ addDeviceAdminBroadcastReceiversForProfile(activeAdminsListForProfile, profileId);
+ }
+
+ /**
+ * Add a profile's device admins that are receivers of
+ * {@code DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED} to the internal collection if they
+ * haven't been added yet.
+ *
+ * @param alreadyAddedComponents the set of active admin component names. Receivers of
+ * {@code DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED} whose component is in this
+ * set are not added to the internal collection again.
+ * @param profileId the identifier of the profile
+ */
+ private void addDeviceAdminBroadcastReceiversForProfile(
+ Collection<ComponentName> alreadyAddedComponents, final int profileId) {
+ final PackageManager pm = getActivity().getPackageManager();
+ List<ResolveInfo> enabledForProfile = pm.queryBroadcastReceivers(
+ new Intent(DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED),
+ PackageManager.GET_META_DATA | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
+ profileId);
+ if (enabledForProfile == null) {
+ enabledForProfile = Collections.emptyList();
+ }
+ final int n = enabledForProfile.size();
+ ArrayList<DeviceAdminInfo> deviceAdmins = mAdminsByProfile.get(profileId);
+ if (deviceAdmins == null) {
+ deviceAdmins = new ArrayList<DeviceAdminInfo>(n);
+ }
+ for (int i = 0; i < n; ++i) {
+ ResolveInfo resolveInfo = enabledForProfile.get(i);
+ ComponentName riComponentName =
+ new ComponentName(resolveInfo.activityInfo.packageName,
+ resolveInfo.activityInfo.name);
+ if (alreadyAddedComponents == null
+ || !alreadyAddedComponents.contains(riComponentName)) {
+ DeviceAdminInfo deviceAdminInfo = createDeviceAdminInfo(resolveInfo);
+ // add only visible ones (note: active admins are added regardless of visibility)
+ if (deviceAdminInfo != null && deviceAdminInfo.isVisible()) {
+ deviceAdmins.add(deviceAdminInfo);
+ }
+ }
+ }
+ if (!deviceAdmins.isEmpty()) {
+ mAdminsByProfile.put(profileId, deviceAdmins);
+ }
+ }
+
+ /**
+ * Add a {@link DeviceAdminInfo} object to the internal collection of available admins for all
+ * active admin components associated with a profile.
+ *
+ * @param profileId a profile identifier.
+ */
+ private void addActiveAdminsForProfile(final List<ComponentName> activeAdmins,
+ final int profileId) {
+ if (activeAdmins != null) {
+ final PackageManager packageManager = getActivity().getPackageManager();
+ final int n = activeAdmins.size();
+ ArrayList<DeviceAdminInfo> deviceAdmins = new ArrayList<DeviceAdminInfo>(n);
+ for (int i = 0; i < n; ++i) {
+ ComponentName activeAdmin = activeAdmins.get(i);
+ List<ResolveInfo> resolved = packageManager.queryBroadcastReceivers(
+ new Intent().setComponent(activeAdmin), PackageManager.GET_META_DATA
+ | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, profileId);
+ if (resolved != null) {
+ final int resolvedMax = resolved.size();
+ for (int j = 0; j < resolvedMax; ++j) {
+ DeviceAdminInfo deviceAdminInfo = createDeviceAdminInfo(resolved.get(j));
+ if (deviceAdminInfo != null) {
+ deviceAdmins.add(deviceAdminInfo);
+ }
+ }
+ }
+ }
+ if (!deviceAdmins.isEmpty()) {
+ mAdminsByProfile.put(profileId, deviceAdmins);
+ }
}
}
+
+ /**
+ * Creates a device admin info object for the resolved intent that points to the component of
+ * the device admin.
+ *
+ * @param resolved resolved intent.
+ * @return new {@link DeviceAdminInfo} object or null if there was an error.
+ */
+ private DeviceAdminInfo createDeviceAdminInfo(ResolveInfo resolved) {
+ try {
+ return new DeviceAdminInfo(getActivity(), resolved);
+ } catch (XmlPullParserException e) {
+ Log.w(TAG, "Skipping " + resolved.activityInfo, e);
+ } catch (IOException e) {
+ Log.w(TAG, "Skipping " + resolved.activityInfo, e);
+ }
+ return null;
+ }
+
+ /**
+ * Extracts the user id from a device admin info object.
+ * @param adminInfo the device administrator info.
+ * @return identifier of the user associated with the device admin.
+ */
+ private int getUserId(DeviceAdminInfo adminInfo) {
+ return UserHandle.getUserId(adminInfo.getActivityInfo().applicationInfo.uid);
+ }
}
diff --git a/src/com/android/settings/DeviceInfoSettings.java b/src/com/android/settings/DeviceInfoSettings.java
index 7e94741..b6d8fef 100644
--- a/src/com/android/settings/DeviceInfoSettings.java
+++ b/src/com/android/settings/DeviceInfoSettings.java
@@ -19,34 +19,47 @@ package com.android.settings;
import android.app.Activity;
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.Binder;
import android.os.Build;
import android.os.Bundle;
+import android.os.Parcel;
+import android.os.RemoteException;
import android.os.SELinux;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.os.UserManager;
import android.preference.Preference;
import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
+import android.provider.SearchIndexableResource;
+import android.provider.Settings;
+import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.search.Index;
+import com.android.settings.search.Indexable;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-public class DeviceInfoSettings extends RestrictedSettingsFragment {
+public class DeviceInfoSettings extends SettingsPreferenceFragment implements Indexable {
private static final String LOG_TAG = "DeviceInfoSettings";
-
private static final String FILENAME_PROC_VERSION = "/proc/version";
private static final String FILENAME_MSV = "/sys/board_properties/soc/msv";
private static final String KEY_CONTAINER = "container";
- private static final String KEY_TEAM = "team";
- private static final String KEY_CONTRIBUTORS = "contributors";
private static final String KEY_REGULATORY_INFO = "regulatory_info";
private static final String KEY_TERMS = "terms";
private static final String KEY_LICENSE = "license";
@@ -63,6 +76,8 @@ public class DeviceInfoSettings extends RestrictedSettingsFragment {
private static final String KEY_UPDATE_SETTING = "additional_system_update_settings";
private static final String KEY_EQUIPMENT_ID = "fcc_equipment_id";
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";
static final int TAPS_TO_BE_A_DEVELOPER = 7;
@@ -70,20 +85,12 @@ public class DeviceInfoSettings extends RestrictedSettingsFragment {
int mDevHitCountdown;
Toast mDevHitToast;
- public DeviceInfoSettings() {
- super(null /* Don't PIN protect the entire screen */);
- }
-
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
addPreferencesFromResource(R.xml.device_info_settings);
- // We only call ensurePinRestrictedPreference() when mDevHitCountdown == 0.
- // This will keep us from entering developer mode without a PIN.
- protectByRestrictions(KEY_BUILD_NUMBER);
-
setStringSummary(KEY_FIRMWARE_VERSION, Build.VERSION.RELEASE);
findPreference(KEY_FIRMWARE_VERSION).setEnabled(true);
setValueSummary(KEY_BASEBAND_VERSION, "gsm.version.baseband");
@@ -107,7 +114,7 @@ public class DeviceInfoSettings extends RestrictedSettingsFragment {
PROPERTY_SELINUX_STATUS);
// Remove Safety information preference if PROPERTY_URL_SAFETYLEGAL is not set
- removePreferenceIfPropertyMissing(getPreferenceScreen(), "safetylegal",
+ removePreferenceIfPropertyMissing(getPreferenceScreen(), KEY_SAFETY_LEGAL,
PROPERTY_URL_SAFETYLEGAL);
// Remove Equipment id preference if FCC ID is not set by RIL
@@ -119,6 +126,11 @@ public class DeviceInfoSettings extends RestrictedSettingsFragment {
getPreferenceScreen().removePreference(findPreference(KEY_BASEBAND_VERSION));
}
+ // Dont show feedback option if there is no reporter.
+ if (TextUtils.isEmpty(getFeedbackReporterPackage(getActivity()))) {
+ getPreferenceScreen().removePreference(findPreference(KEY_DEVICE_FEEDBACK));
+ }
+
/*
* Settings is a generic app and should not contain any device-specific
* info.
@@ -132,8 +144,6 @@ public class DeviceInfoSettings extends RestrictedSettingsFragment {
Utils.UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY);
Utils.updatePreferenceToSpecificActivityOrRemove(act, parentPreference, KEY_COPYRIGHT,
Utils.UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY);
- Utils.updatePreferenceToSpecificActivityOrRemove(act, parentPreference, KEY_TEAM,
- Utils.UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY);
// These are contained by the root preference screen
parentPreference = getPreferenceScreen();
@@ -145,16 +155,19 @@ public class DeviceInfoSettings extends RestrictedSettingsFragment {
// Remove for secondary users
removePreference(KEY_SYSTEM_UPDATE_SETTINGS);
}
- Utils.updatePreferenceToSpecificActivityOrRemove(act, parentPreference, KEY_CONTRIBUTORS,
- Utils.UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY);
// Read platform settings for additional system update setting
removePreferenceIfBoolFalse(KEY_UPDATE_SETTING,
R.bool.config_additional_system_update_setting_enable);
- // Remove regulatory information if not enabled.
- removePreferenceIfBoolFalse(KEY_REGULATORY_INFO,
- R.bool.config_show_regulatory_info);
+ // 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);
+ if (pref != null) {
+ getPreferenceScreen().removePreference(pref);
+ }
+ }
}
@Override
@@ -185,12 +198,10 @@ public class DeviceInfoSettings extends RestrictedSettingsFragment {
// Don't enable developer options for secondary users.
if (UserHandle.myUserId() != UserHandle.USER_OWNER) return true;
+ final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
+ if (um.hasUserRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES)) return true;
+
if (mDevHitCountdown > 0) {
- if (mDevHitCountdown == 1) {
- if (super.ensurePinRestrictedPreference(preference)) {
- return true;
- }
- }
mDevHitCountdown--;
if (mDevHitCountdown == 0) {
getActivity().getSharedPreferences(DevelopmentSettings.PREF_FILE,
@@ -202,6 +213,11 @@ public class DeviceInfoSettings extends RestrictedSettingsFragment {
mDevHitToast = Toast.makeText(getActivity(), R.string.show_dev_on,
Toast.LENGTH_LONG);
mDevHitToast.show();
+ // This is good time to index the Developer Options
+ Index.getInstance(
+ getActivity().getApplicationContext()).updateFromClassNameResource(
+ DevelopmentSettings.class.getName(), true, true);
+
} else if (mDevHitCountdown > 0
&& mDevHitCountdown < (TAPS_TO_BE_A_DEVELOPER-2)) {
if (mDevHitToast != null) {
@@ -220,6 +236,8 @@ public class DeviceInfoSettings extends RestrictedSettingsFragment {
Toast.LENGTH_LONG);
mDevHitToast.show();
}
+ } else if (preference.getKey().equals(KEY_DEVICE_FEEDBACK)) {
+ sendFeedback();
}
return super.onPreferenceTreeClick(preferenceScreen, preference);
}
@@ -265,6 +283,16 @@ public class DeviceInfoSettings extends RestrictedSettingsFragment {
}
}
+ private void sendFeedback() {
+ String reporterPackage = getFeedbackReporterPackage(getActivity());
+ if (TextUtils.isEmpty(reporterPackage)) {
+ return;
+ }
+ Intent intent = new Intent(Intent.ACTION_BUG_REPORT);
+ intent.setPackage(reporterPackage);
+ startActivityForResult(intent, 0);
+ }
+
/**
* Reads a line from the specified file.
* @param filename the file to read from
@@ -341,4 +369,118 @@ public class DeviceInfoSettings extends RestrictedSettingsFragment {
}
return "";
}
+
+ private static String getFeedbackReporterPackage(Context context) {
+ final String feedbackReporter =
+ context.getResources().getString(R.string.oem_preferred_feedback_reporter);
+ if (TextUtils.isEmpty(feedbackReporter)) {
+ // Reporter not configured. Return.
+ return feedbackReporter;
+ }
+ // Additional checks to ensure the reporter is on system image, and reporter is
+ // configured to listen to the intent. Otherwise, dont show the "send feedback" option.
+ final Intent intent = new Intent(Intent.ACTION_BUG_REPORT);
+
+ PackageManager pm = context.getPackageManager();
+ List<ResolveInfo> resolvedPackages =
+ pm.queryIntentActivities(intent, PackageManager.GET_RESOLVED_FILTER);
+ for (ResolveInfo info : resolvedPackages) {
+ if (info.activityInfo != null) {
+ if (!TextUtils.isEmpty(info.activityInfo.packageName)) {
+ try {
+ ApplicationInfo ai = pm.getApplicationInfo(info.activityInfo.packageName, 0);
+ if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ // Package is on the system image
+ if (TextUtils.equals(
+ info.activityInfo.packageName, feedbackReporter)) {
+ return feedbackReporter;
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // No need to do anything here.
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * For Search.
+ */
+ public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider() {
+
+ @Override
+ public List<SearchIndexableResource> getXmlResourcesToIndex(
+ Context context, boolean enabled) {
+ final SearchIndexableResource sir = new SearchIndexableResource(context);
+ sir.xmlResId = R.xml.device_info_settings;
+ return Arrays.asList(sir);
+ }
+
+ @Override
+ public List<String> getNonIndexableKeys(Context context) {
+ final List<String> keys = new ArrayList<String>();
+ if (isPropertyMissing(PROPERTY_SELINUX_STATUS)) {
+ keys.add(KEY_SELINUX_STATUS);
+ }
+ if (isPropertyMissing(PROPERTY_URL_SAFETYLEGAL)) {
+ keys.add(KEY_SAFETY_LEGAL);
+ }
+ if (isPropertyMissing(PROPERTY_EQUIPMENT_ID)) {
+ keys.add(KEY_EQUIPMENT_ID);
+ }
+ // Remove Baseband version if wifi-only device
+ if (Utils.isWifiOnly(context)) {
+ keys.add((KEY_BASEBAND_VERSION));
+ }
+ // Dont show feedback option if there is no reporter.
+ if (TextUtils.isEmpty(getFeedbackReporterPackage(context))) {
+ keys.add(KEY_DEVICE_FEEDBACK);
+ }
+ if (!checkIntentAction(context, "android.settings.TERMS")) {
+ keys.add(KEY_TERMS);
+ }
+ if (!checkIntentAction(context, "android.settings.LICENSE")) {
+ keys.add(KEY_LICENSE);
+ }
+ if (!checkIntentAction(context, "android.settings.COPYRIGHT")) {
+ keys.add(KEY_COPYRIGHT);
+ }
+ if (UserHandle.myUserId() != UserHandle.USER_OWNER) {
+ keys.add(KEY_SYSTEM_UPDATE_SETTINGS);
+ }
+ if (!context.getResources().getBoolean(
+ R.bool.config_additional_system_update_setting_enable)) {
+ keys.add(KEY_UPDATE_SETTING);
+ }
+ return keys;
+ }
+
+ private boolean isPropertyMissing(String property) {
+ return SystemProperties.get(property).equals("");
+ }
+
+ private boolean checkIntentAction(Context context, String action) {
+ final Intent intent = new Intent(action);
+
+ // Find the activity that is in the system image
+ final PackageManager pm = context.getPackageManager();
+ final List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
+ final 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) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ };
+
}
+
diff --git a/src/com/android/settings/DisplaySettings.java b/src/com/android/settings/DisplaySettings.java
index b0c944d..ddd6728 100644
--- a/src/com/android/settings/DisplaySettings.java
+++ b/src/com/android/settings/DisplaySettings.java
@@ -16,8 +16,20 @@
package com.android.settings;
+import com.android.internal.view.RotationPolicy;
+import com.android.settings.notification.DropDownPreference;
+import com.android.settings.notification.DropDownPreference.Callback;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.search.Indexable;
+
+import static android.provider.Settings.Secure.DOZE_ENABLED;
+import static android.provider.Settings.Secure.WAKE_GESTURE_ENABLED;
+import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE;
+import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
+import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL;
import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT;
+import android.app.Activity;
import android.app.ActivityManagerNative;
import android.app.Dialog;
import android.app.admin.DevicePolicyManager;
@@ -25,71 +37,60 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.os.Build;
import android.os.Bundle;
import android.os.RemoteException;
-import android.preference.CheckBoxPreference;
+import android.os.SystemProperties;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceScreen;
+import android.preference.SwitchPreference;
+import android.provider.SearchIndexableResource;
import android.provider.Settings;
-import android.provider.Settings.SettingNotFoundException;
+import android.text.TextUtils;
import android.util.Log;
-import com.android.internal.view.RotationPolicy;
-import com.android.settings.DreamSettings;
-
import java.util.ArrayList;
+import java.util.List;
public class DisplaySettings extends SettingsPreferenceFragment implements
- Preference.OnPreferenceChangeListener, OnPreferenceClickListener {
+ Preference.OnPreferenceChangeListener, OnPreferenceClickListener, Indexable {
private static final String TAG = "DisplaySettings";
/** If there is no setting in the provider, use this. */
private static final int FALLBACK_SCREEN_TIMEOUT_VALUE = 30000;
private static final String KEY_SCREEN_TIMEOUT = "screen_timeout";
- private static final String KEY_ACCELEROMETER = "accelerometer";
private static final String KEY_FONT_SIZE = "font_size";
- private static final String KEY_NOTIFICATION_PULSE = "notification_pulse";
private static final String KEY_SCREEN_SAVER = "screensaver";
+ private static final String KEY_LIFT_TO_WAKE = "lift_to_wake";
+ private static final String KEY_DOZE = "doze";
+ private static final String KEY_AUTO_BRIGHTNESS = "auto_brightness";
+ private static final String KEY_AUTO_ROTATE = "auto_rotate";
private static final int DLG_GLOBAL_CHANGE_WARNING = 1;
- private CheckBoxPreference mAccelerometer;
private WarnedListPreference mFontSizePref;
- private CheckBoxPreference mNotificationPulse;
private final Configuration mCurConfig = new Configuration();
private ListPreference mScreenTimeoutPreference;
private Preference mScreenSaverPreference;
-
- private final RotationPolicy.RotationPolicyListener mRotationPolicyListener =
- new RotationPolicy.RotationPolicyListener() {
- @Override
- public void onChange() {
- updateAccelerometerRotationCheckbox();
- }
- };
+ private SwitchPreference mLiftToWakePreference;
+ private SwitchPreference mDozePreference;
+ private SwitchPreference mAutoBrightnessPreference;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- ContentResolver resolver = getActivity().getContentResolver();
+ final Activity activity = getActivity();
+ final ContentResolver resolver = activity.getContentResolver();
addPreferencesFromResource(R.xml.display_settings);
- mAccelerometer = (CheckBoxPreference) findPreference(KEY_ACCELEROMETER);
- mAccelerometer.setPersistent(false);
- if (!RotationPolicy.isRotationSupported(getActivity())
- || RotationPolicy.isRotationLockToggleSupported(getActivity())) {
- // If rotation lock is supported, then we do not provide this option in
- // Display settings. However, is still available in Accessibility settings,
- // if the device supports rotation.
- getPreferenceScreen().removePreference(mAccelerometer);
- }
-
mScreenSaverPreference = findPreference(KEY_SCREEN_SAVER);
if (mScreenSaverPreference != null
&& getResources().getBoolean(
@@ -108,22 +109,87 @@ public class DisplaySettings extends SettingsPreferenceFragment implements
mFontSizePref = (WarnedListPreference) findPreference(KEY_FONT_SIZE);
mFontSizePref.setOnPreferenceChangeListener(this);
mFontSizePref.setOnPreferenceClickListener(this);
- mNotificationPulse = (CheckBoxPreference) findPreference(KEY_NOTIFICATION_PULSE);
- if (mNotificationPulse != null
- && getResources().getBoolean(
- com.android.internal.R.bool.config_intrusiveNotificationLed) == false) {
- getPreferenceScreen().removePreference(mNotificationPulse);
+
+ if (isAutomaticBrightnessAvailable(getResources())) {
+ mAutoBrightnessPreference = (SwitchPreference) findPreference(KEY_AUTO_BRIGHTNESS);
+ mAutoBrightnessPreference.setOnPreferenceChangeListener(this);
} else {
- try {
- mNotificationPulse.setChecked(Settings.System.getInt(resolver,
- Settings.System.NOTIFICATION_LIGHT_PULSE) == 1);
- mNotificationPulse.setOnPreferenceChangeListener(this);
- } catch (SettingNotFoundException snfe) {
- Log.e(TAG, Settings.System.NOTIFICATION_LIGHT_PULSE + " not found");
+ removePreference(KEY_AUTO_BRIGHTNESS);
+ }
+
+ if (isLiftToWakeAvailable(activity)) {
+ mLiftToWakePreference = (SwitchPreference) findPreference(KEY_LIFT_TO_WAKE);
+ mLiftToWakePreference.setOnPreferenceChangeListener(this);
+ } else {
+ removePreference(KEY_LIFT_TO_WAKE);
+ }
+
+ if (isDozeAvailable(activity)) {
+ mDozePreference = (SwitchPreference) findPreference(KEY_DOZE);
+ mDozePreference.setOnPreferenceChangeListener(this);
+ } else {
+ removePreference(KEY_DOZE);
+ }
+
+ 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;
+ }
}
+ 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) {
+ RotationPolicy.setRotationLock(activity, (Boolean) value);
+ return true;
+ }
+ });
+ } else {
+ removePreference(KEY_AUTO_ROTATE);
}
}
+ private static boolean allowAllRotations(Context context) {
+ return Resources.getSystem().getBoolean(
+ com.android.internal.R.bool.config_allowAllRotations);
+ }
+
+ private static boolean isLiftToWakeAvailable(Context context) {
+ SensorManager sensors = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
+ 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 isAutomaticBrightnessAvailable(Resources res) {
+ return res.getBoolean(com.android.internal.R.bool.config_automatic_brightness_available);
+ }
+
private void updateTimeoutPreferenceDescription(long currentTimeout) {
ListPreference preference = mScreenTimeoutPreference;
String summary;
@@ -225,22 +291,10 @@ public class DisplaySettings extends SettingsPreferenceFragment implements
@Override
public void onResume() {
super.onResume();
-
- RotationPolicy.registerRotationPolicyListener(getActivity(),
- mRotationPolicyListener);
-
updateState();
}
@Override
- public void onPause() {
- super.onPause();
-
- RotationPolicy.unregisterRotationPolicyListener(getActivity(),
- mRotationPolicyListener);
- }
-
- @Override
public Dialog onCreateDialog(int dialogId) {
if (dialogId == DLG_GLOBAL_CHANGE_WARNING) {
return Utils.buildGlobalChangeWarningDialog(getActivity(),
@@ -255,9 +309,27 @@ public class DisplaySettings extends SettingsPreferenceFragment implements
}
private void updateState() {
- updateAccelerometerRotationCheckbox();
readFontSizePreference(mFontSizePref);
updateScreenSaverSummary();
+
+ // Update auto brightness if it is available.
+ if (mAutoBrightnessPreference != null) {
+ int brightnessMode = Settings.System.getInt(getContentResolver(),
+ SCREEN_BRIGHTNESS_MODE, SCREEN_BRIGHTNESS_MODE_MANUAL);
+ mAutoBrightnessPreference.setChecked(brightnessMode != SCREEN_BRIGHTNESS_MODE_MANUAL);
+ }
+
+ // Update lift-to-wake if it is available.
+ if (mLiftToWakePreference != null) {
+ int value = Settings.Secure.getInt(getContentResolver(), WAKE_GESTURE_ENABLED, 0);
+ mLiftToWakePreference.setChecked(value != 0);
+ }
+
+ // Update doze if it is available.
+ if (mDozePreference != null) {
+ int value = Settings.Secure.getInt(getContentResolver(), DOZE_ENABLED, 1);
+ mDozePreference.setChecked(value != 0);
+ }
}
private void updateScreenSaverSummary() {
@@ -267,12 +339,6 @@ public class DisplaySettings extends SettingsPreferenceFragment implements
}
}
- private void updateAccelerometerRotationCheckbox() {
- if (getActivity() == null) return;
-
- mAccelerometer.setChecked(!RotationPolicy.isRotationLocked(getActivity()));
- }
-
public void writeFontSizePreference(Object objValue) {
try {
mCurConfig.fontScale = Float.parseFloat(objValue.toString());
@@ -284,15 +350,6 @@ public class DisplaySettings extends SettingsPreferenceFragment implements
@Override
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
- if (preference == mAccelerometer) {
- RotationPolicy.setRotationLockForAccessibility(
- getActivity(), !mAccelerometer.isChecked());
- } else if (preference == mNotificationPulse) {
- boolean value = mNotificationPulse.isChecked();
- Settings.System.putInt(getContentResolver(), Settings.System.NOTIFICATION_LIGHT_PULSE,
- value ? 1 : 0);
- return true;
- }
return super.onPreferenceTreeClick(preferenceScreen, preference);
}
@@ -311,7 +368,19 @@ public class DisplaySettings extends SettingsPreferenceFragment implements
if (KEY_FONT_SIZE.equals(key)) {
writeFontSizePreference(objValue);
}
-
+ if (preference == mAutoBrightnessPreference) {
+ boolean auto = (Boolean) objValue;
+ Settings.System.putInt(getContentResolver(), SCREEN_BRIGHTNESS_MODE,
+ auto ? SCREEN_BRIGHTNESS_MODE_AUTOMATIC : SCREEN_BRIGHTNESS_MODE_MANUAL);
+ }
+ if (preference == mLiftToWakePreference) {
+ boolean value = (Boolean) objValue;
+ Settings.Secure.putInt(getContentResolver(), WAKE_GESTURE_ENABLED, value ? 1 : 0);
+ }
+ if (preference == mDozePreference) {
+ boolean value = (Boolean) objValue;
+ Settings.Secure.putInt(getContentResolver(), DOZE_ENABLED, value ? 1 : 0);
+ }
return true;
}
@@ -327,4 +396,42 @@ public class DisplaySettings extends SettingsPreferenceFragment implements
}
return false;
}
+
+ 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.display_settings;
+ result.add(sir);
+
+ return result;
+ }
+
+ @Override
+ public List<String> getNonIndexableKeys(Context context) {
+ ArrayList<String> result = new ArrayList<String>();
+ if (!context.getResources().getBoolean(
+ com.android.internal.R.bool.config_dreamsSupported)) {
+ result.add(KEY_SCREEN_SAVER);
+ }
+ if (!isAutomaticBrightnessAvailable(context.getResources())) {
+ result.add(KEY_AUTO_BRIGHTNESS);
+ }
+ if (!isLiftToWakeAvailable(context)) {
+ result.add(KEY_LIFT_TO_WAKE);
+ }
+ if (!isDozeAvailable(context)) {
+ result.add(KEY_DOZE);
+ }
+ if (!RotationPolicy.isRotationLockToggleVisible(context)) {
+ result.add(KEY_AUTO_ROTATE);
+ }
+ return result;
+ }
+ };
}
diff --git a/src/com/android/settings/DreamSettings.java b/src/com/android/settings/DreamSettings.java
index cb91f39..38cba7a 100644
--- a/src/com/android/settings/DreamSettings.java
+++ b/src/com/android/settings/DreamSettings.java
@@ -16,7 +16,6 @@
package com.android.settings;
-import android.app.ActionBar;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
@@ -26,9 +25,7 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
-import android.preference.PreferenceActivity;
import android.util.Log;
-import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -40,8 +37,6 @@ import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
-import android.widget.CompoundButton;
-import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.RadioButton;
@@ -49,10 +44,12 @@ import android.widget.Switch;
import android.widget.TextView;
import com.android.settings.DreamBackend.DreamInfo;
+import com.android.settings.widget.SwitchBar;
import java.util.List;
-public class DreamSettings extends SettingsPreferenceFragment {
+public class DreamSettings extends SettingsPreferenceFragment implements
+ SwitchBar.OnSwitchChangeListener {
private static final String TAG = DreamSettings.class.getSimpleName();
static final boolean DEBUG = false;
private static final int DIALOG_WHEN_TO_DREAM = 1;
@@ -63,7 +60,7 @@ public class DreamSettings extends SettingsPreferenceFragment {
private Context mContext;
private DreamBackend mBackend;
private DreamInfoAdapter mAdapter;
- private Switch mSwitch;
+ private SwitchBar mSwitchBar;
private MenuItem[] mMenuItemsWhenEnabled;
private boolean mRefreshing;
@@ -83,37 +80,33 @@ public class DreamSettings extends SettingsPreferenceFragment {
public void onCreate(Bundle icicle) {
logd("onCreate(%s)", icicle);
super.onCreate(icicle);
- Activity activity = getActivity();
- mBackend = new DreamBackend(activity);
- mSwitch = new Switch(activity);
- mSwitch.setOnCheckedChangeListener(new OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- if (!mRefreshing) {
- mBackend.setEnabled(isChecked);
- refreshFromBackend();
- }
- }
- });
-
- final int padding = activity.getResources().getDimensionPixelSize(
- R.dimen.action_bar_switch_padding);
- mSwitch.setPaddingRelative(0, 0, padding, 0);
- activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
- ActionBar.DISPLAY_SHOW_CUSTOM);
- activity.getActionBar().setCustomView(mSwitch, new ActionBar.LayoutParams(
- ActionBar.LayoutParams.WRAP_CONTENT,
- ActionBar.LayoutParams.WRAP_CONTENT,
- Gravity.CENTER_VERTICAL | Gravity.END));
+ mBackend = new DreamBackend(getActivity());
setHasOptionsMenu(true);
}
@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() {
- getActivity().getActionBar().setCustomView(null);
+ logd("onDestroyView()");
super.onDestroyView();
+
+ mSwitchBar.removeOnSwitchChangeListener(this);
+ mSwitchBar.hide();
}
@Override
@@ -122,7 +115,6 @@ public class DreamSettings extends SettingsPreferenceFragment {
super.onActivityCreated(savedInstanceState);
ListView listView = getListView();
-
listView.setItemsCanFocus(true);
TextView emptyView = (TextView) getView().findViewById(android.R.id.empty);
@@ -131,6 +123,11 @@ public class DreamSettings extends SettingsPreferenceFragment {
mAdapter = new DreamInfoAdapter(mContext);
listView.setAdapter(mAdapter);
+
+ final SettingsActivity sa = (SettingsActivity) getActivity();
+ mSwitchBar = sa.getSwitchBar();
+ mSwitchBar.addOnSwitchChangeListener(this);
+ mSwitchBar.show();
}
@Override
@@ -141,7 +138,7 @@ public class DreamSettings extends SettingsPreferenceFragment {
// create "start" action
MenuItem start = createMenuItem(menu, R.string.screensaver_settings_dream_start,
- MenuItem.SHOW_AS_ACTION_ALWAYS,
+ MenuItem.SHOW_AS_ACTION_NEVER,
isEnabled, new Runnable(){
@Override
public void run() {
@@ -151,7 +148,7 @@ public class DreamSettings extends SettingsPreferenceFragment {
// create "when to dream" overflow menu item
MenuItem whenToDream = createMenuItem(menu,
R.string.screensaver_settings_when_to_dream,
- MenuItem.SHOW_AS_ACTION_IF_ROOM,
+ MenuItem.SHOW_AS_ACTION_NEVER,
isEnabled,
new Runnable() {
@Override
@@ -216,6 +213,7 @@ public class DreamSettings extends SettingsPreferenceFragment {
public void onPause() {
logd("onPause()");
super.onPause();
+
mContext.unregisterReceiver(mPackageReceiver);
}
@@ -262,8 +260,8 @@ public class DreamSettings extends SettingsPreferenceFragment {
logd("refreshFromBackend()");
mRefreshing = true;
boolean dreamsEnabled = mBackend.isEnabled();
- if (mSwitch.isChecked() != dreamsEnabled)
- mSwitch.setChecked(dreamsEnabled);
+ if (mSwitchBar.isChecked() != dreamsEnabled)
+ mSwitchBar.setChecked(dreamsEnabled);
mAdapter.clear();
if (dreamsEnabled) {
diff --git a/src/com/android/settings/EncryptionInterstitial.java b/src/com/android/settings/EncryptionInterstitial.java
new file mode 100644
index 0000000..e836aed
--- /dev/null
+++ b/src/com/android/settings/EncryptionInterstitial.java
@@ -0,0 +1,212 @@
+/*
+ * 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 com.android.internal.widget.LockPatternUtils;
+import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.SettingsPreferenceFragment;
+
+import java.util.List;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.RadioButton;
+import android.widget.TextView;
+
+public class EncryptionInterstitial extends SettingsActivity {
+
+ private static final String EXTRA_PASSWORD_QUALITY = "extra_password_quality";
+ public static final String EXTRA_REQUIRE_PASSWORD = "extra_require_password";
+
+ @Override
+ public Intent getIntent() {
+ Intent modIntent = new Intent(super.getIntent());
+ modIntent.putExtra(EXTRA_SHOW_FRAGMENT, EncryptionInterstitialFragment.class.getName());
+ return modIntent;
+ }
+
+ @Override
+ protected boolean isValidFragment(String fragmentName) {
+ return EncryptionInterstitialFragment.class.getName().equals(fragmentName);
+ }
+
+ public static Intent createStartIntent(Context ctx, int quality,
+ boolean requirePasswordDefault) {
+ return new Intent(ctx, EncryptionInterstitial.class)
+ .putExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, true)
+ .putExtra(EXTRA_PREFS_SET_BACK_TEXT, (String) null)
+ .putExtra(EXTRA_PREFS_SET_NEXT_TEXT, ctx.getString(
+ R.string.encryption_continue_button))
+ .putExtra(EXTRA_PASSWORD_QUALITY, quality)
+ .putExtra(EXTRA_SHOW_FRAGMENT_TITLE_RESID, R.string.encryption_interstitial_header)
+ .putExtra(EXTRA_REQUIRE_PASSWORD, requirePasswordDefault);
+ }
+
+ public static class EncryptionInterstitialFragment extends SettingsPreferenceFragment
+ implements View.OnClickListener, OnClickListener {
+
+ private static final int ACCESSIBILITY_WARNING_DIALOG = 1;
+ private RadioButton mRequirePasswordToDecryptButton;
+ private RadioButton mDontRequirePasswordToDecryptButton;
+ private TextView mEncryptionMessage;
+ private boolean mPasswordRequired;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ final int layoutId = R.layout.encryption_interstitial;
+ View view = inflater.inflate(layoutId, container, false);
+ mRequirePasswordToDecryptButton =
+ (RadioButton) view.findViewById(R.id.encrypt_require_password);
+ mDontRequirePasswordToDecryptButton =
+ (RadioButton) view.findViewById(R.id.encrypt_dont_require_password);
+ mEncryptionMessage =
+ (TextView) view.findViewById(R.id.encryption_message);
+ int quality = getActivity().getIntent().getIntExtra(EXTRA_PASSWORD_QUALITY, 0);
+ final int msgId;
+ final int enableId;
+ final int disableId;
+ switch (quality) {
+ case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
+ msgId = R.string.encryption_interstitial_message_pattern;
+ enableId = R.string.encrypt_require_pattern;
+ disableId = R.string.encrypt_dont_require_pattern;
+ break;
+ case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
+ case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX:
+ msgId = R.string.encryption_interstitial_message_pin;
+ enableId = R.string.encrypt_require_pin;
+ disableId = R.string.encrypt_dont_require_pin;
+ break;
+ default:
+ msgId = R.string.encryption_interstitial_message_password;
+ enableId = R.string.encrypt_require_password;
+ disableId = R.string.encrypt_dont_require_password;
+ break;
+ }
+ mEncryptionMessage.setText(msgId);
+
+ mRequirePasswordToDecryptButton.setOnClickListener(this);
+ mRequirePasswordToDecryptButton.setText(enableId);
+
+ mDontRequirePasswordToDecryptButton.setOnClickListener(this);
+ mDontRequirePasswordToDecryptButton.setText(disableId);
+
+ setRequirePasswordState(getActivity().getIntent().getBooleanExtra(
+ EXTRA_REQUIRE_PASSWORD, true));
+ return view;
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v == mRequirePasswordToDecryptButton) {
+ final boolean accEn = AccessibilityManager.getInstance(getActivity()).isEnabled();
+ if (accEn && !mPasswordRequired) {
+ setRequirePasswordState(false); // clear the UI state
+ showDialog(ACCESSIBILITY_WARNING_DIALOG);
+ } else {
+ setRequirePasswordState(true);
+ }
+ } else {
+ setRequirePasswordState(false);
+ }
+ }
+
+ @Override
+ public Dialog onCreateDialog(int dialogId) {
+ switch(dialogId) {
+ case ACCESSIBILITY_WARNING_DIALOG: {
+ final int quality = new LockPatternUtils(getActivity())
+ .getKeyguardStoredPasswordQuality();
+ final int titleId;
+ final int messageId;
+ switch (quality) {
+ case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
+ titleId = R.string.encrypt_talkback_dialog_require_pattern;
+ messageId = R.string.encrypt_talkback_dialog_message_pattern;
+ break;
+ case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
+ case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX:
+ titleId = R.string.encrypt_talkback_dialog_require_pin;
+ messageId = R.string.encrypt_talkback_dialog_message_pin;
+ break;
+ default:
+ titleId = R.string.encrypt_talkback_dialog_require_password;
+ messageId = R.string.encrypt_talkback_dialog_message_password;
+ break;
+ }
+
+
+ List<AccessibilityServiceInfo> list =
+ AccessibilityManager.getInstance(getActivity())
+ .getEnabledAccessibilityServiceList(
+ AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+ final CharSequence exampleAccessibility;
+ if (list.isEmpty()) {
+ // This should never happen. But we shouldn't crash
+ exampleAccessibility = "";
+ } else {
+ exampleAccessibility = list.get(0).getResolveInfo()
+ .loadLabel(getPackageManager());
+ }
+ return new AlertDialog.Builder(getActivity())
+ .setTitle(titleId)
+ .setMessage(getString(messageId, exampleAccessibility))
+ .setCancelable(true)
+ .setPositiveButton(android.R.string.ok, this)
+ .setNegativeButton(android.R.string.cancel, this)
+ .create();
+ }
+ default: throw new IllegalArgumentException();
+ }
+ }
+
+ private void setRequirePasswordState(boolean required) {
+ mPasswordRequired = required;
+ mRequirePasswordToDecryptButton.setChecked(required);
+ mDontRequirePasswordToDecryptButton.setChecked(!required);
+
+ // Updates value returned by SettingsActivity.onActivityResult().
+ SettingsActivity sa = (SettingsActivity)getActivity();
+ Intent resultIntentData = sa.getResultIntentData();
+ resultIntentData = resultIntentData == null ? new Intent() : resultIntentData;
+ resultIntentData.putExtra(EXTRA_REQUIRE_PASSWORD, mPasswordRequired);
+ sa.setResultIntentData(resultIntentData);
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ setRequirePasswordState(true);
+ } else if (which == DialogInterface.BUTTON_NEGATIVE) {
+ setRequirePasswordState(false);
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/EventLogTags.logtags b/src/com/android/settings/EventLogTags.logtags
index 3e87c53..b21623c 100644
--- a/src/com/android/settings/EventLogTags.logtags
+++ b/src/com/android/settings/EventLogTags.logtags
@@ -4,3 +4,9 @@ option java_package com.android.settings
# log the type of screen lock when user sets lock screen
90200 lock_screen_type (type|3)
+
+# log whether user accepted and activated device admin
+90201 exp_det_device_admin_activated_by_user (app_signature|3)
+
+# log whether user declined activation of device admin
+90202 exp_det_device_admin_declined_by_user (app_signature|3)
diff --git a/src/com/android/settings/HighlightingFragment.java b/src/com/android/settings/HighlightingFragment.java
new file mode 100644
index 0000000..4a233b4
--- /dev/null
+++ b/src/com/android/settings/HighlightingFragment.java
@@ -0,0 +1,126 @@
+/*
+ * 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.app.Fragment;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class HighlightingFragment extends Fragment {
+
+ private static final String TAG = "HighlightSettingsFragment";
+
+ private static final int DELAY_HIGHLIGHT_DURATION_MILLIS = 400;
+ private static final String SAVE_HIGHLIGHTED_KEY = "android:view_highlighted";
+
+ private String mViewKey;
+ private boolean mViewHighlighted = false;
+ private Drawable mHighlightDrawable;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ if (icicle != null) {
+ mViewHighlighted = icicle.getBoolean(SAVE_HIGHLIGHTED_KEY);
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ outState.putBoolean(SAVE_HIGHLIGHTED_KEY, mViewHighlighted);
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ final Bundle args = getArguments();
+ if (args != null) {
+ mViewKey = args.getString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY);
+ highlightViewIfNeeded();
+ }
+ }
+
+ public void highlightViewIfNeeded() {
+ if (!mViewHighlighted &&!TextUtils.isEmpty(mViewKey)) {
+ highlightView(mViewKey);
+ }
+ }
+
+ private Drawable getHighlightDrawable() {
+ if (mHighlightDrawable == null) {
+ mHighlightDrawable = getActivity().getDrawable(R.drawable.preference_highlight);
+ }
+ return mHighlightDrawable;
+ }
+
+ private void highlightView(String key) {
+ final Drawable highlight = getHighlightDrawable();
+
+ // Try locating the View thru its Tag / Key
+ final View view = findViewForKey(getView(), key);
+ if (view != null ) {
+ view.setBackground(highlight);
+
+ getView().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ final int centerX = view.getWidth() / 2;
+ final int centerY = view.getHeight() / 2;
+ highlight.setHotspot(centerX, centerY);
+ view.setPressed(true);
+ view.setPressed(false);
+ }
+ }, DELAY_HIGHLIGHT_DURATION_MILLIS);
+
+ mViewHighlighted = true;
+ }
+ }
+
+ private View findViewForKey(View root, String key) {
+ if (checkTag(root, key)) {
+ return root;
+ }
+ if (root instanceof ViewGroup) {
+ final ViewGroup group = (ViewGroup) root;
+ final int count = group.getChildCount();
+ for (int n = 0; n < count; n++) {
+ final View child = group.getChildAt(n);
+ final View view = findViewForKey(child, key);
+ if (view != null) {
+ return view;
+ }
+ }
+ }
+ return null;
+ }
+
+ private boolean checkTag(View view, String key) {
+ final Object tag = view.getTag(R.id.preference_highlight_key);
+ if (tag == null || !(tag instanceof String)) {
+ return false;
+ }
+ final String viewKey = (String) tag;
+ return (!TextUtils.isEmpty(viewKey) && viewKey.equals(key));
+ }
+}
diff --git a/src/com/android/settings/HomeSettings.java b/src/com/android/settings/HomeSettings.java
index eb659e2..6da1848 100644
--- a/src/com/android/settings/HomeSettings.java
+++ b/src/com/android/settings/HomeSettings.java
@@ -17,8 +17,11 @@
package com.android.settings;
import java.util.ArrayList;
+import java.util.List;
+import android.app.Activity;
import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -29,24 +32,37 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.content.pm.UserInfo;
import android.graphics.ColorFilter;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
+import android.os.UserManager;
import android.preference.Preference;
import android.preference.PreferenceGroup;
+import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageView;
import android.widget.RadioButton;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.search.Index;
+import com.android.settings.search.Indexable;
+import com.android.settings.search.SearchIndexableRaw;
-public class HomeSettings extends SettingsPreferenceFragment {
+public class HomeSettings extends SettingsPreferenceFragment implements Indexable {
static final String TAG = "HomeSettings";
+ // Boolean extra, indicates only launchers that support managed profiles should be shown.
+ // Note: must match the constant defined in ManagedProvisioning
+ private static final String EXTRA_SUPPORT_MANAGED_PROFILES = "support_managed_profiles";
+
static final int REQUESTING_UNINSTALL = 10;
public static final String HOME_PREFS = "home_prefs";
@@ -54,14 +70,23 @@ public class HomeSettings extends SettingsPreferenceFragment {
public static final String HOME_SHOW_NOTICE = "show";
- PreferenceGroup mPrefGroup;
+ private class HomePackageReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ buildHomeActivitiesList();
+ Index.getInstance(context).updateFromClassNameResource(
+ HomeSettings.class.getName(), true, true);
+ }
+ }
- PackageManager mPm;
- ComponentName[] mHomeComponentSet;
- ArrayList<HomeAppPreference> mPrefs;
- HomeAppPreference mCurrentHome = null;
- final IntentFilter mHomeFilter;
- boolean mShowNotice;
+ private PreferenceGroup mPrefGroup;
+ private PackageManager mPm;
+ private ComponentName[] mHomeComponentSet;
+ private ArrayList<HomeAppPreference> mPrefs;
+ private HomeAppPreference mCurrentHome = null;
+ private final IntentFilter mHomeFilter;
+ private boolean mShowNotice;
+ private HomePackageReceiver mHomePackageReceiver = new HomePackageReceiver();
public HomeSettings() {
mHomeFilter = new IntentFilter(Intent.ACTION_MAIN);
@@ -97,6 +122,8 @@ public class HomeSettings extends SettingsPreferenceFragment {
mPm.replacePreferredActivity(mHomeFilter, IntentFilter.MATCH_CATEGORY_EMPTY,
mHomeComponentSet, newHome.activityName);
+
+ getActivity().setResult(Activity.RESULT_OK);
}
void uninstallApp(HomeAppPreference pref) {
@@ -138,13 +165,13 @@ public class HomeSettings extends SettingsPreferenceFragment {
if (mPrefs.size() < 2) {
if (mShowNotice) {
mShowNotice = false;
- Settings.requestHomeNotice();
+ SettingsActivity.requestHomeNotice();
}
finishFragment();
}
}
- void buildHomeActivitiesList() {
+ private void buildHomeActivitiesList() {
ArrayList<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>();
ComponentName currentDefaultHome = mPm.getHomeActivities(homeActivities);
@@ -154,6 +181,10 @@ public class HomeSettings extends SettingsPreferenceFragment {
mPrefs = new ArrayList<HomeAppPreference>();
mHomeComponentSet = new ComponentName[homeActivities.size()];
int prefIndex = 0;
+ boolean supportManagedProfilesExtra =
+ getActivity().getIntent().getBooleanExtra(EXTRA_SUPPORT_MANAGED_PROFILES, false);
+ boolean mustSupportManagedProfile = hasManagedProfile()
+ || supportManagedProfilesExtra;
for (int i = 0; i < homeActivities.size(); i++) {
final ResolveInfo candidate = homeActivities.get(i);
final ActivityInfo info = candidate.activityInfo;
@@ -162,11 +193,19 @@ public class HomeSettings extends SettingsPreferenceFragment {
try {
Drawable icon = info.loadIcon(mPm);
CharSequence name = info.loadLabel(mPm);
- HomeAppPreference pref = new HomeAppPreference(context, activityName, prefIndex,
- icon, name, this, info);
+ HomeAppPreference pref;
+
+ if (mustSupportManagedProfile && !launcherHasManagedProfilesFeature(candidate)) {
+ pref = new HomeAppPreference(context, activityName, prefIndex,
+ icon, name, this, info, false /* not enabled */,
+ getResources().getString(R.string.home_work_profile_not_supported));
+ } else {
+ pref = new HomeAppPreference(context, activityName, prefIndex,
+ icon, name, this, info, true /* enabled */, null);
+ }
+
mPrefs.add(pref);
mPrefGroup.addPreference(pref);
- pref.setEnabled(true);
if (activityName.equals(currentDefaultHome)) {
mCurrentHome = pref;
}
@@ -177,6 +216,10 @@ public class HomeSettings extends SettingsPreferenceFragment {
}
if (mCurrentHome != null) {
+ if (mCurrentHome.isEnabled()) {
+ getActivity().setResult(Activity.RESULT_OK);
+ }
+
new Handler().post(new Runnable() {
public void run() {
mCurrentHome.setChecked(true);
@@ -185,6 +228,30 @@ public class HomeSettings extends SettingsPreferenceFragment {
}
}
+ private boolean hasManagedProfile() {
+ Context context = getActivity();
+ UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
+ List<UserInfo> profiles = userManager.getProfiles(context.getUserId());
+ for (UserInfo userInfo : profiles) {
+ if (userInfo.isManagedProfile()) return true;
+ }
+ return false;
+ }
+
+ private boolean launcherHasManagedProfilesFeature(ResolveInfo resolveInfo) {
+ try {
+ ApplicationInfo appInfo = getPackageManager().getApplicationInfo(
+ resolveInfo.activityInfo.packageName, 0 /* default flags */);
+ return versionNumberAtLeastL(appInfo.targetSdkVersion);
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+
+ private boolean versionNumberAtLeastL(int versionNumber) {
+ return versionNumber >= Build.VERSION_CODES.LOLLIPOP;
+ }
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -200,10 +267,24 @@ public class HomeSettings extends SettingsPreferenceFragment {
@Override
public void onResume() {
super.onResume();
+
+ final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+ filter.addDataScheme("package");
+ getActivity().registerReceiver(mHomePackageReceiver, filter);
+
buildHomeActivitiesList();
}
- class HomeAppPreference extends Preference {
+ @Override
+ public void onPause() {
+ super.onPause();
+ getActivity().unregisterReceiver(mHomePackageReceiver);
+ }
+
+ private class HomeAppPreference extends Preference {
ComponentName activityName;
int index;
HomeSettings fragment;
@@ -214,12 +295,14 @@ public class HomeSettings extends SettingsPreferenceFragment {
String uninstallTarget;
public HomeAppPreference(Context context, ComponentName activity,
- int i, Drawable icon, CharSequence title,
- HomeSettings parent, ActivityInfo info) {
+ int i, Drawable icon, CharSequence title, HomeSettings parent, ActivityInfo info,
+ boolean enabled, CharSequence summary) {
super(context);
setLayoutResource(R.layout.preference_home_app);
setIcon(icon);
setTitle(title);
+ setEnabled(enabled);
+ setSummary(summary);
activityName = activity;
fragment = parent;
index = i;
@@ -274,13 +357,15 @@ public class HomeSettings extends SettingsPreferenceFragment {
icon.setEnabled(false);
icon.setColorFilter(grayscaleFilter);
} else {
+ icon.setEnabled(true);
icon.setOnClickListener(mDeleteClickListener);
icon.setTag(indexObj);
}
View v = view.findViewById(R.id.home_app_pref);
- v.setOnClickListener(mHomeClickListener);
v.setTag(indexObj);
+
+ v.setOnClickListener(mHomeClickListener);
}
void setChecked(boolean state) {
@@ -290,4 +375,59 @@ public class HomeSettings extends SettingsPreferenceFragment {
}
}
}
+
+ /**
+ * For search
+ */
+ public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider() {
+ @Override
+ public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
+ final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>();
+
+ final PackageManager pm = context.getPackageManager();
+ final ArrayList<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>();
+ pm.getHomeActivities(homeActivities);
+
+ final SharedPreferences sp = context.getSharedPreferences(
+ HomeSettings.HOME_PREFS, Context.MODE_PRIVATE);
+ final boolean doShowHome = sp.getBoolean(HomeSettings.HOME_PREFS_DO_SHOW, false);
+
+ // We index Home Launchers only if there are more than one or if we are showing the
+ // Home tile into the Dashboard
+ if (homeActivities.size() > 1 || doShowHome) {
+ final Resources res = context.getResources();
+
+ // Add fragment title
+ SearchIndexableRaw data = new SearchIndexableRaw(context);
+ data.title = res.getString(R.string.home_settings);
+ data.screenTitle = res.getString(R.string.home_settings);
+ data.keywords = res.getString(R.string.keywords_home);
+ result.add(data);
+
+ for (int i = 0; i < homeActivities.size(); i++) {
+ final ResolveInfo resolveInfo = homeActivities.get(i);
+ final ActivityInfo activityInfo = resolveInfo.activityInfo;
+
+ CharSequence name;
+ try {
+ name = activityInfo.loadLabel(pm);
+ if (TextUtils.isEmpty(name)) {
+ continue;
+ }
+ } catch (Exception e) {
+ Log.v(TAG, "Problem dealing with Home " + activityInfo.name, e);
+ continue;
+ }
+
+ data = new SearchIndexableRaw(context);
+ data.title = name.toString();
+ data.screenTitle = res.getString(R.string.home_settings);
+ result.add(data);
+ }
+ }
+
+ return result;
+ }
+ };
}
diff --git a/src/com/android/settings/KeyguardAppWidgetPickActivity.java b/src/com/android/settings/KeyguardAppWidgetPickActivity.java
deleted file mode 100644
index 7e801be..0000000
--- a/src/com/android/settings/KeyguardAppWidgetPickActivity.java
+++ /dev/null
@@ -1,634 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings;
-
-import android.app.Activity;
-import android.app.ActivityManager;
-import android.app.LauncherActivity.IconResizer;
-import android.appwidget.AppWidgetHost;
-import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProviderInfo;
-import android.content.ActivityNotFoundException;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.UserHandle;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.IWindowManager;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.widget.AdapterView;
-import android.widget.BaseAdapter;
-import android.widget.GridView;
-import android.widget.ImageView;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.android.internal.widget.LockPatternUtils;
-
-import java.lang.ref.WeakReference;
-import java.util.List;
-
-/**
- * Displays a list of {@link AppWidgetProviderInfo} widgets, along with any
- * injected special widgets specified through
- * {@link AppWidgetManager#EXTRA_CUSTOM_INFO} and
- * {@link AppWidgetManager#EXTRA_CUSTOM_EXTRAS}.
- * <p>
- * When an installed {@link AppWidgetProviderInfo} is selected, this activity
- * will bind it to the given {@link AppWidgetManager#EXTRA_APPWIDGET_ID},
- * otherwise it will return the requested extras.
- */
-public class KeyguardAppWidgetPickActivity extends Activity
- implements GridView.OnItemClickListener,
- AppWidgetLoader.ItemConstructor<KeyguardAppWidgetPickActivity.Item> {
- private static final String TAG = "KeyguardAppWidgetPickActivity";
- private static final int REQUEST_PICK_APPWIDGET = 126;
- private static final int REQUEST_CREATE_APPWIDGET = 127;
-
- private AppWidgetLoader<Item> mAppWidgetLoader;
- private List<Item> mItems;
- private GridView mGridView;
- private AppWidgetAdapter mAppWidgetAdapter;
- private AppWidgetManager mAppWidgetManager;
- private int mAppWidgetId;
- // Might make it possible to make this be false in future
- private boolean mAddingToKeyguard = true;
- private Intent mResultData;
- private LockPatternUtils mLockPatternUtils;
- private Bundle mExtraConfigureOptions;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- getWindow().addPrivateFlags(
- WindowManager.LayoutParams.PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR);
- setContentView(R.layout.keyguard_appwidget_picker_layout);
- super.onCreate(savedInstanceState);
-
- // Set default return data
- setResultData(RESULT_CANCELED, null);
-
- // Read the appWidgetId passed our direction, otherwise bail if not found
- final Intent intent = getIntent();
- if (intent.hasExtra(AppWidgetManager.EXTRA_APPWIDGET_ID)) {
- mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
- AppWidgetManager.INVALID_APPWIDGET_ID);
- } else {
- finish();
- }
- mExtraConfigureOptions = intent.getBundleExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS);
-
- mGridView = (GridView) findViewById(R.id.widget_list);
- DisplayMetrics dm = new DisplayMetrics();
- getWindowManager().getDefaultDisplay().getMetrics(dm);
- int maxGridWidth = getResources().getDimensionPixelSize(
- R.dimen.keyguard_appwidget_picker_max_width);
-
- if (maxGridWidth < dm.widthPixels) {
- mGridView.getLayoutParams().width = maxGridWidth;
- }
- mAppWidgetManager = AppWidgetManager.getInstance(this);
- mAppWidgetLoader = new AppWidgetLoader<Item>(this, mAppWidgetManager, this);
- mItems = mAppWidgetLoader.getItems(getIntent());
- mAppWidgetAdapter = new AppWidgetAdapter(this, mItems);
- mGridView.setAdapter(mAppWidgetAdapter);
- mGridView.setOnItemClickListener(this);
-
- mLockPatternUtils = new LockPatternUtils(this); // TEMP-- we want to delete this
- }
-
- /**
- * Convenience method for setting the result code and intent. This method
- * correctly injects the {@link AppWidgetManager#EXTRA_APPWIDGET_ID} that
- * most hosts expect returned.
- */
- void setResultData(int code, Intent intent) {
- Intent result = intent != null ? intent : new Intent();
- result.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
- mResultData = result;
- setResult(code, result);
- }
-
- /**
- * Item that appears in the AppWidget picker grid.
- */
- public static class Item implements AppWidgetLoader.LabelledItem {
- protected static IconResizer sResizer;
-
-
- CharSequence label;
- int appWidgetPreviewId;
- int iconId;
- String packageName;
- String className;
- Bundle extras;
- private WidgetPreviewLoader mWidgetPreviewLoader;
- private Context mContext;
-
- /**
- * Create a list item from given label and icon.
- */
- Item(Context context, CharSequence label) {
- this.label = label;
- mContext = context;
- }
-
- void loadWidgetPreview(ImageView v) {
- mWidgetPreviewLoader = new WidgetPreviewLoader(mContext, v);
- mWidgetPreviewLoader.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
- }
-
- void cancelLoadingWidgetPreview() {
- if (mWidgetPreviewLoader != null) {
- mWidgetPreviewLoader.cancel(false);
- mWidgetPreviewLoader = null;
- }
- }
-
- /**
- * Build the {@link Intent} described by this item. If this item
- * can't create a valid {@link android.content.ComponentName}, it will return
- * {@link Intent#ACTION_CREATE_SHORTCUT} filled with the item label.
- */
- Intent getIntent() {
- Intent intent = new Intent();
- if (packageName != null && className != null) {
- // Valid package and class, so fill details as normal intent
- intent.setClassName(packageName, className);
- if (extras != null) {
- intent.putExtras(extras);
- }
- } else {
- // No valid package or class, so treat as shortcut with label
- intent.setAction(Intent.ACTION_CREATE_SHORTCUT);
- intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, label);
- }
- return intent;
- }
-
- public CharSequence getLabel() {
- return label;
- }
-
- class WidgetPreviewLoader extends AsyncTask<Void, Bitmap, Void> {
- private Resources mResources;
- private PackageManager mPackageManager;
- private int mIconDpi;
- private ImageView mView;
- public WidgetPreviewLoader(Context context, ImageView v) {
- super();
- mResources = context.getResources();
- mPackageManager = context.getPackageManager();
- ActivityManager activityManager =
- (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
- mIconDpi = activityManager.getLauncherLargeIconDensity();
- mView = v;
- }
- public Void doInBackground(Void... params) {
- if (!isCancelled()) {
- int appWidgetPreviewWidth =
- mResources.getDimensionPixelSize(R.dimen.appwidget_preview_width);
- int appWidgetPreviewHeight =
- mResources.getDimensionPixelSize(R.dimen.appwidget_preview_height);
- Bitmap b = getWidgetPreview(new ComponentName(packageName, className),
- appWidgetPreviewId, iconId,
- appWidgetPreviewWidth, appWidgetPreviewHeight);
- publishProgress(b);
- }
- return null;
- }
- public void onProgressUpdate(Bitmap... values) {
- if (!isCancelled()) {
- Bitmap b = values[0];
- mView.setImageBitmap(b);
- }
- }
- abstract class WeakReferenceThreadLocal<T> {
- private ThreadLocal<WeakReference<T>> mThreadLocal;
- public WeakReferenceThreadLocal() {
- mThreadLocal = new ThreadLocal<WeakReference<T>>();
- }
-
- abstract T initialValue();
-
- public void set(T t) {
- mThreadLocal.set(new WeakReference<T>(t));
- }
-
- public T get() {
- WeakReference<T> reference = mThreadLocal.get();
- T obj;
- if (reference == null) {
- obj = initialValue();
- mThreadLocal.set(new WeakReference<T>(obj));
- return obj;
- } else {
- obj = reference.get();
- if (obj == null) {
- obj = initialValue();
- mThreadLocal.set(new WeakReference<T>(obj));
- }
- return obj;
- }
- }
- }
-
- class CanvasCache extends WeakReferenceThreadLocal<Canvas> {
- @Override
- protected Canvas initialValue() {
- return new Canvas();
- }
- }
-
- class PaintCache extends WeakReferenceThreadLocal<Paint> {
- @Override
- protected Paint initialValue() {
- return null;
- }
- }
-
- class BitmapCache extends WeakReferenceThreadLocal<Bitmap> {
- @Override
- protected Bitmap initialValue() {
- return null;
- }
- }
-
- class RectCache extends WeakReferenceThreadLocal<Rect> {
- @Override
- protected Rect initialValue() {
- return new Rect();
- }
- }
-
- // Used for drawing widget previews
- CanvasCache sCachedAppWidgetPreviewCanvas = new CanvasCache();
- RectCache sCachedAppWidgetPreviewSrcRect = new RectCache();
- RectCache sCachedAppWidgetPreviewDestRect = new RectCache();
- PaintCache sCachedAppWidgetPreviewPaint = new PaintCache();
-
- private Bitmap getWidgetPreview(ComponentName provider, int previewImage,
- int iconId, int maxWidth, int maxHeight) {
- // Load the preview image if possible
- String packageName = provider.getPackageName();
- if (maxWidth < 0) maxWidth = Integer.MAX_VALUE;
- if (maxHeight < 0) maxHeight = Integer.MAX_VALUE;
-
-
- int appIconSize = mResources.getDimensionPixelSize(R.dimen.app_icon_size);
-
- Drawable drawable = null;
- if (previewImage != 0) {
- drawable = mPackageManager.getDrawable(packageName, previewImage, null);
- if (drawable == null) {
- Log.w(TAG, "Can't load widget preview drawable 0x" +
- Integer.toHexString(previewImage) + " for provider: " + provider);
- }
- }
-
- int bitmapWidth;
- int bitmapHeight;
- Bitmap defaultPreview = null;
- boolean widgetPreviewExists = (drawable != null);
- if (widgetPreviewExists) {
- bitmapWidth = drawable.getIntrinsicWidth();
- bitmapHeight = drawable.getIntrinsicHeight();
- } else {
- // Generate a preview image if we couldn't load one
- bitmapWidth = appIconSize;
- bitmapHeight = appIconSize;
- defaultPreview = Bitmap.createBitmap(bitmapWidth, bitmapHeight,
- Config.ARGB_8888);
-
- try {
- Drawable icon = null;
- if (iconId > 0)
- icon = getFullResIcon(packageName, iconId);
- if (icon != null) {
- renderDrawableToBitmap(icon, defaultPreview, 0,
- 0, appIconSize, appIconSize);
- }
- } catch (Resources.NotFoundException e) {
- }
- }
-
- // Scale to fit width only - let the widget preview be clipped in the
- // vertical dimension
- float scale = 1f;
- if (bitmapWidth > maxWidth) {
- scale = maxWidth / (float) bitmapWidth;
- }
- int finalPreviewWidth = (int) (scale * bitmapWidth);
- int finalPreviewHeight = (int) (scale * bitmapHeight);
-
- bitmapWidth = finalPreviewWidth;
- bitmapHeight = Math.min(finalPreviewHeight, maxHeight);
-
- Bitmap preview = Bitmap.createBitmap(bitmapWidth, bitmapHeight,
- Config.ARGB_8888);
-
- // Draw the scaled preview into the final bitmap
- if (widgetPreviewExists) {
- renderDrawableToBitmap(drawable, preview, 0, 0, finalPreviewWidth,
- finalPreviewHeight);
- } else {
- final Canvas c = sCachedAppWidgetPreviewCanvas.get();
- final Rect src = sCachedAppWidgetPreviewSrcRect.get();
- final Rect dest = sCachedAppWidgetPreviewDestRect.get();
- c.setBitmap(preview);
- src.set(0, 0, defaultPreview.getWidth(), defaultPreview.getHeight());
- dest.set(0, 0, finalPreviewWidth, finalPreviewHeight);
-
- Paint p = sCachedAppWidgetPreviewPaint.get();
- if (p == null) {
- p = new Paint();
- p.setFilterBitmap(true);
- sCachedAppWidgetPreviewPaint.set(p);
- }
- c.drawBitmap(defaultPreview, src, dest, p);
- c.setBitmap(null);
- }
- return preview;
- }
- public Drawable getFullResDefaultActivityIcon() {
- return getFullResIcon(Resources.getSystem(),
- android.R.mipmap.sym_def_app_icon);
- }
-
- public Drawable getFullResIcon(Resources resources, int iconId) {
- Drawable d;
- try {
- d = resources.getDrawableForDensity(iconId, mIconDpi);
- } catch (Resources.NotFoundException e) {
- d = null;
- }
-
- return (d != null) ? d : getFullResDefaultActivityIcon();
- }
-
- public Drawable getFullResIcon(String packageName, int iconId) {
- Resources resources;
- try {
- resources = mPackageManager.getResourcesForApplication(packageName);
- } catch (PackageManager.NameNotFoundException e) {
- resources = null;
- }
- if (resources != null) {
- if (iconId != 0) {
- return getFullResIcon(resources, iconId);
- }
- }
- return getFullResDefaultActivityIcon();
- }
-
- private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h) {
- renderDrawableToBitmap(d, bitmap, x, y, w, h, 1f);
- }
-
- private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h,
- float scale) {
- if (bitmap != null) {
- Canvas c = new Canvas(bitmap);
- c.scale(scale, scale);
- Rect oldBounds = d.copyBounds();
- d.setBounds(x, y, x + w, y + h);
- d.draw(c);
- d.setBounds(oldBounds); // Restore the bounds
- c.setBitmap(null);
- }
- }
- }
- }
-
- @Override
- public Item createItem(Context context, AppWidgetProviderInfo info, Bundle extras) {
- CharSequence label = info.label;
-
- Item item = new Item(context, label);
- item.appWidgetPreviewId = info.previewImage;
- item.iconId = info.icon;
- item.packageName = info.provider.getPackageName();
- item.className = info.provider.getClassName();
- item.extras = extras;
- return item;
- }
-
- protected static class AppWidgetAdapter extends BaseAdapter {
- private final LayoutInflater mInflater;
- private final List<Item> mItems;
-
- /**
- * Create an adapter for the given items.
- */
- public AppWidgetAdapter(Context context, List<Item> items) {
- mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- mItems = items;
- }
-
- /**
- * {@inheritDoc}
- */
- public int getCount() {
- return mItems.size();
- }
-
- /**
- * {@inheritDoc}
- */
- public Object getItem(int position) {
- return mItems.get(position);
- }
-
- /**
- * {@inheritDoc}
- */
- public long getItemId(int position) {
- return position;
- }
-
- /**
- * {@inheritDoc}
- */
- public View getView(int position, View convertView, ViewGroup parent) {
- if (convertView == null) {
- convertView = mInflater.inflate(R.layout.keyguard_appwidget_item, parent, false);
- }
-
- Item item = (Item) getItem(position);
- TextView textView = (TextView) convertView.findViewById(R.id.label);
- textView.setText(item.label);
- ImageView iconView = (ImageView) convertView.findViewById(R.id.icon);
- iconView.setImageDrawable(null);
- item.loadWidgetPreview(iconView);
- return convertView;
- }
-
- public void cancelAllWidgetPreviewLoaders() {
- for (int i = 0; i < mItems.size(); i++) {
- mItems.get(i).cancelLoadingWidgetPreview();
- }
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- Item item = mItems.get(position);
- Intent intent = item.getIntent();
-
- int result;
- if (item.extras != null) {
- // If these extras are present it's because this entry is custom.
- // Don't try to bind it, just pass it back to the app.
- result = RESULT_OK;
- setResultData(result, intent);
- } else {
- try {
- if (mAddingToKeyguard && mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
- // Found in KeyguardHostView.java
- final int KEYGUARD_HOST_ID = 0x4B455947;
- int userId = ActivityManager.getCurrentUser();
- mAppWidgetId = AppWidgetHost.allocateAppWidgetIdForPackage(KEYGUARD_HOST_ID,
- userId, "com.android.keyguard");
- }
- mAppWidgetManager.bindAppWidgetId(
- mAppWidgetId, intent.getComponent(), mExtraConfigureOptions);
- result = RESULT_OK;
- } catch (IllegalArgumentException e) {
- // This is thrown if they're already bound, or otherwise somehow
- // bogus. Set the result to canceled, and exit. The app *should*
- // clean up at this point. We could pass the error along, but
- // it's not clear that that's useful -- the widget will simply not
- // appear.
- result = RESULT_CANCELED;
- }
- setResultData(result, null);
- }
- if (mAddingToKeyguard) {
- onActivityResult(REQUEST_PICK_APPWIDGET, result, mResultData);
- } else {
- finish();
- }
- }
-
- protected void onDestroy() {
- if (mAppWidgetAdapter != null) {
- mAppWidgetAdapter.cancelAllWidgetPreviewLoaders();
- }
- super.onDestroy();
- }
-
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- if (requestCode == REQUEST_PICK_APPWIDGET || requestCode == REQUEST_CREATE_APPWIDGET) {
- int appWidgetId;
- if (data == null) {
- appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID ;
- } else {
- appWidgetId = data.getIntExtra(
- AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
- }
- if (requestCode == REQUEST_PICK_APPWIDGET && resultCode == Activity.RESULT_OK) {
- AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this);
-
- AppWidgetProviderInfo appWidget = null;
- appWidget = appWidgetManager.getAppWidgetInfo(appWidgetId);
-
- if (appWidget.configure != null) {
- // Launch over to configure widget, if needed
- Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
- intent.setComponent(appWidget.configure);
- intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
-
- startActivityForResultSafely(intent, REQUEST_CREATE_APPWIDGET);
- } else {
- // Otherwise just add it
- onActivityResult(REQUEST_CREATE_APPWIDGET, Activity.RESULT_OK, data);
- }
- } else if (requestCode == REQUEST_CREATE_APPWIDGET && resultCode == Activity.RESULT_OK) {
- mLockPatternUtils.addAppWidget(appWidgetId, 0);
- finishDelayedAndShowLockScreen(appWidgetId);
- } else {
- if (mAddingToKeyguard &&
- mAppWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) {
- int userId = ActivityManager.getCurrentUser();
- AppWidgetHost.deleteAppWidgetIdForSystem(mAppWidgetId, userId);
- }
- finishDelayedAndShowLockScreen(AppWidgetManager.INVALID_APPWIDGET_ID);
- }
- }
- }
-
- private void finishDelayedAndShowLockScreen(int appWidgetId) {
- IBinder b = ServiceManager.getService(Context.WINDOW_SERVICE);
- IWindowManager iWm = IWindowManager.Stub.asInterface(b);
- Bundle opts = null;
- if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) {
- opts = new Bundle();
- opts.putInt(LockPatternUtils.KEYGUARD_SHOW_APPWIDGET, appWidgetId);
- }
- try {
- iWm.lockNow(opts);
- } catch (RemoteException e) {
- }
-
- // Change background to all black
- ViewGroup root = (ViewGroup) findViewById(R.id.layout_root);
- root.setBackgroundColor(0xFF000000);
- // Hide all children
- final int childCount = root.getChildCount();
- for (int i = 0; i < childCount; i++) {
- root.getChildAt(i).setVisibility(View.INVISIBLE);
- }
- mGridView.postDelayed(new Runnable() {
- public void run() {
- finish();
- }
- }, 500);
- }
-
- void startActivityForResultSafely(Intent intent, int requestCode) {
- try {
- startActivityForResult(intent, requestCode);
- } catch (ActivityNotFoundException e) {
- Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
- } catch (SecurityException e) {
- Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
- Log.e(TAG, "Settings does not have the permission to launch " + intent, e);
- }
- }
-}
diff --git a/src/com/android/settings/LocalePicker.java b/src/com/android/settings/LocalePicker.java
index 6600703..c6158b1 100644
--- a/src/com/android/settings/LocalePicker.java
+++ b/src/com/android/settings/LocalePicker.java
@@ -48,15 +48,6 @@ public class LocalePicker extends com.android.internal.app.LocalePicker
}
@Override
- protected boolean isInDeveloperMode() {
- final boolean showDev = getActivity().getSharedPreferences(DevelopmentSettings.PREF_FILE,
- Context.MODE_PRIVATE).getBoolean(
- DevelopmentSettings.PREF_SHOW,
- android.os.Build.TYPE.equals("eng"));
- return showDev;
- }
-
- @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null && savedInstanceState.containsKey(SAVE_TARGET_LOCALE)) {
diff --git a/src/com/android/settings/ManagedProfileSetup.java b/src/com/android/settings/ManagedProfileSetup.java
new file mode 100644
index 0000000..198abe0
--- /dev/null
+++ b/src/com/android/settings/ManagedProfileSetup.java
@@ -0,0 +1,86 @@
+/*
+ * 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.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.util.Log;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import java.util.List;
+
+import static android.content.pm.PackageManager.GET_ACTIVITIES;
+import static android.content.pm.PackageManager.GET_META_DATA;
+import static android.content.pm.PackageManager.GET_RESOLVED_FILTER;
+
+/**
+ * Listens to {@link Intent.ACTION_BOOT_COMPLETED} and {@link Intent.ACTION_PRE_BOOT_COMPLETED}
+ * performs setup steps for a managed profile (disables the launcher icon of the Settings app and
+ * adds cross-profile intent filters for the appropriate Settings activities).
+ */
+public class ManagedProfileSetup extends BroadcastReceiver {
+ private static final String TAG = "Settings";
+ private static final String PRIMARY_PROFILE_SETTING =
+ "com.android.settings.PRIMARY_PROFILE_CONTROLLED";
+
+ @Override
+ public void onReceive(Context context, Intent broadcast) {
+ final UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ if (!Utils.isManagedProfile(um)) {
+ return;
+ }
+ Log.i(TAG, "Received broadcast: " + broadcast.getAction()
+ + ". Setting up intent forwarding for managed profile.");
+ final PackageManager pm = context.getPackageManager();
+ // Clear any previous intent forwarding we set up
+ pm.clearCrossProfileIntentFilters(UserHandle.myUserId());
+
+ // Set up intent forwarding for implicit intents
+ Intent intent = new Intent();
+ intent.addCategory(Intent.CATEGORY_DEFAULT);
+ intent.setPackage(context.getPackageName());
+
+ // Resolves activities for the managed profile (which we're running as)
+ List<ResolveInfo> resolvedIntents = pm.queryIntentActivities(intent,
+ GET_ACTIVITIES | GET_META_DATA | GET_RESOLVED_FILTER);
+ final int count = resolvedIntents.size();
+ for (int i = 0; i < count; i++) {
+ ResolveInfo info = resolvedIntents.get(i);
+ if (info.filter != null && info.activityInfo != null
+ && info.activityInfo.metaData != null) {
+ boolean shouldForward = info.activityInfo.metaData.getBoolean(
+ PRIMARY_PROFILE_SETTING);
+ if (shouldForward) {
+ pm.addCrossProfileIntentFilter(info.filter, UserHandle.myUserId(),
+ UserHandle.USER_OWNER, PackageManager.SKIP_CURRENT_PROFILE);
+ }
+ }
+ }
+
+ // Disable launcher icon
+ // Note: This needs to happen after forwarding intents, otherwise the main Settings
+ // intent gets lost
+ ComponentName settingsComponentName = new ComponentName(context, Settings.class);
+ pm.setComponentEnabledSetting(settingsComponentName,
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
+ }
+}
diff --git a/src/com/android/settings/MasterClear.java b/src/com/android/settings/MasterClear.java
index 262aca3..f789b93 100644
--- a/src/com/android/settings/MasterClear.java
+++ b/src/com/android/settings/MasterClear.java
@@ -28,10 +28,10 @@ import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Environment;
+import android.os.Process;
import android.os.SystemProperties;
import android.os.UserManager;
import android.preference.Preference;
-import android.preference.PreferenceActivity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -55,7 +55,6 @@ public class MasterClear extends Fragment {
private static final String TAG = "MasterClear";
private static final int KEYGUARD_REQUEST = 55;
- private static final int PIN_REQUEST = 56;
static final String ERASE_EXTERNAL_EXTRA = "erase_sd";
@@ -63,7 +62,6 @@ public class MasterClear extends Fragment {
private Button mInitiateButton;
private View mExternalStorageContainer;
private CheckBox mExternalStorage;
- private boolean mPinConfirmed;
/**
* Keyguard validation is run using the standard {@link ConfirmLockPattern}
@@ -79,25 +77,11 @@ public class MasterClear extends Fragment {
res.getText(R.string.master_clear_gesture_explanation));
}
- private boolean runRestrictionsChallenge() {
- if (UserManager.get(getActivity()).hasRestrictionsChallenge()) {
- startActivityForResult(
- new Intent(Intent.ACTION_RESTRICTIONS_CHALLENGE), PIN_REQUEST);
- return true;
- }
- return false;
- }
-
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
- if (requestCode == PIN_REQUEST) {
- if (resultCode == Activity.RESULT_OK) {
- mPinConfirmed = true;
- }
- return;
- } else if (requestCode != KEYGUARD_REQUEST) {
+ if (requestCode != KEYGUARD_REQUEST) {
return;
}
@@ -115,7 +99,7 @@ public class MasterClear extends Fragment {
preference.setFragment(MasterClearConfirm.class.getName());
preference.setTitle(R.string.master_clear_confirm_title);
preference.getExtras().putBoolean(ERASE_EXTERNAL_EXTRA, mExternalStorage.isChecked());
- ((PreferenceActivity) getActivity()).onPreferenceStartFragment(null, preference);
+ ((SettingsActivity) getActivity()).onPreferenceStartFragment(null, preference);
}
/**
@@ -126,10 +110,6 @@ public class MasterClear extends Fragment {
private final Button.OnClickListener mInitiateListener = new Button.OnClickListener() {
public void onClick(View v) {
- mPinConfirmed = false;
- if (runRestrictionsChallenge()) {
- return;
- }
if (!runKeyguardConfirmation(KEYGUARD_REQUEST)) {
showFinalConfirmation();
}
@@ -255,22 +235,15 @@ public class MasterClear extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
+ if (!Process.myUserHandle().isOwner()
+ || UserManager.get(getActivity()).hasUserRestriction(
+ UserManager.DISALLOW_FACTORY_RESET)) {
+ return inflater.inflate(R.layout.master_clear_disallowed_screen, null);
+ }
+
mContentView = inflater.inflate(R.layout.master_clear, null);
establishInitialState();
return mContentView;
}
-
- @Override
- public void onResume() {
- super.onResume();
-
- // If this is the second step after restrictions pin challenge
- if (mPinConfirmed) {
- mPinConfirmed = false;
- if (!runKeyguardConfirmation(KEYGUARD_REQUEST)) {
- showFinalConfirmation();
- }
- }
- }
}
diff --git a/src/com/android/settings/MasterClearConfirm.java b/src/com/android/settings/MasterClearConfirm.java
index 9c15c73..3521aa3 100644
--- a/src/com/android/settings/MasterClearConfirm.java
+++ b/src/com/android/settings/MasterClearConfirm.java
@@ -16,19 +16,21 @@
package com.android.settings;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.os.AsyncTask;
+import android.service.persistentdata.PersistentDataBlockManager;
import com.android.internal.os.storage.ExternalStorageFormatter;
-import com.android.internal.widget.LockPatternUtils;
-import android.app.Activity;
import android.app.Fragment;
import android.content.Intent;
-import android.content.res.Resources;
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.widget.CheckBox;
/**
* Confirm and execute a reset of the device to a clean "just out of the box"
@@ -44,7 +46,6 @@ public class MasterClearConfirm extends Fragment {
private View mContentView;
private boolean mEraseSdCard;
- private Button mFinalButton;
/**
* The user has gone through the multiple confirmation, so now we go ahead
@@ -58,28 +59,79 @@ public class MasterClearConfirm extends Fragment {
return;
}
- if (mEraseSdCard) {
- Intent intent = new Intent(ExternalStorageFormatter.FORMAT_AND_FACTORY_RESET);
- intent.setComponent(ExternalStorageFormatter.COMPONENT_NAME);
- getActivity().startService(intent);
+ 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.
+ final ProgressDialog progressDialog = getProgressDialog();
+ progressDialog.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
+ final int oldOrientation = getActivity().getRequestedOrientation();
+ getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ pdbManager.wipe();
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void aVoid) {
+ progressDialog.hide();
+ getActivity().setRequestedOrientation(oldOrientation);
+ doMasterClear();
+ }
+ }.execute();
} else {
- getActivity().sendBroadcast(new Intent("android.intent.action.MASTER_CLEAR"));
- // Intent handling is asynchronous -- assume it will happen soon.
+ doMasterClear();
}
}
+
+ private ProgressDialog getProgressDialog() {
+ final ProgressDialog progressDialog = new ProgressDialog(getActivity());
+ 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));
+ return progressDialog;
+ }
};
+ private void doMasterClear() {
+ if (mEraseSdCard) {
+ Intent intent = new Intent(ExternalStorageFormatter.FORMAT_AND_FACTORY_RESET);
+ intent.putExtra(Intent.EXTRA_REASON, "MasterClearConfirm");
+ intent.setComponent(ExternalStorageFormatter.COMPONENT_NAME);
+ getActivity().startService(intent);
+ } else {
+ Intent intent = new Intent(Intent.ACTION_MASTER_CLEAR);
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ intent.putExtra(Intent.EXTRA_REASON, "MasterClearConfirm");
+ getActivity().sendBroadcast(intent);
+ // Intent handling is asynchronous -- assume it will happen soon.
+ }
+ }
+
/**
* Configure the UI for the final confirmation interaction
*/
private void establishFinalConfirmationState() {
- mFinalButton = (Button) mContentView.findViewById(R.id.execute_master_clear);
- mFinalButton.setOnClickListener(mFinalClickListener);
+ mContentView.findViewById(R.id.execute_master_clear)
+ .setOnClickListener(mFinalClickListener);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
+ if (UserManager.get(getActivity()).hasUserRestriction(
+ UserManager.DISALLOW_FACTORY_RESET)) {
+ return inflater.inflate(R.layout.master_clear_disallowed_screen, null);
+ }
mContentView = inflater.inflate(R.layout.master_clear_confirm, null);
establishFinalConfirmationState();
return mContentView;
@@ -90,6 +142,6 @@ public class MasterClearConfirm extends Fragment {
super.onCreate(savedInstanceState);
Bundle args = getArguments();
- mEraseSdCard = args != null ? args.getBoolean(MasterClear.ERASE_EXTERNAL_EXTRA) : false;
+ mEraseSdCard = args != null && args.getBoolean(MasterClear.ERASE_EXTERNAL_EXTRA);
}
}
diff --git a/src/com/android/settings/OwnerInfoSettings.java b/src/com/android/settings/OwnerInfoSettings.java
index 2f7721b..0761f38 100644
--- a/src/com/android/settings/OwnerInfoSettings.java
+++ b/src/com/android/settings/OwnerInfoSettings.java
@@ -60,18 +60,11 @@ public class OwnerInfoSettings extends Fragment {
mView = inflater.inflate(R.layout.ownerinfo, container, false);
mUserId = UserHandle.myUserId();
mLockPatternUtils = new LockPatternUtils(getActivity());
- initView(mView);
+ initView();
return mView;
}
- private void initView(View view) {
- final ContentResolver res = getActivity().getContentResolver();
- String info = mLockPatternUtils.getOwnerInfo(mUserId);
- boolean enabled = mLockPatternUtils.isOwnerInfoEnabled();
- mCheckbox = (CheckBox) mView.findViewById(R.id.show_owner_info_on_lockscreen_checkbox);
- mOwnerInfo = (EditText) mView.findViewById(R.id.owner_info_edit_text);
- mOwnerInfo.setText(info);
- mOwnerInfo.setEnabled(enabled);
+ private void initView() {
mNickname = (EditText) mView.findViewById(R.id.owner_info_nickname);
if (!mShowNickname) {
mNickname.setVisibility(View.GONE);
@@ -79,6 +72,10 @@ public class OwnerInfoSettings extends Fragment {
mNickname.setText(UserManager.get(getActivity()).getUserName());
mNickname.setSelected(true);
}
+
+ final boolean enabled = mLockPatternUtils.isOwnerInfoEnabled();
+
+ mCheckbox = (CheckBox) mView.findViewById(R.id.show_owner_info_on_lockscreen_checkbox);
mCheckbox.setChecked(enabled);
if (UserHandle.myUserId() != UserHandle.USER_OWNER) {
if (UserManager.get(getActivity()).isLinkedUser()) {
@@ -93,6 +90,14 @@ public class OwnerInfoSettings extends Fragment {
mOwnerInfo.setEnabled(isChecked); // disable text field if not enabled
}
});
+
+ String info = mLockPatternUtils.getOwnerInfo(mUserId);
+
+ mOwnerInfo = (EditText) mView.findViewById(R.id.owner_info_edit_text);
+ mOwnerInfo.setEnabled(enabled);
+ if (!TextUtils.isEmpty(info)) {
+ mOwnerInfo.setText(info);
+ }
}
@Override
@@ -102,7 +107,6 @@ public class OwnerInfoSettings extends Fragment {
}
void saveChanges() {
- ContentResolver res = getActivity().getContentResolver();
String info = mOwnerInfo.getText().toString();
mLockPatternUtils.setOwnerInfo(info, mUserId);
if (mShowNickname) {
@@ -114,5 +118,4 @@ public class OwnerInfoSettings extends Fragment {
}
}
}
-
}
diff --git a/src/com/android/settings/PinnedHeaderListFragment.java b/src/com/android/settings/PinnedHeaderListFragment.java
new file mode 100644
index 0000000..2ef5f02
--- /dev/null
+++ b/src/com/android/settings/PinnedHeaderListFragment.java
@@ -0,0 +1,47 @@
+/*
+ * 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.app.ListFragment;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A ListFragment with a pinned header
+ */
+public class PinnedHeaderListFragment extends ListFragment {
+
+ public PinnedHeaderListFragment() {
+ super();
+ }
+
+ /**
+ * Set the pinned header view. This can only be done when the ListView is already created.
+ *
+ * @param pinnedHeaderView the view to be used for the pinned header view.
+ */
+ public void setPinnedHeaderView(View pinnedHeaderView) {
+ ((ViewGroup) getListView().getParent()).addView(pinnedHeaderView, 0);
+ }
+
+ /**
+ * Clear the pinned header view. This can only be done when the ListView is already created.
+ */
+ public void clearPinnedHeaderView() {
+ ((ViewGroup) getListView().getParent()).removeViewAt(0);
+ }
+}
diff --git a/src/com/android/settings/PrivacySettings.java b/src/com/android/settings/PrivacySettings.java
index d936f46..0a9f086 100644
--- a/src/com/android/settings/PrivacySettings.java
+++ b/src/com/android/settings/PrivacySettings.java
@@ -24,13 +24,24 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.preference.CheckBoxPreference;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.PreferenceScreen;
+import android.preference.SwitchPreference;
+import android.provider.SearchIndexableResource;
import android.provider.Settings;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.search.Indexable.SearchIndexProvider;
+
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Gesture lock pattern settings.
*/
@@ -43,11 +54,13 @@ public class PrivacySettings extends SettingsPreferenceFragment implements
private static final String BACKUP_DATA = "backup_data";
private static final String AUTO_RESTORE = "auto_restore";
private static final String CONFIGURE_ACCOUNT = "configure_account";
+ private static final String PERSONAL_DATA_CATEGORY = "personal_data_category";
private IBackupManager mBackupManager;
- private CheckBoxPreference mBackup;
- private CheckBoxPreference mAutoRestore;
+ private SwitchPreference mBackup;
+ private SwitchPreference mAutoRestore;
private Dialog mConfirmDialog;
private PreferenceScreen mConfigure;
+ private boolean mEnabled;
private static final int DIALOG_ERASE_BACKUP = 2;
private int mDialogType;
@@ -55,16 +68,30 @@ public class PrivacySettings extends SettingsPreferenceFragment implements
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ // Don't allow any access if this is a secondary user
+ mEnabled = Process.myUserHandle().isOwner();
+ if (!mEnabled) {
+ return;
+ }
+
addPreferencesFromResource(R.xml.privacy_settings);
final PreferenceScreen screen = getPreferenceScreen();
-
mBackupManager = IBackupManager.Stub.asInterface(
ServiceManager.getService(Context.BACKUP_SERVICE));
- mBackup = (CheckBoxPreference) screen.findPreference(BACKUP_DATA);
- mAutoRestore = (CheckBoxPreference) screen.findPreference(AUTO_RESTORE);
+ mBackup = (SwitchPreference) screen.findPreference(BACKUP_DATA);
+ mBackup.setOnPreferenceChangeListener(preferenceChangeListener);
+
+ mAutoRestore = (SwitchPreference) screen.findPreference(AUTO_RESTORE);
+ mAutoRestore.setOnPreferenceChangeListener(preferenceChangeListener);
+
mConfigure = (PreferenceScreen) screen.findPreference(CONFIGURE_ACCOUNT);
+ if (UserManager.get(getActivity()).hasUserRestriction(
+ UserManager.DISALLOW_FACTORY_RESET)) {
+ screen.removePreference(findPreference(PERSONAL_DATA_CATEGORY));
+ }
+
// Vendor specific
if (getActivity().getPackageManager().
resolveContentProvider(GSETTINGS_PROVIDER, 0) == null) {
@@ -78,7 +105,9 @@ public class PrivacySettings extends SettingsPreferenceFragment implements
super.onResume();
// Refresh UI
- updateToggles();
+ if (mEnabled) {
+ updateToggles();
+ }
}
@Override
@@ -91,35 +120,41 @@ public class PrivacySettings extends SettingsPreferenceFragment implements
super.onStop();
}
- @Override
- public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
- Preference preference) {
- if (preference == mBackup) {
- if (!mBackup.isChecked()) {
- showEraseBackupDialog();
- } else {
- setBackupEnabled(true);
+ private OnPreferenceChangeListener preferenceChangeListener = new OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if (!(preference instanceof SwitchPreference)) {
+ return true;
}
- } else if (preference == mAutoRestore) {
- boolean curState = mAutoRestore.isChecked();
- try {
- mBackupManager.setAutoRestore(curState);
- } catch (RemoteException e) {
- mAutoRestore.setChecked(!curState);
+ boolean nextValue = (Boolean) newValue;
+ boolean result = false;
+ if (preference == mBackup) {
+ if (nextValue == false) {
+ // Don't change Switch status until user makes choice in dialog
+ // so return false here.
+ showEraseBackupDialog();
+ } else {
+ setBackupEnabled(true);
+ result = true;
+ }
+ } else if (preference == mAutoRestore) {
+ try {
+ mBackupManager.setAutoRestore(nextValue);
+ result = true;
+ } catch (RemoteException e) {
+ mAutoRestore.setChecked(!nextValue);
+ }
}
+ return result;
}
- return super.onPreferenceTreeClick(preferenceScreen, preference);
- }
+ };
private void showEraseBackupDialog() {
- mBackup.setChecked(true);
-
mDialogType = DIALOG_ERASE_BACKUP;
CharSequence msg = getResources().getText(R.string.backup_erase_dialog_message);
// TODO: DialogFragment?
mConfirmDialog = new AlertDialog.Builder(getActivity()).setMessage(msg)
.setTitle(R.string.backup_erase_dialog_title)
- .setIconAttribute(android.R.attr.alertDialogIcon)
.setPositiveButton(android.R.string.ok, this)
.setNegativeButton(android.R.string.cancel, this)
.show();
@@ -153,7 +188,7 @@ public class PrivacySettings extends SettingsPreferenceFragment implements
mConfigure.setEnabled(configureEnabled);
mConfigure.setIntent(configIntent);
setConfigureSummary(configSummary);
-}
+ }
private void setConfigureSummary(String summary) {
if (summary != null) {
@@ -173,13 +208,20 @@ public class PrivacySettings extends SettingsPreferenceFragment implements
}
}
+ @Override
public void onClick(DialogInterface dialog, int which) {
- if (which == DialogInterface.BUTTON_POSITIVE) {
- //updateProviders();
- if (mDialogType == DIALOG_ERASE_BACKUP) {
+ // Dialog is triggered before Switch status change, that means marking the Switch to
+ // true in showEraseBackupDialog() method will be override by following status change.
+ // So we do manual switching here due to users' response.
+ if (mDialogType == DIALOG_ERASE_BACKUP) {
+ // Accept turning off backup
+ if (which == DialogInterface.BUTTON_POSITIVE) {
setBackupEnabled(false);
- updateConfigureSummary();
+ } else if (which == DialogInterface.BUTTON_NEGATIVE) {
+ // Reject turning off backup
+ setBackupEnabled(true);
}
+ updateConfigureSummary();
}
mDialogType = 0;
}
@@ -208,4 +250,40 @@ public class PrivacySettings extends SettingsPreferenceFragment implements
protected int getHelpResource() {
return R.string.help_url_backup_reset;
}
-}
+
+ /**
+ * For Search.
+ */
+ public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new PrivacySearchIndexProvider();
+
+ private static class PrivacySearchIndexProvider extends BaseSearchIndexProvider {
+
+ boolean mIsPrimary;
+
+ public PrivacySearchIndexProvider() {
+ super();
+
+ mIsPrimary = UserHandle.myUserId() == UserHandle.USER_OWNER;
+ }
+
+ @Override
+ public List<SearchIndexableResource> getXmlResourcesToIndex(
+ Context context, boolean enabled) {
+
+ List<SearchIndexableResource> result = new ArrayList<SearchIndexableResource>();
+
+ // For non-primary user, no backup or reset is available
+ if (!mIsPrimary) {
+ return result;
+ }
+
+ SearchIndexableResource sir = new SearchIndexableResource(context);
+ sir.xmlResId = R.xml.privacy_settings;
+ result.add(sir);
+
+ return result;
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/com/android/settings/ProgressCategory.java b/src/com/android/settings/ProgressCategory.java
index 6fe34bf..c85dd0b 100644
--- a/src/com/android/settings/ProgressCategory.java
+++ b/src/com/android/settings/ProgressCategory.java
@@ -21,17 +21,35 @@ import android.preference.Preference;
import android.util.AttributeSet;
import android.view.View;
+/**
+ * A category with a progress spinner
+ */
public class ProgressCategory extends ProgressCategoryBase {
- private final int mEmptyTextRes;
+ private int mEmptyTextRes;
private boolean mProgress = false;
private Preference mNoDeviceFoundPreference;
private boolean mNoDeviceFoundAdded;
+ public ProgressCategory(Context context) {
+ this(context, null);
+ }
+
+ public ProgressCategory(Context context, AttributeSet attrs) {
+ super(context, attrs, 0);
+ }
+
public ProgressCategory(Context context, AttributeSet attrs,
- int emptyTextRes) {
- super(context, attrs);
+ int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public ProgressCategory(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
setLayoutResource(R.layout.preference_progress_category);
+ }
+
+ public void setEmptyTextRes(int emptyTextRes) {
mEmptyTextRes = emptyTextRes;
}
diff --git a/src/com/android/settings/ProgressCategoryBase.java b/src/com/android/settings/ProgressCategoryBase.java
index d120b94..c08806c 100644
--- a/src/com/android/settings/ProgressCategoryBase.java
+++ b/src/com/android/settings/ProgressCategoryBase.java
@@ -21,8 +21,21 @@ import android.preference.PreferenceCategory;
import android.util.AttributeSet;
public abstract class ProgressCategoryBase extends PreferenceCategory {
+ public ProgressCategoryBase(Context context) {
+ this(context, null);
+ }
+
public ProgressCategoryBase(Context context, AttributeSet attrs) {
- super(context, attrs);
+ this(context, attrs, 0);
+ }
+
+ public ProgressCategoryBase(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr, 0);
+ }
+
+ public ProgressCategoryBase(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
}
/**
diff --git a/src/com/android/settings/ProxySelector.java b/src/com/android/settings/ProxySelector.java
index 21e717a..6941271 100644
--- a/src/com/android/settings/ProxySelector.java
+++ b/src/com/android/settings/ProxySelector.java
@@ -28,7 +28,7 @@ import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.Proxy;
-import android.net.ProxyProperties;
+import android.net.ProxyInfo;
import android.os.Bundle;
import android.provider.Settings;
import android.text.Selection;
@@ -45,8 +45,6 @@ import android.widget.EditText;
import android.widget.TextView;
import java.net.InetSocketAddress;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
public class ProxySelector extends Fragment implements DialogCreatable {
private static final String TAG = "ProxySelector";
@@ -58,22 +56,6 @@ public class ProxySelector extends Fragment implements DialogCreatable {
Button mClearButton;
Button mDefaultButton;
- // Allows underscore char to supports proxies that do not
- // follow the spec
- private static final String HC = "a-zA-Z0-9\\_";
-
- // Matches blank input, ips, and domain names
- private static final String HOSTNAME_REGEXP =
- "^$|^[" + HC + "]+(\\-[" + HC + "]+)*(\\.[" + HC + "]+(\\-[" + HC + "]+)*)*$";
- private static final Pattern HOSTNAME_PATTERN;
- private static final String EXCLUSION_REGEXP =
- "$|^(\\*)?\\.?[" + HC + "]+(\\-[" + HC + "]+)*(\\.[" + HC + "]+(\\-[" + HC + "]+)*)*$";
- private static final Pattern EXCLUSION_PATTERN;
- static {
- HOSTNAME_PATTERN = Pattern.compile(HOSTNAME_REGEXP);
- EXCLUSION_PATTERN = Pattern.compile(EXCLUSION_REGEXP);
- }
-
private static final int ERROR_DIALOG_ID = 0;
private SettingsDialogFragment mDialogFragment;
@@ -167,11 +149,11 @@ public class ProxySelector extends Fragment implements DialogCreatable {
ConnectivityManager cm =
(ConnectivityManager)getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
- ProxyProperties proxy = cm.getGlobalProxy();
+ ProxyInfo proxy = cm.getGlobalProxy();
if (proxy != null) {
hostname = proxy.getHost();
port = proxy.getPort();
- exclList = proxy.getExclusionList();
+ exclList = proxy.getExclusionListAsString();
}
if (hostname == null) {
@@ -203,35 +185,24 @@ public class ProxySelector extends Fragment implements DialogCreatable {
* @return 0 on success, string resource ID on failure
*/
public static int validate(String hostname, String port, String exclList) {
- Matcher match = HOSTNAME_PATTERN.matcher(hostname);
- String exclListArray[] = exclList.split(",");
-
- if (!match.matches()) return R.string.proxy_error_invalid_host;
-
- for (String excl : exclListArray) {
- Matcher m = EXCLUSION_PATTERN.matcher(excl);
- if (!m.matches()) return R.string.proxy_error_invalid_exclusion_list;
- }
-
- if (hostname.length() > 0 && port.length() == 0) {
- return R.string.proxy_error_empty_port;
- }
-
- if (port.length() > 0) {
- if (hostname.length() == 0) {
+ switch (Proxy.validate(hostname, port, exclList)) {
+ case Proxy.PROXY_VALID:
+ return 0;
+ case Proxy.PROXY_HOSTNAME_EMPTY:
return R.string.proxy_error_empty_host_set_port;
- }
- int portVal = -1;
- try {
- portVal = Integer.parseInt(port);
- } catch (NumberFormatException ex) {
+ case Proxy.PROXY_HOSTNAME_INVALID:
+ return R.string.proxy_error_invalid_host;
+ case Proxy.PROXY_PORT_EMPTY:
+ return R.string.proxy_error_empty_port;
+ case Proxy.PROXY_PORT_INVALID:
return R.string.proxy_error_invalid_port;
- }
- if (portVal <= 0 || portVal > 0xFFFF) {
- return R.string.proxy_error_invalid_port;
- }
+ case Proxy.PROXY_EXCLLIST_INVALID:
+ return R.string.proxy_error_invalid_exclusion_list;
+ default:
+ // should neven happen
+ Log.e(TAG, "Unknown proxy settings error");
+ return -1;
}
- return 0;
}
/**
@@ -245,7 +216,7 @@ public class ProxySelector extends Fragment implements DialogCreatable {
int port = 0;
int result = validate(hostname, portStr, exclList);
- if (result > 0) {
+ if (result != 0) {
showDialog(ERROR_DIALOG_ID);
return false;
}
@@ -258,7 +229,7 @@ public class ProxySelector extends Fragment implements DialogCreatable {
return false;
}
}
- ProxyProperties p = new ProxyProperties(hostname, port, exclList);
+ ProxyInfo p = new ProxyInfo(hostname, port, exclList);
// FIXME: The best solution would be to make a better UI that would
// disable editing of the text boxes if the user chooses to use the
// default settings. i.e. checking a box to always use the default
diff --git a/src/com/android/settings/RadioInfo.java b/src/com/android/settings/RadioInfo.java
index df93626..b0a4a53 100644
--- a/src/com/android/settings/RadioInfo.java
+++ b/src/com/android/settings/RadioInfo.java
@@ -22,7 +22,6 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
-import android.net.ConnectivityManager;
import android.net.TrafficStats;
import android.net.Uri;
import android.os.AsyncResult;
@@ -32,6 +31,7 @@ import android.os.Message;
import android.os.SystemProperties;
import android.telephony.CellInfo;
import android.telephony.CellLocation;
+import android.telephony.DataConnectionRealTimeInfo;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
@@ -103,6 +103,7 @@ public class RadioInfo extends Activity {
private TextView mLocation;
private TextView mNeighboringCids;
private TextView mCellInfo;
+ private TextView mDcRtInfoTv;
private TextView resets;
private TextView attempts;
private TextView successes;
@@ -171,6 +172,12 @@ public class RadioInfo extends Activity {
log("onCellInfoChanged: arrayCi=" + arrayCi);
updateCellInfoTv(arrayCi);
}
+
+ @Override
+ public void onDataConnectionRealTimeInfoChanged(DataConnectionRealTimeInfo dcRtInfo) {
+ log("onDataConnectionRealTimeInfoChanged: dcRtInfo=" + dcRtInfo);
+ updateDcRtInfoTv(dcRtInfo);
+ }
};
private Handler mHandler = new Handler() {
@@ -264,6 +271,7 @@ public class RadioInfo extends Activity {
mLocation = (TextView) findViewById(R.id.location);
mNeighboringCids = (TextView) findViewById(R.id.neighboring);
mCellInfo = (TextView) findViewById(R.id.cellinfo);
+ mDcRtInfoTv = (TextView) findViewById(R.id.dcrtinfo);
resets = (TextView) findViewById(R.id.resets);
attempts = (TextView) findViewById(R.id.attempts);
@@ -366,7 +374,8 @@ public class RadioInfo extends Activity {
| PhoneStateListener.LISTEN_CELL_LOCATION
| PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR
| PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR
- | PhoneStateListener.LISTEN_CELL_INFO);
+ | PhoneStateListener.LISTEN_CELL_INFO
+ | PhoneStateListener.LISTEN_DATA_CONNECTION_REAL_TIME_INFO);
}
@Override
@@ -541,6 +550,10 @@ public class RadioInfo extends Activity {
mCellInfo.setText(value.toString());
}
+ private final void updateDcRtInfoTv(DataConnectionRealTimeInfo dcRtInfo) {
+ mDcRtInfoTv.setText(dcRtInfo.toString());
+ }
+
private final void
updateMessageWaiting() {
mMwi.setText(String.valueOf(mMwiValue));
@@ -903,15 +916,13 @@ public class RadioInfo extends Activity {
private MenuItem.OnMenuItemClickListener mToggleData = new MenuItem.OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
- ConnectivityManager cm =
- (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
int state = mTelephonyManager.getDataState();
switch (state) {
case TelephonyManager.DATA_CONNECTED:
- cm.setMobileDataEnabled(false);
+ phone.setDataEnabled(false);
break;
case TelephonyManager.DATA_DISCONNECTED:
- cm.setMobileDataEnabled(true);
+ phone.setDataEnabled(true);
break;
default:
// do nothing
diff --git a/src/com/android/settings/RestrictedSettingsFragment.java b/src/com/android/settings/RestrictedSettingsFragment.java
index 34eda1e..7d7599f 100644
--- a/src/com/android/settings/RestrictedSettingsFragment.java
+++ b/src/com/android/settings/RestrictedSettingsFragment.java
@@ -23,62 +23,59 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.RestrictionsManager;
import android.os.Bundle;
+import android.os.PersistableBundle;
import android.os.UserManager;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
/**
- * Base class for settings activities that should be pin protected when in restricted mode.
+ * Base class for settings screens that should be pin protected when in restricted mode.
* The constructor for this class will take the restriction key that this screen should be
- * locked by. If {@link UserManager.hasRestrictionsPin()} and
- * {@link UserManager.hasUserRestriction(String)} returns true for the restriction key, then
- * the user will have to enter the restrictions pin before seeing the Settings screen.
+ * locked by. If {@link RestrictionsManager.hasRestrictionsProvider()} and
+ * {@link UserManager.hasUserRestriction()}, then the user will have to enter the restrictions
+ * pin before seeing the Settings screen.
*
* If this settings screen should be pin protected whenever
- * {@link UserManager.hasUserRestriction(String)} returns true, pass in
- * {@link RESTRICTIONS_PIN_SET} to the constructor instead of a restrictions key.
+ * {@link RestrictionsManager.hasRestrictionsProvider()} returns true, pass in
+ * {@link RESTRICT_IF_OVERRIDABLE} to the constructor instead of a restrictions key.
*/
public class RestrictedSettingsFragment extends SettingsPreferenceFragment {
- protected static final String RESTRICTIONS_PIN_SET = "restrictions_pin_set";
+ protected static final String RESTRICT_IF_OVERRIDABLE = "restrict_if_overridable";
- private static final String EXTRA_PREFERENCE = "pref";
- private static final String EXTRA_CHECKBOX_STATE = "isChecked";
- // Should be unique across all settings screens that use this.
+ // No RestrictedSettingsFragment screens should use this number in startActivityForResult.
private static final int REQUEST_PIN_CHALLENGE = 12309;
private static final String KEY_CHALLENGE_SUCCEEDED = "chsc";
private static final String KEY_CHALLENGE_REQUESTED = "chrq";
- private static final String KEY_RESUME_ACTION_BUNDLE = "rsmb";
// If the restriction PIN is entered correctly.
private boolean mChallengeSucceeded;
private boolean mChallengeRequested;
- private Bundle mResumeActionBundle;
private UserManager mUserManager;
+ private RestrictionsManager mRestrictionsManager;
private final String mRestrictionKey;
- private final HashSet<Preference> mProtectedByRestictionsPrefs = new HashSet<Preference>();
-
// Receiver to clear pin status when the screen is turned off.
private BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- mChallengeSucceeded = false;
- if (shouldBePinProtected(mRestrictionKey)) {
- ensurePin(null);
+ if (!mChallengeRequested) {
+ mChallengeSucceeded = false;
+ mChallengeRequested = false;
}
}
};
/**
* @param restrictionKey The restriction key to check before pin protecting
- * this settings page. Pass in {@link RESTRICTIONS_PIN_SET} if it should
- * be PIN protected whenever a restrictions pin is set. Pass in
- * null if it should never be PIN protected.
+ * this settings page. Pass in {@link RESTRICT_IF_OVERRIDABLE} if it should
+ * be protected whenever a restrictions provider is set. Pass in
+ * null if it should never be protected.
*/
public RestrictedSettingsFragment(String restrictionKey) {
mRestrictionKey = restrictionKey;
@@ -88,24 +85,25 @@ public class RestrictedSettingsFragment extends SettingsPreferenceFragment {
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
+ mRestrictionsManager = (RestrictionsManager) getSystemService(Context.RESTRICTIONS_SERVICE);
mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
if (icicle != null) {
mChallengeSucceeded = icicle.getBoolean(KEY_CHALLENGE_SUCCEEDED, false);
mChallengeRequested = icicle.getBoolean(KEY_CHALLENGE_REQUESTED, false);
- mResumeActionBundle = icicle.getBundle(KEY_RESUME_ACTION_BUNDLE);
}
+
+ IntentFilter offFilter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
+ offFilter.addAction(Intent.ACTION_USER_PRESENT);
+ getActivity().registerReceiver(mScreenOffReceiver, offFilter);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
- outState.putBoolean(KEY_CHALLENGE_REQUESTED, mChallengeRequested);
- if (mResumeActionBundle != null) {
- outState.putBundle(KEY_RESUME_ACTION_BUNDLE, mResumeActionBundle);
- }
if (getActivity().isChangingConfigurations()) {
+ outState.putBoolean(KEY_CHALLENGE_REQUESTED, mChallengeRequested);
outState.putBoolean(KEY_CHALLENGE_SUCCEEDED, mChallengeSucceeded);
}
}
@@ -113,55 +111,26 @@ public class RestrictedSettingsFragment extends SettingsPreferenceFragment {
@Override
public void onResume() {
super.onResume();
- if (shouldBePinProtected(mRestrictionKey)) {
- ensurePin(null);
- } else {
- // If the whole screen is not pin protected, reset mChallengeSucceeded so next
- // time user uses a protected preference, they are prompted for pin again.
- mChallengeSucceeded = false;
+
+ if (shouldBeProviderProtected(mRestrictionKey)) {
+ ensurePin();
}
- IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
- filter.addAction(Intent.ACTION_USER_PRESENT);
- getActivity().registerReceiver(mScreenOffReceiver, filter);
}
@Override
- public void onPause() {
- super.onPause();
+ public void onDestroy() {
getActivity().unregisterReceiver(mScreenOffReceiver);
+ super.onDestroy();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_PIN_CHALLENGE) {
- Bundle resumeActionBundle = mResumeActionBundle;
- mResumeActionBundle = null;
- mChallengeRequested = false;
if (resultCode == Activity.RESULT_OK) {
mChallengeSucceeded = true;
- String prefKey = resumeActionBundle == null ?
- null : resumeActionBundle.getString(EXTRA_PREFERENCE);
- if (prefKey != null) {
- Preference pref = findPreference(prefKey);
- if (pref != null) {
- // Make sure the checkbox state is the same as it was when we launched the
- // pin challenge.
- if (pref instanceof CheckBoxPreference
- && resumeActionBundle.containsKey(EXTRA_CHECKBOX_STATE)) {
- boolean isChecked =
- resumeActionBundle.getBoolean(EXTRA_CHECKBOX_STATE, false);
- ((CheckBoxPreference)pref).setChecked(isChecked);
- }
- if (!onPreferenceTreeClick(getPreferenceScreen(), pref)) {
- Intent prefIntent = pref.getIntent();
- if (prefIntent != null) {
- pref.getContext().startActivity(prefIntent);
- }
- }
- }
- }
- } else if (!isDetached()) {
- finishFragment();
+ mChallengeRequested = false;
+ } else {
+ mChallengeSucceeded = false;
}
return;
}
@@ -169,93 +138,54 @@ public class RestrictedSettingsFragment extends SettingsPreferenceFragment {
super.onActivityResult(requestCode, resultCode, data);
}
- private void ensurePin(Preference preference) {
- if (!mChallengeSucceeded) {
- final UserManager um = UserManager.get(getActivity());
- if (!mChallengeRequested) {
- if (um.hasRestrictionsChallenge()) {
- mResumeActionBundle = new Bundle();
- if (preference != null) {
- mResumeActionBundle.putString(EXTRA_PREFERENCE, preference.getKey());
- if (preference instanceof CheckBoxPreference) {
- mResumeActionBundle.putBoolean(EXTRA_CHECKBOX_STATE,
- ((CheckBoxPreference)preference).isChecked());
- }
- }
- Intent requestPin = new Intent(Intent.ACTION_RESTRICTIONS_CHALLENGE);
- startActivityForResult(requestPin, REQUEST_PIN_CHALLENGE);
- mChallengeRequested = true;
- }
+ private void ensurePin() {
+ if (!mChallengeSucceeded && !mChallengeRequested
+ && mRestrictionsManager.hasRestrictionsProvider()) {
+ Intent intent = mRestrictionsManager.createLocalApprovalIntent();
+ if (intent != null) {
+ mChallengeRequested = true;
+ mChallengeSucceeded = false;
+ PersistableBundle request = new PersistableBundle();
+ request.putString(RestrictionsManager.REQUEST_KEY_MESSAGE,
+ getResources().getString(R.string.restr_pin_enter_admin_pin));
+ intent.putExtra(RestrictionsManager.EXTRA_REQUEST_BUNDLE, request);
+ startActivityForResult(intent, REQUEST_PIN_CHALLENGE);
}
}
- mChallengeSucceeded = false;
}
/**
- * Returns true if this activity is restricted, but no restriction pin has been set.
+ * Returns true if this activity is restricted, but no restrictions provider has been set.
* Used to determine if the settings UI should disable UI.
*/
- protected boolean isRestrictedAndNotPinProtected() {
- if (mRestrictionKey == null || RESTRICTIONS_PIN_SET.equals(mRestrictionKey)) {
+ protected boolean isRestrictedAndNotProviderProtected() {
+ if (mRestrictionKey == null || RESTRICT_IF_OVERRIDABLE.equals(mRestrictionKey)) {
return false;
}
return mUserManager.hasUserRestriction(mRestrictionKey)
- && !mUserManager.hasRestrictionsChallenge();
+ && !mRestrictionsManager.hasRestrictionsProvider();
+ }
+
+ protected boolean hasChallengeSucceeded() {
+ return (mChallengeRequested && mChallengeSucceeded) || !mChallengeRequested;
}
/**
- * Called to trigger the pin entry if the given restriction key is locked down.
- * @param restrictionsKey The restriction key or {@link RESTRICTIONS_PIN_SET} if
- * pin entry should get triggered if there is a pin set.
+ * Returns true if this restrictions key is locked down.
*/
- protected boolean restrictionsPinCheck(String restrictionsKey, Preference preference) {
- if (shouldBePinProtected(restrictionsKey) && !mChallengeSucceeded) {
- ensurePin(preference);
- return false;
- } else {
- return true;
- }
- }
-
- protected boolean hasChallengeSucceeded() {
- return mChallengeSucceeded;
- }
-
- /**
- * Returns true if this restrictions key is locked down.
- */
- protected boolean shouldBePinProtected(String restrictionKey) {
- if (restrictionKey == null) {
- return false;
- }
- boolean restricted = RESTRICTIONS_PIN_SET.equals(restrictionKey)
- || mUserManager.hasUserRestriction(restrictionKey);
- return restricted && mUserManager.hasRestrictionsChallenge();
- }
-
- /**
- * If the preference is one that was added by protectByRestrictions(), then it will
- * prompt the user for the restrictions pin if they haven't entered it already.
- * Intended to be called at the top of onPreferenceTreeClick. If this function returns
- * true, then onPreferenceTreeClick should return true.
- */
- boolean ensurePinRestrictedPreference(Preference preference) {
- return mProtectedByRestictionsPrefs.contains(preference)
- && !restrictionsPinCheck(RESTRICTIONS_PIN_SET, preference);
- }
+ protected boolean shouldBeProviderProtected(String restrictionKey) {
+ if (restrictionKey == null) {
+ return false;
+ }
+ boolean restricted = RESTRICT_IF_OVERRIDABLE.equals(restrictionKey)
+ || mUserManager.hasUserRestriction(mRestrictionKey);
+ return restricted && mRestrictionsManager.hasRestrictionsProvider();
+ }
/**
- * Call this with any preferences that should require the PIN to be entered
- * before they are accessible.
+ * Returns whether restricted or actionable UI elements should be removed or disabled.
*/
- protected void protectByRestrictions(Preference pref) {
- if (pref != null) {
- mProtectedByRestictionsPrefs.add(pref);
- }
- }
-
- protected void protectByRestrictions(String key) {
- Preference pref = findPreference(key);
- protectByRestrictions(pref);
- }
+ protected boolean isUiRestricted() {
+ return isRestrictedAndNotProviderProtected() || !hasChallengeSucceeded();
+ }
}
diff --git a/src/com/android/settings/RingerVolumePreference.java b/src/com/android/settings/RingerVolumePreference.java
deleted file mode 100644
index dd81ded..0000000
--- a/src/com/android/settings/RingerVolumePreference.java
+++ /dev/null
@@ -1,388 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings;
-
-import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
-
-import com.android.internal.telephony.TelephonyIntents;
-
-import android.app.Dialog;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.media.AudioManager;
-import android.media.AudioSystem;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.preference.VolumePreference;
-import android.provider.Settings;
-import android.provider.Settings.System;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.CheckBox;
-import android.widget.CompoundButton;
-import android.widget.ImageView;
-import android.widget.SeekBar;
-import android.widget.TextView;
-
-/**
- * Special preference type that allows configuration of both the ring volume and
- * notification volume.
- */
-public class RingerVolumePreference extends VolumePreference {
- private static final String TAG = "RingerVolumePreference";
- private static final int MSG_RINGER_MODE_CHANGED = 101;
-
- private SeekBarVolumizer [] mSeekBarVolumizer;
-
- // These arrays must all match in length and order
- private static final int[] SEEKBAR_ID = new int[] {
- R.id.media_volume_seekbar,
- R.id.ringer_volume_seekbar,
- R.id.notification_volume_seekbar,
- R.id.alarm_volume_seekbar
- };
-
- private static final int[] SEEKBAR_TYPE = new int[] {
- AudioManager.STREAM_MUSIC,
- AudioManager.STREAM_RING,
- AudioManager.STREAM_NOTIFICATION,
- AudioManager.STREAM_ALARM
- };
-
- private static final int[] CHECKBOX_VIEW_ID = new int[] {
- R.id.media_mute_button,
- R.id.ringer_mute_button,
- R.id.notification_mute_button,
- R.id.alarm_mute_button
- };
-
- private static final int[] SEEKBAR_SECTION_ID = new int[] {
- R.id.media_section,
- R.id.ringer_section,
- R.id.notification_section,
- R.id.alarm_section
- };
-
- private static final int[] SEEKBAR_MUTED_RES_ID = new int[] {
- com.android.internal.R.drawable.ic_audio_vol_mute,
- com.android.internal.R.drawable.ic_audio_ring_notif_mute,
- com.android.internal.R.drawable.ic_audio_notification_mute,
- com.android.internal.R.drawable.ic_audio_alarm_mute
- };
-
- private static final int[] SEEKBAR_UNMUTED_RES_ID = new int[] {
- com.android.internal.R.drawable.ic_audio_vol,
- com.android.internal.R.drawable.ic_audio_ring_notif,
- com.android.internal.R.drawable.ic_audio_notification,
- com.android.internal.R.drawable.ic_audio_alarm
- };
-
- private ImageView[] mCheckBoxes = new ImageView[SEEKBAR_MUTED_RES_ID.length];
- private SeekBar[] mSeekBars = new SeekBar[SEEKBAR_ID.length];
-
- private Handler mHandler = new Handler() {
- public void handleMessage(Message msg) {
- updateSlidersAndMutedStates();
- }
- };
-
- @Override
- public void createActionButtons() {
- setPositiveButtonText(android.R.string.ok);
- setNegativeButtonText(null);
- }
-
- private void updateSlidersAndMutedStates() {
- for (int i = 0; i < SEEKBAR_TYPE.length; i++) {
- int streamType = SEEKBAR_TYPE[i];
- boolean muted = mAudioManager.isStreamMute(streamType);
-
- if (mCheckBoxes[i] != null) {
- if (((streamType == AudioManager.STREAM_RING) ||
- (streamType == AudioManager.STREAM_NOTIFICATION)) &&
- (mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE)) {
- mCheckBoxes[i].setImageResource(
- com.android.internal.R.drawable.ic_audio_ring_notif_vibrate);
- } else {
- mCheckBoxes[i].setImageResource(
- muted ? SEEKBAR_MUTED_RES_ID[i] : SEEKBAR_UNMUTED_RES_ID[i]);
- }
- }
- if (mSeekBars[i] != null) {
- final int volume = mAudioManager.getStreamVolume(streamType);
- mSeekBars[i].setProgress(volume);
- if (streamType != mAudioManager.getMasterStreamType() && muted) {
- mSeekBars[i].setEnabled(false);
- } else {
- mSeekBars[i].setEnabled(true);
- }
- }
- }
- }
-
- private BroadcastReceiver mRingModeChangedReceiver;
- private AudioManager mAudioManager;
-
- //private SeekBarVolumizer mNotificationSeekBarVolumizer;
- //private TextView mNotificationVolumeTitle;
-
- public RingerVolumePreference(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- // The always visible seekbar is for ring volume
- setStreamType(AudioManager.STREAM_RING);
-
- setDialogLayoutResource(R.layout.preference_dialog_ringervolume);
- //setDialogIcon(R.drawable.ic_settings_sound);
-
- mSeekBarVolumizer = new SeekBarVolumizer[SEEKBAR_ID.length];
-
- mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- }
-
- @Override
- protected void onBindDialogView(View view) {
- super.onBindDialogView(view);
-
- for (int i = 0; i < SEEKBAR_ID.length; i++) {
- SeekBar seekBar = (SeekBar) view.findViewById(SEEKBAR_ID[i]);
- mSeekBars[i] = seekBar;
- if (SEEKBAR_TYPE[i] == AudioManager.STREAM_MUSIC) {
- mSeekBarVolumizer[i] = new SeekBarVolumizer(getContext(), seekBar,
- SEEKBAR_TYPE[i], getMediaVolumeUri(getContext()));
- } else {
- mSeekBarVolumizer[i] = new SeekBarVolumizer(getContext(), seekBar,
- SEEKBAR_TYPE[i]);
- }
- }
-
- // Register callbacks for mute/unmute buttons
- for (int i = 0; i < mCheckBoxes.length; i++) {
- ImageView checkbox = (ImageView) view.findViewById(CHECKBOX_VIEW_ID[i]);
- mCheckBoxes[i] = checkbox;
- }
-
- // Load initial states from AudioManager
- updateSlidersAndMutedStates();
-
- // Listen for updates from AudioManager
- if (mRingModeChangedReceiver == null) {
- final IntentFilter filter = new IntentFilter();
- filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
- mRingModeChangedReceiver = new BroadcastReceiver() {
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(action)) {
- mHandler.sendMessage(mHandler.obtainMessage(MSG_RINGER_MODE_CHANGED, intent
- .getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1), 0));
- }
- }
- };
- getContext().registerReceiver(mRingModeChangedReceiver, filter);
- }
-
- boolean useMasterVolume = getContext().getResources().
- getBoolean(com.android.internal.R.bool.config_useMasterVolume);
- if (useMasterVolume) {
- // If config_useMasterVolume is true, all streams are treated as STREAM_MASTER.
- // So hide all except a stream.
- int id;
- if (Utils.isVoiceCapable(getContext())) {
- id = R.id.ringer_section;
- } else {
- id = R.id.media_section;
- }
- for (int i = 0; i < SEEKBAR_SECTION_ID.length; i++) {
- if (SEEKBAR_SECTION_ID[i] != id) {
- view.findViewById(SEEKBAR_SECTION_ID[i]).setVisibility(View.GONE);
- }
- }
- } else {
- // Disable either ringer+notifications or notifications
- int id;
- if (!Utils.isVoiceCapable(getContext())) {
- id = R.id.ringer_section;
- } else {
- id = R.id.notification_section;
- }
- View hideSection = view.findViewById(id);
- hideSection.setVisibility(View.GONE);
- }
- }
-
- private Uri getMediaVolumeUri(Context context) {
- return Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://"
- + context.getPackageName()
- + "/" + R.raw.media_volume);
- }
-
- @Override
- protected void onDialogClosed(boolean positiveResult) {
- super.onDialogClosed(positiveResult);
-
- if (!positiveResult) {
- for (SeekBarVolumizer vol : mSeekBarVolumizer) {
- if (vol != null) vol.revertVolume();
- }
- }
- cleanup();
- }
-
- @Override
- public void onActivityStop() {
- super.onActivityStop();
-
- for (SeekBarVolumizer vol : mSeekBarVolumizer) {
- if (vol != null) vol.stopSample();
- }
- }
-
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- boolean isdown = (event.getAction() == KeyEvent.ACTION_DOWN);
- switch (keyCode) {
- case KeyEvent.KEYCODE_VOLUME_DOWN:
- case KeyEvent.KEYCODE_VOLUME_UP:
- case KeyEvent.KEYCODE_VOLUME_MUTE:
- return true;
- default:
- return false;
- }
- }
-
- @Override
- protected void onSampleStarting(SeekBarVolumizer volumizer) {
- super.onSampleStarting(volumizer);
- for (SeekBarVolumizer vol : mSeekBarVolumizer) {
- if (vol != null && vol != volumizer) vol.stopSample();
- }
- }
-
- private void cleanup() {
- for (int i = 0; i < SEEKBAR_ID.length; i++) {
- if (mSeekBarVolumizer[i] != null) {
- Dialog dialog = getDialog();
- if (dialog != null && dialog.isShowing()) {
- // Stopped while dialog was showing, revert changes
- mSeekBarVolumizer[i].revertVolume();
- }
- mSeekBarVolumizer[i].stop();
- mSeekBarVolumizer[i] = null;
- }
- }
- if (mRingModeChangedReceiver != null) {
- getContext().unregisterReceiver(mRingModeChangedReceiver);
- mRingModeChangedReceiver = null;
- }
- }
-
- @Override
- protected Parcelable onSaveInstanceState() {
- final Parcelable superState = super.onSaveInstanceState();
- if (isPersistent()) {
- // No need to save instance state since it's persistent
- return superState;
- }
-
- final SavedState myState = new SavedState(superState);
- VolumeStore[] volumeStore = myState.getVolumeStore(SEEKBAR_ID.length);
- for (int i = 0; i < SEEKBAR_ID.length; i++) {
- SeekBarVolumizer vol = mSeekBarVolumizer[i];
- if (vol != null) {
- vol.onSaveInstanceState(volumeStore[i]);
- }
- }
- 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());
- VolumeStore[] volumeStore = myState.getVolumeStore(SEEKBAR_ID.length);
- for (int i = 0; i < SEEKBAR_ID.length; i++) {
- SeekBarVolumizer vol = mSeekBarVolumizer[i];
- if (vol != null) {
- vol.onRestoreInstanceState(volumeStore[i]);
- }
- }
- }
-
- private static class SavedState extends BaseSavedState {
- VolumeStore [] mVolumeStore;
-
- public SavedState(Parcel source) {
- super(source);
- mVolumeStore = new VolumeStore[SEEKBAR_ID.length];
- for (int i = 0; i < SEEKBAR_ID.length; i++) {
- mVolumeStore[i] = new VolumeStore();
- mVolumeStore[i].volume = source.readInt();
- mVolumeStore[i].originalVolume = source.readInt();
- }
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- super.writeToParcel(dest, flags);
- for (int i = 0; i < SEEKBAR_ID.length; i++) {
- dest.writeInt(mVolumeStore[i].volume);
- dest.writeInt(mVolumeStore[i].originalVolume);
- }
- }
-
- VolumeStore[] getVolumeStore(int count) {
- if (mVolumeStore == null || mVolumeStore.length != count) {
- mVolumeStore = new VolumeStore[count];
- for (int i = 0; i < count; i++) {
- mVolumeStore[i] = new VolumeStore();
- }
- }
- return mVolumeStore;
- }
-
- public SavedState(Parcelable superState) {
- super(superState);
- }
-
- 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];
- }
- };
- }
-}
diff --git a/src/com/android/settings/ScreenPinningSettings.java b/src/com/android/settings/ScreenPinningSettings.java
new file mode 100644
index 0000000..ada5b17
--- /dev/null
+++ b/src/com/android/settings/ScreenPinningSettings.java
@@ -0,0 +1,118 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.List;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Switch;
+
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.search.Indexable;
+import com.android.settings.search.SearchIndexableRaw;
+import com.android.settings.widget.SwitchBar;
+
+/**
+ * Screen pinning settings.
+ */
+public class ScreenPinningSettings extends SettingsPreferenceFragment
+ implements SwitchBar.OnSwitchChangeListener, Indexable {
+
+ private SwitchBar mSwitchBar;
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ final SettingsActivity activity = (SettingsActivity) getActivity();
+
+ mSwitchBar = activity.getSwitchBar();
+ mSwitchBar.addOnSwitchChangeListener(this);
+ mSwitchBar.show();
+ mSwitchBar.setChecked(isLockToAppEnabled());
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+
+ mSwitchBar.removeOnSwitchChangeListener(this);
+ mSwitchBar.hide();
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.screen_pinning_instructions, null);
+ }
+
+ private boolean isLockToAppEnabled() {
+ try {
+ return Settings.System.getInt(getContentResolver(), Settings.System.LOCK_TO_APP_ENABLED)
+ != 0;
+ } catch (SettingNotFoundException e) {
+ return false;
+ }
+ }
+
+ private void setLockToAppEnabled(boolean isEnabled) {
+ Settings.System.putInt(getContentResolver(), Settings.System.LOCK_TO_APP_ENABLED,
+ isEnabled ? 1 : 0);
+ }
+
+ /**
+ * Listens to the state change of the lock-to-app master switch.
+ */
+ @Override
+ public void onSwitchChanged(Switch switchView, boolean isChecked) {
+ setLockToAppEnabled(isChecked);
+ }
+
+ /**
+ * For search
+ */
+ public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider() {
+ @Override
+ public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
+ final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>();
+
+ final Resources res = context.getResources();
+
+ // Add fragment title
+ SearchIndexableRaw data = new SearchIndexableRaw(context);
+ data.title = res.getString(R.string.screen_pinning_title);
+ data.screenTitle = res.getString(R.string.screen_pinning_title);
+ result.add(data);
+
+ // Screen pinning description.
+ data = new SearchIndexableRaw(context);
+ data.title = res.getString(R.string.screen_pinning_description);
+ data.screenTitle = res.getString(R.string.screen_pinning_title);
+ result.add(data);
+
+ return result;
+ }
+ };
+}
diff --git a/src/com/android/settings/SecuritySettings.java b/src/com/android/settings/SecuritySettings.java
index e4dcea1..ecededc 100644
--- a/src/com/android/settings/SecuritySettings.java
+++ b/src/com/android/settings/SecuritySettings.java
@@ -17,43 +17,56 @@
package com.android.settings;
-import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT;
-
import android.app.Activity;
-import android.app.ActivityManager;
import android.app.AlertDialog;
import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
+import android.content.res.Resources;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
-import android.preference.CheckBoxPreference;
+import android.preference.SwitchPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
+import android.provider.SearchIndexableResource;
import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
import android.security.KeyStore;
+import android.service.trust.TrustAgentService;
import android.telephony.TelephonyManager;
+import android.text.TextUtils;
import android.util.Log;
import com.android.internal.widget.LockPatternUtils;
+import com.android.settings.TrustAgentUtils.TrustAgentComponentInfo;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.search.Index;
+import com.android.settings.search.Indexable;
+import com.android.settings.search.SearchIndexableRaw;
import java.util.ArrayList;
import java.util.List;
+import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT;
+
/**
* Gesture lock pattern settings.
*/
-public class SecuritySettings extends RestrictedSettingsFragment
- implements OnPreferenceChangeListener, DialogInterface.OnClickListener {
+public class SecuritySettings extends SettingsPreferenceFragment
+ implements OnPreferenceChangeListener, DialogInterface.OnClickListener, Indexable {
+ private static final String TRUST_AGENT_CLICK_INTENT = "trust_agent_click_intent";
static final String TAG = "SecuritySettings";
+ private static final Intent TRUST_AGENT_INTENT =
+ new Intent(TrustAgentService.SERVICE_INTERFACE);
// Lock Settings
private static final String KEY_UNLOCK_SET_OR_CHANGE = "unlock_set_or_change";
@@ -66,53 +79,56 @@ public class SecuritySettings extends RestrictedSettingsFragment
private static final String KEY_DEVICE_ADMIN_CATEGORY = "device_admin_category";
private static final String KEY_LOCK_AFTER_TIMEOUT = "lock_after_timeout";
private static final String KEY_OWNER_INFO_SETTINGS = "owner_info_settings";
- private static final String KEY_ENABLE_WIDGETS = "keyguard_enable_widgets";
+ private static final String KEY_ADVANCED_SECURITY = "advanced_security";
+ private static final String KEY_MANAGE_TRUST_AGENTS = "manage_trust_agents";
private static final int SET_OR_CHANGE_LOCK_METHOD_REQUEST = 123;
private static final int CONFIRM_EXISTING_FOR_BIOMETRIC_WEAK_IMPROVE_REQUEST = 124;
private static final int CONFIRM_EXISTING_FOR_BIOMETRIC_WEAK_LIVELINESS_OFF = 125;
+ 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_SHOW_PASSWORD = "show_password";
private static final String KEY_CREDENTIAL_STORAGE_TYPE = "credential_storage_type";
- private static final String KEY_RESET_CREDENTIALS = "reset_credentials";
+ private static final String KEY_RESET_CREDENTIALS = "credentials_reset";
private static final String KEY_CREDENTIALS_INSTALL = "credentials_install";
private static final String KEY_TOGGLE_INSTALL_APPLICATIONS = "toggle_install_applications";
- private static final String KEY_TOGGLE_VERIFY_APPLICATIONS = "toggle_verify_applications";
private static final String KEY_POWER_INSTANTLY_LOCKS = "power_button_instantly_locks";
private static final String KEY_CREDENTIALS_MANAGER = "credentials_management";
- private static final String KEY_NOTIFICATION_ACCESS = "manage_notification_access";
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";
+
+ // 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_LOCK_ENABLED, KEY_VISIBLE_PATTERN, KEY_BIOMETRIC_WEAK_LIVELINESS,
+ 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 PackageManager mPM;
private DevicePolicyManager mDPM;
private ChooseLockSettingsHelper mChooseLockSettingsHelper;
private LockPatternUtils mLockPatternUtils;
private ListPreference mLockAfter;
- private CheckBoxPreference mBiometricWeakLiveliness;
- private CheckBoxPreference mVisiblePattern;
+ private SwitchPreference mBiometricWeakLiveliness;
+ private SwitchPreference mVisiblePattern;
- private CheckBoxPreference mShowPassword;
+ private SwitchPreference mShowPassword;
private KeyStore mKeyStore;
private Preference mResetCredentials;
- private CheckBoxPreference mToggleAppInstallation;
+ private SwitchPreference mToggleAppInstallation;
private DialogInterface mWarnInstallApps;
- private CheckBoxPreference mToggleVerifyApps;
- private CheckBoxPreference mPowerButtonInstantlyLocks;
- private CheckBoxPreference mEnableKeyguardWidgets;
-
- private Preference mNotificationAccess;
+ private SwitchPreference mPowerButtonInstantlyLocks;
private boolean mIsPrimary;
- public SecuritySettings() {
- super(null /* Don't ask for restrictions pin on creation. */);
- }
+ private Intent mTrustAgentClickIntent;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -120,42 +136,40 @@ public class SecuritySettings extends RestrictedSettingsFragment
mLockPatternUtils = new LockPatternUtils(getActivity());
- mPM = getActivity().getPackageManager();
mDPM = (DevicePolicyManager)getSystemService(Context.DEVICE_POLICY_SERVICE);
mChooseLockSettingsHelper = new ChooseLockSettingsHelper(getActivity());
- }
- private PreferenceScreen createPreferenceHierarchy() {
- PreferenceScreen root = getPreferenceScreen();
- if (root != null) {
- root.removeAll();
+ if (savedInstanceState != null
+ && savedInstanceState.containsKey(TRUST_AGENT_CLICK_INTENT)) {
+ mTrustAgentClickIntent = savedInstanceState.getParcelable(TRUST_AGENT_CLICK_INTENT);
}
- addPreferencesFromResource(R.xml.security_settings);
- root = getPreferenceScreen();
+ }
- // Add options for lock/unlock screen
+ private static int getResIdForLockUnlockScreen(Context context,
+ LockPatternUtils lockPatternUtils) {
int resid = 0;
- if (!mLockPatternUtils.isSecure()) {
+ if (!lockPatternUtils.isSecure()) {
// if there are multiple users, disable "None" setting
- UserManager mUm = (UserManager) getSystemService(Context.USER_SERVICE);
+ UserManager mUm = (UserManager) context. getSystemService(Context.USER_SERVICE);
List<UserInfo> users = mUm.getUsers(true);
final boolean singleUser = users.size() == 1;
- if (singleUser && mLockPatternUtils.isLockScreenDisabled()) {
+ if (singleUser && lockPatternUtils.isLockScreenDisabled()) {
resid = R.xml.security_settings_lockscreen;
} else {
resid = R.xml.security_settings_chooser;
}
- } else if (mLockPatternUtils.usingBiometricWeak() &&
- mLockPatternUtils.isBiometricWeakInstalled()) {
+ } else if (lockPatternUtils.usingBiometricWeak() &&
+ lockPatternUtils.isBiometricWeakInstalled()) {
resid = R.xml.security_settings_biometric_weak;
} else {
- switch (mLockPatternUtils.getKeyguardStoredPasswordQuality()) {
+ switch (lockPatternUtils.getKeyguardStoredPasswordQuality()) {
case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
resid = R.xml.security_settings_pattern;
break;
case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
+ case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX:
resid = R.xml.security_settings_pin;
break;
case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
@@ -165,8 +179,26 @@ public class SecuritySettings extends RestrictedSettingsFragment
break;
}
}
- addPreferencesFromResource(resid);
+ return resid;
+ }
+ /**
+ * Important!
+ *
+ * Don't forget to update the SecuritySearchIndexProvider if you are doing any change in the
+ * logic or adding/removing preferences here.
+ */
+ private PreferenceScreen createPreferenceHierarchy() {
+ PreferenceScreen root = getPreferenceScreen();
+ if (root != null) {
+ root.removeAll();
+ }
+ addPreferencesFromResource(R.xml.security_settings);
+ root = getPreferenceScreen();
+
+ // Add options for lock/unlock screen
+ final int resid = getResIdForLockUnlockScreen(getActivity(), mLockPatternUtils);
+ addPreferencesFromResource(resid);
// Add options for device encryption
mIsPrimary = UserHandle.myUserId() == UserHandle.USER_OWNER;
@@ -184,15 +216,40 @@ public class SecuritySettings extends RestrictedSettingsFragment
}
if (mIsPrimary) {
- switch (mDPM.getStorageEncryptionStatus()) {
- case DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE:
+ if (LockPatternUtils.isDeviceEncryptionEnabled()) {
// The device is currently encrypted.
addPreferencesFromResource(R.xml.security_settings_encrypted);
- break;
- case DevicePolicyManager.ENCRYPTION_STATUS_INACTIVE:
+ } else {
// This device supports encryption but isn't encrypted.
addPreferencesFromResource(R.xml.security_settings_unencrypted);
- break;
+ }
+ }
+
+ // Trust Agent preferences
+ PreferenceGroup securityCategory = (PreferenceGroup)
+ root.findPreference(KEY_SECURITY_CATEGORY);
+ if (securityCategory != null) {
+ final boolean hasSecurity = mLockPatternUtils.isSecure();
+ ArrayList<TrustAgentComponentInfo> agents =
+ getActiveTrustAgents(getPackageManager(), mLockPatternUtils);
+ for (int i = 0; i < agents.size(); i++) {
+ final TrustAgentComponentInfo agent = agents.get(i);
+ Preference trustAgentPreference =
+ new Preference(securityCategory.getContext());
+ trustAgentPreference.setKey(KEY_TRUST_AGENT);
+ trustAgentPreference.setTitle(agent.title);
+ trustAgentPreference.setSummary(agent.summary);
+ // Create intent for this preference.
+ Intent intent = new Intent();
+ intent.setComponent(agent.componentName);
+ intent.setAction(Intent.ACTION_MAIN);
+ trustAgentPreference.setIntent(intent);
+ // Add preference to the settings menu.
+ securityCategory.addPreference(trustAgentPreference);
+ if (!hasSecurity) {
+ trustAgentPreference.setEnabled(false);
+ trustAgentPreference.setSummary(R.string.disabled_because_no_backup_security);
+ }
}
}
@@ -205,21 +262,27 @@ public class SecuritySettings extends RestrictedSettingsFragment
// biometric weak liveliness
mBiometricWeakLiveliness =
- (CheckBoxPreference) root.findPreference(KEY_BIOMETRIC_WEAK_LIVELINESS);
+ (SwitchPreference) root.findPreference(KEY_BIOMETRIC_WEAK_LIVELINESS);
// visible pattern
- mVisiblePattern = (CheckBoxPreference) root.findPreference(KEY_VISIBLE_PATTERN);
+ mVisiblePattern = (SwitchPreference) root.findPreference(KEY_VISIBLE_PATTERN);
// lock instantly on power key press
- mPowerButtonInstantlyLocks = (CheckBoxPreference) root.findPreference(
+ 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()));
+ }
// don't display visible pattern if biometric and backup is not pattern
if (resid == R.xml.security_settings_biometric_weak &&
mLockPatternUtils.getKeyguardStoredPasswordQuality() !=
DevicePolicyManager.PASSWORD_QUALITY_SOMETHING) {
- PreferenceGroup securityCategory = (PreferenceGroup)
- root.findPreference(KEY_SECURITY_CATEGORY);
if (securityCategory != null && mVisiblePattern != null) {
securityCategory.removePreference(root.findPreference(KEY_VISIBLE_PATTERN));
}
@@ -241,34 +304,14 @@ public class SecuritySettings extends RestrictedSettingsFragment
root.findPreference(KEY_SIM_LOCK).setEnabled(false);
}
}
-
- // Enable or disable keyguard widget checkbox based on DPM state
- mEnableKeyguardWidgets = (CheckBoxPreference) root.findPreference(KEY_ENABLE_WIDGETS);
- if (mEnableKeyguardWidgets != null) {
- if (ActivityManager.isLowRamDeviceStatic()
- || mLockPatternUtils.isLockScreenDisabled()) {
- // Widgets take a lot of RAM, so disable them on low-memory devices
- PreferenceGroup securityCategory
- = (PreferenceGroup) root.findPreference(KEY_SECURITY_CATEGORY);
- if (securityCategory != null) {
- securityCategory.removePreference(root.findPreference(KEY_ENABLE_WIDGETS));
- mEnableKeyguardWidgets = null;
- }
- } else {
- final boolean disabled = (0 != (mDPM.getKeyguardDisabledFeatures(null)
- & DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL));
- if (disabled) {
- mEnableKeyguardWidgets.setSummary(
- R.string.security_enable_widgets_disabled_summary);
- } else {
- mEnableKeyguardWidgets.setSummary("");
- }
- mEnableKeyguardWidgets.setEnabled(!disabled);
- }
+ 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));
}
// Show password
- mShowPassword = (CheckBoxPreference) root.findPreference(KEY_SHOW_PASSWORD);
+ mShowPassword = (SwitchPreference) root.findPreference(KEY_SHOW_PASSWORD);
mResetCredentials = root.findPreference(KEY_RESET_CREDENTIALS);
// Credential storage
@@ -281,73 +324,73 @@ public class SecuritySettings extends RestrictedSettingsFragment
mKeyStore.isHardwareBacked() ? R.string.credential_storage_type_hardware
: R.string.credential_storage_type_software;
credentialStorageType.setSummary(storageSummaryRes);
-
} else {
- removePreference(KEY_CREDENTIALS_MANAGER);
+ 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)
+ PreferenceGroup deviceAdminCategory = (PreferenceGroup)
root.findPreference(KEY_DEVICE_ADMIN_CATEGORY);
- mToggleAppInstallation = (CheckBoxPreference) findPreference(
+ mToggleAppInstallation = (SwitchPreference) findPreference(
KEY_TOGGLE_INSTALL_APPLICATIONS);
mToggleAppInstallation.setChecked(isNonMarketAppsAllowed());
-
// Side loading of apps.
mToggleAppInstallation.setEnabled(mIsPrimary);
-
- // Package verification, only visible to primary user and if enabled
- mToggleVerifyApps = (CheckBoxPreference) findPreference(KEY_TOGGLE_VERIFY_APPLICATIONS);
- if (mIsPrimary && showVerifierSetting()) {
- if (isVerifierInstalled()) {
- mToggleVerifyApps.setChecked(isVerifyAppsEnabled());
- } else {
- mToggleVerifyApps.setChecked(false);
- mToggleVerifyApps.setEnabled(false);
- }
- } else {
- if (deviceAdminCategory != null) {
- deviceAdminCategory.removePreference(mToggleVerifyApps);
- } else {
- mToggleVerifyApps.setEnabled(false);
- }
+ if (um.hasUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES)
+ || um.hasUserRestriction(UserManager.DISALLOW_INSTALL_APPS)) {
+ mToggleAppInstallation.setEnabled(false);
}
- mNotificationAccess = findPreference(KEY_NOTIFICATION_ACCESS);
- if (mNotificationAccess != null) {
- final int total = NotificationAccessSettings.getListenersCount(mPM);
- if (total == 0) {
- if (deviceAdminCategory != null) {
- deviceAdminCategory.removePreference(mNotificationAccess);
- }
- } else {
- final int n = getNumEnabledNotificationListeners();
- if (n == 0) {
- mNotificationAccess.setSummary(getResources().getString(
- R.string.manage_notification_access_summary_zero));
- } else {
- mNotificationAccess.setSummary(String.format(getResources().getQuantityString(
- R.plurals.manage_notification_access_summary_nonzero,
- n, n)));
- }
+ // 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()) {
+ manageAgents.setEnabled(false);
+ manageAgents.setSummary(R.string.disabled_because_no_backup_security);
}
}
- if (shouldBePinProtected(RESTRICTIONS_PIN_SET)) {
- protectByRestrictions(mToggleAppInstallation);
- protectByRestrictions(mToggleVerifyApps);
- protectByRestrictions(mResetCredentials);
- protectByRestrictions(root.findPreference(KEY_CREDENTIALS_INSTALL));
+ // 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;
}
- private int getNumEnabledNotificationListeners() {
- final String flat = Settings.Secure.getString(getContentResolver(),
- Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
- if (flat == null || "".equals(flat)) return 0;
- final String[] components = flat.split(":");
- return components.length;
+ private static ArrayList<TrustAgentComponentInfo> getActiveTrustAgents(
+ PackageManager pm, LockPatternUtils utils) {
+ ArrayList<TrustAgentComponentInfo> result = new ArrayList<TrustAgentComponentInfo>();
+ List<ResolveInfo> resolveInfos = pm.queryIntentServices(TRUST_AGENT_INTENT,
+ PackageManager.GET_META_DATA);
+ List<ComponentName> enabledTrustAgents = utils.getEnabledTrustAgents();
+ if (enabledTrustAgents != null && !enabledTrustAgents.isEmpty()) {
+ for (int i = 0; i < resolveInfos.size(); i++) {
+ ResolveInfo resolveInfo = resolveInfos.get(i);
+ if (resolveInfo.serviceInfo == null) continue;
+ if (!TrustAgentUtils.checkProvidePermission(resolveInfo, pm)) continue;
+ TrustAgentComponentInfo trustAgentComponentInfo =
+ TrustAgentUtils.getSettingsComponent(pm, resolveInfo);
+ if (trustAgentComponentInfo.componentName == null ||
+ !enabledTrustAgents.contains(
+ TrustAgentUtils.getComponentName(resolveInfo)) ||
+ TextUtils.isEmpty(trustAgentComponentInfo.title)) continue;
+ result.add(trustAgentComponentInfo);
+ if (ONLY_ONE_TRUST_AGENT) break;
+ }
+ }
+ return result;
}
private boolean isNonMarketAppsAllowed() {
@@ -365,25 +408,6 @@ public class SecuritySettings extends RestrictedSettingsFragment
enabled ? 1 : 0);
}
- private boolean isVerifyAppsEnabled() {
- return Settings.Global.getInt(getContentResolver(),
- Settings.Global.PACKAGE_VERIFIER_ENABLE, 1) > 0;
- }
-
- private boolean isVerifierInstalled() {
- final PackageManager pm = getPackageManager();
- final Intent verification = new Intent(Intent.ACTION_PACKAGE_NEEDS_VERIFICATION);
- verification.setType(PACKAGE_MIME_TYPE);
- verification.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- final List<ResolveInfo> receivers = pm.queryBroadcastReceivers(verification, 0);
- return (receivers.size() > 0) ? true : false;
- }
-
- private boolean showVerifierSetting() {
- return Settings.Global.getInt(getContentResolver(),
- Settings.Global.PACKAGE_VERIFIER_SETTING_VISIBLE, 1) > 0;
- }
-
private void warnAppInstallation() {
// TODO: DialogFragment?
mWarnInstallApps = new AlertDialog.Builder(getActivity()).setTitle(
@@ -391,16 +415,17 @@ public class SecuritySettings extends RestrictedSettingsFragment
.setIcon(com.android.internal.R.drawable.ic_dialog_alert)
.setMessage(getResources().getString(R.string.install_all_warning))
.setPositiveButton(android.R.string.yes, this)
- .setNegativeButton(android.R.string.no, null)
+ .setNegativeButton(android.R.string.no, this)
.show();
}
@Override
public void onClick(DialogInterface dialog, int which) {
- if (dialog == mWarnInstallApps && which == DialogInterface.BUTTON_POSITIVE) {
- setNonMarketAppsAllowed(true);
+ if (dialog == mWarnInstallApps) {
+ boolean turnOn = which == DialogInterface.BUTTON_POSITIVE;
+ setNonMarketAppsAllowed(turnOn);
if (mToggleAppInstallation != null) {
- mToggleAppInstallation.setChecked(true);
+ mToggleAppInstallation.setChecked(turnOn);
}
}
}
@@ -443,7 +468,14 @@ public class SecuritySettings extends RestrictedSettingsFragment
best = i;
}
}
- mLockAfter.setSummary(getString(R.string.lock_after_timeout_summary, entries[best]));
+
+ Preference preference = getPreferenceScreen().findPreference(KEY_TRUST_AGENT);
+ if (preference != null && preference.getTitle().length() > 0) {
+ mLockAfter.setSummary(getString(R.string.lock_after_timeout_summary_with_exception,
+ entries[best], preference.getTitle()));
+ } else {
+ mLockAfter.setSummary(getString(R.string.lock_after_timeout_summary, entries[best]));
+ }
}
private void disableUnusableTimeouts(long maxTimeout) {
@@ -476,6 +508,14 @@ public class SecuritySettings extends RestrictedSettingsFragment
}
@Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ if (mTrustAgentClickIntent != null) {
+ outState.putParcelable(TRUST_AGENT_CLICK_INTENT, mTrustAgentClickIntent);
+ }
+ }
+
+ @Override
public void onResume() {
super.onResume();
@@ -503,23 +543,14 @@ public class SecuritySettings extends RestrictedSettingsFragment
if (mResetCredentials != null) {
mResetCredentials.setEnabled(!mKeyStore.isEmpty());
}
-
- if (mEnableKeyguardWidgets != null) {
- mEnableKeyguardWidgets.setChecked(lockPatternUtils.getWidgetsEnabled());
- }
}
@Override
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
- if (ensurePinRestrictedPreference(preference)) {
- return true;
- }
final String key = preference.getKey();
-
- final LockPatternUtils lockPatternUtils = mChooseLockSettingsHelper.utils();
if (KEY_UNLOCK_SET_OR_CHANGE.equals(key)) {
startFragment(this, "com.android.settings.ChooseLockGeneric$ChooseLockGenericFragment",
- SET_OR_CHANGE_LOCK_METHOD_REQUEST, null);
+ R.string.lock_settings_picker_title, SET_OR_CHANGE_LOCK_METHOD_REQUEST, null);
} else if (KEY_BIOMETRIC_WEAK_IMPROVE_MATCHING.equals(key)) {
ChooseLockSettingsHelper helper =
new ChooseLockSettingsHelper(this.getActivity(), this);
@@ -531,59 +562,23 @@ public class SecuritySettings extends RestrictedSettingsFragment
// can't be reached, but is here in case things change in the future
startBiometricWeakImprove();
}
- } else if (KEY_BIOMETRIC_WEAK_LIVELINESS.equals(key)) {
- if (isToggled(preference)) {
- lockPatternUtils.setBiometricWeakLivelinessEnabled(true);
- } else {
- // In this case the user has just unchecked the checkbox, but this action requires
- // them to confirm their password. We need to re-check the checkbox until
- // they've confirmed their password
- mBiometricWeakLiveliness.setChecked(true);
- ChooseLockSettingsHelper helper =
- new ChooseLockSettingsHelper(this.getActivity(), this);
- if (!helper.launchConfirmationActivity(
- CONFIRM_EXISTING_FOR_BIOMETRIC_WEAK_LIVELINESS_OFF, null, null)) {
- // If this returns false, it means no password confirmation is required, so
- // go ahead and uncheck it here.
- // Note: currently a backup is required for biometric_weak so this code path
- // can't be reached, but is here in case things change in the future
- lockPatternUtils.setBiometricWeakLivelinessEnabled(false);
- mBiometricWeakLiveliness.setChecked(false);
- }
- }
- } else if (KEY_LOCK_ENABLED.equals(key)) {
- lockPatternUtils.setLockPatternEnabled(isToggled(preference));
- } else if (KEY_VISIBLE_PATTERN.equals(key)) {
- lockPatternUtils.setVisiblePatternEnabled(isToggled(preference));
- } else if (KEY_POWER_INSTANTLY_LOCKS.equals(key)) {
- lockPatternUtils.setPowerButtonInstantlyLocks(isToggled(preference));
- } else if (KEY_ENABLE_WIDGETS.equals(key)) {
- lockPatternUtils.setWidgetsEnabled(mEnableKeyguardWidgets.isChecked());
- } else if (preference == mShowPassword) {
- Settings.System.putInt(getContentResolver(), Settings.System.TEXT_SHOW_PASSWORD,
- mShowPassword.isChecked() ? 1 : 0);
- } else if (preference == mToggleAppInstallation) {
- if (mToggleAppInstallation.isChecked()) {
- mToggleAppInstallation.setChecked(false);
- warnAppInstallation();
- } else {
- setNonMarketAppsAllowed(false);
+ } else if (KEY_TRUST_AGENT.equals(key)) {
+ ChooseLockSettingsHelper helper =
+ new ChooseLockSettingsHelper(this.getActivity(), this);
+ mTrustAgentClickIntent = preference.getIntent();
+ if (!helper.launchConfirmationActivity(CHANGE_TRUST_AGENT_SETTINGS, null, null) &&
+ mTrustAgentClickIntent != null) {
+ // If this returns false, it means no password confirmation is required.
+ startActivity(mTrustAgentClickIntent);
+ mTrustAgentClickIntent = null;
}
- } else if (KEY_TOGGLE_VERIFY_APPLICATIONS.equals(key)) {
- Settings.Global.putInt(getContentResolver(), Settings.Global.PACKAGE_VERIFIER_ENABLE,
- mToggleVerifyApps.isChecked() ? 1 : 0);
} else {
// If we didn't handle it, let preferences handle it.
return super.onPreferenceTreeClick(preferenceScreen, preference);
}
-
return true;
}
- private boolean isToggled(Preference pref) {
- return ((CheckBoxPreference) pref).isChecked();
- }
-
/**
* see confirmPatternThenDisableAndClear
*/
@@ -602,13 +597,22 @@ public class SecuritySettings extends RestrictedSettingsFragment
// is called by grabbing the value from lockPatternUtils. We can't set it here
// because mBiometricWeakLiveliness could be null
return;
+ } else if (requestCode == CHANGE_TRUST_AGENT_SETTINGS && resultCode == Activity.RESULT_OK) {
+ if (mTrustAgentClickIntent != null) {
+ startActivity(mTrustAgentClickIntent);
+ mTrustAgentClickIntent = null;
+ }
+ return;
}
createPreferenceHierarchy();
}
@Override
public boolean onPreferenceChange(Preference preference, Object value) {
- if (preference == mLockAfter) {
+ boolean result = true;
+ final String key = preference.getKey();
+ final LockPatternUtils lockPatternUtils = mChooseLockSettingsHelper.utils();
+ if (KEY_LOCK_AFTER_TIMEOUT.equals(key)) {
int timeout = Integer.parseInt((String) value);
try {
Settings.Secure.putInt(getContentResolver(),
@@ -617,8 +621,46 @@ public class SecuritySettings extends RestrictedSettingsFragment
Log.e("SecuritySettings", "could not persist lockAfter timeout setting", e);
}
updateLockAfterPreferenceSummary();
+ } else if (KEY_LOCK_ENABLED.equals(key)) {
+ lockPatternUtils.setLockPatternEnabled((Boolean) value);
+ } else if (KEY_VISIBLE_PATTERN.equals(key)) {
+ lockPatternUtils.setVisiblePatternEnabled((Boolean) value);
+ } else if (KEY_BIOMETRIC_WEAK_LIVELINESS.equals(key)) {
+ if ((Boolean) value) {
+ lockPatternUtils.setBiometricWeakLivelinessEnabled(true);
+ } else {
+ // In this case the user has just unchecked the checkbox, but this action requires
+ // them to confirm their password. We need to re-check the checkbox until
+ // they've confirmed their password
+ mBiometricWeakLiveliness.setChecked(true);
+ ChooseLockSettingsHelper helper =
+ new ChooseLockSettingsHelper(this.getActivity(), this);
+ if (!helper.launchConfirmationActivity(
+ CONFIRM_EXISTING_FOR_BIOMETRIC_WEAK_LIVELINESS_OFF, null, null)) {
+ // If this returns false, it means no password confirmation is required, so
+ // go ahead and uncheck it here.
+ // Note: currently a backup is required for biometric_weak so this code path
+ // can't be reached, but is here in case things change in the future
+ lockPatternUtils.setBiometricWeakLivelinessEnabled(false);
+ mBiometricWeakLiveliness.setChecked(false);
+ }
+ }
+ } else if (KEY_POWER_INSTANTLY_LOCKS.equals(key)) {
+ mLockPatternUtils.setPowerButtonInstantlyLocks((Boolean) value);
+ } else if (KEY_SHOW_PASSWORD.equals(key)) {
+ Settings.System.putInt(getContentResolver(), Settings.System.TEXT_SHOW_PASSWORD,
+ ((Boolean) value) ? 1 : 0);
+ } else if (KEY_TOGGLE_INSTALL_APPLICATIONS.equals(key)) {
+ if ((Boolean) value) {
+ mToggleAppInstallation.setChecked(false);
+ warnAppInstallation();
+ // Don't change Switch status until user makes choice in dialog, so return false.
+ result = false;
+ } else {
+ setNonMarketAppsAllowed(false);
+ }
}
- return true;
+ return result;
}
@Override
@@ -631,4 +673,153 @@ public class SecuritySettings extends RestrictedSettingsFragment
intent.setClassName("com.android.facelock", "com.android.facelock.AddToSetup");
startActivity(intent);
}
+
+ /**
+ * For Search. Please keep it in sync when updating "createPreferenceHierarchy()"
+ */
+ public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new SecuritySearchIndexProvider();
+
+ private static class SecuritySearchIndexProvider extends BaseSearchIndexProvider {
+
+ boolean mIsPrimary;
+
+ public SecuritySearchIndexProvider() {
+ super();
+
+ mIsPrimary = UserHandle.myUserId() == 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);
+
+ if (mIsPrimary) {
+ DevicePolicyManager dpm = (DevicePolicyManager)
+ context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+
+ switch (dpm.getStorageEncryptionStatus()) {
+ case DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE:
+ // The device is currently encrypted.
+ resId = R.xml.security_settings_encrypted;
+ break;
+ case DevicePolicyManager.ENCRYPTION_STATUS_INACTIVE:
+ // This device supports encryption but isn't encrypted.
+ resId = R.xml.security_settings_unencrypted;
+ break;
+ }
+
+ sir = new SearchIndexableResource(context);
+ sir.xmlResId = resId;
+ result.add(sir);
+ }
+
+ // Append the rest of the settings
+ sir = new SearchIndexableResource(context);
+ sir.xmlResId = R.xml.security_settings_misc;
+ 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.security_settings_title);
+
+ 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);
+ }
+
+ // Credential storage
+ final UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
+
+ if (!um.hasUserRestriction(UserManager.DISALLOW_CONFIG_CREDENTIALS)) {
+ KeyStore keyStore = KeyStore.getInstance();
+
+ final int storageSummaryRes = keyStore.isHardwareBacked() ?
+ R.string.credential_storage_type_hardware :
+ R.string.credential_storage_type_software;
+
+ data = new SearchIndexableRaw(context);
+ data.title = res.getString(storageSummaryRes);
+ data.screenTitle = screenTitle;
+ result.add(data);
+ }
+
+ // Advanced
+ final LockPatternUtils lockPatternUtils = new LockPatternUtils(context);
+ if (lockPatternUtils.isSecure()) {
+ ArrayList<TrustAgentComponentInfo> agents =
+ getActiveTrustAgents(context.getPackageManager(), lockPatternUtils);
+ 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;
+ }
+
+ @Override
+ public List<String> getNonIndexableKeys(Context context) {
+ final List<String> keys = new ArrayList<String>();
+
+ LockPatternUtils lockPatternUtils = new LockPatternUtils(context);
+ // Add options for lock/unlock screen
+ int resId = getResIdForLockUnlockScreen(context, lockPatternUtils);
+
+ // don't display visible pattern if biometric and backup is not pattern
+ if (resId == R.xml.security_settings_biometric_weak &&
+ lockPatternUtils.getKeyguardStoredPasswordQuality() !=
+ DevicePolicyManager.PASSWORD_QUALITY_SOMETHING) {
+ keys.add(KEY_VISIBLE_PATTERN);
+ }
+
+ // Do not display SIM lock for devices without an Icc card
+ TelephonyManager tm = TelephonyManager.getDefault();
+ if (!mIsPrimary || !tm.hasIccCard()) {
+ keys.add(KEY_SIM_LOCK);
+ }
+
+ final UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ if (um.hasUserRestriction(UserManager.DISALLOW_CONFIG_CREDENTIALS)) {
+ keys.add(KEY_CREDENTIALS_MANAGER);
+ }
+
+ // TrustAgent settings disappear when the user has no primary security.
+ if (!lockPatternUtils.isSecure()) {
+ keys.add(KEY_TRUST_AGENT);
+ keys.add(KEY_MANAGE_TRUST_AGENTS);
+ }
+
+ return keys;
+ }
+ }
+
}
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index f6f49b8..84bf615 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -16,1105 +16,90 @@
package com.android.settings;
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.accounts.OnAccountsUpdateListener;
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.DialogFragment;
-import android.app.admin.DevicePolicyManager;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.SharedPreferences;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
-import android.graphics.drawable.Drawable;
-import android.nfc.NfcAdapter;
-import android.os.Bundle;
-import android.os.INetworkManagementService;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.preference.Preference;
-import android.preference.PreferenceActivity;
-import android.preference.PreferenceFragment;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.Button;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.ListAdapter;
-import android.widget.Switch;
-import android.widget.TextView;
-
-import com.android.internal.util.ArrayUtils;
-import com.android.settings.accessibility.AccessibilitySettings;
-import com.android.settings.accessibility.CaptionPropertiesFragment;
-import com.android.settings.accessibility.ToggleAccessibilityServicePreferenceFragment;
-import com.android.settings.accounts.AccountSyncSettings;
-import com.android.settings.accounts.AuthenticatorHelper;
-import com.android.settings.accounts.ManageAccountsSettings;
import com.android.settings.applications.AppOpsSummary;
-import com.android.settings.applications.ManageApplications;
-import com.android.settings.applications.ProcessStatsUi;
-import com.android.settings.bluetooth.BluetoothEnabler;
-import com.android.settings.bluetooth.BluetoothSettings;
-import com.android.settings.deviceinfo.Memory;
-import com.android.settings.deviceinfo.UsbSettings;
-import com.android.settings.fuelgauge.PowerUsageSummary;
-import com.android.settings.inputmethod.InputMethodAndLanguageSettings;
-import com.android.settings.inputmethod.KeyboardLayoutPickerFragment;
-import com.android.settings.inputmethod.SpellCheckersSettings;
-import com.android.settings.inputmethod.UserDictionaryList;
-import com.android.settings.location.LocationSettings;
-import com.android.settings.nfc.AndroidBeam;
-import com.android.settings.nfc.PaymentSettings;
-import com.android.settings.print.PrintJobSettingsFragment;
-import com.android.settings.print.PrintServiceSettingsFragment;
-import com.android.settings.print.PrintSettingsFragment;
-import com.android.settings.tts.TextToSpeechSettings;
-import com.android.settings.users.UserSettings;
-import com.android.settings.vpn2.VpnSettings;
-import com.android.settings.wfd.WifiDisplaySettings;
-import com.android.settings.wifi.AdvancedWifiSettings;
-import com.android.settings.wifi.WifiEnabler;
-import com.android.settings.wifi.WifiSettings;
-import com.android.settings.wifi.p2p.WifiP2pSettings;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
/**
- * Top-level settings activity to handle single pane and double pane UI layout.
+ * Top-level Settings activity
*/
-public class Settings extends PreferenceActivity
- implements ButtonBarHandler, OnAccountsUpdateListener {
-
- private static final String LOG_TAG = "Settings";
-
- private static final String META_DATA_KEY_HEADER_ID =
- "com.android.settings.TOP_LEVEL_HEADER_ID";
- private static final String META_DATA_KEY_FRAGMENT_CLASS =
- "com.android.settings.FRAGMENT_CLASS";
- private static final String META_DATA_KEY_PARENT_TITLE =
- "com.android.settings.PARENT_FRAGMENT_TITLE";
- private static final String META_DATA_KEY_PARENT_FRAGMENT_CLASS =
- "com.android.settings.PARENT_FRAGMENT_CLASS";
-
- private static final String EXTRA_UI_OPTIONS = "settings:ui_options";
-
- private static final String SAVE_KEY_CURRENT_HEADER = "com.android.settings.CURRENT_HEADER";
- private static final String SAVE_KEY_PARENT_HEADER = "com.android.settings.PARENT_HEADER";
-
- static final int DIALOG_ONLY_ONE_HOME = 1;
-
- private static boolean sShowNoHomeNotice = false;
-
- private String mFragmentClass;
- private int mTopLevelHeaderId;
- private Header mFirstHeader;
- private Header mCurrentHeader;
- private Header mParentHeader;
- private boolean mInLocalHeaderSwitch;
-
- // Show only these settings for restricted users
- private int[] SETTINGS_FOR_RESTRICTED = {
- R.id.wireless_section,
- R.id.wifi_settings,
- R.id.bluetooth_settings,
- R.id.data_usage_settings,
- R.id.wireless_settings,
- R.id.device_section,
- R.id.sound_settings,
- R.id.display_settings,
- R.id.storage_settings,
- R.id.application_settings,
- R.id.battery_settings,
- R.id.personal_section,
- R.id.location_settings,
- R.id.security_settings,
- R.id.language_settings,
- R.id.user_settings,
- R.id.account_settings,
- R.id.account_add,
- R.id.system_section,
- R.id.date_time_settings,
- R.id.about_settings,
- R.id.accessibility_settings,
- R.id.print_settings,
- R.id.nfc_payment_settings,
- R.id.home_settings
- };
-
- private SharedPreferences mDevelopmentPreferences;
- private SharedPreferences.OnSharedPreferenceChangeListener mDevelopmentPreferencesListener;
-
- // TODO: Update Call Settings based on airplane mode state.
-
- protected HashMap<Integer, Integer> mHeaderIndexMap = new HashMap<Integer, Integer>();
-
- private AuthenticatorHelper mAuthenticatorHelper;
- private Header mLastHeader;
- private boolean mListeningToAccountUpdates;
-
- private boolean mBatteryPresent = true;
- private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
- boolean batteryPresent = Utils.isBatteryPresent(intent);
-
- if (mBatteryPresent != batteryPresent) {
- mBatteryPresent = batteryPresent;
- invalidateHeaders();
- }
- }
- }
- };
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- if (getIntent().hasExtra(EXTRA_UI_OPTIONS)) {
- getWindow().setUiOptions(getIntent().getIntExtra(EXTRA_UI_OPTIONS, 0));
- }
-
- mAuthenticatorHelper = new AuthenticatorHelper();
- mAuthenticatorHelper.updateAuthDescriptions(this);
- mAuthenticatorHelper.onAccountsUpdated(this, null);
-
- mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE,
- Context.MODE_PRIVATE);
-
- getMetaData();
- mInLocalHeaderSwitch = true;
- super.onCreate(savedInstanceState);
- mInLocalHeaderSwitch = false;
-
- if (!onIsHidingHeaders() && onIsMultiPane()) {
- highlightHeader(mTopLevelHeaderId);
- // Force the title so that it doesn't get overridden by a direct launch of
- // a specific settings screen.
- setTitle(R.string.settings_label);
- }
-
- // Retrieve any saved state
- if (savedInstanceState != null) {
- mCurrentHeader = savedInstanceState.getParcelable(SAVE_KEY_CURRENT_HEADER);
- mParentHeader = savedInstanceState.getParcelable(SAVE_KEY_PARENT_HEADER);
- }
-
- // If the current header was saved, switch to it
- if (savedInstanceState != null && mCurrentHeader != null) {
- //switchToHeaderLocal(mCurrentHeader);
- showBreadCrumbs(mCurrentHeader.title, null);
- }
-
- if (mParentHeader != null) {
- setParentTitle(mParentHeader.title, null, new OnClickListener() {
- @Override
- public void onClick(View v) {
- switchToParent(mParentHeader.fragment);
- }
- });
- }
-
- // Override up navigation for multi-pane, since we handle it in the fragment breadcrumbs
- if (onIsMultiPane()) {
- getActionBar().setDisplayHomeAsUpEnabled(false);
- getActionBar().setHomeButtonEnabled(false);
- }
- }
-
- @Override
- protected void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
-
- // Save the current fragment, if it is the same as originally launched
- if (mCurrentHeader != null) {
- outState.putParcelable(SAVE_KEY_CURRENT_HEADER, mCurrentHeader);
- }
- if (mParentHeader != null) {
- outState.putParcelable(SAVE_KEY_PARENT_HEADER, mParentHeader);
- }
- }
-
- @Override
- public void onResume() {
- super.onResume();
-
- mDevelopmentPreferencesListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
- @Override
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
- invalidateHeaders();
- }
- };
- mDevelopmentPreferences.registerOnSharedPreferenceChangeListener(
- mDevelopmentPreferencesListener);
-
- ListAdapter listAdapter = getListAdapter();
- if (listAdapter instanceof HeaderAdapter) {
- ((HeaderAdapter) listAdapter).resume();
- }
- invalidateHeaders();
-
- registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
- }
-
- @Override
- public void onPause() {
- super.onPause();
-
- unregisterReceiver(mBatteryInfoReceiver);
-
- ListAdapter listAdapter = getListAdapter();
- if (listAdapter instanceof HeaderAdapter) {
- ((HeaderAdapter) listAdapter).pause();
- }
-
- mDevelopmentPreferences.unregisterOnSharedPreferenceChangeListener(
- mDevelopmentPreferencesListener);
- mDevelopmentPreferencesListener = null;
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- if (mListeningToAccountUpdates) {
- AccountManager.get(this).removeOnAccountsUpdatedListener(this);
- }
- }
-
- @Override
- public boolean onIsMultiPane() {
- return false;
- }
-
- private static final String[] ENTRY_FRAGMENTS = {
- WirelessSettings.class.getName(),
- WifiSettings.class.getName(),
- AdvancedWifiSettings.class.getName(),
- BluetoothSettings.class.getName(),
- TetherSettings.class.getName(),
- WifiP2pSettings.class.getName(),
- VpnSettings.class.getName(),
- DateTimeSettings.class.getName(),
- LocalePicker.class.getName(),
- InputMethodAndLanguageSettings.class.getName(),
- SpellCheckersSettings.class.getName(),
- UserDictionaryList.class.getName(),
- UserDictionarySettings.class.getName(),
- SoundSettings.class.getName(),
- DisplaySettings.class.getName(),
- DeviceInfoSettings.class.getName(),
- ManageApplications.class.getName(),
- ProcessStatsUi.class.getName(),
- NotificationStation.class.getName(),
- LocationSettings.class.getName(),
- SecuritySettings.class.getName(),
- PrivacySettings.class.getName(),
- DeviceAdminSettings.class.getName(),
- AccessibilitySettings.class.getName(),
- CaptionPropertiesFragment.class.getName(),
- TextToSpeechSettings.class.getName(),
- Memory.class.getName(),
- DevelopmentSettings.class.getName(),
- UsbSettings.class.getName(),
- AndroidBeam.class.getName(),
- WifiDisplaySettings.class.getName(),
- PowerUsageSummary.class.getName(),
- AccountSyncSettings.class.getName(),
- CryptKeeperSettings.class.getName(),
- DataUsageSummary.class.getName(),
- DreamSettings.class.getName(),
- UserSettings.class.getName(),
- NotificationAccessSettings.class.getName(),
- ManageAccountsSettings.class.getName(),
- PrintSettingsFragment.class.getName(),
- PrintJobSettingsFragment.class.getName(),
- TrustedCredentialsSettings.class.getName(),
- PaymentSettings.class.getName(),
- KeyboardLayoutPickerFragment.class.getName()
- };
-
- @Override
- protected boolean isValidFragment(String fragmentName) {
- // Almost all fragments are wrapped in this,
- // except for a few that have their own activities.
- for (int i = 0; i < ENTRY_FRAGMENTS.length; i++) {
- if (ENTRY_FRAGMENTS[i].equals(fragmentName)) return true;
- }
- return false;
- }
-
- private void switchToHeaderLocal(Header header) {
- mInLocalHeaderSwitch = true;
- switchToHeader(header);
- mInLocalHeaderSwitch = false;
- }
-
- @Override
- public void switchToHeader(Header header) {
- if (!mInLocalHeaderSwitch) {
- mCurrentHeader = null;
- mParentHeader = null;
- }
- super.switchToHeader(header);
- }
-
- /**
- * Switch to parent fragment and store the grand parent's info
- * @param className name of the activity wrapper for the parent fragment.
- */
- private void switchToParent(String className) {
- final ComponentName cn = new ComponentName(this, className);
- try {
- final PackageManager pm = getPackageManager();
- final ActivityInfo parentInfo = pm.getActivityInfo(cn, PackageManager.GET_META_DATA);
-
- if (parentInfo != null && parentInfo.metaData != null) {
- String fragmentClass = parentInfo.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
- CharSequence fragmentTitle = parentInfo.loadLabel(pm);
- Header parentHeader = new Header();
- parentHeader.fragment = fragmentClass;
- parentHeader.title = fragmentTitle;
- mCurrentHeader = parentHeader;
-
- switchToHeaderLocal(parentHeader);
- highlightHeader(mTopLevelHeaderId);
-
- mParentHeader = new Header();
- mParentHeader.fragment
- = parentInfo.metaData.getString(META_DATA_KEY_PARENT_FRAGMENT_CLASS);
- mParentHeader.title = parentInfo.metaData.getString(META_DATA_KEY_PARENT_TITLE);
- }
- } catch (NameNotFoundException nnfe) {
- Log.w(LOG_TAG, "Could not find parent activity : " + className);
- }
- }
-
- @Override
- public void onNewIntent(Intent intent) {
- super.onNewIntent(intent);
-
- // If it is not launched from history, then reset to top-level
- if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0) {
- if (mFirstHeader != null && !onIsHidingHeaders() && onIsMultiPane()) {
- switchToHeaderLocal(mFirstHeader);
- }
- getListView().setSelectionFromTop(0, 0);
- }
- }
-
- private void highlightHeader(int id) {
- if (id != 0) {
- Integer index = mHeaderIndexMap.get(id);
- if (index != null) {
- getListView().setItemChecked(index, true);
- if (isMultiPane()) {
- getListView().smoothScrollToPosition(index);
- }
- }
- }
- }
-
- @Override
- public Intent getIntent() {
- Intent superIntent = super.getIntent();
- String startingFragment = getStartingFragmentClass(superIntent);
- // This is called from super.onCreate, isMultiPane() is not yet reliable
- // Do not use onIsHidingHeaders either, which relies itself on this method
- if (startingFragment != null && !onIsMultiPane()) {
- Intent modIntent = new Intent(superIntent);
- modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
- Bundle args = superIntent.getExtras();
- if (args != null) {
- args = new Bundle(args);
- } else {
- args = new Bundle();
- }
- args.putParcelable("intent", superIntent);
- modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, superIntent.getExtras());
- return modIntent;
- }
- return superIntent;
- }
-
- /**
- * Checks if the component name in the intent is different from the Settings class and
- * returns the class name to load as a fragment.
- */
- protected String getStartingFragmentClass(Intent intent) {
- if (mFragmentClass != null) return mFragmentClass;
-
- String intentClass = intent.getComponent().getClassName();
- if (intentClass.equals(getClass().getName())) return null;
-
- if ("com.android.settings.ManageApplications".equals(intentClass)
- || "com.android.settings.RunningServices".equals(intentClass)
- || "com.android.settings.applications.StorageUse".equals(intentClass)) {
- // Old names of manage apps.
- intentClass = com.android.settings.applications.ManageApplications.class.getName();
- }
-
- return intentClass;
- }
-
- /**
- * Override initial header when an activity-alias is causing Settings to be launched
- * for a specific fragment encoded in the android:name parameter.
- */
- @Override
- public Header onGetInitialHeader() {
- String fragmentClass = getStartingFragmentClass(super.getIntent());
- if (fragmentClass != null) {
- Header header = new Header();
- header.fragment = fragmentClass;
- header.title = getTitle();
- header.fragmentArguments = getIntent().getExtras();
- mCurrentHeader = header;
- return header;
- }
-
- return mFirstHeader;
- }
-
- @Override
- public Intent onBuildStartFragmentIntent(String fragmentName, Bundle args,
- int titleRes, int shortTitleRes) {
- Intent intent = super.onBuildStartFragmentIntent(fragmentName, args,
- titleRes, shortTitleRes);
-
- // Some fragments want split ActionBar; these should stay in sync with
- // uiOptions for fragments also defined as activities in manifest.
- if (WifiSettings.class.getName().equals(fragmentName) ||
- WifiP2pSettings.class.getName().equals(fragmentName) ||
- BluetoothSettings.class.getName().equals(fragmentName) ||
- DreamSettings.class.getName().equals(fragmentName) ||
- LocationSettings.class.getName().equals(fragmentName) ||
- ToggleAccessibilityServicePreferenceFragment.class.getName().equals(fragmentName) ||
- PrintSettingsFragment.class.getName().equals(fragmentName) ||
- PrintServiceSettingsFragment.class.getName().equals(fragmentName)) {
- intent.putExtra(EXTRA_UI_OPTIONS, ActivityInfo.UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW);
- }
-
- intent.setClass(this, SubSettings.class);
- return intent;
- }
-
- /**
- * Populate the activity with the top-level headers.
- */
- @Override
- public void onBuildHeaders(List<Header> headers) {
- if (!onIsHidingHeaders()) {
- loadHeadersFromResource(R.xml.settings_headers, headers);
- updateHeaderList(headers);
- }
- }
-
- private void updateHeaderList(List<Header> target) {
- final boolean showDev = mDevelopmentPreferences.getBoolean(
- DevelopmentSettings.PREF_SHOW,
- android.os.Build.TYPE.equals("eng"));
- int i = 0;
-
- final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
- mHeaderIndexMap.clear();
- while (i < target.size()) {
- Header header = target.get(i);
- // Ids are integers, so downcasting
- int id = (int) header.id;
- if (id == R.id.operator_settings || id == R.id.manufacturer_settings) {
- Utils.updateHeaderToSpecificActivityFromMetaDataOrRemove(this, target, header);
- } else if (id == R.id.wifi_settings) {
- // Remove WiFi Settings if WiFi service is not available.
- if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
- target.remove(i);
- }
- } else if (id == R.id.bluetooth_settings) {
- // Remove Bluetooth Settings if Bluetooth service is not available.
- if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
- target.remove(i);
- }
- } else if (id == R.id.data_usage_settings) {
- // Remove data usage when kernel module not enabled
- final INetworkManagementService netManager = INetworkManagementService.Stub
- .asInterface(ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
- try {
- if (!netManager.isBandwidthControlEnabled()) {
- target.remove(i);
- }
- } catch (RemoteException e) {
- // ignored
- }
- } else if (id == R.id.battery_settings) {
- // Remove battery settings when battery is not available. (e.g. TV)
-
- if (!mBatteryPresent) {
- target.remove(i);
- }
- } else if (id == R.id.account_settings) {
- int headerIndex = i + 1;
- i = insertAccountsHeaders(target, headerIndex);
- } else if (id == R.id.home_settings) {
- if (!updateHomeSettingHeaders(header)) {
- target.remove(i);
- }
- } else if (id == R.id.user_settings) {
- if (!UserHandle.MU_ENABLED
- || !UserManager.supportsMultipleUsers()
- || Utils.isMonkeyRunning()) {
- target.remove(i);
- }
- } else if (id == R.id.nfc_payment_settings) {
- if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)) {
- target.remove(i);
- } else {
- // Only show if NFC is on and we have the HCE feature
- NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
- if (!adapter.isEnabled() || !getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
- target.remove(i);
- }
- }
- } else if (id == R.id.development_settings) {
- if (!showDev) {
- target.remove(i);
- }
- } else if (id == R.id.account_add) {
- if (um.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
- target.remove(i);
- }
- }
-
- if (i < target.size() && target.get(i) == header
- && UserHandle.MU_ENABLED && UserHandle.myUserId() != 0
- && !ArrayUtils.contains(SETTINGS_FOR_RESTRICTED, id)) {
- target.remove(i);
- }
-
- // Increment if the current one wasn't removed by the Utils code.
- if (i < target.size() && target.get(i) == header) {
- // Hold on to the first header, when we need to reset to the top-level
- if (mFirstHeader == null &&
- HeaderAdapter.getHeaderType(header) != HeaderAdapter.HEADER_TYPE_CATEGORY) {
- mFirstHeader = header;
- }
- mHeaderIndexMap.put(id, i);
- i++;
- }
- }
- }
-
- private int insertAccountsHeaders(List<Header> target, int headerIndex) {
- String[] accountTypes = mAuthenticatorHelper.getEnabledAccountTypes();
- List<Header> accountHeaders = new ArrayList<Header>(accountTypes.length);
- for (String accountType : accountTypes) {
- CharSequence label = mAuthenticatorHelper.getLabelForType(this, accountType);
- if (label == null) {
- continue;
- }
-
- Account[] accounts = AccountManager.get(this).getAccountsByType(accountType);
- boolean skipToAccount = accounts.length == 1
- && !mAuthenticatorHelper.hasAccountPreferences(accountType);
- Header accHeader = new Header();
- accHeader.title = label;
- if (accHeader.extras == null) {
- accHeader.extras = new Bundle();
- }
- if (skipToAccount) {
- accHeader.breadCrumbTitleRes = R.string.account_sync_settings_title;
- accHeader.breadCrumbShortTitleRes = R.string.account_sync_settings_title;
- accHeader.fragment = AccountSyncSettings.class.getName();
- accHeader.fragmentArguments = new Bundle();
- // Need this for the icon
- accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
- accHeader.extras.putParcelable(AccountSyncSettings.ACCOUNT_KEY, accounts[0]);
- accHeader.fragmentArguments.putParcelable(AccountSyncSettings.ACCOUNT_KEY,
- accounts[0]);
- } else {
- accHeader.breadCrumbTitle = label;
- accHeader.breadCrumbShortTitle = label;
- accHeader.fragment = ManageAccountsSettings.class.getName();
- accHeader.fragmentArguments = new Bundle();
- accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
- accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE,
- accountType);
- if (!isMultiPane()) {
- accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_LABEL,
- label.toString());
- }
- }
- accountHeaders.add(accHeader);
- mAuthenticatorHelper.preloadDrawableForType(this, accountType);
- }
-
- // Sort by label
- Collections.sort(accountHeaders, new Comparator<Header>() {
- @Override
- public int compare(Header h1, Header h2) {
- return h1.title.toString().compareTo(h2.title.toString());
- }
- });
-
- for (Header header : accountHeaders) {
- target.add(headerIndex++, header);
- }
- if (!mListeningToAccountUpdates) {
- AccountManager.get(this).addOnAccountsUpdatedListener(this, null, true);
- mListeningToAccountUpdates = true;
- }
- return headerIndex;
- }
-
- private boolean updateHomeSettingHeaders(Header header) {
- // Once we decide to show Home settings, keep showing it forever
- SharedPreferences sp = getSharedPreferences(HomeSettings.HOME_PREFS, Context.MODE_PRIVATE);
- if (sp.getBoolean(HomeSettings.HOME_PREFS_DO_SHOW, false)) {
- return true;
- }
-
- try {
- final ArrayList<ResolveInfo> homeApps = new ArrayList<ResolveInfo>();
- getPackageManager().getHomeActivities(homeApps);
- if (homeApps.size() < 2) {
- // When there's only one available home app, omit this settings
- // category entirely at the top level UI. If the user just
- // uninstalled the penultimate home app candidiate, we also
- // now tell them about why they aren't seeing 'Home' in the list.
- if (sShowNoHomeNotice) {
- sShowNoHomeNotice = false;
- NoHomeDialogFragment.show(this);
- }
- return false;
- } else {
- // Okay, we're allowing the Home settings category. Tell it, when
- // invoked via this front door, that we'll need to be told about the
- // case when the user uninstalls all but one home app.
- if (header.fragmentArguments == null) {
- header.fragmentArguments = new Bundle();
- }
- header.fragmentArguments.putBoolean(HomeSettings.HOME_SHOW_NOTICE, true);
- }
- } catch (Exception e) {
- // Can't look up the home activity; bail on configuring the icon
- Log.w(LOG_TAG, "Problem looking up home activity!", e);
- }
-
- sp.edit().putBoolean(HomeSettings.HOME_PREFS_DO_SHOW, true).apply();
- return true;
- }
-
- private void getMetaData() {
- try {
- ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
- PackageManager.GET_META_DATA);
- if (ai == null || ai.metaData == null) return;
- mTopLevelHeaderId = ai.metaData.getInt(META_DATA_KEY_HEADER_ID);
- mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
-
- // Check if it has a parent specified and create a Header object
- final int parentHeaderTitleRes = ai.metaData.getInt(META_DATA_KEY_PARENT_TITLE);
- String parentFragmentClass = ai.metaData.getString(META_DATA_KEY_PARENT_FRAGMENT_CLASS);
- if (parentFragmentClass != null) {
- mParentHeader = new Header();
- mParentHeader.fragment = parentFragmentClass;
- if (parentHeaderTitleRes != 0) {
- mParentHeader.title = getResources().getString(parentHeaderTitleRes);
- }
- }
- } catch (NameNotFoundException nnfe) {
- // No recovery
- }
- }
-
- @Override
- public boolean hasNextButton() {
- return super.hasNextButton();
- }
-
- @Override
- public Button getNextButton() {
- return super.getNextButton();
- }
-
- public static class NoHomeDialogFragment extends DialogFragment {
- public static void show(Activity parent) {
- final NoHomeDialogFragment dialog = new NoHomeDialogFragment();
- dialog.show(parent.getFragmentManager(), null);
- }
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- return new AlertDialog.Builder(getActivity())
- .setMessage(R.string.only_one_home_message)
- .setPositiveButton(android.R.string.ok, null)
- .create();
- }
- }
-
- private static class HeaderAdapter extends ArrayAdapter<Header> {
- static final int HEADER_TYPE_CATEGORY = 0;
- static final int HEADER_TYPE_NORMAL = 1;
- static final int HEADER_TYPE_SWITCH = 2;
- static final int HEADER_TYPE_BUTTON = 3;
- private static final int HEADER_TYPE_COUNT = HEADER_TYPE_BUTTON + 1;
-
- private final WifiEnabler mWifiEnabler;
- private final BluetoothEnabler mBluetoothEnabler;
- private AuthenticatorHelper mAuthHelper;
- private DevicePolicyManager mDevicePolicyManager;
-
- private static class HeaderViewHolder {
- ImageView icon;
- TextView title;
- TextView summary;
- Switch switch_;
- ImageButton button_;
- View divider_;
- }
-
- private LayoutInflater mInflater;
-
- static int getHeaderType(Header header) {
- if (header.fragment == null && header.intent == null) {
- return HEADER_TYPE_CATEGORY;
- } else if (header.id == R.id.wifi_settings || header.id == R.id.bluetooth_settings) {
- return HEADER_TYPE_SWITCH;
- } else if (header.id == R.id.security_settings) {
- return HEADER_TYPE_BUTTON;
- } else {
- return HEADER_TYPE_NORMAL;
- }
- }
-
- @Override
- public int getItemViewType(int position) {
- Header header = getItem(position);
- return getHeaderType(header);
- }
-
- @Override
- public boolean areAllItemsEnabled() {
- return false; // because of categories
- }
-
- @Override
- public boolean isEnabled(int position) {
- return getItemViewType(position) != HEADER_TYPE_CATEGORY;
- }
-
- @Override
- public int getViewTypeCount() {
- return HEADER_TYPE_COUNT;
- }
-
- @Override
- public boolean hasStableIds() {
- return true;
- }
-
- public HeaderAdapter(Context context, List<Header> objects,
- AuthenticatorHelper authenticatorHelper, DevicePolicyManager dpm) {
- super(context, 0, objects);
-
- mAuthHelper = authenticatorHelper;
- mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-
- // Temp Switches provided as placeholder until the adapter replaces these with actual
- // Switches inflated from their layouts. Must be done before adapter is set in super
- mWifiEnabler = new WifiEnabler(context, new Switch(context));
- mBluetoothEnabler = new BluetoothEnabler(context, new Switch(context));
- mDevicePolicyManager = dpm;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- HeaderViewHolder holder;
- Header header = getItem(position);
- int headerType = getHeaderType(header);
- View view = null;
-
- if (convertView == null) {
- holder = new HeaderViewHolder();
- switch (headerType) {
- case HEADER_TYPE_CATEGORY:
- view = new TextView(getContext(), null,
- android.R.attr.listSeparatorTextViewStyle);
- holder.title = (TextView) view;
- break;
-
- case HEADER_TYPE_SWITCH:
- view = mInflater.inflate(R.layout.preference_header_switch_item, parent,
- false);
- holder.icon = (ImageView) view.findViewById(R.id.icon);
- holder.title = (TextView)
- view.findViewById(com.android.internal.R.id.title);
- holder.summary = (TextView)
- view.findViewById(com.android.internal.R.id.summary);
- holder.switch_ = (Switch) view.findViewById(R.id.switchWidget);
- break;
-
- case HEADER_TYPE_BUTTON:
- view = mInflater.inflate(R.layout.preference_header_button_item, parent,
- false);
- holder.icon = (ImageView) view.findViewById(R.id.icon);
- holder.title = (TextView)
- view.findViewById(com.android.internal.R.id.title);
- holder.summary = (TextView)
- view.findViewById(com.android.internal.R.id.summary);
- holder.button_ = (ImageButton) view.findViewById(R.id.buttonWidget);
- holder.divider_ = view.findViewById(R.id.divider);
- break;
-
- case HEADER_TYPE_NORMAL:
- view = mInflater.inflate(
- R.layout.preference_header_item, parent,
- false);
- holder.icon = (ImageView) view.findViewById(R.id.icon);
- holder.title = (TextView)
- view.findViewById(com.android.internal.R.id.title);
- holder.summary = (TextView)
- view.findViewById(com.android.internal.R.id.summary);
- break;
- }
- view.setTag(holder);
- } else {
- view = convertView;
- holder = (HeaderViewHolder) view.getTag();
- }
-
- // All view fields must be updated every time, because the view may be recycled
- switch (headerType) {
- case HEADER_TYPE_CATEGORY:
- holder.title.setText(header.getTitle(getContext().getResources()));
- break;
-
- case HEADER_TYPE_SWITCH:
- // Would need a different treatment if the main menu had more switches
- if (header.id == R.id.wifi_settings) {
- mWifiEnabler.setSwitch(holder.switch_);
- } else {
- mBluetoothEnabler.setSwitch(holder.switch_);
- }
- updateCommonHeaderView(header, holder);
- break;
-
- case HEADER_TYPE_BUTTON:
- if (header.id == R.id.security_settings) {
- boolean hasCert = DevicePolicyManager.hasAnyCaCertsInstalled();
- if (hasCert) {
- holder.button_.setVisibility(View.VISIBLE);
- holder.divider_.setVisibility(View.VISIBLE);
- boolean isManaged = mDevicePolicyManager.getDeviceOwner() != null;
- if (isManaged) {
- holder.button_.setImageResource(R.drawable.ic_settings_about);
- } else {
- holder.button_.setImageResource(
- android.R.drawable.stat_notify_error);
- }
- holder.button_.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- Intent intent = new Intent(
- android.provider.Settings.ACTION_MONITORING_CERT_INFO);
- getContext().startActivity(intent);
- }
- });
- } else {
- holder.button_.setVisibility(View.GONE);
- holder.divider_.setVisibility(View.GONE);
- }
- }
- updateCommonHeaderView(header, holder);
- break;
-
- case HEADER_TYPE_NORMAL:
- updateCommonHeaderView(header, holder);
- break;
- }
-
- return view;
- }
-
- private void updateCommonHeaderView(Header header, HeaderViewHolder holder) {
- if (header.extras != null
- && header.extras.containsKey(ManageAccountsSettings.KEY_ACCOUNT_TYPE)) {
- String accType = header.extras.getString(
- ManageAccountsSettings.KEY_ACCOUNT_TYPE);
- Drawable icon = mAuthHelper.getDrawableForType(getContext(), accType);
- setHeaderIcon(holder, icon);
- } else {
- holder.icon.setImageResource(header.iconRes);
- }
- holder.title.setText(header.getTitle(getContext().getResources()));
- CharSequence summary = header.getSummary(getContext().getResources());
- if (!TextUtils.isEmpty(summary)) {
- holder.summary.setVisibility(View.VISIBLE);
- holder.summary.setText(summary);
- } else {
- holder.summary.setVisibility(View.GONE);
- }
- }
-
- private void setHeaderIcon(HeaderViewHolder holder, Drawable icon) {
- ViewGroup.LayoutParams lp = holder.icon.getLayoutParams();
- lp.width = getContext().getResources().getDimensionPixelSize(
- R.dimen.header_icon_width);
- lp.height = lp.width;
- holder.icon.setLayoutParams(lp);
- holder.icon.setImageDrawable(icon);
- }
-
- public void resume() {
- mWifiEnabler.resume();
- mBluetoothEnabler.resume();
- }
-
- public void pause() {
- mWifiEnabler.pause();
- mBluetoothEnabler.pause();
- }
- }
-
- @Override
- public void onHeaderClick(Header header, int position) {
- boolean revert = false;
- if (header.id == R.id.account_add) {
- revert = true;
- }
-
- super.onHeaderClick(header, position);
-
- if (revert && mLastHeader != null) {
- highlightHeader((int) mLastHeader.id);
- } else {
- mLastHeader = header;
- }
- }
-
- @Override
- public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
- // Override the fragment title for Wallpaper settings
- int titleRes = pref.getTitleRes();
- if (pref.getFragment().equals(WallpaperTypeSettings.class.getName())) {
- titleRes = R.string.wallpaper_settings_fragment_title;
- } else if (pref.getFragment().equals(OwnerInfoSettings.class.getName())
- && UserHandle.myUserId() != UserHandle.USER_OWNER) {
- if (UserManager.get(this).isLinkedUser()) {
- titleRes = R.string.profile_info_settings_title;
- } else {
- titleRes = R.string.user_info_settings_title;
- }
- }
- startPreferencePanel(pref.getFragment(), pref.getExtras(), titleRes, pref.getTitle(),
- null, 0);
- return true;
- }
-
- @Override
- public boolean shouldUpRecreateTask(Intent targetIntent) {
- return super.shouldUpRecreateTask(new Intent(this, Settings.class));
- }
-
- @Override
- public void setListAdapter(ListAdapter adapter) {
- if (adapter == null) {
- super.setListAdapter(null);
- } else {
- DevicePolicyManager dpm =
- (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
- super.setListAdapter(new HeaderAdapter(this, getHeaders(), mAuthenticatorHelper, dpm));
- }
- }
-
- @Override
- public void onAccountsUpdated(Account[] accounts) {
- // TODO: watch for package upgrades to invalidate cache; see 7206643
- mAuthenticatorHelper.updateAuthDescriptions(this);
- mAuthenticatorHelper.onAccountsUpdated(this, accounts);
- invalidateHeaders();
- }
-
- public static void requestHomeNotice() {
- sShowNoHomeNotice = true;
- }
+public class Settings extends SettingsActivity {
/*
- * Settings subclasses for launching independently.
- */
- public static class BluetoothSettingsActivity extends Settings { /* empty */ }
- public static class WirelessSettingsActivity extends Settings { /* empty */ }
- public static class TetherSettingsActivity extends Settings { /* empty */ }
- public static class VpnSettingsActivity extends Settings { /* empty */ }
- public static class DateTimeSettingsActivity extends Settings { /* empty */ }
- public static class StorageSettingsActivity extends Settings { /* empty */ }
- public static class WifiSettingsActivity extends Settings { /* empty */ }
- public static class WifiP2pSettingsActivity extends Settings { /* empty */ }
- public static class InputMethodAndLanguageSettingsActivity extends Settings { /* empty */ }
- public static class KeyboardLayoutPickerActivity extends Settings { /* empty */ }
- public static class InputMethodAndSubtypeEnablerActivity extends Settings { /* empty */ }
- public static class SpellCheckersSettingsActivity extends Settings { /* empty */ }
- public static class LocalePickerActivity extends Settings { /* empty */ }
- public static class UserDictionarySettingsActivity extends Settings { /* empty */ }
- public static class SoundSettingsActivity extends Settings { /* empty */ }
- public static class DisplaySettingsActivity extends Settings { /* empty */ }
- public static class DeviceInfoSettingsActivity extends Settings { /* empty */ }
- public static class ApplicationSettingsActivity extends Settings { /* empty */ }
- public static class ManageApplicationsActivity extends Settings { /* empty */ }
- public static class AppOpsSummaryActivity extends Settings {
+ * Settings subclasses for launching independently.
+ */
+ public static class BluetoothSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class WirelessSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class SimSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class TetherSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class VpnSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class DateTimeSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class StorageSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class WifiSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class WifiP2pSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class InputMethodAndLanguageSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class KeyboardLayoutPickerActivity extends SettingsActivity { /* empty */ }
+ public static class InputMethodAndSubtypeEnablerActivity extends SettingsActivity { /* empty */ }
+ public static class VoiceInputSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class SpellCheckersSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class LocalePickerActivity extends SettingsActivity { /* empty */ }
+ public static class UserDictionarySettingsActivity extends SettingsActivity { /* empty */ }
+ public static class HomeSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class DisplaySettingsActivity extends SettingsActivity { /* empty */ }
+ public static class DeviceInfoSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class ApplicationSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class ManageApplicationsActivity extends SettingsActivity { /* empty */ }
+ public static class AppOpsSummaryActivity extends SettingsActivity {
@Override
public boolean isValidFragment(String className) {
if (AppOpsSummary.class.getName().equals(className)) {
return true;
}
return super.isValidFragment(className);
- }
+ }
}
- public static class StorageUseActivity extends Settings { /* empty */ }
- public static class DevelopmentSettingsActivity extends Settings { /* empty */ }
- public static class AccessibilitySettingsActivity extends Settings { /* empty */ }
- public static class CaptioningSettingsActivity extends Settings { /* empty */ }
- public static class SecuritySettingsActivity extends Settings { /* empty */ }
- public static class LocationSettingsActivity extends Settings { /* empty */ }
- public static class PrivacySettingsActivity extends Settings { /* empty */ }
- public static class RunningServicesActivity extends Settings { /* empty */ }
- public static class ManageAccountsSettingsActivity extends Settings { /* empty */ }
- public static class PowerUsageSummaryActivity extends Settings { /* empty */ }
- public static class AccountSyncSettingsActivity extends Settings { /* empty */ }
- public static class AccountSyncSettingsInAddAccountActivity extends Settings { /* empty */ }
- public static class CryptKeeperSettingsActivity extends Settings { /* empty */ }
- public static class DeviceAdminSettingsActivity extends Settings { /* empty */ }
- public static class DataUsageSummaryActivity extends Settings { /* empty */ }
- public static class AdvancedWifiSettingsActivity extends Settings { /* empty */ }
- public static class TextToSpeechSettingsActivity extends Settings { /* empty */ }
- public static class AndroidBeamSettingsActivity extends Settings { /* empty */ }
- public static class WifiDisplaySettingsActivity extends Settings { /* empty */ }
- public static class DreamSettingsActivity extends Settings { /* empty */ }
- public static class NotificationStationActivity extends Settings { /* empty */ }
- public static class UserSettingsActivity extends Settings { /* empty */ }
- public static class NotificationAccessSettingsActivity extends Settings { /* empty */ }
- public static class UsbSettingsActivity extends Settings { /* empty */ }
- public static class TrustedCredentialsSettingsActivity extends Settings { /* empty */ }
- public static class PaymentSettingsActivity extends Settings { /* empty */ }
- public static class PrintSettingsActivity extends Settings { /* empty */ }
- public static class PrintJobSettingsActivity extends Settings { /* empty */ }
+ public static class StorageUseActivity extends SettingsActivity { /* empty */ }
+ public static class DevelopmentSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class AccessibilitySettingsActivity extends SettingsActivity { /* empty */ }
+ public static class CaptioningSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class AccessibilityInversionSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class AccessibilityContrastSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class AccessibilityDaltonizerSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class SecuritySettingsActivity extends SettingsActivity { /* empty */ }
+ public static class UsageAccessSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class LocationSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class PrivacySettingsActivity extends SettingsActivity { /* empty */ }
+ 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 */ }
+ public static class CryptKeeperSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class DeviceAdminSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class DataUsageSummaryActivity extends SettingsActivity { /* empty */ }
+ public static class AdvancedWifiSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class SavedAccessPointsSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class TextToSpeechSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class AndroidBeamSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class WifiDisplaySettingsActivity extends SettingsActivity { /* empty */ }
+ public static class DreamSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class NotificationStationActivity extends SettingsActivity { /* empty */ }
+ public static class UserSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class NotificationAccessSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class ConditionProviderSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class UsbSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class TrustedCredentialsSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class PaymentSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class PrintSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class PrintJobSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class ZenModeSettingsActivity 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 */ }
+ public static class QuickLaunchSettingsActivity extends SettingsActivity { /* empty */ }
+
+ public static class TopLevelSettings extends SettingsActivity { /* empty */ }
+ public static class ApnSettingsActivity extends SettingsActivity { /* empty */ }
}
+
diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java
new file mode 100644
index 0000000..37fcc87
--- /dev/null
+++ b/src/com/android/settings/SettingsActivity.java
@@ -0,0 +1,1358 @@
+/*
+ * 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.app.ActionBar;
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.nfc.NfcAdapter;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.INetworkManagementService;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.preference.Preference;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceManager;
+import android.preference.PreferenceScreen;
+import android.text.TextUtils;
+import android.transition.TransitionManager;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.TypedValue;
+import android.util.Xml;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.SearchView;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.XmlUtils;
+import com.android.settings.accessibility.AccessibilitySettings;
+import com.android.settings.accessibility.CaptionPropertiesFragment;
+import com.android.settings.accounts.AccountSettings;
+import com.android.settings.accounts.AccountSyncSettings;
+import com.android.settings.applications.InstalledAppDetails;
+import com.android.settings.applications.ManageApplications;
+import com.android.settings.applications.ProcessStatsUi;
+import com.android.settings.bluetooth.BluetoothSettings;
+import com.android.settings.dashboard.DashboardCategory;
+import com.android.settings.dashboard.DashboardSummary;
+import com.android.settings.dashboard.DashboardTile;
+import com.android.settings.dashboard.NoHomeDialogFragment;
+import com.android.settings.dashboard.SearchResultsSummary;
+import com.android.settings.deviceinfo.Memory;
+import com.android.settings.deviceinfo.UsbSettings;
+import com.android.settings.fuelgauge.BatterySaverSettings;
+import com.android.settings.fuelgauge.PowerUsageSummary;
+import com.android.settings.notification.NotificationAppList;
+import com.android.settings.notification.OtherSoundSettings;
+import com.android.settings.quicklaunch.QuickLaunchSettings;
+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;
+import com.android.settings.inputmethod.UserDictionaryList;
+import com.android.settings.location.LocationSettings;
+import com.android.settings.nfc.AndroidBeam;
+import com.android.settings.nfc.PaymentSettings;
+import com.android.settings.notification.AppNotificationSettings;
+import com.android.settings.notification.ConditionProviderSettings;
+import com.android.settings.notification.NotificationAccessSettings;
+import com.android.settings.notification.NotificationSettings;
+import com.android.settings.notification.NotificationStation;
+import com.android.settings.notification.ZenModeSettings;
+import com.android.settings.print.PrintJobSettingsFragment;
+import com.android.settings.print.PrintSettingsFragment;
+import com.android.settings.sim.SimSettings;
+import com.android.settings.tts.TextToSpeechSettings;
+import com.android.settings.users.UserSettings;
+import com.android.settings.voice.VoiceInputSettings;
+import com.android.settings.vpn2.VpnSettings;
+import com.android.settings.wfd.WifiDisplaySettings;
+import com.android.settings.widget.SwitchBar;
+import com.android.settings.wifi.AdvancedWifiSettings;
+import com.android.settings.wifi.SavedAccessPointsWifiSettings;
+import com.android.settings.wifi.WifiSettings;
+import com.android.settings.wifi.p2p.WifiP2pSettings;
+
+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.Set;
+
+import static com.android.settings.dashboard.DashboardTile.TILE_ID_UNDEFINED;
+
+public class SettingsActivity extends Activity
+ implements PreferenceManager.OnPreferenceTreeClickListener,
+ PreferenceFragment.OnPreferenceStartFragmentCallback,
+ ButtonBarHandler, FragmentManager.OnBackStackChangedListener,
+ SearchView.OnQueryTextListener, SearchView.OnCloseListener,
+ MenuItem.OnActionExpandListener {
+
+ private static final String LOG_TAG = "Settings";
+
+ // Constants for state save/restore
+ private static final String SAVE_KEY_CATEGORIES = ":settings:categories";
+ private static final String SAVE_KEY_SEARCH_MENU_EXPANDED = ":settings:search_menu_expanded";
+ private static final String SAVE_KEY_SEARCH_QUERY = ":settings:search_query";
+ private static final String SAVE_KEY_SHOW_HOME_AS_UP = ":settings:show_home_as_up";
+ private static final String SAVE_KEY_SHOW_SEARCH = ":settings:show_search";
+ private static final String SAVE_KEY_HOME_ACTIVITIES_COUNT = ":settings:home_activities_count";
+
+ /**
+ * When starting this activity, the invoking Intent can contain this extra
+ * string to specify which fragment should be initially displayed.
+ * <p/>Starting from Key Lime Pie, when this argument is passed in, the activity
+ * will call isValidFragment() to confirm that the fragment class name is valid for this
+ * activity.
+ */
+ public static final String EXTRA_SHOW_FRAGMENT = ":settings:show_fragment";
+
+ /**
+ * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
+ * this extra can also be specified to supply a Bundle of arguments to pass
+ * to that fragment when it is instantiated during the initial creation
+ * of the activity.
+ */
+ public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args";
+
+ /**
+ * Fragment "key" argument passed thru {@link #EXTRA_SHOW_FRAGMENT_ARGUMENTS}
+ */
+ public static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
+
+ public static final String BACK_STACK_PREFS = ":settings:prefs";
+
+ // extras that allow any preference activity to be launched as part of a wizard
+
+ // show Back and Next buttons? takes boolean parameter
+ // Back will then return RESULT_CANCELED and Next RESULT_OK
+ protected static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar";
+
+ // add a Skip button?
+ private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip";
+
+ // specify custom text for the Back or Next buttons, or cause a button to not appear
+ // at all by setting it to null
+ protected static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text";
+ protected static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text";
+
+ /**
+ * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
+ * those extra can also be specify to supply the title or title res id to be shown for
+ * that fragment.
+ */
+ public static final String EXTRA_SHOW_FRAGMENT_TITLE = ":settings:show_fragment_title";
+ public static final String EXTRA_SHOW_FRAGMENT_TITLE_RESID =
+ ":settings:show_fragment_title_resid";
+ public static final String EXTRA_SHOW_FRAGMENT_AS_SHORTCUT =
+ ":settings:show_fragment_as_shortcut";
+
+ public static final String EXTRA_SHOW_FRAGMENT_AS_SUBSETTING =
+ ":settings:show_fragment_as_subsetting";
+
+ private static final String META_DATA_KEY_FRAGMENT_CLASS =
+ "com.android.settings.FRAGMENT_CLASS";
+
+ private static final String EXTRA_UI_OPTIONS = "settings:ui_options";
+
+ private static final String EMPTY_QUERY = "";
+
+ private static boolean sShowNoHomeNotice = false;
+
+ private String mFragmentClass;
+
+ private CharSequence mInitialTitle;
+ private int mInitialTitleResId;
+
+ // Show only these settings for restricted users
+ private int[] SETTINGS_FOR_RESTRICTED = {
+ R.id.wireless_section,
+ R.id.wifi_settings,
+ R.id.bluetooth_settings,
+ R.id.data_usage_settings,
+ R.id.sim_settings,
+ R.id.wireless_settings,
+ R.id.device_section,
+ R.id.notification_settings,
+ R.id.display_settings,
+ R.id.storage_settings,
+ R.id.application_settings,
+ R.id.battery_settings,
+ R.id.personal_section,
+ R.id.location_settings,
+ R.id.security_settings,
+ R.id.language_settings,
+ R.id.user_settings,
+ R.id.account_settings,
+ R.id.system_section,
+ R.id.date_time_settings,
+ R.id.about_settings,
+ R.id.accessibility_settings,
+ R.id.print_settings,
+ R.id.nfc_payment_settings,
+ R.id.home_settings,
+ R.id.dashboard
+ };
+
+ private static final String[] ENTRY_FRAGMENTS = {
+ WirelessSettings.class.getName(),
+ WifiSettings.class.getName(),
+ AdvancedWifiSettings.class.getName(),
+ SavedAccessPointsWifiSettings.class.getName(),
+ BluetoothSettings.class.getName(),
+ SimSettings.class.getName(),
+ TetherSettings.class.getName(),
+ WifiP2pSettings.class.getName(),
+ VpnSettings.class.getName(),
+ DateTimeSettings.class.getName(),
+ LocalePicker.class.getName(),
+ InputMethodAndLanguageSettings.class.getName(),
+ VoiceInputSettings.class.getName(),
+ SpellCheckersSettings.class.getName(),
+ UserDictionaryList.class.getName(),
+ UserDictionarySettings.class.getName(),
+ HomeSettings.class.getName(),
+ DisplaySettings.class.getName(),
+ DeviceInfoSettings.class.getName(),
+ ManageApplications.class.getName(),
+ ProcessStatsUi.class.getName(),
+ NotificationStation.class.getName(),
+ LocationSettings.class.getName(),
+ SecuritySettings.class.getName(),
+ UsageAccessSettings.class.getName(),
+ PrivacySettings.class.getName(),
+ DeviceAdminSettings.class.getName(),
+ AccessibilitySettings.class.getName(),
+ CaptionPropertiesFragment.class.getName(),
+ com.android.settings.accessibility.ToggleDaltonizerPreferenceFragment.class.getName(),
+ TextToSpeechSettings.class.getName(),
+ Memory.class.getName(),
+ DevelopmentSettings.class.getName(),
+ UsbSettings.class.getName(),
+ AndroidBeam.class.getName(),
+ WifiDisplaySettings.class.getName(),
+ PowerUsageSummary.class.getName(),
+ AccountSyncSettings.class.getName(),
+ AccountSettings.class.getName(),
+ CryptKeeperSettings.class.getName(),
+ DataUsageSummary.class.getName(),
+ DreamSettings.class.getName(),
+ UserSettings.class.getName(),
+ NotificationAccessSettings.class.getName(),
+ ConditionProviderSettings.class.getName(),
+ PrintSettingsFragment.class.getName(),
+ PrintJobSettingsFragment.class.getName(),
+ TrustedCredentialsSettings.class.getName(),
+ PaymentSettings.class.getName(),
+ KeyboardLayoutPickerFragment.class.getName(),
+ ZenModeSettings.class.getName(),
+ NotificationSettings.class.getName(),
+ ChooseLockPassword.ChooseLockPasswordFragment.class.getName(),
+ ChooseLockPattern.ChooseLockPatternFragment.class.getName(),
+ InstalledAppDetails.class.getName(),
+ BatterySaverSettings.class.getName(),
+ NotificationAppList.class.getName(),
+ AppNotificationSettings.class.getName(),
+ OtherSoundSettings.class.getName(),
+ QuickLaunchSettings.class.getName(),
+ ApnSettings.class.getName()
+ };
+
+
+ private static final String[] LIKE_SHORTCUT_INTENT_ACTION_ARRAY = {
+ "android.settings.APPLICATION_DETAILS_SETTINGS"
+ };
+
+ private SharedPreferences mDevelopmentPreferences;
+ private SharedPreferences.OnSharedPreferenceChangeListener mDevelopmentPreferencesListener;
+
+ private boolean mBatteryPresent = true;
+ private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
+ boolean batteryPresent = Utils.isBatteryPresent(intent);
+
+ if (mBatteryPresent != batteryPresent) {
+ mBatteryPresent = batteryPresent;
+ invalidateCategories(true);
+ }
+ }
+ }
+ };
+
+ private final DynamicIndexableContentMonitor mDynamicIndexableContentMonitor =
+ new DynamicIndexableContentMonitor();
+
+ private ActionBar mActionBar;
+ private SwitchBar mSwitchBar;
+
+ private Button mNextButton;
+
+ private boolean mDisplayHomeAsUpEnabled;
+ private boolean mDisplaySearch;
+
+ private boolean mIsShowingDashboard;
+ private boolean mIsShortcut;
+
+ private ViewGroup mContent;
+
+ private SearchView mSearchView;
+ private MenuItem mSearchMenuItem;
+ private boolean mSearchMenuItemExpanded = false;
+ private SearchResultsSummary mSearchResultsFragment;
+ private String mSearchQuery;
+
+ // Categories
+ private ArrayList<DashboardCategory> mCategories = new ArrayList<DashboardCategory>();
+
+ private static final String MSG_DATA_FORCE_REFRESH = "msg_data_force_refresh";
+ private static final int MSG_BUILD_CATEGORIES = 1;
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_BUILD_CATEGORIES: {
+ final boolean forceRefresh = msg.getData().getBoolean(MSG_DATA_FORCE_REFRESH);
+ if (forceRefresh) {
+ buildDashboardCategories(mCategories);
+ }
+ } break;
+ }
+ }
+ };
+
+ private boolean mNeedToRevertToInitialFragment = false;
+ private int mHomeActivitiesCount = 1;
+
+ private Intent mResultIntentData;
+
+ public SwitchBar getSwitchBar() {
+ return mSwitchBar;
+ }
+
+ public List<DashboardCategory> getDashboardCategories(boolean forceRefresh) {
+ if (forceRefresh || mCategories.size() == 0) {
+ buildDashboardCategories(mCategories);
+ }
+ return mCategories;
+ }
+
+ @Override
+ public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
+ // Override the fragment title for Wallpaper settings
+ int titleRes = pref.getTitleRes();
+ if (pref.getFragment().equals(WallpaperTypeSettings.class.getName())) {
+ titleRes = R.string.wallpaper_settings_fragment_title;
+ } else if (pref.getFragment().equals(OwnerInfoSettings.class.getName())
+ && UserHandle.myUserId() != UserHandle.USER_OWNER) {
+ if (UserManager.get(this).isLinkedUser()) {
+ titleRes = R.string.profile_info_settings_title;
+ } else {
+ titleRes = R.string.user_info_settings_title;
+ }
+ }
+ startPreferencePanel(pref.getFragment(), pref.getExtras(), titleRes, pref.getTitle(),
+ null, 0);
+ return true;
+ }
+
+ @Override
+ public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
+ return false;
+ }
+
+ private void invalidateCategories(boolean forceRefresh) {
+ if (!mHandler.hasMessages(MSG_BUILD_CATEGORIES)) {
+ Message msg = new Message();
+ msg.what = MSG_BUILD_CATEGORIES;
+ msg.getData().putBoolean(MSG_DATA_FORCE_REFRESH, forceRefresh);
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ Index.getInstance(this).update();
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+
+ if (mNeedToRevertToInitialFragment) {
+ revertToInitialFragment();
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ if (!mDisplaySearch) {
+ return false;
+ }
+
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.options_menu, menu);
+
+ // Cache the search query (can be overriden by the OnQueryTextListener)
+ final String query = mSearchQuery;
+
+ mSearchMenuItem = menu.findItem(R.id.search);
+ mSearchView = (SearchView) mSearchMenuItem.getActionView();
+
+ if (mSearchMenuItem == null || mSearchView == null) {
+ return false;
+ }
+
+ if (mSearchResultsFragment != null) {
+ mSearchResultsFragment.setSearchView(mSearchView);
+ }
+
+ mSearchMenuItem.setOnActionExpandListener(this);
+ mSearchView.setOnQueryTextListener(this);
+ mSearchView.setOnCloseListener(this);
+
+ if (mSearchMenuItemExpanded) {
+ mSearchMenuItem.expandActionView();
+ }
+ mSearchView.setQuery(query, true /* submit */);
+
+ return true;
+ }
+
+ private static boolean isShortCutIntent(final Intent intent) {
+ Set<String> categories = intent.getCategories();
+ return (categories != null) && categories.contains("com.android.settings.SHORTCUT");
+ }
+
+ private static boolean isLikeShortCutIntent(final Intent intent) {
+ String action = intent.getAction();
+ if (action == null) {
+ return false;
+ }
+ for (int i = 0; i < LIKE_SHORTCUT_INTENT_ACTION_ARRAY.length; i++) {
+ if (LIKE_SHORTCUT_INTENT_ACTION_ARRAY[i].equals(action)) return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedState) {
+ super.onCreate(savedState);
+
+ // Should happen before any call to getIntent()
+ getMetaData();
+
+ final Intent intent = getIntent();
+ if (intent.hasExtra(EXTRA_UI_OPTIONS)) {
+ getWindow().setUiOptions(intent.getIntExtra(EXTRA_UI_OPTIONS, 0));
+ }
+
+ mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE,
+ Context.MODE_PRIVATE);
+
+ // Getting Intent properties can only be done after the super.onCreate(...)
+ final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);
+
+ mIsShortcut = isShortCutIntent(intent) || isLikeShortCutIntent(intent) ||
+ intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SHORTCUT, false);
+
+ final ComponentName cn = intent.getComponent();
+ final String className = cn.getClassName();
+
+ mIsShowingDashboard = className.equals(Settings.class.getName());
+
+ // This is a "Sub Settings" when:
+ // - this is a real SubSettings
+ // - or :settings:show_fragment_as_subsetting is passed to the Intent
+ final boolean isSubSettings = className.equals(SubSettings.class.getName()) ||
+ intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, false);
+
+ // If this is a sub settings, then apply the SubSettings Theme for the ActionBar content insets
+ if (isSubSettings) {
+ // Check also that we are not a Theme Dialog as we don't want to override them
+ final int themeResId = getThemeResId();
+ if (themeResId != R.style.Theme_DialogWhenLarge &&
+ themeResId != R.style.Theme_SubSettingsDialogWhenLarge) {
+ setTheme(R.style.Theme_SubSettings);
+ }
+ }
+
+ setContentView(mIsShowingDashboard ?
+ R.layout.settings_main_dashboard : R.layout.settings_main_prefs);
+
+ mContent = (ViewGroup) findViewById(R.id.main_content);
+
+ getFragmentManager().addOnBackStackChangedListener(this);
+
+ if (mIsShowingDashboard) {
+ Index.getInstance(getApplicationContext()).update();
+ }
+
+ if (savedState != null) {
+ // We are restarting from a previous saved state; used that to initialize, instead
+ // of starting fresh.
+ mSearchMenuItemExpanded = savedState.getBoolean(SAVE_KEY_SEARCH_MENU_EXPANDED);
+ mSearchQuery = savedState.getString(SAVE_KEY_SEARCH_QUERY);
+
+ setTitleFromIntent(intent);
+
+ ArrayList<DashboardCategory> categories =
+ savedState.getParcelableArrayList(SAVE_KEY_CATEGORIES);
+ if (categories != null) {
+ mCategories.clear();
+ mCategories.addAll(categories);
+ setTitleFromBackStack();
+ }
+
+ mDisplayHomeAsUpEnabled = savedState.getBoolean(SAVE_KEY_SHOW_HOME_AS_UP);
+ mDisplaySearch = savedState.getBoolean(SAVE_KEY_SHOW_SEARCH);
+ mHomeActivitiesCount = savedState.getInt(SAVE_KEY_HOME_ACTIVITIES_COUNT,
+ 1 /* one home activity by default */);
+ } else {
+ if (!mIsShowingDashboard) {
+ // Search is shown we are launched thru a Settings "shortcut". UP will be shown
+ // only if it is a sub settings
+ if (mIsShortcut) {
+ mDisplayHomeAsUpEnabled = isSubSettings;
+ mDisplaySearch = false;
+ } else if (isSubSettings) {
+ mDisplayHomeAsUpEnabled = true;
+ mDisplaySearch = true;
+ } else {
+ mDisplayHomeAsUpEnabled = false;
+ mDisplaySearch = false;
+ }
+ setTitleFromIntent(intent);
+
+ Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
+ switchToFragment(initialFragmentName, initialArguments, true, false,
+ mInitialTitleResId, mInitialTitle, false);
+ } else {
+ // No UP affordance if we are displaying the main Dashboard
+ mDisplayHomeAsUpEnabled = false;
+ // Show Search affordance
+ mDisplaySearch = true;
+ mInitialTitleResId = R.string.dashboard_title;
+ switchToFragment(DashboardSummary.class.getName(), null, false, false,
+ mInitialTitleResId, mInitialTitle, false);
+ }
+ }
+
+ mActionBar = getActionBar();
+ if (mActionBar != null) {
+ mActionBar.setDisplayHomeAsUpEnabled(mDisplayHomeAsUpEnabled);
+ mActionBar.setHomeButtonEnabled(mDisplayHomeAsUpEnabled);
+ }
+ mSwitchBar = (SwitchBar) findViewById(R.id.switch_bar);
+
+ // see if we should show Back/Next buttons
+ if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
+
+ View buttonBar = findViewById(R.id.button_bar);
+ if (buttonBar != null) {
+ buttonBar.setVisibility(View.VISIBLE);
+
+ Button backButton = (Button)findViewById(R.id.back_button);
+ backButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ setResult(RESULT_CANCELED, getResultIntentData());
+ finish();
+ }
+ });
+ Button skipButton = (Button)findViewById(R.id.skip_button);
+ skipButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ setResult(RESULT_OK, getResultIntentData());
+ finish();
+ }
+ });
+ mNextButton = (Button)findViewById(R.id.next_button);
+ mNextButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ setResult(RESULT_OK, getResultIntentData());
+ finish();
+ }
+ });
+
+ // set our various button parameters
+ if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
+ String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
+ if (TextUtils.isEmpty(buttonText)) {
+ mNextButton.setVisibility(View.GONE);
+ }
+ else {
+ mNextButton.setText(buttonText);
+ }
+ }
+ if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
+ String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
+ if (TextUtils.isEmpty(buttonText)) {
+ backButton.setVisibility(View.GONE);
+ }
+ else {
+ backButton.setText(buttonText);
+ }
+ }
+ if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
+ skipButton.setVisibility(View.VISIBLE);
+ }
+ }
+ }
+
+ mHomeActivitiesCount = getHomeActivitiesCount();
+ }
+
+ private int getHomeActivitiesCount() {
+ final ArrayList<ResolveInfo> homeApps = new ArrayList<ResolveInfo>();
+ getPackageManager().getHomeActivities(homeApps);
+ return homeApps.size();
+ }
+
+ private void setTitleFromIntent(Intent intent) {
+ final int initialTitleResId = intent.getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE_RESID, -1);
+ if (initialTitleResId > 0) {
+ mInitialTitle = null;
+ mInitialTitleResId = initialTitleResId;
+ setTitle(mInitialTitleResId);
+ } else {
+ mInitialTitleResId = -1;
+ final String initialTitle = intent.getStringExtra(EXTRA_SHOW_FRAGMENT_TITLE);
+ mInitialTitle = (initialTitle != null) ? initialTitle : getTitle();
+ setTitle(mInitialTitle);
+ }
+ }
+
+ @Override
+ public void onBackStackChanged() {
+ setTitleFromBackStack();
+ }
+
+ private int setTitleFromBackStack() {
+ final int count = getFragmentManager().getBackStackEntryCount();
+
+ if (count == 0) {
+ if (mInitialTitleResId > 0) {
+ setTitle(mInitialTitleResId);
+ } else {
+ setTitle(mInitialTitle);
+ }
+ return 0;
+ }
+
+ FragmentManager.BackStackEntry bse = getFragmentManager().getBackStackEntryAt(count - 1);
+ setTitleFromBackStackEntry(bse);
+
+ return count;
+ }
+
+ private void setTitleFromBackStackEntry(FragmentManager.BackStackEntry bse) {
+ final CharSequence title;
+ final int titleRes = bse.getBreadCrumbTitleRes();
+ if (titleRes > 0) {
+ title = getText(titleRes);
+ } else {
+ title = bse.getBreadCrumbTitle();
+ }
+ if (title != null) {
+ setTitle(title);
+ }
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ if (mCategories.size() > 0) {
+ outState.putParcelableArrayList(SAVE_KEY_CATEGORIES, mCategories);
+ }
+
+ outState.putBoolean(SAVE_KEY_SHOW_HOME_AS_UP, mDisplayHomeAsUpEnabled);
+ outState.putBoolean(SAVE_KEY_SHOW_SEARCH, mDisplaySearch);
+
+ if (mDisplaySearch) {
+ // The option menus are created if the ActionBar is visible and they are also created
+ // asynchronously. If you launch Settings with an Intent action like
+ // android.intent.action.POWER_USAGE_SUMMARY and at the same time your device is locked
+ // thru a LockScreen, onCreateOptionsMenu() is not yet called and references to the search
+ // menu item and search view are null.
+ boolean isExpanded = (mSearchMenuItem != null) && mSearchMenuItem.isActionViewExpanded();
+ outState.putBoolean(SAVE_KEY_SEARCH_MENU_EXPANDED, isExpanded);
+
+ String query = (mSearchView != null) ? mSearchView.getQuery().toString() : EMPTY_QUERY;
+ outState.putString(SAVE_KEY_SEARCH_QUERY, query);
+ }
+
+ outState.putInt(SAVE_KEY_HOME_ACTIVITIES_COUNT, mHomeActivitiesCount);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ final int newHomeActivityCount = getHomeActivitiesCount();
+ if (newHomeActivityCount != mHomeActivitiesCount) {
+ mHomeActivitiesCount = newHomeActivityCount;
+ invalidateCategories(true);
+ }
+
+ mDevelopmentPreferencesListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ invalidateCategories(true);
+ }
+ };
+ mDevelopmentPreferences.registerOnSharedPreferenceChangeListener(
+ mDevelopmentPreferencesListener);
+
+ registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+
+ mDynamicIndexableContentMonitor.register(this);
+
+ if(mDisplaySearch && !TextUtils.isEmpty(mSearchQuery)) {
+ onQueryTextSubmit(mSearchQuery);
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+
+ unregisterReceiver(mBatteryInfoReceiver);
+ mDynamicIndexableContentMonitor.unregister();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+
+ mDevelopmentPreferences.unregisterOnSharedPreferenceChangeListener(
+ mDevelopmentPreferencesListener);
+ mDevelopmentPreferencesListener = null;
+ }
+
+ protected boolean isValidFragment(String fragmentName) {
+ // Almost all fragments are wrapped in this,
+ // except for a few that have their own activities.
+ for (int i = 0; i < ENTRY_FRAGMENTS.length; i++) {
+ if (ENTRY_FRAGMENTS[i].equals(fragmentName)) return true;
+ }
+ return false;
+ }
+
+ @Override
+ public Intent getIntent() {
+ Intent superIntent = super.getIntent();
+ String startingFragment = getStartingFragmentClass(superIntent);
+ // This is called from super.onCreate, isMultiPane() is not yet reliable
+ // Do not use onIsHidingHeaders either, which relies itself on this method
+ if (startingFragment != null) {
+ Intent modIntent = new Intent(superIntent);
+ modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
+ Bundle args = superIntent.getExtras();
+ if (args != null) {
+ args = new Bundle(args);
+ } else {
+ args = new Bundle();
+ }
+ args.putParcelable("intent", superIntent);
+ modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
+ return modIntent;
+ }
+ return superIntent;
+ }
+
+ /**
+ * Checks if the component name in the intent is different from the Settings class and
+ * returns the class name to load as a fragment.
+ */
+ private String getStartingFragmentClass(Intent intent) {
+ if (mFragmentClass != null) return mFragmentClass;
+
+ String intentClass = intent.getComponent().getClassName();
+ if (intentClass.equals(getClass().getName())) return null;
+
+ if ("com.android.settings.ManageApplications".equals(intentClass)
+ || "com.android.settings.RunningServices".equals(intentClass)
+ || "com.android.settings.applications.StorageUse".equals(intentClass)) {
+ // Old names of manage apps.
+ intentClass = com.android.settings.applications.ManageApplications.class.getName();
+ }
+
+ return intentClass;
+ }
+
+ /**
+ * Start a new fragment containing a preference panel. If the preferences
+ * are being displayed in multi-pane mode, the given fragment class will
+ * be instantiated and placed in the appropriate pane. If running in
+ * single-pane mode, a new activity will be launched in which to show the
+ * fragment.
+ *
+ * @param fragmentClass Full name of the class implementing the fragment.
+ * @param args Any desired arguments to supply to the fragment.
+ * @param titleRes Optional resource identifier of the title of this
+ * fragment.
+ * @param titleText Optional text of the title of this fragment.
+ * @param resultTo Optional fragment that result data should be sent to.
+ * If non-null, resultTo.onActivityResult() will be called when this
+ * preference panel is done. The launched panel must use
+ * {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
+ * @param resultRequestCode If resultTo is non-null, this is the caller's
+ * request code to be received with the result.
+ */
+ public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes,
+ CharSequence titleText, Fragment resultTo, int resultRequestCode) {
+ String title = null;
+ if (titleRes < 0) {
+ if (titleText != null) {
+ title = titleText.toString();
+ } else {
+ // There not much we can do in that case
+ title = "";
+ }
+ }
+ Utils.startWithFragment(this, fragmentClass, args, resultTo, resultRequestCode,
+ titleRes, title, mIsShortcut);
+ }
+
+ /**
+ * Start a new fragment in a new activity containing a preference panel for a given user. If the
+ * preferences are being displayed in multi-pane mode, the given fragment class will be
+ * instantiated and placed in the appropriate pane. If running in single-pane mode, a new
+ * activity will be launched in which to show the fragment.
+ *
+ * @param fragmentClass Full name of the class implementing the fragment.
+ * @param args Any desired arguments to supply to the fragment.
+ * @param titleRes Optional resource identifier of the title of this fragment.
+ * @param titleText Optional text of the title of this fragment.
+ * @param userHandle The user for which the panel has to be started.
+ */
+ public void startPreferencePanelAsUser(String fragmentClass, Bundle args, int titleRes,
+ CharSequence titleText, UserHandle userHandle) {
+ String title = null;
+ if (titleRes < 0) {
+ if (titleText != null) {
+ title = titleText.toString();
+ } else {
+ // There not much we can do in that case
+ title = "";
+ }
+ }
+ Utils.startWithFragmentAsUser(this, fragmentClass, args,
+ titleRes, title, mIsShortcut, userHandle);
+ }
+
+ /**
+ * Called by a preference panel fragment to finish itself.
+ *
+ * @param caller The fragment that is asking to be finished.
+ * @param resultCode Optional result code to send back to the original
+ * launching fragment.
+ * @param resultData Optional result data to send back to the original
+ * launching fragment.
+ */
+ public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) {
+ setResult(resultCode, resultData);
+ finish();
+ }
+
+ /**
+ * Start a new fragment.
+ *
+ * @param fragment The fragment to start
+ * @param push If true, the current fragment will be pushed onto the back stack. If false,
+ * the current fragment will be replaced.
+ */
+ public void startPreferenceFragment(Fragment fragment, boolean push) {
+ FragmentTransaction transaction = getFragmentManager().beginTransaction();
+ transaction.replace(R.id.main_content, fragment);
+ if (push) {
+ transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
+ transaction.addToBackStack(BACK_STACK_PREFS);
+ } else {
+ transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
+ }
+ transaction.commitAllowingStateLoss();
+ }
+
+ /**
+ * Switch to a specific Fragment with taking care of validation, Title and BackStack
+ */
+ private Fragment switchToFragment(String fragmentName, Bundle args, boolean validate,
+ boolean addToBackStack, int titleResId, CharSequence title, boolean withTransition) {
+ if (validate && !isValidFragment(fragmentName)) {
+ throw new IllegalArgumentException("Invalid fragment for this activity: "
+ + fragmentName);
+ }
+ Fragment f = Fragment.instantiate(this, fragmentName, args);
+ FragmentTransaction transaction = getFragmentManager().beginTransaction();
+ transaction.replace(R.id.main_content, f);
+ if (withTransition) {
+ TransitionManager.beginDelayedTransition(mContent);
+ }
+ if (addToBackStack) {
+ transaction.addToBackStack(SettingsActivity.BACK_STACK_PREFS);
+ }
+ if (titleResId > 0) {
+ transaction.setBreadCrumbTitle(titleResId);
+ } else if (title != null) {
+ transaction.setBreadCrumbTitle(title);
+ }
+ transaction.commitAllowingStateLoss();
+ getFragmentManager().executePendingTransactions();
+ return f;
+ }
+
+ /**
+ * Called when the activity needs its list of categories/tiles built.
+ *
+ * @param categories The list in which to place the tiles categories.
+ */
+ private void buildDashboardCategories(List<DashboardCategory> categories) {
+ categories.clear();
+ loadCategoriesFromResource(R.xml.dashboard_categories, categories);
+ updateTilesList(categories);
+ }
+
+ /**
+ * Parse the given XML file as a categories description, adding each
+ * parsed categories and tiles into the target list.
+ *
+ * @param resid The XML resource to load and parse.
+ * @param target The list in which the parsed categories and tiles should be placed.
+ */
+ private void loadCategoriesFromResource(int resid, List<DashboardCategory> target) {
+ XmlResourceParser parser = null;
+ try {
+ parser = getResources().getXml(resid);
+ AttributeSet attrs = Xml.asAttributeSet(parser);
+
+ int type;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ // Parse next until start tag is found
+ }
+
+ String nodeName = parser.getName();
+ if (!"dashboard-categories".equals(nodeName)) {
+ throw new RuntimeException(
+ "XML document must start with <preference-categories> tag; found"
+ + nodeName + " at " + parser.getPositionDescription());
+ }
+
+ Bundle curBundle = null;
+
+ final int outerDepth = parser.getDepth();
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ nodeName = parser.getName();
+ if ("dashboard-category".equals(nodeName)) {
+ DashboardCategory category = new DashboardCategory();
+
+ TypedArray sa = obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.PreferenceHeader);
+ category.id = sa.getResourceId(
+ com.android.internal.R.styleable.PreferenceHeader_id,
+ (int)DashboardCategory.CAT_ID_UNDEFINED);
+
+ TypedValue tv = sa.peekValue(
+ com.android.internal.R.styleable.PreferenceHeader_title);
+ if (tv != null && tv.type == TypedValue.TYPE_STRING) {
+ if (tv.resourceId != 0) {
+ category.titleRes = tv.resourceId;
+ } else {
+ category.title = tv.string;
+ }
+ }
+ sa.recycle();
+
+ final int innerDepth = parser.getDepth();
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String innerNodeName = parser.getName();
+ if (innerNodeName.equals("dashboard-tile")) {
+ DashboardTile tile = new DashboardTile();
+
+ sa = obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.PreferenceHeader);
+ tile.id = sa.getResourceId(
+ com.android.internal.R.styleable.PreferenceHeader_id,
+ (int)TILE_ID_UNDEFINED);
+ tv = sa.peekValue(
+ com.android.internal.R.styleable.PreferenceHeader_title);
+ if (tv != null && tv.type == TypedValue.TYPE_STRING) {
+ if (tv.resourceId != 0) {
+ tile.titleRes = tv.resourceId;
+ } else {
+ tile.title = tv.string;
+ }
+ }
+ tv = sa.peekValue(
+ com.android.internal.R.styleable.PreferenceHeader_summary);
+ if (tv != null && tv.type == TypedValue.TYPE_STRING) {
+ if (tv.resourceId != 0) {
+ tile.summaryRes = tv.resourceId;
+ } else {
+ tile.summary = tv.string;
+ }
+ }
+ tile.iconRes = sa.getResourceId(
+ com.android.internal.R.styleable.PreferenceHeader_icon, 0);
+ tile.fragment = sa.getString(
+ com.android.internal.R.styleable.PreferenceHeader_fragment);
+ sa.recycle();
+
+ if (curBundle == null) {
+ curBundle = new Bundle();
+ }
+
+ final int innerDepth2 = parser.getDepth();
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth2)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String innerNodeName2 = parser.getName();
+ if (innerNodeName2.equals("extra")) {
+ getResources().parseBundleExtra("extra", attrs, curBundle);
+ XmlUtils.skipCurrentTag(parser);
+
+ } else if (innerNodeName2.equals("intent")) {
+ tile.intent = Intent.parseIntent(getResources(), parser, attrs);
+
+ } else {
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+
+ if (curBundle.size() > 0) {
+ tile.fragmentArguments = curBundle;
+ curBundle = null;
+ }
+
+ // Show the SIM Cards setting if there are more than 2 SIMs installed.
+ if(tile.id != R.id.sim_settings || Utils.showSimCardTile(this)){
+ category.addTile(tile);
+ }
+
+ } else {
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+
+ target.add(category);
+ } else {
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+
+ } catch (XmlPullParserException e) {
+ throw new RuntimeException("Error parsing categories", e);
+ } catch (IOException e) {
+ throw new RuntimeException("Error parsing categories", e);
+ } finally {
+ if (parser != null) parser.close();
+ }
+ }
+
+ private void updateTilesList(List<DashboardCategory> target) {
+ final boolean showDev = mDevelopmentPreferences.getBoolean(
+ DevelopmentSettings.PREF_SHOW,
+ android.os.Build.TYPE.equals("eng"));
+
+ final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
+
+ final int size = target.size();
+ for (int i = 0; i < size; i++) {
+
+ DashboardCategory category = target.get(i);
+
+ // Ids are integers, so downcasting is ok
+ int id = (int) category.id;
+ int n = category.getTilesCount() - 1;
+ while (n >= 0) {
+
+ DashboardTile tile = category.getTile(n);
+ boolean removeTile = false;
+ id = (int) tile.id;
+ if (id == R.id.operator_settings || id == R.id.manufacturer_settings) {
+ if (!Utils.updateTileToSpecificActivityFromMetaDataOrRemove(this, tile)) {
+ removeTile = true;
+ }
+ } else if (id == R.id.wifi_settings) {
+ // Remove WiFi Settings if WiFi service is not available.
+ if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+ removeTile = true;
+ }
+ } else if (id == R.id.bluetooth_settings) {
+ // Remove Bluetooth Settings if Bluetooth service is not available.
+ if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
+ removeTile = true;
+ }
+ } else if (id == R.id.data_usage_settings) {
+ // Remove data usage when kernel module not enabled
+ final INetworkManagementService netManager = INetworkManagementService.Stub
+ .asInterface(ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
+ try {
+ if (!netManager.isBandwidthControlEnabled()) {
+ removeTile = true;
+ }
+ } catch (RemoteException e) {
+ // ignored
+ }
+ } else if (id == R.id.battery_settings) {
+ // Remove battery settings when battery is not available. (e.g. TV)
+
+ if (!mBatteryPresent) {
+ removeTile = true;
+ }
+ } else if (id == R.id.home_settings) {
+ if (!updateHomeSettingTiles(tile)) {
+ removeTile = true;
+ }
+ } else if (id == R.id.user_settings) {
+ boolean hasMultipleUsers =
+ ((UserManager) getSystemService(Context.USER_SERVICE))
+ .getUserCount() > 1;
+ if (!UserHandle.MU_ENABLED
+ || (!UserManager.supportsMultipleUsers()
+ && !hasMultipleUsers)
+ || 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);
+ if (!hasPrintingSupport) {
+ removeTile = true;
+ }
+ } else if (id == R.id.development_settings) {
+ if (!showDev || um.hasUserRestriction(
+ UserManager.DISALLOW_DEBUGGING_FEATURES)) {
+ removeTile = true;
+ }
+ }
+
+ if (UserHandle.MU_ENABLED && UserHandle.myUserId() != 0
+ && !ArrayUtils.contains(SETTINGS_FOR_RESTRICTED, id)) {
+ removeTile = true;
+ }
+
+ if (removeTile && n < category.getTilesCount()) {
+ category.removeTile(n);
+ }
+ n--;
+ }
+ }
+ }
+
+ private boolean updateHomeSettingTiles(DashboardTile tile) {
+ // Once we decide to show Home settings, keep showing it forever
+ SharedPreferences sp = getSharedPreferences(HomeSettings.HOME_PREFS, Context.MODE_PRIVATE);
+ if (sp.getBoolean(HomeSettings.HOME_PREFS_DO_SHOW, false)) {
+ return true;
+ }
+
+ try {
+ mHomeActivitiesCount = getHomeActivitiesCount();
+ if (mHomeActivitiesCount < 2) {
+ // When there's only one available home app, omit this settings
+ // category entirely at the top level UI. If the user just
+ // uninstalled the penultimate home app candidiate, we also
+ // now tell them about why they aren't seeing 'Home' in the list.
+ if (sShowNoHomeNotice) {
+ sShowNoHomeNotice = false;
+ NoHomeDialogFragment.show(this);
+ }
+ return false;
+ } else {
+ // Okay, we're allowing the Home settings category. Tell it, when
+ // invoked via this front door, that we'll need to be told about the
+ // case when the user uninstalls all but one home app.
+ if (tile.fragmentArguments == null) {
+ tile.fragmentArguments = new Bundle();
+ }
+ tile.fragmentArguments.putBoolean(HomeSettings.HOME_SHOW_NOTICE, true);
+ }
+ } catch (Exception e) {
+ // Can't look up the home activity; bail on configuring the icon
+ Log.w(LOG_TAG, "Problem looking up home activity!", e);
+ }
+
+ sp.edit().putBoolean(HomeSettings.HOME_PREFS_DO_SHOW, true).apply();
+ return true;
+ }
+
+ private void getMetaData() {
+ try {
+ ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
+ PackageManager.GET_META_DATA);
+ if (ai == null || ai.metaData == null) return;
+ mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
+ } catch (NameNotFoundException nnfe) {
+ // No recovery
+ Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
+ }
+ }
+
+ // give subclasses access to the Next button
+ public boolean hasNextButton() {
+ return mNextButton != null;
+ }
+
+ public Button getNextButton() {
+ return mNextButton;
+ }
+
+ @Override
+ public boolean shouldUpRecreateTask(Intent targetIntent) {
+ return super.shouldUpRecreateTask(new Intent(this, SettingsActivity.class));
+ }
+
+ public static void requestHomeNotice() {
+ sShowNoHomeNotice = true;
+ }
+
+ @Override
+ public boolean onQueryTextSubmit(String query) {
+ switchToSearchResultsFragmentIfNeeded();
+ mSearchQuery = query;
+ return mSearchResultsFragment.onQueryTextSubmit(query);
+ }
+
+ @Override
+ public boolean onQueryTextChange(String newText) {
+ mSearchQuery = newText;
+ if (mSearchResultsFragment == null) {
+ return false;
+ }
+ return mSearchResultsFragment.onQueryTextChange(newText);
+ }
+
+ @Override
+ public boolean onClose() {
+ return false;
+ }
+
+ @Override
+ public boolean onMenuItemActionExpand(MenuItem item) {
+ if (item.getItemId() == mSearchMenuItem.getItemId()) {
+ switchToSearchResultsFragmentIfNeeded();
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onMenuItemActionCollapse(MenuItem item) {
+ if (item.getItemId() == mSearchMenuItem.getItemId()) {
+ if (mSearchMenuItemExpanded) {
+ revertToInitialFragment();
+ }
+ }
+ return true;
+ }
+
+ private void switchToSearchResultsFragmentIfNeeded() {
+ if (mSearchResultsFragment != null) {
+ return;
+ }
+ Fragment current = getFragmentManager().findFragmentById(R.id.main_content);
+ if (current != null && current instanceof SearchResultsSummary) {
+ mSearchResultsFragment = (SearchResultsSummary) current;
+ } else {
+ mSearchResultsFragment = (SearchResultsSummary) switchToFragment(
+ SearchResultsSummary.class.getName(), null, false, true,
+ R.string.search_results_title, null, true);
+ }
+ mSearchResultsFragment.setSearchView(mSearchView);
+ mSearchMenuItemExpanded = true;
+ }
+
+ public void needToRevertToInitialFragment() {
+ mNeedToRevertToInitialFragment = true;
+ }
+
+ private void revertToInitialFragment() {
+ mNeedToRevertToInitialFragment = false;
+ mSearchResultsFragment = null;
+ mSearchMenuItemExpanded = false;
+ getFragmentManager().popBackStackImmediate(SettingsActivity.BACK_STACK_PREFS,
+ FragmentManager.POP_BACK_STACK_INCLUSIVE);
+ if (mSearchMenuItem != null) {
+ mSearchMenuItem.collapseActionView();
+ }
+ }
+
+ public Intent getResultIntentData() {
+ return mResultIntentData;
+ }
+
+ public void setResultIntentData(Intent resultIntentData) {
+ mResultIntentData = resultIntentData;
+ }
+}
diff --git a/src/com/android/settings/SettingsPreferenceFragment.java b/src/com/android/settings/SettingsPreferenceFragment.java
index 0a382b5..e87f676 100644
--- a/src/com/android/settings/SettingsPreferenceFragment.java
+++ b/src/com/android/settings/SettingsPreferenceFragment.java
@@ -16,25 +16,32 @@
package com.android.settings;
+import android.app.Activity;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.Fragment;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
-import android.content.Intent;
import android.content.pm.PackageManager;
-import android.net.Uri;
+import android.database.DataSetObserver;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;
+import android.preference.PreferenceGroupAdapter;
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.Button;
+import android.widget.ListAdapter;
+import android.widget.ListView;
/**
* Base class for Settings fragments, with some helper functions and dialog management.
@@ -44,6 +51,9 @@ public class SettingsPreferenceFragment extends PreferenceFragment implements Di
private static final String TAG = "SettingsPreferenceFragment";
private static final int MENU_HELP = Menu.FIRST + 100;
+ private static final int DELAY_HIGHLIGHT_DURATION_MILLIS = 600;
+
+ private static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted";
private SettingsDialogFragment mDialogFragment;
@@ -52,10 +62,34 @@ public class SettingsPreferenceFragment extends PreferenceFragment implements Di
// Cache the content resolver for async callbacks
private ContentResolver mContentResolver;
+ private String mPreferenceKey;
+ private boolean mPreferenceHighlighted = false;
+ private Drawable mHighlightDrawable;
+
+ private ListAdapter mCurrentRootAdapter;
+ private boolean mIsDataSetObserverRegistered = false;
+ private DataSetObserver mDataSetObserver = new DataSetObserver() {
+ @Override
+ public void onChanged() {
+ highlightPreferenceIfNeeded();
+ }
+
+ @Override
+ public void onInvalidated() {
+ highlightPreferenceIfNeeded();
+ }
+ };
+
+ private ViewGroup mPinnedHeaderFrameLayout;
+
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
+ if (icicle != null) {
+ mPreferenceHighlighted = icicle.getBoolean(SAVE_HIGHLIGHTED_KEY);
+ }
+
// Prepare help url and enable menu if necessary
int helpResource = getHelpResource();
if (helpResource != 0) {
@@ -64,6 +98,31 @@ public class SettingsPreferenceFragment extends PreferenceFragment implements Di
}
@Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ final View root = super.onCreateView(inflater, container, savedInstanceState);
+ mPinnedHeaderFrameLayout = (ViewGroup) root.findViewById(R.id.pinned_header);
+ return root;
+ }
+
+ public void setPinnedHeaderView(View pinnedHeader) {
+ mPinnedHeaderFrameLayout.addView(pinnedHeader);
+ mPinnedHeaderFrameLayout.setVisibility(View.VISIBLE);
+ }
+
+ public void clearPinnedHeaderView() {
+ mPinnedHeaderFrameLayout.removeAllViews();
+ mPinnedHeaderFrameLayout.setVisibility(View.GONE);
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ outState.putBoolean(SAVE_HIGHLIGHTED_KEY, mPreferenceHighlighted);
+ }
+
+ @Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (!TextUtils.isEmpty(mHelpUrl)) {
@@ -71,6 +130,137 @@ public class SettingsPreferenceFragment extends PreferenceFragment implements Di
}
}
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ final Bundle args = getArguments();
+ if (args != null) {
+ mPreferenceKey = args.getString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY);
+ highlightPreferenceIfNeeded();
+ }
+ }
+
+ @Override
+ protected void onBindPreferences() {
+ registerObserverIfNeeded();
+ }
+
+ @Override
+ protected void onUnbindPreferences() {
+ unregisterObserverIfNeeded();
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+
+ unregisterObserverIfNeeded();
+ }
+
+ public void registerObserverIfNeeded() {
+ if (!mIsDataSetObserverRegistered) {
+ if (mCurrentRootAdapter != null) {
+ mCurrentRootAdapter.unregisterDataSetObserver(mDataSetObserver);
+ }
+ mCurrentRootAdapter = getPreferenceScreen().getRootAdapter();
+ mCurrentRootAdapter.registerDataSetObserver(mDataSetObserver);
+ mIsDataSetObserverRegistered = true;
+ }
+ }
+
+ public void unregisterObserverIfNeeded() {
+ if (mIsDataSetObserverRegistered) {
+ if (mCurrentRootAdapter != null) {
+ mCurrentRootAdapter.unregisterDataSetObserver(mDataSetObserver);
+ mCurrentRootAdapter = null;
+ }
+ mIsDataSetObserverRegistered = false;
+ }
+ }
+
+ public void highlightPreferenceIfNeeded() {
+ if (isAdded() && !mPreferenceHighlighted &&!TextUtils.isEmpty(mPreferenceKey)) {
+ highlightPreference(mPreferenceKey);
+ }
+ }
+
+ private Drawable getHighlightDrawable() {
+ if (mHighlightDrawable == null) {
+ mHighlightDrawable = getActivity().getDrawable(R.drawable.preference_highlight);
+ }
+ return mHighlightDrawable;
+ }
+
+ /**
+ * Return a valid ListView position or -1 if none is found
+ */
+ private int canUseListViewForHighLighting(String key) {
+ if (!hasListView()) {
+ return -1;
+ }
+
+ ListView listView = getListView();
+ ListAdapter adapter = listView.getAdapter();
+
+ if (adapter != null && adapter instanceof PreferenceGroupAdapter) {
+ return findListPositionFromKey(adapter, key);
+ }
+
+ return -1;
+ }
+
+ private void highlightPreference(String key) {
+ final Drawable highlight = getHighlightDrawable();
+
+ final int position = canUseListViewForHighLighting(key);
+ if (position >= 0) {
+ mPreferenceHighlighted = true;
+
+ final ListView listView = getListView();
+ final ListAdapter adapter = listView.getAdapter();
+
+ ((PreferenceGroupAdapter) adapter).setHighlightedDrawable(highlight);
+ ((PreferenceGroupAdapter) adapter).setHighlighted(position);
+
+ listView.post(new Runnable() {
+ @Override
+ public void run() {
+ listView.setSelection(position);
+ listView.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ final int index = position - listView.getFirstVisiblePosition();
+ if (index >= 0 && index < listView.getChildCount()) {
+ final View v = listView.getChildAt(index);
+ final int centerX = v.getWidth() / 2;
+ final int centerY = v.getHeight() / 2;
+ highlight.setHotspot(centerX, centerY);
+ v.setPressed(true);
+ v.setPressed(false);
+ }
+ }
+ }, DELAY_HIGHLIGHT_DURATION_MILLIS);
+ }
+ });
+ }
+ }
+
+ private int findListPositionFromKey(ListAdapter adapter, String key) {
+ final int count = adapter.getCount();
+ for (int n = 0; n < count; n++) {
+ final Object item = adapter.getItem(n);
+ if (item instanceof Preference) {
+ Preference preference = (Preference) item;
+ final String preferenceKey = preference.getKey();
+ if (preferenceKey != null && preferenceKey.equals(key)) {
+ return n;
+ }
+ }
+ }
+ return -1;
+ }
+
protected void removePreference(String key) {
Preference pref = findPreference(key);
if (pref != null) {
@@ -147,7 +337,7 @@ public class SettingsPreferenceFragment extends PreferenceFragment implements Di
Log.e(TAG, "Old dialog fragment not null!");
}
mDialogFragment = new SettingsDialogFragment(this, dialogId);
- mDialogFragment.show(getActivity().getFragmentManager(), Integer.toString(dialogId));
+ mDialogFragment.show(getChildFragmentManager(), Integer.toString(dialogId));
}
public Dialog onCreateDialog(int dialogId) {
@@ -236,17 +426,18 @@ public class SettingsPreferenceFragment extends PreferenceFragment implements Di
public Dialog onCreateDialog(Bundle savedInstanceState) {
if (savedInstanceState != null) {
mDialogId = savedInstanceState.getInt(KEY_DIALOG_ID, 0);
+ mParentFragment = getParentFragment();
int mParentFragmentId = savedInstanceState.getInt(KEY_PARENT_FRAGMENT_ID, -1);
- if (mParentFragmentId > -1) {
+ if (mParentFragment == null) {
mParentFragment = getFragmentManager().findFragmentById(mParentFragmentId);
- if (!(mParentFragment instanceof DialogCreatable)) {
- throw new IllegalArgumentException(
- (mParentFragment != null
- ? mParentFragment.getClass().getName()
- : mParentFragmentId)
- + " must implement "
- + DialogCreatable.class.getName());
- }
+ }
+ if (!(mParentFragment instanceof DialogCreatable)) {
+ throw new IllegalArgumentException(
+ (mParentFragment != null
+ ? mParentFragment.getClass().getName()
+ : mParentFragmentId)
+ + " must implement "
+ + DialogCreatable.class.getName());
}
// This dialog fragment could be created from non-SettingsPreferenceFragment
if (mParentFragment instanceof SettingsPreferenceFragment) {
@@ -303,19 +494,23 @@ public class SettingsPreferenceFragment extends PreferenceFragment implements Di
getActivity().onBackPressed();
}
- public boolean startFragment(
- Fragment caller, String fragmentClass, int requestCode, Bundle extras) {
- if (getActivity() instanceof PreferenceActivity) {
- PreferenceActivity preferenceActivity = (PreferenceActivity)getActivity();
- preferenceActivity.startPreferencePanel(fragmentClass, extras,
- R.string.lock_settings_picker_title, null, caller, requestCode);
+ public boolean startFragment(Fragment caller, String fragmentClass, int titleRes,
+ int requestCode, Bundle extras) {
+ final Activity activity = getActivity();
+ if (activity instanceof SettingsActivity) {
+ SettingsActivity sa = (SettingsActivity) activity;
+ sa.startPreferencePanel(fragmentClass, extras, titleRes, null, caller, requestCode);
+ return true;
+ } else if (activity instanceof PreferenceActivity) {
+ PreferenceActivity sa = (PreferenceActivity) activity;
+ sa.startPreferencePanel(fragmentClass, extras, titleRes, null, caller, requestCode);
return true;
} else {
- Log.w(TAG, "Parent isn't PreferenceActivity, thus there's no way to launch the "
- + "given Fragment (name: " + fragmentClass + ", requestCode: " + requestCode
- + ")");
+ Log.w(TAG,
+ "Parent isn't SettingsActivity nor PreferenceActivity, thus there's no way to "
+ + "launch the given Fragment (name: " + fragmentClass
+ + ", requestCode: " + requestCode + ")");
return false;
}
}
-
}
diff --git a/src/com/android/settings/SmsDefaultDialog.java b/src/com/android/settings/SmsDefaultDialog.java
index d9a6c5f..3a3848b 100644
--- a/src/com/android/settings/SmsDefaultDialog.java
+++ b/src/com/android/settings/SmsDefaultDialog.java
@@ -64,7 +64,7 @@ public final class SmsDefaultDialog extends AlertActivity implements
private boolean buildDialog(String packageName) {
TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
- if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_NONE) {
+ if (!tm.isSmsCapable()) {
// No phone, no SMS
return false;
}
diff --git a/src/com/android/settings/SmsListPreference.java b/src/com/android/settings/SmsListPreference.java
deleted file mode 100644
index 15df776..0000000
--- a/src/com/android/settings/SmsListPreference.java
+++ /dev/null
@@ -1,86 +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;
-
-import android.app.Activity;
-import android.app.AlertDialog.Builder;
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.preference.ListPreference;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.CheckedTextView;
-import android.widget.ImageView;
-import android.widget.ListAdapter;
-
-/**
- * Extends ListPreference to allow us to show the icons for the available SMS applications. We do
- * this because the names of SMS applications are very similar and the user may not be able to
- * determine what app they are selecting without an icon.
- */
-public class SmsListPreference extends ListPreference {
- private Drawable[] mEntryDrawables;
-
- public class SmsArrayAdapter extends ArrayAdapter<CharSequence> {
- private Drawable[] mImageDrawables = null;
- private int mSelectedIndex = 0;
-
- public SmsArrayAdapter(Context context, int textViewResourceId,
- CharSequence[] objects, Drawable[] imageDrawables, int selectedIndex) {
- super(context, textViewResourceId, objects);
- mSelectedIndex = selectedIndex;
- mImageDrawables = imageDrawables;
- }
-
- public View getView(int position, View convertView, ViewGroup parent) {
- LayoutInflater inflater = ((Activity)getContext()).getLayoutInflater();
- View view = inflater.inflate(R.layout.sms_preference_item, parent, false);
- CheckedTextView checkedTextView = (CheckedTextView)view.findViewById(R.id.sms_text);
- checkedTextView.setText(getItem(position));
- if (position == mSelectedIndex) {
- checkedTextView.setChecked(true);
- }
- ImageView imageView = (ImageView)view.findViewById(R.id.sms_image);
- imageView.setImageDrawable(mImageDrawables[position]);
- return view;
- }
- }
-
- public SmsListPreference(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public void setEntryDrawables(Drawable[] entries) {
- mEntryDrawables = entries;
- }
-
- public Drawable[] getEntryDrawables() {
- return mEntryDrawables;
- }
-
- @Override
- protected void onPrepareDialogBuilder(Builder builder) {
- int selectedIndex = findIndexOfValue(getValue());
- ListAdapter adapter = new SmsArrayAdapter(getContext(),
- R.layout.sms_preference_item, getEntries(), mEntryDrawables, selectedIndex);
- builder.setAdapter(adapter, this);
- super.onPrepareDialogBuilder(builder);
- }
-} \ No newline at end of file
diff --git a/src/com/android/settings/SoundSettings.java b/src/com/android/settings/SoundSettings.java
deleted file mode 100644
index 28d93f1..0000000
--- a/src/com/android/settings/SoundSettings.java
+++ /dev/null
@@ -1,445 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings;
-
-import com.android.settings.bluetooth.DockEventReceiver;
-
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.bluetooth.BluetoothDevice;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteException;
-import android.media.AudioManager;
-import android.media.RingtoneManager;
-import android.media.audiofx.AudioEffect;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.os.Vibrator;
-import android.preference.CheckBoxPreference;
-import android.preference.ListPreference;
-import android.preference.Preference;
-import android.preference.PreferenceGroup;
-import android.preference.PreferenceScreen;
-import android.provider.MediaStore;
-import android.provider.Settings;
-import android.telephony.TelephonyManager;
-import android.util.Log;
-
-import java.util.List;
-
-public class SoundSettings extends SettingsPreferenceFragment implements
- Preference.OnPreferenceChangeListener {
- private static final String TAG = "SoundSettings";
-
- private static final int DIALOG_NOT_DOCKED = 1;
-
- /** If there is no setting in the provider, use this. */
- private static final int FALLBACK_EMERGENCY_TONE_VALUE = 0;
-
- private static final String KEY_VIBRATE = "vibrate_when_ringing";
- private static final String KEY_RING_VOLUME = "ring_volume";
- private static final String KEY_MUSICFX = "musicfx";
- private static final String KEY_DTMF_TONE = "dtmf_tone";
- private static final String KEY_SOUND_EFFECTS = "sound_effects";
- private static final String KEY_HAPTIC_FEEDBACK = "haptic_feedback";
- private static final String KEY_EMERGENCY_TONE = "emergency_tone";
- private static final String KEY_SOUND_SETTINGS = "sound_settings";
- private static final String KEY_LOCK_SOUNDS = "lock_sounds";
- private static final String KEY_RINGTONE = "ringtone";
- private static final String KEY_NOTIFICATION_SOUND = "notification_sound";
- private static final String KEY_CATEGORY_CALLS = "category_calls_and_notification";
- private static final String KEY_DOCK_CATEGORY = "dock_category";
- private static final String KEY_DOCK_AUDIO_SETTINGS = "dock_audio";
- private static final String KEY_DOCK_SOUNDS = "dock_sounds";
- private static final String KEY_DOCK_AUDIO_MEDIA_ENABLED = "dock_audio_media_enabled";
-
- private static final String[] NEED_VOICE_CAPABILITY = {
- KEY_RINGTONE, KEY_DTMF_TONE, KEY_CATEGORY_CALLS,
- KEY_EMERGENCY_TONE, KEY_VIBRATE
- };
-
- private static final int MSG_UPDATE_RINGTONE_SUMMARY = 1;
- private static final int MSG_UPDATE_NOTIFICATION_SUMMARY = 2;
-
- private CheckBoxPreference mVibrateWhenRinging;
- private CheckBoxPreference mDtmfTone;
- private CheckBoxPreference mSoundEffects;
- private CheckBoxPreference mHapticFeedback;
- private Preference mMusicFx;
- private CheckBoxPreference mLockSounds;
- private Preference mRingtonePreference;
- private Preference mNotificationPreference;
-
- private Runnable mRingtoneLookupRunnable;
-
- private AudioManager mAudioManager;
-
- private Preference mDockAudioSettings;
- private CheckBoxPreference mDockSounds;
- private Intent mDockIntent;
- private CheckBoxPreference mDockAudioMediaEnabled;
-
- private Handler mHandler = new Handler() {
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_UPDATE_RINGTONE_SUMMARY:
- mRingtonePreference.setSummary((CharSequence) msg.obj);
- break;
- case MSG_UPDATE_NOTIFICATION_SUMMARY:
- mNotificationPreference.setSummary((CharSequence) msg.obj);
- break;
- }
- }
- };
-
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(Intent.ACTION_DOCK_EVENT)) {
- handleDockChange(intent);
- }
- }
- };
-
- private PreferenceGroup mSoundSettings;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- ContentResolver resolver = getContentResolver();
- int activePhoneType = TelephonyManager.getDefault().getCurrentPhoneType();
-
- mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
-
- addPreferencesFromResource(R.xml.sound_settings);
-
- if (TelephonyManager.PHONE_TYPE_CDMA != activePhoneType) {
- // device is not CDMA, do not display CDMA emergency_tone
- getPreferenceScreen().removePreference(findPreference(KEY_EMERGENCY_TONE));
- }
-
- if (!getResources().getBoolean(R.bool.has_silent_mode)) {
- findPreference(KEY_RING_VOLUME).setDependency(null);
- }
-
- if (getResources().getBoolean(com.android.internal.R.bool.config_useFixedVolume)) {
- // device with fixed volume policy, do not display volumes submenu
- getPreferenceScreen().removePreference(findPreference(KEY_RING_VOLUME));
- }
-
- mVibrateWhenRinging = (CheckBoxPreference) findPreference(KEY_VIBRATE);
- mVibrateWhenRinging.setPersistent(false);
- mVibrateWhenRinging.setChecked(Settings.System.getInt(resolver,
- Settings.System.VIBRATE_WHEN_RINGING, 0) != 0);
-
- mDtmfTone = (CheckBoxPreference) findPreference(KEY_DTMF_TONE);
- mDtmfTone.setPersistent(false);
- mDtmfTone.setChecked(Settings.System.getInt(resolver,
- Settings.System.DTMF_TONE_WHEN_DIALING, 1) != 0);
- mSoundEffects = (CheckBoxPreference) findPreference(KEY_SOUND_EFFECTS);
- mSoundEffects.setPersistent(false);
- mSoundEffects.setChecked(Settings.System.getInt(resolver,
- Settings.System.SOUND_EFFECTS_ENABLED, 1) != 0);
- mHapticFeedback = (CheckBoxPreference) findPreference(KEY_HAPTIC_FEEDBACK);
- mHapticFeedback.setPersistent(false);
- mHapticFeedback.setChecked(Settings.System.getInt(resolver,
- Settings.System.HAPTIC_FEEDBACK_ENABLED, 1) != 0);
- mLockSounds = (CheckBoxPreference) findPreference(KEY_LOCK_SOUNDS);
- mLockSounds.setPersistent(false);
- mLockSounds.setChecked(Settings.System.getInt(resolver,
- Settings.System.LOCKSCREEN_SOUNDS_ENABLED, 1) != 0);
-
- mRingtonePreference = findPreference(KEY_RINGTONE);
- mNotificationPreference = findPreference(KEY_NOTIFICATION_SOUND);
-
- Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
- if (vibrator == null || !vibrator.hasVibrator()) {
- removePreference(KEY_VIBRATE);
- removePreference(KEY_HAPTIC_FEEDBACK);
- }
-
- if (TelephonyManager.PHONE_TYPE_CDMA == activePhoneType) {
- ListPreference emergencyTonePreference =
- (ListPreference) findPreference(KEY_EMERGENCY_TONE);
- emergencyTonePreference.setValue(String.valueOf(Settings.Global.getInt(
- resolver, Settings.Global.EMERGENCY_TONE, FALLBACK_EMERGENCY_TONE_VALUE)));
- emergencyTonePreference.setOnPreferenceChangeListener(this);
- }
-
- mSoundSettings = (PreferenceGroup) findPreference(KEY_SOUND_SETTINGS);
-
- mMusicFx = mSoundSettings.findPreference(KEY_MUSICFX);
- Intent i = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL);
- PackageManager p = getPackageManager();
- List<ResolveInfo> ris = p.queryIntentActivities(i, PackageManager.GET_DISABLED_COMPONENTS);
- if (ris.size() <= 2) {
- // no need to show the item if there is no choice for the user to make
- // note: the built in musicfx panel has two activities (one being a
- // compatibility shim that launches either the other activity, or a
- // third party one), hence the check for <=2. If the implementation
- // of the compatbility layer changes, this check may need to be updated.
- mSoundSettings.removePreference(mMusicFx);
- }
-
- if (!Utils.isVoiceCapable(getActivity())) {
- for (String prefKey : NEED_VOICE_CAPABILITY) {
- Preference pref = findPreference(prefKey);
- if (pref != null) {
- getPreferenceScreen().removePreference(pref);
- }
- }
- }
-
- mRingtoneLookupRunnable = new Runnable() {
- public void run() {
- if (mRingtonePreference != null) {
- updateRingtoneName(RingtoneManager.TYPE_RINGTONE, mRingtonePreference,
- MSG_UPDATE_RINGTONE_SUMMARY);
- }
- if (mNotificationPreference != null) {
- updateRingtoneName(RingtoneManager.TYPE_NOTIFICATION, mNotificationPreference,
- MSG_UPDATE_NOTIFICATION_SUMMARY);
- }
- }
- };
-
- initDockSettings();
- }
-
- @Override
- public void onResume() {
- super.onResume();
-
- lookupRingtoneNames();
-
- IntentFilter filter = new IntentFilter(Intent.ACTION_DOCK_EVENT);
- getActivity().registerReceiver(mReceiver, filter);
- }
-
- @Override
- public void onPause() {
- super.onPause();
-
- getActivity().unregisterReceiver(mReceiver);
- }
-
- private void updateRingtoneName(int type, Preference preference, int msg) {
- if (preference == null) return;
- Context context = getActivity();
- if (context == null) return;
- Uri ringtoneUri = RingtoneManager.getActualDefaultRingtoneUri(context, type);
- CharSequence summary = context.getString(com.android.internal.R.string.ringtone_unknown);
- // Is it a silent ringtone?
- if (ringtoneUri == null) {
- summary = context.getString(com.android.internal.R.string.ringtone_silent);
- } else {
- // Fetch the ringtone title from the media provider
- try {
- Cursor cursor = context.getContentResolver().query(ringtoneUri,
- new String[] { MediaStore.Audio.Media.TITLE }, null, null, null);
- if (cursor != null) {
- if (cursor.moveToFirst()) {
- summary = cursor.getString(0);
- }
- cursor.close();
- }
- } catch (SQLiteException sqle) {
- // Unknown title for the ringtone
- }
- }
- mHandler.sendMessage(mHandler.obtainMessage(msg, summary));
- }
-
- private void lookupRingtoneNames() {
- new Thread(mRingtoneLookupRunnable).start();
- }
-
- @Override
- public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
- if (preference == mVibrateWhenRinging) {
- Settings.System.putInt(getContentResolver(), Settings.System.VIBRATE_WHEN_RINGING,
- mVibrateWhenRinging.isChecked() ? 1 : 0);
- } else if (preference == mDtmfTone) {
- Settings.System.putInt(getContentResolver(), Settings.System.DTMF_TONE_WHEN_DIALING,
- mDtmfTone.isChecked() ? 1 : 0);
-
- } else if (preference == mSoundEffects) {
- if (mSoundEffects.isChecked()) {
- mAudioManager.loadSoundEffects();
- } else {
- mAudioManager.unloadSoundEffects();
- }
- Settings.System.putInt(getContentResolver(), Settings.System.SOUND_EFFECTS_ENABLED,
- mSoundEffects.isChecked() ? 1 : 0);
-
- } else if (preference == mHapticFeedback) {
- Settings.System.putInt(getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED,
- mHapticFeedback.isChecked() ? 1 : 0);
-
- } else if (preference == mLockSounds) {
- Settings.System.putInt(getContentResolver(), Settings.System.LOCKSCREEN_SOUNDS_ENABLED,
- mLockSounds.isChecked() ? 1 : 0);
-
- } else if (preference == mMusicFx) {
- // let the framework fire off the intent
- return false;
- } else if (preference == mDockAudioSettings) {
- int dockState = mDockIntent != null
- ? mDockIntent.getIntExtra(Intent.EXTRA_DOCK_STATE, 0)
- : Intent.EXTRA_DOCK_STATE_UNDOCKED;
-
- if (dockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) {
- showDialog(DIALOG_NOT_DOCKED);
- } else {
- boolean isBluetooth = mDockIntent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) != null;
-
- if (isBluetooth) {
- Intent i = new Intent(mDockIntent);
- i.setAction(DockEventReceiver.ACTION_DOCK_SHOW_UI);
- i.setClass(getActivity(), DockEventReceiver.class);
- getActivity().sendBroadcast(i);
- } else {
- PreferenceScreen ps = (PreferenceScreen)mDockAudioSettings;
- Bundle extras = ps.getExtras();
- extras.putBoolean("checked",
- Settings.Global.getInt(getContentResolver(),
- Settings.Global.DOCK_AUDIO_MEDIA_ENABLED, 0) == 1);
- super.onPreferenceTreeClick(ps, ps);
- }
- }
- } else if (preference == mDockSounds) {
- Settings.Global.putInt(getContentResolver(), Settings.Global.DOCK_SOUNDS_ENABLED,
- mDockSounds.isChecked() ? 1 : 0);
- } else if (preference == mDockAudioMediaEnabled) {
- Settings.Global.putInt(getContentResolver(), Settings.Global.DOCK_AUDIO_MEDIA_ENABLED,
- mDockAudioMediaEnabled.isChecked() ? 1 : 0);
- }
- return true;
- }
-
- public boolean onPreferenceChange(Preference preference, Object objValue) {
- final String key = preference.getKey();
- if (KEY_EMERGENCY_TONE.equals(key)) {
- try {
- int value = Integer.parseInt((String) objValue);
- Settings.Global.putInt(getContentResolver(),
- Settings.Global.EMERGENCY_TONE, value);
- } catch (NumberFormatException e) {
- Log.e(TAG, "could not persist emergency tone setting", e);
- }
- }
-
- return true;
- }
-
- @Override
- protected int getHelpResource() {
- return R.string.help_url_sound;
- }
-
- private boolean needsDockSettings() {
- return getResources().getBoolean(R.bool.has_dock_settings);
- }
-
- private void initDockSettings() {
- ContentResolver resolver = getContentResolver();
-
- if (needsDockSettings()) {
- mDockSounds = (CheckBoxPreference) findPreference(KEY_DOCK_SOUNDS);
- mDockSounds.setPersistent(false);
- mDockSounds.setChecked(Settings.Global.getInt(resolver,
- Settings.Global.DOCK_SOUNDS_ENABLED, 0) != 0);
- mDockAudioSettings = findPreference(KEY_DOCK_AUDIO_SETTINGS);
- mDockAudioSettings.setEnabled(false);
- } else {
- getPreferenceScreen().removePreference(findPreference(KEY_DOCK_CATEGORY));
- getPreferenceScreen().removePreference(findPreference(KEY_DOCK_AUDIO_SETTINGS));
- getPreferenceScreen().removePreference(findPreference(KEY_DOCK_SOUNDS));
- Settings.Global.putInt(resolver, Settings.Global.DOCK_AUDIO_MEDIA_ENABLED, 1);
- }
- }
-
- private void handleDockChange(Intent intent) {
- if (mDockAudioSettings != null) {
- int dockState = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, 0);
-
- boolean isBluetooth =
- intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) != null;
-
- mDockIntent = intent;
-
- if (dockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
- // remove undocked dialog if currently showing.
- try {
- removeDialog(DIALOG_NOT_DOCKED);
- } catch (IllegalArgumentException iae) {
- // Maybe it was already dismissed
- }
-
- if (isBluetooth) {
- mDockAudioSettings.setEnabled(true);
- } else {
- if (dockState == Intent.EXTRA_DOCK_STATE_LE_DESK) {
- ContentResolver resolver = getContentResolver();
- mDockAudioSettings.setEnabled(true);
- if (Settings.Global.getInt(resolver,
- Settings.Global.DOCK_AUDIO_MEDIA_ENABLED, -1) == -1) {
- Settings.Global.putInt(resolver,
- Settings.Global.DOCK_AUDIO_MEDIA_ENABLED, 0);
- }
- mDockAudioMediaEnabled =
- (CheckBoxPreference) findPreference(KEY_DOCK_AUDIO_MEDIA_ENABLED);
- mDockAudioMediaEnabled.setPersistent(false);
- mDockAudioMediaEnabled.setChecked(
- Settings.Global.getInt(resolver,
- Settings.Global.DOCK_AUDIO_MEDIA_ENABLED, 0) != 0);
- } else {
- mDockAudioSettings.setEnabled(false);
- }
- }
- } else {
- mDockAudioSettings.setEnabled(false);
- }
- }
- }
-
- @Override
- public Dialog onCreateDialog(int id) {
- if (id == DIALOG_NOT_DOCKED) {
- return createUndockedMessage();
- }
- return null;
- }
-
- private Dialog createUndockedMessage() {
- final AlertDialog.Builder ab = new AlertDialog.Builder(getActivity());
- ab.setTitle(R.string.dock_not_found_title);
- ab.setMessage(R.string.dock_not_found_text);
- ab.setPositiveButton(android.R.string.ok, null);
- return ab.create();
- }
-}
-
diff --git a/src/com/android/settings/SubSettings.java b/src/com/android/settings/SubSettings.java
index 34e9ba3..04955b2 100644
--- a/src/com/android/settings/SubSettings.java
+++ b/src/com/android/settings/SubSettings.java
@@ -16,16 +16,13 @@
package com.android.settings;
-import android.app.Fragment;
import android.util.Log;
-import com.android.settings.ChooseLockGeneric.ChooseLockGenericFragment;
-
/**
* Stub class for showing sub-settings; we can't use the main Settings class
* since for our app it is a special singleTask class.
*/
-public class SubSettings extends Settings {
+public class SubSettings extends SettingsActivity {
@Override
public boolean onNavigateUp() {
diff --git a/src/com/android/settings/TetherSettings.java b/src/com/android/settings/TetherSettings.java
index 4e0933d..c611772 100644
--- a/src/com/android/settings/TetherSettings.java
+++ b/src/com/android/settings/TetherSettings.java
@@ -30,6 +30,7 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.hardware.usb.UsbManager;
import android.net.ConnectivityManager;
@@ -40,13 +41,14 @@ import android.os.Environment;
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.SwitchPreference;
import android.text.TextUtils;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.webkit.WebView;
+import android.widget.TextView;
import java.io.InputStream;
import java.util.ArrayList;
@@ -63,16 +65,17 @@ public class TetherSettings extends SettingsPreferenceFragment
private static final String USB_TETHER_SETTINGS = "usb_tether_settings";
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 int DIALOG_AP_SETTINGS = 1;
private WebView mView;
- private CheckBoxPreference mUsbTether;
+ private SwitchPreference mUsbTether;
private WifiApEnabler mWifiApEnabler;
- private CheckBoxPreference mEnableWifiAp;
+ private SwitchPreference mEnableWifiAp;
- private CheckBoxPreference mBluetoothTether;
+ private SwitchPreference mBluetoothTether;
private BroadcastReceiver mTetherChangeReceiver;
@@ -92,6 +95,7 @@ public class TetherSettings extends SettingsPreferenceFragment
private WifiApDialog mDialog;
private WifiManager mWifiManager;
private WifiConfiguration mWifiConfig = null;
+ private UserManager mUm;
private boolean mUsbConnected;
private boolean mMassStorageActive;
@@ -110,11 +114,25 @@ public class TetherSettings extends SettingsPreferenceFragment
private String[] mProvisionApp;
private static final int PROVISION_REQUEST = 0;
+ private boolean mUnavailable;
+
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
+
+ if(icicle != null) {
+ mTetherChoice = icicle.getInt(TETHER_CHOICE);
+ }
addPreferencesFromResource(R.xml.tether_prefs);
+ mUm = (UserManager) getSystemService(Context.USER_SERVICE);
+
+ if (mUm.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING)) {
+ mUnavailable = true;
+ setPreferenceScreen(new PreferenceScreen(getActivity(), null));
+ return;
+ }
+
final Activity activity = getActivity();
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter != null) {
@@ -123,10 +141,10 @@ public class TetherSettings extends SettingsPreferenceFragment
}
mEnableWifiAp =
- (CheckBoxPreference) findPreference(ENABLE_WIFI_AP);
+ (SwitchPreference) findPreference(ENABLE_WIFI_AP);
Preference wifiApSettings = findPreference(WIFI_AP_SSID_AND_SECURITY);
- mUsbTether = (CheckBoxPreference) findPreference(USB_TETHER_SETTINGS);
- mBluetoothTether = (CheckBoxPreference) findPreference(ENABLE_BLUETOOTH_TETHERING);
+ mUsbTether = (SwitchPreference) findPreference(USB_TETHER_SETTINGS);
+ mBluetoothTether = (SwitchPreference) findPreference(ENABLE_BLUETOOTH_TETHERING);
ConnectivityManager cm =
(ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
@@ -168,6 +186,12 @@ public class TetherSettings extends SettingsPreferenceFragment
mView = new WebView(activity);
}
+ @Override
+ public void onSaveInstanceState(Bundle savedInstanceState) {
+ savedInstanceState.putInt(TETHER_CHOICE, mTetherChoice);
+ super.onSaveInstanceState(savedInstanceState);
+ }
+
private void initWifiTethering() {
final Activity activity = getActivity();
mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
@@ -264,6 +288,15 @@ public class TetherSettings extends SettingsPreferenceFragment
public void onStart() {
super.onStart();
+ if (mUnavailable) {
+ TextView emptyView = (TextView) getView().findViewById(android.R.id.empty);
+ getListView().setEmptyView(emptyView);
+ if (emptyView != null) {
+ emptyView.setText(R.string.tethering_settings_not_available);
+ }
+ return;
+ }
+
final Activity activity = getActivity();
mMassStorageActive = Environment.MEDIA_SHARED.equals(Environment.getExternalStorageState());
@@ -297,6 +330,10 @@ public class TetherSettings extends SettingsPreferenceFragment
@Override
public void onStop() {
super.onStop();
+
+ if (mUnavailable) {
+ return;
+ }
getActivity().unregisterReceiver(mTetherChangeReceiver);
mTetherChangeReceiver = null;
if (mWifiApEnabler != null) {
@@ -435,18 +472,40 @@ public class TetherSettings extends SettingsPreferenceFragment
return false;
}
- boolean isProvisioningNeeded() {
- if (SystemProperties.getBoolean("net.tethering.noprovisioning", false)) {
+ public static boolean isProvisioningNeededButUnavailable(Context context) {
+ String[] provisionApp = context.getResources().getStringArray(
+ com.android.internal.R.array.config_mobile_hotspot_provision_app);
+ return (isProvisioningNeeded(provisionApp)
+ && !isIntentAvailable(context, provisionApp));
+ }
+
+ private static boolean isIntentAvailable(Context context, String[] provisionApp) {
+ if (provisionApp.length < 2) {
+ throw new IllegalArgumentException("provisionApp length should at least be 2");
+ }
+ final PackageManager packageManager = context.getPackageManager();
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setClassName(provisionApp[0], provisionApp[1]);
+
+ return (packageManager.queryIntentActivities(intent,
+ PackageManager.MATCH_DEFAULT_ONLY).size() > 0);
+ }
+
+
+ private static boolean isProvisioningNeeded(String[] provisionApp) {
+ if (SystemProperties.getBoolean("net.tethering.noprovisioning", false)
+ || provisionApp == null) {
return false;
}
- return mProvisionApp.length == 2;
+ return (provisionApp.length == 2);
}
private void startProvisioningIfNecessary(int choice) {
mTetherChoice = choice;
- if (isProvisioningNeeded()) {
+ if (isProvisioningNeeded(mProvisionApp)) {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setClassName(mProvisionApp[0], mProvisionApp[1]);
+ intent.putExtra(TETHER_CHOICE, mTetherChoice);
startActivityForResult(intent, PROVISION_REQUEST);
} else {
startTethering();
@@ -459,7 +518,7 @@ public class TetherSettings extends SettingsPreferenceFragment
if (resultCode == Activity.RESULT_OK) {
startTethering();
} else {
- //BT and USB need checkbox turned off on failure
+ //BT and USB need switch turned off on failure
//Wifi tethering is never turned on until afterwards
switch (mTetherChoice) {
case BLUETOOTH_TETHERING:
diff --git a/src/com/android/settings/TrustAgentSettings.java b/src/com/android/settings/TrustAgentSettings.java
new file mode 100644
index 0000000..54fef36
--- /dev/null
+++ b/src/com/android/settings/TrustAgentSettings.java
@@ -0,0 +1,157 @@
+/*
+ * 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 java.util.List;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceGroup;
+import android.preference.SwitchPreference;
+import android.service.trust.TrustAgentService;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import com.android.internal.widget.LockPatternUtils;
+
+public class TrustAgentSettings extends SettingsPreferenceFragment implements
+ Preference.OnPreferenceChangeListener {
+ private static final String SERVICE_INTERFACE = TrustAgentService.SERVICE_INTERFACE;
+ private ArrayMap<ComponentName, AgentInfo> mAvailableAgents;
+ private final ArraySet<ComponentName> mActiveAgents = new ArraySet<ComponentName>();
+ private LockPatternUtils mLockPatternUtils;
+
+ public static final class AgentInfo {
+ CharSequence label;
+ ComponentName component; // service that implements ITrustAgent
+ SwitchPreference preference;
+ public Drawable icon;
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof AgentInfo) {
+ return component.equals(((AgentInfo)other).component);
+ }
+ return true;
+ }
+
+ public int compareTo(AgentInfo other) {
+ return component.compareTo(other.component);
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ addPreferencesFromResource(R.xml.trust_agent_settings);
+ }
+
+ public void onResume() {
+ super.onResume();
+ updateAgents();
+ };
+
+ private void updateAgents() {
+ final Context context = getActivity();
+ if (mAvailableAgents == null) {
+ mAvailableAgents = findAvailableTrustAgents();
+ }
+ if (mLockPatternUtils == null) {
+ mLockPatternUtils = new LockPatternUtils(getActivity());
+ }
+ loadActiveAgents();
+ PreferenceGroup category =
+ (PreferenceGroup) getPreferenceScreen().findPreference("trust_agents");
+ category.removeAll();
+ final int count = mAvailableAgents.size();
+ for (int i = 0; i < count; i++) {
+ AgentInfo agent = mAvailableAgents.valueAt(i);
+ final SwitchPreference preference = new SwitchPreference(context);
+ agent.preference = preference;
+ preference.setPersistent(false);
+ preference.setTitle(agent.label);
+ preference.setIcon(agent.icon);
+ preference.setPersistent(false);
+ preference.setOnPreferenceChangeListener(this);
+ preference.setChecked(mActiveAgents.contains(agent.component));
+ category.addPreference(agent.preference);
+ }
+ }
+
+ private void loadActiveAgents() {
+ List<ComponentName> activeTrustAgents = mLockPatternUtils.getEnabledTrustAgents();
+ if (activeTrustAgents != null) {
+ mActiveAgents.addAll(activeTrustAgents);
+ }
+ }
+
+ private void saveActiveAgents() {
+ mLockPatternUtils.setEnabledTrustAgents(mActiveAgents);
+ }
+
+ ArrayMap<ComponentName, AgentInfo> findAvailableTrustAgents() {
+ PackageManager pm = getActivity().getPackageManager();
+ Intent trustAgentIntent = new Intent(SERVICE_INTERFACE);
+ List<ResolveInfo> resolveInfos = pm.queryIntentServices(trustAgentIntent,
+ PackageManager.GET_META_DATA);
+
+ ArrayMap<ComponentName, AgentInfo> agents = new ArrayMap<ComponentName, AgentInfo>();
+ final int count = resolveInfos.size();
+ agents.ensureCapacity(count);
+ for (int i = 0; i < count; i++ ) {
+ ResolveInfo resolveInfo = resolveInfos.get(i);
+ if (resolveInfo.serviceInfo == null) continue;
+ if (!TrustAgentUtils.checkProvidePermission(resolveInfo, pm)) continue;
+ ComponentName name = TrustAgentUtils.getComponentName(resolveInfo);
+ AgentInfo agentInfo = new AgentInfo();
+ agentInfo.label = resolveInfo.loadLabel(pm);
+ agentInfo.icon = resolveInfo.loadIcon(pm);
+ agentInfo.component = name;
+ agents.put(name, agentInfo);
+ }
+ return agents;
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if (preference instanceof SwitchPreference) {
+ final int count = mAvailableAgents.size();
+ for (int i = 0; i < count; i++) {
+ AgentInfo agent = mAvailableAgents.valueAt(i);
+ if (agent.preference == preference) {
+ if ((Boolean) newValue) {
+ if (!mActiveAgents.contains(agent.component)) {
+ mActiveAgents.add(agent.component);
+ }
+ } else {
+ mActiveAgents.remove(agent.component);
+ }
+ saveActiveAgents();
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/src/com/android/settings/TrustAgentUtils.java b/src/com/android/settings/TrustAgentUtils.java
new file mode 100644
index 0000000..31a073c
--- /dev/null
+++ b/src/com/android/settings/TrustAgentUtils.java
@@ -0,0 +1,119 @@
+/*
+ * 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.ComponentName;
+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.service.trust.TrustAgentService;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Slog;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+public class TrustAgentUtils {
+ static final String TAG = "TrustAgentUtils";
+
+ private static final String TRUST_AGENT_META_DATA = TrustAgentService.TRUST_AGENT_META_DATA;
+ private static final String PERMISSION_PROVIDE_AGENT = android.Manifest.permission.PROVIDE_TRUST_AGENT;
+
+ /**
+ * @return true, if the service in resolveInfo has the permission to provide a trust agent.
+ */
+ public static boolean checkProvidePermission(ResolveInfo resolveInfo, PackageManager pm) {
+ String packageName = resolveInfo.serviceInfo.packageName;
+ if (pm.checkPermission(PERMISSION_PROVIDE_AGENT, packageName)
+ != PackageManager.PERMISSION_GRANTED) {
+ Log.w(TAG, "Skipping agent because package " + packageName
+ + " does not have permission " + PERMISSION_PROVIDE_AGENT + ".");
+ return false;
+ }
+ return true;
+ }
+
+ public static class TrustAgentComponentInfo {
+ ComponentName componentName;
+ String title;
+ String summary;
+ }
+
+ public static ComponentName getComponentName(ResolveInfo resolveInfo) {
+ if (resolveInfo == null || resolveInfo.serviceInfo == null) return null;
+ return new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name);
+ }
+
+ public static TrustAgentComponentInfo getSettingsComponent(
+ PackageManager pm, ResolveInfo resolveInfo) {
+ if (resolveInfo == null || resolveInfo.serviceInfo == null
+ || resolveInfo.serviceInfo.metaData == null) return null;
+ String cn = null;
+ TrustAgentComponentInfo trustAgentComponentInfo = new TrustAgentComponentInfo();
+ XmlResourceParser parser = null;
+ Exception caughtException = null;
+ try {
+ parser = resolveInfo.serviceInfo.loadXmlMetaData(pm, TRUST_AGENT_META_DATA);
+ if (parser == null) {
+ Slog.w(TAG, "Can't find " + TRUST_AGENT_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 (!"trust-agent".equals(nodeName)) {
+ Slog.w(TAG, "Meta-data does not start with trust-agent tag");
+ return null;
+ }
+ TypedArray sa =
+ res.obtainAttributes(attrs, com.android.internal.R.styleable.TrustAgent);
+ trustAgentComponentInfo.summary =
+ sa.getString(com.android.internal.R.styleable.TrustAgent_summary);
+ trustAgentComponentInfo.title =
+ sa.getString(com.android.internal.R.styleable.TrustAgent_title);
+ cn = sa.getString(com.android.internal.R.styleable.TrustAgent_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) {
+ Slog.w(TAG, "Error parsing : " + resolveInfo.serviceInfo.packageName, caughtException);
+ return null;
+ }
+ if (cn != null && cn.indexOf('/') < 0) {
+ cn = resolveInfo.serviceInfo.packageName + "/" + cn;
+ }
+ trustAgentComponentInfo.componentName = (cn == null) ? null : ComponentName.unflattenFromString(cn);
+ return trustAgentComponentInfo;
+ }
+}
diff --git a/src/com/android/settings/TrustedCredentialsSettings.java b/src/com/android/settings/TrustedCredentialsSettings.java
index cdb96cb..14c4936 100644
--- a/src/com/android/settings/TrustedCredentialsSettings.java
+++ b/src/com/android/settings/TrustedCredentialsSettings.java
@@ -16,41 +16,49 @@
package com.android.settings;
-import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.Fragment;
import android.content.Context;
import android.content.DialogInterface;
-import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.content.res.TypedArray;
import android.net.http.SslCertificate;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.os.UserManager;
import android.security.IKeyChainService;
import android.security.KeyChain;
import android.security.KeyChain.KeyChainConnection;
+import android.util.SparseArray;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
+import android.widget.BaseExpandableListAdapter;
import android.widget.Button;
import android.widget.CheckBox;
-import android.widget.FrameLayout;
+import android.widget.ExpandableListView;
+import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
+import android.widget.Spinner;
import android.widget.TabHost;
import android.widget.TextView;
+
+import com.android.internal.util.ParcelableString;
+
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.Set;
-
-import com.android.org.conscrypt.TrustedCertificateStore;
public class TrustedCredentialsSettings extends Fragment {
@@ -60,24 +68,20 @@ public class TrustedCredentialsSettings extends Fragment {
private static final String USER_ACTION = "com.android.settings.TRUSTED_CREDENTIALS_USER";
- private static final int REQUEST_PIN_CHALLENGE = 12309;
- // If the restriction PIN is entered correctly.
- private boolean mChallengeSucceeded;
- private boolean mChallengeRequested;
-
-
private enum Tab {
SYSTEM("system",
R.string.trusted_credentials_system_tab,
R.id.system_tab,
R.id.system_progress,
R.id.system_list,
+ R.id.system_expandable_list,
true),
USER("user",
R.string.trusted_credentials_user_tab,
R.id.user_tab,
R.id.user_progress,
R.id.user_list,
+ R.id.user_expandable_list,
false);
private final String mTag;
@@ -85,28 +89,34 @@ public class TrustedCredentialsSettings extends Fragment {
private final int mView;
private final int mProgress;
private final int mList;
+ private final int mExpandableList;
private final boolean mCheckbox;
- private Tab(String tag, int label, int view, int progress, int list, boolean checkbox) {
+
+ private Tab(String tag, int label, int view, int progress, int list, int expandableList,
+ boolean checkbox) {
mTag = tag;
mLabel = label;
mView = view;
mProgress = progress;
mList = list;
+ mExpandableList = expandableList;
mCheckbox = checkbox;
}
- private Set<String> getAliases(TrustedCertificateStore store) {
+
+ private List<ParcelableString> getAliases(IKeyChainService service) throws RemoteException {
switch (this) {
- case SYSTEM:
- return store.allSystemAliases();
+ case SYSTEM: {
+ return service.getSystemCaAliases().getList();
+ }
case USER:
- return store.userAliases();
+ return service.getUserCaAliases().getList();
}
throw new AssertionError();
}
- private boolean deleted(TrustedCertificateStore store, String alias) {
+ private boolean deleted(IKeyChainService service, String alias) throws RemoteException {
switch (this) {
case SYSTEM:
- return !store.containsAlias(alias);
+ return !service.containsCaAlias(alias);
case USER:
return false;
}
@@ -141,7 +151,7 @@ public class TrustedCredentialsSettings extends Fragment {
if (certHolder.mTab.mCheckbox) {
certHolder.mDeleted = !certHolder.mDeleted;
} else {
- certHolder.mAdapter.mCertHolders.remove(certHolder);
+ certHolder.mAdapter.remove(certHolder);
}
certHolder.mAdapter.notifyDataSetChanged();
} else {
@@ -151,10 +161,9 @@ public class TrustedCredentialsSettings extends Fragment {
}
}
- // be careful not to use this on the UI thread since it is does file operations
- private final TrustedCertificateStore mStore = new TrustedCertificateStore();
-
private TabHost mTabHost;
+ private final SparseArray<KeyChainConnection>
+ mKeyChainConnectionByProfileId = new SparseArray<KeyChainConnection>();
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -176,6 +185,19 @@ public class TrustedCredentialsSettings extends Fragment {
}
return mTabHost;
}
+ @Override
+ public void onDestroy() {
+ closeKeyChainConnections();
+ super.onDestroy();
+ }
+
+ private void closeKeyChainConnections() {
+ final int n = mKeyChainConnectionByProfileId.size();
+ for (int i = 0; i < n; ++i) {
+ mKeyChainConnectionByProfileId.valueAt(i).close();
+ }
+ mKeyChainConnectionByProfileId.clear();
+ }
private void addTab(Tab tab) {
TabHost.TabSpec systemSpec = mTabHost.newTabSpec(tab.mTag)
@@ -183,86 +205,266 @@ public class TrustedCredentialsSettings extends Fragment {
.setContent(tab.mView);
mTabHost.addTab(systemSpec);
- ListView lv = (ListView) mTabHost.findViewById(tab.mList);
- final TrustedCertificateAdapter adapter = new TrustedCertificateAdapter(tab);
- lv.setAdapter(adapter);
- lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override public void onItemClick(AdapterView<?> parent, View view, int pos, long id) {
- showCertDialog(adapter.getItem(pos));
+ if (mUserManager.getUserProfiles().size() > 1) {
+ ExpandableListView lv = (ExpandableListView) mTabHost.findViewById(tab.mExpandableList);
+ final TrustedCertificateExpandableAdapter adapter =
+ new TrustedCertificateExpandableAdapter(tab);
+ lv.setAdapter(adapter);
+ lv.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
+ @Override
+ public boolean onChildClick(ExpandableListView parent, View v,
+ int groupPosition, int childPosition, long id) {
+ showCertDialog(adapter.getChild(groupPosition, childPosition));
+ return true;
+ }
+ });
+ } else {
+ ListView lv = (ListView) mTabHost.findViewById(tab.mList);
+ final TrustedCertificateAdapter adapter = new TrustedCertificateAdapter(tab);
+ lv.setAdapter(adapter);
+ lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override public void onItemClick(AdapterView<?> parent, View view,
+ int pos, long id) {
+ showCertDialog(adapter.getItem(pos));
+ }
+ });
+ }
+ }
+
+ /**
+ * Common interface for adapters of both expandable and non-expandable certificate lists.
+ */
+ private interface TrustedCertificateAdapterCommons {
+ /**
+ * Remove a certificate from the list.
+ * @param certHolder the certificate to be removed.
+ */
+ void remove(CertHolder certHolder);
+ /**
+ * Notify the adapter that the underlying data set has changed.
+ */
+ void notifyDataSetChanged();
+ /**
+ * Load the certificates.
+ */
+ void load();
+ /**
+ * Gets the identifier of the list view the adapter is connected to.
+ * @param tab the tab on which the list view resides.
+ * @return identifier of the list view.
+ */
+ int getListViewId(Tab tab);
+ }
+
+ /**
+ * Adapter for expandable list view of certificates. Groups in the view correspond to profiles
+ * whereas children correspond to certificates.
+ */
+ private class TrustedCertificateExpandableAdapter extends BaseExpandableListAdapter implements
+ TrustedCertificateAdapterCommons {
+ private AdapterData mData;
+
+ private TrustedCertificateExpandableAdapter(Tab tab) {
+ mData = new AdapterData(tab, this);
+ load();
+ }
+ @Override
+ public void remove(CertHolder certHolder) {
+ mData.remove(certHolder);
+ }
+ @Override
+ public int getGroupCount() {
+ return mData.mCertHoldersByUserId.size();
+ }
+ @Override
+ public int getChildrenCount(int groupPosition) {
+ List<CertHolder> certHolders = mData.mCertHoldersByUserId.valueAt(groupPosition);
+ if (certHolders != null) {
+ return certHolders.size();
}
- });
+ return 0;
+ }
+ @Override
+ public UserHandle getGroup(int groupPosition) {
+ return new UserHandle(mData.mCertHoldersByUserId.keyAt(groupPosition));
+ }
+ @Override
+ public CertHolder getChild(int groupPosition, int childPosition) {
+ return mData.mCertHoldersByUserId.valueAt(groupPosition).get(childPosition);
+ }
+ @Override
+ public long getGroupId(int groupPosition) {
+ return mData.mCertHoldersByUserId.keyAt(groupPosition);
+ }
+ @Override
+ public long getChildId(int groupPosition, int childPosition) {
+ return childPosition;
+ }
+ @Override
+ public boolean hasStableIds() {
+ return false;
+ }
+ @Override
+ public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
+ ViewGroup parent) {
+ if (convertView == null) {
+ LayoutInflater inflater = (LayoutInflater) getActivity()
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ convertView = inflateCategoryHeader(inflater, parent);
+ }
+
+ final TextView title = (TextView) convertView.findViewById(android.R.id.title);
+ final UserHandle profile = getGroup(groupPosition);
+ final UserInfo userInfo = mUserManager.getUserInfo(profile.getIdentifier());
+ if (userInfo.isManagedProfile()) {
+ title.setText(R.string.category_work);
+ } else {
+ title.setText(R.string.category_personal);
+ }
+ title.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_END);
+
+ return convertView;
+ }
+ @Override
+ public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
+ View convertView, ViewGroup parent) {
+ return getViewForCertificate(getChild(groupPosition, childPosition), mData.mTab,
+ convertView, parent);
+ }
+ @Override
+ public boolean isChildSelectable(int groupPosition, int childPosition) {
+ return true;
+ }
+ @Override
+ public void load() {
+ mData.new AliasLoader().execute();
+ }
+ @Override
+ public int getListViewId(Tab tab) {
+ return tab.mExpandableList;
+ }
+ private View inflateCategoryHeader(LayoutInflater inflater, ViewGroup parent) {
+ final TypedArray a = inflater.getContext().obtainStyledAttributes(null,
+ com.android.internal.R.styleable.Preference,
+ com.android.internal.R.attr.preferenceCategoryStyle, 0);
+ final int resId = a.getResourceId(com.android.internal.R.styleable.Preference_layout,
+ 0);
+ return inflater.inflate(resId, parent, false);
+ }
+
}
- private class TrustedCertificateAdapter extends BaseAdapter {
- private final List<CertHolder> mCertHolders = new ArrayList<CertHolder>();
- private final Tab mTab;
+ private class TrustedCertificateAdapter extends BaseAdapter implements
+ TrustedCertificateAdapterCommons {
+ private final AdapterData mData;
private TrustedCertificateAdapter(Tab tab) {
- mTab = tab;
+ mData = new AdapterData(tab, this);
load();
}
- private void load() {
- new AliasLoader().execute();
+ @Override
+ public void remove(CertHolder certHolder) {
+ mData.remove(certHolder);
+ }
+ @Override
+ public int getListViewId(Tab tab) {
+ return tab.mList;
+ }
+ @Override
+ public void load() {
+ mData.new AliasLoader().execute();
}
@Override public int getCount() {
- return mCertHolders.size();
+ List<CertHolder> certHolders = mData.mCertHoldersByUserId.valueAt(0);
+ if (certHolders != null) {
+ return certHolders.size();
+ }
+ return 0;
}
@Override public CertHolder getItem(int position) {
- return mCertHolders.get(position);
+ return mData.mCertHoldersByUserId.valueAt(0).get(position);
}
@Override public long getItemId(int position) {
return position;
}
@Override public View getView(int position, View view, ViewGroup parent) {
- ViewHolder holder;
- if (view == null) {
- LayoutInflater inflater = LayoutInflater.from(getActivity());
- view = inflater.inflate(R.layout.trusted_credential, parent, false);
- holder = new ViewHolder();
- holder.mSubjectPrimaryView = (TextView)
- view.findViewById(R.id.trusted_credential_subject_primary);
- holder.mSubjectSecondaryView = (TextView)
- view.findViewById(R.id.trusted_credential_subject_secondary);
- holder.mCheckBox = (CheckBox) view.findViewById(R.id.trusted_credential_status);
- view.setTag(holder);
- } else {
- holder = (ViewHolder) view.getTag();
- }
- CertHolder certHolder = mCertHolders.get(position);
- holder.mSubjectPrimaryView.setText(certHolder.mSubjectPrimary);
- holder.mSubjectSecondaryView.setText(certHolder.mSubjectSecondary);
- if (mTab.mCheckbox) {
- holder.mCheckBox.setChecked(!certHolder.mDeleted);
- holder.mCheckBox.setVisibility(View.VISIBLE);
- }
- return view;
- };
+ return getViewForCertificate(getItem(position), mData.mTab, view, parent);
+ }
+ }
+
+ private class AdapterData {
+ private final SparseArray<List<CertHolder>> mCertHoldersByUserId =
+ new SparseArray<List<CertHolder>>();
+ private final Tab mTab;
+ private final TrustedCertificateAdapterCommons mAdapter;
+
+ private AdapterData(Tab tab, TrustedCertificateAdapterCommons adapter) {
+ mAdapter = adapter;
+ mTab = tab;
+ }
+
+ private class AliasLoader extends AsyncTask<Void, Integer, SparseArray<List<CertHolder>>> {
+ private ProgressBar mProgressBar;
+ private View mList;
- private class AliasLoader extends AsyncTask<Void, Integer, List<CertHolder>> {
- ProgressBar mProgressBar;
- View mList;
@Override protected void onPreExecute() {
View content = mTabHost.getTabContentView();
mProgressBar = (ProgressBar) content.findViewById(mTab.mProgress);
- mList = content.findViewById(mTab.mList);
+ mList = content.findViewById(mAdapter.getListViewId(mTab));
mProgressBar.setVisibility(View.VISIBLE);
mList.setVisibility(View.GONE);
}
- @Override protected List<CertHolder> doInBackground(Void... params) {
- Set<String> aliases = mTab.getAliases(mStore);
- int max = aliases.size();
- int progress = 0;
- List<CertHolder> certHolders = new ArrayList<CertHolder>(max);
- for (String alias : aliases) {
- X509Certificate cert = (X509Certificate) mStore.getCertificate(alias, true);
- certHolders.add(new CertHolder(mStore,
- TrustedCertificateAdapter.this,
- mTab,
- alias,
- cert));
- publishProgress(++progress, max);
+ @Override protected SparseArray<List<CertHolder>> doInBackground(Void... params) {
+ SparseArray<List<CertHolder>> certHoldersByProfile =
+ new SparseArray<List<CertHolder>>();
+ try {
+ List<UserHandle> profiles = mUserManager.getUserProfiles();
+ final int n = profiles.size();
+ // First we get all aliases for all profiles in order to show progress
+ // correctly. Otherwise this could all be in a single loop.
+ SparseArray<List<ParcelableString>> aliasesByProfileId = new SparseArray<
+ List<ParcelableString>>(n);
+ int max = 0;
+ int progress = 0;
+ for (int i = 0; i < n; ++i) {
+ UserHandle profile = profiles.get(i);
+ int profileId = profile.getIdentifier();
+ KeyChainConnection keyChainConnection = KeyChain.bindAsUser(getActivity(),
+ profile);
+ // Saving the connection for later use on the certificate dialog.
+ mKeyChainConnectionByProfileId.put(profileId, keyChainConnection);
+ IKeyChainService service = keyChainConnection.getService();
+ List<ParcelableString> aliases = mTab.getAliases(service);
+ max += aliases.size();
+ aliasesByProfileId.put(profileId, aliases);
+ }
+ for (int i = 0; i < n; ++i) {
+ UserHandle profile = profiles.get(i);
+ int profileId = profile.getIdentifier();
+ List<ParcelableString> aliases = aliasesByProfileId.get(profileId);
+ IKeyChainService service = mKeyChainConnectionByProfileId.get(profileId)
+ .getService();
+ List<CertHolder> certHolders = new ArrayList<CertHolder>(max);
+ final int aliasMax = aliases.size();
+ for (int j = 0; j < aliasMax; ++j) {
+ String alias = aliases.get(j).string;
+ byte[] encodedCertificate = service.getEncodedCaCertificate(alias,
+ true);
+ X509Certificate cert = KeyChain.toCertificate(encodedCertificate);
+ certHolders.add(new CertHolder(service, mAdapter,
+ mTab, alias, cert, profileId));
+ publishProgress(++progress, max);
+ }
+ Collections.sort(certHolders);
+ certHoldersByProfile.put(profileId, certHolders);
+ }
+ return certHoldersByProfile;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Remote exception while loading aliases.", e);
+ return new SparseArray<List<CertHolder>>();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "InterruptedException while loading aliases.", e);
+ return new SparseArray<List<CertHolder>>();
}
- Collections.sort(certHolders);
- return certHolders;
}
@Override protected void onProgressUpdate(Integer... progressAndMax) {
int progress = progressAndMax[0];
@@ -272,21 +474,33 @@ public class TrustedCredentialsSettings extends Fragment {
}
mProgressBar.setProgress(progress);
}
- @Override protected void onPostExecute(List<CertHolder> certHolders) {
- mCertHolders.clear();
- mCertHolders.addAll(certHolders);
- notifyDataSetChanged();
- View content = mTabHost.getTabContentView();
+ @Override protected void onPostExecute(SparseArray<List<CertHolder>> certHolders) {
+ mCertHoldersByUserId.clear();
+ final int n = certHolders.size();
+ for (int i = 0; i < n; ++i) {
+ mCertHoldersByUserId.put(certHolders.keyAt(i), certHolders.valueAt(i));
+ }
+ mAdapter.notifyDataSetChanged();
mProgressBar.setVisibility(View.GONE);
mList.setVisibility(View.VISIBLE);
mProgressBar.setProgress(0);
}
}
+
+ public void remove(CertHolder certHolder) {
+ if (mCertHoldersByUserId != null) {
+ final List<CertHolder> certs = mCertHoldersByUserId.get(certHolder.mProfileId);
+ if (certs != null) {
+ certs.remove(certHolder);
+ }
+ }
+ }
}
private static class CertHolder implements Comparable<CertHolder> {
- private final TrustedCertificateStore mStore;
- private final TrustedCertificateAdapter mAdapter;
+ public int mProfileId;
+ private final IKeyChainService mService;
+ private final TrustedCertificateAdapterCommons mAdapter;
private final Tab mTab;
private final String mAlias;
private final X509Certificate mX509Cert;
@@ -296,12 +510,14 @@ public class TrustedCredentialsSettings extends Fragment {
private final String mSubjectSecondary;
private boolean mDeleted;
- private CertHolder(TrustedCertificateStore store,
- TrustedCertificateAdapter adapter,
+ private CertHolder(IKeyChainService service,
+ TrustedCertificateAdapterCommons adapter,
Tab tab,
String alias,
- X509Certificate x509Cert) {
- mStore = store;
+ X509Certificate x509Cert,
+ int profileId) {
+ mProfileId = profileId;
+ mService = service;
mAdapter = adapter;
mTab = tab;
mAlias = alias;
@@ -332,7 +548,13 @@ public class TrustedCredentialsSettings extends Fragment {
mSubjectSecondary = "";
}
}
- mDeleted = mTab.deleted(mStore, mAlias);
+ try {
+ mDeleted = mTab.deleted(mService, mAlias);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Remote exception while checking if alias " + mAlias + " is deleted.",
+ e);
+ mDeleted = false;
+ }
}
@Override public int compareTo(CertHolder o) {
int primary = this.mSubjectPrimary.compareToIgnoreCase(o.mSubjectPrimary);
@@ -353,6 +575,32 @@ public class TrustedCredentialsSettings extends Fragment {
}
}
+ private View getViewForCertificate(CertHolder certHolder, Tab mTab, View convertView,
+ ViewGroup parent) {
+ ViewHolder holder;
+ if (convertView == null) {
+ LayoutInflater inflater = LayoutInflater.from(getActivity());
+ convertView = inflater.inflate(R.layout.trusted_credential, parent, false);
+ holder = new ViewHolder();
+ holder.mSubjectPrimaryView = (TextView)
+ convertView.findViewById(R.id.trusted_credential_subject_primary);
+ holder.mSubjectSecondaryView = (TextView)
+ convertView.findViewById(R.id.trusted_credential_subject_secondary);
+ holder.mCheckBox = (CheckBox) convertView.findViewById(
+ R.id.trusted_credential_status);
+ convertView.setTag(holder);
+ } else {
+ holder = (ViewHolder) convertView.getTag();
+ }
+ holder.mSubjectPrimaryView.setText(certHolder.mSubjectPrimary);
+ holder.mSubjectSecondaryView.setText(certHolder.mSubjectSecondary);
+ if (mTab.mCheckbox) {
+ holder.mCheckBox.setChecked(!certHolder.mDeleted);
+ holder.mCheckBox.setVisibility(View.VISIBLE);
+ }
+ return convertView;
+ }
+
private static class ViewHolder {
private TextView mSubjectPrimaryView;
private TextView mSubjectSecondaryView;
@@ -360,10 +608,42 @@ public class TrustedCredentialsSettings extends Fragment {
}
private void showCertDialog(final CertHolder certHolder) {
- View view = certHolder.mSslCert.inflateCertificateView(getActivity());
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(com.android.internal.R.string.ssl_certificate);
- builder.setView(view);
+
+ final ArrayList<View> views = new ArrayList<View>();
+ final ArrayList<String> titles = new ArrayList<String>();
+ addCertChain(certHolder, views, titles);
+
+ ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(getActivity(),
+ android.R.layout.simple_spinner_item,
+ titles);
+ arrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ Spinner spinner = new Spinner(getActivity());
+ spinner.setAdapter(arrayAdapter);
+ spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position,
+ long id) {
+ for(int i = 0; i < views.size(); i++) {
+ views.get(i).setVisibility(i == position ? View.VISIBLE : View.GONE);
+ }
+ }
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) { }
+ });
+
+ LinearLayout container = new LinearLayout(getActivity());
+ container.setOrientation(LinearLayout.VERTICAL);
+ container.addView(spinner);
+ for (int i = 0; i < views.size(); ++i) {
+ View certificateView = views.get(i);
+ if (i != 0) {
+ certificateView.setVisibility(View.GONE);
+ }
+ container.addView(certificateView);
+ }
+ builder.setView(container);
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
@@ -371,20 +651,17 @@ public class TrustedCredentialsSettings extends Fragment {
});
final Dialog certDialog = builder.create();
- ViewGroup body = (ViewGroup) view.findViewById(com.android.internal.R.id.body);
+ ViewGroup body = (ViewGroup) container.findViewById(com.android.internal.R.id.body);
LayoutInflater inflater = LayoutInflater.from(getActivity());
Button removeButton = (Button) inflater.inflate(R.layout.trusted_credential_details,
body,
false);
- body.addView(removeButton);
+ if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_CREDENTIALS)) {
+ body.addView(removeButton);
+ }
removeButton.setText(certHolder.mTab.getButtonLabel(certHolder));
removeButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
- if (mUserManager.hasRestrictionsChallenge() && !mChallengeSucceeded) {
- ensurePin();
- return;
- }
-
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage(certHolder.mTab.getButtonConfirmation(certHolder));
builder.setPositiveButton(
@@ -409,68 +686,75 @@ public class TrustedCredentialsSettings extends Fragment {
certDialog.show();
}
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (requestCode == REQUEST_PIN_CHALLENGE) {
- mChallengeRequested = false;
- if (resultCode == Activity.RESULT_OK) {
- mChallengeSucceeded = true;
+ private void addCertChain(final CertHolder certHolder,
+ final ArrayList<View> views, final ArrayList<String> titles) {
+
+ List<X509Certificate> certificates = null;
+ try {
+ KeyChainConnection keyChainConnection = mKeyChainConnectionByProfileId.get(
+ certHolder.mProfileId);
+ IKeyChainService service = keyChainConnection.getService();
+ List<String> chain = service.getCaCertificateChainAliases(certHolder.mAlias, true);
+ final int n = chain.size();
+ certificates = new ArrayList<X509Certificate>(n);
+ for (int i = 0; i < n; ++i) {
+ byte[] encodedCertificate = service.getEncodedCaCertificate(chain.get(i), true);
+ X509Certificate certificate = KeyChain.toCertificate(encodedCertificate);
+ certificates.add(certificate);
}
+ } catch (RemoteException ex) {
+ Log.e(TAG, "RemoteException while retrieving certificate chain for root "
+ + certHolder.mAlias, ex);
return;
}
-
- super.onActivityResult(requestCode, resultCode, data);
- }
-
- private void ensurePin() {
- if (!mChallengeSucceeded) {
- final UserManager um = UserManager.get(getActivity());
- if (!mChallengeRequested) {
- if (um.hasRestrictionsChallenge()) {
- Intent requestPin =
- new Intent(Intent.ACTION_RESTRICTIONS_CHALLENGE);
- startActivityForResult(requestPin, REQUEST_PIN_CHALLENGE);
- mChallengeRequested = true;
- }
- }
+ for (X509Certificate certificate : certificates) {
+ addCertDetails(certificate, views, titles);
}
- mChallengeSucceeded = false;
}
+ private void addCertDetails(X509Certificate certificate, final ArrayList<View> views,
+ final ArrayList<String> titles) {
+ SslCertificate sslCert = new SslCertificate(certificate);
+ views.add(sslCert.inflateCertificateView(getActivity()));
+ titles.add(sslCert.getIssuedTo().getCName());
+ }
private class AliasOperation extends AsyncTask<Void, Void, Boolean> {
private final CertHolder mCertHolder;
+
private AliasOperation(CertHolder certHolder) {
mCertHolder = certHolder;
}
- @Override protected Boolean doInBackground(Void... params) {
+
+ @Override
+ protected Boolean doInBackground(Void... params) {
try {
- KeyChainConnection keyChainConnection = KeyChain.bind(getActivity());
+ KeyChainConnection keyChainConnection = mKeyChainConnectionByProfileId.get(
+ mCertHolder.mProfileId);
IKeyChainService service = keyChainConnection.getService();
- try {
- if (mCertHolder.mDeleted) {
- byte[] bytes = mCertHolder.mX509Cert.getEncoded();
- service.installCaCertificate(bytes);
- return true;
- } else {
- return service.deleteCaCertificate(mCertHolder.mAlias);
- }
- } finally {
- keyChainConnection.close();
+ if (mCertHolder.mDeleted) {
+ byte[] bytes = mCertHolder.mX509Cert.getEncoded();
+ service.installCaCertificate(bytes);
+ return true;
+ } else {
+ return service.deleteCaCertificate(mCertHolder.mAlias);
}
} catch (CertificateEncodingException e) {
+ Log.w(TAG, "Error while toggling alias " + mCertHolder.mAlias,
+ e);
return false;
} catch (IllegalStateException e) {
// used by installCaCertificate to report errors
+ Log.w(TAG, "Error while toggling alias " + mCertHolder.mAlias, e);
return false;
} catch (RemoteException e) {
- return false;
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
+ Log.w(TAG, "Error while toggling alias " + mCertHolder.mAlias, e);
return false;
}
}
- @Override protected void onPostExecute(Boolean ok) {
+
+ @Override
+ protected void onPostExecute(Boolean ok) {
mCertHolder.mTab.postOperationUpdate(ok, mCertHolder);
}
}
diff --git a/src/com/android/settings/UsageAccessSettings.java b/src/com/android/settings/UsageAccessSettings.java
new file mode 100644
index 0000000..89e184e
--- /dev/null
+++ b/src/com/android/settings/UsageAccessSettings.java
@@ -0,0 +1,394 @@
+/*
+ * 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 com.android.internal.content.PackageMonitor;
+
+import android.Manifest;
+import android.app.ActivityThread;
+import android.app.AlertDialog;
+import android.app.AppOpsManager;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.preference.Preference;
+import android.preference.PreferenceScreen;
+import android.preference.SwitchPreference;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import java.util.List;
+
+public class UsageAccessSettings extends SettingsPreferenceFragment implements
+ Preference.OnPreferenceChangeListener {
+
+ private static final String TAG = "UsageAccessSettings";
+
+ private static final String[] PM_USAGE_STATS_PERMISSION = new String[] {
+ Manifest.permission.PACKAGE_USAGE_STATS
+ };
+
+ private static final int[] APP_OPS_OP_CODES = new int[] {
+ AppOpsManager.OP_GET_USAGE_STATS
+ };
+
+ private static class PackageEntry {
+ public PackageEntry(String packageName) {
+ this.packageName = packageName;
+ this.appOpMode = AppOpsManager.MODE_DEFAULT;
+ }
+
+ final String packageName;
+ PackageInfo packageInfo;
+ boolean permissionGranted;
+ int appOpMode;
+
+ SwitchPreference preference;
+ }
+
+ /**
+ * Fetches the list of Apps that are requesting access to the UsageStats API and updates
+ * the PreferenceScreen with the results when complete.
+ */
+ private class AppsRequestingAccessFetcher extends
+ AsyncTask<Void, Void, ArrayMap<String, PackageEntry>> {
+
+ private final Context mContext;
+ private final PackageManager mPackageManager;
+ private final IPackageManager mIPackageManager;
+
+ public AppsRequestingAccessFetcher(Context context) {
+ mContext = context;
+ mPackageManager = context.getPackageManager();
+ mIPackageManager = ActivityThread.getPackageManager();
+ }
+
+ @Override
+ protected ArrayMap<String, PackageEntry> doInBackground(Void... params) {
+ final String[] packages;
+ try {
+ packages = mIPackageManager.getAppOpPermissionPackages(
+ Manifest.permission.PACKAGE_USAGE_STATS);
+ } catch (RemoteException e) {
+ Log.w(TAG, "PackageManager is dead. Can't get list of packages requesting "
+ + Manifest.permission.PACKAGE_USAGE_STATS);
+ return null;
+ }
+
+ if (packages == null) {
+ // No packages are requesting permission to use the UsageStats API.
+ return null;
+ }
+
+ ArrayMap<String, PackageEntry> entries = new ArrayMap<>();
+ for (final String packageName : packages) {
+ if (!shouldIgnorePackage(packageName)) {
+ entries.put(packageName, new PackageEntry(packageName));
+ }
+ }
+
+ // Load the packages that have been granted the PACKAGE_USAGE_STATS permission.
+ final List<PackageInfo> packageInfos = mPackageManager.getPackagesHoldingPermissions(
+ PM_USAGE_STATS_PERMISSION, 0);
+ final int packageInfoCount = packageInfos != null ? packageInfos.size() : 0;
+ for (int i = 0; i < packageInfoCount; i++) {
+ final PackageInfo packageInfo = packageInfos.get(i);
+ final PackageEntry pe = entries.get(packageInfo.packageName);
+ if (pe != null) {
+ pe.packageInfo = packageInfo;
+ pe.permissionGranted = true;
+ }
+ }
+
+ // Load the remaining packages that have requested but don't have the
+ // PACKAGE_USAGE_STATS permission.
+ int packageCount = entries.size();
+ for (int i = 0; i < packageCount; i++) {
+ final PackageEntry pe = entries.valueAt(i);
+ if (pe.packageInfo == null) {
+ try {
+ pe.packageInfo = mPackageManager.getPackageInfo(pe.packageName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ // This package doesn't exist. This may occur when an app is uninstalled for
+ // one user, but it is not removed from the system.
+ entries.removeAt(i);
+ i--;
+ packageCount--;
+ }
+ }
+ }
+
+ // Find out which packages have been granted permission from AppOps.
+ final List<AppOpsManager.PackageOps> packageOps = mAppOpsManager.getPackagesForOps(
+ APP_OPS_OP_CODES);
+ final int packageOpsCount = packageOps != null ? packageOps.size() : 0;
+ for (int i = 0; i < packageOpsCount; i++) {
+ final AppOpsManager.PackageOps packageOp = packageOps.get(i);
+ final PackageEntry pe = entries.get(packageOp.getPackageName());
+ if (pe == null) {
+ Log.w(TAG, "AppOp permission exists for package " + packageOp.getPackageName()
+ + " but package doesn't exist or did not request UsageStats access");
+ continue;
+ }
+
+ if (packageOp.getUid() != pe.packageInfo.applicationInfo.uid) {
+ // This AppOp does not belong to this user.
+ continue;
+ }
+
+ if (packageOp.getOps().size() < 1) {
+ Log.w(TAG, "No AppOps permission exists for package "
+ + packageOp.getPackageName());
+ continue;
+ }
+
+ pe.appOpMode = packageOp.getOps().get(0).getMode();
+ }
+
+ return entries;
+ }
+
+ @Override
+ protected void onPostExecute(ArrayMap<String, PackageEntry> newEntries) {
+ mLastFetcherTask = null;
+
+ if (getActivity() == null) {
+ // We must have finished the Activity while we were processing in the background.
+ return;
+ }
+
+ if (newEntries == null) {
+ mPackageEntryMap.clear();
+ mPreferenceScreen.removeAll();
+ return;
+ }
+
+ // Find the deleted entries and remove them from the PreferenceScreen.
+ final int oldPackageCount = mPackageEntryMap.size();
+ for (int i = 0; i < oldPackageCount; i++) {
+ final PackageEntry oldPackageEntry = mPackageEntryMap.valueAt(i);
+ final PackageEntry newPackageEntry = newEntries.get(oldPackageEntry.packageName);
+ if (newPackageEntry == null) {
+ // This package has been removed.
+ mPreferenceScreen.removePreference(oldPackageEntry.preference);
+ } else {
+ // This package already exists in the preference hierarchy, so reuse that
+ // Preference.
+ newPackageEntry.preference = oldPackageEntry.preference;
+ }
+ }
+
+ // Now add new packages to the PreferenceScreen.
+ final int packageCount = newEntries.size();
+ for (int i = 0; i < packageCount; i++) {
+ final PackageEntry packageEntry = newEntries.valueAt(i);
+ if (packageEntry.preference == null) {
+ packageEntry.preference = new SwitchPreference(mContext);
+ packageEntry.preference.setPersistent(false);
+ packageEntry.preference.setOnPreferenceChangeListener(UsageAccessSettings.this);
+ mPreferenceScreen.addPreference(packageEntry.preference);
+ }
+ updatePreference(packageEntry);
+ }
+
+ mPackageEntryMap.clear();
+ mPackageEntryMap = newEntries;
+ }
+
+ private void updatePreference(PackageEntry pe) {
+ pe.preference.setIcon(pe.packageInfo.applicationInfo.loadIcon(mPackageManager));
+ pe.preference.setTitle(pe.packageInfo.applicationInfo.loadLabel(mPackageManager));
+ pe.preference.setKey(pe.packageName);
+
+ boolean check = false;
+ if (pe.appOpMode == AppOpsManager.MODE_ALLOWED) {
+ check = true;
+ } else if (pe.appOpMode == AppOpsManager.MODE_DEFAULT) {
+ // If the default AppOps mode is set, then fall back to
+ // whether the app has been granted permission by PackageManager.
+ check = pe.permissionGranted;
+ }
+
+ if (check != pe.preference.isChecked()) {
+ pe.preference.setChecked(check);
+ }
+ }
+ }
+
+ static boolean shouldIgnorePackage(String packageName) {
+ return packageName.equals("android") || packageName.equals("com.android.settings");
+ }
+
+ private AppsRequestingAccessFetcher mLastFetcherTask;
+ ArrayMap<String, PackageEntry> mPackageEntryMap = new ArrayMap<>();
+ AppOpsManager mAppOpsManager;
+ PreferenceScreen mPreferenceScreen;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ addPreferencesFromResource(R.xml.usage_access_settings);
+ mPreferenceScreen = getPreferenceScreen();
+ mPreferenceScreen.setOrderingAsAdded(false);
+ mAppOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ updateInterestedApps();
+ mPackageMonitor.register(getActivity(), Looper.getMainLooper(), false);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+
+ mPackageMonitor.unregister();
+ if (mLastFetcherTask != null) {
+ mLastFetcherTask.cancel(true);
+ mLastFetcherTask = null;
+ }
+ }
+
+ private void updateInterestedApps() {
+ if (mLastFetcherTask != null) {
+ // Canceling can only fail for some obscure reason since mLastFetcherTask would be
+ // null if the task has already completed. So we ignore the result of cancel and
+ // spawn a new task to get fresh data. AsyncTask executes tasks serially anyways,
+ // so we are safe from running two tasks at the same time.
+ mLastFetcherTask.cancel(true);
+ }
+
+ mLastFetcherTask = new AppsRequestingAccessFetcher(getActivity());
+ mLastFetcherTask.execute();
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ final String packageName = preference.getKey();
+ final PackageEntry pe = mPackageEntryMap.get(packageName);
+ if (pe == null) {
+ Log.w(TAG, "Preference change event for package " + packageName
+ + " but that package is no longer valid.");
+ return false;
+ }
+
+ if (!(newValue instanceof Boolean)) {
+ Log.w(TAG, "Preference change event for package " + packageName
+ + " had non boolean value of type " + newValue.getClass().getName());
+ return false;
+ }
+
+ final int newMode = (Boolean) newValue ?
+ AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED;
+
+ // Check if we need to do any work.
+ if (pe.appOpMode != newMode) {
+ if (newMode != AppOpsManager.MODE_ALLOWED) {
+ // Turning off the setting has no warning.
+ setNewMode(pe, newMode);
+ return true;
+ }
+
+ // Turning on the setting has a Warning.
+ FragmentTransaction ft = getChildFragmentManager().beginTransaction();
+ Fragment prev = getChildFragmentManager().findFragmentByTag("warning");
+ if (prev != null) {
+ ft.remove(prev);
+ }
+ WarningDialogFragment.newInstance(pe.packageName).show(ft, "warning");
+ return false;
+ }
+ return true;
+ }
+
+ void setNewMode(PackageEntry pe, int newMode) {
+ mAppOpsManager.setMode(AppOpsManager.OP_GET_USAGE_STATS,
+ pe.packageInfo.applicationInfo.uid, pe.packageName, newMode);
+ pe.appOpMode = newMode;
+ }
+
+ void allowAccess(String packageName) {
+ final PackageEntry entry = mPackageEntryMap.get(packageName);
+ if (entry == null) {
+ Log.w(TAG, "Unable to give access to package " + packageName + ": it does not exist.");
+ return;
+ }
+
+ setNewMode(entry, AppOpsManager.MODE_ALLOWED);
+ entry.preference.setChecked(true);
+ }
+
+ private final PackageMonitor mPackageMonitor = new PackageMonitor() {
+ @Override
+ public void onPackageAdded(String packageName, int uid) {
+ updateInterestedApps();
+ }
+
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ updateInterestedApps();
+ }
+ };
+
+ public static class WarningDialogFragment extends DialogFragment
+ implements DialogInterface.OnClickListener {
+ private static final String ARG_PACKAGE_NAME = "package";
+
+ public static WarningDialogFragment newInstance(String packageName) {
+ WarningDialogFragment dialog = new WarningDialogFragment();
+ Bundle args = new Bundle();
+ args.putString(ARG_PACKAGE_NAME, packageName);
+ dialog.setArguments(args);
+ return dialog;
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ return new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.allow_usage_access_title)
+ .setMessage(R.string.allow_usage_access_message)
+ .setIconAttribute(android.R.attr.alertDialogIcon)
+ .setNegativeButton(R.string.cancel, this)
+ .setPositiveButton(android.R.string.ok, this)
+ .create();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ ((UsageAccessSettings) getParentFragment()).allowAccess(
+ getArguments().getString(ARG_PACKAGE_NAME));
+ } else {
+ dialog.cancel();
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/UsageStats.java b/src/com/android/settings/UsageStatsActivity.java
index f67eeec..90aec5b 100755
--- a/src/com/android/settings/UsageStats.java
+++ b/src/com/android/settings/UsageStatsActivity.java
@@ -1,5 +1,3 @@
-
-
/**
* Copyright (C) 2007 The Android Open Source Project
*
@@ -18,24 +16,25 @@
package com.android.settings;
-import com.android.internal.app.IUsageStats;
-import com.android.settings.R;
import android.app.Activity;
+import android.app.usage.UsageStats;
+import android.app.usage.UsageStatsManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import com.android.internal.os.PkgUsageStats;
+
+import java.text.DateFormat;
import java.util.ArrayList;
+import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import android.text.format.DateUtils;
+import android.util.ArrayMap;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -50,111 +49,121 @@ import android.widget.AdapterView.OnItemSelectedListener;
/**
* Activity to display package usage statistics.
*/
-public class UsageStats extends Activity implements OnItemSelectedListener {
- private static final String TAG="UsageStatsActivity";
+public class UsageStatsActivity extends Activity implements OnItemSelectedListener {
+ private static final String TAG = "UsageStatsActivity";
private static final boolean localLOGV = false;
- private Spinner mTypeSpinner;
- private ListView mListView;
- private IUsageStats mUsageStatsService;
+ private UsageStatsManager mUsageStatsManager;
private LayoutInflater mInflater;
private UsageStatsAdapter mAdapter;
private PackageManager mPm;
-
- public static class AppNameComparator implements Comparator<PkgUsageStats> {
- Map<String, CharSequence> mAppLabelList;
- AppNameComparator(Map<String, CharSequence> appList) {
+
+ public static class AppNameComparator implements Comparator<UsageStats> {
+ private Map<String, String> mAppLabelList;
+
+ AppNameComparator(Map<String, String> appList) {
mAppLabelList = appList;
}
- public final int compare(PkgUsageStats a, PkgUsageStats b) {
- String alabel = mAppLabelList.get(a.packageName).toString();
- String blabel = mAppLabelList.get(b.packageName).toString();
+
+ @Override
+ public final int compare(UsageStats a, UsageStats b) {
+ String alabel = mAppLabelList.get(a.getPackageName());
+ String blabel = mAppLabelList.get(b.getPackageName());
return alabel.compareTo(blabel);
}
}
-
- public static class LaunchCountComparator implements Comparator<PkgUsageStats> {
- public final int compare(PkgUsageStats a, PkgUsageStats b) {
+
+ public static class LastTimeUsedComparator implements Comparator<UsageStats> {
+ @Override
+ public final int compare(UsageStats a, UsageStats b) {
// return by descending order
- return b.launchCount - a.launchCount;
+ return (int)(b.getLastTimeUsed() - a.getLastTimeUsed());
}
}
-
- public static class UsageTimeComparator implements Comparator<PkgUsageStats> {
- public final int compare(PkgUsageStats a, PkgUsageStats b) {
- long ret = a.usageTime-b.usageTime;
- if (ret == 0) {
- return 0;
- }
- if (ret < 0) {
- return 1;
- }
- return -1;
+
+ public static class UsageTimeComparator implements Comparator<UsageStats> {
+ @Override
+ public final int compare(UsageStats a, UsageStats b) {
+ return (int)(b.getTotalTimeInForeground() - a.getTotalTimeInForeground());
}
}
-
- // View Holder used when displaying views
+
+ // View Holder used when displaying views
static class AppViewHolder {
TextView pkgName;
- TextView launchCount;
+ TextView lastTimeUsed;
TextView usageTime;
}
-
+
class UsageStatsAdapter extends BaseAdapter {
// Constants defining order for display order
private static final int _DISPLAY_ORDER_USAGE_TIME = 0;
- private static final int _DISPLAY_ORDER_LAUNCH_COUNT = 1;
+ private static final int _DISPLAY_ORDER_LAST_TIME_USED = 1;
private static final int _DISPLAY_ORDER_APP_NAME = 2;
-
+
private int mDisplayOrder = _DISPLAY_ORDER_USAGE_TIME;
- private List<PkgUsageStats> mUsageStats;
- private LaunchCountComparator mLaunchCountComparator;
- private UsageTimeComparator mUsageTimeComparator;
+ private LastTimeUsedComparator mLastTimeUsedComparator = new LastTimeUsedComparator();
+ private UsageTimeComparator mUsageTimeComparator = new UsageTimeComparator();
private AppNameComparator mAppLabelComparator;
- private HashMap<String, CharSequence> mAppLabelMap;
-
+ private final ArrayMap<String, String> mAppLabelMap = new ArrayMap<>();
+ private final ArrayList<UsageStats> mPackageStats = new ArrayList<>();
+
UsageStatsAdapter() {
- mUsageStats = new ArrayList<PkgUsageStats>();
- mAppLabelMap = new HashMap<String, CharSequence>();
- PkgUsageStats[] stats;
- try {
- stats = mUsageStatsService.getAllPkgUsageStats();
- } catch (RemoteException e) {
- Log.e(TAG, "Failed initializing usage stats service");
+ Calendar cal = Calendar.getInstance();
+ cal.add(Calendar.DAY_OF_YEAR, -5);
+
+ final List<UsageStats> stats =
+ mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_BEST,
+ cal.getTimeInMillis(), System.currentTimeMillis());
+ if (stats == null) {
return;
}
- if (stats == null) {
- return;
- }
- for (PkgUsageStats ps : stats) {
- mUsageStats.add(ps);
- // load application labels for each application
- CharSequence label;
- try {
- ApplicationInfo appInfo = mPm.getApplicationInfo(ps.packageName, 0);
- label = appInfo.loadLabel(mPm);
+
+ ArrayMap<String, UsageStats> map = new ArrayMap<>();
+ final int statCount = stats.size();
+ for (int i = 0; i < statCount; i++) {
+ final android.app.usage.UsageStats pkgStats = stats.get(i);
+
+ // load application labels for each application
+ try {
+ ApplicationInfo appInfo = mPm.getApplicationInfo(pkgStats.getPackageName(), 0);
+ String label = appInfo.loadLabel(mPm).toString();
+ mAppLabelMap.put(pkgStats.getPackageName(), label);
+
+ UsageStats existingStats =
+ map.get(pkgStats.getPackageName());
+ if (existingStats == null) {
+ map.put(pkgStats.getPackageName(), pkgStats);
+ } else {
+ existingStats.add(pkgStats);
+ }
+
} catch (NameNotFoundException e) {
- label = ps.packageName;
+ // This package may be gone.
}
- mAppLabelMap.put(ps.packageName, label);
- }
- // Sort list
- mLaunchCountComparator = new LaunchCountComparator();
- mUsageTimeComparator = new UsageTimeComparator();
- mAppLabelComparator = new AppNameComparator(mAppLabelMap);
- sortList();
+ }
+ mPackageStats.addAll(map.values());
+
+ // Sort list
+ mAppLabelComparator = new AppNameComparator(mAppLabelMap);
+ sortList();
}
+
+ @Override
public int getCount() {
- return mUsageStats.size();
+ return mPackageStats.size();
}
+ @Override
public Object getItem(int position) {
- return mUsageStats.get(position);
+ return mPackageStats.get(position);
}
+ @Override
public long getItemId(int position) {
return position;
}
+ @Override
public View getView(int position, View convertView, ViewGroup parent) {
// A ViewHolder keeps references to children views to avoid unneccessary calls
// to findViewById() on each row.
@@ -170,7 +179,7 @@ public class UsageStats extends Activity implements OnItemSelectedListener {
// we want to bind data to.
holder = new AppViewHolder();
holder.pkgName = (TextView) convertView.findViewById(R.id.package_name);
- holder.launchCount = (TextView) convertView.findViewById(R.id.launch_count);
+ holder.lastTimeUsed = (TextView) convertView.findViewById(R.id.last_time_used);
holder.usageTime = (TextView) convertView.findViewById(R.id.usage_time);
convertView.setTag(holder);
} else {
@@ -180,18 +189,20 @@ public class UsageStats extends Activity implements OnItemSelectedListener {
}
// Bind the data efficiently with the holder
- PkgUsageStats pkgStats = mUsageStats.get(position);
+ UsageStats pkgStats = mPackageStats.get(position);
if (pkgStats != null) {
- CharSequence label = mAppLabelMap.get(pkgStats.packageName);
+ String label = mAppLabelMap.get(pkgStats.getPackageName());
holder.pkgName.setText(label);
- holder.launchCount.setText(String.valueOf(pkgStats.launchCount));
- holder.usageTime.setText(String.valueOf(pkgStats.usageTime)+" ms");
+ holder.lastTimeUsed.setText(DateUtils.formatSameDayTime(pkgStats.getLastTimeUsed(),
+ System.currentTimeMillis(), DateFormat.MEDIUM, DateFormat.MEDIUM));
+ holder.usageTime.setText(
+ DateUtils.formatElapsedTime(pkgStats.getTotalTimeInForeground() / 1000));
} else {
Log.w(TAG, "No usage stats info for package:" + position);
}
return convertView;
}
-
+
void sortList(int sortOrder) {
if (mDisplayOrder == sortOrder) {
// do nothing
@@ -203,47 +214,43 @@ public class UsageStats extends Activity implements OnItemSelectedListener {
private void sortList() {
if (mDisplayOrder == _DISPLAY_ORDER_USAGE_TIME) {
if (localLOGV) Log.i(TAG, "Sorting by usage time");
- Collections.sort(mUsageStats, mUsageTimeComparator);
- } else if (mDisplayOrder == _DISPLAY_ORDER_LAUNCH_COUNT) {
- if (localLOGV) Log.i(TAG, "Sorting launch count");
- Collections.sort(mUsageStats, mLaunchCountComparator);
+ Collections.sort(mPackageStats, mUsageTimeComparator);
+ } else if (mDisplayOrder == _DISPLAY_ORDER_LAST_TIME_USED) {
+ if (localLOGV) Log.i(TAG, "Sorting by last time used");
+ Collections.sort(mPackageStats, mLastTimeUsedComparator);
} else if (mDisplayOrder == _DISPLAY_ORDER_APP_NAME) {
if (localLOGV) Log.i(TAG, "Sorting by application name");
- Collections.sort(mUsageStats, mAppLabelComparator);
+ Collections.sort(mPackageStats, mAppLabelComparator);
}
notifyDataSetChanged();
}
}
/** Called when the activity is first created. */
+ @Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
- mUsageStatsService = IUsageStats.Stub.asInterface(ServiceManager.getService("usagestats"));
- if (mUsageStatsService == null) {
- Log.e(TAG, "Failed to retrieve usagestats service");
- return;
- }
+ setContentView(R.layout.usage_stats);
+
+ mUsageStatsManager = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE);
mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mPm = getPackageManager();
-
- setContentView(R.layout.usage_stats);
- mTypeSpinner = (Spinner) findViewById(R.id.typeSpinner);
- mTypeSpinner.setOnItemSelectedListener(this);
-
- mListView = (ListView) findViewById(R.id.pkg_list);
- // Initialize the inflater
-
+
+ Spinner typeSpinner = (Spinner) findViewById(R.id.typeSpinner);
+ typeSpinner.setOnItemSelectedListener(this);
+
+ ListView listView = (ListView) findViewById(R.id.pkg_list);
mAdapter = new UsageStatsAdapter();
- mListView.setAdapter(mAdapter);
+ listView.setAdapter(mAdapter);
}
- public void onItemSelected(AdapterView<?> parent, View view, int position,
- long id) {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
mAdapter.sortList(position);
}
+ @Override
public void onNothingSelected(AdapterView<?> parent) {
// do nothing
}
}
-
diff --git a/src/com/android/settings/UserDictionarySettings.java b/src/com/android/settings/UserDictionarySettings.java
index da12004..1e9fd0a 100644
--- a/src/com/android/settings/UserDictionarySettings.java
+++ b/src/com/android/settings/UserDictionarySettings.java
@@ -192,9 +192,8 @@ public class UserDictionarySettings extends ListFragment {
args.putString(UserDictionaryAddWordContents.EXTRA_WORD, editingWord);
args.putString(UserDictionaryAddWordContents.EXTRA_SHORTCUT, editingShortcut);
args.putString(UserDictionaryAddWordContents.EXTRA_LOCALE, mLocale);
- android.preference.PreferenceActivity pa =
- (android.preference.PreferenceActivity)getActivity();
- pa.startPreferencePanel(
+ SettingsActivity sa = (SettingsActivity) getActivity();
+ sa.startPreferencePanel(
com.android.settings.inputmethod.UserDictionaryAddWordFragment.class.getName(),
args, R.string.user_dict_settings_add_dialog_title, null, null, 0);
}
diff --git a/src/com/android/settings/UserSpinnerAdapter.java b/src/com/android/settings/UserSpinnerAdapter.java
new file mode 100644
index 0000000..001dfc4
--- /dev/null
+++ b/src/com/android/settings/UserSpinnerAdapter.java
@@ -0,0 +1,148 @@
+/*
+ * 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.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.database.DataSetObserver;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.SpinnerAdapter;
+import android.widget.TextView;
+
+import com.android.internal.util.UserIcons;
+
+import java.util.ArrayList;
+
+/**
+ * Adapter for a spinner that shows a list of users.
+ */
+public class UserSpinnerAdapter implements SpinnerAdapter {
+ // TODO: Update UI. See: http://b/16518801
+ /** Holder for user details */
+ public static class UserDetails {
+ private final UserHandle mUserHandle;
+ private final String name;
+ private final Drawable icon;
+
+ public UserDetails(UserHandle userHandle, UserManager um, Context context) {
+ mUserHandle = userHandle;
+ UserInfo userInfo = um.getUserInfo(mUserHandle.getIdentifier());
+ if (userInfo.isManagedProfile()) {
+ name = context.getString(R.string.managed_user_title);
+ icon = Resources.getSystem().getDrawable(
+ com.android.internal.R.drawable.ic_corp_icon);
+ } else {
+ name = userInfo.name;
+ final int userId = userInfo.id;
+ if (um.getUserIcon(userId) != null) {
+ icon = new BitmapDrawable(context.getResources(), um.getUserIcon(userId));
+ } else {
+ icon = UserIcons.getDefaultUserIcon(userId, /* light= */ false);
+ }
+ }
+ }
+ }
+ private ArrayList<UserDetails> data;
+ private final LayoutInflater mInflater;
+
+ public UserSpinnerAdapter(Context context, ArrayList<UserDetails> users) {
+ if (users == null) {
+ throw new IllegalArgumentException("A list of user details must be provided");
+ }
+ this.data = users;
+ mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ }
+
+ public UserHandle getUserHandle(int position) {
+ if (position < 0 || position >= data.size()) {
+ return null;
+ }
+ return data.get(position).mUserHandle;
+ }
+
+ @Override
+ public View getDropDownView(int position, View convertView, ViewGroup parent) {
+ final View row = convertView != null ? convertView : createUser(parent);
+
+ UserDetails user = data.get(position);
+ ((ImageView) row.findViewById(android.R.id.icon)).setImageDrawable(user.icon);
+ ((TextView) row.findViewById(android.R.id.title)).setText(user.name);
+ return row;
+ }
+
+ private View createUser(ViewGroup parent) {
+ return mInflater.inflate(R.layout.user_preference, parent, false);
+ }
+
+ @Override
+ public void registerDataSetObserver(DataSetObserver observer) {
+ // We don't support observers
+ }
+
+ @Override
+ public void unregisterDataSetObserver(DataSetObserver observer) {
+ // We don't support observers
+ }
+
+ @Override
+ public int getCount() {
+ return data.size();
+ }
+
+ @Override
+ public UserDetails getItem(int position) {
+ return data.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return data.get(position).mUserHandle.getIdentifier();
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return false;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ return getDropDownView(position, convertView, parent);
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return 0;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 1;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return data.isEmpty();
+ }
+} \ No newline at end of file
diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java
index b9e729c..08cfc58 100644
--- a/src/com/android/settings/Utils.java
+++ b/src/com/android/settings/Utils.java
@@ -16,18 +16,25 @@
package com.android.settings;
-import android.app.Activity;
+import static android.content.Intent.EXTRA_USER;
+
+import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
import android.app.AlertDialog;
import android.app.Dialog;
+import android.app.Fragment;
+import android.app.IActivityManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
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.content.pm.Signature;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
@@ -40,21 +47,22 @@ import android.net.LinkProperties;
import android.net.Uri;
import android.os.BatteryManager;
import android.os.Bundle;
-import android.os.ParcelFileDescriptor;
+import android.os.IBinder;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.preference.Preference;
-import android.preference.PreferenceActivity.Header;
import android.preference.PreferenceFrameLayout;
import android.preference.PreferenceGroup;
import android.provider.ContactsContract.CommonDataKinds;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.Profile;
import android.provider.ContactsContract.RawContacts;
+import android.service.persistentdata.PersistentDataBlockManager;
import android.telephony.TelephonyManager;
+import android.text.BidiFormatter;
+import android.text.TextDirectionHeuristics;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
@@ -62,17 +70,24 @@ import android.view.ViewGroup;
import android.widget.ListView;
import android.widget.TabWidget;
-import com.android.settings.users.ProfileUpdateReceiver;
+import com.android.internal.util.ImageUtils;
+import com.android.internal.util.UserIcons;
+import com.android.settings.UserSpinnerAdapter.UserDetails;
+import com.android.settings.dashboard.DashboardCategory;
+import com.android.settings.dashboard.DashboardTile;
+import com.android.settings.drawable.CircleFramedDrawable;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
+import java.text.NumberFormat;
+import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
-public class Utils {
+public final class Utils {
+ private static final String TAG = "Settings";
/**
* Set the preference's title to the matching activity's label.
@@ -85,6 +100,15 @@ public class Utils {
public static final float DISABLED_ALPHA = 0.4f;
/**
+ * Color spectrum to use to indicate badness. 0 is completely transparent (no data),
+ * 1 is most bad (red), the last value is least bad (green).
+ */
+ public static final int[] BADNESS_COLORS = new int[] {
+ 0x00000000, 0xffc43828, 0xffe54918, 0xfff47b00,
+ 0xfffabf2c, 0xff679e37, 0xff0a7f42
+ };
+
+ /**
* Name of the meta-data item that should be set in the AndroidManifest.xml
* to specify the icon that should be displayed for the preference.
*/
@@ -102,6 +126,12 @@ public class Utils {
*/
private static final String META_DATA_PREFERENCE_SUMMARY = "com.android.settings.summary";
+ private static final String SETTINGS_PACKAGE_NAME = "com.android.settings";
+
+ private static final int SECONDS_PER_MINUTE = 60;
+ private static final int SECONDS_PER_HOUR = 60 * 60;
+ private static final int SECONDS_PER_DAY = 24 * 60 * 60;
+
/**
* Finds a matching activity for a preference's intent. If a matching
* activity is not found, it will remove the preference.
@@ -157,98 +187,10 @@ public 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) {
-
- IconPreferenceScreen preference = (IconPreferenceScreen)parentPreferenceGroup
- .findPreference(preferenceKey);
- if (preference == null) {
- return false;
- }
+ public static boolean updateTileToSpecificActivityFromMetaDataOrRemove(Context context,
+ DashboardTile tile) {
- 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) {
- 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.setIcon(icon);
- preference.setTitle(title);
- preference.setSummary(summary);
-
- // 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 updateHeaderToSpecificActivityFromMetaDataOrRemove(Context context,
- List<Header> target, Header header) {
-
- Intent intent = header.intent;
+ Intent intent = tile.intent;
if (intent != null) {
// Find the activity that is in the system image
PackageManager pm = context.getPackageManager();
@@ -287,11 +229,11 @@ public class Utils {
// Set icon, title and summary for the preference
// TODO:
- //header.icon = icon;
- header.title = title;
- header.summary = summary;
+ //tile.icon = icon;
+ tile.title = title;
+ tile.summary = summary;
// Replace the intent with this specific activity
- header.intent = new Intent().setClassName(resolveInfo.activityInfo.packageName,
+ tile.intent = new Intent().setClassName(resolveInfo.activityInfo.packageName,
resolveInfo.activityInfo.name);
return true;
@@ -299,9 +241,6 @@ public class Utils {
}
}
- // Did not find a matching activity, so remove the preference
- target.remove(header);
-
return false;
}
@@ -384,14 +323,34 @@ public class Utils {
}
}
+ /** Formats the ratio of amount/total as a percentage. */
+ public static String formatPercentage(long amount, long total) {
+ return formatPercentage(((double) amount) / total);
+ }
+
+ /** Formats an integer from 0..100 as a percentage. */
+ public static String formatPercentage(int percentage) {
+ return formatPercentage(((double) percentage) / 100.0);
+ }
+
+ /** Formats a double from 0.0..1.0 as a percentage. */
+ private static String formatPercentage(double percentage) {
+ BidiFormatter bf = BidiFormatter.getInstance();
+ return bf.unicodeWrap(NumberFormat.getPercentInstance().format(percentage));
+ }
+
public static boolean isBatteryPresent(Intent batteryChangedIntent) {
return batteryChangedIntent.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true);
}
public static String getBatteryPercentage(Intent batteryChangedIntent) {
+ return formatPercentage(getBatteryLevel(batteryChangedIntent));
+ }
+
+ public static int getBatteryLevel(Intent batteryChangedIntent) {
int level = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
int scale = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 100);
- return String.valueOf(level * 100 / scale) + "%";
+ return (level * 100) / scale;
}
public static String getBatteryStatus(Resources res, Intent batteryChangedIntent) {
@@ -402,18 +361,17 @@ public class Utils {
BatteryManager.BATTERY_STATUS_UNKNOWN);
String statusString;
if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
- statusString = res.getString(R.string.battery_info_status_charging);
- if (plugType > 0) {
- int resId;
- if (plugType == BatteryManager.BATTERY_PLUGGED_AC) {
- resId = R.string.battery_info_status_charging_ac;
- } else if (plugType == BatteryManager.BATTERY_PLUGGED_USB) {
- resId = R.string.battery_info_status_charging_usb;
- } else {
- resId = R.string.battery_info_status_charging_wireless;
- }
- statusString = statusString + " " + res.getString(resId);
+ int resId;
+ if (plugType == BatteryManager.BATTERY_PLUGGED_AC) {
+ resId = R.string.battery_info_status_charging_ac;
+ } else if (plugType == BatteryManager.BATTERY_PLUGGED_USB) {
+ resId = R.string.battery_info_status_charging_usb;
+ } else if (plugType == BatteryManager.BATTERY_PLUGGED_WIRELESS) {
+ resId = R.string.battery_info_status_charging_wireless;
+ } 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) {
@@ -442,19 +400,35 @@ public class Utils {
public static void prepareCustomPreferencesList(
ViewGroup parent, View child, View list, boolean ignoreSidePadding) {
final boolean movePadding = list.getScrollBarStyle() == View.SCROLLBARS_OUTSIDE_OVERLAY;
- if (movePadding && parent instanceof PreferenceFrameLayout) {
- ((PreferenceFrameLayout.LayoutParams) child.getLayoutParams()).removeBorders = true;
-
+ if (movePadding) {
final Resources res = list.getResources();
final int paddingSide = res.getDimensionPixelSize(R.dimen.settings_side_margin);
final int paddingBottom = res.getDimensionPixelSize(
com.android.internal.R.dimen.preference_fragment_padding_bottom);
- final int effectivePaddingSide = ignoreSidePadding ? 0 : paddingSide;
- list.setPaddingRelative(effectivePaddingSide, 0, effectivePaddingSide, paddingBottom);
+ if (parent instanceof PreferenceFrameLayout) {
+ ((PreferenceFrameLayout.LayoutParams) child.getLayoutParams()).removeBorders = true;
+
+ final int effectivePaddingSide = ignoreSidePadding ? 0 : paddingSide;
+ list.setPaddingRelative(effectivePaddingSide, 0, effectivePaddingSide, paddingBottom);
+ } else {
+ list.setPaddingRelative(paddingSide, 0, paddingSide, paddingBottom);
+ }
}
}
+ public static void forceCustomPadding(View view, boolean additive) {
+ final Resources res = view.getResources();
+ final int paddingSide = res.getDimensionPixelSize(R.dimen.settings_side_margin);
+
+ final int paddingStart = paddingSide + (additive ? view.getPaddingStart() : 0);
+ final int paddingEnd = paddingSide + (additive ? view.getPaddingEnd() : 0);
+ final int paddingBottom = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.preference_fragment_padding_bottom);
+
+ view.setPaddingRelative(paddingStart, 0, paddingEnd, paddingBottom);
+ }
+
/**
* Return string resource that best describes combination of tethering
* options available on this device.
@@ -601,4 +575,393 @@ public class Utils {
return ((UserManager) context.getSystemService(Context.USER_SERVICE))
.getUsers().size() > 1;
}
+
+ /**
+ * 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
+ * entire activity.
+ *
+ * @param context The context.
+ * @param fragmentName The name of the fragment to display.
+ * @param args Optional arguments to supply to the fragment.
+ * @param resultTo Option fragment that should receive the result of the activity launch.
+ * @param resultRequestCode If resultTo is non-null, this is the request code in which
+ * to report the result.
+ * @param titleResId resource id for the String to display for the title of this set
+ * of preferences.
+ * @param title String to display for the title of this set of preferences.
+ */
+ public static void startWithFragment(Context context, String fragmentName, Bundle args,
+ Fragment resultTo, int resultRequestCode, int titleResId, CharSequence title) {
+ startWithFragment(context, fragmentName, args, resultTo, resultRequestCode,
+ titleResId, title, false /* not a shortcut */);
+ }
+
+ public static void startWithFragment(Context context, String fragmentName, Bundle args,
+ Fragment resultTo, int resultRequestCode, int titleResId, CharSequence title,
+ boolean isShortcut) {
+ Intent intent = onBuildStartFragmentIntent(context, fragmentName, args, titleResId,
+ title, isShortcut);
+ if (resultTo == null) {
+ context.startActivity(intent);
+ } else {
+ resultTo.startActivityForResult(intent, resultRequestCode);
+ }
+ }
+
+ public static void startWithFragmentAsUser(Context context, String fragmentName, Bundle args,
+ int titleResId, CharSequence title, boolean isShortcut, UserHandle userHandle) {
+ Intent intent = onBuildStartFragmentIntent(context, fragmentName, args, titleResId,
+ title, isShortcut);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ context.startActivityAsUser(intent, userHandle);
+ }
+
+ /**
+ * Build an Intent to launch a new activity showing the selected fragment.
+ * The implementation constructs an Intent that re-launches the current activity with the
+ * appropriate arguments to display the fragment.
+ *
+ *
+ * @param context The Context.
+ * @param fragmentName The name of the fragment to display.
+ * @param args Optional arguments to supply to the fragment.
+ * @param titleResId Optional title resource id to show for this item.
+ * @param title Optional title to show for this item.
+ * @param isShortcut tell if this is a Launcher Shortcut or not
+ * @return Returns an Intent that can be launched to display the given
+ * fragment.
+ */
+ public static Intent onBuildStartFragmentIntent(Context context, String fragmentName,
+ Bundle args, int titleResId, CharSequence title, boolean isShortcut) {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ 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_RESID, titleResId);
+ intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE, title);
+ intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_AS_SHORTCUT, isShortcut);
+ return intent;
+ }
+
+ /**
+ * Returns the managed profile of the current user or null if none found.
+ */
+ public static UserHandle getManagedProfile(UserManager userManager) {
+ List<UserHandle> userProfiles = userManager.getUserProfiles();
+ final int count = userProfiles.size();
+ for (int i = 0; i < count; i++) {
+ final UserHandle profile = userProfiles.get(i);
+ if (profile.getIdentifier() == userManager.getUserHandle()) {
+ continue;
+ }
+ final UserInfo userInfo = userManager.getUserInfo(profile.getIdentifier());
+ if (userInfo.isManagedProfile()) {
+ return profile;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns true if the current profile is a managed one.
+ */
+ public static boolean isManagedProfile(UserManager userManager) {
+ UserInfo currentUser = userManager.getUserInfo(userManager.getUserHandle());
+ return currentUser.isManagedProfile();
+ }
+
+ /**
+ * Creates a {@link UserSpinnerAdapter} if there is more than one profile on the device.
+ *
+ * <p> The adapter can be used to populate a spinner that switches between the Settings
+ * app on the different profiles.
+ *
+ * @return a {@link UserSpinnerAdapter} or null if there is only one profile.
+ */
+ public static UserSpinnerAdapter createUserSpinnerAdapter(UserManager userManager,
+ Context context) {
+ List<UserHandle> userProfiles = userManager.getUserProfiles();
+ if (userProfiles.size() < 2) {
+ return null;
+ }
+
+ UserHandle myUserHandle = new UserHandle(UserHandle.myUserId());
+ // The first option should be the current profile
+ userProfiles.remove(myUserHandle);
+ userProfiles.add(0, myUserHandle);
+
+ ArrayList<UserDetails> userDetails = new ArrayList<UserDetails>(userProfiles.size());
+ final int count = userProfiles.size();
+ for (int i = 0; i < count; i++) {
+ userDetails.add(new UserDetails(userProfiles.get(i), userManager, context));
+ }
+ return new UserSpinnerAdapter(context, userDetails);
+ }
+
+ /**
+ * Returns the target user for a Settings activity.
+ *
+ * The target user can be either the current user, the user that launched this activity or
+ * the user contained as an extra in the arguments or intent extras.
+ *
+ * Note: This is secure in the sense that it only returns a target user different to the current
+ * one if the app launching this activity is the Settings app itself, running in the same user
+ * or in one that is in the same profile group, or if the user id is provided by the system.
+ */
+ public static UserHandle getSecureTargetUser(IBinder activityToken,
+ UserManager um, @Nullable Bundle arguments, @Nullable Bundle intentExtras) {
+ UserHandle currentUser = new UserHandle(UserHandle.myUserId());
+ IActivityManager am = ActivityManagerNative.getDefault();
+ try {
+ String launchedFromPackage = am.getLaunchedFromPackage(activityToken);
+ boolean launchedFromSettingsApp = SETTINGS_PACKAGE_NAME.equals(launchedFromPackage);
+
+ UserHandle launchedFromUser = new UserHandle(UserHandle.getUserId(
+ am.getLaunchedFromUid(activityToken)));
+ if (launchedFromUser != null && !launchedFromUser.equals(currentUser)) {
+ // Check it's secure
+ if (isProfileOf(um, launchedFromUser)) {
+ return launchedFromUser;
+ }
+ }
+ UserHandle extrasUser = intentExtras != null
+ ? (UserHandle) intentExtras.getParcelable(EXTRA_USER) : null;
+ if (extrasUser != null && !extrasUser.equals(currentUser)) {
+ // Check it's secure
+ if (launchedFromSettingsApp && isProfileOf(um, extrasUser)) {
+ return extrasUser;
+ }
+ }
+ UserHandle argumentsUser = arguments != null
+ ? (UserHandle) arguments.getParcelable(EXTRA_USER) : null;
+ if (argumentsUser != null && !argumentsUser.equals(currentUser)) {
+ // Check it's secure
+ if (launchedFromSettingsApp && isProfileOf(um, argumentsUser)) {
+ return argumentsUser;
+ }
+ }
+ } catch (RemoteException e) {
+ // Should not happen
+ Log.v(TAG, "Could not talk to activity manager.", e);
+ }
+ return currentUser;
+ }
+
+ /**
+ * Returns the target user for a Settings activity.
+ *
+ * The target user can be either the current user, the user that launched this activity or
+ * the user contained as an extra in the arguments or intent extras.
+ *
+ * You should use {@link #getSecureTargetUser(IBinder, UserManager, Bundle, Bundle)} if
+ * possible.
+ *
+ * @see #getInsecureTargetUser(IBinder, Bundle, Bundle)
+ */
+ public static UserHandle getInsecureTargetUser(IBinder activityToken, @Nullable Bundle arguments,
+ @Nullable Bundle intentExtras) {
+ UserHandle currentUser = new UserHandle(UserHandle.myUserId());
+ IActivityManager am = ActivityManagerNative.getDefault();
+ try {
+ UserHandle launchedFromUser = new UserHandle(UserHandle.getUserId(
+ am.getLaunchedFromUid(activityToken)));
+ if (launchedFromUser != null && !launchedFromUser.equals(currentUser)) {
+ return launchedFromUser;
+ }
+ UserHandle extrasUser = intentExtras != null
+ ? (UserHandle) intentExtras.getParcelable(EXTRA_USER) : null;
+ if (extrasUser != null && !extrasUser.equals(currentUser)) {
+ return extrasUser;
+ }
+ UserHandle argumentsUser = arguments != null
+ ? (UserHandle) arguments.getParcelable(EXTRA_USER) : null;
+ if (argumentsUser != null && !argumentsUser.equals(currentUser)) {
+ return argumentsUser;
+ }
+ } catch (RemoteException e) {
+ // Should not happen
+ Log.v(TAG, "Could not talk to activity manager.", e);
+ return null;
+ }
+ return currentUser;
+ }
+
+ /**
+ * Returns true if the user provided is in the same profiles group as the current user.
+ */
+ private static boolean isProfileOf(UserManager um, UserHandle otherUser) {
+ if (um == null || otherUser == null) return false;
+ return (UserHandle.myUserId() == otherUser.getIdentifier())
+ || um.getUserProfiles().contains(otherUser);
+ }
+
+ /**
+ * Creates a dialog to confirm with the user if it's ok to remove the user
+ * and delete all the data.
+ *
+ * @param context a Context object
+ * @param removingUserId The userId of the user to remove
+ * @param onConfirmListener Callback object for positive action
+ * @return the created Dialog
+ */
+ public static Dialog createRemoveConfirmationDialog(Context context, int removingUserId,
+ DialogInterface.OnClickListener onConfirmListener) {
+ UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ UserInfo userInfo = um.getUserInfo(removingUserId);
+ int titleResId;
+ int messageResId;
+ if (UserHandle.myUserId() == removingUserId) {
+ titleResId = R.string.user_confirm_remove_self_title;
+ messageResId = R.string.user_confirm_remove_self_message;
+ } else if (userInfo.isRestricted()) {
+ titleResId = R.string.user_profile_confirm_remove_title;
+ messageResId = R.string.user_profile_confirm_remove_message;
+ } else if (userInfo.isManagedProfile()) {
+ titleResId = R.string.work_profile_confirm_remove_title;
+ messageResId = R.string.work_profile_confirm_remove_message;
+ } else {
+ titleResId = R.string.user_confirm_remove_title;
+ messageResId = R.string.user_confirm_remove_message;
+ }
+ Dialog dlg = new AlertDialog.Builder(context)
+ .setTitle(titleResId)
+ .setMessage(messageResId)
+ .setPositiveButton(R.string.user_delete_button,
+ onConfirmListener)
+ .setNegativeButton(android.R.string.cancel, null)
+ .create();
+ return dlg;
+ }
+
+ /**
+ * Returns whether or not this device is able to be OEM unlocked.
+ */
+ static boolean isOemUnlockEnabled(Context context) {
+ PersistentDataBlockManager manager =(PersistentDataBlockManager)
+ context.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
+ return manager.getOemUnlockEnabled();
+ }
+
+ /**
+ * Allows enabling or disabling OEM unlock on this device. OEM unlocked
+ * devices allow users to flash other OSes to them.
+ */
+ static void setOemUnlockEnabled(Context context, boolean enabled) {
+ PersistentDataBlockManager manager =(PersistentDataBlockManager)
+ context.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
+ manager.setOemUnlockEnabled(enabled);
+ }
+
+ /**
+ * Returns a circular icon for a user.
+ */
+ public static Drawable getUserIcon(Context context, UserManager um, UserInfo user) {
+ if (user.iconPath != null) {
+ Bitmap icon = um.getUserIcon(user.id);
+ if (icon != null) {
+ return CircleFramedDrawable.getInstance(context, icon);
+ }
+ }
+ return UserIcons.getDefaultUserIcon(user.id, /* light= */ false);
+ }
+
+ /**
+ * 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.
+ */
+ public static boolean showSimCardTile(Context context) {
+ final TelephonyManager tm =
+ (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+
+ // TODO: Uncomment to re-enable SimSettings.
+ // return tm.getSimCount() > 0;
+ return false;
+ }
+
+ /**
+ * Determine whether a package is a "system package", in which case certain things (like
+ * disabling notifications or disabling the package altogether) should be disallowed.
+ */
+ public static boolean isSystemPackage(PackageManager pm, PackageInfo pkg) {
+ if (sSystemSignature == null) {
+ sSystemSignature = new Signature[]{ getSystemSignature(pm) };
+ }
+ return sSystemSignature[0] != null && sSystemSignature[0].equals(getFirstSignature(pkg));
+ }
+
+ private static Signature[] sSystemSignature;
+
+ private static Signature getFirstSignature(PackageInfo pkg) {
+ if (pkg != null && pkg.signatures != null && pkg.signatures.length > 0) {
+ return pkg.signatures[0];
+ }
+ return null;
+ }
+
+ private static Signature getSystemSignature(PackageManager pm) {
+ try {
+ final PackageInfo sys = pm.getPackageInfo("android", PackageManager.GET_SIGNATURES);
+ return getFirstSignature(sys);
+ } catch (NameNotFoundException e) {
+ }
+ return null;
+ }
+
+ /**
+ * Returns elapsed time for the given millis, in the following format:
+ * 2d 5h 40m 29s
+ * @param context the application context
+ * @param millis the elapsed time in milli seconds
+ * @param withSeconds include seconds?
+ * @return the formatted elapsed time
+ */
+ public static String formatElapsedTime(Context context, double millis, boolean withSeconds) {
+ StringBuilder sb = new StringBuilder();
+ int seconds = (int) Math.floor(millis / 1000);
+ if (!withSeconds) {
+ // Round up.
+ seconds += 30;
+ }
+
+ int days = 0, hours = 0, minutes = 0;
+ if (seconds >= SECONDS_PER_DAY) {
+ days = seconds / SECONDS_PER_DAY;
+ seconds -= days * SECONDS_PER_DAY;
+ }
+ if (seconds >= SECONDS_PER_HOUR) {
+ hours = seconds / SECONDS_PER_HOUR;
+ seconds -= hours * SECONDS_PER_HOUR;
+ }
+ if (seconds >= SECONDS_PER_MINUTE) {
+ minutes = seconds / SECONDS_PER_MINUTE;
+ seconds -= minutes * SECONDS_PER_MINUTE;
+ }
+ if (withSeconds) {
+ if (days > 0) {
+ sb.append(context.getString(R.string.battery_history_days,
+ days, hours, minutes, seconds));
+ } else if (hours > 0) {
+ sb.append(context.getString(R.string.battery_history_hours,
+ hours, minutes, seconds));
+ } else if (minutes > 0) {
+ sb.append(context.getString(R.string.battery_history_minutes, minutes, seconds));
+ } else {
+ sb.append(context.getString(R.string.battery_history_seconds, seconds));
+ }
+ } else {
+ if (days > 0) {
+ sb.append(context.getString(R.string.battery_history_days_no_seconds,
+ days, hours, minutes));
+ } else if (hours > 0) {
+ sb.append(context.getString(R.string.battery_history_hours_no_seconds,
+ hours, minutes));
+ } else {
+ sb.append(context.getString(R.string.battery_history_minutes_no_seconds, minutes));
+ }
+ }
+ return sb.toString();
+ }
}
diff --git a/src/com/android/settings/VoiceInputOutputSettings.java b/src/com/android/settings/VoiceInputOutputSettings.java
index b499ded..e052f8e 100644
--- a/src/com/android/settings/VoiceInputOutputSettings.java
+++ b/src/com/android/settings/VoiceInputOutputSettings.java
@@ -16,58 +16,31 @@
package com.android.settings;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
-import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.preference.PreferenceGroup;
-import android.preference.PreferenceScreen;
-import android.preference.Preference.OnPreferenceChangeListener;
-import android.provider.Settings;
-import android.speech.RecognitionService;
import android.speech.tts.TtsEngines;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.Xml;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.List;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
+import com.android.settings.voice.VoiceInputHelper;
/**
* Settings screen for voice input/output.
*/
-public class VoiceInputOutputSettings implements OnPreferenceChangeListener {
+public class VoiceInputOutputSettings {
private static final String TAG = "VoiceInputOutputSettings";
private static final String KEY_VOICE_CATEGORY = "voice_category";
- private static final String KEY_RECOGNIZER = "recognizer";
- private static final String KEY_RECOGNIZER_SETTINGS = "recognizer_settings";
+ private static final String KEY_VOICE_INPUT_SETTINGS = "voice_input_settings";
private static final String KEY_TTS_SETTINGS = "tts_settings";
private PreferenceGroup mParent;
private PreferenceCategory mVoiceCategory;
- private ListPreference mRecognizerPref;
- private Preference mRecognizerSettingsPref;
+ private Preference mVoiceInputSettingsPref;
private Preference mTtsSettingsPref;
- private PreferenceScreen mSettingsPref;
private final SettingsPreferenceFragment mFragment;
private final TtsEngines mTtsEngines;
- private HashMap<String, ResolveInfo> mAvailableRecognizersMap;
-
public VoiceInputOutputSettings(SettingsPreferenceFragment fragment) {
mFragment = fragment;
mTtsEngines = new TtsEngines(fragment.getPreferenceScreen().getContext());
@@ -77,22 +50,16 @@ public class VoiceInputOutputSettings implements OnPreferenceChangeListener {
mParent = mFragment.getPreferenceScreen();
mVoiceCategory = (PreferenceCategory) mParent.findPreference(KEY_VOICE_CATEGORY);
- mRecognizerPref = (ListPreference) mVoiceCategory.findPreference(KEY_RECOGNIZER);
- mRecognizerSettingsPref = mVoiceCategory.findPreference(KEY_RECOGNIZER_SETTINGS);
+ mVoiceInputSettingsPref = mVoiceCategory.findPreference(KEY_VOICE_INPUT_SETTINGS);
mTtsSettingsPref = mVoiceCategory.findPreference(KEY_TTS_SETTINGS);
- mRecognizerPref.setOnPreferenceChangeListener(this);
- mSettingsPref = (PreferenceScreen)
- mVoiceCategory.findPreference(KEY_RECOGNIZER_SETTINGS);
-
- mAvailableRecognizersMap = new HashMap<String, ResolveInfo>();
populateOrRemovePreferences();
}
private void populateOrRemovePreferences() {
- boolean hasRecognizerPrefs = populateOrRemoveRecognizerPrefs();
+ boolean hasVoiceInputPrefs = populateOrRemoveVoiceInputPrefs();
boolean hasTtsPrefs = populateOrRemoveTtsPrefs();
- if (!hasRecognizerPrefs && !hasTtsPrefs) {
+ if (!hasVoiceInputPrefs && !hasTtsPrefs) {
// There were no TTS settings and no recognizer settings,
// so it should be safe to hide the preference category
// entirely.
@@ -100,42 +67,13 @@ public class VoiceInputOutputSettings implements OnPreferenceChangeListener {
}
}
- private boolean populateOrRemoveRecognizerPrefs() {
- List<ResolveInfo> availableRecognitionServices =
- mFragment.getPackageManager().queryIntentServices(
- new Intent(RecognitionService.SERVICE_INTERFACE),
- PackageManager.GET_META_DATA);
- int numAvailable = availableRecognitionServices.size();
-
- if (numAvailable == 0) {
- mVoiceCategory.removePreference(mRecognizerPref);
- mVoiceCategory.removePreference(mRecognizerSettingsPref);
+ private boolean populateOrRemoveVoiceInputPrefs() {
+ VoiceInputHelper helper = new VoiceInputHelper(mFragment.getActivity());
+ if (!helper.hasItems()) {
+ mVoiceCategory.removePreference(mVoiceInputSettingsPref);
return false;
}
- if (numAvailable == 1) {
- // Only one recognizer available, so don't show the list of choices, but do
- // set up the link to settings for the available recognizer.
- mVoiceCategory.removePreference(mRecognizerPref);
-
- // But first set up the available recognizers map with just the one recognizer.
- ResolveInfo resolveInfo = availableRecognitionServices.get(0);
- String recognizerComponent =
- new ComponentName(resolveInfo.serviceInfo.packageName,
- resolveInfo.serviceInfo.name).flattenToShortString();
-
- mAvailableRecognizersMap.put(recognizerComponent, resolveInfo);
-
- String currentSetting = Settings.Secure.getString(
- mFragment.getContentResolver(), Settings.Secure.VOICE_RECOGNITION_SERVICE);
- updateSettingsLink(currentSetting);
- } else {
- // Multiple recognizers available, so show the full list of choices.
- populateRecognizerPreference(availableRecognitionServices);
- }
-
- // In this case, there was at least one available recognizer so
- // we populated the settings.
return true;
}
@@ -147,111 +85,4 @@ public class VoiceInputOutputSettings implements OnPreferenceChangeListener {
return true;
}
-
- private void populateRecognizerPreference(List<ResolveInfo> recognizers) {
- int size = recognizers.size();
- CharSequence[] entries = new CharSequence[size];
- CharSequence[] values = new CharSequence[size];
-
- // Get the current value from the secure setting.
- String currentSetting = Settings.Secure.getString(
- mFragment.getContentResolver(), Settings.Secure.VOICE_RECOGNITION_SERVICE);
-
- // Iterate through all the available recognizers and load up their info to show
- // in the preference. Also build up a map of recognizer component names to their
- // ResolveInfos - we'll need that a little later.
- for (int i = 0; i < size; i++) {
- ResolveInfo resolveInfo = recognizers.get(i);
- String recognizerComponent =
- new ComponentName(resolveInfo.serviceInfo.packageName,
- resolveInfo.serviceInfo.name).flattenToShortString();
-
- mAvailableRecognizersMap.put(recognizerComponent, resolveInfo);
-
- entries[i] = resolveInfo.loadLabel(mFragment.getPackageManager());
- values[i] = recognizerComponent;
- }
-
- mRecognizerPref.setEntries(entries);
- mRecognizerPref.setEntryValues(values);
-
- mRecognizerPref.setDefaultValue(currentSetting);
- mRecognizerPref.setValue(currentSetting);
-
- updateSettingsLink(currentSetting);
- }
-
- private void updateSettingsLink(String currentSetting) {
- ResolveInfo currentRecognizer = mAvailableRecognizersMap.get(currentSetting);
- if (currentRecognizer == null) return;
-
- ServiceInfo si = currentRecognizer.serviceInfo;
- XmlResourceParser parser = null;
- String settingsActivity = null;
- try {
- parser = si.loadXmlMetaData(mFragment.getPackageManager(),
- RecognitionService.SERVICE_META_DATA);
- if (parser == null) {
- throw new XmlPullParserException("No " + RecognitionService.SERVICE_META_DATA +
- " meta-data for " + si.packageName);
- }
-
- Resources res = mFragment.getPackageManager().getResourcesForApplication(
- si.applicationInfo);
-
- AttributeSet attrs = Xml.asAttributeSet(parser);
-
- int type;
- while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
- && type != XmlPullParser.START_TAG) {
- }
-
- String nodeName = parser.getName();
- if (!"recognition-service".equals(nodeName)) {
- throw new XmlPullParserException(
- "Meta-data does not start with recognition-service tag");
- }
-
- TypedArray array = res.obtainAttributes(attrs,
- com.android.internal.R.styleable.RecognitionService);
- settingsActivity = array.getString(
- com.android.internal.R.styleable.RecognitionService_settingsActivity);
- array.recycle();
- } catch (XmlPullParserException e) {
- Log.e(TAG, "error parsing recognition service meta-data", e);
- } catch (IOException e) {
- Log.e(TAG, "error parsing recognition service meta-data", e);
- } catch (NameNotFoundException e) {
- Log.e(TAG, "error parsing recognition service meta-data", e);
- } finally {
- if (parser != null) parser.close();
- }
-
- if (settingsActivity == null) {
- // No settings preference available - hide the preference.
- Log.w(TAG, "no recognizer settings available for " + si.packageName);
- mSettingsPref.setIntent(null);
- mVoiceCategory.removePreference(mSettingsPref);
- } else {
- Intent i = new Intent(Intent.ACTION_MAIN);
- i.setComponent(new ComponentName(si.packageName, settingsActivity));
- mSettingsPref.setIntent(i);
- mRecognizerPref.setSummary(currentRecognizer.loadLabel(mFragment.getPackageManager()));
- }
- }
-
- public boolean onPreferenceChange(Preference preference, Object newValue) {
- if (preference == mRecognizerPref) {
- String setting = (String) newValue;
-
- // Put the new value back into secure settings.
- Settings.Secure.putString(mFragment.getContentResolver(),
- Settings.Secure.VOICE_RECOGNITION_SERVICE,
- setting);
-
- // Update the settings item so it points to the right settings.
- updateSettingsLink(setting);
- }
- return true;
- }
}
diff --git a/src/com/android/settings/VoiceSettingsActivity.java b/src/com/android/settings/VoiceSettingsActivity.java
new file mode 100644
index 0000000..b5e8ede
--- /dev/null
+++ b/src/com/android/settings/VoiceSettingsActivity.java
@@ -0,0 +1,51 @@
+/*
+ * 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.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * Activity for modifying a setting using the Voice Interaction API. This activity
+ * MUST only modify the setting if the intent was sent using
+ * {@link android.service.voice.VoiceInteractionSession#startVoiceActivity startVoiceActivity}.
+ */
+abstract public class VoiceSettingsActivity extends Activity {
+
+ private static final String TAG = "VoiceSettingsActivity";
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (isVoiceInteraction()) {
+ // Only permit if this is a voice interaction.
+ onVoiceSettingInteraction(getIntent());
+ } else {
+ Log.v(TAG, "Cannot modify settings without voice interaction");
+ }
+ finish();
+ }
+
+ /**
+ * Modify the setting as a voice interaction. The activity will finish
+ * after this method is called.
+ */
+ abstract protected void onVoiceSettingInteraction(Intent intent);
+}
diff --git a/src/com/android/settings/WallpaperTypeSettings.java b/src/com/android/settings/WallpaperTypeSettings.java
index fa5f0ac..7b2dcbf 100644
--- a/src/com/android/settings/WallpaperTypeSettings.java
+++ b/src/com/android/settings/WallpaperTypeSettings.java
@@ -16,18 +16,23 @@
package com.android.settings;
-import android.app.Activity;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceScreen;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.search.Indexable;
+import com.android.settings.search.SearchIndexableRaw;
+import java.util.ArrayList;
import java.util.List;
-public class WallpaperTypeSettings extends SettingsPreferenceFragment {
+public class WallpaperTypeSettings extends SettingsPreferenceFragment implements Indexable {
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -38,9 +43,9 @@ public class WallpaperTypeSettings extends SettingsPreferenceFragment {
private void populateWallpaperTypes() {
// Search for activities that satisfy the ACTION_SET_WALLPAPER action
- Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER);
+ final Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER);
final PackageManager pm = getPackageManager();
- List<ResolveInfo> rList = pm.queryIntentActivities(intent,
+ final List<ResolveInfo> rList = pm.queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY);
final PreferenceScreen parent = getPreferenceScreen();
@@ -58,4 +63,34 @@ public class WallpaperTypeSettings extends SettingsPreferenceFragment {
parent.addPreference(pref);
}
}
+
+ public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider() {
+ @Override
+ public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
+ final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>();
+
+ final Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER);
+ final PackageManager pm = context.getPackageManager();
+ final List<ResolveInfo> rList = pm.queryIntentActivities(intent,
+ PackageManager.MATCH_DEFAULT_ONLY);
+
+ // Add indexable data for each of the matching activities
+ for (ResolveInfo info : rList) {
+ CharSequence label = info.loadLabel(pm);
+ if (label == null) label = info.activityInfo.packageName;
+
+ SearchIndexableRaw data = new SearchIndexableRaw(context);
+ data.title = label.toString();
+ data.screenTitle = context.getResources().getString(
+ R.string.wallpaper_settings_fragment_title);
+ data.intentAction = Intent.ACTION_SET_WALLPAPER;
+ data.intentTargetPackage = info.activityInfo.packageName;
+ data.intentTargetClass = info.activityInfo.name;
+ result.add(data);
+ }
+
+ return result;
+ }
+ };
}
diff --git a/src/com/android/settings/WirelessSettings.java b/src/com/android/settings/WirelessSettings.java
index 65127b5..09d4a54 100644
--- a/src/com/android/settings/WirelessSettings.java
+++ b/src/com/android/settings/WirelessSettings.java
@@ -21,24 +21,28 @@ import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.admin.DevicePolicyManager;
+import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
+import android.net.Uri;
import android.nfc.NfcAdapter;
+import android.nfc.NfcManager;
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.Preference.OnPreferenceChangeListener;
import android.preference.PreferenceScreen;
+import android.preference.SwitchPreference;
+import android.provider.SearchIndexableResource;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -49,11 +53,16 @@ import com.android.internal.telephony.SmsApplication.SmsApplicationData;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.telephony.TelephonyProperties;
import com.android.settings.nfc.NfcEnabler;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.search.Indexable;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.List;
-public class WirelessSettings extends RestrictedSettingsFragment
- implements OnPreferenceChangeListener {
+public class WirelessSettings extends SettingsPreferenceFragment
+ implements OnPreferenceChangeListener, Indexable {
private static final String TAG = "WirelessSettings";
private static final String KEY_TOGGLE_AIRPLANE = "toggle_airplane";
@@ -73,32 +82,28 @@ public class WirelessSettings extends RestrictedSettingsFragment
public static final int REQUEST_CODE_EXIT_ECM = 1;
private AirplaneModeEnabler mAirplaneModeEnabler;
- private CheckBoxPreference mAirplaneModePreference;
+ private SwitchPreference mAirplaneModePreference;
private NfcEnabler mNfcEnabler;
private NfcAdapter mNfcAdapter;
private NsdEnabler mNsdEnabler;
private ConnectivityManager mCm;
private TelephonyManager mTm;
+ private PackageManager mPm;
+ private UserManager mUm;
private static final int MANAGE_MOBILE_PLAN_DIALOG_ID = 1;
private static final String SAVED_MANAGE_MOBILE_PLAN_MSG = "mManageMobilePlanMessage";
- private SmsListPreference mSmsApplicationPreference;
+ private AppListPreference mSmsApplicationPreference;
- public WirelessSettings() {
- super(null);
- }
/**
* Invoked on each preference click in this hierarchy, overrides
- * PreferenceActivity's implementation. Used to make sure we track the
+ * PreferenceFragment's implementation. Used to make sure we track the
* preference click events.
*/
@Override
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
- if (ensurePinRestrictedPreference(preference)) {
- return true;
- }
log("onPreferenceTreeClick: preference=" + preference);
if (preference == mAirplaneModePreference && Boolean.parseBoolean(
SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) {
@@ -115,8 +120,6 @@ public class WirelessSettings extends RestrictedSettingsFragment
}
private String mManageMobilePlanMessage;
- private static final String CONNECTED_TO_PROVISIONING_NETWORK_ACTION
- = "com.android.server.connectivityservice.CONNECTED_TO_PROVISIONING_NETWORK_ACTION";
public void onManageMobilePlanClick() {
log("onManageMobilePlanClick:");
mManageMobilePlanMessage = null;
@@ -124,14 +127,32 @@ public class WirelessSettings extends RestrictedSettingsFragment
NetworkInfo ni = mCm.getProvisioningOrActiveNetworkInfo();
if (mTm.hasIccCard() && (ni != null)) {
+ // Check for carrier apps that can handle provisioning first
+ Intent provisioningIntent = new Intent(TelephonyIntents.ACTION_CARRIER_SETUP);
+ List<String> carrierPackages =
+ mTm.getCarrierPackageNamesForIntent(provisioningIntent);
+ if (carrierPackages != null && !carrierPackages.isEmpty()) {
+ if (carrierPackages.size() != 1) {
+ Log.w(TAG, "Multiple matching carrier apps found, launching the first.");
+ }
+ provisioningIntent.setPackage(carrierPackages.get(0));
+ startActivity(provisioningIntent);
+ return;
+ }
+
// Get provisioning URL
String url = mCm.getMobileProvisioningUrl();
if (!TextUtils.isEmpty(url)) {
- Intent intent = new Intent(CONNECTED_TO_PROVISIONING_NETWORK_ACTION);
- intent.putExtra("EXTRA_URL", url);
- Context context = getActivity().getBaseContext();
- context.sendBroadcast(intent);
- mManageMobilePlanMessage = null;
+ Intent intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN,
+ Intent.CATEGORY_APP_BROWSER);
+ intent.setData(Uri.parse(url));
+ intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
+ Intent.FLAG_ACTIVITY_NEW_TASK);
+ try {
+ startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ Log.w(TAG, "onManageMobilePlanClick: startActivity failed" + e);
+ }
} else {
// No provisioning URL
String operatorName = mTm.getSimOperatorName();
@@ -164,23 +185,6 @@ public class WirelessSettings extends RestrictedSettingsFragment
}
}
- private void updateSmsApplicationSetting() {
- log("updateSmsApplicationSetting:");
- ComponentName appName = SmsApplication.getDefaultSmsApplication(getActivity(), true);
- if (appName != null) {
- String packageName = appName.getPackageName();
-
- CharSequence[] values = mSmsApplicationPreference.getEntryValues();
- for (int i = 0; i < values.length; i++) {
- if (packageName.contentEquals(values[i])) {
- mSmsApplicationPreference.setValueIndex(i);
- mSmsApplicationPreference.setSummary(mSmsApplicationPreference.getEntries()[i]);
- break;
- }
- }
- }
- }
-
private void initSmsApplicationSetting() {
log("initSmsApplicationSetting:");
Collection<SmsApplicationData> smsApplications =
@@ -188,26 +192,18 @@ public class WirelessSettings extends RestrictedSettingsFragment
// If the list is empty the dialog will be empty, but we will not crash.
int count = smsApplications.size();
- CharSequence[] entries = new CharSequence[count];
- CharSequence[] entryValues = new CharSequence[count];
- Drawable[] entryImages = new Drawable[count];
-
- PackageManager packageManager = getPackageManager();
+ String[] packageNames = new String[count];
int i = 0;
for (SmsApplicationData smsApplicationData : smsApplications) {
- entries[i] = smsApplicationData.mApplicationName;
- entryValues[i] = smsApplicationData.mPackageName;
- try {
- entryImages[i] = packageManager.getApplicationIcon(smsApplicationData.mPackageName);
- } catch (NameNotFoundException e) {
- entryImages[i] = packageManager.getDefaultActivityIcon();
- }
+ packageNames[i] = smsApplicationData.mPackageName;
i++;
}
- mSmsApplicationPreference.setEntries(entries);
- mSmsApplicationPreference.setEntryValues(entryValues);
- mSmsApplicationPreference.setEntryDrawables(entryImages);
- updateSmsApplicationSetting();
+ String defaultPackageName = null;
+ ComponentName appName = SmsApplication.getDefaultSmsApplication(getActivity(), true);
+ if (appName != null) {
+ defaultPackageName = appName.getPackageName();
+ }
+ mSmsApplicationPreference.setPackageNames(packageNames, defaultPackageName);
}
@Override
@@ -247,7 +243,7 @@ public class WirelessSettings extends RestrictedSettingsFragment
private boolean isSmsSupported() {
// Some tablet has sim card but could not do telephony operations. Skip those.
- return (mTm.getPhoneType() != TelephonyManager.PHONE_TYPE_NONE);
+ return mTm.isSmsCapable();
}
@Override
@@ -260,21 +256,23 @@ public class WirelessSettings extends RestrictedSettingsFragment
mCm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
mTm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
+ mPm = getPackageManager();
+ mUm = (UserManager) getSystemService(Context.USER_SERVICE);
addPreferencesFromResource(R.xml.wireless_settings);
final boolean isSecondaryUser = UserHandle.myUserId() != UserHandle.USER_OWNER;
final Activity activity = getActivity();
- mAirplaneModePreference = (CheckBoxPreference) findPreference(KEY_TOGGLE_AIRPLANE);
- CheckBoxPreference nfc = (CheckBoxPreference) findPreference(KEY_TOGGLE_NFC);
+ mAirplaneModePreference = (SwitchPreference) findPreference(KEY_TOGGLE_AIRPLANE);
+ SwitchPreference nfc = (SwitchPreference) findPreference(KEY_TOGGLE_NFC);
PreferenceScreen androidBeam = (PreferenceScreen) findPreference(KEY_ANDROID_BEAM_SETTINGS);
CheckBoxPreference nsd = (CheckBoxPreference) findPreference(KEY_TOGGLE_NSD);
mAirplaneModeEnabler = new AirplaneModeEnabler(activity, mAirplaneModePreference);
mNfcEnabler = new NfcEnabler(activity, nfc, androidBeam);
- mSmsApplicationPreference = (SmsListPreference) findPreference(KEY_SMS_APPLICATION);
+ mSmsApplicationPreference = (AppListPreference) findPreference(KEY_SMS_APPLICATION);
mSmsApplicationPreference.setOnPreferenceChangeListener(this);
initSmsApplicationSetting();
@@ -286,9 +284,10 @@ public class WirelessSettings extends RestrictedSettingsFragment
Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS);
//enable/disable wimax depending on the value in config.xml
- boolean isWimaxEnabled = !isSecondaryUser && this.getResources().getBoolean(
+ final boolean isWimaxEnabled = !isSecondaryUser && this.getResources().getBoolean(
com.android.internal.R.bool.config_wimaxEnabled);
- if (!isWimaxEnabled) {
+ if (!isWimaxEnabled
+ || mUm.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)) {
PreferenceScreen root = getPreferenceScreen();
Preference ps = (Preference) findPreference(KEY_WIMAX_SETTINGS);
if (ps != null) root.removePreference(ps);
@@ -299,16 +298,16 @@ public class WirelessSettings extends RestrictedSettingsFragment
ps.setDependency(KEY_TOGGLE_AIRPLANE);
}
}
- protectByRestrictions(KEY_WIMAX_SETTINGS);
// Manually set dependencies for Wifi when not toggleable.
if (toggleable == null || !toggleable.contains(Settings.Global.RADIO_WIFI)) {
findPreference(KEY_VPN_SETTINGS).setDependency(KEY_TOGGLE_AIRPLANE);
}
- if (isSecondaryUser) { // Disable VPN
+ // Disable VPN.
+ if (isSecondaryUser || mUm.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN)) {
removePreference(KEY_VPN_SETTINGS);
}
- protectByRestrictions(KEY_VPN_SETTINGS);
+
// Manually set dependencies for Bluetooth when not toggleable.
if (toggleable == null || !toggleable.contains(Settings.Global.RADIO_BLUETOOTH)) {
// No bluetooth-dependent items in the list. Code kept in case one is added later.
@@ -320,7 +319,7 @@ public class WirelessSettings extends RestrictedSettingsFragment
findPreference(KEY_ANDROID_BEAM_SETTINGS).setDependency(KEY_TOGGLE_AIRPLANE);
}
- // Remove NFC if its not available
+ // Remove NFC if not available
mNfcAdapter = NfcAdapter.getDefaultAdapter(activity);
if (mNfcAdapter == null) {
getPreferenceScreen().removePreference(nfc);
@@ -328,14 +327,16 @@ public class WirelessSettings extends RestrictedSettingsFragment
mNfcEnabler = null;
}
- // Remove Mobile Network Settings and Manage Mobile Plan if it's a wifi-only device.
- if (isSecondaryUser || Utils.isWifiOnly(getActivity())) {
+ // Remove Mobile Network Settings and Manage Mobile Plan for secondary users,
+ // if it's a wifi-only device, or if the settings are restricted.
+ if (isSecondaryUser || Utils.isWifiOnly(getActivity())
+ || mUm.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)) {
removePreference(KEY_MOBILE_NETWORK_SETTINGS);
removePreference(KEY_MANAGE_MOBILE_PLAN);
}
// Remove Mobile Network Settings and Manage Mobile Plan
// if config_show_mobile_plan sets false.
- boolean isMobilePlanEnabled = this.getResources().getBoolean(
+ final boolean isMobilePlanEnabled = this.getResources().getBoolean(
R.bool.config_show_mobile_plan);
if (!isMobilePlanEnabled) {
Preference pref = findPreference(KEY_MANAGE_MOBILE_PLAN);
@@ -343,8 +344,6 @@ public class WirelessSettings extends RestrictedSettingsFragment
removePreference(KEY_MANAGE_MOBILE_PLAN);
}
}
- protectByRestrictions(KEY_MOBILE_NETWORK_SETTINGS);
- protectByRestrictions(KEY_MANAGE_MOBILE_PLAN);
// Remove SMS Application if the device does not support SMS
if (!isSmsSupported()) {
@@ -352,36 +351,39 @@ public class WirelessSettings extends RestrictedSettingsFragment
}
// Remove Airplane Mode settings if it's a stationary device such as a TV.
- if (getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEVISION)) {
+ if (mPm.hasSystemFeature(PackageManager.FEATURE_TELEVISION)) {
removePreference(KEY_TOGGLE_AIRPLANE);
}
// Enable Proxy selector settings if allowed.
Preference mGlobalProxy = findPreference(KEY_PROXY_SETTINGS);
- DevicePolicyManager mDPM = (DevicePolicyManager)
+ final DevicePolicyManager mDPM = (DevicePolicyManager)
activity.getSystemService(Context.DEVICE_POLICY_SERVICE);
// proxy UI disabled until we have better app support
getPreferenceScreen().removePreference(mGlobalProxy);
mGlobalProxy.setEnabled(mDPM.getGlobalProxyAdmin() == null);
// Disable Tethering if it's not allowed or if it's a wifi-only device
- ConnectivityManager cm =
+ final ConnectivityManager cm =
(ConnectivityManager) activity.getSystemService(Context.CONNECTIVITY_SERVICE);
- if (isSecondaryUser || !cm.isTetheringSupported()) {
+ if (isSecondaryUser || !cm.isTetheringSupported()
+ || mUm.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING)) {
getPreferenceScreen().removePreference(findPreference(KEY_TETHER_SETTINGS));
} else {
Preference p = findPreference(KEY_TETHER_SETTINGS);
p.setTitle(Utils.getTetheringLabel(cm));
+
+ // Grey out if provisioning is not available.
+ p.setEnabled(!TetherSettings
+ .isProvisioningNeededButUnavailable(getActivity()));
}
- protectByRestrictions(KEY_TETHER_SETTINGS);
// Enable link to CMAS app settings depending on the value in config.xml.
boolean isCellBroadcastAppLinkEnabled = this.getResources().getBoolean(
com.android.internal.R.bool.config_cellBroadcastAppLinks);
try {
if (isCellBroadcastAppLinkEnabled) {
- PackageManager pm = getPackageManager();
- if (pm.getApplicationEnabledSetting("com.android.cellbroadcastreceiver")
+ if (mPm.getApplicationEnabledSetting("com.android.cellbroadcastreceiver")
== PackageManager.COMPONENT_ENABLED_STATE_DISABLED) {
isCellBroadcastAppLinkEnabled = false; // CMAS app disabled
}
@@ -389,12 +391,12 @@ public class WirelessSettings extends RestrictedSettingsFragment
} catch (IllegalArgumentException ignored) {
isCellBroadcastAppLinkEnabled = false; // CMAS app not installed
}
- if (isSecondaryUser || !isCellBroadcastAppLinkEnabled) {
+ if (isSecondaryUser || !isCellBroadcastAppLinkEnabled
+ || mUm.hasUserRestriction(UserManager.DISALLOW_CONFIG_CELL_BROADCASTS)) {
PreferenceScreen root = getPreferenceScreen();
Preference ps = findPreference(KEY_CELL_BROADCAST_SETTINGS);
if (ps != null) root.removePreference(ps);
}
- protectByRestrictions(KEY_CELL_BROADCAST_SETTINGS);
}
@Override
@@ -459,9 +461,109 @@ public class WirelessSettings extends RestrictedSettingsFragment
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (preference == mSmsApplicationPreference && newValue != null) {
SmsApplication.setDefaultApplication(newValue.toString(), getActivity());
- updateSmsApplicationSetting();
return true;
}
return false;
}
+
+ /**
+ * For Search.
+ */
+ public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider() {
+ @Override
+ public List<SearchIndexableResource> getXmlResourcesToIndex(
+ Context context, boolean enabled) {
+ SearchIndexableResource sir = new SearchIndexableResource(context);
+ sir.xmlResId = R.xml.wireless_settings;
+ return Arrays.asList(sir);
+ }
+
+ @Override
+ public List<String> getNonIndexableKeys(Context context) {
+ final ArrayList<String> result = new ArrayList<String>();
+
+ result.add(KEY_TOGGLE_NSD);
+
+ final UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ final boolean isSecondaryUser = UserHandle.myUserId() != UserHandle.USER_OWNER;
+ final boolean isWimaxEnabled = !isSecondaryUser && context.getResources().getBoolean(
+ com.android.internal.R.bool.config_wimaxEnabled);
+ if (!isWimaxEnabled
+ || um.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)) {
+ result.add(KEY_WIMAX_SETTINGS);
+ }
+
+ if (isSecondaryUser) { // Disable VPN
+ result.add(KEY_VPN_SETTINGS);
+ }
+
+ // Remove NFC if not available
+ final NfcManager manager = (NfcManager) context.getSystemService(Context.NFC_SERVICE);
+ if (manager != null) {
+ NfcAdapter adapter = manager.getDefaultAdapter();
+ if (adapter == null) {
+ result.add(KEY_TOGGLE_NFC);
+ result.add(KEY_ANDROID_BEAM_SETTINGS);
+ }
+ }
+
+ // Remove Mobile Network Settings and Manage Mobile Plan if it's a wifi-only device.
+ if (isSecondaryUser || Utils.isWifiOnly(context)) {
+ result.add(KEY_MOBILE_NETWORK_SETTINGS);
+ result.add(KEY_MANAGE_MOBILE_PLAN);
+ }
+
+ // 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) {
+ result.add(KEY_MANAGE_MOBILE_PLAN);
+ }
+
+ // Remove SMS Application if the device does not support SMS
+ TelephonyManager tm =
+ (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ if (!tm.isSmsCapable()) {
+ result.add(KEY_SMS_APPLICATION);
+ }
+
+ 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);
+ }
+
+ // proxy UI disabled until we have better app support
+ 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);
+ }
+
+ // Enable link to CMAS app settings depending on the value in config.xml.
+ boolean isCellBroadcastAppLinkEnabled = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_cellBroadcastAppLinks);
+ try {
+ if (isCellBroadcastAppLinkEnabled) {
+ if (pm.getApplicationEnabledSetting("com.android.cellbroadcastreceiver")
+ == PackageManager.COMPONENT_ENABLED_STATE_DISABLED) {
+ isCellBroadcastAppLinkEnabled = false; // CMAS app disabled
+ }
+ }
+ } catch (IllegalArgumentException ignored) {
+ isCellBroadcastAppLinkEnabled = false; // CMAS app not installed
+ }
+ if (isSecondaryUser || !isCellBroadcastAppLinkEnabled) {
+ result.add(KEY_CELL_BROADCAST_SETTINGS);
+ }
+
+ return result;
+ }
+ };
}
diff --git a/src/com/android/settings/accessibility/AccessibilitySettings.java b/src/com/android/settings/accessibility/AccessibilitySettings.java
index 4d24d09..92c478e 100644
--- a/src/com/android/settings/accessibility/AccessibilitySettings.java
+++ b/src/com/android/settings/accessibility/AccessibilitySettings.java
@@ -18,14 +18,10 @@ package com.android.settings.accessibility;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.ActivityManagerNative;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.content.ActivityNotFoundException;
+import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Configuration;
@@ -33,16 +29,17 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
-import android.os.SystemProperties;
+import android.os.UserHandle;
import android.preference.CheckBoxPreference;
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.provider.Settings;
import android.text.TextUtils;
import android.text.TextUtils.SimpleStringSplitter;
-import android.util.Log;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.View;
@@ -56,7 +53,11 @@ import com.android.settings.DialogCreatable;
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 com.android.settings.search.SearchIndexableRaw;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -67,21 +68,12 @@ import java.util.Set;
* Activity with the accessibility settings.
*/
public class AccessibilitySettings extends SettingsPreferenceFragment implements DialogCreatable,
- Preference.OnPreferenceChangeListener {
- private static final String LOG_TAG = "AccessibilitySettings";
-
- private static final String DEFAULT_SCREENREADER_MARKET_LINK =
- "market://search?q=pname:com.google.android.marvin.talkback";
+ Preference.OnPreferenceChangeListener, Indexable {
private static final float LARGE_FONT_SCALE = 1.3f;
- private static final String SYSTEM_PROPERTY_MARKET_URL = "ro.screenreader.market";
-
static final char ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR = ':';
- private static final String KEY_INSTALL_ACCESSIBILITY_SERVICE_OFFERED_ONCE =
- "key_install_accessibility_service_offered_once";
-
// Preference categories
private static final String SERVICES_CATEGORY = "services_category";
private static final String SYSTEM_CATEGORY = "system_category";
@@ -89,6 +81,10 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
// Preferences
private static final String TOGGLE_LARGE_TEXT_PREFERENCE =
"toggle_large_text_preference";
+ private static final String TOGGLE_HIGH_TEXT_CONTRAST_PREFERENCE =
+ "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 =
@@ -103,6 +99,8 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
"captioning_preference_screen";
private static final String DISPLAY_MAGNIFICATION_PREFERENCE_SCREEN =
"screen_magnification_preference_screen";
+ private static final String DISPLAY_DALTONIZER_PREFERENCE_SCREEN =
+ "daltonizer_preference_screen";
// Extras passed to sub-fragments.
static final String EXTRA_PREFERENCE_KEY = "preference_key";
@@ -119,9 +117,6 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
// presentation.
private static final long DELAY_UPDATE_SERVICES_MILLIS = 1000;
- // Dialog IDs.
- private static final int DIALOG_ID_NO_ACCESSIBILITY_SERVICES = 1;
-
// Auxiliary members.
final static SimpleStringSplitter sStringColonSplitter =
new SimpleStringSplitter(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR);
@@ -190,6 +185,7 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
private PreferenceCategory mSystemsCategory;
private CheckBoxPreference mToggleLargeTextPreference;
+ private CheckBoxPreference mToggleHighTextContrastPreference;
private CheckBoxPreference mTogglePowerButtonEndsCallPreference;
private CheckBoxPreference mToggleLockScreenRotationPreference;
private CheckBoxPreference mToggleSpeakPasswordPreference;
@@ -198,14 +194,20 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
private PreferenceScreen mCaptioningPreferenceScreen;
private PreferenceScreen mDisplayMagnificationPreferenceScreen;
private PreferenceScreen mGlobalGesturePreferenceScreen;
+ private PreferenceScreen mDisplayDaltonizerPreferenceScreen;
+ private SwitchPreference mToggleInversionPreference;
private int mLongPressTimeoutDefault;
+ private DevicePolicyManager mDpm;
+
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
addPreferencesFromResource(R.xml.accessibility_settings);
initializeAllPreferences();
+ mDpm = (DevicePolicyManager) (getActivity()
+ .getSystemService(Context.DEVICE_POLICY_SERVICE));
}
@Override
@@ -214,8 +216,6 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
loadInstalledServices();
updateAllPreferences();
- offerInstallAccessibilitySerivceOnce();
-
mSettingsPackageMonitor.register(getActivity(), getActivity().getMainLooper(), false);
mSettingsContentObserver.register(getContentResolver());
if (RotationPolicy.isRotationSupported(getActivity())) {
@@ -237,22 +237,36 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
- if (preference == mSelectLongPressTimeoutPreference) {
- String stringValue = (String) newValue;
- Settings.Secure.putInt(getContentResolver(),
- Settings.Secure.LONG_PRESS_TIMEOUT, Integer.parseInt(stringValue));
- mSelectLongPressTimeoutPreference.setSummary(
- mLongPressTimeoutValuetoTitleMap.get(stringValue));
+ if (mSelectLongPressTimeoutPreference == preference) {
+ handleLongPressTimeoutPreferenceChange((String) newValue);
+ return true;
+ } else if (mToggleInversionPreference == preference) {
+ handleToggleInversionPreferenceChange((Boolean) newValue);
return true;
}
return false;
}
+ private void handleLongPressTimeoutPreferenceChange(String stringValue) {
+ Settings.Secure.putInt(getContentResolver(),
+ Settings.Secure.LONG_PRESS_TIMEOUT, Integer.parseInt(stringValue));
+ mSelectLongPressTimeoutPreference.setSummary(
+ mLongPressTimeoutValuetoTitleMap.get(stringValue));
+ }
+
+ private void handleToggleInversionPreferenceChange(boolean checked) {
+ Settings.Secure.putInt(getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, (checked ? 1 : 0));
+ }
+
@Override
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
if (mToggleLargeTextPreference == preference) {
handleToggleLargeTextPreferenceClick();
return true;
+ } else if (mToggleHighTextContrastPreference == preference) {
+ handleToggleTextContrastPreferenceClick();
+ return true;
} else if (mTogglePowerButtonEndsCallPreference == preference) {
handleTogglePowerButtonEndsCallPreferenceClick();
return true;
@@ -263,7 +277,7 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
handleToggleSpeakPasswordPreferenceClick();
return true;
} else if (mGlobalGesturePreferenceScreen == preference) {
- handleTogglEnableAccessibilityGesturePreferenceClick();
+ handleToggleEnableAccessibilityGesturePreferenceClick();
return true;
} else if (mDisplayMagnificationPreferenceScreen == preference) {
handleDisplayMagnificationPreferenceScreenClick();
@@ -281,6 +295,12 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
}
}
+ private void handleToggleTextContrastPreferenceClick() {
+ Settings.Secure.putInt(getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED,
+ (mToggleHighTextContrastPreference.isChecked() ? 1 : 0));
+ }
+
private void handleTogglePowerButtonEndsCallPreferenceClick() {
Settings.Secure.putInt(getContentResolver(),
Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR,
@@ -300,7 +320,7 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
mToggleSpeakPasswordPreference.isChecked() ? 1 : 0);
}
- private void handleTogglEnableAccessibilityGesturePreferenceClick() {
+ private void handleToggleEnableAccessibilityGesturePreferenceClick() {
Bundle extras = mGlobalGesturePreferenceScreen.getExtras();
extras.putString(EXTRA_TITLE, getString(
R.string.accessibility_global_gesture_preference_title));
@@ -332,6 +352,14 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
mToggleLargeTextPreference =
(CheckBoxPreference) findPreference(TOGGLE_LARGE_TEXT_PREFERENCE);
+ // Text contrast.
+ mToggleHighTextContrastPreference =
+ (CheckBoxPreference) findPreference(TOGGLE_HIGH_TEXT_CONTRAST_PREFERENCE);
+
+ // Display inversion.
+ mToggleInversionPreference = (SwitchPreference) findPreference(TOGGLE_INVERSION_PREFERENCE);
+ mToggleInversionPreference.setOnPreferenceChangeListener(this);
+
// Power button ends calls.
mTogglePowerButtonEndsCallPreference =
(CheckBoxPreference) findPreference(TOGGLE_POWER_BUTTON_ENDS_CALL_PREFERENCE);
@@ -375,6 +403,10 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
mDisplayMagnificationPreferenceScreen = (PreferenceScreen) findPreference(
DISPLAY_MAGNIFICATION_PREFERENCE_SCREEN);
+ // Display color adjustments.
+ mDisplayDaltonizerPreferenceScreen = (PreferenceScreen) findPreference(
+ DISPLAY_DALTONIZER_PREFERENCE_SCREEN);
+
// Global gesture.
mGlobalGesturePreferenceScreen =
(PreferenceScreen) findPreference(ENABLE_ACCESSIBILITY_GESTURE_PREFERENCE_SCREEN);
@@ -408,7 +440,8 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
accessibilityManager.getInstalledAccessibilityServiceList();
Set<ComponentName> enabledServices = AccessibilityUtils.getEnabledServicesFromSettings(
getActivity());
-
+ List<String> permittedServices = mDpm.getPermittedAccessibilityServices(
+ UserHandle.myUserId());
final boolean accessibilityEnabled = Settings.Secure.getInt(getContentResolver(),
Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1;
@@ -428,12 +461,27 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
preference.setTitle(title);
final boolean serviceEnabled = accessibilityEnabled
&& enabledServices.contains(componentName);
+ String serviceEnabledString;
if (serviceEnabled) {
- preference.setSummary(getString(R.string.accessibility_feature_state_on));
+ serviceEnabledString = getString(R.string.accessibility_feature_state_on);
} else {
- preference.setSummary(getString(R.string.accessibility_feature_state_off));
+ serviceEnabledString = getString(R.string.accessibility_feature_state_off);
}
+ // Disable all accessibility services that are not permitted.
+ String packageName = serviceInfo.packageName;
+ boolean serviceAllowed =
+ permittedServices == null || permittedServices.contains(packageName);
+ preference.setEnabled(serviceAllowed || serviceEnabled);
+
+ String summaryString;
+ if (serviceAllowed) {
+ summaryString = serviceEnabledString;
+ } else {
+ summaryString = getString(R.string.accessibility_feature_or_input_method_not_allowed);
+ }
+ preference.setSummary(summaryString);
+
preference.setOrder(i);
preference.setFragment(ToggleAccessibilityServicePreferenceFragment.class.getName());
preference.setPersistent(true);
@@ -465,19 +513,13 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
if (mServicesCategory.getPreferenceCount() == 0) {
if (mNoServicesMessagePreference == null) {
- mNoServicesMessagePreference = new Preference(getActivity()) {
- @Override
- protected void onBindView(View view) {
- super.onBindView(view);
- TextView summaryView = (TextView) view.findViewById(R.id.summary);
- String title = getString(R.string.accessibility_no_services_installed);
- summaryView.setText(title);
- }
- };
+ mNoServicesMessagePreference = new Preference(getActivity());
mNoServicesMessagePreference.setPersistent(false);
mNoServicesMessagePreference.setLayoutResource(
R.layout.text_description_preference);
mNoServicesMessagePreference.setSelectable(false);
+ mNoServicesMessagePreference.setSummary(
+ getString(R.string.accessibility_no_services_installed));
}
mServicesCategory.addPreference(mNoServicesMessagePreference);
}
@@ -492,6 +534,14 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
}
mToggleLargeTextPreference.setChecked(mCurConfig.fontScale == LARGE_FONT_SCALE);
+ mToggleHighTextContrastPreference.setChecked(
+ Settings.Secure.getInt(getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, 0) == 1);
+
+ // If the quick setting is enabled, the preference MUST be enabled.
+ 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())) {
@@ -518,25 +568,12 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
mSelectLongPressTimeoutPreference.setValue(value);
mSelectLongPressTimeoutPreference.setSummary(mLongPressTimeoutValuetoTitleMap.get(value));
- // Captioning.
- final boolean captioningEnabled = Settings.Secure.getInt(getContentResolver(),
- Settings.Secure.ACCESSIBILITY_CAPTIONING_ENABLED, 0) == 1;
- if (captioningEnabled) {
- mCaptioningPreferenceScreen.setSummary(R.string.accessibility_feature_state_on);
- } else {
- mCaptioningPreferenceScreen.setSummary(R.string.accessibility_feature_state_off);
- }
-
- // Screen magnification.
- final boolean magnificationEnabled = Settings.Secure.getInt(getContentResolver(),
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, 0) == 1;
- if (magnificationEnabled) {
- mDisplayMagnificationPreferenceScreen.setSummary(
- R.string.accessibility_feature_state_on);
- } else {
- mDisplayMagnificationPreferenceScreen.setSummary(
- R.string.accessibility_feature_state_off);
- }
+ updateFeatureSummary(Settings.Secure.ACCESSIBILITY_CAPTIONING_ENABLED,
+ mCaptioningPreferenceScreen);
+ updateFeatureSummary(Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
+ mDisplayMagnificationPreferenceScreen);
+ updateFeatureSummary(Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
+ mDisplayDaltonizerPreferenceScreen);
// Global gesture
final boolean globalGestureEnabled = Settings.Global.getInt(getContentResolver(),
@@ -550,6 +587,12 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
}
}
+ private void updateFeatureSummary(String prefKey, Preference pref) {
+ final boolean enabled = Settings.Secure.getInt(getContentResolver(), prefKey, 0) == 1;
+ pref.setSummary(enabled ? R.string.accessibility_feature_state_on
+ : R.string.accessibility_feature_state_off);
+ }
+
private void updateLockScreenRotationCheckbox() {
Context context = getActivity();
if (context != null) {
@@ -558,72 +601,6 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
}
}
- private void offerInstallAccessibilitySerivceOnce() {
- // There is always one preference - if no services it is just a message.
- if (mServicesCategory.getPreference(0) != mNoServicesMessagePreference) {
- return;
- }
- SharedPreferences preferences = getActivity().getPreferences(Context.MODE_PRIVATE);
- final boolean offerInstallService = !preferences.getBoolean(
- KEY_INSTALL_ACCESSIBILITY_SERVICE_OFFERED_ONCE, false);
- if (offerInstallService) {
- String screenreaderMarketLink = SystemProperties.get(
- SYSTEM_PROPERTY_MARKET_URL,
- DEFAULT_SCREENREADER_MARKET_LINK);
- Uri marketUri = Uri.parse(screenreaderMarketLink);
- Intent marketIntent = new Intent(Intent.ACTION_VIEW, marketUri);
-
- if (getPackageManager().resolveActivity(marketIntent, 0) == null) {
- // Don't show the dialog if no market app is found/installed.
- return;
- }
-
- preferences.edit().putBoolean(KEY_INSTALL_ACCESSIBILITY_SERVICE_OFFERED_ONCE,
- true).commit();
- // Notify user that they do not have any accessibility
- // services installed and direct them to Market to get TalkBack.
- showDialog(DIALOG_ID_NO_ACCESSIBILITY_SERVICES);
- }
- }
-
- @Override
- public Dialog onCreateDialog(int dialogId) {
- switch (dialogId) {
- case DIALOG_ID_NO_ACCESSIBILITY_SERVICES:
- return new AlertDialog.Builder(getActivity())
- .setTitle(R.string.accessibility_service_no_apps_title)
- .setMessage(R.string.accessibility_service_no_apps_message)
- .setPositiveButton(android.R.string.ok,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- // dismiss the dialog before launching
- // the activity otherwise the dialog
- // removal occurs after
- // onSaveInstanceState which triggers an
- // exception
- removeDialog(DIALOG_ID_NO_ACCESSIBILITY_SERVICES);
- String screenreaderMarketLink = SystemProperties.get(
- SYSTEM_PROPERTY_MARKET_URL,
- DEFAULT_SCREENREADER_MARKET_LINK);
- Uri marketUri = Uri.parse(screenreaderMarketLink);
- Intent marketIntent = new Intent(Intent.ACTION_VIEW,
- marketUri);
- try {
- startActivity(marketIntent);
- } catch (ActivityNotFoundException anfe) {
- Log.w(LOG_TAG, "Couldn't start play store activity",
- anfe);
- }
- }
- })
- .setNegativeButton(android.R.string.cancel, null)
- .create();
- default:
- return null;
- }
- }
-
private void loadInstalledServices() {
Set<ComponentName> installedServices = sInstalledServices;
installedServices.clear();
@@ -644,4 +621,54 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
installedServices.add(installedService);
}
}
+
+ public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider() {
+ @Override
+ public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
+ List<SearchIndexableRaw> indexables = new ArrayList<SearchIndexableRaw>();
+
+ PackageManager packageManager = context.getPackageManager();
+ AccessibilityManager accessibilityManager = (AccessibilityManager)
+ context.getSystemService(Context.ACCESSIBILITY_SERVICE);
+
+ String screenTitle = context.getResources().getString(
+ R.string.accessibility_services_title);
+
+ // Indexing all services, regardless if enabled.
+ List<AccessibilityServiceInfo> services = accessibilityManager
+ .getInstalledAccessibilityServiceList();
+ final int serviceCount = services.size();
+ for (int i = 0; i < serviceCount; i++) {
+ AccessibilityServiceInfo service = services.get(i);
+ if (service == null || service.getResolveInfo() == null) {
+ continue;
+ }
+
+ ServiceInfo serviceInfo = service.getResolveInfo().serviceInfo;
+ ComponentName componentName = new ComponentName(serviceInfo.packageName,
+ serviceInfo.name);
+
+ SearchIndexableRaw indexable = new SearchIndexableRaw(context);
+ indexable.key = componentName.flattenToString();
+ indexable.title = service.getResolveInfo().loadLabel(packageManager).toString();
+ indexable.summaryOn = context.getString(R.string.accessibility_feature_state_on);
+ indexable.summaryOff = context.getString(R.string.accessibility_feature_state_off);
+ indexable.screenTitle = screenTitle;
+ indexables.add(indexable);
+ }
+
+ return indexables;
+ }
+
+ @Override
+ public List<SearchIndexableResource> getXmlResourcesToIndex(Context context,
+ boolean enabled) {
+ List<SearchIndexableResource> indexables = new ArrayList<SearchIndexableResource>();
+ SearchIndexableResource indexable = new SearchIndexableResource(context);
+ indexable.xmlResId = R.xml.accessibility_settings;
+ indexables.add(indexable);
+ return indexables;
+ }
+ };
}
diff --git a/src/com/android/settings/accessibility/CaptionPropertiesFragment.java b/src/com/android/settings/accessibility/CaptionPropertiesFragment.java
index 8a08d90..9822fc3 100644
--- a/src/com/android/settings/accessibility/CaptionPropertiesFragment.java
+++ b/src/com/android/settings/accessibility/CaptionPropertiesFragment.java
@@ -16,8 +16,6 @@
package com.android.settings.accessibility;
-import android.app.ActionBar;
-import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
@@ -29,9 +27,9 @@ import android.preference.PreferenceCategory;
import android.preference.PreferenceFrameLayout;
import android.preference.Preference.OnPreferenceChangeListener;
import android.provider.Settings;
-import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.View.OnLayoutChangeListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.accessibility.CaptioningManager;
@@ -39,9 +37,12 @@ import android.view.accessibility.CaptioningManager.CaptionStyle;
import com.android.internal.widget.SubtitleView;
import com.android.settings.R;
+import com.android.settings.SettingsActivity;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.accessibility.ListDialogPreference.OnValueChangedListener;
-import com.android.settings.accessibility.ToggleSwitch.OnBeforeCheckedChangeListener;
+import com.android.settings.widget.SwitchBar;
+import com.android.settings.widget.ToggleSwitch;
+import com.android.settings.widget.ToggleSwitch.OnBeforeCheckedChangeListener;
import java.util.Locale;
@@ -54,6 +55,8 @@ public class CaptionPropertiesFragment extends SettingsPreferenceFragment
private static final String PREF_BACKGROUND_OPACITY = "captioning_background_opacity";
private static final String PREF_FOREGROUND_COLOR = "captioning_foreground_color";
private static final String PREF_FOREGROUND_OPACITY = "captioning_foreground_opacity";
+ private static final String PREF_WINDOW_COLOR = "captioning_window_color";
+ private static final String PREF_WINDOW_OPACITY = "captioning_window_opacity";
private static final String PREF_EDGE_COLOR = "captioning_edge_color";
private static final String PREF_EDGE_TYPE = "captioning_edge_type";
private static final String PREF_FONT_SIZE = "captioning_font_size";
@@ -62,10 +65,15 @@ public class CaptionPropertiesFragment extends SettingsPreferenceFragment
private static final String PREF_PRESET = "captioning_preset";
private static final String PREF_CUSTOM = "custom";
- private static final float DEFAULT_FONT_SIZE = 48f;
+ /** WebVtt specifies line height as 5.3% of the viewport height. */
+ private static final float LINE_HEIGHT_RATIO = 0.0533f;
private CaptioningManager mCaptioningManager;
private SubtitleView mPreviewText;
+ private View mPreviewWindow;
+ private View mPreviewViewport;
+ private SwitchBar mSwitchBar;
+ private ToggleSwitch mToggleSwitch;
// Standard options.
private LocalePreference mLocale;
@@ -80,6 +88,8 @@ public class CaptionPropertiesFragment extends SettingsPreferenceFragment
private ColorPreference mEdgeColor;
private ColorPreference mBackgroundColor;
private ColorPreference mBackgroundOpacity;
+ private ColorPreference mWindowColor;
+ private ColorPreference mWindowOpacity;
private PreferenceCategory mCustom;
private boolean mShowingCustom;
@@ -119,10 +129,42 @@ public class CaptionPropertiesFragment extends SettingsPreferenceFragment
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
+ final boolean enabled = mCaptioningManager.isEnabled();
mPreviewText = (SubtitleView) view.findViewById(R.id.preview_text);
+ mPreviewText.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE);
+
+ mPreviewWindow = view.findViewById(R.id.preview_window);
+ mPreviewViewport = view.findViewById(R.id.preview_viewport);
+ mPreviewViewport.addOnLayoutChangeListener(new OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ refreshPreviewText();
+ }
+ });
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ final boolean enabled = mCaptioningManager.isEnabled();
+ SettingsActivity activity = (SettingsActivity) getActivity();
+ mSwitchBar = activity.getSwitchBar();
+ mSwitchBar.setCheckedInternal(enabled);
+ mToggleSwitch = mSwitchBar.getSwitch();
+
+ getPreferenceScreen().setEnabled(enabled);
- installActionBarToggleSwitch();
refreshPreviewText();
+
+ installSwitchBarToggleSwitch();
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ removeSwitchBarToggleSwitch();
}
private void refreshPreviewText() {
@@ -135,7 +177,7 @@ public class CaptionPropertiesFragment extends SettingsPreferenceFragment
final SubtitleView preview = mPreviewText;
if (preview != null) {
final int styleId = mCaptioningManager.getRawUserStyle();
- applyCaptionProperties(mCaptioningManager, preview, styleId);
+ applyCaptionProperties(mCaptioningManager, preview, mPreviewViewport, styleId);
final Locale locale = mCaptioningManager.getLocale();
if (locale != null) {
@@ -145,17 +187,34 @@ public class CaptionPropertiesFragment extends SettingsPreferenceFragment
} else {
preview.setText(R.string.captioning_preview_text);
}
+
+ final CaptionStyle style = mCaptioningManager.getUserStyle();
+ if (style.hasWindowColor()) {
+ mPreviewWindow.setBackgroundColor(style.windowColor);
+ } else {
+ final CaptionStyle defStyle = CaptionStyle.DEFAULT;
+ mPreviewWindow.setBackgroundColor(defStyle.windowColor);
+ }
}
}
- public static void applyCaptionProperties(
- CaptioningManager manager, SubtitleView previewText, int styleId) {
+ public static void applyCaptionProperties(CaptioningManager manager, SubtitleView previewText,
+ View previewWindow, int styleId) {
previewText.setStyle(styleId);
final Context context = previewText.getContext();
final ContentResolver cr = context.getContentResolver();
final float fontScale = manager.getFontScale();
- previewText.setTextSize(fontScale * DEFAULT_FONT_SIZE);
+ if (previewWindow != null) {
+ // Assume the viewport is clipped with a 16:9 aspect ratio.
+ final float virtualHeight = Math.max(9 * previewWindow.getWidth(),
+ 16 * previewWindow.getHeight()) / 16.0f;
+ previewText.setTextSize(virtualHeight * LINE_HEIGHT_RATIO * fontScale);
+ } else {
+ final float textSize = context.getResources().getDimension(
+ R.dimen.caption_preview_text_size);
+ previewText.setTextSize(textSize * fontScale);
+ }
final Locale locale = manager.getLocale();
if (locale != null) {
@@ -167,39 +226,32 @@ public class CaptionPropertiesFragment extends SettingsPreferenceFragment
}
}
- private void installActionBarToggleSwitch() {
- final Activity activity = getActivity();
- final ToggleSwitch toggleSwitch = new ToggleSwitch(activity);
-
- final int padding = getResources().getDimensionPixelSize(
- R.dimen.action_bar_switch_padding);
- toggleSwitch.setPaddingRelative(0, 0, padding, 0);
-
- final ActionBar actionBar = activity.getActionBar();
- actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM, ActionBar.DISPLAY_SHOW_CUSTOM);
-
- final ActionBar.LayoutParams params = new ActionBar.LayoutParams(
- ActionBar.LayoutParams.WRAP_CONTENT, ActionBar.LayoutParams.WRAP_CONTENT,
- Gravity.CENTER_VERTICAL | Gravity.END);
- actionBar.setCustomView(toggleSwitch, params);
-
- final boolean enabled = mCaptioningManager.isEnabled();
- getPreferenceScreen().setEnabled(enabled);
- mPreviewText.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE);
- toggleSwitch.setCheckedInternal(enabled);
- toggleSwitch.setOnBeforeCheckedChangeListener(new OnBeforeCheckedChangeListener() {
+ protected void onInstallSwitchBarToggleSwitch() {
+ mToggleSwitch.setOnBeforeCheckedChangeListener(new OnBeforeCheckedChangeListener() {
@Override
public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) {
- toggleSwitch.setCheckedInternal(checked);
+ mSwitchBar.setCheckedInternal(checked);
Settings.Secure.putInt(getActivity().getContentResolver(),
Settings.Secure.ACCESSIBILITY_CAPTIONING_ENABLED, checked ? 1 : 0);
getPreferenceScreen().setEnabled(checked);
- mPreviewText.setVisibility(checked ? View.VISIBLE : View.INVISIBLE);
+ if (mPreviewText != null) {
+ mPreviewText.setVisibility(checked ? View.VISIBLE : View.INVISIBLE);
+ }
return false;
}
});
}
+ private void installSwitchBarToggleSwitch() {
+ onInstallSwitchBarToggleSwitch();
+ mSwitchBar.show();
+ }
+
+ private void removeSwitchBarToggleSwitch() {
+ mSwitchBar.hide();
+ mToggleSwitch.setOnBeforeCheckedChangeListener(null);
+ }
+
private void initializeAllPreferences() {
mLocale = (LocalePreference) findPreference(PREF_LOCALE);
mFontSize = (ListPreference) findPreference(PREF_FONT_SIZE);
@@ -246,6 +298,14 @@ public class CaptionPropertiesFragment extends SettingsPreferenceFragment
mBackgroundOpacity.setTitles(opacityTitles);
mBackgroundOpacity.setValues(opacityValues);
+ mWindowColor = (ColorPreference) mCustom.findPreference(PREF_WINDOW_COLOR);
+ mWindowColor.setTitles(bgColorTitles);
+ mWindowColor.setValues(bgColorValues);
+
+ mWindowOpacity = (ColorPreference) mCustom.findPreference(PREF_WINDOW_OPACITY);
+ mWindowOpacity.setTitles(opacityTitles);
+ mWindowOpacity.setValues(opacityValues);
+
mEdgeType = (EdgeTypePreference) mCustom.findPreference(PREF_EDGE_TYPE);
mTypeface = (ListPreference) mCustom.findPreference(PREF_TYPEFACE);
}
@@ -257,6 +317,8 @@ public class CaptionPropertiesFragment extends SettingsPreferenceFragment
mEdgeColor.setOnValueChangedListener(this);
mBackgroundColor.setOnValueChangedListener(this);
mBackgroundOpacity.setOnValueChangedListener(this);
+ mWindowColor.setOnValueChangedListener(this);
+ mWindowOpacity.setOnValueChangedListener(this);
mEdgeType.setOnValueChangedListener(this);
mTypeface.setOnPreferenceChangeListener(this);
@@ -278,6 +340,7 @@ public class CaptionPropertiesFragment extends SettingsPreferenceFragment
parseColorOpacity(mForegroundColor, mForegroundOpacity, attrs.foregroundColor);
parseColorOpacity(mBackgroundColor, mBackgroundOpacity, attrs.backgroundColor);
+ parseColorOpacity(mWindowColor, mWindowOpacity, attrs.windowColor);
final String rawTypeface = attrs.mRawTypeface;
mTypeface.setValue(rawTypeface == null ? "" : rawTypeface);
@@ -305,7 +368,7 @@ public class CaptionPropertiesFragment extends SettingsPreferenceFragment
final int opacityValue = opacity.getValue();
final int value;
if (Color.alpha(colorValue) == 0) {
- value = Color.alpha(opacityValue);
+ value = colorValue & 0x00FFFF00 | Color.alpha(opacityValue);
} else {
value = colorValue & 0x00FFFFFF | opacityValue & 0xFF000000;
}
@@ -334,6 +397,10 @@ public class CaptionPropertiesFragment extends SettingsPreferenceFragment
final int merged = mergeColorOpacity(mBackgroundColor, mBackgroundOpacity);
Settings.Secure.putInt(
cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR, merged);
+ } else if (mWindowColor == preference || mWindowOpacity == preference) {
+ final int merged = mergeColorOpacity(mWindowColor, mWindowOpacity);
+ Settings.Secure.putInt(
+ cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_WINDOW_COLOR, merged);
} else if (mEdgeColor == preference) {
Settings.Secure.putInt(cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR, value);
} else if (mPreset == preference) {
diff --git a/src/com/android/settings/accessibility/ColorPreference.java b/src/com/android/settings/accessibility/ColorPreference.java
index f4a5ba7..39e555a 100644
--- a/src/com/android/settings/accessibility/ColorPreference.java
+++ b/src/com/android/settings/accessibility/ColorPreference.java
@@ -60,7 +60,7 @@ public class ColorPreference extends ListDialogPreference {
@Override
public boolean shouldDisableDependents() {
- return getValue() == Color.TRANSPARENT || super.shouldDisableDependents();
+ return Color.alpha(getValue()) == 0 || super.shouldDisableDependents();
}
@Override
diff --git a/src/com/android/settings/accessibility/EdgeTypePreference.java b/src/com/android/settings/accessibility/EdgeTypePreference.java
index ad71a76..3bff704 100644
--- a/src/com/android/settings/accessibility/EdgeTypePreference.java
+++ b/src/com/android/settings/accessibility/EdgeTypePreference.java
@@ -34,7 +34,7 @@ public class EdgeTypePreference extends ListDialogPreference {
private static final int DEFAULT_FOREGROUND_COLOR = Color.WHITE;
private static final int DEFAULT_BACKGROUND_COLOR = Color.TRANSPARENT;
private static final int DEFAULT_EDGE_COLOR = Color.BLACK;
- private static final float DEFAULT_FONT_SIZE = 96f;
+ private static final float DEFAULT_FONT_SIZE = 32f;
public EdgeTypePreference(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -57,7 +57,9 @@ public class EdgeTypePreference extends ListDialogPreference {
preview.setForegroundColor(DEFAULT_FOREGROUND_COLOR);
preview.setBackgroundColor(DEFAULT_BACKGROUND_COLOR);
- preview.setTextSize(DEFAULT_FONT_SIZE);
+
+ final float density = getContext().getResources().getDisplayMetrics().density;
+ preview.setTextSize(DEFAULT_FONT_SIZE * density);
final int value = getValueAt(index);
preview.setEdgeType(value);
diff --git a/src/com/android/settings/accessibility/ListDialogPreference.java b/src/com/android/settings/accessibility/ListDialogPreference.java
index a252454..2140d91 100644
--- a/src/com/android/settings/accessibility/ListDialogPreference.java
+++ b/src/com/android/settings/accessibility/ListDialogPreference.java
@@ -82,6 +82,10 @@ public abstract class ListDialogPreference extends DialogPreference {
*/
public void setValues(int[] values) {
mEntryValues = values;
+
+ if (mValueSet && mValueIndex == AbsListView.INVALID_POSITION) {
+ mValueIndex = getIndexForValue(mValue);
+ }
}
/**
@@ -172,10 +176,12 @@ public abstract class ListDialogPreference extends DialogPreference {
*/
protected int getIndexForValue(int value) {
final int[] values = mEntryValues;
- final int count = values.length;
- for (int i = 0; i < count; i++) {
- if (values[i] == value) {
- return i;
+ if (values != null) {
+ final int count = values.length;
+ for (int i = 0; i < count; i++) {
+ if (values[i] == value) {
+ return i;
+ }
}
}
diff --git a/src/com/android/settings/accessibility/LocalePreference.java b/src/com/android/settings/accessibility/LocalePreference.java
index 41cb1e5..0a94817 100644
--- a/src/com/android/settings/accessibility/LocalePreference.java
+++ b/src/com/android/settings/accessibility/LocalePreference.java
@@ -21,10 +21,12 @@ import android.content.res.Resources;
import android.preference.ListPreference;
import android.util.AttributeSet;
+import com.android.internal.app.LocalePicker;
import com.android.settings.R;
import java.text.Collator;
import java.util.Arrays;
+import java.util.List;
import java.util.Locale;
/**
@@ -43,108 +45,22 @@ public class LocalePreference extends ListPreference {
}
public void init(Context context) {
- final String[] systemLocales = Resources.getSystem().getAssets().getLocales();
- Arrays.sort(systemLocales);
-
- final Resources resources = context.getResources();
- final String[] specialLocaleCodes = resources.getStringArray(
- com.android.internal.R.array.special_locale_codes);
- final String[] specialLocaleNames = resources.getStringArray(
- com.android.internal.R.array.special_locale_names);
-
- int finalSize = 0;
-
- final int origSize = systemLocales.length;
- final LocaleInfo[] localeInfos = new LocaleInfo[origSize];
- for (int i = 0; i < origSize; i++) {
- final String localeStr = systemLocales[i];
- final int len = localeStr.length();
- if (len != 5) {
- continue;
- }
-
- final String language = localeStr.substring(0, 2);
- final String country = localeStr.substring(3, 5);
- final Locale l = new Locale(language, country);
-
- if (finalSize == 0) {
- localeInfos[finalSize++] = new LocaleInfo(l.getDisplayLanguage(l), l);
- } else {
- // check previous entry:
- // same lang and a country -> upgrade to full name and
- // insert ours with full name
- // diff lang -> insert ours with lang-only name
- final LocaleInfo previous = localeInfos[finalSize - 1];
- if (previous.locale.getLanguage().equals(language)
- && !previous.locale.getLanguage().equals("zz")) {
- previous.label = getDisplayName(
- localeInfos[finalSize - 1].locale, specialLocaleCodes,
- specialLocaleNames);
- localeInfos[finalSize++] = new LocaleInfo(getDisplayName(l,
- specialLocaleCodes, specialLocaleNames), l);
- } else {
- final String displayName;
- if (localeStr.equals("zz_ZZ")) {
- displayName = "[Developer] Accented English";
- } else if (localeStr.equals("zz_ZY")) {
- displayName = "[Developer] Fake Bi-Directional";
- } else {
- displayName = l.getDisplayLanguage(l);
- }
- localeInfos[finalSize++] = new LocaleInfo(displayName, l);
- }
- }
- }
+ List<LocalePicker.LocaleInfo> locales = LocalePicker.getAllAssetLocales(context,
+ false /* in developer mode */);
+ final int finalSize = locales.size();
final CharSequence[] entries = new CharSequence[finalSize + 1];
final CharSequence[] entryValues = new CharSequence[finalSize + 1];
- Arrays.sort(localeInfos, 0, finalSize);
-
- entries[0] = resources.getString(R.string.locale_default);
+ entries[0] = context.getResources().getString(R.string.locale_default);
entryValues[0] = "";
for (int i = 0; i < finalSize; i++) {
- final LocaleInfo info = localeInfos[i];
+ final LocalePicker.LocaleInfo info = locales.get(i);
entries[i + 1] = info.toString();
- entryValues[i + 1] = info.locale.toString();
+ entryValues[i + 1] = info.getLocale().toString();
}
setEntries(entries);
setEntryValues(entryValues);
}
-
- private static String getDisplayName(
- Locale l, String[] specialLocaleCodes, String[] specialLocaleNames) {
- String code = l.toString();
-
- for (int i = 0; i < specialLocaleCodes.length; i++) {
- if (specialLocaleCodes[i].equals(code)) {
- return specialLocaleNames[i];
- }
- }
-
- return l.getDisplayName(l);
- }
-
- private static class LocaleInfo implements Comparable<LocaleInfo> {
- private static final Collator sCollator = Collator.getInstance();
-
- public String label;
- public Locale locale;
-
- public LocaleInfo(String label, Locale locale) {
- this.label = label;
- this.locale = locale;
- }
-
- @Override
- public String toString() {
- return label;
- }
-
- @Override
- public int compareTo(LocaleInfo another) {
- return sCollator.compare(this.label, another.label);
- }
- }
}
diff --git a/src/com/android/settings/accessibility/PresetPreference.java b/src/com/android/settings/accessibility/PresetPreference.java
index 84aba6c..fe5ca68 100644
--- a/src/com/android/settings/accessibility/PresetPreference.java
+++ b/src/com/android/settings/accessibility/PresetPreference.java
@@ -27,7 +27,7 @@ import com.android.internal.widget.SubtitleView;
import com.android.settings.R;
public class PresetPreference extends ListDialogPreference {
- private static final float DEFAULT_FONT_SIZE = 96f;
+ private static final float DEFAULT_FONT_SIZE = 32f;
private final CaptioningManager mCaptioningManager;
@@ -49,12 +49,14 @@ public class PresetPreference extends ListDialogPreference {
@Override
protected void onBindListItem(View view, int index) {
+ final View previewViewport = view.findViewById(R.id.preview_viewport);
final SubtitleView previewText = (SubtitleView) view.findViewById(R.id.preview);
final int value = getValueAt(index);
CaptionPropertiesFragment.applyCaptionProperties(
- mCaptioningManager, previewText, value);
+ mCaptioningManager, previewText, previewViewport, value);
- previewText.setTextSize(DEFAULT_FONT_SIZE);
+ final float density = getContext().getResources().getDisplayMetrics().density;
+ previewText.setTextSize(DEFAULT_FONT_SIZE * density);
final CharSequence title = getTitleAt(index);
if (title != null) {
diff --git a/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java b/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java
index 0c568f0..08fba67 100644
--- a/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java
+++ b/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java
@@ -17,8 +17,10 @@
package com.android.settings.accessibility;
import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
+import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
@@ -36,8 +38,11 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.settings.ConfirmDeviceCredentialActivity;
import com.android.settings.R;
-import com.android.settings.accessibility.ToggleSwitch.OnBeforeCheckedChangeListener;
+import com.android.settings.widget.ToggleSwitch;
+import com.android.settings.widget.ToggleSwitch.OnBeforeCheckedChangeListener;
import java.util.Collections;
import java.util.HashSet;
@@ -50,6 +55,10 @@ public class ToggleAccessibilityServicePreferenceFragment
private static final int DIALOG_ID_ENABLE_WARNING = 1;
private static final int DIALOG_ID_DISABLE_WARNING = 2;
+ public static final int ACTIVITY_REQUEST_CONFIRM_CREDENTIAL_FOR_WEAKER_ENCRYPTION = 1;
+
+ private LockPatternUtils mLockPatternUtils;
+
private final SettingsContentObserver mSettingsContentObserver =
new SettingsContentObserver(new Handler()) {
@Override
@@ -57,7 +66,7 @@ public class ToggleAccessibilityServicePreferenceFragment
String settingValue = Settings.Secure.getString(getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
final boolean enabled = settingValue.contains(mComponentName.flattenToString());
- mToggleSwitch.setCheckedInternal(enabled);
+ mSwitchBar.setCheckedInternal(enabled);
}
};
@@ -66,6 +75,12 @@ public class ToggleAccessibilityServicePreferenceFragment
private int mShownDialogId;
@Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mLockPatternUtils = new LockPatternUtils(getActivity());
+ }
+
+ @Override
public void onResume() {
mSettingsContentObserver.register(getContentResolver());
super.onResume();
@@ -154,41 +169,42 @@ public class ToggleAccessibilityServicePreferenceFragment
public Dialog onCreateDialog(int dialogId) {
switch (dialogId) {
case DIALOG_ID_ENABLE_WARNING: {
- mShownDialogId = DIALOG_ID_ENABLE_WARNING;
- AccessibilityServiceInfo info = getAccessibilityServiceInfo();
- if (info == null) {
- return null;
+ mShownDialogId = DIALOG_ID_ENABLE_WARNING;
+ AccessibilityServiceInfo info = getAccessibilityServiceInfo();
+ if (info == null) {
+ return null;
+ }
+ AlertDialog ad = new AlertDialog.Builder(getActivity())
+ .setTitle(getString(R.string.enable_service_title,
+ info.getResolveInfo().loadLabel(getPackageManager())))
+ .setView(createEnableDialogContentView(info))
+ .setCancelable(true)
+ .setPositiveButton(android.R.string.ok, this)
+ .setNegativeButton(android.R.string.cancel, this)
+ .create();
+ ad.create();
+ ad.getButton(AlertDialog.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
+ return ad;
}
- return new AlertDialog.Builder(getActivity())
- .setTitle(getString(R.string.enable_service_title,
- info.getResolveInfo().loadLabel(getPackageManager())))
- .setIconAttribute(android.R.attr.alertDialogIcon)
- .setView(createEnableDialogContentView(info))
- .setCancelable(true)
- .setPositiveButton(android.R.string.ok, this)
- .setNegativeButton(android.R.string.cancel, this)
- .create();
- }
case DIALOG_ID_DISABLE_WARNING: {
- mShownDialogId = DIALOG_ID_DISABLE_WARNING;
- AccessibilityServiceInfo info = getAccessibilityServiceInfo();
- if (info == null) {
- return null;
+ mShownDialogId = DIALOG_ID_DISABLE_WARNING;
+ AccessibilityServiceInfo info = getAccessibilityServiceInfo();
+ if (info == null) {
+ return null;
+ }
+ return new AlertDialog.Builder(getActivity())
+ .setTitle(getString(R.string.disable_service_title,
+ info.getResolveInfo().loadLabel(getPackageManager())))
+ .setMessage(getString(R.string.disable_service_message,
+ info.getResolveInfo().loadLabel(getPackageManager())))
+ .setCancelable(true)
+ .setPositiveButton(android.R.string.ok, this)
+ .setNegativeButton(android.R.string.cancel, this)
+ .create();
}
- return new AlertDialog.Builder(getActivity())
- .setTitle(getString(R.string.disable_service_title,
- info.getResolveInfo().loadLabel(getPackageManager())))
- .setIconAttribute(android.R.attr.alertDialogIcon)
- .setMessage(getString(R.string.disable_service_message,
- info.getResolveInfo().loadLabel(getPackageManager())))
- .setCancelable(true)
- .setPositiveButton(android.R.string.ok, this)
- .setNegativeButton(android.R.string.cancel, this)
- .create();
- }
default: {
- throw new IllegalArgumentException();
- }
+ throw new IllegalArgumentException();
+ }
}
}
@@ -199,6 +215,17 @@ public class ToggleAccessibilityServicePreferenceFragment
View content = inflater.inflate(R.layout.enable_accessibility_service_dialog_content,
null);
+ TextView encryptionWarningView = (TextView) content.findViewById(
+ R.id.encryption_warning);
+ if (LockPatternUtils.isDeviceEncrypted()) {
+ String text = getString(R.string.enable_service_encryption_warning,
+ info.getResolveInfo().loadLabel(getPackageManager()));
+ encryptionWarningView.setText(text);
+ encryptionWarningView.setVisibility(View.VISIBLE);
+ } else {
+ encryptionWarningView.setVisibility(View.GONE);
+ }
+
TextView capabilitiesHeaderView = (TextView) content.findViewById(
R.id.capabilities_header);
capabilitiesHeaderView.setText(getString(R.string.capabilities_list_title,
@@ -256,38 +283,84 @@ public class ToggleAccessibilityServicePreferenceFragment
}
@Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == ACTIVITY_REQUEST_CONFIRM_CREDENTIAL_FOR_WEAKER_ENCRYPTION) {
+ if (resultCode == Activity.RESULT_OK) {
+ handleConfirmServiceEnabled(true);
+ // The user confirmed that they accept weaker encryption when
+ // enabling the accessibility service, so change encryption.
+ // Since we came here asynchronously, check encryption again.
+ if (LockPatternUtils.isDeviceEncrypted()) {
+ mLockPatternUtils.clearEncryptionPassword();
+ Settings.Global.putInt(getContentResolver(),
+ Settings.Global.REQUIRE_PASSWORD_TO_DECRYPT, 0);
+ }
+ } else {
+ handleConfirmServiceEnabled(false);
+ }
+ }
+ }
+
+ @Override
public void onClick(DialogInterface dialog, int which) {
final boolean checked;
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
- checked = (mShownDialogId == DIALOG_ID_ENABLE_WARNING);
- mToggleSwitch.setCheckedInternal(checked);
- getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, checked);
- onPreferenceToggled(mPreferenceKey, checked);
+ if (mShownDialogId == DIALOG_ID_ENABLE_WARNING) {
+ if (LockPatternUtils.isDeviceEncrypted()) {
+ String title = createConfirmCredentialReasonMessage();
+ Intent intent = ConfirmDeviceCredentialActivity.createIntent(title, null);
+ startActivityForResult(intent,
+ ACTIVITY_REQUEST_CONFIRM_CREDENTIAL_FOR_WEAKER_ENCRYPTION);
+ } else {
+ handleConfirmServiceEnabled(true);
+ }
+ } else {
+ handleConfirmServiceEnabled(false);
+ }
break;
case DialogInterface.BUTTON_NEGATIVE:
checked = (mShownDialogId == DIALOG_ID_DISABLE_WARNING);
- mToggleSwitch.setCheckedInternal(checked);
- getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, checked);
- onPreferenceToggled(mPreferenceKey, checked);
+ handleConfirmServiceEnabled(checked);
break;
default:
throw new IllegalArgumentException();
}
}
+ private void handleConfirmServiceEnabled(boolean confirmed) {
+ mSwitchBar.setCheckedInternal(confirmed);
+ getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, confirmed);
+ onPreferenceToggled(mPreferenceKey, confirmed);
+ }
+
+ private String createConfirmCredentialReasonMessage() {
+ int resId = R.string.enable_service_password_reason;
+ switch (mLockPatternUtils.getKeyguardStoredPasswordQuality()) {
+ case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING: {
+ resId = R.string.enable_service_pattern_reason;
+ } break;
+ case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
+ case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX: {
+ resId = R.string.enable_service_pin_reason;
+ } break;
+ }
+ return getString(resId, getAccessibilityServiceInfo().getResolveInfo()
+ .loadLabel(getPackageManager()));
+ }
+
@Override
- protected void onInstallActionBarToggleSwitch() {
- super.onInstallActionBarToggleSwitch();
+ protected void onInstallSwitchBarToggleSwitch() {
+ super.onInstallSwitchBarToggleSwitch();
mToggleSwitch.setOnBeforeCheckedChangeListener(new OnBeforeCheckedChangeListener() {
@Override
public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) {
if (checked) {
- toggleSwitch.setCheckedInternal(false);
+ mSwitchBar.setCheckedInternal(false);
getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, false);
showDialog(DIALOG_ID_ENABLE_WARNING);
} else {
- toggleSwitch.setCheckedInternal(true);
+ mSwitchBar.setCheckedInternal(true);
getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, true);
showDialog(DIALOG_ID_DISABLE_WARNING);
}
diff --git a/src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java
new file mode 100644
index 0000000..1f7ecf7
--- /dev/null
+++ b/src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java
@@ -0,0 +1,103 @@
+/*
+ * 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.accessibility;
+
+import android.os.Bundle;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.provider.Settings;
+import android.view.View;
+import android.view.accessibility.AccessibilityManager;
+
+import android.widget.Switch;
+import com.android.settings.R;
+import com.android.settings.widget.SwitchBar;
+
+public class ToggleDaltonizerPreferenceFragment extends ToggleFeaturePreferenceFragment
+ implements Preference.OnPreferenceChangeListener, SwitchBar.OnSwitchChangeListener {
+ private static final String ENABLED = Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED;
+ private static final String TYPE = Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER;
+ private static final int DEFAULT_TYPE = AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY;
+
+ private ListPreference mType;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ addPreferencesFromResource(R.xml.accessibility_daltonizer_settings);
+
+ mType = (ListPreference) findPreference("type");
+
+ initPreferences();
+ }
+
+ @Override
+ protected void onPreferenceToggled(String preferenceKey, boolean enabled) {
+ Settings.Secure.putInt(getContentResolver(), ENABLED, enabled ? 1 : 0);
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if (preference == mType) {
+ Settings.Secure.putInt(getContentResolver(), TYPE, Integer.parseInt((String) newValue));
+ preference.setSummary("%s");
+ }
+
+ return true;
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ setTitle(getString(R.string.accessibility_display_daltonizer_preference_title));
+ }
+
+ @Override
+ protected void onInstallSwitchBarToggleSwitch() {
+ super.onInstallSwitchBarToggleSwitch();
+
+ mSwitchBar.setCheckedInternal(
+ Settings.Secure.getInt(getContentResolver(), ENABLED, 0) == 1);
+ mSwitchBar.addOnSwitchChangeListener(this);
+ }
+
+ @Override
+ protected void onRemoveSwitchBarToggleSwitch() {
+ super.onRemoveSwitchBarToggleSwitch();
+ mSwitchBar.removeOnSwitchChangeListener(this);
+ }
+
+ private void initPreferences() {
+ final String value = Integer.toString(
+ Settings.Secure.getInt(getContentResolver(), TYPE, DEFAULT_TYPE));
+ mType.setValue(value);
+ mType.setOnPreferenceChangeListener(this);
+ final int index = mType.findIndexOfValue(value);
+ if (index < 0) {
+ // We're using a mode controlled by developer preferences.
+ mType.setSummary(getString(R.string.daltonizer_type_overridden,
+ getString(R.string.simulate_color_space)));
+ }
+ }
+
+ @Override
+ public void onSwitchChanged(Switch switchView, boolean isChecked) {
+ onPreferenceToggled(mPreferenceKey, isChecked);
+ }
+}
diff --git a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java
index 2fbbabd..b23035b 100644
--- a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java
+++ b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java
@@ -16,16 +16,12 @@
package com.android.settings.accessibility;
-import android.app.ActionBar;
-import android.app.Activity;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.preference.Preference;
-import android.preference.PreferenceActivity;
import android.preference.PreferenceScreen;
-import android.view.Gravity;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
@@ -35,11 +31,15 @@ import android.view.accessibility.AccessibilityManager;
import android.widget.TextView;
import com.android.settings.R;
+import com.android.settings.SettingsActivity;
import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.widget.SwitchBar;
+import com.android.settings.widget.ToggleSwitch;
public abstract class ToggleFeaturePreferenceFragment
extends SettingsPreferenceFragment {
+ protected SwitchBar mSwitchBar;
protected ToggleSwitch mToggleSwitch;
protected String mPreferenceKey;
@@ -55,10 +55,10 @@ public abstract class ToggleFeaturePreferenceFragment
getActivity());
setPreferenceScreen(preferenceScreen);
mSummaryPreference = new Preference(getActivity()) {
- @Override
+ @Override
protected void onBindView(View view) {
super.onBindView(view);
- TextView summaryView = (TextView) view.findViewById(R.id.summary);
+ final TextView summaryView = (TextView) view.findViewById(android.R.id.summary);
summaryView.setText(getSummary());
sendAccessibilityEvent(summaryView);
}
@@ -86,18 +86,26 @@ public abstract class ToggleFeaturePreferenceFragment
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
- onInstallActionBarToggleSwitch();
+
+ SettingsActivity activity = (SettingsActivity) getActivity();
+ mSwitchBar = activity.getSwitchBar();
+ mToggleSwitch = mSwitchBar.getSwitch();
+
onProcessArguments(getArguments());
- // Set a transparent drawable to prevent use of the default one.
- getListView().setSelector(new ColorDrawable(Color.TRANSPARENT));
- getListView().setDivider(null);
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ installActionBarToggleSwitch();
}
@Override
public void onDestroyView() {
- getActivity().getActionBar().setCustomView(null);
- mToggleSwitch.setOnBeforeCheckedChangeListener(null);
super.onDestroyView();
+
+ removeActionBarToggleSwitch();
}
protected abstract void onPreferenceToggled(String preferenceKey, boolean enabled);
@@ -110,38 +118,60 @@ public abstract class ToggleFeaturePreferenceFragment
menuItem.setIntent(mSettingsIntent);
}
- protected void onInstallActionBarToggleSwitch() {
- mToggleSwitch = createAndAddActionBarToggleSwitch(getActivity());
+ protected void onInstallSwitchBarToggleSwitch() {
+ // Implement this to set a checked listener.
+ }
+
+ protected void onRemoveSwitchBarToggleSwitch() {
+ // Implement this to reset a checked listener.
+ }
+
+ private void installActionBarToggleSwitch() {
+ mSwitchBar.show();
+ onInstallSwitchBarToggleSwitch();
+ }
+
+ private void removeActionBarToggleSwitch() {
+ mToggleSwitch.setOnBeforeCheckedChangeListener(null);
+ onRemoveSwitchBarToggleSwitch();
+ mSwitchBar.hide();
}
- private ToggleSwitch createAndAddActionBarToggleSwitch(Activity activity) {
- ToggleSwitch toggleSwitch = new ToggleSwitch(activity);
- final int padding = activity.getResources().getDimensionPixelSize(
- R.dimen.action_bar_switch_padding);
- toggleSwitch.setPaddingRelative(0, 0, padding, 0);
- activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
- ActionBar.DISPLAY_SHOW_CUSTOM);
- activity.getActionBar().setCustomView(toggleSwitch,
- new ActionBar.LayoutParams(ActionBar.LayoutParams.WRAP_CONTENT,
- ActionBar.LayoutParams.WRAP_CONTENT,
- Gravity.CENTER_VERTICAL | Gravity.END));
- return toggleSwitch;
+ public void setTitle(String title) {
+ getActivity().setTitle(title);
}
protected void onProcessArguments(Bundle arguments) {
+ if (arguments == null) {
+ getPreferenceScreen().removePreference(mSummaryPreference);
+ return;
+ }
+
// Key.
mPreferenceKey = arguments.getString(AccessibilitySettings.EXTRA_PREFERENCE_KEY);
+
// Enabled.
- final boolean enabled = arguments.getBoolean(AccessibilitySettings.EXTRA_CHECKED);
- mToggleSwitch.setCheckedInternal(enabled);
+ if (arguments.containsKey(AccessibilitySettings.EXTRA_CHECKED)) {
+ final boolean enabled = arguments.getBoolean(AccessibilitySettings.EXTRA_CHECKED);
+ mSwitchBar.setCheckedInternal(enabled);
+ }
+
// Title.
- PreferenceActivity activity = (PreferenceActivity) getActivity();
- if (!activity.onIsMultiPane() || activity.onIsHidingHeaders()) {
- String title = arguments.getString(AccessibilitySettings.EXTRA_TITLE);
- getActivity().setTitle(title);
+ if (arguments.containsKey(AccessibilitySettings.EXTRA_TITLE)) {
+ setTitle(arguments.getString(AccessibilitySettings.EXTRA_TITLE));
}
+
// Summary.
- CharSequence summary = arguments.getCharSequence(AccessibilitySettings.EXTRA_SUMMARY);
- mSummaryPreference.setSummary(summary);
+ if (arguments.containsKey(AccessibilitySettings.EXTRA_SUMMARY)) {
+ final CharSequence summary = arguments.getCharSequence(
+ AccessibilitySettings.EXTRA_SUMMARY);
+ mSummaryPreference.setSummary(summary);
+
+ // Set a transparent drawable to prevent use of the default one.
+ getListView().setSelector(new ColorDrawable(Color.TRANSPARENT));
+ getListView().setDivider(null);
+ } else {
+ getPreferenceScreen().removePreference(mSummaryPreference);
+ }
}
}
diff --git a/src/com/android/settings/accessibility/ToggleGlobalGesturePreferenceFragment.java b/src/com/android/settings/accessibility/ToggleGlobalGesturePreferenceFragment.java
index f4ac2cc..7fb1625 100644
--- a/src/com/android/settings/accessibility/ToggleGlobalGesturePreferenceFragment.java
+++ b/src/com/android/settings/accessibility/ToggleGlobalGesturePreferenceFragment.java
@@ -18,7 +18,8 @@ package com.android.settings.accessibility;
import android.provider.Settings;
-import com.android.settings.accessibility.ToggleSwitch.OnBeforeCheckedChangeListener;
+import com.android.settings.widget.ToggleSwitch;
+import com.android.settings.widget.ToggleSwitch.OnBeforeCheckedChangeListener;
public class ToggleGlobalGesturePreferenceFragment
extends ToggleFeaturePreferenceFragment {
@@ -29,12 +30,12 @@ public class ToggleGlobalGesturePreferenceFragment
}
@Override
- protected void onInstallActionBarToggleSwitch() {
- super.onInstallActionBarToggleSwitch();
+ protected void onInstallSwitchBarToggleSwitch() {
+ super.onInstallSwitchBarToggleSwitch();
mToggleSwitch.setOnBeforeCheckedChangeListener(new OnBeforeCheckedChangeListener() {
@Override
public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) {
- toggleSwitch.setCheckedInternal(checked);
+ mSwitchBar.setCheckedInternal(checked);
getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, checked);
onPreferenceToggled(mPreferenceKey, checked);
return false;
diff --git a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java
index 27d07d2..4650c06 100644
--- a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java
+++ b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java
@@ -18,7 +18,8 @@ package com.android.settings.accessibility;
import android.provider.Settings;
-import com.android.settings.accessibility.ToggleSwitch.OnBeforeCheckedChangeListener;
+import com.android.settings.widget.ToggleSwitch;
+import com.android.settings.widget.ToggleSwitch.OnBeforeCheckedChangeListener;
public class ToggleScreenMagnificationPreferenceFragment
extends ToggleFeaturePreferenceFragment {
@@ -29,12 +30,12 @@ public class ToggleScreenMagnificationPreferenceFragment
}
@Override
- protected void onInstallActionBarToggleSwitch() {
- super.onInstallActionBarToggleSwitch();
+ protected void onInstallSwitchBarToggleSwitch() {
+ super.onInstallSwitchBarToggleSwitch();
mToggleSwitch.setOnBeforeCheckedChangeListener(new OnBeforeCheckedChangeListener() {
@Override
public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) {
- toggleSwitch.setCheckedInternal(checked);
+ mSwitchBar.setCheckedInternal(checked);
getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, checked);
onPreferenceToggled(mPreferenceKey, checked);
return false;
diff --git a/src/com/android/settings/accounts/AccountPreferenceBase.java b/src/com/android/settings/accounts/AccountPreferenceBase.java
index 2759a8f..bb60871 100644
--- a/src/com/android/settings/accounts/AccountPreferenceBase.java
+++ b/src/com/android/settings/accounts/AccountPreferenceBase.java
@@ -1,4 +1,5 @@
/*
+
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,50 +17,63 @@
package com.android.settings.accounts;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
-
-import com.android.settings.SettingsPreferenceFragment;
-import com.google.android.collect.Maps;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
import android.accounts.AuthenticatorDescription;
-import android.accounts.OnAccountsUpdateListener;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
-import android.content.SyncAdapterType;
import android.content.SyncStatusObserver;
import android.content.pm.PackageManager;
import android.content.res.Resources;
+import android.content.res.Resources.Theme;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
-import android.preference.PreferenceActivity;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.preference.PreferenceScreen;
import android.text.format.DateFormat;
import android.util.Log;
+import android.view.ContextThemeWrapper;
+
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.Utils;
+
+import java.util.ArrayList;
+import java.util.Date;
class AccountPreferenceBase extends SettingsPreferenceFragment
- implements OnAccountsUpdateListener {
+ implements AuthenticatorHelper.OnAccountsUpdateListener {
protected static final String TAG = "AccountSettings";
+
public static final String AUTHORITIES_FILTER_KEY = "authorities";
public static final String ACCOUNT_TYPES_FILTER_KEY = "account_types";
+
private final Handler mHandler = new Handler();
+
+ private UserManager mUm;
private Object mStatusChangeListenerHandle;
- private HashMap<String, ArrayList<String>> mAccountTypeToAuthorities = null;
- private AuthenticatorHelper mAuthenticatorHelper = new AuthenticatorHelper();
+ protected AuthenticatorHelper mAuthenticatorHelper;
+ protected UserHandle mUserHandle;
+
private java.text.DateFormat mDateFormat;
private java.text.DateFormat mTimeFormat;
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ mUm = (UserManager) getSystemService(Context.USER_SERVICE);
+ final Activity activity = getActivity();
+ mUserHandle = Utils.getSecureTargetUser(activity.getActivityToken(), mUm, getArguments(),
+ activity.getIntent().getExtras());
+ mAuthenticatorHelper = new AuthenticatorHelper(activity, mUserHandle, mUm, this);
+ }
+
/**
* Overload to handle account updates.
*/
- public void onAccountsUpdated(Account[] accounts) {
+ @Override
+ public void onAccountsUpdate(UserHandle userHandle) {
}
@@ -115,24 +129,7 @@ class AccountPreferenceBase extends SettingsPreferenceFragment
};
public ArrayList<String> getAuthoritiesForAccountType(String type) {
- if (mAccountTypeToAuthorities == null) {
- mAccountTypeToAuthorities = Maps.newHashMap();
- SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypes();
- for (int i = 0, n = syncAdapters.length; i < n; i++) {
- final SyncAdapterType sa = syncAdapters[i];
- ArrayList<String> authorities = mAccountTypeToAuthorities.get(sa.accountType);
- if (authorities == null) {
- authorities = new ArrayList<String>();
- mAccountTypeToAuthorities.put(sa.accountType, authorities);
- }
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.d(TAG, "added authority " + sa.authority + " to accountType "
- + sa.accountType);
- }
- authorities.add(sa.authority);
- }
- }
- return mAccountTypeToAuthorities.get(type);
+ return mAuthenticatorHelper.getAuthoritiesForAccountType(type);
}
/**
@@ -148,8 +145,19 @@ class AccountPreferenceBase extends SettingsPreferenceFragment
try {
desc = mAuthenticatorHelper.getAccountTypeDescription(accountType);
if (desc != null && desc.accountPreferencesId != 0) {
- Context authContext = getActivity().createPackageContext(desc.packageName, 0);
- prefs = getPreferenceManager().inflateFromResource(authContext,
+ // Load the context of the target package, then apply the
+ // base Settings theme (no references to local resources)
+ // and create a context theme wrapper so that we get the
+ // 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 Context targetCtx = getActivity().createPackageContextAsUser(
+ desc.packageName, 0, mUserHandle);
+ final Theme baseTheme = getResources().newTheme();
+ baseTheme.applyStyle(com.android.settings.R.style.Theme_SettingsBase, true);
+ final Context themedCtx = new ContextThemeWrapper(targetCtx, 0);
+ themedCtx.getTheme().setTo(baseTheme);
+ prefs = getPreferenceManager().inflateFromResource(themedCtx,
desc.accountPreferencesId, parent);
}
} catch (PackageManager.NameNotFoundException e) {
diff --git a/src/com/android/settings/accounts/AccountSettings.java b/src/com/android/settings/accounts/AccountSettings.java
new file mode 100644
index 0000000..8a183da
--- /dev/null
+++ b/src/com/android/settings/accounts/AccountSettings.java
@@ -0,0 +1,588 @@
+/*
+ * 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.accounts;
+
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.app.ActivityManager;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.UserInfo;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.Process;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceClickListener;
+import android.preference.PreferenceGroup;
+import android.preference.PreferenceCategory;
+import android.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.Utils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import static android.content.Intent.EXTRA_USER;
+import static android.os.UserManager.DISALLOW_MODIFY_ACCOUNTS;
+import static android.provider.Settings.EXTRA_AUTHORITIES;
+
+/**
+ * Settings screen for the account types on the device.
+ * This shows all account types available for personal and work profiles.
+ *
+ * An extra {@link UserHandle} can be specified in the intent as {@link EXTRA_USER}, if the user for
+ * which the action needs to be performed is different to the one the Settings App will run in.
+ */
+public class AccountSettings extends SettingsPreferenceFragment
+ implements AuthenticatorHelper.OnAccountsUpdateListener,
+ OnPreferenceClickListener {
+ public static final String TAG = "AccountSettings";
+
+ private static final String KEY_ACCOUNT = "account";
+
+ private static final String ADD_ACCOUNT_ACTION = "android.settings.ADD_ACCOUNT_SETTINGS";
+ private static final String TAG_CONFIRM_AUTO_SYNC_CHANGE = "confirmAutoSyncChange";
+
+ private static final int ORDER_LAST = 1001;
+ private static final int ORDER_NEXT_TO_LAST = 1000;
+
+ private UserManager mUm;
+ private SparseArray<ProfileData> mProfiles = new SparseArray<ProfileData>();
+ private ManagedProfileBroadcastReceiver mManagedProfileBroadcastReceiver
+ = new ManagedProfileBroadcastReceiver();
+ private Preference mProfileNotAvailablePreference;
+ private String[] mAuthorities;
+ private int mAuthoritiesCount = 0;
+
+ /**
+ * Holds data related to the accounts belonging to one profile.
+ */
+ private static class ProfileData {
+ /**
+ * The preference that displays the accounts.
+ */
+ public PreferenceGroup preferenceGroup;
+ /**
+ * The preference that displays the add account button.
+ */
+ public Preference addAccountPreference;
+ /**
+ * The preference that displays the button to remove the managed profile
+ */
+ public Preference removeWorkProfilePreference;
+ /**
+ * The {@link AuthenticatorHelper} that holds accounts data for this profile.
+ */
+ public AuthenticatorHelper authenticatorHelper;
+ /**
+ * The {@link UserInfo} of the profile.
+ */
+ public UserInfo userInfo;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mUm = (UserManager) getSystemService(Context.USER_SERVICE);
+ mProfileNotAvailablePreference = new Preference(getActivity());
+ mAuthorities = getActivity().getIntent().getStringArrayExtra(EXTRA_AUTHORITIES);
+ if (mAuthorities != null) {
+ mAuthoritiesCount = mAuthorities.length;
+ }
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ inflater.inflate(R.menu.account_settings, menu);
+ super.onCreateOptionsMenu(menu, inflater);
+ }
+
+ @Override
+ public void onPrepareOptionsMenu(Menu menu) {
+ final UserHandle currentProfile = Process.myUserHandle();
+ if (mProfiles.size() == 1) {
+ menu.findItem(R.id.account_settings_menu_auto_sync)
+ .setVisible(true)
+ .setOnMenuItemClickListener(new MasterSyncStateClickListener(currentProfile))
+ .setChecked(ContentResolver.getMasterSyncAutomaticallyAsUser(
+ currentProfile.getIdentifier()));
+ menu.findItem(R.id.account_settings_menu_auto_sync_personal).setVisible(false);
+ menu.findItem(R.id.account_settings_menu_auto_sync_work).setVisible(false);
+ } else if (mProfiles.size() > 1) {
+ // We assume there's only one managed profile, otherwise UI needs to change
+ final UserHandle managedProfile = mProfiles.valueAt(1).userInfo.getUserHandle();
+
+ menu.findItem(R.id.account_settings_menu_auto_sync_personal)
+ .setVisible(true)
+ .setOnMenuItemClickListener(new MasterSyncStateClickListener(currentProfile))
+ .setChecked(ContentResolver.getMasterSyncAutomaticallyAsUser(
+ currentProfile.getIdentifier()));
+ menu.findItem(R.id.account_settings_menu_auto_sync_work)
+ .setVisible(true)
+ .setOnMenuItemClickListener(new MasterSyncStateClickListener(managedProfile))
+ .setChecked(ContentResolver.getMasterSyncAutomaticallyAsUser(
+ managedProfile.getIdentifier()));
+ menu.findItem(R.id.account_settings_menu_auto_sync).setVisible(false);
+ } else {
+ Log.w(TAG, "Method onPrepareOptionsMenu called before mProfiles was initialized");
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ updateUi();
+ mManagedProfileBroadcastReceiver.register(getActivity());
+ listenToAccountUpdates();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ stopListeningToAccountUpdates();
+ mManagedProfileBroadcastReceiver.unregister(getActivity());
+ cleanUpPreferences();
+ }
+
+ @Override
+ public void onAccountsUpdate(UserHandle userHandle) {
+ final ProfileData profileData = mProfiles.get(userHandle.getIdentifier());
+ if (profileData != null) {
+ updateAccountTypes(profileData);
+ } else {
+ Log.w(TAG, "Missing Settings screen for: " + userHandle.getIdentifier());
+ }
+ }
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ // Check the preference
+ final int count = mProfiles.size();
+ for (int i = 0; i < count; i++) {
+ ProfileData profileData = mProfiles.valueAt(i);
+ if (preference == profileData.addAccountPreference) {
+ Intent intent = new Intent(ADD_ACCOUNT_ACTION);
+ intent.putExtra(EXTRA_USER, profileData.userInfo.getUserHandle());
+ intent.putExtra(EXTRA_AUTHORITIES, mAuthorities);
+ startActivity(intent);
+ return true;
+ }
+ if (preference == profileData.removeWorkProfilePreference) {
+ final int userId = profileData.userInfo.id;
+ Utils.createRemoveConfirmationDialog(getActivity(), userId,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ mUm.removeUser(userId);
+ }
+ }
+ ).show();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void updateUi() {
+ // Load the preferences from an XML resource
+ addPreferencesFromResource(R.xml.account_settings);
+
+ if (Utils.isManagedProfile(mUm)) {
+ // This should not happen
+ Log.e(TAG, "We should not be showing settings for a managed profile");
+ finish();
+ return;
+ }
+
+ final PreferenceScreen preferenceScreen = (PreferenceScreen) findPreference(KEY_ACCOUNT);
+ if(mUm.isLinkedUser()) {
+ // Restricted user or similar
+ UserInfo userInfo = mUm.getUserInfo(UserHandle.myUserId());
+ updateProfileUi(userInfo, false /* no category needed */, preferenceScreen);
+ } else {
+ List<UserInfo> profiles = mUm.getProfiles(UserHandle.myUserId());
+ final int profilesCount = profiles.size();
+ final boolean addCategory = profilesCount > 1;
+ for (int i = 0; i < profilesCount; i++) {
+ updateProfileUi(profiles.get(i), addCategory, preferenceScreen);
+ }
+ }
+
+ // Add all preferences, starting with one for the primary profile.
+ // Note that we're relying on the ordering given by the SparseArray keys, and on the
+ // value of UserHandle.USER_OWNER being smaller than all the rest.
+ final int profilesCount = mProfiles.size();
+ for (int i = 0; i < profilesCount; i++) {
+ ProfileData profileData = mProfiles.valueAt(i);
+ if (!profileData.preferenceGroup.equals(preferenceScreen)) {
+ preferenceScreen.addPreference(profileData.preferenceGroup);
+ }
+ updateAccountTypes(profileData);
+ }
+ }
+
+ private void updateProfileUi(final UserInfo userInfo, boolean addCategory,
+ PreferenceScreen parent) {
+ final Context context = getActivity();
+ final ProfileData profileData = new ProfileData();
+ profileData.userInfo = userInfo;
+ if (addCategory) {
+ profileData.preferenceGroup = new PreferenceCategory(context);
+ profileData.preferenceGroup.setTitle(userInfo.isManagedProfile()
+ ? R.string.category_work : R.string.category_personal);
+ parent.addPreference(profileData.preferenceGroup);
+ } else {
+ profileData.preferenceGroup = parent;
+ }
+ if (userInfo.isEnabled()) {
+ profileData.authenticatorHelper = new AuthenticatorHelper(context,
+ userInfo.getUserHandle(), mUm, this);
+ if (!mUm.hasUserRestriction(DISALLOW_MODIFY_ACCOUNTS, userInfo.getUserHandle())) {
+ profileData.addAccountPreference = newAddAccountPreference(context);
+ }
+ }
+ if (userInfo.isManagedProfile()) {
+ profileData.removeWorkProfilePreference = newRemoveWorkProfilePreference(context);
+ }
+ mProfiles.put(userInfo.id, profileData);
+ }
+
+ private Preference newAddAccountPreference(Context context) {
+ Preference preference = new Preference(context);
+ preference.setTitle(R.string.add_account_label);
+ preference.setIcon(R.drawable.ic_menu_add_dark);
+ preference.setOnPreferenceClickListener(this);
+ preference.setOrder(ORDER_NEXT_TO_LAST);
+ return preference;
+ }
+
+ private Preference newRemoveWorkProfilePreference(Context context) {
+ Preference preference = new Preference(context);
+ preference.setTitle(R.string.remove_managed_profile_label);
+ preference.setIcon(R.drawable.ic_menu_delete);
+ preference.setOnPreferenceClickListener(this);
+ preference.setOrder(ORDER_LAST);
+ return preference;
+ }
+
+ private void cleanUpPreferences() {
+ PreferenceScreen preferenceScreen = getPreferenceScreen();
+ if (preferenceScreen != null) {
+ preferenceScreen.removeAll();
+ }
+ mProfiles.clear();
+ }
+
+ private void listenToAccountUpdates() {
+ final int count = mProfiles.size();
+ for (int i = 0; i < count; i++) {
+ AuthenticatorHelper authenticatorHelper = mProfiles.valueAt(i).authenticatorHelper;
+ if (authenticatorHelper != null) {
+ authenticatorHelper.listenToAccountUpdates();
+ }
+ }
+ }
+
+ private void stopListeningToAccountUpdates() {
+ final int count = mProfiles.size();
+ for (int i = 0; i < count; i++) {
+ AuthenticatorHelper authenticatorHelper = mProfiles.valueAt(i).authenticatorHelper;
+ if (authenticatorHelper != null) {
+ authenticatorHelper.stopListeningToAccountUpdates();
+ }
+ }
+ }
+
+ private void updateAccountTypes(ProfileData profileData) {
+ profileData.preferenceGroup.removeAll();
+ if (profileData.userInfo.isEnabled()) {
+ final ArrayList<AccountPreference> preferences = getAccountTypePreferences(
+ profileData.authenticatorHelper, profileData.userInfo.getUserHandle());
+ final int count = preferences.size();
+ for (int i = 0; i < count; i++) {
+ profileData.preferenceGroup.addPreference(preferences.get(i));
+ }
+ if (profileData.addAccountPreference != null) {
+ profileData.preferenceGroup.addPreference(profileData.addAccountPreference);
+ }
+ } else {
+ // Put a label instead of the accounts list
+ mProfileNotAvailablePreference.setEnabled(false);
+ mProfileNotAvailablePreference.setIcon(R.drawable.empty_icon);
+ mProfileNotAvailablePreference.setTitle(null);
+ mProfileNotAvailablePreference.setSummary(
+ R.string.managed_profile_not_available_label);
+ profileData.preferenceGroup.addPreference(mProfileNotAvailablePreference);
+ }
+ if (profileData.removeWorkProfilePreference != null) {
+ profileData.preferenceGroup.addPreference(profileData.removeWorkProfilePreference);
+ }
+ }
+
+ private ArrayList<AccountPreference> getAccountTypePreferences(AuthenticatorHelper helper,
+ UserHandle userHandle) {
+ final String[] accountTypes = helper.getEnabledAccountTypes();
+ final ArrayList<AccountPreference> accountTypePreferences =
+ new ArrayList<AccountPreference>(accountTypes.length);
+
+ for (int i = 0; i < accountTypes.length; i++) {
+ final String accountType = accountTypes[i];
+ // Skip showing any account that does not have any of the requested authorities
+ if (!accountTypeHasAnyRequestedAuthorities(helper, accountType)) {
+ continue;
+ }
+ final CharSequence label = helper.getLabelForType(getActivity(), accountType);
+ if (label == null) {
+ continue;
+ }
+
+ final Account[] accounts = AccountManager.get(getActivity())
+ .getAccountsByTypeAsUser(accountType, userHandle);
+ final boolean skipToAccount = accounts.length == 1
+ && !helper.hasAccountPreferences(accountType);
+
+ if (skipToAccount) {
+ final Bundle fragmentArguments = new Bundle();
+ fragmentArguments.putParcelable(AccountSyncSettings.ACCOUNT_KEY,
+ accounts[0]);
+ fragmentArguments.putParcelable(EXTRA_USER, userHandle);
+
+ accountTypePreferences.add(new AccountPreference(getActivity(), label,
+ AccountSyncSettings.class.getName(), fragmentArguments,
+ helper.getDrawableForType(getActivity(), accountType)));
+ } else {
+ final Bundle fragmentArguments = new Bundle();
+ fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
+ fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_LABEL,
+ label.toString());
+ fragmentArguments.putParcelable(EXTRA_USER, userHandle);
+
+ accountTypePreferences.add(new AccountPreference(getActivity(), label,
+ ManageAccountsSettings.class.getName(), fragmentArguments,
+ helper.getDrawableForType(getActivity(), accountType)));
+ }
+ helper.preloadDrawableForType(getActivity(), accountType);
+ }
+ // Sort by label
+ Collections.sort(accountTypePreferences, new Comparator<AccountPreference>() {
+ @Override
+ public int compare(AccountPreference t1, AccountPreference t2) {
+ return t1.mTitle.toString().compareTo(t2.mTitle.toString());
+ }
+ });
+ return accountTypePreferences;
+ }
+
+ private boolean accountTypeHasAnyRequestedAuthorities(AuthenticatorHelper helper,
+ String accountType) {
+ if (mAuthoritiesCount == 0) {
+ // No authorities required
+ return true;
+ }
+ final ArrayList<String> authoritiesForType = helper.getAuthoritiesForAccountType(
+ accountType);
+ if (authoritiesForType == null) {
+ Log.d(TAG, "No sync authorities for account type: " + accountType);
+ return false;
+ }
+ for (int j = 0; j < mAuthoritiesCount; j++) {
+ if (authoritiesForType.contains(mAuthorities[j])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private class AccountPreference extends Preference implements OnPreferenceClickListener {
+ /**
+ * Title of the tile that is shown to the user.
+ * @attr ref android.R.styleable#PreferenceHeader_title
+ */
+ private final CharSequence mTitle;
+
+ /**
+ * Full class name of the fragment to display when this tile is
+ * selected.
+ * @attr ref android.R.styleable#PreferenceHeader_fragment
+ */
+ private final String mFragment;
+
+ /**
+ * Optional arguments to supply to the fragment when it is
+ * instantiated.
+ */
+ private final Bundle mFragmentArguments;
+
+ public AccountPreference(Context context, CharSequence title, String fragment,
+ Bundle fragmentArguments, Drawable icon) {
+ super(context);
+ mTitle = title;
+ mFragment = fragment;
+ mFragmentArguments = fragmentArguments;
+ setWidgetLayoutResource(R.layout.account_type_preference);
+
+ setTitle(title);
+ setIcon(icon);
+
+ setOnPreferenceClickListener(this);
+ }
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ if (mFragment != null) {
+ Utils.startWithFragment(
+ getContext(), mFragment, mFragmentArguments, null, 0, 0, mTitle);
+ return true;
+ }
+ return false;
+ }
+ }
+
+ private class ManagedProfileBroadcastReceiver extends BroadcastReceiver {
+ private boolean listeningToManagedProfileEvents;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(Intent.ACTION_MANAGED_PROFILE_REMOVED)
+ || intent.getAction().equals(Intent.ACTION_MANAGED_PROFILE_ADDED)) {
+ Log.v(TAG, "Received broadcast: " + intent.getAction());
+ // Clean old state
+ stopListeningToAccountUpdates();
+ cleanUpPreferences();
+ // Build new state
+ updateUi();
+ listenToAccountUpdates();
+ // Force the menu to update. Note that #onPrepareOptionsMenu uses data built by
+ // #updateUi so we must call this later
+ getActivity().invalidateOptionsMenu();
+ return;
+ }
+ Log.w(TAG, "Cannot handle received broadcast: " + intent.getAction());
+ }
+
+ public void register(Context context) {
+ if (!listeningToManagedProfileEvents) {
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
+ intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
+ context.registerReceiver(this, intentFilter);
+ listeningToManagedProfileEvents = true;
+ }
+ }
+
+ public void unregister(Context context) {
+ if (listeningToManagedProfileEvents) {
+ context.unregisterReceiver(this);
+ listeningToManagedProfileEvents = false;
+ }
+ }
+ }
+
+ private class MasterSyncStateClickListener implements MenuItem.OnMenuItemClickListener {
+ private final UserHandle mUserHandle;
+
+ public MasterSyncStateClickListener(UserHandle userHandle) {
+ mUserHandle = userHandle;
+ }
+
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ if (ActivityManager.isUserAMonkey()) {
+ Log.d(TAG, "ignoring monkey's attempt to flip sync state");
+ } else {
+ ConfirmAutoSyncChangeFragment.show(AccountSettings.this, !item.isChecked(),
+ mUserHandle);
+ }
+ return true;
+ }
+ }
+
+ /**
+ * Dialog to inform user about changing auto-sync setting
+ */
+ public static class ConfirmAutoSyncChangeFragment extends DialogFragment {
+ private static final String SAVE_ENABLING = "enabling";
+ private boolean mEnabling;
+ private UserHandle mUserHandle;
+
+ public static void show(AccountSettings parent, boolean enabling, UserHandle userHandle) {
+ if (!parent.isAdded()) return;
+
+ final ConfirmAutoSyncChangeFragment dialog = new ConfirmAutoSyncChangeFragment();
+ dialog.mEnabling = enabling;
+ dialog.mUserHandle = userHandle;
+ dialog.setTargetFragment(parent, 0);
+ dialog.show(parent.getFragmentManager(), TAG_CONFIRM_AUTO_SYNC_CHANGE);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Context context = getActivity();
+ if (savedInstanceState != null) {
+ mEnabling = savedInstanceState.getBoolean(SAVE_ENABLING);
+ }
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ if (!mEnabling) {
+ builder.setTitle(R.string.data_usage_auto_sync_off_dialog_title);
+ builder.setMessage(R.string.data_usage_auto_sync_off_dialog);
+ } else {
+ builder.setTitle(R.string.data_usage_auto_sync_on_dialog_title);
+ builder.setMessage(R.string.data_usage_auto_sync_on_dialog);
+ }
+
+ builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ ContentResolver.setMasterSyncAutomaticallyAsUser(mEnabling,
+ mUserHandle.getIdentifier());
+ }
+ });
+ builder.setNegativeButton(android.R.string.cancel, null);
+
+ return builder.create();
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putBoolean(SAVE_ENABLING, mEnabling);
+ }
+ }
+ // TODO Implement a {@link SearchIndexProvider} to allow Indexing and Search of account types
+ // See http://b/15403806
+}
diff --git a/src/com/android/settings/accounts/AccountSyncSettings.java b/src/com/android/settings/accounts/AccountSyncSettings.java
index c14dbbe..f0896c9 100644
--- a/src/com/android/settings/accounts/AccountSyncSettings.java
+++ b/src/com/android/settings/accounts/AccountSyncSettings.java
@@ -16,13 +16,14 @@
package com.android.settings.accounts;
+import com.google.android.collect.Lists;
+
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.AlertDialog;
import android.app.Dialog;
import android.content.ContentResolver;
@@ -34,6 +35,7 @@ import android.content.SyncStatusInfo;
import android.content.pm.ProviderInfo;
import android.net.ConnectivityManager;
import android.os.Bundle;
+import android.os.UserHandle;
import android.os.UserManager;
import android.preference.Preference;
import android.preference.PreferenceScreen;
@@ -51,14 +53,11 @@ import android.widget.TextView;
import com.android.settings.R;
import com.android.settings.Utils;
-import com.google.android.collect.Lists;
-import com.google.android.collect.Maps;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
-import java.util.HashMap;
import java.util.List;
public class AccountSyncSettings extends AccountPreferenceBase {
@@ -75,9 +74,6 @@ public class AccountSyncSettings extends AccountPreferenceBase {
private ImageView mProviderIcon;
private TextView mErrorInfoView;
private Account mAccount;
- // List of all accounts, updated when accounts are added/removed
- // We need to re-scan the accounts on sync events, in case sync state changes.
- private Account[] mAccounts;
private ArrayList<SyncStateCheckBoxPreference> mCheckBoxes =
new ArrayList<SyncStateCheckBoxPreference>();
private ArrayList<SyncAdapterType> mInvisibleAdapters = Lists.newArrayList();
@@ -94,8 +90,10 @@ public class AccountSyncSettings extends AccountPreferenceBase {
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
+ // TODO: We need an API to remove an account from a different user.
+ // See: http://b/15466880
AccountManager.get(AccountSyncSettings.this.getActivity())
- .removeAccount(mAccount,
+ .removeAccountAsUser(mAccount,
new AccountManagerCallback<Boolean>() {
@Override
public void run(AccountManagerFuture<Boolean> future) {
@@ -122,7 +120,7 @@ public class AccountSyncSettings extends AccountPreferenceBase {
finish();
}
}
- }, null);
+ }, null, mUserHandle);
}
})
.create();
@@ -180,23 +178,27 @@ public class AccountSyncSettings extends AccountPreferenceBase {
Bundle arguments = getArguments();
if (arguments == null) {
Log.e(TAG, "No arguments provided when starting intent. ACCOUNT_KEY needed.");
+ finish();
return;
}
-
mAccount = (Account) arguments.getParcelable(ACCOUNT_KEY);
- if (mAccount != null) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "Got account: " + mAccount);
- mUserId.setText(mAccount.name);
- mProviderId.setText(mAccount.type);
+ if (!accountExists(mAccount)) {
+ Log.e(TAG, "Account provided does not exist: " + mAccount);
+ finish();
+ return;
}
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Got account: " + mAccount);
+ }
+ mUserId.setText(mAccount.name);
+ mProviderId.setText(mAccount.type);
}
@Override
public void onResume() {
- final Activity activity = getActivity();
- AccountManager.get(activity).addOnAccountsUpdatedListener(this, null, false);
+ mAuthenticatorHelper.listenToAccountUpdates();
updateAuthDescriptions();
- onAccountsUpdated(AccountManager.get(activity).getAccounts());
+ onAccountsUpdate(UserHandle.getCallingUserHandle());
super.onResume();
}
@@ -204,14 +206,15 @@ public class AccountSyncSettings extends AccountPreferenceBase {
@Override
public void onPause() {
super.onPause();
- AccountManager.get(getActivity()).removeOnAccountsUpdatedListener(this);
+ mAuthenticatorHelper.stopListeningToAccountUpdates();
}
private void addSyncStateCheckBox(Account account, String authority) {
SyncStateCheckBoxPreference item =
new SyncStateCheckBoxPreference(getActivity(), account, authority);
item.setPersistent(false);
- final ProviderInfo providerInfo = getPackageManager().resolveContentProvider(authority, 0);
+ final ProviderInfo providerInfo = getPackageManager().resolveContentProviderAsUser(
+ authority, 0, mUserHandle.getIdentifier());
if (providerInfo == null) {
return;
}
@@ -235,12 +238,11 @@ public class AccountSyncSettings extends AccountPreferenceBase {
MenuItem syncCancel = menu.add(0, MENU_SYNC_CANCEL_ID, 0,
getString(R.string.sync_menu_sync_cancel))
.setIcon(com.android.internal.R.drawable.ic_menu_close_clear_cancel);
-
final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
- if (!um.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
+ if (!um.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS, mUserHandle)) {
MenuItem removeAccount = menu.add(0, MENU_REMOVE_ACCOUNT_ID, 0,
getString(R.string.remove_account_label))
- .setIcon(R.drawable.ic_menu_delete_holo_dark);
+ .setIcon(R.drawable.ic_menu_delete);
removeAccount.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER |
MenuItem.SHOW_AS_ACTION_WITH_TEXT);
}
@@ -255,7 +257,9 @@ public class AccountSyncSettings extends AccountPreferenceBase {
@Override
public void onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
- boolean syncActive = ContentResolver.getCurrentSync() != null;
+ // Note that this also counts accounts that are not currently displayed
+ boolean syncActive = ContentResolver.getCurrentSyncsAsUser(
+ mUserHandle.getIdentifier()).isEmpty();
menu.findItem(MENU_SYNC_NOW_ID).setVisible(!syncActive);
menu.findItem(MENU_SYNC_CANCEL_ID).setVisible(syncActive);
}
@@ -282,7 +286,9 @@ public class AccountSyncSettings extends AccountPreferenceBase {
SyncStateCheckBoxPreference syncPref = (SyncStateCheckBoxPreference) preference;
String authority = syncPref.getAuthority();
Account account = syncPref.getAccount();
- boolean syncAutomatically = ContentResolver.getSyncAutomatically(account, authority);
+ final int userId = mUserHandle.getIdentifier();
+ boolean syncAutomatically = ContentResolver.getSyncAutomaticallyAsUser(account,
+ authority, userId);
if (syncPref.isOneTimeSyncMode()) {
requestOrCancelSync(account, authority, true);
} else {
@@ -290,11 +296,11 @@ public class AccountSyncSettings extends AccountPreferenceBase {
boolean oldSyncState = syncAutomatically;
if (syncOn != oldSyncState) {
// if we're enabling sync, this will request a sync as well
- ContentResolver.setSyncAutomatically(account, authority, syncOn);
+ ContentResolver.setSyncAutomaticallyAsUser(account, authority, syncOn, userId);
// if the master sync switch is off, the request above will
// get dropped. when the user clicks on this toggle,
// we want to force the sync, however.
- if (!ContentResolver.getMasterSyncAutomatically() || !syncOn) {
+ if (!ContentResolver.getMasterSyncAutomaticallyAsUser(userId) || !syncOn) {
requestOrCancelSync(account, authority, syncOn);
}
}
@@ -332,10 +338,7 @@ public class AccountSyncSettings extends AccountPreferenceBase {
// plus whatever the system needs to sync, e.g., invisible sync adapters
if (mAccount != null) {
for (SyncAdapterType syncAdapter : mInvisibleAdapters) {
- // invisible sync adapters' account type should be same as current account type
- if (syncAdapter.accountType.equals(mAccount.type)) {
- requestOrCancelSync(mAccount, syncAdapter.authority, startSync);
- }
+ requestOrCancelSync(mAccount, syncAdapter.authority, startSync);
}
}
}
@@ -344,9 +347,10 @@ public class AccountSyncSettings extends AccountPreferenceBase {
if (flag) {
Bundle extras = new Bundle();
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
- ContentResolver.requestSync(account, authority, extras);
+ ContentResolver.requestSyncAsUser(account, authority, mUserHandle.getIdentifier(),
+ extras);
} else {
- ContentResolver.cancelSync(account, authority);
+ ContentResolver.cancelSyncAsUser(account, authority, mUserHandle.getIdentifier());
}
}
@@ -368,11 +372,12 @@ public class AccountSyncSettings extends AccountPreferenceBase {
private void setFeedsState() {
// iterate over all the preferences, setting the state properly for each
Date date = new Date();
- List<SyncInfo> currentSyncs = ContentResolver.getCurrentSyncs();
+ final int userId = mUserHandle.getIdentifier();
+ List<SyncInfo> currentSyncs = ContentResolver.getCurrentSyncsAsUser(userId);
boolean syncIsFailing = false;
// Refresh the sync status checkboxes - some syncs may have become active.
- updateAccountCheckboxes(mAccounts);
+ updateAccountCheckboxes();
for (int i = 0, count = getPreferenceScreen().getPreferenceCount(); i < count; i++) {
Preference pref = getPreferenceScreen().getPreference(i);
@@ -384,8 +389,9 @@ public class AccountSyncSettings extends AccountPreferenceBase {
String authority = syncPref.getAuthority();
Account account = syncPref.getAccount();
- SyncStatusInfo status = ContentResolver.getSyncStatus(account, authority);
- boolean syncEnabled = ContentResolver.getSyncAutomatically(account, authority);
+ SyncStatusInfo status = ContentResolver.getSyncStatusAsUser(account, authority, userId);
+ boolean syncEnabled = ContentResolver.getSyncAutomaticallyAsUser(account, authority,
+ userId);
boolean authorityIsPending = status == null ? false : status.pending;
boolean initialSync = status == null ? false : status.initialize;
@@ -415,7 +421,7 @@ public class AccountSyncSettings extends AccountPreferenceBase {
} else {
syncPref.setSummary("");
}
- int syncState = ContentResolver.getIsSyncable(account, authority);
+ int syncState = ContentResolver.getIsSyncableAsUser(account, authority, userId);
syncPref.setActive(activelySyncing && (syncState >= 0) &&
!initialSync);
@@ -425,7 +431,8 @@ public class AccountSyncSettings extends AccountPreferenceBase {
syncPref.setFailed(lastSyncFailed);
ConnectivityManager connManager =
(ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
- final boolean masterSyncAutomatically = ContentResolver.getMasterSyncAutomatically();
+ final boolean masterSyncAutomatically =
+ ContentResolver.getMasterSyncAutomaticallyAsUser(userId);
final boolean backgroundDataEnabled = connManager.getBackgroundDataSetting();
final boolean oneTimeSyncMode = !masterSyncAutomatically || !backgroundDataEnabled;
syncPref.setOneTimeSyncMode(oneTimeSyncMode);
@@ -436,29 +443,44 @@ public class AccountSyncSettings extends AccountPreferenceBase {
}
@Override
- public void onAccountsUpdated(Account[] accounts) {
- super.onAccountsUpdated(accounts);
- mAccounts = accounts;
- updateAccountCheckboxes(accounts);
+ public void onAccountsUpdate(final UserHandle userHandle) {
+ super.onAccountsUpdate(userHandle);
+ if (!accountExists(mAccount)) {
+ // The account was deleted
+ finish();
+ return;
+ }
+ updateAccountCheckboxes();
onSyncStateUpdated();
}
- private void updateAccountCheckboxes(Account[] accounts) {
+ private boolean accountExists(Account account) {
+ if (account == null) return false;
+
+ Account[] accounts = AccountManager.get(getActivity()).getAccountsByTypeAsUser(
+ account.type, mUserHandle);
+ final int count = accounts.length;
+ for (int i = 0; i < count; i++) {
+ if (accounts[i].equals(account)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void updateAccountCheckboxes() {
mInvisibleAdapters.clear();
- SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypes();
- HashMap<String, ArrayList<String>> accountTypeToAuthorities =
- Maps.newHashMap();
+ SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser(
+ mUserHandle.getIdentifier());
+ ArrayList<String> authorities = new ArrayList<String>();
for (int i = 0, n = syncAdapters.length; i < n; i++) {
final SyncAdapterType sa = syncAdapters[i];
+ // Only keep track of sync adapters for this account
+ if (!sa.accountType.equals(mAccount.type)) continue;
if (sa.isUserVisible()) {
- ArrayList<String> authorities = accountTypeToAuthorities.get(sa.accountType);
- if (authorities == null) {
- authorities = new ArrayList<String>();
- accountTypeToAuthorities.put(sa.accountType, authorities);
- }
if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.d(TAG, "onAccountUpdated: added authority " + sa.authority
+ Log.d(TAG, "updateAccountCheckboxes: added authority " + sa.authority
+ " to accountType " + sa.accountType);
}
authorities.add(sa.authority);
@@ -474,24 +496,19 @@ public class AccountSyncSettings extends AccountPreferenceBase {
}
mCheckBoxes.clear();
- for (int i = 0, n = accounts.length; i < n; i++) {
- final Account account = accounts[i];
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.d(TAG, "looking for sync adapters that match account " + mAccount);
+ }
+ for (int j = 0, m = authorities.size(); j < m; j++) {
+ final String authority = authorities.get(j);
+ // We could check services here....
+ int syncState = ContentResolver.getIsSyncableAsUser(mAccount, authority,
+ mUserHandle.getIdentifier());
if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.d(TAG, "looking for sync adapters that match account " + account);
+ Log.d(TAG, " found authority " + authority + " " + syncState);
}
- final ArrayList<String> authorities = accountTypeToAuthorities.get(account.type);
- if (authorities != null && (mAccount == null || mAccount.equals(account))) {
- for (int j = 0, m = authorities.size(); j < m; j++) {
- final String authority = authorities.get(j);
- // We could check services here....
- int syncState = ContentResolver.getIsSyncable(account, authority);
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.d(TAG, " found authority " + authority + " " + syncState);
- }
- if (syncState > 0) {
- addSyncStateCheckBox(account, authority);
- }
- }
+ if (syncState > 0) {
+ addSyncStateCheckBox(mAccount, authority);
}
}
diff --git a/src/com/android/settings/accounts/AddAccountSettings.java b/src/com/android/settings/accounts/AddAccountSettings.java
index add3f86..3af28b2 100644
--- a/src/com/android/settings/accounts/AddAccountSettings.java
+++ b/src/com/android/settings/accounts/AddAccountSettings.java
@@ -27,6 +27,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
+import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
import android.widget.Toast;
@@ -36,8 +37,10 @@ import com.android.settings.Utils;
import java.io.IOException;
+import static android.content.Intent.EXTRA_USER;
+
/**
- * Entry point Actiivty for account setup. Works as follows
+ * Entry point Activity for account setup. Works as follows
*
* 1) When the other Activities launch this Activity, it launches {@link ChooseAccountActivity}
* without showing anything.
@@ -50,6 +53,9 @@ import java.io.IOException;
* currently delegate the work to the other Activity. When we let this Activity do that work, users
* would see the list of account types when leaving this Activity, since the UI is already ready
* when returning from each account setup, which doesn't look good.
+ *
+ * An extra {@link UserHandle} can be specified in the intent as {@link EXTRA_USER}, if the user for
+ * which the action needs to be performed is different to the one the Settings App will run in.
*/
public class AddAccountSettings extends Activity {
/**
@@ -91,8 +97,9 @@ public class AddAccountSettings extends Activity {
addAccountOptions.putParcelable(KEY_CALLER_IDENTITY, mPendingIntent);
addAccountOptions.putBoolean(EXTRA_HAS_MULTIPLE_USERS,
Utils.hasMultipleUsers(AddAccountSettings.this));
+ addAccountOptions.putParcelable(EXTRA_USER, mUserHandle);
intent.putExtras(addAccountOptions);
- startActivityForResult(intent, ADD_ACCOUNT_REQUEST);
+ startActivityForResultAsUser(intent, ADD_ACCOUNT_REQUEST, mUserHandle);
} else {
setResult(RESULT_OK);
if (mPendingIntent != null) {
@@ -117,6 +124,7 @@ public class AddAccountSettings extends Activity {
};
private boolean mAddAccountCalled = false;
+ private UserHandle mUserHandle;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -128,7 +136,9 @@ public class AddAccountSettings extends Activity {
}
final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
- if (um.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
+ mUserHandle = Utils.getSecureTargetUser(getActivityToken(), um, null /* arguments */,
+ getIntent().getExtras());
+ if (um.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS, mUserHandle)) {
// We aren't allowed to add an account.
Toast.makeText(this, R.string.user_cannot_add_accounts_message, Toast.LENGTH_LONG)
.show();
@@ -151,6 +161,7 @@ public class AddAccountSettings extends Activity {
if (accountTypes != null) {
intent.putExtra(AccountPreferenceBase.ACCOUNT_TYPES_FILTER_KEY, accountTypes);
}
+ intent.putExtra(EXTRA_USER, mUserHandle);
startActivityForResult(intent, CHOOSE_ACCOUNT_REQUEST);
}
@@ -203,14 +214,15 @@ public class AddAccountSettings extends Activity {
mPendingIntent = PendingIntent.getBroadcast(this, 0, identityIntent, 0);
addAccountOptions.putParcelable(KEY_CALLER_IDENTITY, mPendingIntent);
addAccountOptions.putBoolean(EXTRA_HAS_MULTIPLE_USERS, Utils.hasMultipleUsers(this));
- AccountManager.get(this).addAccount(
+ AccountManager.get(this).addAccountAsUser(
accountType,
null, /* authTokenType */
null, /* requiredFeatures */
addAccountOptions,
null,
mCallback,
- null /* handler */);
+ null /* handler */,
+ mUserHandle);
mAddAccountCalled = true;
}
}
diff --git a/src/com/android/settings/accounts/AuthenticatorHelper.java b/src/com/android/settings/accounts/AuthenticatorHelper.java
index a164b8b..cc8a6d5 100644
--- a/src/com/android/settings/accounts/AuthenticatorHelper.java
+++ b/src/com/android/settings/accounts/AuthenticatorHelper.java
@@ -16,30 +16,67 @@
package com.android.settings.accounts;
+import com.google.android.collect.Maps;
+
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AuthenticatorDescription;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SyncAdapterType;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.util.Log;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
-public class AuthenticatorHelper {
-
+/**
+ * Helper class for monitoring accounts on the device for a given user.
+ *
+ * Classes using this helper should implement {@link OnAccountsUpdateListener}.
+ * {@link OnAccountsUpdateListener#onAccountsUpdate(UserHandle)} will then be
+ * called once accounts get updated. For setting up listening for account
+ * updates, {@link #listenToAccountUpdates()} and
+ * {@link #stopListeningToAccountUpdates()} should be used.
+ */
+final public class AuthenticatorHelper extends BroadcastReceiver {
private static final String TAG = "AuthenticatorHelper";
+
private Map<String, AuthenticatorDescription> mTypeToAuthDescription
= new HashMap<String, AuthenticatorDescription>();
private AuthenticatorDescription[] mAuthDescs;
private ArrayList<String> mEnabledAccountTypes = new ArrayList<String>();
private Map<String, Drawable> mAccTypeIconCache = new HashMap<String, Drawable>();
+ private HashMap<String, ArrayList<String>> mAccountTypeToAuthorities = Maps.newHashMap();
+
+ private final UserHandle mUserHandle;
+ private final UserManager mUm;
+ private final Context mContext;
+ private final OnAccountsUpdateListener mListener;
+ private boolean mListeningToAccountUpdates;
- public AuthenticatorHelper() {
+ public interface OnAccountsUpdateListener {
+ void onAccountsUpdate(UserHandle userHandle);
+ }
+
+ public AuthenticatorHelper(Context context, UserHandle userHandle, UserManager userManager,
+ OnAccountsUpdateListener listener) {
+ mContext = context;
+ mUm = userManager;
+ mUserHandle = userHandle;
+ mListener = listener;
+ // This guarantees that the helper is ready to use once constructed: the account types and
+ // authorities are initialized
+ onAccountsUpdated(null);
}
public String[] getEnabledAccountTypes() {
@@ -71,8 +108,10 @@ public class AuthenticatorHelper {
if (mTypeToAuthDescription.containsKey(accountType)) {
try {
AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType);
- Context authContext = context.createPackageContext(desc.packageName, 0);
- icon = authContext.getResources().getDrawable(desc.iconId);
+ Context authContext = context.createPackageContextAsUser(desc.packageName, 0,
+ mUserHandle);
+ icon = mContext.getPackageManager().getUserBadgedIcon(
+ authContext.getResources().getDrawable(desc.iconId), mUserHandle);
synchronized (mAccTypeIconCache) {
mAccTypeIconCache.put(accountType, icon);
}
@@ -96,7 +135,8 @@ public class AuthenticatorHelper {
if (mTypeToAuthDescription.containsKey(accountType)) {
try {
AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType);
- Context authContext = context.createPackageContext(desc.packageName, 0);
+ Context authContext = context.createPackageContextAsUser(desc.packageName, 0,
+ mUserHandle);
label = authContext.getResources().getText(desc.labelId);
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "No label name for account type " + accountType);
@@ -112,25 +152,13 @@ public class AuthenticatorHelper {
* and update any UI that depends on AuthenticatorDescriptions in onAuthDescriptionsUpdated().
*/
public void updateAuthDescriptions(Context context) {
- mAuthDescs = AccountManager.get(context).getAuthenticatorTypes();
+ mAuthDescs = AccountManager.get(context)
+ .getAuthenticatorTypesAsUser(mUserHandle.getIdentifier());
for (int i = 0; i < mAuthDescs.length; i++) {
mTypeToAuthDescription.put(mAuthDescs[i].type, mAuthDescs[i]);
}
}
- public void onAccountsUpdated(Context context, Account[] accounts) {
- if (accounts == null) {
- accounts = AccountManager.get(context).getAccounts();
- }
- mEnabledAccountTypes.clear();
- mAccTypeIconCache.clear();
- for (Account account: accounts) {
- if (!mEnabledAccountTypes.contains(account.type)) {
- mEnabledAccountTypes.add(account.type);
- }
- }
- }
-
public boolean containsAccountType(String accountType) {
return mTypeToAuthDescription.containsKey(accountType);
}
@@ -148,4 +176,73 @@ public class AuthenticatorHelper {
}
return false;
}
+
+ void onAccountsUpdated(Account[] accounts) {
+ updateAuthDescriptions(mContext);
+ if (accounts == null) {
+ accounts = AccountManager.get(mContext).getAccountsAsUser(mUserHandle.getIdentifier());
+ }
+ mEnabledAccountTypes.clear();
+ mAccTypeIconCache.clear();
+ for (int i = 0; i < accounts.length; i++) {
+ final Account account = accounts[i];
+ if (!mEnabledAccountTypes.contains(account.type)) {
+ mEnabledAccountTypes.add(account.type);
+ }
+ }
+ buildAccountTypeToAuthoritiesMap();
+ if (mListeningToAccountUpdates) {
+ mListener.onAccountsUpdate(mUserHandle);
+ }
+ }
+
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ // TODO: watch for package upgrades to invalidate cache; see http://b/7206643
+ final Account[] accounts = AccountManager.get(mContext)
+ .getAccountsAsUser(mUserHandle.getIdentifier());
+ onAccountsUpdated(accounts);
+ }
+
+ public void listenToAccountUpdates() {
+ if (!mListeningToAccountUpdates) {
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION);
+ // At disk full, certain actions are blocked (such as writing the accounts to storage).
+ // It is useful to also listen for recovery from disk full to avoid bugs.
+ intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
+ mContext.registerReceiverAsUser(this, mUserHandle, intentFilter, null, null);
+ mListeningToAccountUpdates = true;
+ }
+ }
+
+ public void stopListeningToAccountUpdates() {
+ if (mListeningToAccountUpdates) {
+ mContext.unregisterReceiver(this);
+ mListeningToAccountUpdates = false;
+ }
+ }
+
+ public ArrayList<String> getAuthoritiesForAccountType(String type) {
+ return mAccountTypeToAuthorities.get(type);
+ }
+
+ private void buildAccountTypeToAuthoritiesMap() {
+ mAccountTypeToAuthorities.clear();
+ SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser(
+ mUserHandle.getIdentifier());
+ for (int i = 0, n = syncAdapters.length; i < n; i++) {
+ final SyncAdapterType sa = syncAdapters[i];
+ ArrayList<String> authorities = mAccountTypeToAuthorities.get(sa.accountType);
+ if (authorities == null) {
+ authorities = new ArrayList<String>();
+ mAccountTypeToAuthorities.put(sa.accountType, authorities);
+ }
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.d(TAG, "Added authority " + sa.authority + " to accountType "
+ + sa.accountType);
+ }
+ authorities.add(sa.authority);
+ }
+ }
}
diff --git a/src/com/android/settings/accounts/ChooseAccountActivity.java b/src/com/android/settings/accounts/ChooseAccountActivity.java
index 631fe47..e52d640 100644
--- a/src/com/android/settings/accounts/ChooseAccountActivity.java
+++ b/src/com/android/settings/accounts/ChooseAccountActivity.java
@@ -26,13 +26,18 @@ import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
import android.util.Log;
+
import com.android.internal.util.CharSequences;
import com.android.settings.R;
+import com.android.settings.Utils;
+
import com.google.android.collect.Maps;
import java.util.ArrayList;
@@ -41,8 +46,13 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
+import static android.content.Intent.EXTRA_USER;
+
/**
* Activity asking a user to select an account to be set up.
+ *
+ * An extra {@link UserHandle} can be specified in the intent as {@link EXTRA_USER}, if the user for
+ * which the action needs to be performed is different to the one the Settings App will run in.
*/
public class ChooseAccountActivity extends PreferenceActivity {
@@ -55,7 +65,10 @@ public class ChooseAccountActivity extends PreferenceActivity {
private HashMap<String, ArrayList<String>> mAccountTypeToAuthorities = null;
private Map<String, AuthenticatorDescription> mTypeToAuthDescription
= new HashMap<String, AuthenticatorDescription>();
-
+ // The UserHandle of the user we are choosing an account for
+ private UserHandle mUserHandle;
+ private UserManager mUm;
+
private static class ProviderEntry implements Comparable<ProviderEntry> {
private final CharSequence name;
private final String type;
@@ -92,6 +105,9 @@ public class ChooseAccountActivity extends PreferenceActivity {
}
}
mAddAccountGroup = getPreferenceScreen();
+ mUm = UserManager.get(this);
+ mUserHandle = Utils.getSecureTargetUser(getActivityToken(), mUm, null /* arguments */,
+ getIntent().getExtras());
updateAuthDescriptions();
}
@@ -100,7 +116,8 @@ public class ChooseAccountActivity extends PreferenceActivity {
* and update any UI that depends on AuthenticatorDescriptions in onAuthDescriptionsUpdated().
*/
private void updateAuthDescriptions() {
- mAuthDescs = AccountManager.get(this).getAuthenticatorTypes();
+ mAuthDescs = AccountManager.get(this).getAuthenticatorTypesAsUser(
+ mUserHandle.getIdentifier());
for (int i = 0; i < mAuthDescs.length; i++) {
mTypeToAuthDescription.put(mAuthDescs[i].type, mAuthDescs[i]);
}
@@ -168,7 +185,8 @@ public class ChooseAccountActivity extends PreferenceActivity {
public ArrayList<String> getAuthoritiesForAccountType(String type) {
if (mAccountTypeToAuthorities == null) {
mAccountTypeToAuthorities = Maps.newHashMap();
- SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypes();
+ SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser(
+ mUserHandle.getIdentifier());
for (int i = 0, n = syncAdapters.length; i < n; i++) {
final SyncAdapterType sa = syncAdapters[i];
ArrayList<String> authorities = mAccountTypeToAuthorities.get(sa.accountType);
@@ -196,8 +214,9 @@ public class ChooseAccountActivity extends PreferenceActivity {
if (mTypeToAuthDescription.containsKey(accountType)) {
try {
AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType);
- Context authContext = createPackageContext(desc.packageName, 0);
- icon = authContext.getResources().getDrawable(desc.iconId);
+ Context authContext = createPackageContextAsUser(desc.packageName, 0, mUserHandle);
+ icon = getPackageManager().getUserBadgedIcon(
+ authContext.getResources().getDrawable(desc.iconId), mUserHandle);
} catch (PackageManager.NameNotFoundException e) {
// TODO: place holder icon for missing account icons?
Log.w(TAG, "No icon name for account type " + accountType);
@@ -219,7 +238,7 @@ public class ChooseAccountActivity extends PreferenceActivity {
if (mTypeToAuthDescription.containsKey(accountType)) {
try {
AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType);
- Context authContext = createPackageContext(desc.packageName, 0);
+ Context authContext = createPackageContextAsUser(desc.packageName, 0, mUserHandle);
label = authContext.getResources().getText(desc.labelId);
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "No label name for account type " + accountType);
@@ -245,6 +264,7 @@ public class ChooseAccountActivity extends PreferenceActivity {
private void finishWithAccountType(String accountType) {
Intent intent = new Intent();
intent.putExtra(AddAccountSettings.EXTRA_SELECTED_ACCOUNT, accountType);
+ intent.putExtra(EXTRA_USER, mUserHandle);
setResult(RESULT_OK, intent);
finish();
}
diff --git a/src/com/android/settings/accounts/ManageAccountsSettings.java b/src/com/android/settings/accounts/ManageAccountsSettings.java
index 184f680..074176b 100644
--- a/src/com/android/settings/accounts/ManageAccountsSettings.java
+++ b/src/com/android/settings/accounts/ManageAccountsSettings.java
@@ -18,7 +18,7 @@ package com.android.settings.accounts;
import android.accounts.Account;
import android.accounts.AccountManager;
-import android.accounts.OnAccountsUpdateListener;
+import android.accounts.AuthenticatorDescription;
import android.app.ActionBar;
import android.app.Activity;
import android.content.ContentResolver;
@@ -26,12 +26,17 @@ import android.content.Intent;
import android.content.SyncAdapterType;
import android.content.SyncInfo;
import android.content.SyncStatusInfo;
+import android.content.pm.ActivityInfo;
+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.graphics.drawable.Drawable;
import android.os.Bundle;
+import android.os.UserHandle;
import android.preference.Preference;
-import android.preference.PreferenceActivity;
+import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceScreen;
import android.util.Log;
import android.view.LayoutInflater;
@@ -45,16 +50,20 @@ import android.widget.TextView;
import com.android.settings.AccountPreference;
import com.android.settings.R;
+import com.android.settings.SettingsActivity;
import com.android.settings.Utils;
import com.android.settings.location.LocationSettings;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
+import java.util.List;
+
+import static android.content.Intent.EXTRA_USER;
/** Manages settings for Google Account. */
public class ManageAccountsSettings extends AccountPreferenceBase
- implements OnAccountsUpdateListener {
+ implements AuthenticatorHelper.OnAccountsUpdateListener {
private static final String ACCOUNT_KEY = "account"; // to pass to auth settings
public static final String KEY_ACCOUNT_TYPE = "account_type";
public static final String KEY_ACCOUNT_LABEL = "account_label";
@@ -92,8 +101,9 @@ public class ManageAccountsSettings extends AccountPreferenceBase
@Override
public void onStart() {
super.onStart();
- Activity activity = getActivity();
- AccountManager.get(activity).addOnAccountsUpdatedListener(this, null, true);
+ mAuthenticatorHelper.listenToAccountUpdates();
+ updateAuthDescriptions();
+ showAccountsIfNeeded();
}
@Override
@@ -121,14 +131,13 @@ public class ManageAccountsSettings extends AccountPreferenceBase
if (args != null && args.containsKey(KEY_ACCOUNT_LABEL)) {
getActivity().setTitle(args.getString(KEY_ACCOUNT_LABEL));
}
- updateAuthDescriptions();
}
@Override
public void onStop() {
super.onStop();
final Activity activity = getActivity();
- AccountManager.get(activity).removeOnAccountsUpdatedListener(this);
+ mAuthenticatorHelper.stopListeningToAccountUpdates();
activity.getActionBar().setDisplayOptions(0, ActionBar.DISPLAY_SHOW_CUSTOM);
activity.getActionBar().setCustomView(null);
}
@@ -146,7 +155,8 @@ public class ManageAccountsSettings extends AccountPreferenceBase
private void startAccountSettings(AccountPreference acctPref) {
Bundle args = new Bundle();
args.putParcelable(AccountSyncSettings.ACCOUNT_KEY, acctPref.getAccount());
- ((PreferenceActivity) getActivity()).startPreferencePanel(
+ args.putParcelable(EXTRA_USER, mUserHandle);
+ ((SettingsActivity) getActivity()).startPreferencePanel(
AccountSyncSettings.class.getCanonicalName(), args,
R.string.account_sync_settings_title, acctPref.getAccount().name,
this, REQUEST_SHOW_SYNC_SETTINGS);
@@ -166,7 +176,8 @@ public class ManageAccountsSettings extends AccountPreferenceBase
@Override
public void onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
- boolean syncActive = ContentResolver.getCurrentSync() != null;
+ boolean syncActive = ContentResolver.getCurrentSyncsAsUser(
+ mUserHandle.getIdentifier()).isEmpty();
menu.findItem(MENU_SYNC_NOW_ID).setVisible(!syncActive && mFirstAccount != null);
menu.findItem(MENU_SYNC_CANCEL_ID).setVisible(syncActive && mFirstAccount != null);
}
@@ -185,7 +196,8 @@ public class ManageAccountsSettings extends AccountPreferenceBase
}
private void requestOrCancelSyncForAccounts(boolean sync) {
- SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypes();
+ final int userId = mUserHandle.getIdentifier();
+ SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser(userId);
Bundle extras = new Bundle();
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
int count = getPreferenceScreen().getPreferenceCount();
@@ -198,11 +210,13 @@ public class ManageAccountsSettings extends AccountPreferenceBase
for (int j = 0; j < syncAdapters.length; j++) {
SyncAdapterType sa = syncAdapters[j];
if (syncAdapters[j].accountType.equals(mAccountType)
- && ContentResolver.getSyncAutomatically(account, sa.authority)) {
+ && ContentResolver.getSyncAutomaticallyAsUser(account, sa.authority,
+ userId)) {
if (sync) {
- ContentResolver.requestSync(account, sa.authority, extras);
+ ContentResolver.requestSyncAsUser(account, sa.authority, userId,
+ extras);
} else {
- ContentResolver.cancelSync(account, sa.authority);
+ ContentResolver.cancelSyncAsUser(account, sa.authority, userId);
}
}
}
@@ -212,17 +226,23 @@ public class ManageAccountsSettings extends AccountPreferenceBase
@Override
protected void onSyncStateUpdated() {
+ showSyncState();
+ }
+
+ private void showSyncState() {
// Catch any delayed delivery of update messages
if (getActivity() == null) return;
+ final int userId = mUserHandle.getIdentifier();
+
// iterate over all the preferences, setting the state properly for each
- SyncInfo currentSync = ContentResolver.getCurrentSync();
+ List<SyncInfo> currentSyncs = ContentResolver.getCurrentSyncsAsUser(userId);
boolean anySyncFailed = false; // true if sync on any account failed
Date date = new Date();
// only track userfacing sync adapters when deciding if account is synced or not
- final SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypes();
+ final SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser(userId);
HashSet<String> userFacing = new HashSet<String>();
for (int k = 0, n = syncAdapters.length; k < n; k++) {
final SyncAdapterType sa = syncAdapters[k];
@@ -245,15 +265,11 @@ public class ManageAccountsSettings extends AccountPreferenceBase
boolean syncingNow = false;
if (authorities != null) {
for (String authority : authorities) {
- SyncStatusInfo status = ContentResolver.getSyncStatus(account, authority);
- boolean syncEnabled = ContentResolver.getSyncAutomatically(account, authority)
- && ContentResolver.getMasterSyncAutomatically()
- && (ContentResolver.getIsSyncable(account, authority) > 0);
+ SyncStatusInfo status = ContentResolver.getSyncStatusAsUser(account, authority,
+ userId);
+ boolean syncEnabled = isSyncEnabled(userId, account, authority);
boolean authorityIsPending = ContentResolver.isSyncPending(account, authority);
- boolean activelySyncing = currentSync != null
- && currentSync.authority.equals(authority)
- && new Account(currentSync.account.name, currentSync.account.type)
- .equals(account);
+ boolean activelySyncing = isSyncing(currentSyncs, account, authority);
boolean lastSyncFailed = status != null
&& syncEnabled
&& status.lastFailureTime != 0
@@ -299,9 +315,34 @@ public class ManageAccountsSettings extends AccountPreferenceBase
mErrorInfoView.setVisibility(anySyncFailed ? View.VISIBLE : View.GONE);
}
+
+ private boolean isSyncing(List<SyncInfo> currentSyncs, Account account, String authority) {
+ final int count = currentSyncs.size();
+ for (int i = 0; i < count; i++) {
+ SyncInfo syncInfo = currentSyncs.get(i);
+ if (syncInfo.account.equals(account) && syncInfo.authority.equals(authority)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isSyncEnabled(int userId, Account account, String authority) {
+ return ContentResolver.getSyncAutomaticallyAsUser(account, authority, userId)
+ && ContentResolver.getMasterSyncAutomaticallyAsUser(userId)
+ && (ContentResolver.getIsSyncableAsUser(account, authority, userId) > 0);
+ }
+
@Override
- public void onAccountsUpdated(Account[] accounts) {
+ public void onAccountsUpdate(UserHandle userHandle) {
+ showAccountsIfNeeded();
+ onSyncStateUpdated();
+ }
+
+ private void showAccountsIfNeeded() {
if (getActivity() == null) return;
+ Account[] accounts = AccountManager.get(getActivity()).getAccountsAsUser(
+ mUserHandle.getIdentifier());
getPreferenceScreen().removeAll();
mFirstAccount = null;
addPreferencesFromResource(R.xml.manage_accounts_settings);
@@ -341,7 +382,6 @@ public class ManageAccountsSettings extends AccountPreferenceBase
settingsTop.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
getActivity().startActivity(settingsTop);
}
- onSyncStateUpdated();
}
private void addAuthenticatorSettings() {
@@ -368,7 +408,7 @@ public class ManageAccountsSettings extends AccountPreferenceBase
@Override
public boolean onPreferenceClick(Preference preference) {
- ((PreferenceActivity) getActivity()).startPreferencePanel(
+ ((SettingsActivity) getActivity()).startPreferencePanel(
mClass, null, mTitleRes, null, null, 0);
// Hack: announce that the Google account preferences page is launching the location
// settings
@@ -388,7 +428,7 @@ public class ManageAccountsSettings extends AccountPreferenceBase
* intent, and hack the location settings to start it as a fragment.
*/
private void updatePreferenceIntents(PreferenceScreen prefs) {
- PackageManager pm = getActivity().getPackageManager();
+ final PackageManager pm = getActivity().getPackageManager();
for (int i = 0; i < prefs.getPreferenceCount();) {
Preference pref = prefs.getPreference(i);
Intent intent = pref.getIntent();
@@ -415,13 +455,36 @@ public class ManageAccountsSettings extends AccountPreferenceBase
LocationSettings.class.getName(),
R.string.location_settings_title));
} else {
- ResolveInfo ri = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
+ ResolveInfo ri = pm.resolveActivityAsUser(intent,
+ PackageManager.MATCH_DEFAULT_ONLY, mUserHandle.getIdentifier());
if (ri == null) {
prefs.removePreference(pref);
continue;
} else {
intent.putExtra(ACCOUNT_KEY, mFirstAccount);
intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
+ pref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ Intent prefIntent = preference.getIntent();
+ /*
+ * Check the intent to see if it resolves to a exported=false
+ * activity that doesn't share a uid with the authenticator.
+ *
+ * Otherwise the intent is considered unsafe in that it will be
+ * exploiting the fact that settings has system privileges.
+ */
+ if (isSafeIntent(pm, prefIntent)) {
+ getActivity().startActivityAsUser(prefIntent, mUserHandle);
+ } else {
+ Log.e(TAG,
+ "Refusing to launch authenticator intent because"
+ + "it exploits Settings permissions: "
+ + prefIntent);
+ }
+ return true;
+ }
+ });
}
}
}
@@ -429,6 +492,32 @@ public class ManageAccountsSettings extends AccountPreferenceBase
}
}
+ /**
+ * Determines if the supplied Intent is safe. A safe intent is one that is
+ * will launch a exported=true activity or owned by the same uid as the
+ * authenticator supplying the intent.
+ */
+ private boolean isSafeIntent(PackageManager pm, Intent intent) {
+ AuthenticatorDescription authDesc =
+ mAuthenticatorHelper.getAccountTypeDescription(mAccountType);
+ ResolveInfo resolveInfo = pm.resolveActivity(intent, 0);
+ if (resolveInfo == null) {
+ return false;
+ }
+ ActivityInfo resolvedActivityInfo = resolveInfo.activityInfo;
+ ApplicationInfo resolvedAppInfo = resolvedActivityInfo.applicationInfo;
+ try {
+ ApplicationInfo authenticatorAppInf = pm.getApplicationInfo(authDesc.packageName, 0);
+ return resolvedActivityInfo.exported
+ || resolvedAppInfo.uid == authenticatorAppInf.uid;
+ } catch (NameNotFoundException e) {
+ Log.e(TAG,
+ "Intent considered unsafe due to exception.",
+ e);
+ return false;
+ }
+ }
+
@Override
protected void onAuthDescriptionsUpdated() {
// Update account icons for all account preference items
diff --git a/src/com/android/settings/accounts/SyncSettings.java b/src/com/android/settings/accounts/SyncSettings.java
deleted file mode 100644
index 3248113..0000000
--- a/src/com/android/settings/accounts/SyncSettings.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * 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.accounts;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.accounts.OnAccountsUpdateListener;
-import android.app.Activity;
-import android.app.ActivityManager;
-import android.content.ContentResolver;
-import android.content.Intent;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.preference.CheckBoxPreference;
-import android.preference.Preference;
-import android.preference.Preference.OnPreferenceChangeListener;
-import android.preference.PreferenceScreen;
-import android.util.Log;
-
-import com.android.settings.AccountPreference;
-import com.android.settings.DialogCreatable;
-import com.android.settings.R;
-
-import java.util.ArrayList;
-
-public class SyncSettings extends AccountPreferenceBase
- implements OnAccountsUpdateListener, DialogCreatable {
-
- private static final String KEY_SYNC_SWITCH = "sync_switch";
-
- private String[] mAuthorities;
-
- private SettingsDialogFragment mDialogFragment;
- private CheckBoxPreference mAutoSyncPreference;
-
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- addPreferencesFromResource(R.xml.sync_settings);
- mAutoSyncPreference =
- (CheckBoxPreference) getPreferenceScreen().findPreference(KEY_SYNC_SWITCH);
- mAutoSyncPreference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
- @Override
- public boolean onPreferenceChange(Preference preference, Object newValue) {
- if (ActivityManager.isUserAMonkey()) {
- Log.d("SyncSettings", "ignoring monkey's attempt to flip sync state");
- } else {
- ContentResolver.setMasterSyncAutomatically((Boolean) newValue);
- }
- return true;
- }
- });
-
- setHasOptionsMenu(true);
- }
-
- @Override
- public void onStart() {
- super.onStart();
- Activity activity = getActivity();
- AccountManager.get(activity).addOnAccountsUpdatedListener(this, null, true);
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
-
- final Activity activity = getActivity();
- mAutoSyncPreference.setChecked(ContentResolver.getMasterSyncAutomatically());
- mAuthorities = activity.getIntent().getStringArrayExtra(AUTHORITIES_FILTER_KEY);
-
- updateAuthDescriptions();
- }
-
- @Override
- public void onStop() {
- super.onStop();
- final Activity activity = getActivity();
- AccountManager.get(activity).removeOnAccountsUpdatedListener(this);
- }
-
- @Override
- public boolean onPreferenceTreeClick(PreferenceScreen preferences, Preference preference) {
- if (preference instanceof AccountPreference) {
- startAccountSettings((AccountPreference) preference);
- } else {
- return false;
- }
- return true;
- }
-
- private void startAccountSettings(AccountPreference acctPref) {
- Intent intent = new Intent("android.settings.ACCOUNT_SYNC_SETTINGS");
- intent.putExtra(AccountSyncSettings.ACCOUNT_KEY, acctPref.getAccount());
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- startActivity(intent);
- finish();
- }
-
- @Override
- public void showDialog(int dialogId) {
- if (mDialogFragment != null) {
- Log.e(TAG, "Old dialog fragment not null!");
- }
- mDialogFragment = new SettingsDialogFragment(this, dialogId);
- mDialogFragment.show(getActivity().getFragmentManager(), Integer.toString(dialogId));
- }
-
- private void removeAccountPreferences() {
- PreferenceScreen parent = getPreferenceScreen();
- for (int i = 0; i < parent.getPreferenceCount(); ) {
- if (parent.getPreference(i) instanceof AccountPreference) {
- parent.removePreference(parent.getPreference(i));
- } else {
- i++;
- }
- }
- }
-
- @Override
- public void onAccountsUpdated(Account[] accounts) {
- if (getActivity() == null) return;
-
- removeAccountPreferences();
- for (int i = 0, n = accounts.length; i < n; i++) {
- final Account account = accounts[i];
- final ArrayList<String> auths = getAuthoritiesForAccountType(account.type);
-
- boolean showAccount = true;
- if (mAuthorities != null && auths != null) {
- showAccount = false;
- for (String requestedAuthority : mAuthorities) {
- if (auths.contains(requestedAuthority)) {
- showAccount = true;
- break;
- }
- }
- }
-
- if (showAccount) {
- final Drawable icon = getDrawableForType(account.type);
- final AccountPreference preference =
- new AccountPreference(getActivity(), account, icon, auths, true);
- getPreferenceScreen().addPreference(preference);
- preference.setSummary(getLabelForType(account.type));
- }
- }
- onSyncStateUpdated();
- }
-
- @Override
- protected void onAuthDescriptionsUpdated() {
- // Update account icons for all account preference items
- for (int i = 0; i < getPreferenceScreen().getPreferenceCount(); i++) {
- Preference pref = getPreferenceScreen().getPreference(i);
- if (pref instanceof AccountPreference) {
- AccountPreference accPref = (AccountPreference)
- getPreferenceScreen().getPreference(i);
- accPref.setIcon(getDrawableForType(accPref.getAccount().type));
- accPref.setSummary(getLabelForType(accPref.getAccount().type));
- }
- }
- }
-}
diff --git a/src/com/android/settings/accounts/SyncSettingsActivity.java b/src/com/android/settings/accounts/SyncSettingsActivity.java
deleted file mode 100644
index 96f16d6..0000000
--- a/src/com/android/settings/accounts/SyncSettingsActivity.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.accounts;
-
-import android.app.Fragment;
-import android.content.Intent;
-import android.preference.PreferenceActivity;
-
-import com.android.settings.ChooseLockGeneric.ChooseLockGenericFragment;
-
-/**
- * Launcher activity for the SyncSettings fragment.
- *
- */
-public class SyncSettingsActivity extends PreferenceActivity {
- @Override
- public Intent getIntent() {
- Intent modIntent = new Intent(super.getIntent());
- modIntent.putExtra(EXTRA_SHOW_FRAGMENT, SyncSettings.class.getName());
- modIntent.putExtra(EXTRA_NO_HEADERS, true);
- return modIntent;
- }
-
- @Override
- protected boolean isValidFragment(String fragmentName) {
- if (SyncSettings.class.getName().equals(fragmentName)) return true;
- return false;
- }
-} \ No newline at end of file
diff --git a/src/com/android/settings/applications/AppOpsCategory.java b/src/com/android/settings/applications/AppOpsCategory.java
index 125a43b..03ebb9e 100644
--- a/src/com/android/settings/applications/AppOpsCategory.java
+++ b/src/com/android/settings/applications/AppOpsCategory.java
@@ -28,7 +28,6 @@ import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Bundle;
-import android.preference.PreferenceActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -40,6 +39,7 @@ import android.widget.TextView;
import java.util.List;
import com.android.settings.R;
+import com.android.settings.SettingsActivity;
import com.android.settings.applications.AppOpsState.AppOpEntry;
public class AppOpsCategory extends ListFragment implements
@@ -333,8 +333,8 @@ public class AppOpsCategory extends ListFragment implements
Bundle args = new Bundle();
args.putString(AppOpsDetails.ARG_PACKAGE_NAME, mCurrentPkgName);
- PreferenceActivity pa = (PreferenceActivity)getActivity();
- pa.startPreferencePanel(AppOpsDetails.class.getName(), args,
+ SettingsActivity sa = (SettingsActivity) getActivity();
+ sa.startPreferencePanel(AppOpsDetails.class.getName(), args,
R.string.app_ops_settings, null, this, RESULT_APP_DETAILS);
}
diff --git a/src/com/android/settings/applications/AppOpsDetails.java b/src/com/android/settings/applications/AppOpsDetails.java
index 1e2ac7d..d9dec19 100644
--- a/src/com/android/settings/applications/AppOpsDetails.java
+++ b/src/com/android/settings/applications/AppOpsDetails.java
@@ -28,7 +28,6 @@ import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.content.res.Resources;
import android.os.Bundle;
-import android.preference.PreferenceActivity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -40,6 +39,7 @@ import android.widget.Switch;
import android.widget.TextView;
import com.android.settings.R;
+import com.android.settings.SettingsActivity;
import com.android.settings.Utils;
import java.util.List;
@@ -161,8 +161,8 @@ public class AppOpsDetails extends Fragment {
private void setIntentAndFinish(boolean finish, boolean appChanged) {
Intent intent = new Intent();
intent.putExtra(ManageApplications.APP_CHG, appChanged);
- PreferenceActivity pa = (PreferenceActivity)getActivity();
- pa.finishPreferencePanel(this, Activity.RESULT_OK, intent);
+ SettingsActivity sa = (SettingsActivity)getActivity();
+ sa.finishPreferencePanel(this, Activity.RESULT_OK, intent);
}
/** Called when the activity is first created. */
diff --git a/src/com/android/settings/applications/AppOpsState.java b/src/com/android/settings/applications/AppOpsState.java
index c396479..580c44e 100644
--- a/src/com/android/settings/applications/AppOpsState.java
+++ b/src/com/android/settings/applications/AppOpsState.java
@@ -103,14 +103,14 @@ public class AppOpsState {
AppOpsManager.OP_WIFI_SCAN,
AppOpsManager.OP_NEIGHBORING_CELLS,
AppOpsManager.OP_MONITOR_LOCATION,
- AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION},
+ AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION },
new boolean[] { true,
true,
false,
false,
false,
false,
- false}
+ false }
);
public static final OpsTemplate PERSONAL_TEMPLATE = new OpsTemplate(
@@ -166,7 +166,8 @@ public class AppOpsState {
AppOpsManager.OP_AUDIO_MEDIA_VOLUME,
AppOpsManager.OP_AUDIO_ALARM_VOLUME,
AppOpsManager.OP_AUDIO_NOTIFICATION_VOLUME,
- AppOpsManager.OP_AUDIO_BLUETOOTH_VOLUME, },
+ AppOpsManager.OP_AUDIO_BLUETOOTH_VOLUME,
+ AppOpsManager.OP_MUTE_MICROPHONE},
new boolean[] { false,
true,
true,
@@ -188,13 +189,17 @@ public class AppOpsState {
AppOpsManager.OP_CALL_PHONE,
AppOpsManager.OP_WRITE_SETTINGS,
AppOpsManager.OP_SYSTEM_ALERT_WINDOW,
- AppOpsManager.OP_WAKE_LOCK },
+ AppOpsManager.OP_WAKE_LOCK,
+ AppOpsManager.OP_PROJECT_MEDIA,
+ AppOpsManager.OP_ACTIVATE_VPN, },
new boolean[] { false,
true,
true,
true,
true,
- true, }
+ true,
+ false,
+ false, }
);
public static final OpsTemplate[] ALL_TEMPLATES = new OpsTemplate[] {
diff --git a/src/com/android/settings/applications/AppOpsSummary.java b/src/com/android/settings/applications/AppOpsSummary.java
index 4cee8e5..3401c99 100644
--- a/src/com/android/settings/applications/AppOpsSummary.java
+++ b/src/com/android/settings/applications/AppOpsSummary.java
@@ -16,7 +16,6 @@
package com.android.settings.applications;
-import android.app.AppOpsManager;
import android.app.Fragment;
import android.app.FragmentManager;
import android.os.Bundle;
@@ -104,7 +103,7 @@ public class AppOpsSummary extends Fragment {
mViewPager.setAdapter(adapter);
mViewPager.setOnPageChangeListener(adapter);
PagerTabStrip tabs = (PagerTabStrip) rootView.findViewById(R.id.tabs);
- tabs.setTabIndicatorColorResource(android.R.color.holo_blue_light);
+ tabs.setTabIndicatorColorResource(R.color.theme_accent);
// We have to do this now because PreferenceFrameLayout looks at it
// only when the view is added.
diff --git a/src/com/android/settings/applications/InstalledAppDetails.java b/src/com/android/settings/applications/InstalledAppDetails.java
index cbb06a0..4b1bc10 100644..100755
--- a/src/com/android/settings/applications/InstalledAppDetails.java
+++ b/src/com/android/settings/applications/InstalledAppDetails.java
@@ -19,6 +19,7 @@ package com.android.settings.applications;
import com.android.internal.telephony.ISms;
import com.android.internal.telephony.SmsUsageMonitor;
import com.android.settings.R;
+import com.android.settings.SettingsActivity;
import com.android.settings.Utils;
import com.android.settings.applications.ApplicationsState.AppEntry;
@@ -57,7 +58,6 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
-import android.preference.PreferenceActivity;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.format.Formatter;
@@ -126,6 +126,7 @@ public class InstalledAppDetails extends Fragment
private CheckBox mAskCompatibilityCB;
private CheckBox mEnableCompatibilityCB;
private boolean mCanClearData = true;
+ private boolean mAppControlRestricted = false;
private TextView mAppVersion;
private TextView mTotalSize;
private TextView mAppSize;
@@ -264,6 +265,10 @@ public class InstalledAppDetails extends Fragment
}
mClearDataButton.setOnClickListener(this);
}
+
+ if (mAppControlRestricted) {
+ mClearDataButton.setEnabled(false);
+ }
}
private CharSequence getMoveErrMsg(int errCode) {
@@ -303,7 +308,7 @@ public class InstalledAppDetails extends Fragment
mCanBeOnSdCardChecker.init();
moveDisable = !mCanBeOnSdCardChecker.check(mAppEntry.info);
}
- if (moveDisable) {
+ if (moveDisable || mAppControlRestricted) {
mMoveAppButton.setEnabled(false);
} else {
mMoveAppButton.setOnClickListener(this);
@@ -311,22 +316,13 @@ public class InstalledAppDetails extends Fragment
}
}
- private boolean isThisASystemPackage() {
- try {
- PackageInfo sys = mPm.getPackageInfo("android", PackageManager.GET_SIGNATURES);
- return (mPackageInfo != null && mPackageInfo.signatures != null &&
- sys.signatures[0].equals(mPackageInfo.signatures[0]));
- } catch (PackageManager.NameNotFoundException e) {
- return false;
- }
- }
-
private boolean handleDisableable(Button button) {
boolean disableable = false;
// Try to prevent the user from bricking their phone
// by not allowing disabling of apps signed with the
// system cert and any launcher app in the system.
- if (mHomePackages.contains(mAppEntry.info.packageName) || isThisASystemPackage()) {
+ if (mHomePackages.contains(mAppEntry.info.packageName)
+ || Utils.isSystemPackage(mPm, mPackageInfo)) {
// Disable button for core system applications.
button.setText(R.string.disable_text);
} else if (mAppEntry.info.enabled) {
@@ -342,18 +338,22 @@ public class InstalledAppDetails extends Fragment
private void initUninstallButtons() {
mUpdatedSysApp = (mAppEntry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
+ final boolean isBundled = (mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
boolean enabled = true;
if (mUpdatedSysApp) {
mUninstallButton.setText(R.string.app_factory_reset);
- boolean specialDisable = false;
- if ((mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
- specialDisable = handleDisableable(mSpecialDisableButton);
+ boolean showSpecialDisable = false;
+ if (isBundled) {
+ showSpecialDisable = handleDisableable(mSpecialDisableButton);
mSpecialDisableButton.setOnClickListener(this);
}
- mMoreControlButtons.setVisibility(specialDisable ? View.VISIBLE : View.GONE);
+ if (mAppControlRestricted) {
+ showSpecialDisable = false;
+ }
+ mMoreControlButtons.setVisibility(showSpecialDisable ? View.VISIBLE : View.GONE);
} else {
mMoreControlButtons.setVisibility(View.GONE);
- if ((mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ if (isBundled) {
enabled = handleDisableable(mUninstallButton);
} else if ((mPackageInfo.applicationInfo.flags
& ApplicationInfo.FLAG_INSTALLED) == 0
@@ -372,22 +372,34 @@ public class InstalledAppDetails extends Fragment
enabled = false;
}
- // If this is the default (or only) home app, suppress uninstall (even if
- // we still think it should be allowed for other reasons)
+ // Home apps need special handling. Bundled ones we don't risk downgrading
+ // because that can interfere with home-key resolution. Furthermore, we
+ // can't allow uninstallation of the only home app, and we don't want to
+ // allow uninstallation of an explicitly preferred one -- the user can go
+ // to Home settings and pick a different one, after which we'll permit
+ // uninstallation of the now-not-default one.
if (enabled && mHomePackages.contains(mPackageInfo.packageName)) {
- ArrayList<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>();
- ComponentName currentDefaultHome = mPm.getHomeActivities(homeActivities);
- if (currentDefaultHome == null) {
- // No preferred default, so permit uninstall only when
- // there is more than one candidate
- enabled = (mHomePackages.size() > 1);
+ if (isBundled) {
+ enabled = false;
} else {
- // There is an explicit default home app -- forbid uninstall of
- // that one, but permit it for installed-but-inactive ones.
- enabled = !mPackageInfo.packageName.equals(currentDefaultHome.getPackageName());
+ ArrayList<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>();
+ ComponentName currentDefaultHome = mPm.getHomeActivities(homeActivities);
+ if (currentDefaultHome == null) {
+ // No preferred default, so permit uninstall only when
+ // there is more than one candidate
+ enabled = (mHomePackages.size() > 1);
+ } else {
+ // There is an explicit default home app -- forbid uninstall of
+ // that one, but permit it for installed-but-inactive ones.
+ enabled = !mPackageInfo.packageName.equals(currentDefaultHome.getPackageName());
+ }
}
}
+ if (mAppControlRestricted) {
+ enabled = false;
+ }
+
mUninstallButton.setEnabled(enabled);
if (enabled) {
// Register listener
@@ -406,7 +418,7 @@ public class InstalledAppDetails extends Fragment
// this does not bode well
}
mNotificationSwitch.setChecked(enabled);
- if (isThisASystemPackage()) {
+ if (Utils.isSystemPackage(mPm, mPackageInfo)) {
mNotificationSwitch.setEnabled(false);
} else {
mNotificationSwitch.setEnabled(true);
@@ -443,17 +455,19 @@ public class InstalledAppDetails extends Fragment
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.installed_app_details, container, false);
- Utils.prepareCustomPreferencesList(container, view, view, false);
+
+ final ViewGroup allDetails = (ViewGroup) view.findViewById(R.id.all_details);
+ Utils.forceCustomPadding(allDetails, true /* additive padding */);
mRootView = view;
mComputingStr = getActivity().getText(R.string.computing_size);
// Set default values on sizes
- mTotalSize = (TextView)view.findViewById(R.id.total_size_text);
- mAppSize = (TextView)view.findViewById(R.id.application_size_text);
- mDataSize = (TextView)view.findViewById(R.id.data_size_text);
- mExternalCodeSize = (TextView)view.findViewById(R.id.external_code_size_text);
- mExternalDataSize = (TextView)view.findViewById(R.id.external_data_size_text);
+ mTotalSize = (TextView) view.findViewById(R.id.total_size_text);
+ mAppSize = (TextView) view.findViewById(R.id.application_size_text);
+ mDataSize = (TextView) view.findViewById(R.id.data_size_text);
+ mExternalCodeSize = (TextView) view.findViewById(R.id.external_code_size_text);
+ mExternalDataSize = (TextView) view.findViewById(R.id.external_data_size_text);
if (Environment.isExternalStorageEmulated()) {
((View)mExternalCodeSize.getParent()).setVisibility(View.GONE);
@@ -464,13 +478,13 @@ public class InstalledAppDetails extends Fragment
View btnPanel = view.findViewById(R.id.control_buttons_panel);
mForceStopButton = (Button) btnPanel.findViewById(R.id.left_button);
mForceStopButton.setText(R.string.force_stop);
- mUninstallButton = (Button)btnPanel.findViewById(R.id.right_button);
+ mUninstallButton = (Button) btnPanel.findViewById(R.id.right_button);
mForceStopButton.setEnabled(false);
// Get More Control button panel
mMoreControlButtons = view.findViewById(R.id.more_control_buttons_panel);
mMoreControlButtons.findViewById(R.id.left_button).setVisibility(View.INVISIBLE);
- mSpecialDisableButton = (Button)mMoreControlButtons.findViewById(R.id.right_button);
+ mSpecialDisableButton = (Button) mMoreControlButtons.findViewById(R.id.right_button);
mMoreControlButtons.setVisibility(View.GONE);
// Initialize clear data and move install location buttons
@@ -482,12 +496,12 @@ public class InstalledAppDetails extends Fragment
mCacheSize = (TextView) view.findViewById(R.id.cache_size_text);
mClearCacheButton = (Button) view.findViewById(R.id.clear_cache_button);
- mActivitiesButton = (Button)view.findViewById(R.id.clear_activities_button);
+ mActivitiesButton = (Button) view.findViewById(R.id.clear_activities_button);
// Screen compatibility control
mScreenCompatSection = view.findViewById(R.id.screen_compatibility_section);
- mAskCompatibilityCB = (CheckBox)view.findViewById(R.id.ask_compatibility_cb);
- mEnableCompatibilityCB = (CheckBox)view.findViewById(R.id.enable_compatibility_cb);
+ mAskCompatibilityCB = (CheckBox) view.findViewById(R.id.ask_compatibility_cb);
+ mEnableCompatibilityCB = (CheckBox) view.findViewById(R.id.enable_compatibility_cb);
mNotificationSwitch = (CompoundButton) view.findViewById(R.id.notification_switch);
@@ -580,6 +594,7 @@ public class InstalledAppDetails extends Fragment
public void onResume() {
super.onResume();
+ mAppControlRestricted = mUserManager.hasUserRestriction(UserManager.DISALLOW_APPS_CONTROL);
mSession.resume();
if (!refreshUi()) {
setIntentAndFinish(true, true);
@@ -593,6 +608,12 @@ public class InstalledAppDetails extends Fragment
}
@Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ mSession.release();
+ }
+
+ @Override
public void onAllSizesComputed() {
}
@@ -937,8 +958,8 @@ public class InstalledAppDetails extends Fragment
if(localLOGV) Log.i(TAG, "appChanged="+appChanged);
Intent intent = new Intent();
intent.putExtra(ManageApplications.APP_CHG, appChanged);
- PreferenceActivity pa = (PreferenceActivity)getActivity();
- pa.finishPreferencePanel(this, Activity.RESULT_OK, intent);
+ SettingsActivity sa = (SettingsActivity)getActivity();
+ sa.finishPreferencePanel(this, Activity.RESULT_OK, intent);
}
private void refreshSizeInfo() {
@@ -1002,6 +1023,10 @@ public class InstalledAppDetails extends Fragment
mClearCacheButton.setOnClickListener(this);
}
}
+ if (mAppControlRestricted) {
+ mClearCacheButton.setEnabled(false);
+ mClearDataButton.setEnabled(false);
+ }
}
/*
@@ -1103,7 +1128,6 @@ public class InstalledAppDetails extends Fragment
case DLG_CLEAR_DATA:
return new AlertDialog.Builder(getActivity())
.setTitle(getActivity().getText(R.string.clear_data_dlg_title))
- .setIconAttribute(android.R.attr.alertDialogIcon)
.setMessage(getActivity().getText(R.string.clear_data_dlg_text))
.setPositiveButton(R.string.dlg_ok,
new DialogInterface.OnClickListener() {
@@ -1117,7 +1141,6 @@ public class InstalledAppDetails extends Fragment
case DLG_FACTORY_RESET:
return new AlertDialog.Builder(getActivity())
.setTitle(getActivity().getText(R.string.app_factory_reset_dlg_title))
- .setIconAttribute(android.R.attr.alertDialogIcon)
.setMessage(getActivity().getText(R.string.app_factory_reset_dlg_text))
.setPositiveButton(R.string.dlg_ok,
new DialogInterface.OnClickListener() {
@@ -1132,7 +1155,6 @@ public class InstalledAppDetails extends Fragment
case DLG_APP_NOT_FOUND:
return new AlertDialog.Builder(getActivity())
.setTitle(getActivity().getText(R.string.app_not_found_dlg_title))
- .setIconAttribute(android.R.attr.alertDialogIcon)
.setMessage(getActivity().getText(R.string.app_not_found_dlg_title))
.setNeutralButton(getActivity().getText(R.string.dlg_ok),
new DialogInterface.OnClickListener() {
@@ -1145,7 +1167,6 @@ public class InstalledAppDetails extends Fragment
case DLG_CANNOT_CLEAR_DATA:
return new AlertDialog.Builder(getActivity())
.setTitle(getActivity().getText(R.string.clear_failed_dlg_title))
- .setIconAttribute(android.R.attr.alertDialogIcon)
.setMessage(getActivity().getText(R.string.clear_failed_dlg_text))
.setNeutralButton(R.string.dlg_ok,
new DialogInterface.OnClickListener() {
@@ -1159,7 +1180,6 @@ public class InstalledAppDetails extends Fragment
case DLG_FORCE_STOP:
return new AlertDialog.Builder(getActivity())
.setTitle(getActivity().getText(R.string.force_stop_dlg_title))
- .setIconAttribute(android.R.attr.alertDialogIcon)
.setMessage(getActivity().getText(R.string.force_stop_dlg_text))
.setPositiveButton(R.string.dlg_ok,
new DialogInterface.OnClickListener() {
@@ -1175,14 +1195,12 @@ public class InstalledAppDetails extends Fragment
getOwner().getMoveErrMsg(moveErrorCode));
return new AlertDialog.Builder(getActivity())
.setTitle(getActivity().getText(R.string.move_app_failed_dlg_title))
- .setIconAttribute(android.R.attr.alertDialogIcon)
.setMessage(msg)
.setNeutralButton(R.string.dlg_ok, null)
.create();
case DLG_DISABLE:
return new AlertDialog.Builder(getActivity())
.setTitle(getActivity().getText(R.string.app_disable_dlg_title))
- .setIconAttribute(android.R.attr.alertDialogIcon)
.setMessage(getActivity().getText(R.string.app_disable_dlg_text))
.setPositiveButton(R.string.dlg_ok,
new DialogInterface.OnClickListener() {
@@ -1198,7 +1216,6 @@ public class InstalledAppDetails extends Fragment
case DLG_DISABLE_NOTIFICATIONS:
return new AlertDialog.Builder(getActivity())
.setTitle(getActivity().getText(R.string.app_disable_notifications_dlg_title))
- .setIconAttribute(android.R.attr.alertDialogIcon)
.setMessage(getActivity().getText(R.string.app_disable_notifications_dlg_text))
.setPositiveButton(R.string.dlg_ok,
new DialogInterface.OnClickListener() {
@@ -1218,7 +1235,6 @@ public class InstalledAppDetails extends Fragment
case DLG_SPECIAL_DISABLE:
return new AlertDialog.Builder(getActivity())
.setTitle(getActivity().getText(R.string.app_special_disable_dlg_title))
- .setIconAttribute(android.R.attr.alertDialogIcon)
.setMessage(getActivity().getText(R.string.app_special_disable_dlg_text))
.setPositiveButton(R.string.dlg_ok,
new DialogInterface.OnClickListener() {
@@ -1264,8 +1280,12 @@ public class InstalledAppDetails extends Fragment
};
private void updateForceStopButton(boolean enabled) {
- mForceStopButton.setEnabled(enabled);
- mForceStopButton.setOnClickListener(InstalledAppDetails.this);
+ if (mAppControlRestricted) {
+ mForceStopButton.setEnabled(false);
+ } else {
+ mForceStopButton.setEnabled(enabled);
+ mForceStopButton.setOnClickListener(InstalledAppDetails.this);
+ }
}
private void checkForceStop() {
diff --git a/src/com/android/settings/applications/InstalledAppDetailsTop.java b/src/com/android/settings/applications/InstalledAppDetailsTop.java
index 44a88fb..e078729 100644
--- a/src/com/android/settings/applications/InstalledAppDetailsTop.java
+++ b/src/com/android/settings/applications/InstalledAppDetailsTop.java
@@ -1,18 +1,30 @@
+/*
+ * 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.applications;
-import android.app.Fragment;
import android.content.Intent;
-import android.preference.PreferenceActivity;
-
-import com.android.settings.ChooseLockGeneric.ChooseLockGenericFragment;
+import com.android.settings.SettingsActivity;
-public class InstalledAppDetailsTop extends PreferenceActivity {
+public class InstalledAppDetailsTop extends SettingsActivity {
@Override
public Intent getIntent() {
Intent modIntent = new Intent(super.getIntent());
modIntent.putExtra(EXTRA_SHOW_FRAGMENT, InstalledAppDetails.class.getName());
- modIntent.putExtra(EXTRA_NO_HEADERS, true);
return modIntent;
}
@@ -21,5 +33,4 @@ public class InstalledAppDetailsTop extends PreferenceActivity {
if (InstalledAppDetails.class.getName().equals(fragmentName)) return true;
return false;
}
-
}
diff --git a/src/com/android/settings/applications/LinearColorBar.java b/src/com/android/settings/applications/LinearColorBar.java
index f374c29..158a625 100644
--- a/src/com/android/settings/applications/LinearColorBar.java
+++ b/src/com/android/settings/applications/LinearColorBar.java
@@ -16,9 +16,9 @@ import android.view.MotionEvent;
import android.widget.LinearLayout;
public class LinearColorBar extends LinearLayout {
- static final int LEFT_COLOR = 0xff0099cc;
- static final int MIDDLE_COLOR = 0xff0099cc;
- static final int RIGHT_COLOR = 0xff888888;
+ static final int LEFT_COLOR = 0xff009688;
+ static final int MIDDLE_COLOR = 0xff009688;
+ static final int RIGHT_COLOR = 0xffced7db;
static final int GRAY_COLOR = 0xff555555;
static final int WHITE_COLOR = 0xffffffff;
diff --git a/src/com/android/settings/applications/LinearColorPreference.java b/src/com/android/settings/applications/LinearColorPreference.java
index 8d9fb72..b5f707e 100644
--- a/src/com/android/settings/applications/LinearColorPreference.java
+++ b/src/com/android/settings/applications/LinearColorPreference.java
@@ -25,6 +25,9 @@ public class LinearColorPreference extends Preference {
float mRedRatio;
float mYellowRatio;
float mGreenRatio;
+ int mRedColor = 0xffaa5030;
+ int mYellowColor = 0xffaaaa30;
+ int mGreenColor = 0xff30aa50;
int mColoredRegions = LinearColorBar.REGION_ALL;
LinearColorBar.OnRegionTappedListener mOnRegionTappedListener;
@@ -40,6 +43,13 @@ public class LinearColorPreference extends Preference {
notifyChanged();
}
+ public void setColors(int red, int yellow, int green) {
+ mRedColor = red;
+ mYellowColor = yellow;
+ mGreenColor = green;
+ notifyChanged();
+ }
+
public void setOnRegionTappedListener(LinearColorBar.OnRegionTappedListener listener) {
mOnRegionTappedListener = listener;
notifyChanged();
@@ -57,7 +67,7 @@ public class LinearColorPreference extends Preference {
LinearColorBar colors = (LinearColorBar)view.findViewById(
R.id.linear_color_bar);
colors.setShowIndicator(false);
- colors.setColors(0xffaa5030, 0xffaaaa30, 0xff30aa50);
+ colors.setColors(mRedColor, mYellowColor, mGreenColor);
colors.setRatios(mRedRatio, mYellowRatio, mGreenRatio);
colors.setColoredRegions(mColoredRegions);
colors.setOnRegionTappedListener(mOnRegionTappedListener);
diff --git a/src/com/android/settings/applications/ManageApplications.java b/src/com/android/settings/applications/ManageApplications.java
index 0a1dcb1..e64e56e 100644
--- a/src/com/android/settings/applications/ManageApplications.java
+++ b/src/com/android/settings/applications/ManageApplications.java
@@ -29,7 +29,6 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
@@ -44,14 +43,12 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
-import android.preference.PreferenceActivity;
+import android.os.UserManager;
import android.preference.PreferenceFrameLayout;
import android.provider.Settings;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.PagerTabStrip;
import android.support.v4.view.ViewPager;
-import android.text.BidiFormatter;
-import android.text.format.Formatter;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -63,15 +60,18 @@ import android.view.animation.AnimationUtils;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
+import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.BaseAdapter;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.ListView;
-import android.widget.TextView;
+import android.widget.Spinner;
import com.android.internal.app.IMediaContainerService;
import com.android.internal.content.PackageHelper;
import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.UserSpinnerAdapter;
import com.android.settings.Settings.RunningServicesActivity;
import com.android.settings.Settings.StorageUseActivity;
import com.android.settings.applications.ApplicationsState.AppEntry;
@@ -136,11 +136,12 @@ interface AppClickListener {
*/
public class ManageApplications extends Fragment implements
AppClickListener, DialogInterface.OnClickListener,
- DialogInterface.OnDismissListener {
+ DialogInterface.OnDismissListener, OnItemSelectedListener {
static final String TAG = "ManageApplications";
static final boolean DEBUG = false;
+ private static final String EXTRA_LIST_TYPE = "currentListType";
private static final String EXTRA_SORT_ORDER = "sortOrder";
private static final String EXTRA_SHOW_BACKGROUND = "showBackground";
private static final String EXTRA_DEFAULT_LIST_TYPE = "defaultListType";
@@ -196,15 +197,17 @@ public class ManageApplications extends Fragment implements
private View mListContainer;
+ private ViewGroup mPinnedHeader;
+
// ListView used to display list
private ListView mListView;
// Custom view used to display running processes
private RunningProcessesView mRunningProcessesView;
- private LinearColorBar mColorBar;
- private TextView mStorageChartLabel;
- private TextView mUsedStorageText;
- private TextView mFreeStorageText;
+ //private LinearColorBar mColorBar;
+ //private TextView mStorageChartLabel;
+ //private TextView mUsedStorageText;
+ //private TextView mFreeStorageText;
private long mFreeStorage = 0, mAppStorage = 0, mTotalStorage = 0;
private long mLastUsedStorage, mLastAppStorage, mLastFreeStorage;
@@ -247,6 +250,14 @@ public class ManageApplications extends Fragment implements
mRootView = inflater.inflate(mListType == LIST_TYPE_RUNNING
? R.layout.manage_applications_running
: R.layout.manage_applications_apps, null);
+ mPinnedHeader = (ViewGroup) mRootView.findViewById(R.id.pinned_header);
+ if (mOwner.mProfileSpinnerAdapter != null) {
+ Spinner spinner = (Spinner) inflater.inflate(R.layout.spinner_view, null);
+ spinner.setAdapter(mOwner.mProfileSpinnerAdapter);
+ spinner.setOnItemSelectedListener(mOwner);
+ mPinnedHeader.addView(spinner);
+ mPinnedHeader.setVisibility(View.VISIBLE);
+ }
mLoadingContainer = mRootView.findViewById(R.id.loading_container);
mLoadingContainer.setVisibility(View.VISIBLE);
mListContainer = mRootView.findViewById(R.id.list_container);
@@ -265,17 +276,17 @@ public class ManageApplications extends Fragment implements
mApplications = new ApplicationsAdapter(mApplicationsState, this, mFilter);
mListView.setAdapter(mApplications);
mListView.setRecyclerListener(mApplications);
- mColorBar = (LinearColorBar)mListContainer.findViewById(R.id.storage_color_bar);
- mStorageChartLabel = (TextView)mListContainer.findViewById(R.id.storageChartLabel);
- mUsedStorageText = (TextView)mListContainer.findViewById(R.id.usedStorageText);
- mFreeStorageText = (TextView)mListContainer.findViewById(R.id.freeStorageText);
+ //mColorBar = (LinearColorBar)mListContainer.findViewById(R.id.storage_color_bar);
+ //mStorageChartLabel = (TextView)mListContainer.findViewById(R.id.storageChartLabel);
+ //mUsedStorageText = (TextView)mListContainer.findViewById(R.id.usedStorageText);
+ //mFreeStorageText = (TextView)mListContainer.findViewById(R.id.freeStorageText);
Utils.prepareCustomPreferencesList(contentParent, contentChild, mListView, false);
if (mFilter == FILTER_APPS_SDCARD) {
- mStorageChartLabel.setText(mOwner.getActivity().getText(
- R.string.sd_card_storage));
+ //mStorageChartLabel.setText(mOwner.getActivity().getText(
+ // R.string.sd_card_storage));
} else {
- mStorageChartLabel.setText(mOwner.getActivity().getText(
- R.string.internal_storage));
+ //mStorageChartLabel.setText(mOwner.getActivity().getText(
+ // R.string.internal_storage));
}
applyCurrentStorage();
}
@@ -321,6 +332,12 @@ public class ManageApplications extends Fragment implements
}
}
+ public void release() {
+ if (mApplications != null) {
+ mApplications.release();
+ }
+ }
+
void updateStorageUsage() {
// Make sure a callback didn't come at an inopportune time.
if (mOwner.getActivity() == null) return;
@@ -385,6 +402,7 @@ public class ManageApplications extends Fragment implements
if (mRootView == null) {
return;
}
+ /*
if (mTotalStorage > 0) {
BidiFormatter bidiFormatter = BidiFormatter.getInstance();
mColorBar.setRatios((mTotalStorage-mFreeStorage-mAppStorage)/(float)mTotalStorage,
@@ -415,6 +433,7 @@ public class ManageApplications extends Fragment implements
mFreeStorageText.setText("");
}
}
+ */
}
@Override
@@ -448,7 +467,8 @@ public class ManageApplications extends Fragment implements
// These are for keeping track of activity and spinner switch state.
private boolean mActivityResumed;
-
+
+ private static final int LIST_TYPE_MISSING = -1;
static final int LIST_TYPE_DOWNLOADED = 0;
static final int LIST_TYPE_RUNNING = 1;
static final int LIST_TYPE_SDCARD = 2;
@@ -462,6 +482,8 @@ public class ManageApplications extends Fragment implements
private ViewGroup mContentContainer;
private View mRootView;
private ViewPager mViewPager;
+ private UserSpinnerAdapter mProfileSpinnerAdapter;
+ private Context mContext;
AlertDialog mResetDialog;
@@ -593,6 +615,10 @@ public class ManageApplications extends Fragment implements
}
}
+ public void release() {
+ mSession.release();
+ }
+
public void rebuild(int sort) {
if (sort == mLastSortMode) {
return;
@@ -820,13 +846,14 @@ public class ManageApplications extends Fragment implements
mActive.remove(view);
}
}
-
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
+ mContext = getActivity();
mApplicationsState = ApplicationsState.getInstance(getActivity().getApplication());
Intent intent = getActivity().getIntent();
String action = intent.getAction();
@@ -844,7 +871,7 @@ public class ManageApplications extends Fragment implements
|| className.endsWith(".StorageUse")) {
mSortOrder = SORT_ORDER_SIZE;
defaultListType = LIST_TYPE_ALL;
- } else if (Settings.ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS.equals(action)) {
+ } else if (android.provider.Settings.ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS.equals(action)) {
// Select the all-apps list, with the default sorting
defaultListType = LIST_TYPE_ALL;
}
@@ -893,6 +920,9 @@ public class ManageApplications extends Fragment implements
mTabs.add(tab);
mNumTabs = mTabs.size();
+
+ final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ mProfileSpinnerAdapter = Utils.createUserSpinnerAdapter(um, mContext);
}
@@ -911,7 +941,7 @@ public class ManageApplications extends Fragment implements
mViewPager.setAdapter(adapter);
mViewPager.setOnPageChangeListener(adapter);
PagerTabStrip tabs = (PagerTabStrip) rootView.findViewById(R.id.tabs);
- tabs.setTabIndicatorColorResource(android.R.color.holo_blue_light);
+ tabs.setTabIndicatorColorResource(R.color.theme_accent);
// We have to do this now because PreferenceFrameLayout looks at it
// only when the view is added.
@@ -925,9 +955,13 @@ public class ManageApplications extends Fragment implements
if (savedInstanceState == null) {
// First time init: make sure view pager is showing the correct tab.
- for (int i = 0; i < mTabs.size(); i++) {
+ int extraCurrentListType = getActivity().getIntent().getIntExtra(EXTRA_LIST_TYPE,
+ LIST_TYPE_MISSING);
+ int currentListType = (extraCurrentListType != LIST_TYPE_MISSING)
+ ? extraCurrentListType : mDefaultListType;
+ for (int i = 0; i < mNumTabs; i++) {
TabInfo tab = mTabs.get(i);
- if (tab.mListType == mDefaultListType) {
+ if (tab.mListType == currentListType) {
mViewPager.setCurrentItem(i);
break;
}
@@ -990,6 +1024,7 @@ public class ManageApplications extends Fragment implements
// are no longer attached to their view hierarchy.
for (int i=0; i<mTabs.size(); i++) {
mTabs.get(i).detachView();
+ mTabs.get(i).release();
}
}
@@ -1000,6 +1035,24 @@ public class ManageApplications extends Fragment implements
}
}
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ UserHandle selectedUser = mProfileSpinnerAdapter.getUserHandle(position);
+ if (selectedUser.getIdentifier() != UserHandle.myUserId()) {
+ Intent intent = new Intent(Settings.ACTION_APPLICATION_SETTINGS);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ int currentTab = mViewPager.getCurrentItem();
+ intent.putExtra(EXTRA_LIST_TYPE, mTabs.get(currentTab).mListType);
+ mContext.startActivityAsUser(intent, selectedUser);
+ }
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ // Nothing to do
+ }
+
private void updateNumTabs() {
int newNum = mApplicationsState.haveDisabledApps() ? mTabs.size() : (mTabs.size()-1);
if (newNum != mNumTabs) {
@@ -1026,8 +1079,8 @@ public class ManageApplications extends Fragment implements
Bundle args = new Bundle();
args.putString(InstalledAppDetails.ARG_PACKAGE_NAME, mCurrentPkgName);
- PreferenceActivity pa = (PreferenceActivity)getActivity();
- pa.startPreferencePanel(InstalledAppDetails.class.getName(), args,
+ SettingsActivity sa = (SettingsActivity) getActivity();
+ sa.startPreferencePanel(InstalledAppDetails.class.getName(), args,
R.string.application_info_label, null, this, INSTALLED_APP_DETAILS);
}
diff --git a/src/com/android/settings/applications/ProcStatsEntry.java b/src/com/android/settings/applications/ProcStatsEntry.java
index 0821ced..8702478 100644
--- a/src/com/android/settings/applications/ProcStatsEntry.java
+++ b/src/com/android/settings/applications/ProcStatsEntry.java
@@ -23,6 +23,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArrayMap;
import android.util.Log;
+import android.util.SparseArray;
import com.android.internal.app.ProcessStats;
import java.util.ArrayList;
@@ -109,22 +110,26 @@ public final class ProcStatsEntry implements Parcelable {
// See if there is one significant package that was running here.
ArrayList<ProcStatsEntry> subProcs = new ArrayList<ProcStatsEntry>();
for (int ipkg=0; ipkg<mPackages.size(); ipkg++) {
- ProcessStats.PackageState pkgState = stats.mPackages.get(mPackages.get(ipkg), mUid);
- if (DEBUG) Log.d(TAG, "Eval pkg of " + mName + ", pkg "
- + mPackages.get(ipkg) + ":");
- if (pkgState == null) {
- Log.w(TAG, "No package state found for " + mPackages.get(ipkg) + "/"
- + mUid + " in process " + mName);
- continue;
- }
- ProcessStats.ProcessState pkgProc = pkgState.mProcesses.get(mName);
- if (pkgProc == null) {
- Log.w(TAG, "No process " + mName + " found in package state "
- + mPackages.get(ipkg) + "/" + mUid);
- continue;
+ SparseArray<ProcessStats.PackageState> vpkgs
+ = stats.mPackages.get(mPackages.get(ipkg), mUid);
+ for (int ivers=0; ivers<vpkgs.size(); ivers++) {
+ ProcessStats.PackageState pkgState = vpkgs.valueAt(ivers);
+ if (DEBUG) Log.d(TAG, "Eval pkg of " + mName + ", pkg "
+ + pkgState + ":");
+ if (pkgState == null) {
+ Log.w(TAG, "No package state found for " + mPackages.get(ipkg) + "/"
+ + mUid + " in process " + mName);
+ continue;
+ }
+ ProcessStats.ProcessState pkgProc = pkgState.mProcesses.get(mName);
+ if (pkgProc == null) {
+ Log.w(TAG, "No process " + mName + " found in package state "
+ + mPackages.get(ipkg) + "/" + mUid);
+ continue;
+ }
+ subProcs.add(new ProcStatsEntry(pkgProc, pkgState.mPackageName, totals, useUss,
+ weightWithTime));
}
- subProcs.add(new ProcStatsEntry(pkgProc, pkgState.mPackageName, totals, useUss,
- weightWithTime));
}
if (subProcs.size() > 1) {
Collections.sort(subProcs, compare);
diff --git a/src/com/android/settings/applications/ProcessStatsDetail.java b/src/com/android/settings/applications/ProcessStatsDetail.java
index 326ca7b..30f6b52 100644
--- a/src/com/android/settings/applications/ProcessStatsDetail.java
+++ b/src/com/android/settings/applications/ProcessStatsDetail.java
@@ -30,7 +30,6 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Process;
import android.os.UserHandle;
-import android.preference.PreferenceActivity;
import android.text.format.Formatter;
import android.view.LayoutInflater;
import android.view.View;
@@ -40,6 +39,7 @@ import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.android.settings.R;
+import com.android.settings.Utils;
import java.util.ArrayList;
import java.util.Collections;
@@ -73,11 +73,6 @@ public class ProcessStatsDetail extends Fragment implements Button.OnClickListen
private ViewGroup mDetailsParent;
private ViewGroup mServicesParent;
- public static String makePercentString(Resources res, long amount, long total) {
- final double percent = (((double)amount) / total) * 100;
- return res.getString(R.string.percentage, (int) Math.round(percent));
- }
-
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
@@ -117,7 +112,7 @@ public class ProcessStatsDetail extends Fragment implements Button.OnClickListen
final double percentOfWeight = (((double)mEntry.mWeight) / mMaxWeight) * 100;
int appLevel = (int) Math.ceil(percentOfWeight);
- String appLevelText = makePercentString(getResources(), mEntry.mDuration, mTotalTime);
+ String appLevelText = Utils.formatPercentage(mEntry.mDuration, mTotalTime);
// Set all values in the header.
final TextView summary = (TextView) mRootView.findViewById(android.R.id.summary);
@@ -161,7 +156,6 @@ public class ProcessStatsDetail extends Fragment implements Button.OnClickListen
}
private void doAction(int action) {
- PreferenceActivity pa = (PreferenceActivity)getActivity();
switch (action) {
case ACTION_FORCE_STOP:
killProcesses();
@@ -205,7 +199,7 @@ public class ProcessStatsDetail extends Fragment implements Button.OnClickListen
Formatter.formatShortFileSize(getActivity(),
(mUseUss ? mEntry.mMaxUss : mEntry.mMaxPss) * 1024));
addDetailsItem(mDetailsParent, getResources().getText(R.string.process_stats_run_time),
- makePercentString(getResources(), mEntry.mDuration, mTotalTime));
+ Utils.formatPercentage(mEntry.mDuration, mTotalTime));
}
final static Comparator<ProcStatsEntry.Service> sServiceCompare
@@ -267,10 +261,8 @@ public class ProcessStatsDetail extends Fragment implements Button.OnClickListen
if (tail >= 0 && tail < (label.length()-1)) {
label = label.substring(tail+1);
}
- long duration = service.mDuration;
- final double percentOfTime = (((double)duration) / mTotalTime) * 100;
- addDetailsItem(mServicesParent, label, getActivity().getResources().getString(
- R.string.percentage, (int) Math.ceil(percentOfTime)));
+ String percentage = Utils.formatPercentage(service.mDuration, mTotalTime);
+ addDetailsItem(mServicesParent, label, percentage);
}
}
}
diff --git a/src/com/android/settings/applications/ProcessStatsMemDetail.java b/src/com/android/settings/applications/ProcessStatsMemDetail.java
new file mode 100644
index 0000000..ab7dbdb
--- /dev/null
+++ b/src/com/android/settings/applications/ProcessStatsMemDetail.java
@@ -0,0 +1,152 @@
+/*
+ * 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.applications;
+
+import android.app.Fragment;
+import android.os.Bundle;
+import android.text.format.Formatter;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import com.android.internal.app.ProcessStats;
+import com.android.settings.R;
+
+import static com.android.settings.Utils.prepareCustomPreferencesList;
+
+public class ProcessStatsMemDetail extends Fragment {
+ public static final String EXTRA_MEM_TIMES = "mem_times";
+ public static final String EXTRA_MEM_STATE_WEIGHTS = "mem_state_weights";
+ public static final String EXTRA_MEM_CACHED_WEIGHT = "mem_cached_weight";
+ public static final String EXTRA_MEM_FREE_WEIGHT = "mem_free_weight";
+ public static final String EXTRA_MEM_ZRAM_WEIGHT = "mem_zram_weight";
+ public static final String EXTRA_MEM_KERNEL_WEIGHT = "mem_kernel_weight";
+ public static final String EXTRA_MEM_NATIVE_WEIGHT = "mem_native_weight";
+ public static final String EXTRA_MEM_TOTAL_WEIGHT = "mem_total_weight";
+ public static final String EXTRA_USE_USS = "use_uss";
+ public static final String EXTRA_TOTAL_TIME = "total_time";
+
+ long[] mMemTimes;
+ double[] mMemStateWeights;
+ double mMemCachedWeight;
+ double mMemFreeWeight;
+ double mMemZRamWeight;
+ double mMemKernelWeight;
+ double mMemNativeWeight;
+ double mMemTotalWeight;
+ boolean mUseUss;
+ long mTotalTime;
+
+ private View mRootView;
+ private ViewGroup mMemStateParent;
+ private ViewGroup mMemUseParent;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ final Bundle args = getArguments();
+ mMemTimes = args.getLongArray(EXTRA_MEM_TIMES);
+ mMemStateWeights = args.getDoubleArray(EXTRA_MEM_STATE_WEIGHTS);
+ mMemCachedWeight = args.getDouble(EXTRA_MEM_CACHED_WEIGHT);
+ mMemFreeWeight = args.getDouble(EXTRA_MEM_FREE_WEIGHT);
+ mMemZRamWeight = args.getDouble(EXTRA_MEM_ZRAM_WEIGHT);
+ mMemKernelWeight = args.getDouble(EXTRA_MEM_KERNEL_WEIGHT);
+ mMemNativeWeight = args.getDouble(EXTRA_MEM_NATIVE_WEIGHT);
+ mMemTotalWeight = args.getDouble(EXTRA_MEM_TOTAL_WEIGHT);
+ mUseUss = args.getBoolean(EXTRA_USE_USS);
+ mTotalTime = args.getLong(EXTRA_TOTAL_TIME);
+ }
+
+ @Override
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ final View view = inflater.inflate(R.layout.process_stats_mem_details, container, false);
+ prepareCustomPreferencesList(container, view, view, false);
+
+ mRootView = view;
+ createDetails();
+ return view;
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ }
+
+ private void createDetails() {
+ mMemStateParent = (ViewGroup)mRootView.findViewById(R.id.mem_state);
+ mMemUseParent = (ViewGroup)mRootView.findViewById(R.id.mem_use);
+
+ fillMemStateSection();
+ fillMemUseSection();
+ }
+
+ private void addDetailsItem(ViewGroup parent, CharSequence title,
+ float level, CharSequence value) {
+ LayoutInflater inflater = getActivity().getLayoutInflater();
+ ViewGroup item = (ViewGroup) inflater.inflate(R.layout.app_percentage_item,
+ null);
+ parent.addView(item);
+ item.findViewById(android.R.id.icon).setVisibility(View.GONE);
+ TextView titleView = (TextView) item.findViewById(android.R.id.title);
+ TextView valueView = (TextView) item.findViewById(android.R.id.text1);
+ titleView.setText(title);
+ valueView.setText(value);
+ ProgressBar progress = (ProgressBar) item.findViewById(android.R.id.progress);
+ progress.setProgress(Math.round(level*100));
+ }
+
+ private void fillMemStateSection() {
+ CharSequence[] labels = getResources().getTextArray(R.array.proc_stats_memory_states);
+ for (int i=0; i<ProcessStats.ADJ_MEM_FACTOR_COUNT; i++) {
+ if (mMemTimes[i] > 0) {
+ float level = ((float)mMemTimes[i])/mTotalTime;
+ addDetailsItem(mMemStateParent, labels[i], level,
+ Formatter.formatShortElapsedTime(getActivity(), mMemTimes[i]));
+ }
+ }
+ }
+
+ private void addMemUseDetailsItem(ViewGroup parent, CharSequence title, double weight) {
+ if (weight > 0) {
+ float level = (float)(weight/mMemTotalWeight);
+ String value = Formatter.formatShortFileSize(getActivity(),
+ (long)((weight * 1024) / mTotalTime));
+ addDetailsItem(parent, title, level, value);
+ }
+ }
+
+ private void fillMemUseSection() {
+ CharSequence[] labels = getResources().getTextArray(R.array.proc_stats_process_states);
+ addMemUseDetailsItem(mMemUseParent,
+ getResources().getText(R.string.mem_use_kernel_type), mMemKernelWeight);
+ addMemUseDetailsItem(mMemUseParent,
+ getResources().getText(R.string.mem_use_zram_type), mMemZRamWeight);
+ addMemUseDetailsItem(mMemUseParent,
+ getResources().getText(R.string.mem_use_native_type), mMemNativeWeight);
+ for (int i=0; i<ProcessStats.STATE_COUNT; i++) {
+ addMemUseDetailsItem(mMemUseParent, labels[i], mMemStateWeights[i]);
+ }
+ addMemUseDetailsItem(mMemUseParent,
+ getResources().getText(R.string.mem_use_kernel_cache_type), mMemCachedWeight);
+ addMemUseDetailsItem(mMemUseParent,
+ getResources().getText(R.string.mem_use_free_type), mMemFreeWeight);
+ addMemUseDetailsItem(mMemUseParent,
+ getResources().getText(R.string.mem_use_total), mMemTotalWeight);
+ }
+}
diff --git a/src/com/android/settings/applications/ProcessStatsPreference.java b/src/com/android/settings/applications/ProcessStatsPreference.java
index bf2676d..adf80e5 100644
--- a/src/com/android/settings/applications/ProcessStatsPreference.java
+++ b/src/com/android/settings/applications/ProcessStatsPreference.java
@@ -21,20 +21,38 @@ import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.preference.Preference;
import android.text.format.Formatter;
+import android.util.AttributeSet;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.android.settings.R;
+import com.android.settings.Utils;
public class ProcessStatsPreference extends Preference {
- private final ProcStatsEntry mEntry;
+ private ProcStatsEntry mEntry;
private int mProgress;
private CharSequence mProgressText;
- public ProcessStatsPreference(Context context, Drawable icon, ProcStatsEntry entry) {
- super(context);
+ public ProcessStatsPreference(Context context) {
+ this(context, null);
+ }
+
+ public ProcessStatsPreference(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ProcessStatsPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public ProcessStatsPreference(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ setLayoutResource(R.layout.preference_app_percentage);
+ }
+
+ public void init(Drawable icon, ProcStatsEntry entry) {
mEntry = entry;
- setLayoutResource(R.layout.app_percentage_item);
setIcon(icon != null ? icon : new ColorDrawable(0));
}
@@ -44,8 +62,7 @@ public class ProcessStatsPreference extends Preference {
public void setPercent(double percentOfWeight, double percentOfTime) {
mProgress = (int) Math.ceil(percentOfWeight);
- mProgressText = getContext().getResources().getString(
- R.string.percentage, (int) Math.round(percentOfTime));
+ mProgressText = Utils.formatPercentage((int) percentOfTime);
notifyChanged();
}
diff --git a/src/com/android/settings/applications/ProcessStatsUi.java b/src/com/android/settings/applications/ProcessStatsUi.java
index 8322ea3..30a8817 100644
--- a/src/com/android/settings/applications/ProcessStatsUi.java
+++ b/src/com/android/settings/applications/ProcessStatsUi.java
@@ -16,6 +16,7 @@
package com.android.settings.applications;
+import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Bundle;
@@ -25,10 +26,10 @@ import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserManager;
import android.preference.Preference;
-import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;
import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
+import android.text.format.Formatter;
import android.util.Log;
import android.util.SparseArray;
import android.util.TimeUtils;
@@ -39,8 +40,10 @@ import android.view.SubMenu;
import com.android.internal.app.IProcessStats;
import com.android.internal.app.ProcessMap;
import com.android.internal.app.ProcessStats;
+import com.android.internal.util.MemInfoReader;
import com.android.settings.R;
-import com.android.settings.fuelgauge.Utils;
+import com.android.settings.SettingsActivity;
+import com.android.settings.Utils;
import java.io.IOException;
import java.io.InputStream;
@@ -112,6 +115,15 @@ public class ProcessStatsUi extends PreferenceFragment
long mMaxWeight;
long mTotalTime;
+ long[] mMemTimes = new long[ProcessStats.ADJ_MEM_FACTOR_COUNT];
+ double[] mMemStateWeights = new double[ProcessStats.STATE_COUNT];
+ double mMemCachedWeight;
+ double mMemFreeWeight;
+ double mMemZRamWeight;
+ double mMemKernelWeight;
+ double mMemNativeWeight;
+ double mMemTotalWeight;
+
// The actual duration value to use for each duration option. Note these
// are lower than the actual duration, since our durations are computed in
// batches of 3 hours so we want to allow the time we use to be slightly
@@ -182,6 +194,24 @@ public class ProcessStatsUi extends PreferenceFragment
@Override
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
+ if (preference instanceof LinearColorPreference) {
+ Bundle args = new Bundle();
+ args.putLongArray(ProcessStatsMemDetail.EXTRA_MEM_TIMES, mMemTimes);
+ args.putDoubleArray(ProcessStatsMemDetail.EXTRA_MEM_STATE_WEIGHTS, mMemStateWeights);
+ args.putDouble(ProcessStatsMemDetail.EXTRA_MEM_CACHED_WEIGHT, mMemCachedWeight);
+ args.putDouble(ProcessStatsMemDetail.EXTRA_MEM_FREE_WEIGHT, mMemFreeWeight);
+ args.putDouble(ProcessStatsMemDetail.EXTRA_MEM_ZRAM_WEIGHT, mMemZRamWeight);
+ args.putDouble(ProcessStatsMemDetail.EXTRA_MEM_KERNEL_WEIGHT, mMemKernelWeight);
+ args.putDouble(ProcessStatsMemDetail.EXTRA_MEM_NATIVE_WEIGHT, mMemNativeWeight);
+ args.putDouble(ProcessStatsMemDetail.EXTRA_MEM_TOTAL_WEIGHT, mMemTotalWeight);
+ args.putBoolean(ProcessStatsMemDetail.EXTRA_USE_USS, mUseUss);
+ args.putLong(ProcessStatsMemDetail.EXTRA_TOTAL_TIME, mTotalTime);
+ ((SettingsActivity) getActivity()).startPreferencePanel(
+ ProcessStatsMemDetail.class.getName(), args, R.string.mem_details_title,
+ null, null, 0);
+ return true;
+ }
+
if (!(preference instanceof ProcessStatsPreference)) {
return false;
}
@@ -192,7 +222,7 @@ public class ProcessStatsUi extends PreferenceFragment
args.putBoolean(ProcessStatsDetail.EXTRA_USE_USS, mUseUss);
args.putLong(ProcessStatsDetail.EXTRA_MAX_WEIGHT, mMaxWeight);
args.putLong(ProcessStatsDetail.EXTRA_TOTAL_TIME, mTotalTime);
- ((PreferenceActivity) getActivity()).startPreferencePanel(
+ ((SettingsActivity) getActivity()).startPreferencePanel(
ProcessStatsDetail.class.getName(), args, R.string.details_title, null, null, 0);
return super.onPreferenceTreeClick(preferenceScreen, preference);
@@ -374,10 +404,11 @@ public class ProcessStatsUi extends PreferenceFragment
mAppListGroup.removeAll();
mAppListGroup.setOrderingAsAdded(false);
+ final long elapsedTime = mStats.mTimePeriodEndRealtime-mStats.mTimePeriodStartRealtime;
+
mMemStatusPref.setOrder(-2);
mAppListGroup.addPreference(mMemStatusPref);
- String durationString = Utils.formatElapsedTime(getActivity(),
- mStats.mTimePeriodEndRealtime-mStats.mTimePeriodStartRealtime, false);
+ String durationString = Utils.formatElapsedTime(getActivity(), elapsedTime, false);
CharSequence memString;
CharSequence[] memStatesStr = getResources().getTextArray(R.array.ram_states);
if (mMemState >= 0 && mMemState < memStatesStr.length) {
@@ -408,11 +439,13 @@ public class ProcessStatsUi extends PreferenceFragment
mStats.mMemFactor, mStats.mStartTime, now);
if (DEBUG) Log.d(TAG, "Total time of stats: " + makeDuration(mTotalTime));
- long[] memTimes = new long[ProcessStats.ADJ_MEM_FACTOR_COUNT];
+ for (int i=0; i<mMemTimes.length; i++) {
+ mMemTimes[i] = 0;
+ }
for (int iscreen=0; iscreen<ProcessStats.ADJ_COUNT; iscreen+=ProcessStats.ADJ_SCREEN_MOD) {
for (int imem=0; imem<ProcessStats.ADJ_MEM_FACTOR_COUNT; imem++) {
int state = imem+iscreen;
- memTimes[imem] += mStats.mMemFactorDurations[state];
+ mMemTimes[imem] += mStats.mMemFactorDurations[state];
}
}
@@ -421,31 +454,155 @@ public class ProcessStatsUi extends PreferenceFragment
LinearColorPreference colors = new LinearColorPreference(getActivity());
colors.setOrder(-1);
- colors.setOnRegionTappedListener(this);
switch (mMemRegion) {
case LinearColorBar.REGION_RED:
- colors.setColoredRegions(LinearColorBar.REGION_RED);
- memTotalTime = memTimes[ProcessStats.ADJ_MEM_FACTOR_CRITICAL];
+ memTotalTime = mMemTimes[ProcessStats.ADJ_MEM_FACTOR_CRITICAL];
memStates = RED_MEM_STATES;
break;
case LinearColorBar.REGION_YELLOW:
- colors.setColoredRegions(LinearColorBar.REGION_RED
- | LinearColorBar.REGION_YELLOW);
- memTotalTime = memTimes[ProcessStats.ADJ_MEM_FACTOR_CRITICAL]
- + memTimes[ProcessStats.ADJ_MEM_FACTOR_LOW]
- + memTimes[ProcessStats.ADJ_MEM_FACTOR_MODERATE];
+ memTotalTime = mMemTimes[ProcessStats.ADJ_MEM_FACTOR_CRITICAL]
+ + mMemTimes[ProcessStats.ADJ_MEM_FACTOR_LOW]
+ + mMemTimes[ProcessStats.ADJ_MEM_FACTOR_MODERATE];
memStates = YELLOW_MEM_STATES;
break;
default:
- colors.setColoredRegions(LinearColorBar.REGION_ALL);
memTotalTime = mTotalTime;
memStates = ProcessStats.ALL_MEM_ADJ;
break;
}
- colors.setRatios(memTimes[ProcessStats.ADJ_MEM_FACTOR_CRITICAL] / (float)mTotalTime,
- (memTimes[ProcessStats.ADJ_MEM_FACTOR_LOW]
- + memTimes[ProcessStats.ADJ_MEM_FACTOR_MODERATE]) / (float)mTotalTime,
- memTimes[ProcessStats.ADJ_MEM_FACTOR_NORMAL] / (float)mTotalTime);
+ colors.setColoredRegions(LinearColorBar.REGION_RED);
+
+ // Compute memory badness for chart color.
+ int[] badColors = com.android.settings.Utils.BADNESS_COLORS;
+ long timeGood = mMemTimes[ProcessStats.ADJ_MEM_FACTOR_NORMAL];
+ timeGood += (mMemTimes[ProcessStats.ADJ_MEM_FACTOR_MODERATE]*2)/3;
+ timeGood += mMemTimes[ProcessStats.ADJ_MEM_FACTOR_LOW]/3;
+ float memBadness = ((float)timeGood)/mTotalTime;
+ int badnessColor = badColors[1 + Math.round(memBadness*(badColors.length-2))];
+ colors.setColors(badnessColor, badnessColor, badnessColor);
+
+ // We are now going to scale the mMemTimes to match the total elapsed time.
+ // These are in uptime, so they will often be smaller than the elapsed time,
+ // but if the user taps on the bar we want to show the times to them. It is confusing
+ // to see them be smaller than what we told them the measured duration is, so just
+ // scaling them up with make things look reasonable with them none the wiser.
+ for (int i=0; i<ProcessStats.ADJ_MEM_FACTOR_COUNT; i++) {
+ mMemTimes[i] = (long)((mMemTimes[i]*(double)elapsedTime)/mTotalTime);
+ }
+
+ ProcessStats.TotalMemoryUseCollection totalMem = new ProcessStats.TotalMemoryUseCollection(
+ ProcessStats.ALL_SCREEN_ADJ, memStates);
+ mStats.computeTotalMemoryUse(totalMem, now);
+ double freeWeight = totalMem.sysMemFreeWeight + totalMem.sysMemCachedWeight;
+ double usedWeight = totalMem.sysMemKernelWeight + totalMem.sysMemNativeWeight
+ + totalMem.sysMemZRamWeight;
+ double backgroundWeight = 0, persBackgroundWeight = 0;
+ mMemCachedWeight = totalMem.sysMemCachedWeight;
+ mMemFreeWeight = totalMem.sysMemFreeWeight;
+ mMemZRamWeight = totalMem.sysMemZRamWeight;
+ mMemKernelWeight = totalMem.sysMemKernelWeight;
+ mMemNativeWeight = totalMem.sysMemNativeWeight;
+ for (int i=0; i<ProcessStats.STATE_COUNT; i++) {
+ if (i == ProcessStats.STATE_SERVICE_RESTARTING) {
+ // These don't really run.
+ mMemStateWeights[i] = 0;
+ } else {
+ mMemStateWeights[i] = totalMem.processStateWeight[i];
+ if (i >= ProcessStats.STATE_HOME) {
+ freeWeight += totalMem.processStateWeight[i];
+ } else {
+ usedWeight += totalMem.processStateWeight[i];
+ }
+ if (i >= ProcessStats.STATE_IMPORTANT_FOREGROUND) {
+ backgroundWeight += totalMem.processStateWeight[i];
+ persBackgroundWeight += totalMem.processStateWeight[i];
+ }
+ if (i == ProcessStats.STATE_PERSISTENT) {
+ persBackgroundWeight += totalMem.processStateWeight[i];
+ }
+ }
+ }
+ if (DEBUG) {
+ Log.i(TAG, "Used RAM: " + Formatter.formatShortFileSize(getActivity(),
+ (long)((usedWeight * 1024) / memTotalTime)));
+ Log.i(TAG, "Free RAM: " + Formatter.formatShortFileSize(getActivity(),
+ (long)((freeWeight * 1024) / memTotalTime)));
+ Log.i(TAG, "Total RAM: " + Formatter.formatShortFileSize(getActivity(),
+ (long)(((freeWeight+usedWeight) * 1024) / memTotalTime)));
+ Log.i(TAG, "Background+Cached RAM: " + Formatter.formatShortFileSize(getActivity(),
+ (long)((backgroundWeight * 1024) / memTotalTime)));
+ }
+ mMemTotalWeight = freeWeight + usedWeight;
+
+ // For computing the ratio to show, we want to count the baseline cached RAM we
+ // need (at which point we start killing processes) as used RAM, so that if we
+ // reach the point of thrashing due to no RAM for any background processes we
+ // report that as RAM being full. To do this, we need to first convert the weights
+ // back to actual RAM... and since the RAM values we compute here won't exactly
+ // match the real physical RAM, scale those to the actual physical RAM. No problem!
+ double usedRam = (usedWeight*1024)/memTotalTime;
+ double freeRam = (freeWeight*1024)/memTotalTime;
+ double totalRam = usedRam + freeRam;
+ MemInfoReader memReader = new MemInfoReader();
+ memReader.readMemInfo();
+ double realTotalRam = memReader.getTotalSize();
+ double totalScale = realTotalRam / totalRam;
+ double realUsedRam = usedRam * totalScale;
+ double realFreeRam = freeRam * totalScale;
+ if (DEBUG) {
+ Log.i(TAG, "Scaled Used RAM: " + Formatter.formatShortFileSize(getActivity(),
+ (long)realUsedRam));
+ Log.i(TAG, "Scaled Free RAM: " + Formatter.formatShortFileSize(getActivity(),
+ (long)realFreeRam));
+ }
+ ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
+ ((ActivityManager)getActivity().getSystemService(Context.ACTIVITY_SERVICE)).getMemoryInfo(
+ memInfo);
+ if (memInfo.hiddenAppThreshold >= realFreeRam) {
+ realUsedRam = realFreeRam;
+ realFreeRam = 0;
+ } else {
+ realUsedRam += memInfo.hiddenAppThreshold;
+ realFreeRam -= memInfo.hiddenAppThreshold;
+ }
+ if (DEBUG) {
+ Log.i(TAG, "Adj Scaled Used RAM: " + Formatter.formatShortFileSize(getActivity(),
+ (long)realUsedRam));
+ Log.i(TAG, "Adj Scaled Free RAM: " + Formatter.formatShortFileSize(getActivity(),
+ (long)realFreeRam));
+ }
+
+ float usedRatio = (float)(realUsedRam/(realFreeRam+realUsedRam));
+ colors.setRatios(usedRatio, 0, 1-usedRatio);
+
+ if (false) {
+ colors.setOnRegionTappedListener(this);
+ switch (mMemRegion) {
+ case LinearColorBar.REGION_RED:
+ colors.setColoredRegions(LinearColorBar.REGION_RED);
+ memTotalTime = mMemTimes[ProcessStats.ADJ_MEM_FACTOR_CRITICAL];
+ memStates = RED_MEM_STATES;
+ break;
+ case LinearColorBar.REGION_YELLOW:
+ colors.setColoredRegions(LinearColorBar.REGION_RED
+ | LinearColorBar.REGION_YELLOW);
+ memTotalTime = mMemTimes[ProcessStats.ADJ_MEM_FACTOR_CRITICAL]
+ + mMemTimes[ProcessStats.ADJ_MEM_FACTOR_LOW]
+ + mMemTimes[ProcessStats.ADJ_MEM_FACTOR_MODERATE];
+ memStates = YELLOW_MEM_STATES;
+ break;
+ default:
+ colors.setColoredRegions(LinearColorBar.REGION_ALL);
+ memTotalTime = mTotalTime;
+ memStates = ProcessStats.ALL_MEM_ADJ;
+ break;
+ }
+ colors.setRatios(mMemTimes[ProcessStats.ADJ_MEM_FACTOR_CRITICAL] / (float)mTotalTime,
+ (mMemTimes[ProcessStats.ADJ_MEM_FACTOR_LOW]
+ + mMemTimes[ProcessStats.ADJ_MEM_FACTOR_MODERATE]) / (float)mTotalTime,
+ mMemTimes[ProcessStats.ADJ_MEM_FACTOR_NORMAL] / (float)mTotalTime);
+ }
+
mAppListGroup.addPreference(colors);
ProcessStats.ProcessDataCollection totals = new ProcessStats.ProcessDataCollection(
@@ -466,33 +623,36 @@ public class ProcessStatsUi extends PreferenceFragment
final ProcessMap<ProcStatsEntry> entriesMap = new ProcessMap<ProcStatsEntry>();
for (int ipkg=0, N=mStats.mPackages.getMap().size(); ipkg<N; ipkg++) {
- final SparseArray<ProcessStats.PackageState> pkgUids
+ final SparseArray<SparseArray<ProcessStats.PackageState>> pkgUids
= mStats.mPackages.getMap().valueAt(ipkg);
for (int iu=0; iu<pkgUids.size(); iu++) {
- final ProcessStats.PackageState st = pkgUids.valueAt(iu);
- for (int iproc=0; iproc<st.mProcesses.size(); iproc++) {
- final ProcessStats.ProcessState pkgProc = st.mProcesses.valueAt(iproc);
- final ProcessStats.ProcessState proc = mStats.mProcesses.get(pkgProc.mName,
- pkgProc.mUid);
- if (proc == null) {
- Log.w(TAG, "No process found for pkg " + st.mPackageName
- + "/" + st.mUid + " proc name " + pkgProc.mName);
- continue;
- }
- ProcStatsEntry ent = entriesMap.get(proc.mName, proc.mUid);
- if (ent == null) {
- ent = new ProcStatsEntry(proc, st.mPackageName, totals, mUseUss,
- mStatsType == MENU_TYPE_BACKGROUND);
- if (ent.mDuration > 0) {
- if (DEBUG) Log.d(TAG, "Adding proc " + proc.mName + "/"
- + proc.mUid + ": time=" + makeDuration(ent.mDuration) + " ("
- + ((((double)ent.mDuration) / memTotalTime) * 100) + "%)"
- + " pss=" + ent.mAvgPss);
- entriesMap.put(proc.mName, proc.mUid, ent);
- entries.add(ent);
+ final SparseArray<ProcessStats.PackageState> vpkgs = pkgUids.valueAt(iu);
+ for (int iv=0; iv<vpkgs.size(); iv++) {
+ final ProcessStats.PackageState st = vpkgs.valueAt(iv);
+ for (int iproc=0; iproc<st.mProcesses.size(); iproc++) {
+ final ProcessStats.ProcessState pkgProc = st.mProcesses.valueAt(iproc);
+ final ProcessStats.ProcessState proc = mStats.mProcesses.get(pkgProc.mName,
+ pkgProc.mUid);
+ if (proc == null) {
+ Log.w(TAG, "No process found for pkg " + st.mPackageName
+ + "/" + st.mUid + " proc name " + pkgProc.mName);
+ continue;
+ }
+ ProcStatsEntry ent = entriesMap.get(proc.mName, proc.mUid);
+ if (ent == null) {
+ ent = new ProcStatsEntry(proc, st.mPackageName, totals, mUseUss,
+ mStatsType == MENU_TYPE_BACKGROUND);
+ if (ent.mDuration > 0) {
+ if (DEBUG) Log.d(TAG, "Adding proc " + proc.mName + "/"
+ + proc.mUid + ": time=" + makeDuration(ent.mDuration) + " ("
+ + ((((double)ent.mDuration) / memTotalTime) * 100) + "%)"
+ + " pss=" + ent.mAvgPss);
+ entriesMap.put(proc.mName, proc.mUid, ent);
+ entries.add(ent);
+ }
+ } else {
+ ent.addPackage(st.mPackageName);
}
- } else {
- ent.addPackage(st.mPackageName);
}
}
}
@@ -503,21 +663,25 @@ public class ProcessStatsUi extends PreferenceFragment
// Add in service info.
if (mStatsType == MENU_TYPE_BACKGROUND) {
for (int ip=0, N=mStats.mPackages.getMap().size(); ip<N; ip++) {
- SparseArray<ProcessStats.PackageState> uids = mStats.mPackages.getMap().valueAt(ip);
+ SparseArray<SparseArray<ProcessStats.PackageState>> uids
+ = mStats.mPackages.getMap().valueAt(ip);
for (int iu=0; iu<uids.size(); iu++) {
- ProcessStats.PackageState ps = uids.valueAt(iu);
- for (int is=0, NS=ps.mServices.size(); is<NS; is++) {
- ProcessStats.ServiceState ss = ps.mServices.valueAt(is);
- if (ss.mProcessName != null) {
- ProcStatsEntry ent = entriesMap.get(ss.mProcessName, uids.keyAt(iu));
- if (ent != null) {
- if (DEBUG) Log.d(TAG, "Adding service " + ps.mPackageName
- + "/" + ss.mName + "/" + uids.keyAt(iu) + " to proc "
- + ss.mProcessName);
- ent.addService(ss);
- } else {
- Log.w(TAG, "No process " + ss.mProcessName + "/" + uids.keyAt(iu)
- + " for service " + ss.mName);
+ SparseArray<ProcessStats.PackageState> vpkgs = uids.valueAt(iu);
+ for (int iv=0; iv<vpkgs.size(); iv++) {
+ ProcessStats.PackageState ps = vpkgs.valueAt(iv);
+ for (int is=0, NS=ps.mServices.size(); is<NS; is++) {
+ ProcessStats.ServiceState ss = ps.mServices.valueAt(is);
+ if (ss.mProcessName != null) {
+ ProcStatsEntry ent = entriesMap.get(ss.mProcessName, uids.keyAt(iu));
+ if (ent != null) {
+ if (DEBUG) Log.d(TAG, "Adding service " + ps.mPackageName
+ + "/" + ss.mName + "/" + uids.keyAt(iu) + " to proc "
+ + ss.mProcessName);
+ ent.addService(ss);
+ } else {
+ Log.w(TAG, "No process " + ss.mProcessName + "/" + uids.keyAt(iu)
+ + " for service " + ss.mName);
+ }
}
}
}
@@ -559,20 +723,39 @@ public class ProcessStatsUi extends PreferenceFragment
maxWeight = proc.mWeight;
}
}
- mMaxWeight = maxWeight;
+ if (mStatsType == MENU_TYPE_BACKGROUND) {
+ mMaxWeight = (long)(mShowSystem ? persBackgroundWeight : backgroundWeight);
+ if (mMaxWeight < maxWeight) {
+ mMaxWeight = maxWeight;
+ }
+ if (DEBUG) {
+ Log.i(TAG, "Bar max RAM: " + Formatter.formatShortFileSize(getActivity(),
+ (mMaxWeight * 1024) / memTotalTime));
+ }
+ } else {
+ mMaxWeight = maxWeight;
+ }
if (DEBUG) Log.d(TAG, "-------------------- BUILDING UI");
- for (int i=0, N=(entries != null ? entries.size() : 0); i<N; i++) {
- ProcStatsEntry proc = entries.get(i);
- final double percentOfWeight = (((double)proc.mWeight) / maxWeight) * 100;
+ // Find where we should stop. Because we have two properties we are looking at,
+ // we need to go from the back looking for the first place either holds.
+ int end = entries != null ? entries.size()-1 : -1;
+ while (end >= 0) {
+ ProcStatsEntry proc = entries.get(end);
+ final double percentOfWeight = (((double)proc.mWeight) / mMaxWeight) * 100;
final double percentOfTime = (((double)proc.mDuration) / memTotalTime) * 100;
- if (percentOfWeight < 1 && percentOfTime < 33) {
- if (DEBUG) Log.d(TAG, "Skipping " + proc.mName + " weight=" + percentOfWeight
- + " time=" + percentOfTime);
- continue;
+ if (percentOfWeight >= 1 || percentOfTime >= 25) {
+ break;
}
- ProcessStatsPreference pref = new ProcessStatsPreference(getActivity(), null, proc);
+ end--;
+ }
+ for (int i=0; i<=end; i++) {
+ ProcStatsEntry proc = entries.get(i);
+ final double percentOfWeight = (((double)proc.mWeight) / mMaxWeight) * 100;
+ final double percentOfTime = (((double)proc.mDuration) / memTotalTime) * 100;
+ ProcessStatsPreference pref = new ProcessStatsPreference(getActivity());
+ pref.init(null, proc);
proc.evaluateTargetPackage(pm, mStats, totals, sEntryCompare, mUseUss,
mStatsType == MENU_TYPE_BACKGROUND);
proc.retrieveUiData(pm);
@@ -583,6 +766,16 @@ public class ProcessStatsUi extends PreferenceFragment
pref.setOrder(i);
pref.setPercent(percentOfWeight, percentOfTime);
mAppListGroup.addPreference(pref);
+ if (mStatsType == MENU_TYPE_BACKGROUND) {
+ if (DEBUG) {
+ Log.i(TAG, "App " + proc.mUiLabel + ": weightedRam="
+ + Formatter.formatShortFileSize(getActivity(),
+ (proc.mWeight * 1024) / memTotalTime)
+ + ", avgRam=" + Formatter.formatShortFileSize(getActivity(),
+ (proc.mAvgPss*1024)));
+ }
+
+ }
if (mAppListGroup.getPreferenceCount() > (MAX_ITEMS_TO_LIST+1)) {
if (DEBUG) Log.d(TAG, "Done with UI, hit item limit!");
break;
diff --git a/src/com/android/settings/applications/RunningProcessesView.java b/src/com/android/settings/applications/RunningProcessesView.java
index 8eb0496..13d9655 100644
--- a/src/com/android/settings/applications/RunningProcessesView.java
+++ b/src/com/android/settings/applications/RunningProcessesView.java
@@ -16,6 +16,7 @@
package com.android.settings.applications;
+import android.content.res.Resources;
import android.text.BidiFormatter;
import com.android.internal.util.MemInfoReader;
import com.android.settings.R;
@@ -28,7 +29,6 @@ import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.SystemClock;
import android.os.UserHandle;
-import android.preference.PreferenceActivity;
import android.text.format.DateUtils;
import android.text.format.Formatter;
import android.util.AttributeSet;
@@ -42,6 +42,7 @@ import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.AbsListView.RecyclerListener;
+import com.android.settings.SettingsActivity;
import java.util.ArrayList;
import java.util.Collections;
@@ -71,19 +72,22 @@ public class RunningProcessesView extends FrameLayout
RunningState.BaseItem mCurSelected;
ListView mListView;
+ View mHeader;
ServiceListAdapter mAdapter;
LinearColorBar mColorBar;
+ TextView mBackgroundProcessPrefix;
+ TextView mAppsProcessPrefix;
+ TextView mForegroundProcessPrefix;
TextView mBackgroundProcessText;
+ TextView mAppsProcessText;
TextView mForegroundProcessText;
-
- int mLastNumBackgroundProcesses = -1;
- int mLastNumForegroundProcesses = -1;
- int mLastNumServiceProcesses = -1;
- long mLastBackgroundProcessMemory = -1;
- long mLastForegroundProcessMemory = -1;
- long mLastServiceProcessMemory = -1;
- long mLastAvailMemory = -1;
-
+
+ long mCurTotalRam = -1;
+ long mCurHighRam = -1; // "System" or "Used"
+ long mCurMedRam = -1; // "Apps" or "Cached"
+ long mCurLowRam = -1; // "Free"
+ boolean mCurShowCached = false;
+
Dialog mCurDialog;
MemInfoReader mMemInfoReader = new MemInfoReader();
@@ -95,7 +99,7 @@ public class RunningProcessesView extends FrameLayout
ViewHolder mHolder;
long mFirstRunTime;
boolean mSetBackground;
-
+
void updateTime(Context context, StringBuilder builder) {
TextView uptimeView = null;
@@ -123,7 +127,7 @@ public class RunningProcessesView extends FrameLayout
uptimeView = mHolder.uptime;
}
}
-
+
if (uptimeView != null) {
mSetBackground = false;
if (mFirstRunTime >= 0) {
@@ -225,8 +229,7 @@ public class RunningProcessesView extends FrameLayout
mShowBackground = showBackground;
mState.setWatchingBackgroundItems(showBackground);
refreshItems();
- notifyDataSetChanged();
- mColorBar.setShowingGreen(mShowBackground);
+ refreshUi(true);
}
}
@@ -316,7 +319,7 @@ public class RunningProcessesView extends FrameLayout
void refreshUi(boolean dataChanged) {
if (dataChanged) {
- ServiceListAdapter adapter = (ServiceListAdapter)(mListView.getAdapter());
+ ServiceListAdapter adapter = mAdapter;
adapter.refreshItems();
adapter.notifyDataSetChanged();
}
@@ -326,56 +329,71 @@ public class RunningProcessesView extends FrameLayout
mDataAvail = null;
}
+ mMemInfoReader.readMemInfo();
+
+ /*
// This is the amount of available memory until we start killing
// background services.
- mMemInfoReader.readMemInfo();
long availMem = mMemInfoReader.getFreeSize() + mMemInfoReader.getCachedSize()
- SECONDARY_SERVER_MEM;
if (availMem < 0) {
availMem = 0;
}
+ */
synchronized (mState.mLock) {
- if (mLastNumBackgroundProcesses != mState.mNumBackgroundProcesses
- || mLastBackgroundProcessMemory != mState.mBackgroundProcessMemory
- || mLastAvailMemory != availMem) {
- mLastNumBackgroundProcesses = mState.mNumBackgroundProcesses;
- mLastBackgroundProcessMemory = mState.mBackgroundProcessMemory;
- mLastAvailMemory = availMem;
- long freeMem = mLastAvailMemory + mLastBackgroundProcessMemory;
+ if (mCurShowCached != mAdapter.mShowBackground) {
+ mCurShowCached = mAdapter.mShowBackground;
+ if (mCurShowCached) {
+ mForegroundProcessPrefix.setText(getResources().getText(
+ R.string.running_processes_header_used_prefix));
+ mAppsProcessPrefix.setText(getResources().getText(
+ R.string.running_processes_header_cached_prefix));
+ } else {
+ mForegroundProcessPrefix.setText(getResources().getText(
+ R.string.running_processes_header_system_prefix));
+ mAppsProcessPrefix.setText(getResources().getText(
+ R.string.running_processes_header_apps_prefix));
+ }
+ }
+
+ final long totalRam = mMemInfoReader.getTotalSize();
+ final long medRam;
+ final long lowRam;
+ if (mCurShowCached) {
+ lowRam = mMemInfoReader.getFreeSize() + mMemInfoReader.getCachedSize();
+ medRam = mState.mBackgroundProcessMemory;
+ } else {
+ lowRam = mMemInfoReader.getFreeSize() + mMemInfoReader.getCachedSize()
+ + mState.mBackgroundProcessMemory;
+ medRam = mState.mServiceProcessMemory;
+
+ }
+ final long highRam = totalRam - medRam - lowRam;
+
+ if (mCurTotalRam != totalRam || mCurHighRam != highRam || mCurMedRam != medRam
+ || mCurLowRam != lowRam) {
+ mCurTotalRam = totalRam;
+ mCurHighRam = highRam;
+ mCurMedRam = medRam;
+ mCurLowRam = lowRam;
BidiFormatter bidiFormatter = BidiFormatter.getInstance();
String sizeStr = bidiFormatter.unicodeWrap(
- Formatter.formatShortFileSize(getContext(), freeMem));
+ Formatter.formatShortFileSize(getContext(), lowRam));
mBackgroundProcessText.setText(getResources().getString(
- R.string.service_background_processes, sizeStr));
+ R.string.running_processes_header_ram, sizeStr));
sizeStr = bidiFormatter.unicodeWrap(
- Formatter.formatShortFileSize(getContext(),
- mMemInfoReader.getTotalSize() - freeMem));
- mForegroundProcessText.setText(getResources().getString(
- R.string.service_foreground_processes, sizeStr));
- }
- if (mLastNumForegroundProcesses != mState.mNumForegroundProcesses
- || mLastForegroundProcessMemory != mState.mForegroundProcessMemory
- || mLastNumServiceProcesses != mState.mNumServiceProcesses
- || mLastServiceProcessMemory != mState.mServiceProcessMemory) {
- mLastNumForegroundProcesses = mState.mNumForegroundProcesses;
- mLastForegroundProcessMemory = mState.mForegroundProcessMemory;
- mLastNumServiceProcesses = mState.mNumServiceProcesses;
- mLastServiceProcessMemory = mState.mServiceProcessMemory;
- /*
- String sizeStr = Formatter.formatShortFileSize(getContext(),
- mLastForegroundProcessMemory + mLastServiceProcessMemory);
+ Formatter.formatShortFileSize(getContext(), medRam));
+ mAppsProcessText.setText(getResources().getString(
+ R.string.running_processes_header_ram, sizeStr));
+ sizeStr = bidiFormatter.unicodeWrap(
+ Formatter.formatShortFileSize(getContext(), highRam));
mForegroundProcessText.setText(getResources().getString(
- R.string.service_foreground_processes, sizeStr));
- */
+ R.string.running_processes_header_ram, sizeStr));
+ mColorBar.setRatios(highRam/(float)totalRam,
+ medRam/(float)totalRam,
+ lowRam/(float)totalRam);
}
-
- float totalMem = mMemInfoReader.getTotalSize();
- float totalShownMem = availMem + mLastBackgroundProcessMemory
- + mLastServiceProcessMemory;
- mColorBar.setRatios((totalMem-totalShownMem)/totalMem,
- mLastServiceProcessMemory/totalMem,
- mLastBackgroundProcessMemory/totalMem);
}
}
@@ -397,9 +415,9 @@ public class RunningProcessesView extends FrameLayout
}
args.putInt(RunningServiceDetails.KEY_USER_ID, mi.mUserId);
args.putBoolean(RunningServiceDetails.KEY_BACKGROUND, mAdapter.mShowBackground);
-
- PreferenceActivity pa = (PreferenceActivity)mOwner.getActivity();
- pa.startPreferencePanel(RunningServiceDetails.class.getName(), args,
+
+ SettingsActivity sa = (SettingsActivity) mOwner.getActivity();
+ sa.startPreferencePanel(RunningServiceDetails.class.getName(), args,
R.string.runningservicedetails_settings_title, null, null, 0);
}
}
@@ -428,27 +446,19 @@ public class RunningProcessesView extends FrameLayout
mListView.setRecyclerListener(this);
mAdapter = new ServiceListAdapter(mState);
mListView.setAdapter(mAdapter);
- mColorBar = (LinearColorBar)findViewById(R.id.color_bar);
- mBackgroundProcessText = (TextView)findViewById(R.id.backgroundText);
- mBackgroundProcessText.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- mAdapter.setShowBackground(true);
- if (mOwner != null) {
- mOwner.getActivity().invalidateOptionsMenu();
- }
- }
- });
- mForegroundProcessText = (TextView)findViewById(R.id.foregroundText);
- mForegroundProcessText.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- mAdapter.setShowBackground(false);
- if (mOwner != null) {
- mOwner.getActivity().invalidateOptionsMenu();
- }
- }
- });
+ mHeader = inflater.inflate(R.layout.running_processes_header, null);
+ mListView.addHeaderView(mHeader, null, false /* set as not selectable */);
+ mColorBar = (LinearColorBar)mHeader.findViewById(R.id.color_bar);
+ Resources res = getResources();
+ mColorBar.setColors(res.getColor(R.color.running_processes_system_ram),
+ res.getColor(R.color.running_processes_apps_ram),
+ res.getColor(R.color.running_processes_free_ram));
+ mBackgroundProcessPrefix = (TextView)mHeader.findViewById(R.id.freeSizePrefix);
+ mAppsProcessPrefix = (TextView)mHeader.findViewById(R.id.appsSizePrefix);
+ mForegroundProcessPrefix = (TextView)mHeader.findViewById(R.id.systemSizePrefix);
+ mBackgroundProcessText = (TextView)mHeader.findViewById(R.id.freeSize);
+ mAppsProcessText = (TextView)mHeader.findViewById(R.id.appsSize);
+ mForegroundProcessText = (TextView)mHeader.findViewById(R.id.systemSize);
ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
mAm.getMemoryInfo(memInfo);
diff --git a/src/com/android/settings/applications/RunningServiceDetails.java b/src/com/android/settings/applications/RunningServiceDetails.java
index 73547f1..45cad3d 100644
--- a/src/com/android/settings/applications/RunningServiceDetails.java
+++ b/src/com/android/settings/applications/RunningServiceDetails.java
@@ -583,7 +583,6 @@ public class RunningServiceDetails extends Fragment
return new AlertDialog.Builder(getActivity())
.setTitle(getActivity().getString(R.string.runningservicedetails_stop_dlg_title))
- .setIconAttribute(android.R.attr.alertDialogIcon)
.setMessage(getActivity().getString(R.string.runningservicedetails_stop_dlg_text))
.setPositiveButton(R.string.dlg_ok,
new DialogInterface.OnClickListener() {
diff --git a/src/com/android/settings/applications/RunningState.java b/src/com/android/settings/applications/RunningState.java
index 94ab11d..1019abc 100644
--- a/src/com/android/settings/applications/RunningState.java
+++ b/src/com/android/settings/applications/RunningState.java
@@ -17,7 +17,7 @@
package com.android.settings.applications;
import com.android.settings.R;
-import com.android.settings.users.UserUtils;
+import com.android.settings.Utils;
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
@@ -837,12 +837,13 @@ public class RunningState {
UserInfo info = mUm.getUserInfo(newItem.mUserId);
userItem.mUser.mInfo = info;
if (info != null) {
- userItem.mUser.mIcon = UserUtils.getUserIcon(context, mUm,
- info, context.getResources());
+ userItem.mUser.mIcon = Utils.getUserIcon(context, mUm, info);
}
String name = info != null ? info.name : null;
- if (name == null) {
+ if (name == null && info != null) {
name = Integer.toString(info.id);
+ } else if (info == null) {
+ name = context.getString(R.string.unknown);
}
userItem.mUser.mLabel = context.getResources().getString(
R.string.running_process_item_user_label, name);
diff --git a/src/com/android/settings/bluetooth/BluetoothDevicePreference.java b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
index 80a3d1f..5d6b17c 100644
--- a/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
+++ b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
@@ -35,6 +35,8 @@ import android.view.View.OnClickListener;
import android.widget.ImageView;
import com.android.settings.R;
+import com.android.settings.search.Index;
+import com.android.settings.search.SearchIndexableRaw;
import java.util.List;
@@ -65,6 +67,8 @@ public final class BluetoothDevicePreference extends Preference implements
mCachedDevice = cachedDevice;
+ setLayoutResource(R.layout.preference_bt_icon);
+
if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED) {
UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
if (! um.hasUserRestriction(DISALLOW_CONFIG_BLUETOOTH)) {
@@ -131,10 +135,10 @@ public final class BluetoothDevicePreference extends Preference implements
if (mCachedDevice.getBondState() == BluetoothDevice.BOND_BONDED) {
ImageView deviceDetails = (ImageView) view.findViewById(R.id.deviceDetails);
+
if (deviceDetails != null) {
deviceDetails.setOnClickListener(this);
deviceDetails.setTag(mCachedDevice);
- deviceDetails.setAlpha(isEnabled() ? 255 : sDimAlpha);
}
}
@@ -209,6 +213,17 @@ public final class BluetoothDevicePreference extends Preference implements
if (!mCachedDevice.startPairing()) {
Utils.showError(getContext(), mCachedDevice.getName(),
R.string.bluetooth_pairing_error_message);
+ } else {
+ final Context context = getContext();
+
+ SearchIndexableRaw data = new SearchIndexableRaw(context);
+ data.className = BluetoothSettings.class.getName();
+ data.title = mCachedDevice.getName();
+ data.screenTitle = context.getResources().getString(R.string.bluetooth_settings);
+ data.iconResId = R.drawable.ic_settings_bluetooth2;
+ data.enabled = true;
+
+ Index.getInstance(context).updateFromSearchIndexableData(data);
}
}
@@ -232,7 +247,7 @@ public final class BluetoothDevicePreference extends Preference implements
break;
case BluetoothProfile.STATE_DISCONNECTED:
- if (profile.isProfileReady() && profile.isPreferred(cachedDevice.getDevice())) {
+ if (profile.isProfileReady()) {
if (profile instanceof A2dpProfile) {
a2dpNotConnected = true;
} else if (profile instanceof HeadsetProfile) {
@@ -305,6 +320,6 @@ public final class BluetoothDevicePreference extends Preference implements
return R.drawable.ic_bt_headset_hfp;
}
}
- return 0;
+ return R.drawable.ic_settings_bluetooth2;
}
}
diff --git a/src/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java b/src/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java
index d687136..17da0a7 100755
--- a/src/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java
+++ b/src/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java
@@ -60,7 +60,7 @@ final class BluetoothDiscoverableEnabler implements Preference.OnPreferenceClick
static final int DEFAULT_DISCOVERABLE_TIMEOUT = DISCOVERABLE_TIMEOUT_TWO_MINUTES;
- private final Context mContext;
+ private Context mContext;
private final Handler mUiHandler;
private final Preference mDiscoveryPreference;
@@ -92,9 +92,8 @@ final class BluetoothDiscoverableEnabler implements Preference.OnPreferenceClick
}
};
- BluetoothDiscoverableEnabler(Context context, LocalBluetoothAdapter adapter,
+ BluetoothDiscoverableEnabler(LocalBluetoothAdapter adapter,
Preference discoveryPreference) {
- mContext = context;
mUiHandler = new Handler();
mLocalAdapter = adapter;
mDiscoveryPreference = discoveryPreference;
@@ -102,11 +101,15 @@ final class BluetoothDiscoverableEnabler implements Preference.OnPreferenceClick
discoveryPreference.setPersistent(false);
}
- public void resume() {
+ public void resume(Context context) {
if (mLocalAdapter == null) {
return;
}
+ if (mContext != context) {
+ mContext = context;
+ }
+
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
mContext.registerReceiver(mReceiver, filter);
mDiscoveryPreference.setOnPreferenceClickListener(this);
@@ -143,7 +146,10 @@ final class BluetoothDiscoverableEnabler implements Preference.OnPreferenceClick
if (timeout > 0) {
BluetoothDiscoverableTimeoutReceiver.setDiscoverableAlarm(mContext, endTimestamp);
+ } else {
+ BluetoothDiscoverableTimeoutReceiver.cancelDiscoverableAlarm(mContext);
}
+
} else {
mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
BluetoothDiscoverableTimeoutReceiver.cancelDiscoverableAlarm(mContext);
diff --git a/src/com/android/settings/bluetooth/BluetoothEnabler.java b/src/com/android/settings/bluetooth/BluetoothEnabler.java
index df13eef..b006c65 100644
--- a/src/com/android/settings/bluetooth/BluetoothEnabler.java
+++ b/src/com/android/settings/bluetooth/BluetoothEnabler.java
@@ -21,6 +21,8 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Message;
import android.provider.Settings;
import android.widget.CompoundButton;
import android.widget.Switch;
@@ -28,19 +30,38 @@ import android.widget.Toast;
import com.android.settings.R;
import com.android.settings.WirelessSettings;
+import com.android.settings.search.Index;
+import com.android.settings.widget.SwitchBar;
/**
* BluetoothEnabler is a helper to manage the Bluetooth on/off checkbox
* preference. It turns on/off Bluetooth and ensures the summary of the
* preference reflects the current state.
*/
-public final class BluetoothEnabler implements CompoundButton.OnCheckedChangeListener {
- private final Context mContext;
+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;
+ private static final String EVENT_DATA_IS_BT_ON = "is_bluetooth_on";
+ private static final int EVENT_UPDATE_INDEX = 0;
+
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_UPDATE_INDEX:
+ final boolean isBluetoothOn = msg.getData().getBoolean(EVENT_DATA_IS_BT_ON);
+ Index.getInstance(mContext).updateFromClassNameResource(
+ BluetoothSettings.class.getName(), true, isBluetoothOn);
+ break;
+ }
+ }
+ };
+
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -51,9 +72,10 @@ public final class BluetoothEnabler implements CompoundButton.OnCheckedChangeLis
}
};
- public BluetoothEnabler(Context context, Switch switch_) {
+ public BluetoothEnabler(Context context, SwitchBar switchBar) {
mContext = context;
- mSwitch = switch_;
+ mSwitchBar = switchBar;
+ mSwitch = switchBar.getSwitch();
mValidListener = false;
LocalBluetoothManager manager = LocalBluetoothManager.getInstance(context);
@@ -67,17 +89,29 @@ public final class BluetoothEnabler implements CompoundButton.OnCheckedChangeLis
mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
}
- public void resume() {
+ public void setupSwitchBar() {
+ mSwitchBar.show();
+ }
+
+ public void teardownSwitchBar() {
+ mSwitchBar.hide();
+ }
+
+ public void resume(Context context) {
if (mLocalAdapter == null) {
mSwitch.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);
- mSwitch.setOnCheckedChangeListener(this);
mValidListener = true;
}
@@ -86,40 +120,11 @@ public final class BluetoothEnabler implements CompoundButton.OnCheckedChangeLis
return;
}
+ mSwitchBar.removeOnSwitchChangeListener(this);
mContext.unregisterReceiver(mReceiver);
- mSwitch.setOnCheckedChangeListener(null);
mValidListener = false;
}
- public void setSwitch(Switch switch_) {
- if (mSwitch == switch_) return;
- mSwitch.setOnCheckedChangeListener(null);
- mSwitch = switch_;
- mSwitch.setOnCheckedChangeListener(mValidListener ? this : null);
-
- int bluetoothState = BluetoothAdapter.STATE_OFF;
- if (mLocalAdapter != null) bluetoothState = mLocalAdapter.getBluetoothState();
- boolean isOn = bluetoothState == BluetoothAdapter.STATE_ON;
- boolean isOff = bluetoothState == BluetoothAdapter.STATE_OFF;
- setChecked(isOn);
- mSwitch.setEnabled(isOn || isOff);
- }
-
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- // Show toast message if Bluetooth is not allowed in airplane mode
- if (isChecked &&
- !WirelessSettings.isRadioAllowed(mContext, Settings.Global.RADIO_BLUETOOTH)) {
- Toast.makeText(mContext, R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show();
- // Reset switch to off
- buttonView.setChecked(false);
- }
-
- if (mLocalAdapter != null) {
- mLocalAdapter.setBluetoothEnabled(isChecked);
- }
- mSwitch.setEnabled(false);
- }
-
void handleStateChanged(int state) {
switch (state) {
case BluetoothAdapter.STATE_TURNING_ON:
@@ -128,6 +133,7 @@ public final class BluetoothEnabler implements CompoundButton.OnCheckedChangeLis
case BluetoothAdapter.STATE_ON:
setChecked(true);
mSwitch.setEnabled(true);
+ updateSearchIndex(true);
break;
case BluetoothAdapter.STATE_TURNING_OFF:
mSwitch.setEnabled(false);
@@ -135,10 +141,12 @@ public final class BluetoothEnabler implements CompoundButton.OnCheckedChangeLis
case BluetoothAdapter.STATE_OFF:
setChecked(false);
mSwitch.setEnabled(true);
+ updateSearchIndex(false);
break;
default:
setChecked(false);
mSwitch.setEnabled(true);
+ updateSearchIndex(false);
}
}
@@ -147,12 +155,37 @@ public final class BluetoothEnabler implements CompoundButton.OnCheckedChangeLis
// set listener to null, so onCheckedChanged won't be called
// if the checked status on Switch isn't changed by user click
if (mValidListener) {
- mSwitch.setOnCheckedChangeListener(null);
+ mSwitchBar.removeOnSwitchChangeListener(this);
}
mSwitch.setChecked(isChecked);
if (mValidListener) {
- mSwitch.setOnCheckedChangeListener(this);
+ mSwitchBar.addOnSwitchChangeListener(this);
}
}
}
+
+ private void updateSearchIndex(boolean isBluetoothOn) {
+ mHandler.removeMessages(EVENT_UPDATE_INDEX);
+
+ Message msg = new Message();
+ msg.what = EVENT_UPDATE_INDEX;
+ msg.getData().putBoolean(EVENT_DATA_IS_BT_ON, isBluetoothOn);
+ mHandler.sendMessage(msg);
+ }
+
+ @Override
+ public void onSwitchChanged(Switch switchView, boolean isChecked) {
+ // Show toast message if Bluetooth is not allowed in airplane mode
+ if (isChecked &&
+ !WirelessSettings.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);
+ }
+
+ if (mLocalAdapter != null) {
+ mLocalAdapter.setBluetoothEnabled(isChecked);
+ }
+ mSwitch.setEnabled(false);
+ }
}
diff --git a/src/com/android/settings/bluetooth/BluetoothEventManager.java b/src/com/android/settings/bluetooth/BluetoothEventManager.java
index 0eead85..bf7606e 100755
--- a/src/com/android/settings/bluetooth/BluetoothEventManager.java
+++ b/src/com/android/settings/bluetooth/BluetoothEventManager.java
@@ -206,7 +206,7 @@ final class BluetoothEventManager {
}
cachedDevice.setRssi(rssi);
cachedDevice.setBtClass(btClass);
- cachedDevice.setName(name);
+ cachedDevice.setNewName(name);
cachedDevice.setVisible(true);
}
}
diff --git a/src/com/android/settings/bluetooth/BluetoothNameDialogFragment.java b/src/com/android/settings/bluetooth/BluetoothNameDialogFragment.java
index 0af9c4e..4466aea 100644
--- a/src/com/android/settings/bluetooth/BluetoothNameDialogFragment.java
+++ b/src/com/android/settings/bluetooth/BluetoothNameDialogFragment.java
@@ -26,7 +26,6 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
-import android.preference.PreferenceActivity;
import android.text.Editable;
import android.text.InputFilter;
import android.text.TextWatcher;
@@ -93,7 +92,6 @@ public final class BluetoothNameDialogFragment extends DialogFragment implements
mDeviceNameEdited = savedInstanceState.getBoolean(KEY_NAME_EDITED, false);
}
mAlertDialog = new AlertDialog.Builder(getActivity())
- .setIcon(android.R.drawable.ic_dialog_info)
.setTitle(R.string.bluetooth_rename_device)
.setView(createDialogView(deviceName))
.setPositiveButton(R.string.bluetooth_rename_button,
@@ -180,8 +178,6 @@ public final class BluetoothNameDialogFragment extends DialogFragment implements
mDeviceNameEdited = false;
mDeviceNameView.setText(mLocalAdapter.getName());
}
- PreferenceActivity activity = (PreferenceActivity)getActivity();
- activity.showBreadCrumbs(mLocalAdapter.getName(), "");
}
public void afterTextChanged(Editable s) {
diff --git a/src/com/android/settings/bluetooth/BluetoothPairingDialog.java b/src/com/android/settings/bluetooth/BluetoothPairingDialog.java
index 9b2a3e8..fd8489b 100755
--- a/src/com/android/settings/bluetooth/BluetoothPairingDialog.java
+++ b/src/com/android/settings/bluetooth/BluetoothPairingDialog.java
@@ -55,6 +55,9 @@ public final class BluetoothPairingDialog extends AlertActivity implements
private static final int BLUETOOTH_PIN_MAX_LENGTH = 16;
private static final int BLUETOOTH_PASSKEY_MAX_LENGTH = 6;
+
+ private LocalBluetoothManager mBluetoothManager;
+ private CachedBluetoothDeviceManager mCachedDeviceManager;
private BluetoothDevice mDevice;
private int mType;
private String mPairingKey;
@@ -98,13 +101,13 @@ public final class BluetoothPairingDialog extends AlertActivity implements
return;
}
- LocalBluetoothManager manager = LocalBluetoothManager.getInstance(this);
- if (manager == null) {
+ mBluetoothManager = LocalBluetoothManager.getInstance(this);
+ if (mBluetoothManager == null) {
Log.e(TAG, "Error: BluetoothAdapter not supported by system");
finish();
return;
}
- CachedBluetoothDeviceManager deviceManager = manager.getCachedDeviceManager();
+ mCachedDeviceManager = mBluetoothManager.getCachedDeviceManager();
mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
mType = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR);
@@ -112,7 +115,7 @@ public final class BluetoothPairingDialog extends AlertActivity implements
switch (mType) {
case BluetoothDevice.PAIRING_VARIANT_PIN:
case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
- createUserEntryDialog(deviceManager);
+ createUserEntryDialog();
break;
case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
@@ -123,12 +126,12 @@ public final class BluetoothPairingDialog extends AlertActivity implements
return;
}
mPairingKey = String.format(Locale.US, "%06d", passkey);
- createConfirmationDialog(deviceManager);
+ createConfirmationDialog();
break;
case BluetoothDevice.PAIRING_VARIANT_CONSENT:
case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
- createConsentDialog(deviceManager);
+ createConsentDialog();
break;
case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
@@ -144,7 +147,7 @@ public final class BluetoothPairingDialog extends AlertActivity implements
} else {
mPairingKey = String.format("%04d", pairingKey);
}
- createDisplayPasskeyOrPinDialog(deviceManager);
+ createDisplayPasskeyOrPinDialog();
break;
default:
@@ -159,11 +162,10 @@ public final class BluetoothPairingDialog extends AlertActivity implements
registerReceiver(mReceiver, new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
}
- private void createUserEntryDialog(CachedBluetoothDeviceManager deviceManager) {
+ private void createUserEntryDialog() {
final AlertController.AlertParams p = mAlertParams;
- p.mIconId = android.R.drawable.ic_dialog_info;
p.mTitle = getString(R.string.bluetooth_pairing_request);
- p.mView = createPinEntryView(deviceManager.getName(mDevice));
+ p.mView = createPinEntryView();
p.mPositiveButtonText = getString(android.R.string.ok);
p.mPositiveButtonListener = this;
p.mNegativeButtonText = getString(android.R.string.cancel);
@@ -174,9 +176,10 @@ public final class BluetoothPairingDialog extends AlertActivity implements
mOkButton.setEnabled(false);
}
- private View createPinEntryView(String deviceName) {
+ private View createPinEntryView() {
View view = getLayoutInflater().inflate(R.layout.bluetooth_pin_entry, null);
- TextView messageView = (TextView) view.findViewById(R.id.message);
+ TextView messageViewCaption = (TextView) view.findViewById(R.id.message_caption);
+ TextView messageViewContent = (TextView) view.findViewById(R.id.message_subhead);
TextView messageView2 = (TextView) view.findViewById(R.id.message_below_pin);
CheckBox alphanumericPin = (CheckBox) view.findViewById(R.id.alphanumeric_pin);
mPairingView = (EditText) view.findViewById(R.id.text);
@@ -195,7 +198,7 @@ public final class BluetoothPairingDialog extends AlertActivity implements
break;
case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
- messageId1 = R.string.bluetooth_enter_passkey_msg;
+ messageId1 = R.string.bluetooth_enter_pin_msg;
messageId2 = R.string.bluetooth_enter_passkey_other_device;
// Maximum of 6 digits for passkey
maxLength = BLUETOOTH_PASSKEY_MAX_LENGTH;
@@ -207,9 +210,8 @@ public final class BluetoothPairingDialog extends AlertActivity implements
return null;
}
- // Format the message string, then parse HTML style tags
- String messageText = getString(messageId1, deviceName);
- messageView.setText(Html.fromHtml(messageText));
+ messageViewCaption.setText(messageId1);
+ messageViewContent.setText(mCachedDeviceManager.getName(mDevice));
messageView2.setText(messageId2);
mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER);
mPairingView.setFilters(new InputFilter[] {
@@ -218,42 +220,56 @@ public final class BluetoothPairingDialog extends AlertActivity implements
return view;
}
- private View createView(CachedBluetoothDeviceManager deviceManager) {
+ private View createView() {
View view = getLayoutInflater().inflate(R.layout.bluetooth_pin_confirm, null);
- String name = deviceManager.getName(mDevice);
- TextView messageView = (TextView) view.findViewById(R.id.message);
-
- String messageText; // formatted string containing HTML style tags
+ // Escape device name to avoid HTML injection.
+ String name = Html.escapeHtml(mCachedDeviceManager.getName(mDevice));
+ TextView messageViewCaption = (TextView) view.findViewById(R.id.message_caption);
+ TextView messageViewContent = (TextView) view.findViewById(R.id.message_subhead);
+ TextView pairingViewCaption = (TextView) view.findViewById(R.id.pairing_caption);
+ TextView pairingViewContent = (TextView) view.findViewById(R.id.pairing_subhead);
+ TextView messagePairing = (TextView) view.findViewById(R.id.pairing_code_message);
+
+ String messageCaption = null;
+ String pairingContent = null;
switch (mType) {
+ case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
+ case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
+ messagePairing.setVisibility(View.VISIBLE);
case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
- messageText = getString(R.string.bluetooth_confirm_passkey_msg,
- name, mPairingKey);
+ messageCaption = getString(R.string.bluetooth_enter_pin_msg);
+ pairingContent = mPairingKey;
break;
case BluetoothDevice.PAIRING_VARIANT_CONSENT:
case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
- messageText = getString(R.string.bluetooth_incoming_pairing_msg, name);
- break;
-
- case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
- case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
- messageText = getString(R.string.bluetooth_display_passkey_pin_msg, name,
- mPairingKey);
+ messagePairing.setVisibility(View.VISIBLE);
+ messageCaption = getString(R.string.bluetooth_enter_pin_msg);
break;
default:
Log.e(TAG, "Incorrect pairing type received, not creating view");
return null;
}
- messageView.setText(Html.fromHtml(messageText));
+
+ if (messageViewCaption != null) {
+ messageViewCaption.setText(messageCaption);
+ messageViewContent.setText(name);
+ }
+
+ if (pairingContent != null) {
+ pairingViewCaption.setVisibility(View.VISIBLE);
+ pairingViewContent.setVisibility(View.VISIBLE);
+ pairingViewContent.setText(pairingContent);
+ }
+
return view;
}
- private void createConfirmationDialog(CachedBluetoothDeviceManager deviceManager) {
+ private void createConfirmationDialog() {
final AlertController.AlertParams p = mAlertParams;
- p.mIconId = android.R.drawable.ic_dialog_info;
p.mTitle = getString(R.string.bluetooth_pairing_request);
- p.mView = createView(deviceManager);
+ p.mView = createView();
p.mPositiveButtonText = getString(R.string.bluetooth_pairing_accept);
p.mPositiveButtonListener = this;
p.mNegativeButtonText = getString(R.string.bluetooth_pairing_decline);
@@ -261,11 +277,10 @@ public final class BluetoothPairingDialog extends AlertActivity implements
setupAlert();
}
- private void createConsentDialog(CachedBluetoothDeviceManager deviceManager) {
+ private void createConsentDialog() {
final AlertController.AlertParams p = mAlertParams;
- p.mIconId = android.R.drawable.ic_dialog_info;
p.mTitle = getString(R.string.bluetooth_pairing_request);
- p.mView = createView(deviceManager);
+ p.mView = createView();
p.mPositiveButtonText = getString(R.string.bluetooth_pairing_accept);
p.mPositiveButtonListener = this;
p.mNegativeButtonText = getString(R.string.bluetooth_pairing_decline);
@@ -273,12 +288,10 @@ public final class BluetoothPairingDialog extends AlertActivity implements
setupAlert();
}
- private void createDisplayPasskeyOrPinDialog(
- CachedBluetoothDeviceManager deviceManager) {
+ private void createDisplayPasskeyOrPinDialog() {
final AlertController.AlertParams p = mAlertParams;
- p.mIconId = android.R.drawable.ic_dialog_info;
p.mTitle = getString(R.string.bluetooth_pairing_request);
- p.mView = createView(deviceManager);
+ p.mView = createView();
p.mNegativeButtonText = getString(android.R.string.cancel);
p.mNegativeButtonListener = this;
setupAlert();
@@ -305,7 +318,20 @@ public final class BluetoothPairingDialog extends AlertActivity implements
}
}
+ private void allowPhonebookAccess() {
+ CachedBluetoothDevice cachedDevice = mCachedDeviceManager.findDevice(mDevice);
+ if (cachedDevice == null) {
+ cachedDevice = mCachedDeviceManager.addDevice(
+ mBluetoothManager.getBluetoothAdapter(),
+ mBluetoothManager.getProfileManager(),
+ mDevice);
+ }
+ cachedDevice.setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_ALLOWED);
+ }
+
private void onPair(String value) {
+ allowPhonebookAccess();
+
switch (mType) {
case BluetoothDevice.PAIRING_VARIANT_PIN:
byte[] pinBytes = BluetoothDevice.convertPinToBytes(value);
diff --git a/src/com/android/settings/bluetooth/BluetoothPairingRequest.java b/src/com/android/settings/bluetooth/BluetoothPairingRequest.java
index 838e7b1..44198d3 100644
--- a/src/com/android/settings/bluetooth/BluetoothPairingRequest.java
+++ b/src/com/android/settings/bluetooth/BluetoothPairingRequest.java
@@ -90,7 +90,9 @@ public final class BluetoothPairingRequest extends BroadcastReceiver {
.setContentText(res.getString(R.string.bluetooth_notif_message, name))
.setContentIntent(pending)
.setAutoCancel(true)
- .setDefaults(Notification.DEFAULT_SOUND);
+ .setDefaults(Notification.DEFAULT_SOUND)
+ .setColor(res.getColor(
+ com.android.internal.R.color.system_notification_accent_color));
NotificationManager manager = (NotificationManager)
context.getSystemService(Context.NOTIFICATION_SERVICE);
@@ -103,6 +105,19 @@ public final class BluetoothPairingRequest extends BroadcastReceiver {
NotificationManager manager = (NotificationManager) context
.getSystemService(Context.NOTIFICATION_SERVICE);
manager.cancel(NOTIFICATION_ID);
+
+ } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
+ int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
+ BluetoothDevice.ERROR);
+ int oldState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE,
+ BluetoothDevice.ERROR);
+ if((oldState == BluetoothDevice.BOND_BONDING) &&
+ (bondState == BluetoothDevice.BOND_NONE)) {
+ // Remove the notification
+ NotificationManager manager = (NotificationManager) context
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+ manager.cancel(NOTIFICATION_ID);
+ }
}
}
}
diff --git a/src/com/android/settings/bluetooth/BluetoothPermissionActivity.java b/src/com/android/settings/bluetooth/BluetoothPermissionActivity.java
index 9f071cc..f43d176 100755
--- a/src/com/android/settings/bluetooth/BluetoothPermissionActivity.java
+++ b/src/com/android/settings/bluetooth/BluetoothPermissionActivity.java
@@ -26,11 +26,8 @@ import android.os.Bundle;
import android.preference.Preference;
import android.util.Log;
import android.view.View;
-import android.widget.CheckBox;
-import android.widget.CompoundButton;
import android.widget.TextView;
import android.widget.Button;
-import android.widget.CompoundButton.OnCheckedChangeListener;
import com.android.internal.app.AlertActivity;
import com.android.internal.app.AlertController;
@@ -62,7 +59,7 @@ public class BluetoothPermissionActivity extends AlertActivity implements
String action = intent.getAction();
if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL)) {
int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
- BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
+ BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
if (requestType != mRequestType) return;
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (mDevice.equals(device)) dismissDialog();
@@ -117,7 +114,6 @@ public class BluetoothPermissionActivity extends AlertActivity implements
private void showDialog(String title, int requestType)
{
final AlertController.AlertParams p = mAlertParams;
- p.mIconId = android.R.drawable.ic_dialog_info;
p.mTitle = title;
if(DEBUG) Log.i(TAG, "showDialog() Request type: " + mRequestType + " this: " + this);
switch(requestType)
@@ -187,40 +183,43 @@ public class BluetoothPermissionActivity extends AlertActivity implements
private void onPositive() {
if (DEBUG) Log.d(TAG, "onPositive");
- savePermissionChoice(mRequestType, CachedBluetoothDevice.ACCESS_ALLOWED);
- // TODO(edjee): Now that we always save the user's choice,
- // we can get rid of BluetoothDevice#EXTRA_ALWAYS_ALLOWED.
- sendIntentToReceiver(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY, true,
- BluetoothDevice.EXTRA_ALWAYS_ALLOWED, true);
+ sendReplyIntentToReceiver(true, true);
finish();
}
private void onNegative() {
if (DEBUG) Log.d(TAG, "onNegative");
- savePermissionChoice(mRequestType, CachedBluetoothDevice.ACCESS_REJECTED);
- sendIntentToReceiver(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY, false,
- null, false // dummy value, no effect since last param is null
- );
- finish();
+
+ boolean always = true;
+ if (mRequestType == BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS) {
+ LocalBluetoothManager bluetoothManager = LocalBluetoothManager.getInstance(this);
+ CachedBluetoothDeviceManager cachedDeviceManager =
+ bluetoothManager.getCachedDeviceManager();
+ CachedBluetoothDevice cachedDevice = cachedDeviceManager.findDevice(mDevice);
+ if (cachedDevice == null) {
+ cachedDevice = cachedDeviceManager.addDevice(bluetoothManager.getBluetoothAdapter(),
+ bluetoothManager.getProfileManager(),
+ mDevice);
+ }
+ always = cachedDevice.checkAndIncreaseMessageRejectionCount();
+ }
+
+ sendReplyIntentToReceiver(false, always);
}
- private void sendIntentToReceiver(final String intentName, final boolean allowed,
- final String extraName, final boolean extraValue) {
- Intent intent = new Intent(intentName);
+ private void sendReplyIntentToReceiver(final boolean allowed, final boolean always) {
+ Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
if (mReturnPackage != null && mReturnClass != null) {
intent.setClassName(mReturnPackage, mReturnClass);
}
- if(DEBUG) Log.i(TAG, "sendIntentToReceiver() Request type: " + mRequestType +
+ if (DEBUG) Log.i(TAG, "sendReplyIntentToReceiver() Request type: " + mRequestType +
" mReturnPackage" + mReturnPackage + " mReturnClass" + mReturnClass);
intent.putExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
- allowed ? BluetoothDevice.CONNECTION_ACCESS_YES :
- BluetoothDevice.CONNECTION_ACCESS_NO);
-
- if (extraName != null) {
- intent.putExtra(extraName, extraValue);
- }
+ allowed ? BluetoothDevice.CONNECTION_ACCESS_YES
+ : BluetoothDevice.CONNECTION_ACCESS_NO);
+ intent.putExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, always);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, mRequestType);
sendBroadcast(intent, android.Manifest.permission.BLUETOOTH_ADMIN);
@@ -252,23 +251,4 @@ public class BluetoothPermissionActivity extends AlertActivity implements
public boolean onPreferenceChange(Preference preference, Object newValue) {
return true;
}
-
- private void savePermissionChoice(int permissionType, int permissionChoice) {
- LocalBluetoothManager bluetoothManager = LocalBluetoothManager.getInstance(this);
- CachedBluetoothDeviceManager cachedDeviceManager =
- bluetoothManager.getCachedDeviceManager();
- CachedBluetoothDevice cachedDevice = cachedDeviceManager.findDevice(mDevice);
- if (DEBUG) Log.d(TAG, "savePermissionChoice permissionType: " + permissionType);
- if (cachedDevice == null ) {
- cachedDevice = cachedDeviceManager.addDevice(bluetoothManager.getBluetoothAdapter(),
- bluetoothManager.getProfileManager(),
- mDevice);
- }
- if(permissionType == BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS){
- cachedDevice.setPhonebookPermissionChoice(permissionChoice);
- }else if (permissionType == BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS){
- cachedDevice.setMessagePermissionChoice(permissionChoice);
- }
- }
-
}
diff --git a/src/com/android/settings/bluetooth/BluetoothPermissionRequest.java b/src/com/android/settings/bluetooth/BluetoothPermissionRequest.java
index c3b93be..bcd4d77 100644
--- a/src/com/android/settings/bluetooth/BluetoothPermissionRequest.java
+++ b/src/com/android/settings/bluetooth/BluetoothPermissionRequest.java
@@ -66,20 +66,30 @@ public final class BluetoothPermissionRequest extends BroadcastReceiver {
if (DEBUG) Log.d(TAG, "onReceive request type: " + mRequestType + " return "
+ mReturnPackage + "," + mReturnClass);
- // Check if user had made decisions on accepting or rejecting the phonebook access
- // request. If there is, reply the request and return, no need to start permission
- // activity dialog or notification.
+ // Even if the user has already made the choice, Bluetooth still may not know that if
+ // the user preference data have not been migrated from Settings app's shared
+ // preferences to Bluetooth app's. In that case, Bluetooth app broadcasts an
+ // ACTION_CONNECTION_ACCESS_REQUEST intent to ask to Settings app.
+ //
+ // If that happens, 'checkUserChoice()' here will do migration because it finds or
+ // creates a 'CachedBluetoothDevice' object for the device.
+ //
+ // After migration is done, 'checkUserChoice()' replies to the request by sending an
+ // ACTION_CONNECTION_ACCESS_REPLY intent. And we don't need to start permission activity
+ // dialog or notification.
if (checkUserChoice()) {
return;
}
Intent connectionAccessIntent = new Intent(action);
connectionAccessIntent.setClass(context, BluetoothPermissionActivity.class);
- // We use the FLAG_ACTIVITY_MULTIPLE_TASK since we can have multiple concurrent access requests
- connectionAccessIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
- connectionAccessIntent.setType(Integer.toString(mRequestType)); /* This is needed to create two pending
- intents to the same activity.
- The value is not used in the activity. */
+ // We use the FLAG_ACTIVITY_MULTIPLE_TASK since we can have multiple concurrent access
+ // requests.
+ connectionAccessIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+ // This is needed to create two pending intents to the same activity. The value is not
+ // used in the activity.
+ connectionAccessIntent.setType(Integer.toString(mRequestType));
connectionAccessIntent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
mRequestType);
connectionAccessIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
@@ -92,8 +102,9 @@ public final class BluetoothPermissionRequest extends BroadcastReceiver {
PowerManager powerManager =
(PowerManager) context.getSystemService(Context.POWER_SERVICE);
- if (powerManager.isScreenOn() &&
- LocalBluetoothPreferences.shouldShowDialogInForeground(context, deviceAddress) ) {
+ if (powerManager.isScreenOn()
+ && LocalBluetoothPreferences.shouldShowDialogInForeground(
+ context, deviceAddress)) {
context.startActivity(connectionAccessIntent);
} else {
// Put up a notification that leads to the dialog
@@ -110,36 +121,43 @@ public final class BluetoothPermissionRequest extends BroadcastReceiver {
switch (mRequestType) {
case BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS:
title = context.getString(R.string.bluetooth_phonebook_request);
- message = context.getString(R.string.bluetooth_pb_acceptance_dialog_text, deviceName, deviceName);
+ message = context.getString(R.string.bluetooth_pb_acceptance_dialog_text,
+ deviceName, deviceName);
break;
case BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS:
title = context.getString(R.string.bluetooth_map_request);
- message = context.getString(R.string.bluetooth_map_acceptance_dialog_text, deviceName, deviceName);
+ message = context.getString(R.string.bluetooth_map_acceptance_dialog_text,
+ deviceName, deviceName);
break;
default:
title = context.getString(R.string.bluetooth_connection_permission_request);
- message = context.getString(R.string.bluetooth_connection_dialog_text, deviceName, deviceName);
+ message = context.getString(R.string.bluetooth_connection_dialog_text,
+ deviceName, deviceName);
break;
}
Notification notification = new Notification.Builder(context)
- .setContentTitle(title)
- .setTicker(message)
- .setContentText(message)
- .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
- .setAutoCancel(true)
- .setPriority(Notification.PRIORITY_MAX)
- .setOnlyAlertOnce(false)
- .setDefaults(Notification.DEFAULT_ALL)
- .setContentIntent(PendingIntent.getActivity(context, 0, connectionAccessIntent, 0))
- .setDeleteIntent(PendingIntent.getBroadcast(context, 0, deleteIntent, 0))
- .build();
-
- notification.flags |= Notification.FLAG_NO_CLEAR; /* cannot be set with the builder */
+ .setContentTitle(title)
+ .setTicker(message)
+ .setContentText(message)
+ .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
+ .setAutoCancel(true)
+ .setPriority(Notification.PRIORITY_MAX)
+ .setOnlyAlertOnce(false)
+ .setDefaults(Notification.DEFAULT_ALL)
+ .setContentIntent(PendingIntent.getActivity(context, 0,
+ connectionAccessIntent, 0))
+ .setDeleteIntent(PendingIntent.getBroadcast(context, 0, deleteIntent, 0))
+ .setColor(context.getResources().getColor(
+ com.android.internal.R.color.system_notification_accent_color))
+ .build();
+
+ notification.flags |= Notification.FLAG_NO_CLEAR; // Cannot be set with the builder.
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
- notificationManager.notify(getNotificationTag(mRequestType),NOTIFICATION_ID, notification);
+ notificationManager.notify(getNotificationTag(mRequestType), NOTIFICATION_ID,
+ notification);
}
} else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL)) {
// Remove the notification
@@ -171,7 +189,7 @@ public final class BluetoothPermissionRequest extends BroadcastReceiver {
// ignore if it is something else than phonebook/message settings it wants us to remember
if (mRequestType != BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS
&& mRequestType != BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS) {
- if (DEBUG) Log.d(TAG, "Unknown RequestType: " + mRequestType);
+ if (DEBUG) Log.d(TAG, "checkUserChoice(): Unknown RequestType " + mRequestType);
return processed;
}
@@ -179,71 +197,56 @@ public final class BluetoothPermissionRequest extends BroadcastReceiver {
CachedBluetoothDeviceManager cachedDeviceManager =
bluetoothManager.getCachedDeviceManager();
CachedBluetoothDevice cachedDevice = cachedDeviceManager.findDevice(mDevice);
-
if (cachedDevice == null) {
cachedDevice = cachedDeviceManager.addDevice(bluetoothManager.getBluetoothAdapter(),
bluetoothManager.getProfileManager(), mDevice);
}
- if(mRequestType == BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS) {
+ String intentName = BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY;
+ if (mRequestType == BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS) {
int phonebookPermission = cachedDevice.getPhonebookPermissionChoice();
if (phonebookPermission == CachedBluetoothDevice.ACCESS_UNKNOWN) {
- return processed;
- }
-
- String intentName = BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY;
- if (phonebookPermission == CachedBluetoothDevice.ACCESS_ALLOWED) {
- sendIntentToReceiver(intentName, true, BluetoothDevice.EXTRA_ALWAYS_ALLOWED, true);
+ // Leave 'processed' as false.
+ } else if (phonebookPermission == CachedBluetoothDevice.ACCESS_ALLOWED) {
+ sendReplyIntentToReceiver(true);
processed = true;
} else if (phonebookPermission == CachedBluetoothDevice.ACCESS_REJECTED) {
- sendIntentToReceiver(intentName, false,
- null, false ); // dummy value, no effect since previous param is null
+ sendReplyIntentToReceiver(false);
processed = true;
} else {
Log.e(TAG, "Bad phonebookPermission: " + phonebookPermission);
}
-
- } else if(mRequestType == BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS) {
-
+ } else if (mRequestType == BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS) {
int messagePermission = cachedDevice.getMessagePermissionChoice();
if (messagePermission == CachedBluetoothDevice.ACCESS_UNKNOWN) {
- return processed;
- }
-
- String intentName = BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY;
- if (messagePermission == CachedBluetoothDevice.ACCESS_ALLOWED) {
- sendIntentToReceiver(intentName, true, BluetoothDevice.EXTRA_ALWAYS_ALLOWED, true);
+ // Leave 'processed' as false.
+ } else if (messagePermission == CachedBluetoothDevice.ACCESS_ALLOWED) {
+ sendReplyIntentToReceiver(true);
processed = true;
} else if (messagePermission == CachedBluetoothDevice.ACCESS_REJECTED) {
- sendIntentToReceiver(intentName, false,
- null, false); // dummy value, no effect since previous param is null
+ sendReplyIntentToReceiver(false);
processed = true;
} else {
Log.e(TAG, "Bad messagePermission: " + messagePermission);
}
}
- if(DEBUG) Log.d(TAG,"checkUserChoice(): returning " + processed);
+ if (DEBUG) Log.d(TAG,"checkUserChoice(): returning " + processed);
return processed;
}
- private void sendIntentToReceiver(final String intentName, final boolean allowed,
- final String extraName, final boolean extraValue) {
- Intent intent = new Intent(intentName);
+ private void sendReplyIntentToReceiver(final boolean allowed) {
+ Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
if (mReturnPackage != null && mReturnClass != null) {
intent.setClassName(mReturnPackage, mReturnClass);
}
intent.putExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
- allowed ? BluetoothDevice.CONNECTION_ACCESS_YES :
- BluetoothDevice.CONNECTION_ACCESS_NO);
-
- if (extraName != null) {
- intent.putExtra(extraName, extraValue);
- }
+ allowed ? BluetoothDevice.CONNECTION_ACCESS_YES
+ : BluetoothDevice.CONNECTION_ACCESS_NO);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, mRequestType);
mContext.sendBroadcast(intent, android.Manifest.permission.BLUETOOTH_ADMIN);
diff --git a/src/com/android/settings/bluetooth/BluetoothProgressCategory.java b/src/com/android/settings/bluetooth/BluetoothProgressCategory.java
index 1c81360..9f64f9d 100644
--- a/src/com/android/settings/bluetooth/BluetoothProgressCategory.java
+++ b/src/com/android/settings/bluetooth/BluetoothProgressCategory.java
@@ -22,8 +22,26 @@ import com.android.settings.R;
import android.content.Context;
import android.util.AttributeSet;
+/**
+ * A Bluetooth discovery progress category
+ */
public class BluetoothProgressCategory extends ProgressCategory {
+ public BluetoothProgressCategory(Context context) {
+ this(context, null);
+ }
+
public BluetoothProgressCategory(Context context, AttributeSet attrs) {
- super(context, attrs, R.string.bluetooth_no_devices_found);
+ this(context, attrs, 0);
+ }
+
+ public BluetoothProgressCategory(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public BluetoothProgressCategory(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ setEmptyTextRes(R.string.bluetooth_no_devices_found);
}
}
diff --git a/src/com/android/settings/bluetooth/BluetoothSettings.java b/src/com/android/settings/bluetooth/BluetoothSettings.java
index bbbeee5..dd2c9df 100755
--- a/src/com/android/settings/bluetooth/BluetoothSettings.java
+++ b/src/com/android/settings/bluetooth/BluetoothSettings.java
@@ -18,58 +18,76 @@ package com.android.settings.bluetooth;
import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
-import android.app.ActionBar;
import android.app.Activity;
+import android.app.AlertDialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Bundle;
import android.preference.Preference;
-import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
+import android.preference.PreferenceFragment;
import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
import android.util.Log;
-import android.view.Gravity;
+import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
-import android.widget.Switch;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
import android.widget.TextView;
import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.search.Index;
+import com.android.settings.search.Indexable;
+import com.android.settings.search.SearchIndexableRaw;
+import com.android.settings.widget.SwitchBar;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
/**
* BluetoothSettings is the Settings screen for Bluetooth configuration and
* connection management.
*/
-public final class BluetoothSettings extends DeviceListPreferenceFragment {
+public final class BluetoothSettings extends DeviceListPreferenceFragment implements Indexable {
private static final String TAG = "BluetoothSettings";
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_VISIBILITY_TIMEOUT = Menu.FIRST + 2;
- private static final int MENU_ID_SHOW_RECEIVED = Menu.FIRST + 3;
+ private static final int MENU_ID_SHOW_RECEIVED = Menu.FIRST + 2;
/* Private intent to show the list of received files */
private static final String BTOPP_ACTION_OPEN_RECEIVED_FILES =
"android.btopp.intent.action.OPEN_RECEIVED_FILES";
- private BluetoothEnabler mBluetoothEnabler;
+ private static View mSettingsDialogView = null;
- private BluetoothDiscoverableEnabler mDiscoverableEnabler;
+ private BluetoothEnabler mBluetoothEnabler;
private PreferenceGroup mPairedDevicesCategory;
-
private PreferenceGroup mAvailableDevicesCategory;
private boolean mAvailableDevicesCategoryIsPresent;
- private boolean mActivityStarted;
+
+ private boolean mInitialScanStarted;
+ private boolean mInitiateDiscoverable;
private TextView mEmptyView;
+ private SwitchBar mSwitchBar;
private final IntentFilter mIntentFilter;
@@ -80,15 +98,23 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment {
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
+ final String action = intent.getAction();
+ final int state =
+ intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
+
if (action.equals(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED)) {
- updateDeviceName();
+ updateDeviceName(context);
+ }
+
+ if (state == BluetoothAdapter.STATE_ON) {
+ mInitiateDiscoverable = true;
}
}
- private void updateDeviceName() {
+ private void updateDeviceName(Context context) {
if (mLocalAdapter.isEnabled() && mMyDevicePreference != null) {
- mMyDevicePreference.setTitle(mLocalAdapter.getName());
+ mMyDevicePreference.setSummary(context.getResources().getString(
+ R.string.bluetooth_is_visible_message, mLocalAdapter.getName()));
}
}
};
@@ -101,36 +127,29 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment {
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
- mActivityStarted = (savedInstanceState == null); // don't auto start scan after rotation
+ mInitialScanStarted = (savedInstanceState != null); // don't auto start scan after rotation
+ mInitiateDiscoverable = true;
mEmptyView = (TextView) getView().findViewById(android.R.id.empty);
getListView().setEmptyView(mEmptyView);
+
+ final SettingsActivity activity = (SettingsActivity) getActivity();
+ mSwitchBar = activity.getSwitchBar();
+
+ mBluetoothEnabler = new BluetoothEnabler(activity, mSwitchBar);
+ mBluetoothEnabler.setupSwitchBar();
}
@Override
- void addPreferencesForActivity() {
- addPreferencesFromResource(R.xml.bluetooth_settings);
+ public void onDestroyView() {
+ super.onDestroyView();
- Activity activity = getActivity();
-
- Switch actionBarSwitch = new Switch(activity);
-
- if (activity instanceof PreferenceActivity) {
- PreferenceActivity preferenceActivity = (PreferenceActivity) activity;
- if (preferenceActivity.onIsHidingHeaders() || !preferenceActivity.onIsMultiPane()) {
- final int padding = activity.getResources().getDimensionPixelSize(
- R.dimen.action_bar_switch_padding);
- actionBarSwitch.setPaddingRelative(0, 0, padding, 0);
- activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
- ActionBar.DISPLAY_SHOW_CUSTOM);
- activity.getActionBar().setCustomView(actionBarSwitch, new ActionBar.LayoutParams(
- ActionBar.LayoutParams.WRAP_CONTENT,
- ActionBar.LayoutParams.WRAP_CONTENT,
- Gravity.CENTER_VERTICAL | Gravity.END));
- }
- }
+ mBluetoothEnabler.teardownSwitchBar();
+ }
- mBluetoothEnabler = new BluetoothEnabler(activity, actionBarSwitch);
+ @Override
+ void addPreferencesForActivity() {
+ addPreferencesFromResource(R.xml.bluetooth_settings);
setHasOptionsMenu(true);
}
@@ -140,16 +159,22 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment {
// resume BluetoothEnabler before calling super.onResume() so we don't get
// any onDeviceAdded() callbacks before setting up view in updateContent()
if (mBluetoothEnabler != null) {
- mBluetoothEnabler.resume();
+ mBluetoothEnabler.resume(getActivity());
}
super.onResume();
- if (mDiscoverableEnabler != null) {
- mDiscoverableEnabler.resume();
+ mInitiateDiscoverable = true;
+
+ if (isUiRestricted()) {
+ setDeviceListGroup(getPreferenceScreen());
+ removeAllDevices();
+ mEmptyView.setText(R.string.bluetooth_empty_list_user_restricted);
+ return;
}
+
getActivity().registerReceiver(mReceiver, mIntentFilter);
if (mLocalAdapter != null) {
- updateContent(mLocalAdapter.getBluetoothState(), mActivityStarted);
+ updateContent(mLocalAdapter.getBluetoothState());
}
}
@@ -159,17 +184,22 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment {
if (mBluetoothEnabler != null) {
mBluetoothEnabler.pause();
}
- getActivity().unregisterReceiver(mReceiver);
- if (mDiscoverableEnabler != null) {
- mDiscoverableEnabler.pause();
+
+ // Make the device only visible to connected devices.
+ mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
+
+ if (isUiRestricted()) {
+ return;
}
+
+ getActivity().unregisterReceiver(mReceiver);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
if (mLocalAdapter == null) return;
// If the user is not allowed to configure bluetooth, do not show the menu.
- if (isRestrictedAndNotPinProtected()) return;
+ if (isUiRestricted()) return;
boolean bluetoothIsEnabled = mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_ON;
boolean isDiscovering = mLocalAdapter.isDiscovering();
@@ -177,11 +207,8 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment {
R.string.bluetooth_search_for_devices;
menu.add(Menu.NONE, MENU_ID_SCAN, 0, textId)
.setEnabled(bluetoothIsEnabled && !isDiscovering)
- .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
- menu.add(Menu.NONE, MENU_ID_RENAME_DEVICE, 0, R.string.bluetooth_rename_device)
- .setEnabled(bluetoothIsEnabled)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
- menu.add(Menu.NONE, MENU_ID_VISIBILITY_TIMEOUT, 0, R.string.bluetooth_visibility_timeout)
+ menu.add(Menu.NONE, MENU_ID_RENAME_DEVICE, 0, R.string.bluetooth_rename_device)
.setEnabled(bluetoothIsEnabled)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
menu.add(Menu.NONE, MENU_ID_SHOW_RECEIVED, 0, R.string.bluetooth_show_received_files)
@@ -203,11 +230,6 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment {
getFragmentManager(), "rename device");
return true;
- case MENU_ID_VISIBILITY_TIMEOUT:
- new BluetoothVisibilityTimeoutFragment().show(
- getFragmentManager(), "visibility timeout");
- return true;
-
case MENU_ID_SHOW_RECEIVED:
Intent intent = new Intent(BTOPP_ACTION_OPEN_RECEIVED_FILES);
getActivity().sendBroadcast(intent);
@@ -217,10 +239,23 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment {
}
private void startScanning() {
- if (isRestrictedAndNotPinProtected()) return;
+ if (isUiRestricted()) {
+ return;
+ }
+
if (!mAvailableDevicesCategoryIsPresent) {
getPreferenceScreen().addPreference(mAvailableDevicesCategory);
+ mAvailableDevicesCategoryIsPresent = true;
}
+
+ if (mAvailableDevicesCategory != null) {
+ setDeviceListGroup(mAvailableDevicesCategory);
+ removeAllDevices();
+ }
+
+ mLocalManager.getCachedDeviceManager().clearNonBondedDevices();
+ mAvailableDevicesCategory.removeAll();
+ mInitialScanStarted = true;
mLocalAdapter.startScanning(true);
}
@@ -231,16 +266,18 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment {
}
private void addDeviceCategory(PreferenceGroup preferenceGroup, int titleId,
- BluetoothDeviceFilter.Filter filter) {
+ BluetoothDeviceFilter.Filter filter, boolean addCachedDevices) {
preferenceGroup.setTitle(titleId);
getPreferenceScreen().addPreference(preferenceGroup);
setFilter(filter);
setDeviceListGroup(preferenceGroup);
- addCachedDevices();
+ if (addCachedDevices) {
+ addCachedDevices();
+ }
preferenceGroup.setEnabled(true);
}
- private void updateContent(int bluetoothState, boolean scanState) {
+ private void updateContent(int bluetoothState) {
final PreferenceScreen preferenceScreen = getPreferenceScreen();
int messageId = 0;
@@ -250,28 +287,9 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment {
preferenceScreen.setOrderingAsAdded(true);
mDevicePreferenceMap.clear();
- // This device
- if (mMyDevicePreference == null) {
- mMyDevicePreference = new Preference(getActivity());
- }
- mMyDevicePreference.setTitle(mLocalAdapter.getName());
- if (getResources().getBoolean(com.android.internal.R.bool.config_voice_capable)) {
- mMyDevicePreference.setIcon(R.drawable.ic_bt_cellphone); // for phones
- } else {
- mMyDevicePreference.setIcon(R.drawable.ic_bt_laptop); // for tablets, etc.
- }
- mMyDevicePreference.setPersistent(false);
- mMyDevicePreference.setEnabled(true);
- preferenceScreen.addPreference(mMyDevicePreference);
-
- if (!isRestrictedAndNotPinProtected()) {
- if (mDiscoverableEnabler == null) {
- mDiscoverableEnabler = new BluetoothDiscoverableEnabler(getActivity(),
- mLocalAdapter, mMyDevicePreference);
- mDiscoverableEnabler.resume();
- LocalBluetoothManager.getInstance(getActivity()).setDiscoverableEnabler(
- mDiscoverableEnabler);
- }
+ if (isUiRestricted()) {
+ messageId = R.string.bluetooth_empty_list_user_restricted;
+ break;
}
// Paired devices category
@@ -282,44 +300,47 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment {
}
addDeviceCategory(mPairedDevicesCategory,
R.string.bluetooth_preference_paired_devices,
- BluetoothDeviceFilter.BONDED_DEVICE_FILTER);
+ BluetoothDeviceFilter.BONDED_DEVICE_FILTER, true);
int numberOfPairedDevices = mPairedDevicesCategory.getPreferenceCount();
- if (mDiscoverableEnabler != null) {
- mDiscoverableEnabler.setNumberOfPairedDevices(numberOfPairedDevices);
+ if (isUiRestricted() || numberOfPairedDevices <= 0) {
+ preferenceScreen.removePreference(mPairedDevicesCategory);
}
// Available devices category
if (mAvailableDevicesCategory == null) {
- mAvailableDevicesCategory = new BluetoothProgressCategory(getActivity(), null);
+ mAvailableDevicesCategory = new BluetoothProgressCategory(getActivity());
+ mAvailableDevicesCategory.setSelectable(false);
} else {
mAvailableDevicesCategory.removeAll();
}
- if (!isRestrictedAndNotPinProtected()) {
- addDeviceCategory(mAvailableDevicesCategory,
- R.string.bluetooth_preference_found_devices,
- BluetoothDeviceFilter.UNBONDED_DEVICE_FILTER);
- }
+ addDeviceCategory(mAvailableDevicesCategory,
+ R.string.bluetooth_preference_found_devices,
+ BluetoothDeviceFilter.UNBONDED_DEVICE_FILTER, mInitialScanStarted);
int numberOfAvailableDevices = mAvailableDevicesCategory.getPreferenceCount();
- mAvailableDevicesCategoryIsPresent = true;
- if (numberOfAvailableDevices == 0) {
- preferenceScreen.removePreference(mAvailableDevicesCategory);
- mAvailableDevicesCategoryIsPresent = false;
+ if (!mInitialScanStarted) {
+ startScanning();
}
- if (numberOfPairedDevices == 0) {
- preferenceScreen.removePreference(mPairedDevicesCategory);
- if (scanState == true) {
- mActivityStarted = false;
- startScanning();
- } else {
- if (!mAvailableDevicesCategoryIsPresent) {
- getPreferenceScreen().addPreference(mAvailableDevicesCategory);
- }
- }
+ if (mMyDevicePreference == null) {
+ mMyDevicePreference = new Preference(getActivity());
}
+
+ mMyDevicePreference.setSummary(getResources().getString(
+ R.string.bluetooth_is_visible_message, mLocalAdapter.getName()));
+ mMyDevicePreference.setSelectable(false);
+ preferenceScreen.addPreference(mMyDevicePreference);
+
getActivity().invalidateOptionsMenu();
+
+ // mLocalAdapter.setScanMode is internally synchronized so it is okay for multiple
+ // threads to execute.
+ if (mInitiateDiscoverable) {
+ // Make the device visible to other devices.
+ mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
+ mInitiateDiscoverable = false;
+ }
return; // not break
case BluetoothAdapter.STATE_TURNING_OFF:
@@ -328,55 +349,126 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment {
case BluetoothAdapter.STATE_OFF:
messageId = R.string.bluetooth_empty_list_bluetooth_off;
+ if (isUiRestricted()) {
+ messageId = R.string.bluetooth_empty_list_user_restricted;
+ }
break;
case BluetoothAdapter.STATE_TURNING_ON:
messageId = R.string.bluetooth_turning_on;
+ mInitialScanStarted = false;
break;
}
setDeviceListGroup(preferenceScreen);
removeAllDevices();
mEmptyView.setText(messageId);
- getActivity().invalidateOptionsMenu();
+ if (!isUiRestricted()) {
+ getActivity().invalidateOptionsMenu();
+ }
}
@Override
public void onBluetoothStateChanged(int bluetoothState) {
super.onBluetoothStateChanged(bluetoothState);
- updateContent(bluetoothState, true);
+ updateContent(bluetoothState);
}
@Override
public void onScanningStateChanged(boolean started) {
super.onScanningStateChanged(started);
// Update options' enabled state
- getActivity().invalidateOptionsMenu();
+ if (getActivity() != null) {
+ getActivity().invalidateOptionsMenu();
+ }
}
public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
setDeviceListGroup(getPreferenceScreen());
removeAllDevices();
- updateContent(mLocalAdapter.getBluetoothState(), false);
+ updateContent(mLocalAdapter.getBluetoothState());
}
- private final View.OnClickListener mDeviceProfilesListener = new View.OnClickListener() {
+ private final View.OnClickListener mDeviceProfilesListener = new View.OnClickListener() {
public void onClick(View v) {
// User clicked on advanced options icon for a device in the list
- if (v.getTag() instanceof CachedBluetoothDevice) {
- if (isRestrictedAndNotPinProtected()) return;
+ if (!(v.getTag() instanceof CachedBluetoothDevice)) {
+ Log.w(TAG, "onClick() called for other View: " + v);
+ return;
+ }
- CachedBluetoothDevice device = (CachedBluetoothDevice) v.getTag();
+ final CachedBluetoothDevice device = (CachedBluetoothDevice) v.getTag();
+ final Activity activity = getActivity();
+ DeviceProfilesSettings profileFragment = (DeviceProfilesSettings)activity.
+ getFragmentManager().findFragmentById(R.id.bluetooth_fragment_settings);
- Bundle args = new Bundle(1);
- args.putParcelable(DeviceProfilesSettings.EXTRA_DEVICE, device.getDevice());
+ if (mSettingsDialogView != null){
+ ViewGroup parent = (ViewGroup) mSettingsDialogView.getParent();
+ if (parent != null) {
+ parent.removeView(mSettingsDialogView);
+ }
+ }
+
+ if (profileFragment == null) {
+ LayoutInflater inflater = getActivity().getLayoutInflater();
+ mSettingsDialogView = inflater.inflate(R.layout.bluetooth_device_settings, null);
+ profileFragment = (DeviceProfilesSettings)activity.getFragmentManager()
+ .findFragmentById(R.id.bluetooth_fragment_settings);
- ((PreferenceActivity) getActivity()).startPreferencePanel(
- DeviceProfilesSettings.class.getName(), args,
- R.string.bluetooth_device_advanced_title, null, null, 0);
- } else {
- Log.w(TAG, "onClick() called for other View: " + v); // TODO remove
+ // To enable scrolling we store the name field in a seperate header and add to
+ // the ListView of the profileFragment.
+ View header = inflater.inflate(R.layout.bluetooth_device_settings_header, null);
+ profileFragment.getListView().addHeaderView(header);
}
+
+ final View dialogLayout = mSettingsDialogView;
+ AlertDialog.Builder settingsDialog = new AlertDialog.Builder(activity);
+ profileFragment.setDevice(device);
+ final EditText deviceName = (EditText)dialogLayout.findViewById(R.id.name);
+ deviceName.setText(device.getName(), TextView.BufferType.EDITABLE);
+
+ final DeviceProfilesSettings dpsFragment = profileFragment;
+ final Context context = v.getContext();
+ settingsDialog.setView(dialogLayout);
+ settingsDialog.setTitle(R.string.bluetooth_preference_paired_devices);
+ settingsDialog.setPositiveButton(R.string.okay,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ EditText deviceName = (EditText)dialogLayout.findViewById(R.id.name);
+ device.setName(deviceName.getText().toString());
+ }
+ });
+
+ settingsDialog.setNegativeButton(R.string.forget,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ device.unpair();
+ com.android.settings.bluetooth.Utils.updateSearchIndex(activity,
+ BluetoothSettings.class.getName(), device.getName(),
+ context.getResources().getString(R.string.bluetooth_settings),
+ R.drawable.ic_settings_bluetooth2, false);
+ }
+ });
+
+ // We must ensure that the fragment gets destroyed to avoid duplicate fragments.
+ settingsDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
+ public void onDismiss(final DialogInterface dialog) {
+ if (!activity.isDestroyed()) {
+ activity.getFragmentManager().beginTransaction().remove(dpsFragment)
+ .commitAllowingStateLoss();
+ }
+ }
+ });
+
+ AlertDialog dialog = settingsDialog.create();
+ dialog.create();
+ dialog.show();
+
+ // We must ensure that clicking on the EditText will bring up the keyboard.
+ dialog.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
}
};
@@ -397,4 +489,39 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment {
protected int getHelpResource() {
return R.string.help_url_bluetooth;
}
+
+ public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider() {
+ @Override
+ public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
+
+ final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>();
+
+ final Resources res = context.getResources();
+
+ // Add fragment title
+ SearchIndexableRaw data = new SearchIndexableRaw(context);
+ data.title = res.getString(R.string.bluetooth_settings);
+ data.screenTitle = res.getString(R.string.bluetooth_settings);
+ result.add(data);
+
+ // Add cached paired BT devices
+ LocalBluetoothManager lbtm = LocalBluetoothManager.getInstance(context);
+ // LocalBluetoothManager.getInstance can return null if the device does not
+ // support bluetooth (e.g. the emulator).
+ if (lbtm != null) {
+ Set<BluetoothDevice> bondedDevices =
+ lbtm.getBluetoothAdapter().getBondedDevices();
+
+ for (BluetoothDevice device : bondedDevices) {
+ data = new SearchIndexableRaw(context);
+ data.title = device.getName();
+ data.screenTitle = res.getString(R.string.bluetooth_settings);
+ data.enabled = enabled;
+ result.add(data);
+ }
+ }
+ return result;
+ }
+ };
}
diff --git a/src/com/android/settings/bluetooth/CachedBluetoothDevice.java b/src/com/android/settings/bluetooth/CachedBluetoothDevice.java
index 1263797..e61b3fd 100755
--- a/src/com/android/settings/bluetooth/CachedBluetoothDevice.java
+++ b/src/com/android/settings/bluetooth/CachedBluetoothDevice.java
@@ -68,27 +68,22 @@ final class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
private int mMessagePermissionChoice;
- private int mPhonebookRejectedTimes;
-
- private int mMessageRejectedTimes;
+ private int mMessageRejectionCount;
private final Collection<Callback> mCallbacks = new ArrayList<Callback>();
// Following constants indicate the user's choices of Phone book/message access settings
// User hasn't made any choice or settings app has wiped out the memory
- final static int ACCESS_UNKNOWN = 0;
+ public final static int ACCESS_UNKNOWN = 0;
// User has accepted the connection and let Settings app remember the decision
- final static int ACCESS_ALLOWED = 1;
+ public final static int ACCESS_ALLOWED = 1;
// User has rejected the connection and let Settings app remember the decision
- final static int ACCESS_REJECTED = 2;
+ public final static int ACCESS_REJECTED = 2;
- // how many times did User reject the connection to make the rejected persist.
- final static int PERSIST_REJECTED_TIMES_LIMIT = 2;
+ // How many times user should reject the connection to make the choice persist.
+ private final static int MESSAGE_REJECTION_COUNT_LIMIT_TO_PERSIST = 2;
- private final static String PHONEBOOK_PREFS_NAME = "bluetooth_phonebook_permission";
- private final static String MESSAGE_PREFS_NAME = "bluetooth_message_permission";
- private final static String PHONEBOOK_REJECT_TIMES = "bluetooth_phonebook_reject";
- private final static String MESSAGE_REJECT_TIMES = "bluetooth_message_reject";
+ private final static String MESSAGE_REJECTION_COUNT_PREFS_NAME = "bluetooth_message_reject";
/**
* When we connect to multiple profiles, we only want to display a single
@@ -138,7 +133,9 @@ final class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
}
mProfileConnectionState.put(profile, newProfileState);
if (newProfileState == BluetoothProfile.STATE_CONNECTED) {
- if (!mProfiles.contains(profile)) {
+ if (profile instanceof MapProfile) {
+ profile.setPreferred(mDevice, true);
+ } else if (!mProfiles.contains(profile)) {
mRemovedProfiles.remove(profile);
mProfiles.add(profile);
if (profile instanceof PanProfile &&
@@ -147,15 +144,8 @@ final class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
mLocalNapRoleConnected = true;
}
}
- if (profile instanceof MapProfile) {
- profile.setPreferred(mDevice, true);
- }
} else if (profile instanceof MapProfile &&
newProfileState == BluetoothProfile.STATE_DISCONNECTED) {
- if (mProfiles.contains(profile)) {
- mRemovedProfiles.add(profile);
- mProfiles.remove(profile);
- }
profile.setPreferred(mDevice, false);
} else if (mLocalNapRoleConnected && profile instanceof PanProfile &&
((PanProfile) profile).isLocalRoleNap(mDevice) &&
@@ -370,10 +360,9 @@ final class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
fetchName();
fetchBtClass();
updateProfiles();
- fetchPhonebookPermissionChoice();
- fetchMessagePermissionChoice();
- fetchPhonebookRejectTimes();
- fetchMessageRejectTimes();
+ migratePhonebookPermissionChoice();
+ migrateMessagePermissionChoice();
+ fetchMessageRejectionCount();
mVisible = false;
dispatchAttributesChanged();
@@ -387,19 +376,30 @@ final class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
return mName;
}
- void setName(String name) {
- if (!mName.equals(name)) {
- if (TextUtils.isEmpty(name)) {
- // TODO: use friendly name for unknown device (bug 1181856)
+ /**
+ * Populate name from BluetoothDevice.ACTION_FOUND intent
+ */
+ void setNewName(String name) {
+ if (mName == null) {
+ mName = name;
+ if (mName == null || TextUtils.isEmpty(mName)) {
mName = mDevice.getAddress();
- } else {
- mName = name;
- mDevice.setAlias(name);
}
dispatchAttributesChanged();
}
}
+ /**
+ * user changes the device name
+ */
+ void setName(String name) {
+ if (!mName.equals(name)) {
+ mName = name;
+ mDevice.setAlias(name);
+ dispatchAttributesChanged();
+ }
+ }
+
void refreshName() {
fetchName();
dispatchAttributesChanged();
@@ -541,10 +541,8 @@ final class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
mConnectAfterPairing = false; // cancel auto-connect
setPhonebookPermissionChoice(ACCESS_UNKNOWN);
setMessagePermissionChoice(ACCESS_UNKNOWN);
- mPhonebookRejectedTimes = 0;
- savePhonebookRejectTimes();
- mMessageRejectedTimes = 0;
- saveMessageRejectTimes();
+ mMessageRejectionCount = 0;
+ saveMessageRejectionCount();
}
refresh();
@@ -657,104 +655,116 @@ final class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
}
int getPhonebookPermissionChoice() {
- return mPhonebookPermissionChoice;
+ int permission = mDevice.getPhonebookAccessPermission();
+ if (permission == BluetoothDevice.ACCESS_ALLOWED) {
+ return ACCESS_ALLOWED;
+ } else if (permission == BluetoothDevice.ACCESS_REJECTED) {
+ return ACCESS_REJECTED;
+ }
+ return ACCESS_UNKNOWN;
}
void setPhonebookPermissionChoice(int permissionChoice) {
- // if user reject it, only save it when reject exceed limit.
- if (permissionChoice == ACCESS_REJECTED) {
- mPhonebookRejectedTimes++;
- savePhonebookRejectTimes();
- if (mPhonebookRejectedTimes < PERSIST_REJECTED_TIMES_LIMIT) {
- return;
- }
+ int permission = BluetoothDevice.ACCESS_UNKNOWN;
+ if (permissionChoice == ACCESS_ALLOWED) {
+ permission = BluetoothDevice.ACCESS_ALLOWED;
+ } else if (permissionChoice == ACCESS_REJECTED) {
+ permission = BluetoothDevice.ACCESS_REJECTED;
}
-
- mPhonebookPermissionChoice = permissionChoice;
-
- SharedPreferences.Editor editor =
- mContext.getSharedPreferences(PHONEBOOK_PREFS_NAME, Context.MODE_PRIVATE).edit();
- if (permissionChoice == ACCESS_UNKNOWN) {
- editor.remove(mDevice.getAddress());
- } else {
- editor.putInt(mDevice.getAddress(), permissionChoice);
- }
- editor.commit();
- }
-
- private void fetchPhonebookPermissionChoice() {
- SharedPreferences preference = mContext.getSharedPreferences(PHONEBOOK_PREFS_NAME,
- Context.MODE_PRIVATE);
- mPhonebookPermissionChoice = preference.getInt(mDevice.getAddress(),
- ACCESS_UNKNOWN);
+ mDevice.setPhonebookAccessPermission(permission);
}
- private void fetchPhonebookRejectTimes() {
- SharedPreferences preference = mContext.getSharedPreferences(PHONEBOOK_REJECT_TIMES,
- Context.MODE_PRIVATE);
- mPhonebookRejectedTimes = preference.getInt(mDevice.getAddress(), 0);
- }
+ // Migrates data from old data store (in Settings app's shared preferences) to new (in Bluetooth
+ // app's shared preferences).
+ private void migratePhonebookPermissionChoice() {
+ SharedPreferences preferences = mContext.getSharedPreferences(
+ "bluetooth_phonebook_permission", Context.MODE_PRIVATE);
+ if (!preferences.contains(mDevice.getAddress())) {
+ return;
+ }
- private void savePhonebookRejectTimes() {
- SharedPreferences.Editor editor =
- mContext.getSharedPreferences(PHONEBOOK_REJECT_TIMES,
- Context.MODE_PRIVATE).edit();
- if (mPhonebookRejectedTimes == 0) {
- editor.remove(mDevice.getAddress());
- } else {
- editor.putInt(mDevice.getAddress(), mPhonebookRejectedTimes);
+ if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_UNKNOWN) {
+ int oldPermission = preferences.getInt(mDevice.getAddress(), ACCESS_UNKNOWN);
+ if (oldPermission == ACCESS_ALLOWED) {
+ mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
+ } else if (oldPermission == ACCESS_REJECTED) {
+ mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
+ }
}
+
+ SharedPreferences.Editor editor = preferences.edit();
+ editor.remove(mDevice.getAddress());
editor.commit();
}
int getMessagePermissionChoice() {
- return mMessagePermissionChoice;
+ int permission = mDevice.getMessageAccessPermission();
+ if (permission == BluetoothDevice.ACCESS_ALLOWED) {
+ return ACCESS_ALLOWED;
+ } else if (permission == BluetoothDevice.ACCESS_REJECTED) {
+ return ACCESS_REJECTED;
+ }
+ return ACCESS_UNKNOWN;
}
void setMessagePermissionChoice(int permissionChoice) {
- // if user reject it, only save it when reject exceed limit.
- if (permissionChoice == ACCESS_REJECTED) {
- mMessageRejectedTimes++;
- saveMessageRejectTimes();
- if (mMessageRejectedTimes < PERSIST_REJECTED_TIMES_LIMIT) {
- return;
- }
+ int permission = BluetoothDevice.ACCESS_UNKNOWN;
+ if (permissionChoice == ACCESS_ALLOWED) {
+ permission = BluetoothDevice.ACCESS_ALLOWED;
+ } else if (permissionChoice == ACCESS_REJECTED) {
+ permission = BluetoothDevice.ACCESS_REJECTED;
}
+ mDevice.setMessageAccessPermission(permission);
+ }
- mMessagePermissionChoice = permissionChoice;
+ // Migrates data from old data store (in Settings app's shared preferences) to new (in Bluetooth
+ // app's shared preferences).
+ private void migrateMessagePermissionChoice() {
+ SharedPreferences preferences = mContext.getSharedPreferences(
+ "bluetooth_message_permission", Context.MODE_PRIVATE);
+ if (!preferences.contains(mDevice.getAddress())) {
+ return;
+ }
- SharedPreferences.Editor editor =
- mContext.getSharedPreferences(MESSAGE_PREFS_NAME, Context.MODE_PRIVATE).edit();
- if (permissionChoice == ACCESS_UNKNOWN) {
- editor.remove(mDevice.getAddress());
- } else {
- editor.putInt(mDevice.getAddress(), permissionChoice);
+ if (mDevice.getMessageAccessPermission() == BluetoothDevice.ACCESS_UNKNOWN) {
+ int oldPermission = preferences.getInt(mDevice.getAddress(), ACCESS_UNKNOWN);
+ if (oldPermission == ACCESS_ALLOWED) {
+ mDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
+ } else if (oldPermission == ACCESS_REJECTED) {
+ mDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_REJECTED);
+ }
}
+
+ SharedPreferences.Editor editor = preferences.edit();
+ editor.remove(mDevice.getAddress());
editor.commit();
}
- private void fetchMessagePermissionChoice() {
- SharedPreferences preference = mContext.getSharedPreferences(MESSAGE_PREFS_NAME,
- Context.MODE_PRIVATE);
- mMessagePermissionChoice = preference.getInt(mDevice.getAddress(),
- ACCESS_UNKNOWN);
+ /**
+ * @return Whether this rejection should persist.
+ */
+ boolean checkAndIncreaseMessageRejectionCount() {
+ if (mMessageRejectionCount < MESSAGE_REJECTION_COUNT_LIMIT_TO_PERSIST) {
+ mMessageRejectionCount++;
+ saveMessageRejectionCount();
+ }
+ return mMessageRejectionCount >= MESSAGE_REJECTION_COUNT_LIMIT_TO_PERSIST;
}
- private void fetchMessageRejectTimes() {
- SharedPreferences preference = mContext.getSharedPreferences(MESSAGE_REJECT_TIMES,
- Context.MODE_PRIVATE);
- mMessageRejectedTimes = preference.getInt(mDevice.getAddress(), 0);
+ private void fetchMessageRejectionCount() {
+ SharedPreferences preference = mContext.getSharedPreferences(
+ MESSAGE_REJECTION_COUNT_PREFS_NAME, Context.MODE_PRIVATE);
+ mMessageRejectionCount = preference.getInt(mDevice.getAddress(), 0);
}
- private void saveMessageRejectTimes() {
- SharedPreferences.Editor editor =
- mContext.getSharedPreferences(MESSAGE_REJECT_TIMES, Context.MODE_PRIVATE).edit();
- if (mMessageRejectedTimes == 0) {
+ private void saveMessageRejectionCount() {
+ SharedPreferences.Editor editor = mContext.getSharedPreferences(
+ MESSAGE_REJECTION_COUNT_PREFS_NAME, Context.MODE_PRIVATE).edit();
+ if (mMessageRejectionCount == 0) {
editor.remove(mDevice.getAddress());
} else {
- editor.putInt(mDevice.getAddress(), mMessageRejectedTimes);
+ editor.putInt(mDevice.getAddress(), mMessageRejectionCount);
}
editor.commit();
}
-
}
diff --git a/src/com/android/settings/bluetooth/CachedBluetoothDeviceManager.java b/src/com/android/settings/bluetooth/CachedBluetoothDeviceManager.java
index ff282cc..2b0e7f1 100755
--- a/src/com/android/settings/bluetooth/CachedBluetoothDeviceManager.java
+++ b/src/com/android/settings/bluetooth/CachedBluetoothDeviceManager.java
@@ -86,7 +86,9 @@ final class CachedBluetoothDeviceManager {
BluetoothDevice device) {
CachedBluetoothDevice newDevice = new CachedBluetoothDevice(mContext, adapter,
profileManager, device);
- mCachedDevices.add(newDevice);
+ synchronized (mCachedDevices) {
+ mCachedDevices.add(newDevice);
+ }
return newDevice;
}
@@ -110,6 +112,15 @@ final class CachedBluetoothDeviceManager {
return device.getAddress();
}
+ public synchronized void clearNonBondedDevices() {
+ for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
+ CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
+ if (cachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
+ mCachedDevices.remove(i);
+ }
+ }
+ }
+
public synchronized void onScanningStateChanged(boolean started) {
if (!started) return;
@@ -142,8 +153,8 @@ final class CachedBluetoothDeviceManager {
for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
if (cachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
- cachedDevice.setVisible(false);
- mCachedDevices.remove(i);
+ cachedDevice.setVisible(false);
+ mCachedDevices.remove(i);
} else {
// For bonded devices, we need to clear the connection status so that
// when BT is enabled next time, device connection status shall be retrieved
diff --git a/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java b/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java
index e2faf7f..e7208b5 100644
--- a/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java
+++ b/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java
@@ -25,9 +25,7 @@ import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
import android.util.Log;
-import com.android.settings.ProgressCategory;
import com.android.settings.RestrictedSettingsFragment;
-import com.android.settings.SettingsPreferenceFragment;
import java.util.Collection;
import java.util.WeakHashMap;
@@ -98,7 +96,7 @@ public abstract class DeviceListPreferenceFragment extends
@Override
public void onResume() {
super.onResume();
- if (mLocalManager == null) return;
+ if (mLocalManager == null || isUiRestricted()) return;
mLocalManager.setForegroundActivity(getActivity());
mLocalManager.getEventManager().registerCallback(this);
@@ -109,7 +107,9 @@ public abstract class DeviceListPreferenceFragment extends
@Override
public void onPause() {
super.onPause();
- if (mLocalManager == null) return;
+ if (mLocalManager == null || isUiRestricted()) {
+ return;
+ }
removeAllDevices();
mLocalManager.setForegroundActivity(null);
@@ -167,6 +167,12 @@ public abstract class DeviceListPreferenceFragment extends
}
void createDevicePreference(CachedBluetoothDevice cachedDevice) {
+ if (mDeviceListGroup == null) {
+ Log.w(TAG, "Trying to create a device preference before the list group/category "
+ + "exists!");
+ return;
+ }
+
BluetoothDevicePreference preference = new BluetoothDevicePreference(
getActivity(), cachedDevice);
diff --git a/src/com/android/settings/bluetooth/DevicePickerFragment.java b/src/com/android/settings/bluetooth/DevicePickerFragment.java
index 4b6a6b0..354d03c 100644
--- a/src/com/android/settings/bluetooth/DevicePickerFragment.java
+++ b/src/com/android/settings/bluetooth/DevicePickerFragment.java
@@ -17,7 +17,6 @@
package com.android.settings.bluetooth;
import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
-
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothDevicePicker;
@@ -25,6 +24,9 @@ import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.UserManager;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import com.android.settings.R;
@@ -33,6 +35,7 @@ import com.android.settings.R;
* connection management.
*/
public final class DevicePickerFragment extends DeviceListPreferenceFragment {
+ private static final int MENU_ID_REFRESH = Menu.FIRST;
public DevicePickerFragment() {
super(null /* Not tied to any user restrictions. */);
@@ -56,12 +59,36 @@ public final class DevicePickerFragment extends DeviceListPreferenceFragment {
}
@Override
+ void initDevicePreference(BluetoothDevicePreference preference) {
+ preference.setWidgetLayoutResource(R.layout.preference_empty_list);
+ }
+
+ @Override
+ 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);
+ super.onCreateOptionsMenu(menu, inflater);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case MENU_ID_REFRESH:
+ mLocalAdapter.startScanning(true);
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getActivity().setTitle(getString(R.string.device_picker));
UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
mStartScanOnResume = !um.hasUserRestriction(DISALLOW_CONFIG_BLUETOOTH)
&& (savedInstanceState == null); // don't start scan after rotation
+ setHasOptionsMenu(true);
}
@Override
diff --git a/src/com/android/settings/bluetooth/DeviceProfilesSettings.java b/src/com/android/settings/bluetooth/DeviceProfilesSettings.java
index 335d888..757535a 100755
--- a/src/com/android/settings/bluetooth/DeviceProfilesSettings.java
+++ b/src/com/android/settings/bluetooth/DeviceProfilesSettings.java
@@ -17,6 +17,7 @@
package com.android.settings.bluetooth;
import android.app.AlertDialog;
+import android.app.Fragment;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
@@ -36,8 +37,11 @@ import android.text.TextWatcher;
import android.app.Dialog;
import android.widget.Button;
import android.text.Editable;
+
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.search.Index;
+import com.android.settings.search.SearchIndexableRaw;
import java.util.HashMap;
@@ -50,14 +54,12 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
implements CachedBluetoothDevice.Callback, Preference.OnPreferenceChangeListener {
private static final String TAG = "DeviceProfilesSettings";
- private static final String KEY_RENAME_DEVICE = "rename_device";
private static final String KEY_PROFILE_CONTAINER = "profile_container";
private static final String KEY_UNPAIR = "unpair";
+ private static final String KEY_PBAP_SERVER = "PBAP Server";
- public static final String EXTRA_DEVICE = "device";
- private RenameEditTextPreference mRenameDeviceNamePref;
- private LocalBluetoothManager mManager;
private CachedBluetoothDevice mCachedDevice;
+ private LocalBluetoothManager mManager;
private LocalBluetoothProfileManager mProfileManager;
private PreferenceGroup mProfileContainer;
@@ -69,66 +71,19 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
private AlertDialog mDisconnectDialog;
private boolean mProfileGroupIsRemoved;
- private class RenameEditTextPreference implements TextWatcher{
- public void afterTextChanged(Editable s) {
- Dialog d = mDeviceNamePref.getDialog();
- if (d instanceof AlertDialog) {
- ((AlertDialog) d).getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(s.length() > 0);
- }
- }
-
- // TextWatcher interface
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- // not used
- }
-
- // TextWatcher interface
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- // not used
- }
- }
-
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- BluetoothDevice device;
- if (savedInstanceState != null) {
- device = savedInstanceState.getParcelable(EXTRA_DEVICE);
- } else {
- Bundle args = getArguments();
- device = args.getParcelable(EXTRA_DEVICE);
- }
-
addPreferencesFromResource(R.xml.bluetooth_device_advanced);
getPreferenceScreen().setOrderingAsAdded(false);
mProfileContainer = (PreferenceGroup) findPreference(KEY_PROFILE_CONTAINER);
- mDeviceNamePref = (EditTextPreference) findPreference(KEY_RENAME_DEVICE);
+ mProfileContainer.setLayoutResource(R.layout.bluetooth_preference_category);
- if (device == null) {
- Log.w(TAG, "Activity started without a remote Bluetooth device");
- finish();
- return; // TODO: test this failure path
- }
- mRenameDeviceNamePref = new RenameEditTextPreference();
mManager = LocalBluetoothManager.getInstance(getActivity());
CachedBluetoothDeviceManager deviceManager =
mManager.getCachedDeviceManager();
mProfileManager = mManager.getProfileManager();
- mCachedDevice = deviceManager.findDevice(device);
- if (mCachedDevice == null) {
- Log.w(TAG, "Device not found, cannot connect to it");
- finish();
- return; // TODO: test this failure path
- }
-
- String deviceName = mCachedDevice.getName();
- mDeviceNamePref.setSummary(deviceName);
- mDeviceNamePref.setText(deviceName);
- mDeviceNamePref.setOnPreferenceChangeListener(this);
-
- // Add a preference for each profile
- addPreferencesForProfiles();
}
@Override
@@ -138,12 +93,14 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
mDisconnectDialog.dismiss();
mDisconnectDialog = null;
}
+ if (mCachedDevice != null) {
+ mCachedDevice.unregisterCallback(this);
+ }
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
- outState.putParcelable(EXTRA_DEVICE, mCachedDevice.getDevice());
}
@Override
@@ -151,18 +108,13 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
super.onResume();
mManager.setForegroundActivity(getActivity());
- mCachedDevice.registerCallback(this);
- if(mCachedDevice.getBondState() == BluetoothDevice.BOND_NONE)
- finish();
- refresh();
- EditText et = mDeviceNamePref.getEditText();
- if (et != null) {
- et.addTextChangedListener(mRenameDeviceNamePref);
- Dialog d = mDeviceNamePref.getDialog();
- if (d instanceof AlertDialog) {
- Button b = ((AlertDialog) d).getButton(AlertDialog.BUTTON_POSITIVE);
- b.setEnabled(et.getText().length() > 0);
+ if (mCachedDevice != null) {
+ mCachedDevice.registerCallback(this);
+ if (mCachedDevice.getBondState() == BluetoothDevice.BOND_NONE) {
+ finish();
+ return;
}
+ refresh();
}
}
@@ -170,15 +122,45 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
public void onPause() {
super.onPause();
- mCachedDevice.unregisterCallback(this);
+ if (mCachedDevice != null) {
+ mCachedDevice.unregisterCallback(this);
+ }
+
mManager.setForegroundActivity(null);
}
+ public void setDevice(CachedBluetoothDevice cachedDevice) {
+ mCachedDevice = cachedDevice;
+
+ if (isResumed()) {
+ mCachedDevice.registerCallback(this);
+ addPreferencesForProfiles();
+ refresh();
+ }
+ }
+
private void addPreferencesForProfiles() {
+ mProfileContainer.removeAll();
for (LocalBluetoothProfile profile : mCachedDevice.getConnectableProfiles()) {
Preference pref = createProfilePreference(profile);
mProfileContainer.addPreference(pref);
}
+
+ final int pbapPermission = mCachedDevice.getPhonebookPermissionChoice();
+ // Only provide PBAP cabability if the client device has requested PBAP.
+ if (pbapPermission != CachedBluetoothDevice.ACCESS_UNKNOWN) {
+ final PbapServerProfile psp = mManager.getProfileManager().getPbapProfile();
+ CheckBoxPreference pbapPref = createProfilePreference(psp);
+ mProfileContainer.addPreference(pbapPref);
+ }
+
+ final MapProfile mapProfile = mManager.getProfileManager().getMapProfile();
+ final int mapPermission = mCachedDevice.getMessagePermissionChoice();
+ if (mapPermission != CachedBluetoothDevice.ACCESS_UNKNOWN) {
+ CheckBoxPreference mapPreference = createProfilePreference(mapProfile);
+ mProfileContainer.addPreference(mapPreference);
+ }
+
showOrHideProfileGroup();
}
@@ -203,6 +185,7 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
*/
private CheckBoxPreference createProfilePreference(LocalBluetoothProfile profile) {
CheckBoxPreference pref = new CheckBoxPreference(getActivity());
+ pref.setLayoutResource(R.layout.preference_start_widget);
pref.setKey(profile.toString());
pref.setTitle(profile.getNameResource(mCachedDevice.getDevice()));
pref.setPersistent(false);
@@ -214,28 +197,11 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
pref.setIcon(getResources().getDrawable(iconResource));
}
- /**
- * Gray out profile while connecting and disconnecting
- */
- pref.setEnabled(!mCachedDevice.isBusy());
-
refreshProfilePreference(pref, profile);
return pref;
}
- @Override
- public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
- String key = preference.getKey();
- if (key.equals(KEY_UNPAIR)) {
- unpairDevice();
- finish();
- return true;
- }
-
- return super.onPreferenceTreeClick(screen, preference);
- }
-
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (preference == mDeviceNamePref) {
mCachedDevice.setName((String) newValue);
@@ -253,13 +219,26 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
private void onProfileClicked(LocalBluetoothProfile profile, CheckBoxPreference profilePref) {
BluetoothDevice device = mCachedDevice.getDevice();
+ if (profilePref.getKey().equals(KEY_PBAP_SERVER)) {
+ final int newPermission = mCachedDevice.getPhonebookPermissionChoice()
+ == CachedBluetoothDevice.ACCESS_ALLOWED ? CachedBluetoothDevice.ACCESS_REJECTED
+ : CachedBluetoothDevice.ACCESS_ALLOWED;
+ mCachedDevice.setPhonebookPermissionChoice(newPermission);
+ profilePref.setChecked(newPermission == CachedBluetoothDevice.ACCESS_ALLOWED);
+ return;
+ }
+
int status = profile.getConnectionStatus(device);
boolean isConnected =
status == BluetoothProfile.STATE_CONNECTED;
- if (isConnected) {
- askDisconnect(getActivity(), profile);
+ if (profilePref.isChecked()) {
+ askDisconnect(mManager.getForegroundActivity(), profile);
} else {
+ if (profile instanceof MapProfile) {
+ mCachedDevice.setMessagePermissionChoice(BluetoothDevice.ACCESS_ALLOWED);
+ refreshProfilePreference(profilePref, profile);
+ }
if (profile.isPreferred(device)) {
// profile is preferred but not connected: disable auto-connect
profile.setPreferred(device, false);
@@ -291,6 +270,11 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
public void onClick(DialogInterface dialog, int which) {
device.disconnect(profile);
profile.setPreferred(device.getDevice(), false);
+ if (profile instanceof MapProfile) {
+ device.setMessagePermissionChoice(BluetoothDevice.ACCESS_REJECTED);
+ refreshProfilePreference(
+ (CheckBoxPreference)findPreference(profile.toString()), profile);
+ }
}
};
@@ -298,14 +282,16 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
mDisconnectDialog, disconnectListener, title, Html.fromHtml(message));
}
+ @Override
public void onDeviceAttributesChanged() {
refresh();
}
private void refresh() {
- String deviceName = mCachedDevice.getName();
- mDeviceNamePref.setSummary(deviceName);
- mDeviceNamePref.setText(deviceName);
+ final EditText deviceNameField = (EditText) getView().findViewById(R.id.name);
+ if (deviceNameField != null) {
+ deviceNameField.setText(mCachedDevice.getName());
+ }
refreshProfiles();
}
@@ -327,6 +313,7 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
mProfileContainer.removePreference(profilePref);
}
}
+
showOrHideProfileGroup();
}
@@ -334,12 +321,19 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
LocalBluetoothProfile profile) {
BluetoothDevice device = mCachedDevice.getDevice();
- /*
- * Gray out checkbox while connecting and disconnecting
- */
+ // Gray out checkbox while connecting and disconnecting.
profilePref.setEnabled(!mCachedDevice.isBusy());
- profilePref.setChecked(profile.isPreferred(device));
- profilePref.setSummary(profile.getSummaryResourceForDevice(device));
+
+ if (profile instanceof MapProfile) {
+ profilePref.setChecked(mCachedDevice.getMessagePermissionChoice()
+ == CachedBluetoothDevice.ACCESS_ALLOWED);
+ } else if (profile instanceof PbapServerProfile) {
+ // Handle PBAP specially.
+ profilePref.setChecked(mCachedDevice.getPhonebookPermissionChoice()
+ == CachedBluetoothDevice.ACCESS_ALLOWED);
+ } else {
+ profilePref.setChecked(profile.isPreferred(device));
+ }
}
private LocalBluetoothProfile getProfileOf(Preference pref) {
@@ -359,8 +353,4 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
private int getProfilePreferenceIndex(int profIndex) {
return mProfileContainer.getOrder() + profIndex * 10;
}
-
- private void unpairDevice() {
- mCachedDevice.unpair();
- }
}
diff --git a/src/com/android/settings/bluetooth/HeadsetProfile.java b/src/com/android/settings/bluetooth/HeadsetProfile.java
index 1caeb65..45b81ab 100755
--- a/src/com/android/settings/bluetooth/HeadsetProfile.java
+++ b/src/com/android/settings/bluetooth/HeadsetProfile.java
@@ -115,7 +115,7 @@ final class HeadsetProfile implements LocalBluetoothProfile {
List<BluetoothDevice> sinks = mService.getConnectedDevices();
if (sinks != null) {
for (BluetoothDevice sink : sinks) {
- mService.disconnect(sink);
+ Log.d(TAG,"Not disconnecting device = " + sink);
}
}
return mService.connect(device);
@@ -124,24 +124,33 @@ final class HeadsetProfile implements LocalBluetoothProfile {
public boolean disconnect(BluetoothDevice device) {
if (mService == null) return false;
List<BluetoothDevice> deviceList = mService.getConnectedDevices();
- if (!deviceList.isEmpty() && deviceList.get(0).equals(device)) {
- // Downgrade priority as user is disconnecting the headset.
- if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
- mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ if (!deviceList.isEmpty()) {
+ for (BluetoothDevice dev : deviceList) {
+ if (dev.equals(device)) {
+ if (V) Log.d(TAG,"Downgrade priority as user" +
+ "is disconnecting the headset");
+ // Downgrade priority as user is disconnecting the headset.
+ if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
+ mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ }
+ return mService.disconnect(device);
+ }
}
- return mService.disconnect(device);
- } else {
- return false;
}
+ return false;
}
public int getConnectionStatus(BluetoothDevice device) {
if (mService == null) return BluetoothProfile.STATE_DISCONNECTED;
List<BluetoothDevice> deviceList = mService.getConnectedDevices();
-
- return !deviceList.isEmpty() && deviceList.get(0).equals(device)
- ? mService.getConnectionState(device)
- : BluetoothProfile.STATE_DISCONNECTED;
+ if (!deviceList.isEmpty()){
+ for (BluetoothDevice dev : deviceList) {
+ if (dev.equals(device)) {
+ return mService.getConnectionState(device);
+ }
+ }
+ }
+ return BluetoothProfile.STATE_DISCONNECTED;
}
public boolean isPreferred(BluetoothDevice device) {
diff --git a/src/com/android/settings/bluetooth/HidProfile.java b/src/com/android/settings/bluetooth/HidProfile.java
index 8df2845..91e715d 100755
--- a/src/com/android/settings/bluetooth/HidProfile.java
+++ b/src/com/android/settings/bluetooth/HidProfile.java
@@ -169,7 +169,7 @@ final class HidProfile implements LocalBluetoothProfile {
public int getDrawableResource(BluetoothClass btClass) {
if (btClass == null) {
- return R.drawable.ic_bt_keyboard_hid;
+ return R.drawable.ic_lockscreen_ime;
}
return getHidClassDrawable(btClass);
}
@@ -178,7 +178,7 @@ final class HidProfile implements LocalBluetoothProfile {
switch (btClass.getDeviceClass()) {
case BluetoothClass.Device.PERIPHERAL_KEYBOARD:
case BluetoothClass.Device.PERIPHERAL_KEYBOARD_POINTING:
- return R.drawable.ic_bt_keyboard_hid;
+ return R.drawable.ic_lockscreen_ime;
case BluetoothClass.Device.PERIPHERAL_POINTING:
return R.drawable.ic_bt_pointing_hid;
default:
diff --git a/src/com/android/settings/bluetooth/LocalBluetoothProfileManager.java b/src/com/android/settings/bluetooth/LocalBluetoothProfileManager.java
index 8fff9648..2a6a759 100644
--- a/src/com/android/settings/bluetooth/LocalBluetoothProfileManager.java
+++ b/src/com/android/settings/bluetooth/LocalBluetoothProfileManager.java
@@ -309,6 +309,10 @@ final class LocalBluetoothProfileManager {
return mPbapProfile;
}
+ MapProfile getMapProfile(){
+ return mMapProfile;
+ }
+
/**
* Fill in a list of LocalBluetoothProfile objects that are supported by
* the local device and the remote device.
diff --git a/src/com/android/settings/bluetooth/MapProfile.java b/src/com/android/settings/bluetooth/MapProfile.java
index fb1b180..f47e24f 100644
--- a/src/com/android/settings/bluetooth/MapProfile.java
+++ b/src/com/android/settings/bluetooth/MapProfile.java
@@ -113,7 +113,7 @@ final class MapProfile implements LocalBluetoothProfile {
public boolean connect(BluetoothDevice device) {
if(V)Log.d(TAG,"connect() - should not get called");
- return true; // MAP never connects out
+ return false; // MAP never connects out
}
public boolean disconnect(BluetoothDevice device) {
diff --git a/src/com/android/settings/bluetooth/PbapServerProfile.java b/src/com/android/settings/bluetooth/PbapServerProfile.java
index 1f5ca32..87e51a5 100755
--- a/src/com/android/settings/bluetooth/PbapServerProfile.java
+++ b/src/com/android/settings/bluetooth/PbapServerProfile.java
@@ -118,16 +118,17 @@ final class PbapServerProfile implements LocalBluetoothProfile {
}
public int getNameResource(BluetoothDevice device) {
- return 0;
+ return R.string.bluetooth_profile_pbap;
}
public int getSummaryResourceForDevice(BluetoothDevice device) {
- return 0;
+ return R.string.bluetooth_profile_pbap_summary;
}
public int getDrawableResource(BluetoothClass btClass) {
- return 0;
+ return R.drawable.ic_bt_cellphone;
}
+
protected void finalize() {
if (V) Log.d(TAG, "finalize()");
if (mService != null) {
diff --git a/src/com/android/settings/bluetooth/Utils.java b/src/com/android/settings/bluetooth/Utils.java
index fb44d5a..e9230de 100755
--- a/src/com/android/settings/bluetooth/Utils.java
+++ b/src/com/android/settings/bluetooth/Utils.java
@@ -24,6 +24,8 @@ import android.content.DialogInterface;
import android.widget.Toast;
import com.android.settings.R;
+import com.android.settings.search.Index;
+import com.android.settings.search.SearchIndexableRaw;
/**
* Utils is a helper class that contains constants for various
@@ -93,7 +95,6 @@ final class Utils {
Context activity = manager.getForegroundActivity();
if(manager.isForegroundActivity()) {
new AlertDialog.Builder(activity)
- .setIconAttribute(android.R.attr.alertDialogIcon)
.setTitle(R.string.bluetooth_error_title)
.setMessage(message)
.setPositiveButton(android.R.string.ok, null)
@@ -102,4 +103,19 @@ final class Utils {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
}
}
+
+ /**
+ * Update the search Index for a specific class name and resources.
+ */
+ public static void updateSearchIndex(Context context, String className, String title,
+ String screenTitle, int iconResId, boolean enabled) {
+ SearchIndexableRaw data = new SearchIndexableRaw(context);
+ data.className = className;
+ data.title = title;
+ data.screenTitle = screenTitle;
+ data.iconResId = iconResId;
+ data.enabled = enabled;
+
+ Index.getInstance(context).updateFromSearchIndexableData(data);
+ }
}
diff --git a/src/com/android/settings/dashboard/DashboardCategory.java b/src/com/android/settings/dashboard/DashboardCategory.java
new file mode 100644
index 0000000..164d988
--- /dev/null
+++ b/src/com/android/settings/dashboard/DashboardCategory.java
@@ -0,0 +1,145 @@
+/*
+ * 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.dashboard;
+
+import android.content.res.Resources;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.Parcelable.Creator;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DashboardCategory implements Parcelable {
+
+ /**
+ * Default value for {@link com.android.settings.dashboard.DashboardCategory#id DashboardCategory.id}
+ * indicating that no identifier value is set. All other values (including those below -1)
+ * are valid.
+ */
+ public static final long CAT_ID_UNDEFINED = -1;
+
+ /**
+ * Identifier for this tile, to correlate with a new list when
+ * it is updated. The default value is
+ * {@link com.android.settings.dashboard.DashboardTile#TILE_ID_UNDEFINED}, meaning no id.
+ * @attr ref android.R.styleable#PreferenceHeader_id
+ */
+ public long id = CAT_ID_UNDEFINED;
+
+ /**
+ * Resource ID of title of the category that is shown to the user.
+ */
+ public int titleRes;
+
+ /**
+ * Title of the category that is shown to the user.
+ */
+ public CharSequence title;
+
+ /**
+ * List of the category's children
+ */
+ public List<DashboardTile> tiles = new ArrayList<DashboardTile>();
+
+
+ public DashboardCategory() {
+ // Empty
+ }
+
+ public void addTile(DashboardTile tile) {
+ tiles.add(tile);
+ }
+
+ public void addTile(int n, DashboardTile tile) {
+ tiles.add(n, tile);
+ }
+
+ public void removeTile(DashboardTile tile) {
+ tiles.remove(tile);
+ }
+
+ public void removeTile(int n) {
+ tiles.remove(n);
+ }
+
+ public int getTilesCount() {
+ return tiles.size();
+ }
+
+ public DashboardTile getTile(int n) {
+ return tiles.get(n);
+ }
+
+ /**
+ * Return the currently set title. If {@link #titleRes} is set,
+ * this resource is loaded from <var>res</var> and returned. Otherwise
+ * {@link #title} is returned.
+ */
+ public CharSequence getTitle(Resources res) {
+ if (titleRes != 0) {
+ return res.getText(titleRes);
+ }
+ return title;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(titleRes);
+ TextUtils.writeToParcel(title, dest, flags);
+
+ final int count = tiles.size();
+ dest.writeInt(count);
+
+ for (int n = 0; n < count; n++) {
+ DashboardTile tile = tiles.get(n);
+ tile.writeToParcel(dest, flags);
+ }
+ }
+
+ public void readFromParcel(Parcel in) {
+ titleRes = in.readInt();
+ title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+
+ final int count = in.readInt();
+
+ for (int n = 0; n < count; n++) {
+ DashboardTile tile = DashboardTile.CREATOR.createFromParcel(in);
+ tiles.add(tile);
+ }
+ }
+
+ DashboardCategory(Parcel in) {
+ readFromParcel(in);
+ }
+
+ public static final Creator<DashboardCategory> CREATOR = new Creator<DashboardCategory>() {
+ public DashboardCategory createFromParcel(Parcel source) {
+ return new DashboardCategory(source);
+ }
+
+ public DashboardCategory[] newArray(int size) {
+ return new DashboardCategory[size];
+ }
+ };
+}
diff --git a/src/com/android/settings/dashboard/DashboardContainerView.java b/src/com/android/settings/dashboard/DashboardContainerView.java
new file mode 100644
index 0000000..f009891
--- /dev/null
+++ b/src/com/android/settings/dashboard/DashboardContainerView.java
@@ -0,0 +1,142 @@
+/*
+ * 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.dashboard;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import com.android.settings.R;
+
+public class DashboardContainerView extends ViewGroup {
+
+ private float mCellGapX;
+ private float mCellGapY;
+
+ private int mNumRows;
+ private int mNumColumns;
+
+ public DashboardContainerView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ final Resources res = context.getResources();
+ mCellGapX = res.getDimension(R.dimen.dashboard_cell_gap_x);
+ mCellGapY = res.getDimension(R.dimen.dashboard_cell_gap_y);
+ mNumColumns = res.getInteger(R.integer.dashboard_num_columns);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int width = MeasureSpec.getSize(widthMeasureSpec);
+ final int availableWidth = (int) (width - getPaddingLeft() - getPaddingRight() -
+ (mNumColumns - 1) * mCellGapX);
+ float cellWidth = (float) Math.ceil(((float) availableWidth) / mNumColumns);
+ final int N = getChildCount();
+
+ int cellHeight = 0;
+ int cursor = 0;
+
+ for (int i = 0; i < N; ++i) {
+ DashboardTileView v = (DashboardTileView) getChildAt(i);
+ if (v.getVisibility() == View.GONE) {
+ continue;
+ }
+
+ ViewGroup.LayoutParams lp = v.getLayoutParams();
+ int colSpan = v.getColumnSpan();
+ lp.width = (int) ((colSpan * cellWidth) + (colSpan - 1) * mCellGapX);
+
+ // Measure the child
+ int newWidthSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width);
+ int newHeightSpec = getChildMeasureSpec(heightMeasureSpec, 0, lp.height);
+ v.measure(newWidthSpec, newHeightSpec);
+
+ // Save the cell height
+ if (cellHeight <= 0) {
+ cellHeight = v.getMeasuredHeight();
+ }
+
+ lp.height = cellHeight;
+
+ cursor += colSpan;
+ }
+
+ mNumRows = (int) Math.ceil((float) cursor / mNumColumns);
+ final int newHeight = (int) ((mNumRows * cellHeight) + ((mNumRows - 1) * mCellGapY)) +
+ getPaddingTop() + getPaddingBottom();
+
+ setMeasuredDimension(width, newHeight);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ final int N = getChildCount();
+ final boolean isLayoutRtl = isLayoutRtl();
+ final int width = getWidth();
+
+ int x = getPaddingStart();
+ int y = getPaddingTop();
+ int cursor = 0;
+
+ for (int i = 0; i < N; ++i) {
+ final DashboardTileView child = (DashboardTileView) getChildAt(i);
+ final ViewGroup.LayoutParams lp = child.getLayoutParams();
+ if (child.getVisibility() == GONE) {
+ continue;
+ }
+
+ final int col = cursor % mNumColumns;
+ final int colSpan = child.getColumnSpan();
+
+ final int childWidth = lp.width;
+ final int childHeight = lp.height;
+
+ int row = cursor / mNumColumns;
+
+ if (row == mNumRows - 1) {
+ child.setDividerVisibility(false);
+ }
+
+ // Push the item to the next row if it can't fit on this one
+ if ((col + colSpan) > mNumColumns) {
+ x = getPaddingStart();
+ y += childHeight + mCellGapY;
+ row++;
+ }
+
+ final int childLeft = (isLayoutRtl) ? width - x - childWidth : x;
+ final int childRight = childLeft + childWidth;
+
+ final int childTop = y;
+ final int childBottom = childTop + childHeight;
+
+ // Layout the container
+ child.layout(childLeft, childTop, childRight, childBottom);
+
+ // Offset the position by the cell gap or reset the position and cursor when we
+ // reach the end of the row
+ cursor += child.getColumnSpan();
+ if (cursor < (((row + 1) * mNumColumns))) {
+ x += childWidth + mCellGapX;
+ } else {
+ x = getPaddingStart();
+ y += childHeight + mCellGapY;
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/dashboard/DashboardSummary.java b/src/com/android/settings/dashboard/DashboardSummary.java
new file mode 100644
index 0000000..cf398d7
--- /dev/null
+++ b/src/com/android/settings/dashboard/DashboardSummary.java
@@ -0,0 +1,174 @@
+/*
+ * 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.dashboard;
+
+import android.app.Fragment;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+
+import java.util.List;
+
+public class DashboardSummary extends Fragment {
+ private static final String LOG_TAG = "DashboardSummary";
+
+ private LayoutInflater mLayoutInflater;
+ private ViewGroup mDashboard;
+
+ private static final int MSG_REBUILD_UI = 1;
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_REBUILD_UI: {
+ final Context context = getActivity();
+ rebuildUI(context);
+ } break;
+ }
+ }
+ };
+
+ private class HomePackageReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ rebuildUI(context);
+ }
+ }
+ private HomePackageReceiver mHomePackageReceiver = new HomePackageReceiver();
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ sendRebuildUI();
+
+ final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+ filter.addDataScheme("package");
+ getActivity().registerReceiver(mHomePackageReceiver, filter);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+
+ getActivity().unregisterReceiver(mHomePackageReceiver);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+
+ mLayoutInflater = inflater;
+
+ final View rootView = inflater.inflate(R.layout.dashboard, container, false);
+ mDashboard = (ViewGroup) rootView.findViewById(R.id.dashboard_container);
+
+ return rootView;
+ }
+
+ private void rebuildUI(Context context) {
+ if (!isAdded()) {
+ Log.w(LOG_TAG, "Cannot build the DashboardSummary UI yet as the Fragment is not added");
+ return;
+ }
+
+ long start = System.currentTimeMillis();
+ final Resources res = getResources();
+
+ mDashboard.removeAllViews();
+
+ List<DashboardCategory> categories =
+ ((SettingsActivity) context).getDashboardCategories(true);
+
+ final int count = categories.size();
+
+ for (int n = 0; n < count; n++) {
+ DashboardCategory category = categories.get(n);
+
+ View categoryView = mLayoutInflater.inflate(R.layout.dashboard_category, mDashboard,
+ false);
+
+ TextView categoryLabel = (TextView) categoryView.findViewById(R.id.category_title);
+ categoryLabel.setText(category.getTitle(res));
+
+ ViewGroup categoryContent =
+ (ViewGroup) categoryView.findViewById(R.id.category_content);
+
+ final int tilesCount = category.getTilesCount();
+ for (int i = 0; i < tilesCount; i++) {
+ DashboardTile tile = category.getTile(i);
+
+ DashboardTileView tileView = new DashboardTileView(context);
+ updateTileView(context, res, tile, tileView.getImageView(),
+ tileView.getTitleTextView(), tileView.getStatusTextView());
+
+ tileView.setTile(tile);
+
+ categoryContent.addView(tileView);
+ }
+
+ // Add the category
+ mDashboard.addView(categoryView);
+ }
+ long delta = System.currentTimeMillis() - start;
+ Log.d(LOG_TAG, "rebuildUI took: " + delta + " ms");
+ }
+
+ private void updateTileView(Context context, Resources res, DashboardTile tile,
+ ImageView tileIcon, TextView tileTextView, TextView statusTextView) {
+
+ if (tile.iconRes > 0) {
+ tileIcon.setImageResource(tile.iconRes);
+ } else {
+ tileIcon.setImageDrawable(null);
+ tileIcon.setBackground(null);
+ }
+
+ tileTextView.setText(tile.getTitle(res));
+
+ CharSequence summary = tile.getSummary(res);
+ if (!TextUtils.isEmpty(summary)) {
+ statusTextView.setVisibility(View.VISIBLE);
+ statusTextView.setText(summary);
+ } else {
+ statusTextView.setVisibility(View.GONE);
+ }
+ }
+
+ private void sendRebuildUI() {
+ if (!mHandler.hasMessages(MSG_REBUILD_UI)) {
+ mHandler.sendEmptyMessage(MSG_REBUILD_UI);
+ }
+ }
+}
diff --git a/src/com/android/settings/dashboard/DashboardTile.java b/src/com/android/settings/dashboard/DashboardTile.java
new file mode 100644
index 0000000..1f1d9c2
--- /dev/null
+++ b/src/com/android/settings/dashboard/DashboardTile.java
@@ -0,0 +1,177 @@
+/*
+ * 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.dashboard;
+
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * Description of a single dashboard tile that the user can select.
+ */
+public class DashboardTile implements Parcelable {
+ /**
+ * Default value for {@link com.android.settings.dashboard.DashboardTile#id DashboardTile.id}
+ * indicating that no identifier value is set. All other values (including those below -1)
+ * are valid.
+ */
+ public static final long TILE_ID_UNDEFINED = -1;
+
+ /**
+ * Identifier for this tile, to correlate with a new list when
+ * it is updated. The default value is
+ * {@link com.android.settings.dashboard.DashboardTile#TILE_ID_UNDEFINED}, meaning no id.
+ * @attr ref android.R.styleable#PreferenceHeader_id
+ */
+ public long id = TILE_ID_UNDEFINED;
+
+ /**
+ * Resource ID of title of the tile that is shown to the user.
+ * @attr ref android.R.styleable#PreferenceHeader_title
+ */
+ public int titleRes;
+
+ /**
+ * Title of the tile that is shown to the user.
+ * @attr ref android.R.styleable#PreferenceHeader_title
+ */
+ public CharSequence title;
+
+ /**
+ * Resource ID of optional summary describing what this tile controls.
+ * @attr ref android.R.styleable#PreferenceHeader_summary
+ */
+ public int summaryRes;
+
+ /**
+ * Optional summary describing what this tile controls.
+ * @attr ref android.R.styleable#PreferenceHeader_summary
+ */
+ public CharSequence summary;
+
+ /**
+ * Optional icon resource to show for this tile.
+ * @attr ref android.R.styleable#PreferenceHeader_icon
+ */
+ public int iconRes;
+
+ /**
+ * Full class name of the fragment to display when this tile is
+ * selected.
+ * @attr ref android.R.styleable#PreferenceHeader_fragment
+ */
+ public String fragment;
+
+ /**
+ * Optional arguments to supply to the fragment when it is
+ * instantiated.
+ */
+ public Bundle fragmentArguments;
+
+ /**
+ * Intent to launch when the preference is selected.
+ */
+ public Intent intent;
+
+ /**
+ * Optional additional data for use by subclasses of the activity
+ */
+ public Bundle extras;
+
+ public DashboardTile() {
+ // Empty
+ }
+
+ /**
+ * Return the currently set title. If {@link #titleRes} is set,
+ * this resource is loaded from <var>res</var> and returned. Otherwise
+ * {@link #title} is returned.
+ */
+ public CharSequence getTitle(Resources res) {
+ if (titleRes != 0) {
+ return res.getText(titleRes);
+ }
+ return title;
+ }
+
+ /**
+ * Return the currently set summary. If {@link #summaryRes} is set,
+ * this resource is loaded from <var>res</var> and returned. Otherwise
+ * {@link #summary} is returned.
+ */
+ public CharSequence getSummary(Resources res) {
+ if (summaryRes != 0) {
+ return res.getText(summaryRes);
+ }
+ return summary;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(id);
+ dest.writeInt(titleRes);
+ TextUtils.writeToParcel(title, dest, flags);
+ dest.writeInt(summaryRes);
+ TextUtils.writeToParcel(summary, dest, flags);
+ dest.writeInt(iconRes);
+ dest.writeString(fragment);
+ dest.writeBundle(fragmentArguments);
+ if (intent != null) {
+ dest.writeInt(1);
+ intent.writeToParcel(dest, flags);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeBundle(extras);
+ }
+
+ public void readFromParcel(Parcel in) {
+ id = in.readLong();
+ titleRes = in.readInt();
+ title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ summaryRes = in.readInt();
+ summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ iconRes = in.readInt();
+ fragment = in.readString();
+ fragmentArguments = in.readBundle();
+ if (in.readInt() != 0) {
+ intent = Intent.CREATOR.createFromParcel(in);
+ }
+ extras = in.readBundle();
+ }
+
+ DashboardTile(Parcel in) {
+ readFromParcel(in);
+ }
+
+ public static final Creator<DashboardTile> CREATOR = new Creator<DashboardTile>() {
+ public DashboardTile createFromParcel(Parcel source) {
+ return new DashboardTile(source);
+ }
+ public DashboardTile[] newArray(int size) {
+ return new DashboardTile[size];
+ }
+ };
+}
diff --git a/src/com/android/settings/dashboard/DashboardTileView.java b/src/com/android/settings/dashboard/DashboardTileView.java
new file mode 100644
index 0000000..a54217b
--- /dev/null
+++ b/src/com/android/settings/dashboard/DashboardTileView.java
@@ -0,0 +1,99 @@
+/*
+ * 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.dashboard;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import android.widget.ImageView;
+import android.widget.TextView;
+import com.android.settings.R;
+import com.android.settings.Utils;
+
+public class DashboardTileView extends FrameLayout implements View.OnClickListener {
+
+ private static final int DEFAULT_COL_SPAN = 1;
+
+ private ImageView mImageView;
+ private TextView mTitleTextView;
+ private TextView mStatusTextView;
+ private View mDivider;
+
+ private int mColSpan = DEFAULT_COL_SPAN;
+
+ private DashboardTile mTile;
+
+ public DashboardTileView(Context context) {
+ this(context, null);
+ }
+
+ public DashboardTileView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ final View view = LayoutInflater.from(context).inflate(R.layout.dashboard_tile, this);
+
+ mImageView = (ImageView) view.findViewById(R.id.icon);
+ mTitleTextView = (TextView) view.findViewById(R.id.title);
+ mStatusTextView = (TextView) view.findViewById(R.id.status);
+ mDivider = view.findViewById(R.id.tile_divider);
+
+ setOnClickListener(this);
+ setBackgroundResource(R.drawable.dashboard_tile_background);
+ setFocusable(true);
+ }
+
+ public TextView getTitleTextView() {
+ return mTitleTextView;
+ }
+
+ public TextView getStatusTextView() {
+ return mStatusTextView;
+ }
+
+ public ImageView getImageView() {
+ return mImageView;
+ }
+
+ public void setTile(DashboardTile tile) {
+ mTile = tile;
+ }
+
+ public void setDividerVisibility(boolean visible) {
+ mDivider.setVisibility(visible ? View.VISIBLE : View.GONE);
+ }
+
+ void setColumnSpan(int span) {
+ mColSpan = span;
+ }
+
+ int getColumnSpan() {
+ return mColSpan;
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (mTile.fragment != null) {
+ Utils.startWithFragment(getContext(), mTile.fragment, mTile.fragmentArguments, null, 0,
+ mTile.titleRes, mTile.getTitle(getResources()));
+ } else if (mTile.intent != null) {
+ getContext().startActivity(mTile.intent);
+ }
+ }
+}
diff --git a/src/com/android/settings/dashboard/NoHomeDialogFragment.java b/src/com/android/settings/dashboard/NoHomeDialogFragment.java
new file mode 100644
index 0000000..a795cc9
--- /dev/null
+++ b/src/com/android/settings/dashboard/NoHomeDialogFragment.java
@@ -0,0 +1,39 @@
+/*
+ * 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.dashboard;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.os.Bundle;
+import com.android.settings.R;
+
+public class NoHomeDialogFragment extends DialogFragment {
+ public static void show(Activity parent) {
+ final NoHomeDialogFragment dialog = new NoHomeDialogFragment();
+ dialog.show(parent.getFragmentManager(), null);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ return new AlertDialog.Builder(getActivity())
+ .setMessage(R.string.only_one_home_message)
+ .setPositiveButton(android.R.string.ok, null)
+ .create();
+ }
+}
diff --git a/src/com/android/settings/dashboard/SearchResultsSummary.java b/src/com/android/settings/dashboard/SearchResultsSummary.java
new file mode 100644
index 0000000..1ae0c25
--- /dev/null
+++ b/src/com/android/settings/dashboard/SearchResultsSummary.java
@@ -0,0 +1,629 @@
+/*
+ * 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.dashboard;
+
+import android.app.Fragment;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.SearchView;
+import android.widget.TextView;
+import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.Utils;
+import com.android.settings.search.Index;
+
+import java.util.HashMap;
+
+public class SearchResultsSummary extends Fragment {
+
+ private static final String LOG_TAG = "SearchResultsSummary";
+
+ private static final String EMPTY_QUERY = "";
+ private static char ELLIPSIS = '\u2026';
+
+ private static final String SAVE_KEY_SHOW_RESULTS = ":settings:show_results";
+
+ private SearchView mSearchView;
+
+ private ListView mResultsListView;
+ private SearchResultsAdapter mResultsAdapter;
+ private UpdateSearchResultsTask mUpdateSearchResultsTask;
+
+ private ListView mSuggestionsListView;
+ private SuggestionsAdapter mSuggestionsAdapter;
+ private UpdateSuggestionsTask mUpdateSuggestionsTask;
+
+ private ViewGroup mLayoutSuggestions;
+ private ViewGroup mLayoutResults;
+
+ private String mQuery;
+
+ private boolean mShowResults;
+
+ /**
+ * A basic AsyncTask for updating the query results cursor
+ */
+ private class UpdateSearchResultsTask extends AsyncTask<String, Void, Cursor> {
+ @Override
+ protected Cursor doInBackground(String... params) {
+ return Index.getInstance(getActivity()).search(params[0]);
+ }
+
+ @Override
+ protected void onPostExecute(Cursor cursor) {
+ if (!isCancelled()) {
+ setResultsCursor(cursor);
+ setResultsVisibility(cursor.getCount() > 0);
+ } else if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+
+ /**
+ * A basic AsyncTask for updating the suggestions cursor
+ */
+ private class UpdateSuggestionsTask extends AsyncTask<String, Void, Cursor> {
+ @Override
+ protected Cursor doInBackground(String... params) {
+ return Index.getInstance(getActivity()).getSuggestions(params[0]);
+ }
+
+ @Override
+ protected void onPostExecute(Cursor cursor) {
+ if (!isCancelled()) {
+ setSuggestionsCursor(cursor);
+ setSuggestionsVisibility(cursor.getCount() > 0);
+ } else if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mResultsAdapter = new SearchResultsAdapter(getActivity());
+ mSuggestionsAdapter = new SuggestionsAdapter(getActivity());
+
+ if (savedInstanceState != null) {
+ mShowResults = savedInstanceState.getBoolean(SAVE_KEY_SHOW_RESULTS);
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ outState.putBoolean(SAVE_KEY_SHOW_RESULTS, mShowResults);
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+
+ clearSuggestions();
+ clearResults();
+ }
+
+ @Override
+ public void onDestroy() {
+ mResultsListView = null;
+ mResultsAdapter = null;
+ mUpdateSearchResultsTask = null;
+
+ mSuggestionsListView = null;
+ mSuggestionsAdapter = null;
+ mUpdateSuggestionsTask = null;
+
+ mSearchView = null;
+
+ super.onDestroy();
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+
+ final View view = inflater.inflate(R.layout.search_panel, container, false);
+
+ mLayoutSuggestions = (ViewGroup) view.findViewById(R.id.layout_suggestions);
+ mLayoutResults = (ViewGroup) view.findViewById(R.id.layout_results);
+
+ mResultsListView = (ListView) view.findViewById(R.id.list_results);
+ mResultsListView.setAdapter(mResultsAdapter);
+ mResultsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ // We have a header, so we need to decrement the position by one
+ position--;
+
+ // Some Monkeys could create a case where they were probably clicking on the
+ // List Header and thus the position passed was "0" and then by decrement was "-1"
+ if (position < 0) {
+ return;
+ }
+
+ final Cursor cursor = mResultsAdapter.mCursor;
+ cursor.moveToPosition(position);
+
+ final String className = cursor.getString(Index.COLUMN_INDEX_CLASS_NAME);
+ final String screenTitle = cursor.getString(Index.COLUMN_INDEX_SCREEN_TITLE);
+ final String action = cursor.getString(Index.COLUMN_INDEX_INTENT_ACTION);
+ final String key = cursor.getString(Index.COLUMN_INDEX_KEY);
+
+ final SettingsActivity sa = (SettingsActivity) getActivity();
+ sa.needToRevertToInitialFragment();
+
+ if (TextUtils.isEmpty(action)) {
+ Bundle args = new Bundle();
+ args.putString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY, key);
+
+ Utils.startWithFragment(sa, className, args, null, 0, -1, screenTitle);
+ } else {
+ final Intent intent = new Intent(action);
+
+ final String targetPackage = cursor.getString(
+ Index.COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE);
+ final String targetClass = cursor.getString(
+ Index.COLUMN_INDEX_INTENT_ACTION_TARGET_CLASS);
+ if (!TextUtils.isEmpty(targetPackage) && !TextUtils.isEmpty(targetClass)) {
+ final ComponentName component =
+ new ComponentName(targetPackage, targetClass);
+ intent.setComponent(component);
+ }
+ intent.putExtra(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY, key);
+
+ sa.startActivity(intent);
+ }
+
+ saveQueryToDatabase();
+ }
+ });
+ mResultsListView.addHeaderView(
+ LayoutInflater.from(getActivity()).inflate(
+ R.layout.search_panel_results_header, mResultsListView, false),
+ null, false);
+
+ mSuggestionsListView = (ListView) view.findViewById(R.id.list_suggestions);
+ mSuggestionsListView.setAdapter(mSuggestionsAdapter);
+ mSuggestionsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ // We have a header, so we need to decrement the position by one
+ position--;
+ // Some Monkeys could create a case where they were probably clicking on the
+ // List Header and thus the position passed was "0" and then by decrement was "-1"
+ if (position < 0) {
+ return;
+ }
+ final Cursor cursor = mSuggestionsAdapter.mCursor;
+ cursor.moveToPosition(position);
+
+ mShowResults = true;
+ mQuery = cursor.getString(0);
+ mSearchView.setQuery(mQuery, false);
+ }
+ });
+ mSuggestionsListView.addHeaderView(
+ LayoutInflater.from(getActivity()).inflate(
+ R.layout.search_panel_suggestions_header, mSuggestionsListView, false),
+ null, false);
+
+ return view;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ if (!mShowResults) {
+ showSomeSuggestions();
+ }
+ }
+
+ public void setSearchView(SearchView searchView) {
+ mSearchView = searchView;
+ }
+
+ private void setSuggestionsVisibility(boolean visible) {
+ if (mLayoutSuggestions != null) {
+ mLayoutSuggestions.setVisibility(visible ? View.VISIBLE : View.GONE);
+ }
+ }
+
+ private void setResultsVisibility(boolean visible) {
+ if (mLayoutResults != null) {
+ mLayoutResults.setVisibility(visible ? View.VISIBLE : View.GONE);
+ }
+ }
+
+ private void saveQueryToDatabase() {
+ Index.getInstance(getActivity()).addSavedQuery(mQuery);
+ }
+
+ public boolean onQueryTextSubmit(String query) {
+ mQuery = getFilteredQueryString(query);
+ mShowResults = true;
+ setSuggestionsVisibility(false);
+ updateSearchResults();
+ saveQueryToDatabase();
+ return true;
+ }
+
+ public boolean onQueryTextChange(String query) {
+ final String newQuery = getFilteredQueryString(query);
+
+ mQuery = newQuery;
+
+ if (TextUtils.isEmpty(mQuery)) {
+ mShowResults = false;
+ setResultsVisibility(false);
+ updateSuggestions();
+ } else {
+ mShowResults = true;
+ setSuggestionsVisibility(false);
+ updateSearchResults();
+ }
+
+ return true;
+ }
+
+ public void showSomeSuggestions() {
+ setResultsVisibility(false);
+ mQuery = EMPTY_QUERY;
+ updateSuggestions();
+ }
+
+ private void clearSuggestions() {
+ if (mUpdateSuggestionsTask != null) {
+ mUpdateSuggestionsTask.cancel(false);
+ mUpdateSuggestionsTask = null;
+ }
+ setSuggestionsCursor(null);
+ }
+
+ private void setSuggestionsCursor(Cursor cursor) {
+ if (mSuggestionsAdapter == null) {
+ return;
+ }
+ Cursor oldCursor = mSuggestionsAdapter.swapCursor(cursor);
+ if (oldCursor != null) {
+ oldCursor.close();
+ }
+ }
+
+ private void clearResults() {
+ if (mUpdateSearchResultsTask != null) {
+ mUpdateSearchResultsTask.cancel(false);
+ mUpdateSearchResultsTask = null;
+ }
+ setResultsCursor(null);
+ }
+
+ private void setResultsCursor(Cursor cursor) {
+ if (mResultsAdapter == null) {
+ return;
+ }
+ Cursor oldCursor = mResultsAdapter.swapCursor(cursor);
+ if (oldCursor != null) {
+ oldCursor.close();
+ }
+ }
+
+ private String getFilteredQueryString(CharSequence query) {
+ if (query == null) {
+ return null;
+ }
+ final StringBuilder filtered = new StringBuilder();
+ for (int n = 0; n < query.length(); n++) {
+ char c = query.charAt(n);
+ if (!Character.isLetterOrDigit(c) && !Character.isSpaceChar(c)) {
+ continue;
+ }
+ filtered.append(c);
+ }
+ return filtered.toString();
+ }
+
+ private void clearAllTasks() {
+ if (mUpdateSearchResultsTask != null) {
+ mUpdateSearchResultsTask.cancel(false);
+ mUpdateSearchResultsTask = null;
+ }
+ if (mUpdateSuggestionsTask != null) {
+ mUpdateSuggestionsTask.cancel(false);
+ mUpdateSuggestionsTask = null;
+ }
+ }
+
+ private void updateSuggestions() {
+ clearAllTasks();
+ if (mQuery == null) {
+ setSuggestionsCursor(null);
+ } else {
+ mUpdateSuggestionsTask = new UpdateSuggestionsTask();
+ mUpdateSuggestionsTask.execute(mQuery);
+ }
+ }
+
+ private void updateSearchResults() {
+ clearAllTasks();
+ if (TextUtils.isEmpty(mQuery)) {
+ setResultsVisibility(false);
+ setResultsCursor(null);
+ } else {
+ mUpdateSearchResultsTask = new UpdateSearchResultsTask();
+ mUpdateSearchResultsTask.execute(mQuery);
+ }
+ }
+
+ private static class SuggestionItem {
+ public String query;
+
+ public SuggestionItem(String query) {
+ this.query = query;
+ }
+ }
+
+ private static class SuggestionsAdapter extends BaseAdapter {
+
+ private static final int COLUMN_SUGGESTION_QUERY = 0;
+ private static final int COLUMN_SUGGESTION_TIMESTAMP = 1;
+
+ private Context mContext;
+ private Cursor mCursor;
+ private LayoutInflater mInflater;
+ private boolean mDataValid = false;
+
+ public SuggestionsAdapter(Context context) {
+ mContext = context;
+ mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mDataValid = false;
+ }
+
+ public Cursor swapCursor(Cursor newCursor) {
+ if (newCursor == mCursor) {
+ return null;
+ }
+ Cursor oldCursor = mCursor;
+ mCursor = newCursor;
+ if (newCursor != null) {
+ mDataValid = true;
+ notifyDataSetChanged();
+ } else {
+ mDataValid = false;
+ notifyDataSetInvalidated();
+ }
+ return oldCursor;
+ }
+
+ @Override
+ public int getCount() {
+ if (!mDataValid || mCursor == null || mCursor.isClosed()) return 0;
+ return mCursor.getCount();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ if (mDataValid && mCursor.moveToPosition(position)) {
+ final String query = mCursor.getString(COLUMN_SUGGESTION_QUERY);
+
+ return new SuggestionItem(query);
+ }
+ return null;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return 0;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (!mDataValid && convertView == null) {
+ throw new IllegalStateException(
+ "this should only be called when the cursor is valid");
+ }
+ if (!mCursor.moveToPosition(position)) {
+ throw new IllegalStateException("couldn't move cursor to position " + position);
+ }
+
+ View view;
+
+ if (convertView == null) {
+ view = mInflater.inflate(R.layout.search_suggestion_item, parent, false);
+ } else {
+ view = convertView;
+ }
+
+ TextView query = (TextView) view.findViewById(R.id.title);
+
+ SuggestionItem item = (SuggestionItem) getItem(position);
+ query.setText(item.query);
+
+ return view;
+ }
+ }
+
+ private static class SearchResult {
+ public Context context;
+ public String title;
+ public String summaryOn;
+ public String summaryOff;
+ public String entries;
+ public int iconResId;
+ public String key;
+
+ public SearchResult(Context context, String title, String summaryOn, String summaryOff,
+ String entries, int iconResId, String key) {
+ this.context = context;
+ this.title = title;
+ this.summaryOn = summaryOn;
+ this.summaryOff = summaryOff;
+ this.entries = entries;
+ this.iconResId = iconResId;
+ this.key = key;
+ }
+ }
+
+ private static class SearchResultsAdapter extends BaseAdapter {
+
+ private Context mContext;
+ private Cursor mCursor;
+ private LayoutInflater mInflater;
+ private boolean mDataValid;
+ private HashMap<String, Context> mContextMap = new HashMap<String, Context>();
+
+ private static final String PERCENT_RECLACE = "%s";
+ private static final String DOLLAR_REPLACE = "$s";
+
+ public SearchResultsAdapter(Context context) {
+ mContext = context;
+ mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mDataValid = false;
+ }
+
+ public Cursor swapCursor(Cursor newCursor) {
+ if (newCursor == mCursor) {
+ return null;
+ }
+ Cursor oldCursor = mCursor;
+ mCursor = newCursor;
+ if (newCursor != null) {
+ mDataValid = true;
+ notifyDataSetChanged();
+ } else {
+ mDataValid = false;
+ notifyDataSetInvalidated();
+ }
+ return oldCursor;
+ }
+
+ @Override
+ public int getCount() {
+ if (!mDataValid || mCursor == null || mCursor.isClosed()) return 0;
+ return mCursor.getCount();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ if (mDataValid && mCursor.moveToPosition(position)) {
+ final String title = mCursor.getString(Index.COLUMN_INDEX_TITLE);
+ final String summaryOn = mCursor.getString(Index.COLUMN_INDEX_SUMMARY_ON);
+ final String summaryOff = mCursor.getString(Index.COLUMN_INDEX_SUMMARY_OFF);
+ final String entries = mCursor.getString(Index.COLUMN_INDEX_ENTRIES);
+ final String iconResStr = mCursor.getString(Index.COLUMN_INDEX_ICON);
+ final String className = mCursor.getString(
+ Index.COLUMN_INDEX_CLASS_NAME);
+ final String packageName = mCursor.getString(
+ Index.COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE);
+ final String key = mCursor.getString(
+ Index.COLUMN_INDEX_KEY);
+
+ Context packageContext;
+ if (TextUtils.isEmpty(className) && !TextUtils.isEmpty(packageName)) {
+ packageContext = mContextMap.get(packageName);
+ if (packageContext == null) {
+ try {
+ packageContext = mContext.createPackageContext(packageName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(LOG_TAG, "Cannot create Context for package: " + packageName);
+ return null;
+ }
+ mContextMap.put(packageName, packageContext);
+ }
+ } else {
+ packageContext = mContext;
+ }
+
+ final int iconResId = TextUtils.isEmpty(iconResStr) ?
+ R.drawable.empty_icon : Integer.parseInt(iconResStr);
+
+ return new SearchResult(packageContext, title, summaryOn, summaryOff,
+ entries, iconResId, key);
+ }
+ return null;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return 0;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (!mDataValid && convertView == null) {
+ throw new IllegalStateException(
+ "this should only be called when the cursor is valid");
+ }
+ if (!mCursor.moveToPosition(position)) {
+ throw new IllegalStateException("couldn't move cursor to position " + position);
+ }
+
+ View view;
+ TextView textTitle;
+ ImageView imageView;
+
+ if (convertView == null) {
+ view = mInflater.inflate(R.layout.search_result_item, parent, false);
+ } else {
+ view = convertView;
+ }
+
+ textTitle = (TextView) view.findViewById(R.id.title);
+ imageView = (ImageView) view.findViewById(R.id.icon);
+
+ final SearchResult result = (SearchResult) getItem(position);
+ textTitle.setText(result.title);
+
+ if (result.iconResId != R.drawable.empty_icon) {
+ final Context packageContext = result.context;
+ final Drawable drawable;
+ try {
+ drawable = packageContext.getDrawable(result.iconResId);
+ imageView.setImageDrawable(drawable);
+ } catch (Resources.NotFoundException nfe) {
+ // Not much we can do except logging
+ Log.e(LOG_TAG, "Cannot load Drawable for " + result.title);
+ }
+ } else {
+ imageView.setImageDrawable(null);
+ imageView.setBackgroundResource(R.drawable.empty_icon);
+ }
+
+ return view;
+ }
+ }
+}
diff --git a/src/com/android/settings/deviceinfo/Memory.java b/src/com/android/settings/deviceinfo/Memory.java
index 999611d..1958f38 100644
--- a/src/com/android/settings/deviceinfo/Memory.java
+++ b/src/com/android/settings/deviceinfo/Memory.java
@@ -40,7 +40,6 @@ import android.os.storage.StorageEventListener;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
import android.preference.Preference;
-import android.preference.PreferenceActivity;
import android.preference.PreferenceScreen;
import android.util.Log;
import android.view.Menu;
@@ -49,8 +48,12 @@ import android.view.MenuItem;
import android.widget.Toast;
import com.android.settings.R;
+import com.android.settings.SettingsActivity;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.Utils;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.search.Indexable;
+import com.android.settings.search.SearchIndexableRaw;
import com.google.android.collect.Lists;
import java.util.ArrayList;
@@ -60,7 +63,7 @@ import java.util.List;
* Panel showing storage usage on disk for known {@link StorageVolume} returned
* by {@link StorageManager}. Calculates and displays usage of data types.
*/
-public class Memory extends SettingsPreferenceFragment {
+public class Memory extends SettingsPreferenceFragment implements Indexable {
private static final String TAG = "MemorySettings";
private static final String TAG_CONFIRM_CLEAR_CACHE = "confirmClearCache";
@@ -186,14 +189,13 @@ public class Memory extends SettingsPreferenceFragment {
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.storage_usb:
- if (getActivity() instanceof PreferenceActivity) {
- ((PreferenceActivity) getActivity()).startPreferencePanel(
+ if (getActivity() instanceof SettingsActivity) {
+ ((SettingsActivity) getActivity()).startPreferencePanel(
UsbSettings.class.getCanonicalName(),
- null,
- R.string.storage_title_usb, null,
- this, 0);
+ null, R.string.storage_title_usb, null, this, 0);
} else {
- startFragment(this, UsbSettings.class.getCanonicalName(), -1, null);
+ startFragment(this, UsbSettings.class.getCanonicalName(),
+ R.string.storage_title_usb, -1, null);
}
return true;
}
@@ -424,4 +426,78 @@ public class Memory extends SettingsPreferenceFragment {
return builder.create();
}
}
+
+ /**
+ * Enable indexing of searchable data
+ */
+ public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider() {
+ @Override
+ public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
+ final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>();
+
+ SearchIndexableRaw data = new SearchIndexableRaw(context);
+ data.title = context.getString(R.string.storage_settings);
+ data.screenTitle = context.getString(R.string.storage_settings);
+ result.add(data);
+
+ data = new SearchIndexableRaw(context);
+ data.title = context.getString(R.string.internal_storage);
+ data.screenTitle = context.getString(R.string.storage_settings);
+ result.add(data);
+
+ data = new SearchIndexableRaw(context);
+ final StorageVolume[] storageVolumes = StorageManager.from(context).getVolumeList();
+ for (StorageVolume volume : storageVolumes) {
+ if (!volume.isEmulated()) {
+ data.title = volume.getDescription(context);
+ data.screenTitle = context.getString(R.string.storage_settings);
+ result.add(data);
+ }
+ }
+
+ data = new SearchIndexableRaw(context);
+ data.title = context.getString(R.string.memory_size);
+ data.screenTitle = context.getString(R.string.storage_settings);
+ result.add(data);
+
+ data = new SearchIndexableRaw(context);
+ data.title = context.getString(R.string.memory_available);
+ data.screenTitle = context.getString(R.string.storage_settings);
+ result.add(data);
+
+ data = new SearchIndexableRaw(context);
+ data.title = context.getString(R.string.memory_apps_usage);
+ data.screenTitle = context.getString(R.string.storage_settings);
+ result.add(data);
+
+ data = new SearchIndexableRaw(context);
+ data.title = context.getString(R.string.memory_dcim_usage);
+ data.screenTitle = context.getString(R.string.storage_settings);
+ result.add(data);
+
+ data = new SearchIndexableRaw(context);
+ data.title = context.getString(R.string.memory_music_usage);
+ data.screenTitle = context.getString(R.string.storage_settings);
+ result.add(data);
+
+ data = new SearchIndexableRaw(context);
+ data.title = context.getString(R.string.memory_downloads_usage);
+ data.screenTitle = context.getString(R.string.storage_settings);
+ result.add(data);
+
+ data = new SearchIndexableRaw(context);
+ data.title = context.getString(R.string.memory_media_cache_usage);
+ data.screenTitle = context.getString(R.string.storage_settings);
+ result.add(data);
+
+ data = new SearchIndexableRaw(context);
+ data.title = context.getString(R.string.memory_media_misc_usage);
+ data.screenTitle = context.getString(R.string.storage_settings);
+ result.add(data);
+
+ return result;
+ }
+ };
+
}
diff --git a/src/com/android/settings/deviceinfo/Status.java b/src/com/android/settings/deviceinfo/Status.java
index b27b241..bb71a02 100644
--- a/src/com/android/settings/deviceinfo/Status.java
+++ b/src/com/android/settings/deviceinfo/Status.java
@@ -18,6 +18,7 @@ package com.android.settings.deviceinfo;
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
+import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -35,13 +36,16 @@ import android.os.SystemProperties;
import android.os.UserHandle;
import android.preference.Preference;
import android.preference.PreferenceActivity;
-import android.preference.PreferenceScreen;
import android.telephony.CellBroadcastMessage;
import android.telephony.PhoneNumberUtils;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ListAdapter;
+import android.widget.Toast;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
@@ -324,7 +328,7 @@ public class Status extends PreferenceActivity {
}
}
- String rawNumber = mPhone.getLine1Number(); // may be null or empty
+ String rawNumber = mTelephonyManager.getLine1Number(); // may be null or empty
String formattedNumber = null;
if (!TextUtils.isEmpty(rawNumber)) {
formattedNumber = PhoneNumberUtils.formatNumber(rawNumber);
@@ -364,6 +368,27 @@ public class Status extends PreferenceActivity {
} else {
removePreferenceFromScreen(KEY_SERIAL_NUMBER);
}
+
+ // Make every pref on this screen copy its data to the clipboard on longpress.
+ // Super convenient for capturing the IMEI, MAC addr, serial, etc.
+ getListView().setOnItemLongClickListener(
+ new AdapterView.OnItemLongClickListener() {
+ @Override
+ public boolean onItemLongClick(AdapterView<?> parent, View view,
+ int position, long id) {
+ ListAdapter listAdapter = (ListAdapter) parent.getAdapter();
+ Preference pref = (Preference) listAdapter.getItem(position);
+
+ ClipboardManager cm = (ClipboardManager)
+ getSystemService(Context.CLIPBOARD_SERVICE);
+ cm.setText(pref.getSummary());
+ Toast.makeText(
+ Status.this,
+ com.android.internal.R.string.text_copied,
+ Toast.LENGTH_SHORT).show();
+ return true;
+ }
+ });
}
@Override
@@ -521,6 +546,7 @@ public class Status extends PreferenceActivity {
if ((ServiceState.STATE_OUT_OF_SERVICE == state) ||
(ServiceState.STATE_POWER_OFF == state)) {
mSignalStrength.setSummary("0");
+ return;
}
int signalDbm = mPhoneStateReceiver.getSignalStrengthDbm();
diff --git a/src/com/android/settings/deviceinfo/StorageVolumePreferenceCategory.java b/src/com/android/settings/deviceinfo/StorageVolumePreferenceCategory.java
index 29b1e92..a98f8d9 100644
--- a/src/com/android/settings/deviceinfo/StorageVolumePreferenceCategory.java
+++ b/src/com/android/settings/deviceinfo/StorageVolumePreferenceCategory.java
@@ -38,6 +38,7 @@ import android.provider.MediaStore;
import android.text.format.Formatter;
import com.android.settings.R;
+import com.android.settings.Settings;
import com.android.settings.deviceinfo.StorageMeasurement.MeasurementDetails;
import com.android.settings.deviceinfo.StorageMeasurement.MeasurementReceiver;
import com.google.android.collect.Lists;
@@ -252,6 +253,9 @@ public class StorageVolumePreferenceCategory extends PreferenceCategory {
mMountTogglePreference.setEnabled(true);
mMountTogglePreference.setTitle(mResources.getString(R.string.sd_eject));
mMountTogglePreference.setSummary(mResources.getString(R.string.sd_eject_summary));
+ addPreference(mUsageBarPreference);
+ addPreference(mItemTotal);
+ addPreference(mItemAvailable);
} else {
if (Environment.MEDIA_UNMOUNTED.equals(state) || Environment.MEDIA_NOFS.equals(state)
|| Environment.MEDIA_UNMOUNTABLE.equals(state)) {
@@ -425,8 +429,7 @@ public class StorageVolumePreferenceCategory extends PreferenceCategory {
intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, mVolume);
} else if (pref == mItemApps) {
intent = new Intent(Intent.ACTION_MANAGE_PACKAGE_STORAGE);
- intent.setClass(getContext(),
- com.android.settings.Settings.ManageApplicationsActivity.class);
+ intent.setClass(getContext(), Settings.ManageApplicationsActivity.class);
} else if (pref == mItemDownloads) {
intent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS).putExtra(
DownloadManager.INTENT_EXTRAS_SORT_BY_SIZE, true);
diff --git a/src/com/android/settings/deviceinfo/UsageBarPreference.java b/src/com/android/settings/deviceinfo/UsageBarPreference.java
index 371c772..4763b79 100644
--- a/src/com/android/settings/deviceinfo/UsageBarPreference.java
+++ b/src/com/android/settings/deviceinfo/UsageBarPreference.java
@@ -35,18 +35,21 @@ public class UsageBarPreference extends Preference {
private final List<PercentageBarChart.Entry> mEntries = Lists.newArrayList();
- public UsageBarPreference(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- setLayoutResource(R.layout.preference_memoryusage);
- }
-
public UsageBarPreference(Context context) {
- super(context);
- setLayoutResource(R.layout.preference_memoryusage);
+ this(context, null);
}
public UsageBarPreference(Context context, AttributeSet attrs) {
- super(context, attrs);
+ this(context, attrs, 0);
+ }
+
+ public UsageBarPreference(Context context, AttributeSet attrs, int defStyle) {
+ this(context, attrs, defStyle, 0);
+ }
+
+ public UsageBarPreference(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
setLayoutResource(R.layout.preference_memoryusage);
}
diff --git a/src/com/android/settings/users/CircleFramedDrawable.java b/src/com/android/settings/drawable/CircleFramedDrawable.java
index 671cfbe..97c96a0 100644
--- a/src/com/android/settings/users/CircleFramedDrawable.java
+++ b/src/com/android/settings/drawable/CircleFramedDrawable.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settings.users;
+package com.android.settings.drawable;
import android.content.Context;
import android.content.res.Resources;
@@ -37,7 +37,7 @@ 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.
*/
-class CircleFramedDrawable extends Drawable {
+public class CircleFramedDrawable extends Drawable {
private final Bitmap mBitmap;
private final int mSize;
@@ -107,7 +107,7 @@ class CircleFramedDrawable extends Drawable {
canvas.drawPath(fillPath, mPaint);
// mask in the icon where the bitmap is opaque
- mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));
+ mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(icon, cropRect, circleRect, mPaint);
// prepare paint for frame drawing
diff --git a/src/com/android/settings/fuelgauge/BatteryEntry.java b/src/com/android/settings/fuelgauge/BatteryEntry.java
new file mode 100644
index 0000000..4ff4dfd
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/BatteryEntry.java
@@ -0,0 +1,333 @@
+/*
+ * 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 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.UserInfo;
+import android.graphics.drawable.Drawable;
+import android.os.BatteryStats;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
+
+import com.android.internal.os.BatterySipper;
+import com.android.settings.R;
+import com.android.settings.Utils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * Wraps the power usage data of a BatterySipper with information about package name
+ * and icon image.
+ */
+public class BatteryEntry {
+ public static final int MSG_UPDATE_NAME_ICON = 1;
+ public static final int MSG_REPORT_FULLY_DRAWN = 2;
+
+ static final HashMap<String,UidToDetail> sUidCache = new HashMap<String,UidToDetail>();
+
+ static final ArrayList<BatteryEntry> mRequestQueue = new ArrayList<BatteryEntry>();
+ static Handler sHandler;
+
+ static private class NameAndIconLoader extends Thread {
+ private boolean mAbort = false;
+
+ public NameAndIconLoader() {
+ super("BatteryUsage Icon Loader");
+ }
+
+ public void abort() {
+ mAbort = true;
+ }
+
+ @Override
+ public void run() {
+ while (true) {
+ BatteryEntry be;
+ synchronized (mRequestQueue) {
+ if (mRequestQueue.isEmpty() || mAbort) {
+ if (sHandler != null) {
+ sHandler.sendEmptyMessage(MSG_REPORT_FULLY_DRAWN);
+ }
+ mRequestQueue.clear();
+ return;
+ }
+ be = mRequestQueue.remove(0);
+ }
+ be.loadNameAndIcon();
+ }
+ }
+ }
+
+ private static NameAndIconLoader mRequestThread;
+
+ public static void startRequestQueue() {
+ if (sHandler != null) {
+ synchronized (mRequestQueue) {
+ if (!mRequestQueue.isEmpty()) {
+ if (mRequestThread != null) {
+ mRequestThread.abort();
+ }
+ mRequestThread = new NameAndIconLoader();
+ mRequestThread.setPriority(Thread.MIN_PRIORITY);
+ mRequestThread.start();
+ mRequestQueue.notify();
+ }
+ }
+ }
+ }
+
+ public static void stopRequestQueue() {
+ synchronized (mRequestQueue) {
+ if (mRequestThread != null) {
+ mRequestThread.abort();
+ mRequestThread = null;
+ sHandler = null;
+ }
+ }
+ }
+
+ public static void clearUidCache() {
+ sUidCache.clear();
+ }
+
+ public final Context context;
+ public final BatterySipper sipper;
+
+ public String name;
+ public Drawable icon;
+ public int iconId; // For passing to the detail screen.
+ public String defaultPackageName;
+
+ static class UidToDetail {
+ String name;
+ String packageName;
+ Drawable icon;
+ }
+
+ public BatteryEntry(Context context, Handler handler, UserManager um, BatterySipper sipper) {
+ sHandler = handler;
+ this.context = context;
+ this.sipper = sipper;
+ switch (sipper.drainType) {
+ case IDLE:
+ name = context.getResources().getString(R.string.power_idle);
+ iconId = R.drawable.ic_settings_phone_idle;
+ break;
+ case CELL:
+ name = context.getResources().getString(R.string.power_cell);
+ iconId = R.drawable.ic_settings_cell_standby;
+ break;
+ case PHONE:
+ name = context.getResources().getString(R.string.power_phone);
+ iconId = R.drawable.ic_settings_voice_calls;
+ break;
+ case WIFI:
+ name = context.getResources().getString(R.string.power_wifi);
+ iconId = R.drawable.ic_settings_wifi;
+ break;
+ case BLUETOOTH:
+ name = context.getResources().getString(R.string.power_bluetooth);
+ iconId = R.drawable.ic_settings_bluetooth;
+ break;
+ case SCREEN:
+ name = context.getResources().getString(R.string.power_screen);
+ iconId = R.drawable.ic_settings_display;
+ break;
+ case FLASHLIGHT:
+ name = context.getResources().getString(R.string.power_flashlight);
+ iconId = R.drawable.ic_settings_display;
+ break;
+ case APP:
+ name = sipper.packageWithHighestDrain;
+ break;
+ case USER: {
+ UserInfo info = um.getUserInfo(sipper.userId);
+ if (info != null) {
+ icon = Utils.getUserIcon(context, um, info);
+ name = info != null ? info.name : null;
+ if (name == null) {
+ name = Integer.toString(info.id);
+ }
+ name = context.getResources().getString(
+ R.string.running_process_item_user_label, name);
+ } else {
+ icon = null;
+ name = context.getResources().getString(
+ R.string.running_process_item_removed_user_label);
+ }
+ } break;
+ case UNACCOUNTED:
+ name = context.getResources().getString(R.string.power_unaccounted);
+ iconId = R.drawable.ic_power_system;
+ break;
+ case OVERCOUNTED:
+ name = context.getResources().getString(R.string.power_overcounted);
+ iconId = R.drawable.ic_power_system;
+ break;
+ }
+ if (iconId > 0) {
+ icon = context.getResources().getDrawable(iconId);
+ }
+ if ((name == null || iconId == 0) && this.sipper.uidObj != null) {
+ getQuickNameIconForUid(this.sipper.uidObj);
+ }
+ }
+
+ public Drawable getIcon() {
+ return icon;
+ }
+
+ /**
+ * Gets the application name
+ */
+ public String getLabel() {
+ return name;
+ }
+
+ void getQuickNameIconForUid(BatteryStats.Uid uidObj) {
+ final int uid = uidObj.getUid();
+ final String uidString = Integer.toString(uid);
+ if (sUidCache.containsKey(uidString)) {
+ UidToDetail utd = sUidCache.get(uidString);
+ defaultPackageName = utd.packageName;
+ name = utd.name;
+ icon = utd.icon;
+ return;
+ }
+ PackageManager pm = context.getPackageManager();
+ String[] packages = pm.getPackagesForUid(uid);
+ icon = pm.getDefaultActivityIcon();
+ if (packages == null) {
+ //name = Integer.toString(uid);
+ if (uid == 0) {
+ name = context.getResources().getString(R.string.process_kernel_label);
+ } else if ("mediaserver".equals(name)) {
+ name = context.getResources().getString(R.string.process_mediaserver_label);
+ }
+ iconId = R.drawable.ic_power_system;
+ icon = context.getResources().getDrawable(iconId);
+ return;
+ } else {
+ //name = packages[0];
+ }
+ if (sHandler != null) {
+ synchronized (mRequestQueue) {
+ mRequestQueue.add(this);
+ }
+ }
+ }
+
+ /**
+ * Loads the app label and icon image and stores into the cache.
+ */
+ public void loadNameAndIcon() {
+ // Bail out if the current sipper is not an App sipper.
+ if (sipper.uidObj == null) {
+ return;
+ }
+ PackageManager pm = context.getPackageManager();
+ final int uid = sipper.uidObj.getUid();
+ final Drawable defaultActivityIcon = pm.getDefaultActivityIcon();
+ sipper.mPackages = pm.getPackagesForUid(uid);
+ if (sipper.mPackages == null) {
+ name = Integer.toString(uid);
+ return;
+ }
+
+ String[] packageLabels = new String[sipper.mPackages.length];
+ System.arraycopy(sipper.mPackages, 0, packageLabels, 0, sipper.mPackages.length);
+
+ // Convert package names to user-facing labels where possible
+ IPackageManager ipm = AppGlobals.getPackageManager();
+ final int userId = UserHandle.getUserId(uid);
+ for (int i = 0; i < packageLabels.length; i++) {
+ try {
+ final ApplicationInfo ai = ipm.getApplicationInfo(packageLabels[i],
+ 0 /* no flags */, userId);
+ if (ai == null) {
+ Log.d(PowerUsageSummary.TAG, "Retrieving null app info for package "
+ + packageLabels[i] + ", user " + userId);
+ continue;
+ }
+ CharSequence label = ai.loadLabel(pm);
+ if (label != null) {
+ packageLabels[i] = label.toString();
+ }
+ if (ai.icon != 0) {
+ defaultPackageName = sipper.mPackages[i];
+ icon = ai.loadIcon(pm);
+ break;
+ }
+ } catch (RemoteException e) {
+ Log.d(PowerUsageSummary.TAG, "Error while retrieving app info for package "
+ + packageLabels[i] + ", user " + userId, e);
+ }
+ }
+ if (icon == null) {
+ icon = defaultActivityIcon;
+ }
+
+ if (packageLabels.length == 1) {
+ name = packageLabels[0];
+ } else {
+ // Look for an official name for this UID.
+ for (String pkgName : sipper.mPackages) {
+ try {
+ final PackageInfo pi = ipm.getPackageInfo(pkgName, 0 /* no flags */, userId);
+ if (pi == null) {
+ Log.d(PowerUsageSummary.TAG, "Retrieving null package info for package "
+ + pkgName + ", user " + userId);
+ continue;
+ }
+ if (pi.sharedUserLabel != 0) {
+ final CharSequence nm = pm.getText(pkgName,
+ pi.sharedUserLabel, pi.applicationInfo);
+ if (nm != null) {
+ name = nm.toString();
+ if (pi.applicationInfo.icon != 0) {
+ defaultPackageName = pkgName;
+ icon = pi.applicationInfo.loadIcon(pm);
+ }
+ break;
+ }
+ }
+ } catch (RemoteException e) {
+ Log.d(PowerUsageSummary.TAG, "Error while retrieving package info for package "
+ + pkgName + ", user " + userId, e);
+ }
+ }
+ }
+ final String uidString = Integer.toString(sipper.uidObj.getUid());
+ UidToDetail utd = new UidToDetail();
+ utd.name = name;
+ utd.icon = icon;
+ utd.packageName = defaultPackageName;
+ sUidCache.put(uidString, utd);
+ if (sHandler != null) {
+ sHandler.sendMessage(sHandler.obtainMessage(MSG_UPDATE_NAME_ICON, this));
+ }
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/BatteryHistoryChart.java b/src/com/android/settings/fuelgauge/BatteryHistoryChart.java
index 55a0457..765f101 100644
--- a/src/com/android/settings/fuelgauge/BatteryHistoryChart.java
+++ b/src/com/android/settings/fuelgauge/BatteryHistoryChart.java
@@ -16,7 +16,17 @@
package com.android.settings.fuelgauge;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.DashPathEffect;
+import android.os.BatteryManager;
+import android.provider.Settings;
+import android.text.format.DateFormat;
+import android.text.format.Formatter;
+import android.util.Log;
+import android.util.TimeUtils;
import com.android.settings.R;
+import com.android.settings.Utils;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -33,8 +43,16 @@ import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
+import libcore.icu.LocaleData;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Locale;
public class BatteryHistoryChart extends View {
+ static final boolean DEBUG = false;
+ static final String TAG = "BatteryHistoryChart";
+
static final int CHART_DATA_X_MASK = 0x0000ffff;
static final int CHART_DATA_BIN_MASK = 0xffff0000;
static final int CHART_DATA_BIN_SHIFT = 16;
@@ -69,7 +87,7 @@ public class BatteryHistoryChart extends View {
void addTick(int x, int bin) {
if (bin != mLastBin && mNumTicks < mTicks.length) {
- mTicks[mNumTicks] = x | bin << CHART_DATA_BIN_SHIFT;
+ mTicks[mNumTicks] = (x&CHART_DATA_X_MASK) | (bin<<CHART_DATA_BIN_SHIFT);
mNumTicks++;
mLastBin = bin;
}
@@ -102,9 +120,6 @@ public class BatteryHistoryChart extends View {
static final int SERIF = 2;
static final int MONOSPACE = 3;
- static final int BATTERY_WARN = 29;
- static final int BATTERY_CRITICAL = 14;
-
// First value if for phone off; first value is "scanning"; following values
// are battery stats signal strength buckets.
static final int NUM_PHONE_SIGNALS = 7;
@@ -113,137 +128,266 @@ public class BatteryHistoryChart extends View {
final Paint mBatteryGoodPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
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 mChargingPaint = new Paint();
final Paint mScreenOnPaint = new Paint();
final Paint mGpsOnPaint = new Paint();
final Paint mWifiRunningPaint = new Paint();
- final Paint mWakeLockPaint = new Paint();
+ final Paint mCpuRunningPaint = new Paint();
+ final Paint mDateLinePaint = new Paint();
final ChartData mPhoneSignalChart = new ChartData();
final TextPaint mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
-
+ final TextPaint mHeaderTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
+ final Paint mDebugRectPaint = new Paint();
+
final Path mBatLevelPath = new Path();
final Path mBatGoodPath = new Path();
final Path mBatWarnPath = new Path();
final Path mBatCriticalPath = new Path();
+ final Path mTimeRemainPath = new Path();
final Path mChargingPath = new Path();
final Path mScreenOnPath = new Path();
final Path mGpsOnPath = new Path();
final Path mWifiRunningPath = new Path();
- final Path mWakeLockPath = new Path();
-
- int mFontSize;
+ final Path mCpuRunningPath = new Path();
+ final Path mDateLinePath = new Path();
BatteryStats mStats;
+ Intent mBatteryBroadcast;
long mStatsPeriod;
+ int mBatteryLevel;
+ String mMaxPercentLabelString;
+ String mMinPercentLabelString;
String mDurationString;
- String mTotalDurationString;
+ String mChargeLabelString;
+ String mChargeDurationString;
+ String mDrainString;
String mChargingLabel;
String mScreenOnLabel;
String mGpsOnLabel;
String mWifiRunningLabel;
- String mWakeLockLabel;
+ String mCpuRunningLabel;
String mPhoneSignalLabel;
-
+
+ int mChartMinHeight;
+ int mHeaderHeight;
+
+ int mBatteryWarnLevel;
+ int mBatteryCriticalLevel;
+
int mTextAscent;
int mTextDescent;
+ int mHeaderTextAscent;
+ int mHeaderTextDescent;
+ int mMaxPercentLabelStringWidth;
+ int mMinPercentLabelStringWidth;
int mDurationStringWidth;
- int mTotalDurationStringWidth;
+ int mChargeLabelStringWidth;
+ int mChargeDurationStringWidth;
+ int mDrainStringWidth;
boolean mLargeMode;
+ int mLastWidth = -1;
+ int mLastHeight = -1;
+
int mLineWidth;
int mThinLineWidth;
int mChargingOffset;
int mScreenOnOffset;
int mGpsOnOffset;
int mWifiRunningOffset;
- int mWakeLockOffset;
+ int mCpuRunningOffset;
int mPhoneSignalOffset;
int mLevelOffset;
int mLevelTop;
int mLevelBottom;
- static final int PHONE_SIGNAL_X_MASK = CHART_DATA_X_MASK;
- static final int PHONE_SIGNAL_BIN_MASK = CHART_DATA_BIN_MASK;
- static final int PHONE_SIGNAL_BIN_SHIFT = CHART_DATA_BIN_SHIFT;
-
+ int mLevelLeft;
+ int mLevelRight;
+
int mNumHist;
long mHistStart;
+ long mHistDataEnd;
long mHistEnd;
+ long mStartWallTime;
+ long mEndDataWallTime;
+ long mEndWallTime;
+ boolean mDischarging;
int mBatLow;
int mBatHigh;
boolean mHaveWifi;
boolean mHaveGps;
boolean mHavePhoneSignal;
-
+
+ final ArrayList<TimeLabel> mTimeLabels = new ArrayList<TimeLabel>();
+ final ArrayList<DateLabel> mDateLabels = new ArrayList<DateLabel>();
+
+ Bitmap mBitmap;
+ Canvas mCanvas;
+
+ static class TextAttrs {
+ ColorStateList textColor = null;
+ int textSize = 15;
+ int typefaceIndex = -1;
+ int styleIndex = -1;
+
+ void retrieve(Context context, TypedArray from, int index) {
+ TypedArray appearance = null;
+ int ap = from.getResourceId(index, -1);
+ if (ap != -1) {
+ appearance = context.obtainStyledAttributes(ap,
+ com.android.internal.R.styleable.TextAppearance);
+ }
+ if (appearance != null) {
+ int n = appearance.getIndexCount();
+ for (int i = 0; i < n; i++) {
+ int attr = appearance.getIndex(i);
+
+ switch (attr) {
+ case com.android.internal.R.styleable.TextAppearance_textColor:
+ textColor = appearance.getColorStateList(attr);
+ break;
+
+ case com.android.internal.R.styleable.TextAppearance_textSize:
+ textSize = appearance.getDimensionPixelSize(attr, textSize);
+ break;
+
+ case com.android.internal.R.styleable.TextAppearance_typeface:
+ typefaceIndex = appearance.getInt(attr, -1);
+ break;
+
+ case com.android.internal.R.styleable.TextAppearance_textStyle:
+ styleIndex = appearance.getInt(attr, -1);
+ break;
+ }
+ }
+
+ appearance.recycle();
+ }
+ }
+
+ void apply(Context context, TextPaint paint) {
+ paint.density = context.getResources().getDisplayMetrics().density;
+ paint.setCompatibilityScaling(
+ context.getResources().getCompatibilityInfo().applicationScale);
+
+ paint.setColor(textColor.getDefaultColor());
+ paint.setTextSize(textSize);
+
+ Typeface tf = null;
+ switch (typefaceIndex) {
+ case SANS:
+ tf = Typeface.SANS_SERIF;
+ break;
+
+ case SERIF:
+ tf = Typeface.SERIF;
+ break;
+
+ case MONOSPACE:
+ tf = Typeface.MONOSPACE;
+ break;
+ }
+
+ setTypeface(paint, tf, styleIndex);
+ }
+
+ public void setTypeface(TextPaint paint, Typeface tf, int style) {
+ if (style > 0) {
+ if (tf == null) {
+ tf = Typeface.defaultFromStyle(style);
+ } else {
+ tf = Typeface.create(tf, style);
+ }
+
+ paint.setTypeface(tf);
+ // now compute what (if any) algorithmic styling is needed
+ int typefaceStyle = tf != null ? tf.getStyle() : 0;
+ int need = style & ~typefaceStyle;
+ paint.setFakeBoldText((need & Typeface.BOLD) != 0);
+ paint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
+ } else {
+ paint.setFakeBoldText(false);
+ paint.setTextSkewX(0);
+ paint.setTypeface(tf);
+ }
+ }
+ }
+
+ static class TimeLabel {
+ final int x;
+ final String label;
+ final int width;
+
+ TimeLabel(TextPaint paint, int x, Calendar cal, boolean use24hr) {
+ this.x = x;
+ final String bestFormat = DateFormat.getBestDateTimePattern(
+ Locale.getDefault(), use24hr ? "km" : "ha");
+ label = DateFormat.format(bestFormat, cal).toString();
+ width = (int)paint.measureText(label);
+ }
+ }
+
+ static class DateLabel {
+ final int x;
+ final String label;
+ final int width;
+
+ DateLabel(TextPaint paint, int x, Calendar cal, boolean dayFirst) {
+ this.x = x;
+ final String bestFormat = DateFormat.getBestDateTimePattern(
+ Locale.getDefault(), dayFirst ? "dM" : "Md");
+ label = DateFormat.format(bestFormat, cal).toString();
+ width = (int)paint.measureText(label);
+ }
+ }
+
public BatteryHistoryChart(Context context, AttributeSet attrs) {
super(context, attrs);
-
- mBatteryBackgroundPaint.setARGB(255, 128, 128, 128);
+
+ if (DEBUG) Log.d(TAG, "New BatteryHistoryChart!");
+
+ mBatteryWarnLevel = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_lowBatteryWarningLevel);
+ mBatteryCriticalLevel = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_criticalBatteryWarningLevel);
+
+ mThinLineWidth = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ 2, getResources().getDisplayMetrics());
+
+ mBatteryBackgroundPaint.setColor(0xFF009688);
mBatteryBackgroundPaint.setStyle(Paint.Style.FILL);
- mBatteryGoodPaint.setARGB(128, 0, 255, 0);
+ mBatteryGoodPaint.setARGB(128, 0, 128, 0);
mBatteryGoodPaint.setStyle(Paint.Style.STROKE);
- mBatteryWarnPaint.setARGB(128, 255, 255, 0);
+ mBatteryWarnPaint.setARGB(128, 128, 128, 0);
mBatteryWarnPaint.setStyle(Paint.Style.STROKE);
- mBatteryCriticalPaint.setARGB(192, 255, 0, 0);
+ mBatteryCriticalPaint.setARGB(192, 128, 0, 0);
mBatteryCriticalPaint.setStyle(Paint.Style.STROKE);
- mChargingPaint.setARGB(255, 0, 128, 0);
+ mTimeRemainPaint.setColor(0xFFCED7BB);
+ mTimeRemainPaint.setStyle(Paint.Style.FILL);
mChargingPaint.setStyle(Paint.Style.STROKE);
mScreenOnPaint.setStyle(Paint.Style.STROKE);
mGpsOnPaint.setStyle(Paint.Style.STROKE);
mWifiRunningPaint.setStyle(Paint.Style.STROKE);
- mWakeLockPaint.setStyle(Paint.Style.STROKE);
- mPhoneSignalChart.setColors(new int[] {
- 0x00000000, 0xffa00000, 0xffa0a000, 0xff808020,
- 0xff808040, 0xff808060, 0xff008000
- });
-
- mTextPaint.density = getResources().getDisplayMetrics().density;
- mTextPaint.setCompatibilityScaling(
- getResources().getCompatibilityInfo().applicationScale);
-
+ mCpuRunningPaint.setStyle(Paint.Style.STROKE);
+ mPhoneSignalChart.setColors(com.android.settings.Utils.BADNESS_COLORS);
+ mDebugRectPaint.setARGB(255, 255, 0, 0);
+ mDebugRectPaint.setStyle(Paint.Style.STROKE);
+ mScreenOnPaint.setColor(0xFF009688);
+ mGpsOnPaint.setColor(0xFF009688);
+ mWifiRunningPaint.setColor(0xFF009688);
+ mCpuRunningPaint.setColor(0xFF009688);
+ mChargingPaint.setColor(0xFF009688);
+
TypedArray a =
context.obtainStyledAttributes(
attrs, R.styleable.BatteryHistoryChart, 0, 0);
-
- ColorStateList textColor = null;
- int textSize = 15;
- int typefaceIndex = -1;
- int styleIndex = -1;
-
- TypedArray appearance = null;
- int ap = a.getResourceId(R.styleable.BatteryHistoryChart_android_textAppearance, -1);
- if (ap != -1) {
- appearance = context.obtainStyledAttributes(ap,
- com.android.internal.R.styleable.
- TextAppearance);
- }
- if (appearance != null) {
- int n = appearance.getIndexCount();
- for (int i = 0; i < n; i++) {
- int attr = appearance.getIndex(i);
-
- switch (attr) {
- case com.android.internal.R.styleable.TextAppearance_textColor:
- textColor = appearance.getColorStateList(attr);
- break;
- case com.android.internal.R.styleable.TextAppearance_textSize:
- textSize = appearance.getDimensionPixelSize(attr, textSize);
- break;
+ final TextAttrs mainTextAttrs = new TextAttrs();
+ final TextAttrs headTextAttrs = new TextAttrs();
+ mainTextAttrs.retrieve(context, a, R.styleable.BatteryHistoryChart_android_textAppearance);
+ headTextAttrs.retrieve(context, a, R.styleable.BatteryHistoryChart_headerAppearance);
- case com.android.internal.R.styleable.TextAppearance_typeface:
- typefaceIndex = appearance.getInt(attr, -1);
- break;
-
- case com.android.internal.R.styleable.TextAppearance_textStyle:
- styleIndex = appearance.getInt(attr, -1);
- break;
- }
- }
-
- appearance.recycle();
- }
-
int shadowcolor = 0;
float dx=0, dy=0, r=0;
@@ -269,134 +413,222 @@ public class BatteryHistoryChart extends View {
break;
case R.styleable.BatteryHistoryChart_android_textColor:
- textColor = a.getColorStateList(attr);
+ mainTextAttrs.textColor = a.getColorStateList(attr);
+ headTextAttrs.textColor = a.getColorStateList(attr);
break;
case R.styleable.BatteryHistoryChart_android_textSize:
- textSize = a.getDimensionPixelSize(attr, textSize);
+ mainTextAttrs.textSize = a.getDimensionPixelSize(attr, mainTextAttrs.textSize);
+ headTextAttrs.textSize = a.getDimensionPixelSize(attr, headTextAttrs.textSize);
break;
case R.styleable.BatteryHistoryChart_android_typeface:
- typefaceIndex = a.getInt(attr, typefaceIndex);
+ mainTextAttrs.typefaceIndex = a.getInt(attr, mainTextAttrs.typefaceIndex);
+ headTextAttrs.typefaceIndex = a.getInt(attr, headTextAttrs.typefaceIndex);
break;
case R.styleable.BatteryHistoryChart_android_textStyle:
- styleIndex = a.getInt(attr, styleIndex);
+ mainTextAttrs.styleIndex = a.getInt(attr, mainTextAttrs.styleIndex);
+ headTextAttrs.styleIndex = a.getInt(attr, headTextAttrs.styleIndex);
+ break;
+
+ case R.styleable.BatteryHistoryChart_barPrimaryColor:
+ mBatteryBackgroundPaint.setColor(a.getInt(attr, 0));
+ mScreenOnPaint.setColor(a.getInt(attr, 0));
+ mGpsOnPaint.setColor(a.getInt(attr, 0));
+ mWifiRunningPaint.setColor(a.getInt(attr, 0));
+ mCpuRunningPaint.setColor(a.getInt(attr, 0));
+ mChargingPaint.setColor(a.getInt(attr, 0));
+ break;
+
+ case R.styleable.BatteryHistoryChart_barPredictionColor:
+ mTimeRemainPaint.setColor(a.getInt(attr, 0));
+ break;
+
+ case R.styleable.BatteryHistoryChart_chartMinHeight:
+ mChartMinHeight = a.getDimensionPixelSize(attr, 0);
break;
}
}
a.recycle();
- mTextPaint.setColor(textColor.getDefaultColor());
- mTextPaint.setTextSize(textSize);
-
- Typeface tf = null;
- switch (typefaceIndex) {
- case SANS:
- tf = Typeface.SANS_SERIF;
- break;
-
- case SERIF:
- tf = Typeface.SERIF;
- break;
+ mainTextAttrs.apply(context, mTextPaint);
+ headTextAttrs.apply(context, mHeaderTextPaint);
- case MONOSPACE:
- tf = Typeface.MONOSPACE;
- break;
+ mDateLinePaint.set(mTextPaint);
+ mDateLinePaint.setStyle(Paint.Style.STROKE);
+ int hairlineWidth = mThinLineWidth/2;
+ if (hairlineWidth < 1) {
+ hairlineWidth = 1;
}
-
- setTypeface(tf, styleIndex);
-
+ mDateLinePaint.setStrokeWidth(hairlineWidth);
+ mDateLinePaint.setPathEffect(new DashPathEffect(new float[] {
+ mThinLineWidth * 2, mThinLineWidth * 2 }, 0));
+
if (shadowcolor != 0) {
mTextPaint.setShadowLayer(r, dx, dy, shadowcolor);
+ mHeaderTextPaint.setShadowLayer(r, dx, dy, shadowcolor);
}
}
-
- public void setTypeface(Typeface tf, int style) {
- if (style > 0) {
- if (tf == null) {
- tf = Typeface.defaultFromStyle(style);
- } else {
- tf = Typeface.create(tf, style);
- }
- mTextPaint.setTypeface(tf);
- // now compute what (if any) algorithmic styling is needed
- int typefaceStyle = tf != null ? tf.getStyle() : 0;
- int need = style & ~typefaceStyle;
- mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
- mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
- } else {
- mTextPaint.setFakeBoldText(false);
- mTextPaint.setTextSkewX(0);
- mTextPaint.setTypeface(tf);
- }
- }
-
- void setStats(BatteryStats stats) {
+ void setStats(BatteryStats stats, Intent broadcast) {
mStats = stats;
-
- long uSecTime = mStats.computeBatteryRealtime(SystemClock.elapsedRealtime() * 1000,
+ mBatteryBroadcast = broadcast;
+
+ if (DEBUG) Log.d(TAG, "Setting stats...");
+
+ final long elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;
+
+ long uSecTime = mStats.computeBatteryRealtime(elapsedRealtimeUs,
BatteryStats.STATS_SINCE_CHARGED);
mStatsPeriod = uSecTime;
- String durationString = Utils.formatElapsedTime(getContext(), mStatsPeriod / 1000, true);
- mDurationString = getContext().getString(R.string.battery_stats_on_battery,
- durationString);
mChargingLabel = getContext().getString(R.string.battery_stats_charging_label);
mScreenOnLabel = getContext().getString(R.string.battery_stats_screen_on_label);
mGpsOnLabel = getContext().getString(R.string.battery_stats_gps_on_label);
mWifiRunningLabel = getContext().getString(R.string.battery_stats_wifi_running_label);
- mWakeLockLabel = getContext().getString(R.string.battery_stats_wake_lock_label);
+ mCpuRunningLabel = getContext().getString(R.string.battery_stats_wake_lock_label);
mPhoneSignalLabel = getContext().getString(R.string.battery_stats_phone_signal_label);
-
+
+ mMaxPercentLabelString = Utils.formatPercentage(100);
+ mMinPercentLabelString = Utils.formatPercentage(0);
+
+ mBatteryLevel = com.android.settings.Utils.getBatteryLevel(mBatteryBroadcast);
+ long remainingTimeUs = 0;
+ mDischarging = true;
+ if (mBatteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) == 0) {
+ final long drainTime = mStats.computeBatteryTimeRemaining(elapsedRealtimeUs);
+ if (drainTime > 0) {
+ remainingTimeUs = drainTime;
+ String timeString = Formatter.formatShortElapsedTime(getContext(),
+ drainTime / 1000);
+ mChargeLabelString = getContext().getResources().getString(
+ R.string.power_discharging_duration, mBatteryLevel, timeString);
+ } else {
+ mChargeLabelString = Utils.formatPercentage(mBatteryLevel);
+ }
+ } else {
+ final long chargeTime = mStats.computeChargeTimeRemaining(elapsedRealtimeUs);
+ final String statusLabel = com.android.settings.Utils.getBatteryStatus(getResources(),
+ mBatteryBroadcast);
+ final int status = mBatteryBroadcast.getIntExtra(BatteryManager.EXTRA_STATUS,
+ BatteryManager.BATTERY_STATUS_UNKNOWN);
+ if (chargeTime > 0 && status != BatteryManager.BATTERY_STATUS_FULL) {
+ mDischarging = false;
+ remainingTimeUs = chargeTime;
+ String timeString = Formatter.formatShortElapsedTime(getContext(),
+ chargeTime / 1000);
+ int plugType = mBatteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
+ int resId;
+ if (plugType == BatteryManager.BATTERY_PLUGGED_AC) {
+ resId = R.string.power_charging_duration_ac;
+ } else if (plugType == BatteryManager.BATTERY_PLUGGED_USB) {
+ resId = R.string.power_charging_duration_usb;
+ } else if (plugType == BatteryManager.BATTERY_PLUGGED_WIRELESS) {
+ resId = R.string.power_charging_duration_wireless;
+ } else {
+ resId = R.string.power_charging_duration;
+ }
+ mChargeLabelString = getContext().getResources().getString(
+ resId, mBatteryLevel, timeString);
+ } else {
+ mChargeLabelString = getContext().getResources().getString(
+ R.string.power_charging, mBatteryLevel, statusLabel);
+ }
+ }
+ mDrainString = "";
+ mChargeDurationString = "";
+ setContentDescription(mChargeLabelString);
+
int pos = 0;
int lastInteresting = 0;
byte lastLevel = -1;
mBatLow = 0;
mBatHigh = 100;
+ mStartWallTime = 0;
+ mEndDataWallTime = 0;
+ mEndWallTime = 0;
+ mHistStart = 0;
+ mHistEnd = 0;
+ long lastWallTime = 0;
+ long lastRealtime = 0;
int aggrStates = 0;
+ int aggrStates2 = 0;
boolean first = true;
if (stats.startIteratingHistoryLocked()) {
final HistoryItem rec = new HistoryItem();
while (stats.getNextHistoryLocked(rec)) {
pos++;
- if (rec.cmd == HistoryItem.CMD_UPDATE) {
- if (first) {
- first = false;
- mHistStart = rec.time;
+ if (first) {
+ first = false;
+ mHistStart = 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 < (mHistStart+(5*60*1000L))) {
+ mStartWallTime = 0;
+ }
+ lastWallTime = rec.currentTime;
+ lastRealtime = rec.time;
+ if (mStartWallTime == 0) {
+ mStartWallTime = lastWallTime - (lastRealtime-mHistStart);
}
+ }
+ if (rec.isDeltaData()) {
if (rec.batteryLevel != lastLevel || pos == 1) {
lastLevel = rec.batteryLevel;
}
lastInteresting = pos;
- mHistEnd = rec.time;
+ mHistDataEnd = rec.time;
aggrStates |= rec.states;
+ aggrStates2 |= rec.states2;
}
}
}
+ mHistEnd = mHistDataEnd + (remainingTimeUs/1000);
+ mEndDataWallTime = lastWallTime + mHistDataEnd - lastRealtime;
+ mEndWallTime = mEndDataWallTime + (remainingTimeUs/1000);
mNumHist = lastInteresting;
mHaveGps = (aggrStates&HistoryItem.STATE_GPS_ON_FLAG) != 0;
- mHaveWifi = (aggrStates&HistoryItem.STATE_WIFI_RUNNING_FLAG) != 0;
+ mHaveWifi = (aggrStates2&HistoryItem.STATE2_WIFI_RUNNING_FLAG) != 0
+ || (aggrStates&(HistoryItem.STATE_WIFI_FULL_LOCK_FLAG
+ |HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG
+ |HistoryItem.STATE_WIFI_SCAN_FLAG)) != 0;
if (!com.android.settings.Utils.isWifiOnly(getContext())) {
mHavePhoneSignal = true;
}
if (mHistEnd <= mHistStart) mHistEnd = mHistStart+1;
- mTotalDurationString = Utils.formatElapsedTime(getContext(), mHistEnd - mHistStart, true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- mDurationStringWidth = (int)mTextPaint.measureText(mDurationString);
- mTotalDurationStringWidth = (int)mTextPaint.measureText(mTotalDurationString);
+ mMaxPercentLabelStringWidth = (int)mTextPaint.measureText(mMaxPercentLabelString);
+ mMinPercentLabelStringWidth = (int)mTextPaint.measureText(mMinPercentLabelString);
+ mDrainStringWidth = (int)mHeaderTextPaint.measureText(mDrainString);
+ mChargeLabelStringWidth = (int)mHeaderTextPaint.measureText(mChargeLabelString);
+ mChargeDurationStringWidth = (int)mHeaderTextPaint.measureText(mChargeDurationString);
mTextAscent = (int)mTextPaint.ascent();
mTextDescent = (int)mTextPaint.descent();
+ mHeaderTextAscent = (int)mHeaderTextPaint.ascent();
+ mHeaderTextDescent = (int)mHeaderTextPaint.descent();
+ int headerTextHeight = mHeaderTextDescent - mHeaderTextAscent;
+ mHeaderHeight = headerTextHeight*2 - mTextAscent;
+ setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
+ getDefaultSize(mChartMinHeight+mHeaderHeight, heightMeasureSpec));
}
void finishPaths(int w, int h, int levelh, int startX, int y, Path curLevelPath,
int lastX, boolean lastCharging, boolean lastScreenOn, boolean lastGpsOn,
- boolean lastWifiRunning, boolean lastWakeLock, Path lastPath) {
+ boolean lastWifiRunning, boolean lastCpuRunning, Path lastPath) {
if (curLevelPath != null) {
if (lastX >= 0 && lastX < w) {
if (lastPath != null) {
@@ -421,22 +653,69 @@ public class BatteryHistoryChart extends View {
if (lastWifiRunning) {
mWifiRunningPath.lineTo(w, h-mWifiRunningOffset);
}
- if (lastWakeLock) {
- mWakeLockPath.lineTo(w, h-mWakeLockOffset);
+ if (lastCpuRunning) {
+ mCpuRunningPath.lineTo(w, h - mCpuRunningOffset);
}
if (mHavePhoneSignal) {
mPhoneSignalChart.finish(w);
}
}
-
+
+ private boolean is24Hour() {
+ return DateFormat.is24HourFormat(getContext());
+ }
+
+ private boolean isDayFirst() {
+ String value = Settings.System.getString(mContext.getContentResolver(),
+ Settings.System.DATE_FORMAT);
+ if (value == null) {
+ LocaleData d = LocaleData.get(mContext.getResources().getConfiguration().locale);
+ value = d.shortDateFormat4;
+ }
+ return value.indexOf('M') > value.indexOf('d');
+ }
+
+ /*
+ private void buildTime() {
+ java.text.DateFormat shortDateFormat = DateFormat.getDateFormat(context);
+ final Calendar now = Calendar.getInstance();
+ mDummyDate.setTimeZone(now.getTimeZone());
+ // We use December 31st because it's unambiguous when demonstrating the date format.
+ // We use 13:00 so we can demonstrate the 12/24 hour options.
+ mDummyDate.set(now.get(Calendar.YEAR), 11, 31, 13, 0, 0);
+ Date dummyDate = mDummyDate.getTime();
+ mTimePref.setSummary(DateFormat.getTimeFormat(getActivity()).format(now.getTime()));
+ mTimeZone.setSummary(getTimeZoneText(now.getTimeZone(), true));
+ mDatePref.setSummary(shortDateFormat.format(now.getTime()));
+ mDateFormat.setSummary(shortDateFormat.format(dummyDate));
+ mTime24Pref.setSummary(DateFormat.getTimeFormat(getActivity()).format(dummyDate));
+
+ }
+ */
+
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
-
+
+ if (DEBUG) Log.d(TAG, "onSizeChanged: " + oldw + "x" + oldh + " to " + w + "x" + h);
+
+ if (mLastWidth == w && mLastHeight == h) {
+ return;
+ }
+
+ if (mLastWidth == 0 || mLastHeight == 0) {
+ return;
+ }
+
+ if (DEBUG) Log.d(TAG, "Rebuilding chart for: " + w + "x" + h);
+
+ mLastWidth = w;
+ mLastHeight = h;
+ mBitmap = null;
+ mCanvas = null;
+
int textHeight = mTextDescent - mTextAscent;
- mThinLineWidth = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- 2, getResources().getDisplayMetrics());
- if (h > (textHeight*6)) {
+ if (h > ((textHeight*10)+mChartMinHeight)) {
mLargeMode = true;
if (h > (textHeight*15)) {
// Plenty of room for the chart.
@@ -445,21 +724,17 @@ public class BatteryHistoryChart extends View {
// Compress lines to make more room for chart.
mLineWidth = textHeight/3;
}
- mLevelTop = textHeight + mLineWidth;
- mScreenOnPaint.setARGB(255, 32, 64, 255);
- mGpsOnPaint.setARGB(255, 32, 64, 255);
- mWifiRunningPaint.setARGB(255, 32, 64, 255);
- mWakeLockPaint.setARGB(255, 32, 64, 255);
} else {
mLargeMode = false;
mLineWidth = mThinLineWidth;
- mLevelTop = 0;
- mScreenOnPaint.setARGB(255, 0, 0, 255);
- mGpsOnPaint.setARGB(255, 0, 0, 255);
- mWifiRunningPaint.setARGB(255, 0, 0, 255);
- mWakeLockPaint.setARGB(255, 0, 0, 255);
}
if (mLineWidth <= 0) mLineWidth = 1;
+
+ mLevelTop = mHeaderHeight;
+ mLevelLeft = mMaxPercentLabelStringWidth + mThinLineWidth*3;
+ mLevelRight = w;
+ int levelWidth = mLevelRight-mLevelLeft;
+
mTextPaint.setStrokeWidth(mThinLineWidth);
mBatteryGoodPaint.setStrokeWidth(mThinLineWidth);
mBatteryWarnPaint.setStrokeWidth(mThinLineWidth);
@@ -468,27 +743,27 @@ public class BatteryHistoryChart extends View {
mScreenOnPaint.setStrokeWidth(mLineWidth);
mGpsOnPaint.setStrokeWidth(mLineWidth);
mWifiRunningPaint.setStrokeWidth(mLineWidth);
- mWakeLockPaint.setStrokeWidth(mLineWidth);
+ mCpuRunningPaint.setStrokeWidth(mLineWidth);
+ mDebugRectPaint.setStrokeWidth(1);
+
+ int fullBarOffset = textHeight + mLineWidth;
if (mLargeMode) {
- int barOffset = textHeight + mLineWidth;
mChargingOffset = mLineWidth;
- mScreenOnOffset = mChargingOffset + barOffset;
- mWakeLockOffset = mScreenOnOffset + barOffset;
- mWifiRunningOffset = mWakeLockOffset + barOffset;
- mGpsOnOffset = mWifiRunningOffset + (mHaveWifi ? barOffset : 0);
- mPhoneSignalOffset = mGpsOnOffset + (mHaveGps ? barOffset : 0);
- mLevelOffset = mPhoneSignalOffset + (mHavePhoneSignal ? barOffset : 0)
- + ((mLineWidth*3)/2);
+ mScreenOnOffset = mChargingOffset + fullBarOffset;
+ mCpuRunningOffset = mScreenOnOffset + fullBarOffset;
+ mWifiRunningOffset = mCpuRunningOffset + fullBarOffset;
+ mGpsOnOffset = mWifiRunningOffset + (mHaveWifi ? fullBarOffset : 0);
+ mPhoneSignalOffset = mGpsOnOffset + (mHaveGps ? fullBarOffset : 0);
+ mLevelOffset = mPhoneSignalOffset + (mHavePhoneSignal ? fullBarOffset : 0)
+ + mLineWidth*2 + mLineWidth/2;
if (mHavePhoneSignal) {
mPhoneSignalChart.init(w);
}
} else {
mScreenOnOffset = mGpsOnOffset = mWifiRunningOffset
- = mWakeLockOffset = mLineWidth;
- mChargingOffset = mLineWidth*2;
- mPhoneSignalOffset = 0;
- mLevelOffset = mLineWidth*3;
+ = mCpuRunningOffset = mChargingOffset = mPhoneSignalOffset = 0;
+ mLevelOffset = fullBarOffset + mThinLineWidth*4;
if (mHavePhoneSignal) {
mPhoneSignalChart.init(0);
}
@@ -497,34 +772,57 @@ public class BatteryHistoryChart extends View {
mBatLevelPath.reset();
mBatGoodPath.reset();
mBatWarnPath.reset();
+ mTimeRemainPath.reset();
mBatCriticalPath.reset();
mScreenOnPath.reset();
mGpsOnPath.reset();
mWifiRunningPath.reset();
- mWakeLockPath.reset();
+ mCpuRunningPath.reset();
mChargingPath.reset();
-
- final long timeStart = mHistStart;
- final long timeChange = mHistEnd-mHistStart;
-
+
+ mTimeLabels.clear();
+ mDateLabels.clear();
+
+ final long walltimeStart = mStartWallTime;
+ final long walltimeChange = mEndWallTime > walltimeStart
+ ? (mEndWallTime-walltimeStart) : 1;
+ long curWalltime = mStartWallTime;
+ long lastRealtime = 0;
+
final int batLow = mBatLow;
final int batChange = mBatHigh-mBatLow;
-
+
final int levelh = h - mLevelOffset - mLevelTop;
mLevelBottom = mLevelTop + levelh;
-
- int x = 0, y = 0, startX = 0, lastX = -1, lastY = -1;
+
+ int x = mLevelLeft, y = 0, startX = mLevelLeft, lastX = -1, lastY = -1;
int i = 0;
Path curLevelPath = null;
Path lastLinePath = null;
boolean lastCharging = false, lastScreenOn = false, lastGpsOn = false;
- boolean lastWifiRunning = false, lastWakeLock = false;
+ boolean lastWifiRunning = false, lastWifiSupplRunning = false, lastCpuRunning = false;
+ int lastWifiSupplState = BatteryStats.WIFI_SUPPL_STATE_INVALID;
final int N = mNumHist;
- if (mStats.startIteratingHistoryLocked()) {
+ if (mEndDataWallTime > mStartWallTime && mStats.startIteratingHistoryLocked()) {
final HistoryItem rec = new HistoryItem();
while (mStats.getNextHistoryLocked(rec) && i < N) {
- if (rec.cmd == BatteryStats.HistoryItem.CMD_UPDATE) {
- x = (int)(((rec.time-timeStart)*w)/timeChange);
+ if (rec.isDeltaData()) {
+ curWalltime += rec.time-lastRealtime;
+ lastRealtime = rec.time;
+ x = mLevelLeft + (int)(((curWalltime-walltimeStart)*levelWidth)/walltimeChange);
+ 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) {
@@ -533,17 +831,19 @@ public class BatteryHistoryChart extends View {
// Don't plot changes within a pixel.
Path path;
byte value = rec.batteryLevel;
- if (value <= BATTERY_CRITICAL) path = mBatCriticalPath;
- else if (value <= BATTERY_WARN) path = mBatWarnPath;
- else path = mBatGoodPath;
+ if (value <= mBatteryCriticalLevel) path = mBatCriticalPath;
+ else if (value <= mBatteryWarnLevel) path = mBatWarnPath;
+ else path = null; //mBatGoodPath;
if (path != lastLinePath) {
if (lastLinePath != null) {
lastLinePath.lineTo(x, y);
}
- path.moveTo(x, y);
+ if (path != null) {
+ path.moveTo(x, y);
+ }
lastLinePath = path;
- } else {
+ } else if (path != null) {
path.lineTo(x, y);
}
@@ -559,156 +859,421 @@ public class BatteryHistoryChart extends View {
}
}
- final boolean charging =
- (rec.states&HistoryItem.STATE_BATTERY_PLUGGED_FLAG) != 0;
- if (charging != lastCharging) {
- if (charging) {
- mChargingPath.moveTo(x, h-mChargingOffset);
- } else {
- mChargingPath.lineTo(x, h-mChargingOffset);
+ if (mLargeMode) {
+ final boolean charging =
+ (rec.states&HistoryItem.STATE_BATTERY_PLUGGED_FLAG) != 0;
+ if (charging != lastCharging) {
+ if (charging) {
+ mChargingPath.moveTo(x, h-mChargingOffset);
+ } else {
+ mChargingPath.lineTo(x, h-mChargingOffset);
+ }
+ lastCharging = charging;
}
- lastCharging = charging;
- }
- final boolean screenOn =
- (rec.states&HistoryItem.STATE_SCREEN_ON_FLAG) != 0;
- if (screenOn != lastScreenOn) {
- if (screenOn) {
- mScreenOnPath.moveTo(x, h-mScreenOnOffset);
- } else {
- mScreenOnPath.lineTo(x, h-mScreenOnOffset);
+ final boolean screenOn =
+ (rec.states&HistoryItem.STATE_SCREEN_ON_FLAG) != 0;
+ if (screenOn != lastScreenOn) {
+ if (screenOn) {
+ mScreenOnPath.moveTo(x, h-mScreenOnOffset);
+ } else {
+ mScreenOnPath.lineTo(x, h-mScreenOnOffset);
+ }
+ lastScreenOn = screenOn;
}
- lastScreenOn = screenOn;
- }
- final boolean gpsOn =
- (rec.states&HistoryItem.STATE_GPS_ON_FLAG) != 0;
- if (gpsOn != lastGpsOn) {
- if (gpsOn) {
- mGpsOnPath.moveTo(x, h-mGpsOnOffset);
- } else {
- mGpsOnPath.lineTo(x, h-mGpsOnOffset);
+ final boolean gpsOn =
+ (rec.states&HistoryItem.STATE_GPS_ON_FLAG) != 0;
+ if (gpsOn != lastGpsOn) {
+ if (gpsOn) {
+ mGpsOnPath.moveTo(x, h-mGpsOnOffset);
+ } else {
+ mGpsOnPath.lineTo(x, h-mGpsOnOffset);
+ }
+ lastGpsOn = gpsOn;
}
- lastGpsOn = gpsOn;
- }
- final boolean wifiRunning =
- (rec.states&HistoryItem.STATE_WIFI_RUNNING_FLAG) != 0;
- if (wifiRunning != lastWifiRunning) {
- if (wifiRunning) {
- mWifiRunningPath.moveTo(x, h-mWifiRunningOffset);
+ final int wifiSupplState =
+ ((rec.states2&HistoryItem.STATE2_WIFI_SUPPL_STATE_MASK)
+ >> HistoryItem.STATE2_WIFI_SUPPL_STATE_SHIFT);
+ boolean wifiRunning;
+ if (lastWifiSupplState != wifiSupplState) {
+ lastWifiSupplState = wifiSupplState;
+ switch (wifiSupplState) {
+ case BatteryStats.WIFI_SUPPL_STATE_DISCONNECTED:
+ case BatteryStats.WIFI_SUPPL_STATE_DORMANT:
+ case BatteryStats.WIFI_SUPPL_STATE_INACTIVE:
+ case BatteryStats.WIFI_SUPPL_STATE_INTERFACE_DISABLED:
+ case BatteryStats.WIFI_SUPPL_STATE_INVALID:
+ case BatteryStats.WIFI_SUPPL_STATE_UNINITIALIZED:
+ wifiRunning = lastWifiSupplRunning = false;
+ break;
+ default:
+ wifiRunning = lastWifiSupplRunning = true;
+ break;
+ }
} else {
- mWifiRunningPath.lineTo(x, h-mWifiRunningOffset);
+ wifiRunning = lastWifiSupplRunning;
+ }
+ if ((rec.states&(HistoryItem.STATE_WIFI_FULL_LOCK_FLAG
+ |HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG
+ |HistoryItem.STATE_WIFI_SCAN_FLAG)) != 0) {
+ wifiRunning = true;
+ }
+ if (wifiRunning != lastWifiRunning) {
+ if (wifiRunning) {
+ mWifiRunningPath.moveTo(x, h-mWifiRunningOffset);
+ } else {
+ mWifiRunningPath.lineTo(x, h-mWifiRunningOffset);
+ }
+ lastWifiRunning = wifiRunning;
}
- lastWifiRunning = wifiRunning;
- }
- final boolean wakeLock =
- (rec.states&HistoryItem.STATE_WAKE_LOCK_FLAG) != 0;
- if (wakeLock != lastWakeLock) {
- if (wakeLock) {
- mWakeLockPath.moveTo(x, h-mWakeLockOffset);
- } else {
- mWakeLockPath.lineTo(x, h-mWakeLockOffset);
+ final boolean cpuRunning =
+ (rec.states&HistoryItem.STATE_CPU_RUNNING_FLAG) != 0;
+ if (cpuRunning != lastCpuRunning) {
+ if (cpuRunning) {
+ mCpuRunningPath.moveTo(x, h - mCpuRunningOffset);
+ } else {
+ mCpuRunningPath.lineTo(x, h - mCpuRunningOffset);
+ }
+ lastCpuRunning = cpuRunning;
+ }
+
+ if (mLargeMode && mHavePhoneSignal) {
+ int bin;
+ if (((rec.states&HistoryItem.STATE_PHONE_STATE_MASK)
+ >> HistoryItem.STATE_PHONE_STATE_SHIFT)
+ == ServiceState.STATE_POWER_OFF) {
+ bin = 0;
+ } else if ((rec.states&HistoryItem.STATE_PHONE_SCANNING_FLAG) != 0) {
+ bin = 1;
+ } else {
+ bin = (rec.states&HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_MASK)
+ >> HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_SHIFT;
+ bin += 2;
+ }
+ mPhoneSignalChart.addTick(x, bin);
}
- lastWakeLock = wakeLock;
}
- if (mLargeMode && mHavePhoneSignal) {
- int bin;
- if (((rec.states&HistoryItem.STATE_PHONE_STATE_MASK)
- >> HistoryItem.STATE_PHONE_STATE_SHIFT)
- == ServiceState.STATE_POWER_OFF) {
- bin = 0;
- } else if ((rec.states&HistoryItem.STATE_PHONE_SCANNING_FLAG) != 0) {
- bin = 1;
+ } else {
+ long lastWalltime = curWalltime;
+ if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
+ || rec.cmd == HistoryItem.CMD_RESET) {
+ if (rec.currentTime >= mStartWallTime) {
+ curWalltime = rec.currentTime;
} else {
- bin = (rec.states&HistoryItem.STATE_SIGNAL_STRENGTH_MASK)
- >> HistoryItem.STATE_SIGNAL_STRENGTH_SHIFT;
- bin += 2;
+ curWalltime = mStartWallTime + (rec.time-mHistStart);
}
- mPhoneSignalChart.addTick(x, bin);
+ lastRealtime = rec.time;
}
- } else if (rec.cmd != BatteryStats.HistoryItem.CMD_OVERFLOW) {
- if (curLevelPath != null) {
- finishPaths(x+1, h, levelh, startX, lastY, curLevelPath, lastX,
- lastCharging, lastScreenOn, lastGpsOn, lastWifiRunning,
- lastWakeLock, lastLinePath);
- lastX = lastY = -1;
- curLevelPath = null;
- lastLinePath = null;
- lastCharging = lastScreenOn = lastGpsOn = lastWakeLock = false;
+ if (rec.cmd != HistoryItem.CMD_OVERFLOW
+ && (rec.cmd != HistoryItem.CMD_CURRENT_TIME
+ || Math.abs(lastWalltime-curWalltime) > (60*60*1000))) {
+ if (curLevelPath != null) {
+ finishPaths(x+1, h, levelh, startX, lastY, curLevelPath, lastX,
+ lastCharging, lastScreenOn, lastGpsOn, lastWifiRunning,
+ lastCpuRunning, lastLinePath);
+ lastX = lastY = -1;
+ curLevelPath = null;
+ lastLinePath = null;
+ lastCharging = lastScreenOn = lastGpsOn = lastCpuRunning = false;
+ }
}
}
i++;
}
+ mStats.finishIteratingHistoryLocked();
}
-
- finishPaths(w, h, levelh, startX, lastY, curLevelPath, lastX,
+
+ if (lastY < 0 || lastX < 0) {
+ // Didn't get any data...
+ x = lastX = mLevelLeft;
+ y = lastY = mLevelTop + levelh - ((mBatteryLevel-batLow)*(levelh-1))/batChange;
+ Path path;
+ byte value = (byte)mBatteryLevel;
+ if (value <= mBatteryCriticalLevel) path = mBatCriticalPath;
+ else if (value <= mBatteryWarnLevel) path = mBatWarnPath;
+ else path = null; //mBatGoodPath;
+ if (path != null) {
+ path.moveTo(x, y);
+ lastLinePath = path;
+ }
+ mBatLevelPath.moveTo(x, y);
+ curLevelPath = mBatLevelPath;
+ x = w;
+ } else {
+ // Figure out where the actual data ends on the screen.
+ x = mLevelLeft + (int)(((mEndDataWallTime-walltimeStart)*levelWidth)/walltimeChange);
+ if (x < 0) {
+ x = 0;
+ }
+ }
+
+ finishPaths(x, h, levelh, startX, lastY, curLevelPath, lastX,
lastCharging, lastScreenOn, lastGpsOn, lastWifiRunning,
- lastWakeLock, lastLinePath);
+ lastCpuRunning, lastLinePath);
+
+ if (x < w) {
+ // If we reserved room for the remaining time, create a final path to draw
+ // that part of the UI.
+ mTimeRemainPath.moveTo(x, lastY);
+ int fullY = mLevelTop + levelh - ((100-batLow)*(levelh-1))/batChange;
+ int emptyY = mLevelTop + levelh - ((0-batLow)*(levelh-1))/batChange;
+ if (mDischarging) {
+ mTimeRemainPath.lineTo(mLevelRight, emptyY);
+ } else {
+ mTimeRemainPath.lineTo(mLevelRight, fullY);
+ mTimeRemainPath.lineTo(mLevelRight, emptyY);
+ }
+ mTimeRemainPath.lineTo(x, emptyY);
+ mTimeRemainPath.close();
+ }
+
+ if (mStartWallTime > 0 && mEndWallTime > mStartWallTime) {
+ // Create the time labels at the bottom.
+ boolean is24hr = is24Hour();
+ Calendar calStart = Calendar.getInstance();
+ calStart.setTimeInMillis(mStartWallTime);
+ calStart.set(Calendar.MILLISECOND, 0);
+ calStart.set(Calendar.SECOND, 0);
+ calStart.set(Calendar.MINUTE, 0);
+ long startRoundTime = calStart.getTimeInMillis();
+ if (startRoundTime < mStartWallTime) {
+ calStart.set(Calendar.HOUR_OF_DAY, calStart.get(Calendar.HOUR_OF_DAY)+1);
+ startRoundTime = calStart.getTimeInMillis();
+ }
+ Calendar calEnd = Calendar.getInstance();
+ calEnd.setTimeInMillis(mEndWallTime);
+ calEnd.set(Calendar.MILLISECOND, 0);
+ calEnd.set(Calendar.SECOND, 0);
+ calEnd.set(Calendar.MINUTE, 0);
+ long endRoundTime = calEnd.getTimeInMillis();
+ if (startRoundTime < endRoundTime) {
+ addTimeLabel(calStart, mLevelLeft, mLevelRight, is24hr);
+ Calendar calMid = Calendar.getInstance();
+ calMid.setTimeInMillis(mStartWallTime+((mEndWallTime-mStartWallTime)/2));
+ calMid.set(Calendar.MILLISECOND, 0);
+ calMid.set(Calendar.SECOND, 0);
+ calMid.set(Calendar.MINUTE, 0);
+ long calMidMillis = calMid.getTimeInMillis();
+ if (calMidMillis > startRoundTime && calMidMillis < endRoundTime) {
+ addTimeLabel(calMid, mLevelLeft, mLevelRight, is24hr);
+ }
+ addTimeLabel(calEnd, mLevelLeft, mLevelRight, is24hr);
+ }
+
+ // Create the date labels if the chart includes multiple days
+ if (calStart.get(Calendar.DAY_OF_YEAR) != calEnd.get(Calendar.DAY_OF_YEAR) ||
+ calStart.get(Calendar.YEAR) != calEnd.get(Calendar.YEAR)) {
+ boolean isDayFirst = isDayFirst();
+ calStart.set(Calendar.HOUR_OF_DAY, 0);
+ startRoundTime = calStart.getTimeInMillis();
+ if (startRoundTime < mStartWallTime) {
+ calStart.set(Calendar.DAY_OF_YEAR, calStart.get(Calendar.DAY_OF_YEAR) + 1);
+ startRoundTime = calStart.getTimeInMillis();
+ }
+ calEnd.set(Calendar.HOUR_OF_DAY, 0);
+ endRoundTime = calEnd.getTimeInMillis();
+ if (startRoundTime < endRoundTime) {
+ addDateLabel(calStart, mLevelLeft, mLevelRight, isDayFirst);
+ Calendar calMid = Calendar.getInstance();
+ calMid.setTimeInMillis(startRoundTime + ((endRoundTime - startRoundTime) / 2));
+ calMid.set(Calendar.HOUR_OF_DAY, 0);
+ long calMidMillis = calMid.getTimeInMillis();
+ if (calMidMillis > startRoundTime && calMidMillis < endRoundTime) {
+ addDateLabel(calMid, mLevelLeft, mLevelRight, isDayFirst);
+ }
+ }
+ addDateLabel(calEnd, mLevelLeft, mLevelRight, isDayFirst);
+ }
+ }
+
+ if (mTimeLabels.size() < 2) {
+ // If there are fewer than 2 time labels, then they are useless. Just
+ // show an axis label giving the entire duration.
+ mDurationString = Formatter.formatShortElapsedTime(getContext(),
+ mEndWallTime - mStartWallTime);
+ mDurationStringWidth = (int)mTextPaint.measureText(mDurationString);
+ } else {
+ mDurationString = null;
+ mDurationStringWidth = 0;
+ }
}
-
+
+ void addTimeLabel(Calendar cal, int levelLeft, int levelRight, boolean is24hr) {
+ final long walltimeStart = mStartWallTime;
+ final long walltimeChange = mEndWallTime-walltimeStart;
+ mTimeLabels.add(new TimeLabel(mTextPaint,
+ levelLeft + (int)(((cal.getTimeInMillis()-walltimeStart)*(levelRight-levelLeft))
+ / walltimeChange),
+ cal, is24hr));
+ }
+
+ void addDateLabel(Calendar cal, int levelLeft, int levelRight, boolean isDayFirst) {
+ final long walltimeStart = mStartWallTime;
+ final long walltimeChange = mEndWallTime-walltimeStart;
+ mDateLabels.add(new DateLabel(mTextPaint,
+ levelLeft + (int)(((cal.getTimeInMillis()-walltimeStart)*(levelRight-levelLeft))
+ / walltimeChange),
+ cal, isDayFirst));
+ }
+
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
final int width = getWidth();
final int height = getHeight();
+
+ //buildBitmap(width, height);
+
+ if (DEBUG) Log.d(TAG, "onDraw: " + width + "x" + height);
+ //canvas.drawBitmap(mBitmap, 0, 0, null);
+ drawChart(canvas, width, height);
+ }
+
+ void buildBitmap(int width, int height) {
+ if (mBitmap != null && width == mBitmap.getWidth() && height == mBitmap.getHeight()) {
+ return;
+ }
+
+ if (DEBUG) Log.d(TAG, "buildBitmap: " + width + "x" + height);
+
+ mBitmap = Bitmap.createBitmap(getResources().getDisplayMetrics(), width, height,
+ Bitmap.Config.ARGB_8888);
+ mCanvas = new Canvas(mBitmap);
+ drawChart(mCanvas, width, height);
+ }
+
+ void drawChart(Canvas canvas, int width, int height) {
final boolean layoutRtl = isLayoutRtl();
final int textStartX = layoutRtl ? width : 0;
- mTextPaint.setTextAlign(layoutRtl ? Paint.Align.RIGHT : Paint.Align.LEFT);
+ final int textEndX = layoutRtl ? 0 : width;
+ final Paint.Align textAlignLeft = layoutRtl ? Paint.Align.RIGHT : Paint.Align.LEFT;
+ final Paint.Align textAlignRight = layoutRtl ? Paint.Align.LEFT : Paint.Align.RIGHT;
+ if (DEBUG) {
+ canvas.drawRect(1, 1, width, height, mDebugRectPaint);
+ }
+
+ if (DEBUG) Log.d(TAG, "Drawing level path.");
canvas.drawPath(mBatLevelPath, mBatteryBackgroundPaint);
- if (mLargeMode) {
- int durationHalfWidth = mTotalDurationStringWidth / 2;
- if (layoutRtl) durationHalfWidth = -durationHalfWidth;
- canvas.drawText(mDurationString, textStartX, -mTextAscent + (mLineWidth / 2),
- mTextPaint);
- canvas.drawText(mTotalDurationString, (width / 2) - durationHalfWidth,
- mLevelBottom - mTextAscent + mThinLineWidth, mTextPaint);
- } else {
- int durationHalfWidth = mDurationStringWidth / 2;
- if (layoutRtl) durationHalfWidth = -durationHalfWidth;
- canvas.drawText(mDurationString, (width / 2) - durationHalfWidth,
- (height / 2) - ((mTextDescent - mTextAscent) / 2) - mTextAscent, mTextPaint);
+ if (!mTimeRemainPath.isEmpty()) {
+ if (DEBUG) Log.d(TAG, "Drawing time remain path.");
+ canvas.drawPath(mTimeRemainPath, mTimeRemainPaint);
+ }
+ if (mTimeLabels.size() > 1) {
+ int y = mLevelBottom - mTextAscent + (mThinLineWidth*4);
+ int ytick = mLevelBottom+mThinLineWidth+(mThinLineWidth/2);
+ mTextPaint.setTextAlign(Paint.Align.LEFT);
+ int lastX = 0;
+ for (int i=0; i<mTimeLabels.size(); i++) {
+ TimeLabel label = mTimeLabels.get(i);
+ if (i == 0) {
+ int x = label.x - label.width/2;
+ if (x < 0) {
+ x = 0;
+ }
+ if (DEBUG) Log.d(TAG, "Drawing left label: " + label.label + " @ " + x);
+ canvas.drawText(label.label, x, y, mTextPaint);
+ canvas.drawLine(label.x, ytick, label.x, ytick+mThinLineWidth, mTextPaint);
+ lastX = x + label.width;
+ } else if (i < (mTimeLabels.size()-1)) {
+ int x = label.x - label.width/2;
+ if (x < (lastX+mTextAscent)) {
+ continue;
+ }
+ TimeLabel nextLabel = mTimeLabels.get(i+1);
+ if (x > (width-nextLabel.width-mTextAscent)) {
+ continue;
+ }
+ if (DEBUG) Log.d(TAG, "Drawing middle label: " + label.label + " @ " + x);
+ canvas.drawText(label.label, x, y, mTextPaint);
+ canvas.drawLine(label.x, ytick, label.x, ytick + mThinLineWidth, mTextPaint);
+ lastX = x + label.width;
+ } else {
+ int x = label.x - label.width/2;
+ if ((x+label.width) >= width) {
+ x = width-1-label.width;
+ }
+ if (DEBUG) Log.d(TAG, "Drawing right label: " + label.label + " @ " + x);
+ canvas.drawText(label.label, x, y, mTextPaint);
+ canvas.drawLine(label.x, ytick, label.x, ytick+mThinLineWidth, mTextPaint);
+ }
+ }
+ } else if (mDurationString != null) {
+ int y = mLevelBottom - mTextAscent + (mThinLineWidth*4);
+ mTextPaint.setTextAlign(Paint.Align.LEFT);
+ canvas.drawText(mDurationString,
+ mLevelLeft + (mLevelRight-mLevelLeft)/2 - mDurationStringWidth/2,
+ y, mTextPaint);
}
+
+ int headerTop = -mHeaderTextAscent + (mHeaderTextDescent-mHeaderTextAscent)/3;
+ mHeaderTextPaint.setTextAlign(textAlignLeft);
+ if (DEBUG) Log.d(TAG, "Drawing charge label string: " + mChargeLabelString);
+ canvas.drawText(mChargeLabelString, textStartX, headerTop, mHeaderTextPaint);
+ int stringHalfWidth = mChargeDurationStringWidth / 2;
+ if (layoutRtl) stringHalfWidth = -stringHalfWidth;
+ int headerCenter = ((width-mChargeDurationStringWidth-mDrainStringWidth)/2)
+ + (layoutRtl ? mDrainStringWidth : mChargeLabelStringWidth);
+ if (DEBUG) Log.d(TAG, "Drawing charge duration string: " + mChargeDurationString);
+ canvas.drawText(mChargeDurationString, headerCenter - stringHalfWidth, headerTop,
+ mHeaderTextPaint);
+ mHeaderTextPaint.setTextAlign(textAlignRight);
+ if (DEBUG) Log.d(TAG, "Drawing drain string: " + mDrainString);
+ canvas.drawText(mDrainString, textEndX, headerTop, mHeaderTextPaint);
+
if (!mBatGoodPath.isEmpty()) {
+ if (DEBUG) Log.d(TAG, "Drawing good battery path");
canvas.drawPath(mBatGoodPath, mBatteryGoodPaint);
}
if (!mBatWarnPath.isEmpty()) {
+ if (DEBUG) Log.d(TAG, "Drawing warn battery path");
canvas.drawPath(mBatWarnPath, mBatteryWarnPaint);
}
if (!mBatCriticalPath.isEmpty()) {
+ if (DEBUG) Log.d(TAG, "Drawing critical battery path");
canvas.drawPath(mBatCriticalPath, mBatteryCriticalPaint);
}
if (mHavePhoneSignal) {
+ if (DEBUG) Log.d(TAG, "Drawing phone signal path");
int top = height-mPhoneSignalOffset - (mLineWidth/2);
mPhoneSignalChart.draw(canvas, top, mLineWidth);
}
if (!mScreenOnPath.isEmpty()) {
+ if (DEBUG) Log.d(TAG, "Drawing screen on path");
canvas.drawPath(mScreenOnPath, mScreenOnPaint);
}
if (!mChargingPath.isEmpty()) {
+ if (DEBUG) Log.d(TAG, "Drawing charging path");
canvas.drawPath(mChargingPath, mChargingPaint);
}
if (mHaveGps) {
if (!mGpsOnPath.isEmpty()) {
+ if (DEBUG) Log.d(TAG, "Drawing gps path");
canvas.drawPath(mGpsOnPath, mGpsOnPaint);
}
}
if (mHaveWifi) {
if (!mWifiRunningPath.isEmpty()) {
+ if (DEBUG) Log.d(TAG, "Drawing wifi path");
canvas.drawPath(mWifiRunningPath, mWifiRunningPaint);
}
}
- if (!mWakeLockPath.isEmpty()) {
- canvas.drawPath(mWakeLockPath, mWakeLockPaint);
+ if (!mCpuRunningPath.isEmpty()) {
+ if (DEBUG) Log.d(TAG, "Drawing running path");
+ canvas.drawPath(mCpuRunningPath, mCpuRunningPaint);
}
if (mLargeMode) {
+ if (DEBUG) Log.d(TAG, "Drawing large mode labels");
+ Paint.Align align = mTextPaint.getTextAlign();
+ mTextPaint.setTextAlign(textAlignLeft); // large-mode labels always aligned to start
if (mHavePhoneSignal) {
canvas.drawText(mPhoneSignalLabel, textStartX,
height - mPhoneSignalOffset - mTextDescent, mTextPaint);
@@ -721,19 +1286,59 @@ public class BatteryHistoryChart extends View {
canvas.drawText(mWifiRunningLabel, textStartX,
height - mWifiRunningOffset - mTextDescent, mTextPaint);
}
- canvas.drawText(mWakeLockLabel, textStartX,
- height - mWakeLockOffset - mTextDescent, mTextPaint);
+ canvas.drawText(mCpuRunningLabel, textStartX,
+ height - mCpuRunningOffset - mTextDescent, mTextPaint);
canvas.drawText(mChargingLabel, textStartX,
height - mChargingOffset - mTextDescent, mTextPaint);
canvas.drawText(mScreenOnLabel, textStartX,
height - mScreenOnOffset - mTextDescent, mTextPaint);
- canvas.drawLine(0, mLevelBottom+(mThinLineWidth/2), width,
- mLevelBottom+(mThinLineWidth/2), mTextPaint);
- canvas.drawLine(0, mLevelTop, 0,
- mLevelBottom+(mThinLineWidth/2), mTextPaint);
+ mTextPaint.setTextAlign(align);
+ }
+
+ canvas.drawLine(mLevelLeft-mThinLineWidth, mLevelTop, mLevelLeft-mThinLineWidth,
+ mLevelBottom+(mThinLineWidth/2), mTextPaint);
+ if (mLargeMode) {
for (int i=0; i<10; i++) {
- int y = mLevelTop + ((mLevelBottom-mLevelTop)*i)/10;
- canvas.drawLine(0, y, mThinLineWidth*2, y, mTextPaint);
+ int y = mLevelTop + mThinLineWidth/2 + ((mLevelBottom-mLevelTop)*i)/10;
+ canvas.drawLine(mLevelLeft-mThinLineWidth*2-mThinLineWidth/2, y,
+ mLevelLeft-mThinLineWidth-mThinLineWidth/2, y, mTextPaint);
+ }
+ }
+ if (DEBUG) Log.d(TAG, "Drawing max percent, origw=" + mMaxPercentLabelStringWidth
+ + ", noww=" + (int)mTextPaint.measureText(mMaxPercentLabelString));
+ canvas.drawText(mMaxPercentLabelString, 0, mLevelTop, mTextPaint);
+ canvas.drawText(mMinPercentLabelString,
+ mMaxPercentLabelStringWidth-mMinPercentLabelStringWidth,
+ mLevelBottom - mThinLineWidth, mTextPaint);
+ canvas.drawLine(mLevelLeft/2, mLevelBottom+mThinLineWidth, width,
+ mLevelBottom+mThinLineWidth, mTextPaint);
+
+ if (mDateLabels.size() > 0) {
+ int ytop = mLevelTop + mTextAscent;
+ int ybottom = mLevelBottom;
+ int lastLeft = mLevelRight;
+ mTextPaint.setTextAlign(Paint.Align.LEFT);
+ for (int i=mDateLabels.size()-1; i>=0; i--) {
+ DateLabel label = mDateLabels.get(i);
+ int left = label.x - mThinLineWidth;
+ int x = label.x + mThinLineWidth*2;
+ if ((x+label.width) >= lastLeft) {
+ x = label.x - mThinLineWidth*2 - label.width;
+ left = x - mThinLineWidth;
+ if (left >= lastLeft) {
+ // okay we give up.
+ continue;
+ }
+ }
+ if (left < mLevelLeft) {
+ // Won't fit on left, give up.
+ continue;
+ }
+ mDateLinePath.reset();
+ mDateLinePath.moveTo(label.x, ytop);
+ mDateLinePath.lineTo(label.x, ybottom);
+ canvas.drawPath(mDateLinePath, mDateLinePaint);
+ canvas.drawText(label.label, x, ytop - mTextAscent, mTextPaint);
}
}
}
diff --git a/src/com/android/settings/fuelgauge/BatteryHistoryDetail.java b/src/com/android/settings/fuelgauge/BatteryHistoryDetail.java
index ad6fb30..63e4e13 100644
--- a/src/com/android/settings/fuelgauge/BatteryHistoryDetail.java
+++ b/src/com/android/settings/fuelgauge/BatteryHistoryDetail.java
@@ -17,37 +17,37 @@
package com.android.settings.fuelgauge;
import android.app.Fragment;
+import android.content.Intent;
+import android.os.BatteryStats;
import android.os.Bundle;
-import android.os.Parcel;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import com.android.internal.os.BatteryStatsImpl;
+import com.android.internal.os.BatteryStatsHelper;
import com.android.settings.R;
public class BatteryHistoryDetail extends Fragment {
public static final String EXTRA_STATS = "stats";
+ public static final String EXTRA_BROADCAST = "broadcast";
- private BatteryStatsImpl mStats;
+ private BatteryStats mStats;
+ private Intent mBatteryBroadcast;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
- byte[] data = getArguments().getByteArray(EXTRA_STATS);
- Parcel parcel = Parcel.obtain();
- parcel.unmarshall(data, 0, data.length);
- parcel.setDataPosition(0);
- mStats = com.android.internal.os.BatteryStatsImpl.CREATOR
- .createFromParcel(parcel);
+ String histFile = getArguments().getString(EXTRA_STATS);
+ mStats = BatteryStatsHelper.statsFromFile(getActivity(), histFile);
+ mBatteryBroadcast = getArguments().getParcelable(EXTRA_BROADCAST);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.preference_batteryhistory, null);
+ View view = inflater.inflate(R.layout.battery_history_chart, null);
BatteryHistoryChart chart = (BatteryHistoryChart)view.findViewById(
R.id.battery_history_chart);
- chart.setStats(mStats);
+ chart.setStats(mStats, mBatteryBroadcast);
return view;
}
}
diff --git a/src/com/android/settings/fuelgauge/BatteryHistoryPreference.java b/src/com/android/settings/fuelgauge/BatteryHistoryPreference.java
index 4579db7..e7326b1 100644
--- a/src/com/android/settings/fuelgauge/BatteryHistoryPreference.java
+++ b/src/com/android/settings/fuelgauge/BatteryHistoryPreference.java
@@ -17,10 +17,12 @@
package com.android.settings.fuelgauge;
import android.content.Context;
+import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.BatteryStats;
import android.preference.Preference;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
@@ -33,24 +35,55 @@ import com.android.settings.R;
*/
public class BatteryHistoryPreference extends Preference {
- private BatteryStats mStats;
+ final private BatteryStats mStats;
+ final private Intent mBatteryBroadcast;
- public BatteryHistoryPreference(Context context, BatteryStats stats) {
+ private boolean mHideLabels;
+ private View mLabelHeader;
+ private BatteryHistoryChart mChart;
+
+ public BatteryHistoryPreference(Context context, BatteryStats stats, Intent batteryBroadcast) {
super(context);
setLayoutResource(R.layout.preference_batteryhistory);
mStats = stats;
+ mBatteryBroadcast = batteryBroadcast;
}
BatteryStats getStats() {
return mStats;
}
+ public void setHideLabels(boolean hide) {
+ if (mHideLabels != hide) {
+ mHideLabels = hide;
+ if (mLabelHeader != null) {
+ mLabelHeader.setVisibility(hide ? View.GONE : View.VISIBLE);
+ }
+ }
+ }
+
@Override
protected void onBindView(View view) {
super.onBindView(view);
BatteryHistoryChart chart = (BatteryHistoryChart)view.findViewById(
R.id.battery_history_chart);
- chart.setStats(mStats);
+ if (mChart == null) {
+ // First time: use and initialize this chart.
+ chart.setStats(mStats, mBatteryBroadcast);
+ mChart = chart;
+ } else {
+ // All future times: forget the newly inflated chart, re-use the
+ // already initialized chart from last time.
+ ViewGroup parent = (ViewGroup)chart.getParent();
+ int index = parent.indexOfChild(chart);
+ parent.removeViewAt(index);
+ if (mChart.getParent() != null) {
+ ((ViewGroup)mChart.getParent()).removeView(mChart);
+ }
+ parent.addView(mChart, index);
+ }
+ mLabelHeader = view.findViewById(R.id.labelsHeader);
+ mLabelHeader.setVisibility(mHideLabels ? View.GONE : View.VISIBLE);
}
}
diff --git a/src/com/android/settings/fuelgauge/BatterySaverSettings.java b/src/com/android/settings/fuelgauge/BatterySaverSettings.java
new file mode 100644
index 0000000..bd989d0
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/BatterySaverSettings.java
@@ -0,0 +1,217 @@
+/*
+ * 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.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.SettingsPreferenceFragment;
+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
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ if (mCreated) 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, 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/BatterySipper.java b/src/com/android/settings/fuelgauge/BatterySipper.java
deleted file mode 100644
index fcc8f69..0000000
--- a/src/com/android/settings/fuelgauge/BatterySipper.java
+++ /dev/null
@@ -1,242 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.settings.fuelgauge;
-
-import com.android.settings.R;
-import com.android.settings.fuelgauge.PowerUsageDetail.DrainType;
-
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.BatteryStats.Uid;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-
-/**
- * Contains information about package name, icon image, power usage about an
- * application or a system service.
- */
-public class BatterySipper implements Comparable<BatterySipper> {
- final Context mContext;
- /* Cache cleared when PowerUsageSummary is destroyed */
- static final HashMap<String,UidToDetail> sUidCache = new HashMap<String,UidToDetail>();
- final ArrayList<BatterySipper> mRequestQueue;
- final Handler mHandler;
- String name;
- Drawable icon;
- int iconId; // For passing to the detail screen.
- Uid uidObj;
- double value;
- double[] values;
- DrainType drainType;
- long usageTime;
- long cpuTime;
- long gpsTime;
- long wifiRunningTime;
- long cpuFgTime;
- long wakeLockTime;
- long mobileRxBytes;
- long mobileTxBytes;
- long wifiRxBytes;
- long wifiTxBytes;
- double percent;
- double noCoveragePercent;
- String defaultPackageName;
- String[] mPackages;
-
- static class UidToDetail {
- String name;
- String packageName;
- Drawable icon;
- }
-
- BatterySipper(Context context, ArrayList<BatterySipper> requestQueue,
- Handler handler, String label, DrainType drainType,
- int iconId, Uid uid, double[] values) {
- mContext = context;
- mRequestQueue = requestQueue;
- mHandler = handler;
- this.values = values;
- name = label;
- this.drainType = drainType;
- if (iconId > 0) {
- icon = mContext.getResources().getDrawable(iconId);
- }
- if (values != null) value = values[0];
- if ((label == null || iconId == 0) && uid != null) {
- getQuickNameIconForUid(uid);
- }
- uidObj = uid;
- }
-
- double getSortValue() {
- return value;
- }
-
- double[] getValues() {
- return values;
- }
-
- public Drawable getIcon() {
- return icon;
- }
-
- /**
- * Gets the application name
- */
- public String getLabel() {
- return name;
- }
-
- @Override
- public int compareTo(BatterySipper other) {
- // Return the flipped value because we want the items in descending order
- return Double.compare(other.getSortValue(), getSortValue());
- }
-
- /**
- * Gets a list of packages associated with the current user
- */
- public String[] getPackages() {
- return mPackages;
- }
-
- public int getUid() {
- // Bail out if the current sipper is not an App sipper.
- if (uidObj == null) {
- return 0;
- }
- return uidObj.getUid();
- }
-
- void getQuickNameIconForUid(Uid uidObj) {
- final int uid = uidObj.getUid();
- final String uidString = Integer.toString(uid);
- if (sUidCache.containsKey(uidString)) {
- UidToDetail utd = sUidCache.get(uidString);
- defaultPackageName = utd.packageName;
- name = utd.name;
- icon = utd.icon;
- return;
- }
- PackageManager pm = mContext.getPackageManager();
- String[] packages = pm.getPackagesForUid(uid);
- icon = pm.getDefaultActivityIcon();
- if (packages == null) {
- //name = Integer.toString(uid);
- if (uid == 0) {
- name = mContext.getResources().getString(R.string.process_kernel_label);
- } else if ("mediaserver".equals(name)) {
- name = mContext.getResources().getString(R.string.process_mediaserver_label);
- }
- iconId = R.drawable.ic_power_system;
- icon = mContext.getResources().getDrawable(iconId);
- return;
- } else {
- //name = packages[0];
- }
- if (mHandler != null) {
- synchronized (mRequestQueue) {
- mRequestQueue.add(this);
- }
- }
- }
-
- public static void clearUidCache() {
- sUidCache.clear();
- }
-
- /**
- * Loads the app label and icon image and stores into the cache.
- */
- public void loadNameAndIcon() {
- // Bail out if the current sipper is not an App sipper.
- if (uidObj == null) {
- return;
- }
- PackageManager pm = mContext.getPackageManager();
- final int uid = uidObj.getUid();
- final Drawable defaultActivityIcon = pm.getDefaultActivityIcon();
- mPackages = pm.getPackagesForUid(uid);
- if (mPackages == null) {
- name = Integer.toString(uid);
- return;
- }
-
- String[] packageLabels = new String[mPackages.length];
- System.arraycopy(mPackages, 0, packageLabels, 0, mPackages.length);
-
- int preferredIndex = -1;
- // Convert package names to user-facing labels where possible
- for (int i = 0; i < packageLabels.length; i++) {
- // Check if package matches preferred package
- if (packageLabels[i].equals(name)) preferredIndex = i;
- try {
- ApplicationInfo ai = pm.getApplicationInfo(packageLabels[i], 0);
- CharSequence label = ai.loadLabel(pm);
- if (label != null) {
- packageLabels[i] = label.toString();
- }
- if (ai.icon != 0) {
- defaultPackageName = mPackages[i];
- icon = ai.loadIcon(pm);
- break;
- }
- } catch (NameNotFoundException e) {
- }
- }
- if (icon == null) icon = defaultActivityIcon;
-
- if (packageLabels.length == 1) {
- name = packageLabels[0];
- } else {
- // Look for an official name for this UID.
- for (String pkgName : mPackages) {
- try {
- final PackageInfo pi = pm.getPackageInfo(pkgName, 0);
- if (pi.sharedUserLabel != 0) {
- final CharSequence nm = pm.getText(pkgName,
- pi.sharedUserLabel, pi.applicationInfo);
- if (nm != null) {
- name = nm.toString();
- if (pi.applicationInfo.icon != 0) {
- defaultPackageName = pkgName;
- icon = pi.applicationInfo.loadIcon(pm);
- }
- break;
- }
- }
- } catch (PackageManager.NameNotFoundException e) {
- }
- }
- }
- final String uidString = Integer.toString(uidObj.getUid());
- UidToDetail utd = new UidToDetail();
- utd.name = name;
- utd.icon = icon;
- utd.packageName = defaultPackageName;
- sUidCache.put(uidString, utd);
- if (mHandler != null) {
- mHandler.sendMessage(
- mHandler.obtainMessage(BatteryStatsHelper.MSG_UPDATE_NAME_ICON, this));
- }
- }
-}
diff --git a/src/com/android/settings/fuelgauge/BatteryStatsHelper.java b/src/com/android/settings/fuelgauge/BatteryStatsHelper.java
deleted file mode 100644
index 0191692..0000000
--- a/src/com/android/settings/fuelgauge/BatteryStatsHelper.java
+++ /dev/null
@@ -1,836 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.fuelgauge;
-
-import static android.os.BatteryStats.NETWORK_MOBILE_RX_BYTES;
-import static android.os.BatteryStats.NETWORK_MOBILE_TX_BYTES;
-import static android.os.BatteryStats.NETWORK_WIFI_RX_BYTES;
-import static android.os.BatteryStats.NETWORK_WIFI_TX_BYTES;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.pm.UserInfo;
-import android.graphics.drawable.Drawable;
-import android.hardware.Sensor;
-import android.hardware.SensorManager;
-import android.os.BatteryStats;
-import android.os.BatteryStats.Uid;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Parcel;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.preference.PreferenceActivity;
-import android.telephony.SignalStrength;
-import android.util.Log;
-import android.util.SparseArray;
-
-import com.android.internal.app.IBatteryStats;
-import com.android.internal.os.BatteryStatsImpl;
-import com.android.internal.os.PowerProfile;
-import com.android.internal.util.FastPrintWriter;
-import com.android.settings.R;
-import com.android.settings.fuelgauge.PowerUsageDetail.DrainType;
-import com.android.settings.users.UserUtils;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.io.Writer;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-/**
- * A helper class for retrieving the power usage information for all applications and services.
- *
- * The caller must initialize this class as soon as activity object is ready to use (for example, in
- * onAttach() for Fragment), call create() in onCreate() and call destroy() in onDestroy().
- */
-public class BatteryStatsHelper {
-
- private static final boolean DEBUG = false;
-
- private static final String TAG = BatteryStatsHelper.class.getSimpleName();
-
- private static BatteryStatsImpl sStatsXfer;
- private IBatteryStats mBatteryInfo;
- private UserManager mUm;
- private BatteryStatsImpl mStats;
- private PowerProfile mPowerProfile;
-
- private final List<BatterySipper> mUsageList = new ArrayList<BatterySipper>();
- private final List<BatterySipper> mWifiSippers = new ArrayList<BatterySipper>();
- private final List<BatterySipper> mBluetoothSippers = new ArrayList<BatterySipper>();
- private final SparseArray<List<BatterySipper>> mUserSippers
- = new SparseArray<List<BatterySipper>>();
- private final SparseArray<Double> mUserPower = new SparseArray<Double>();
-
- private int mStatsType = BatteryStats.STATS_SINCE_CHARGED;
-
- private long mStatsPeriod = 0;
- private double mMaxPower = 1;
- private double mTotalPower;
- private double mWifiPower;
- private double mBluetoothPower;
-
- // How much the apps together have left WIFI running.
- private long mAppWifiRunning;
-
- /** Queue for fetching name and icon for an application */
- private ArrayList<BatterySipper> mRequestQueue = new ArrayList<BatterySipper>();
-
- private Activity mActivity;
- private Handler mHandler;
-
- private class NameAndIconLoader extends Thread {
- private boolean mAbort = false;
-
- public NameAndIconLoader() {
- super("BatteryUsage Icon Loader");
- }
-
- public void abort() {
- mAbort = true;
- }
-
- @Override
- public void run() {
- while (true) {
- BatterySipper bs;
- synchronized (mRequestQueue) {
- if (mRequestQueue.isEmpty() || mAbort) {
- mHandler.sendEmptyMessage(MSG_REPORT_FULLY_DRAWN);
- return;
- }
- bs = mRequestQueue.remove(0);
- }
- bs.loadNameAndIcon();
- }
- }
- }
-
- private NameAndIconLoader mRequestThread;
-
- public BatteryStatsHelper(Activity activity, Handler handler) {
- mActivity = activity;
- mHandler = handler;
- }
-
- /** Clears the current stats and forces recreating for future use. */
- public void clearStats() {
- mStats = null;
- }
-
- public BatteryStatsImpl getStats() {
- if (mStats == null) {
- load();
- }
- return mStats;
- }
-
- public PowerProfile getPowerProfile() {
- return mPowerProfile;
- }
-
- public void create(Bundle icicle) {
- if (icicle != null) {
- mStats = sStatsXfer;
- }
- mBatteryInfo = IBatteryStats.Stub.asInterface(
- ServiceManager.getService(BatteryStats.SERVICE_NAME));
- mUm = (UserManager) mActivity.getSystemService(Context.USER_SERVICE);
- mPowerProfile = new PowerProfile(mActivity);
- }
-
- public void pause() {
- if (mRequestThread != null) {
- mRequestThread.abort();
- }
- }
-
- public void destroy() {
- if (mActivity.isChangingConfigurations()) {
- sStatsXfer = mStats;
- } else {
- BatterySipper.sUidCache.clear();
- }
- }
-
- public void startBatteryDetailPage(
- PreferenceActivity caller, BatterySipper sipper, boolean showLocationButton) {
- // Initialize mStats if necessary.
- getStats();
-
- Bundle args = new Bundle();
- args.putString(PowerUsageDetail.EXTRA_TITLE, sipper.name);
- args.putInt(PowerUsageDetail.EXTRA_PERCENT, (int)
- Math.ceil(sipper.getSortValue() * 100 / mTotalPower));
- args.putInt(PowerUsageDetail.EXTRA_GAUGE, (int)
- Math.ceil(sipper.getSortValue() * 100 / mMaxPower));
- args.putLong(PowerUsageDetail.EXTRA_USAGE_DURATION, mStatsPeriod);
- args.putString(PowerUsageDetail.EXTRA_ICON_PACKAGE, sipper.defaultPackageName);
- args.putInt(PowerUsageDetail.EXTRA_ICON_ID, sipper.iconId);
- args.putDouble(PowerUsageDetail.EXTRA_NO_COVERAGE, sipper.noCoveragePercent);
- if (sipper.uidObj != null) {
- args.putInt(PowerUsageDetail.EXTRA_UID, sipper.uidObj.getUid());
- }
- args.putSerializable(PowerUsageDetail.EXTRA_DRAIN_TYPE, sipper.drainType);
- args.putBoolean(PowerUsageDetail.EXTRA_SHOW_LOCATION_BUTTON, showLocationButton);
-
- int[] types;
- double[] values;
- switch (sipper.drainType) {
- case APP:
- case USER:
- {
- Uid uid = sipper.uidObj;
- types = new int[] {
- R.string.usage_type_cpu,
- R.string.usage_type_cpu_foreground,
- R.string.usage_type_wake_lock,
- R.string.usage_type_gps,
- R.string.usage_type_wifi_running,
- R.string.usage_type_data_recv,
- R.string.usage_type_data_send,
- R.string.usage_type_data_wifi_recv,
- R.string.usage_type_data_wifi_send,
- R.string.usage_type_audio,
- R.string.usage_type_video,
- };
- values = new double[] {
- sipper.cpuTime,
- sipper.cpuFgTime,
- sipper.wakeLockTime,
- sipper.gpsTime,
- sipper.wifiRunningTime,
- sipper.mobileRxBytes,
- sipper.mobileTxBytes,
- sipper.wifiRxBytes,
- sipper.wifiTxBytes,
- 0,
- 0
- };
-
- if (sipper.drainType == DrainType.APP) {
- Writer result = new StringWriter();
- PrintWriter printWriter = new FastPrintWriter(result, false, 1024);
- mStats.dumpLocked(printWriter, "", mStatsType, uid.getUid());
- printWriter.flush();
- args.putString(PowerUsageDetail.EXTRA_REPORT_DETAILS, result.toString());
-
- result = new StringWriter();
- printWriter = new FastPrintWriter(result, false, 1024);
- mStats.dumpCheckinLocked(printWriter, mStatsType, uid.getUid());
- printWriter.flush();
- args.putString(PowerUsageDetail.EXTRA_REPORT_CHECKIN_DETAILS,
- result.toString());
- }
- }
- break;
- case CELL:
- {
- types = new int[] {
- R.string.usage_type_on_time,
- R.string.usage_type_no_coverage
- };
- values = new double[] {
- sipper.usageTime,
- sipper.noCoveragePercent
- };
- }
- break;
- case WIFI:
- {
- types = new int[] {
- R.string.usage_type_wifi_running,
- R.string.usage_type_cpu,
- R.string.usage_type_cpu_foreground,
- R.string.usage_type_wake_lock,
- R.string.usage_type_data_recv,
- R.string.usage_type_data_send,
- R.string.usage_type_data_wifi_recv,
- R.string.usage_type_data_wifi_send,
- };
- values = new double[] {
- sipper.usageTime,
- sipper.cpuTime,
- sipper.cpuFgTime,
- sipper.wakeLockTime,
- sipper.mobileRxBytes,
- sipper.mobileTxBytes,
- sipper.wifiRxBytes,
- sipper.wifiTxBytes,
- };
- } break;
- case BLUETOOTH:
- {
- types = new int[] {
- R.string.usage_type_on_time,
- R.string.usage_type_cpu,
- R.string.usage_type_cpu_foreground,
- R.string.usage_type_wake_lock,
- R.string.usage_type_data_recv,
- R.string.usage_type_data_send,
- R.string.usage_type_data_wifi_recv,
- R.string.usage_type_data_wifi_send,
- };
- values = new double[] {
- sipper.usageTime,
- sipper.cpuTime,
- sipper.cpuFgTime,
- sipper.wakeLockTime,
- sipper.mobileRxBytes,
- sipper.mobileTxBytes,
- sipper.wifiRxBytes,
- sipper.wifiTxBytes,
- };
- } break;
- default:
- {
- types = new int[] {
- R.string.usage_type_on_time
- };
- values = new double[] {
- sipper.usageTime
- };
- }
- }
- args.putIntArray(PowerUsageDetail.EXTRA_DETAIL_TYPES, types);
- args.putDoubleArray(PowerUsageDetail.EXTRA_DETAIL_VALUES, values);
- caller.startPreferencePanel(PowerUsageDetail.class.getName(), args,
- R.string.details_title, null, null, 0);
- }
-
- /**
- * Refreshes the power usage list.
- * @param includeZeroConsumption whether includes those applications which have consumed very
- * little power up till now.
- */
- public void refreshStats(boolean includeZeroConsumption) {
- // Initialize mStats if necessary.
- getStats();
-
- mMaxPower = 0;
- mTotalPower = 0;
- mWifiPower = 0;
- mBluetoothPower = 0;
- mAppWifiRunning = 0;
-
- mUsageList.clear();
- mWifiSippers.clear();
- mBluetoothSippers.clear();
- mUserSippers.clear();
- mUserPower.clear();
-
- processAppUsage(includeZeroConsumption);
- processMiscUsage();
-
- Collections.sort(mUsageList);
-
- if (mHandler != null) {
- synchronized (mRequestQueue) {
- if (!mRequestQueue.isEmpty()) {
- if (mRequestThread != null) {
- mRequestThread.abort();
- }
- mRequestThread = new NameAndIconLoader();
- mRequestThread.setPriority(Thread.MIN_PRIORITY);
- mRequestThread.start();
- mRequestQueue.notify();
- }
- }
- }
- }
-
- private void processAppUsage(boolean includeZeroConsumption) {
- SensorManager sensorManager = (SensorManager) mActivity.getSystemService(
- Context.SENSOR_SERVICE);
- final int which = mStatsType;
- final int speedSteps = mPowerProfile.getNumSpeedSteps();
- final double[] powerCpuNormal = new double[speedSteps];
- final long[] cpuSpeedStepTimes = new long[speedSteps];
- for (int p = 0; p < speedSteps; p++) {
- powerCpuNormal[p] = mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_ACTIVE, p);
- }
- final double mobilePowerPerByte = getMobilePowerPerByte();
- final double wifiPowerPerByte = getWifiPowerPerByte();
- long uSecTime = mStats.computeBatteryRealtime(SystemClock.elapsedRealtime() * 1000, which);
- long appWakelockTime = 0;
- BatterySipper osApp = null;
- mStatsPeriod = uSecTime;
- SparseArray<? extends Uid> uidStats = mStats.getUidStats();
- final int NU = uidStats.size();
- for (int iu = 0; iu < NU; iu++) {
- Uid u = uidStats.valueAt(iu);
- double p; // in mAs
- double power = 0; // in mAs
- double highestDrain = 0;
- String packageWithHighestDrain = null;
- //mUsageList.add(new AppUsage(u.getUid(), new double[] {power}));
- Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
- long cpuTime = 0;
- long cpuFgTime = 0;
- long wakelockTime = 0;
- long gpsTime = 0;
- if (DEBUG) Log.i(TAG, "UID " + u.getUid());
- if (processStats.size() > 0) {
- // Process CPU time
- for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent
- : processStats.entrySet()) {
- Uid.Proc ps = ent.getValue();
- final long userTime = ps.getUserTime(which);
- final long systemTime = ps.getSystemTime(which);
- final long foregroundTime = ps.getForegroundTime(which);
- cpuFgTime += foregroundTime * 10; // convert to millis
- final long tmpCpuTime = (userTime + systemTime) * 10; // convert to millis
- int totalTimeAtSpeeds = 0;
- // Get the total first
- for (int step = 0; step < speedSteps; step++) {
- cpuSpeedStepTimes[step] = ps.getTimeAtCpuSpeedStep(step, which);
- totalTimeAtSpeeds += cpuSpeedStepTimes[step];
- }
- if (totalTimeAtSpeeds == 0) totalTimeAtSpeeds = 1;
- // Then compute the ratio of time spent at each speed
- double processPower = 0;
- for (int step = 0; step < speedSteps; step++) {
- double ratio = (double) cpuSpeedStepTimes[step] / totalTimeAtSpeeds;
- processPower += ratio * tmpCpuTime * powerCpuNormal[step];
- }
- cpuTime += tmpCpuTime;
- if (DEBUG && processPower != 0) {
- Log.i(TAG, String.format("process %s, cpu power=%.2f",
- ent.getKey(), processPower / 1000));
- }
- power += processPower;
- if (packageWithHighestDrain == null
- || packageWithHighestDrain.startsWith("*")) {
- highestDrain = processPower;
- packageWithHighestDrain = ent.getKey();
- } else if (highestDrain < processPower
- && !ent.getKey().startsWith("*")) {
- highestDrain = processPower;
- packageWithHighestDrain = ent.getKey();
- }
- }
- }
- if (cpuFgTime > cpuTime) {
- if (DEBUG && cpuFgTime > cpuTime + 10000) {
- Log.i(TAG, "WARNING! Cputime is more than 10 seconds behind Foreground time");
- }
- cpuTime = cpuFgTime; // Statistics may not have been gathered yet.
- }
- power /= 1000;
- if (DEBUG && power != 0) Log.i(TAG, String.format("total cpu power=%.2f", power));
-
- // Process wake lock usage
- Map<String, ? extends BatteryStats.Uid.Wakelock> wakelockStats = u.getWakelockStats();
- for (Map.Entry<String, ? extends BatteryStats.Uid.Wakelock> wakelockEntry
- : wakelockStats.entrySet()) {
- Uid.Wakelock wakelock = wakelockEntry.getValue();
- // Only care about partial wake locks since full wake locks
- // are canceled when the user turns the screen off.
- BatteryStats.Timer timer = wakelock.getWakeTime(BatteryStats.WAKE_TYPE_PARTIAL);
- if (timer != null) {
- wakelockTime += timer.getTotalTimeLocked(uSecTime, which);
- }
- }
- wakelockTime /= 1000; // convert to millis
- appWakelockTime += wakelockTime;
-
- // Add cost of holding a wake lock
- p = (wakelockTime
- * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE)) / 1000;
- power += p;
- if (DEBUG && p != 0) Log.i(TAG, String.format("wakelock power=%.2f", p));
-
- // Add cost of mobile traffic
- final long mobileRx = u.getNetworkActivityCount(NETWORK_MOBILE_RX_BYTES, mStatsType);
- final long mobileTx = u.getNetworkActivityCount(NETWORK_MOBILE_TX_BYTES, mStatsType);
- p = (mobileRx + mobileTx) * mobilePowerPerByte;
- power += p;
- if (DEBUG && p != 0) Log.i(TAG, String.format("mobile power=%.2f", p));
-
- // Add cost of wifi traffic
- final long wifiRx = u.getNetworkActivityCount(NETWORK_WIFI_RX_BYTES, mStatsType);
- final long wifiTx = u.getNetworkActivityCount(NETWORK_WIFI_TX_BYTES, mStatsType);
- p = (wifiRx + wifiTx) * wifiPowerPerByte;
- power += p;
- if (DEBUG && p != 0) Log.i(TAG, String.format("wifi power=%.2f", p));
-
- // Add cost of keeping WIFI running.
- long wifiRunningTimeMs = u.getWifiRunningTime(uSecTime, which) / 1000;
- mAppWifiRunning += wifiRunningTimeMs;
- p = (wifiRunningTimeMs
- * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) / 1000;
- power += p;
- if (DEBUG && p != 0) Log.i(TAG, String.format("wifi running power=%.2f", p));
-
- // Add cost of WIFI scans
- long wifiScanTimeMs = u.getWifiScanTime(uSecTime, which) / 1000;
- p = (wifiScanTimeMs
- * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_SCAN)) / 1000;
- power += p;
- if (DEBUG && p != 0) Log.i(TAG, String.format("wifi scanning power=%.2f", p));
- for (int bin = 0; bin < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bin++) {
- long batchScanTimeMs = u.getWifiBatchedScanTime(bin, uSecTime, which) / 1000;
- p = (batchScanTimeMs
- * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN, bin));
- power += p;
- if (DEBUG && p != 0) {
- Log.i(TAG, String.format("wifi batched scanning lvl %d = %.2f", bin, p));
- }
- }
-
- // Process Sensor usage
- Map<Integer, ? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats();
- for (Map.Entry<Integer, ? extends BatteryStats.Uid.Sensor> sensorEntry
- : sensorStats.entrySet()) {
- Uid.Sensor sensor = sensorEntry.getValue();
- int sensorHandle = sensor.getHandle();
- BatteryStats.Timer timer = sensor.getSensorTime();
- long sensorTime = timer.getTotalTimeLocked(uSecTime, which) / 1000;
- double multiplier = 0;
- switch (sensorHandle) {
- case Uid.Sensor.GPS:
- multiplier = mPowerProfile.getAveragePower(PowerProfile.POWER_GPS_ON);
- gpsTime = sensorTime;
- break;
- default:
- List<Sensor> sensorList = sensorManager.getSensorList(
- android.hardware.Sensor.TYPE_ALL);
- for (android.hardware.Sensor s : sensorList) {
- if (s.getHandle() == sensorHandle) {
- multiplier = s.getPower();
- break;
- }
- }
- }
- p = (multiplier * sensorTime) / 1000;
- power += p;
- if (DEBUG && p != 0) {
- Log.i(TAG, String.format("sensor %s power=%.2f", sensor.toString(), p));
- }
- }
-
- if (DEBUG) Log.i(TAG, String.format("UID %d total power=%.2f", u.getUid(), power));
-
- // Add the app to the list if it is consuming power
- boolean isOtherUser = false;
- final int userId = UserHandle.getUserId(u.getUid());
- if (power != 0 || includeZeroConsumption || u.getUid() == 0) {
- BatterySipper app = new BatterySipper(mActivity, mRequestQueue, mHandler,
- packageWithHighestDrain, DrainType.APP, 0, u,
- new double[] {power});
- app.cpuTime = cpuTime;
- app.gpsTime = gpsTime;
- app.wifiRunningTime = wifiRunningTimeMs;
- app.cpuFgTime = cpuFgTime;
- app.wakeLockTime = wakelockTime;
- app.mobileRxBytes = mobileRx;
- app.mobileTxBytes = mobileTx;
- app.wifiRxBytes = wifiRx;
- app.wifiTxBytes = wifiTx;
- if (u.getUid() == Process.WIFI_UID) {
- mWifiSippers.add(app);
- } else if (u.getUid() == Process.BLUETOOTH_UID) {
- mBluetoothSippers.add(app);
- } else if (userId != UserHandle.myUserId()
- && UserHandle.getAppId(u.getUid()) >= Process.FIRST_APPLICATION_UID) {
- isOtherUser = true;
- List<BatterySipper> list = mUserSippers.get(userId);
- if (list == null) {
- list = new ArrayList<BatterySipper>();
- mUserSippers.put(userId, list);
- }
- list.add(app);
- } else {
- mUsageList.add(app);
- }
- if (u.getUid() == 0) {
- osApp = app;
- }
- }
- if (power != 0 || includeZeroConsumption) {
- if (u.getUid() == Process.WIFI_UID) {
- mWifiPower += power;
- } else if (u.getUid() == Process.BLUETOOTH_UID) {
- mBluetoothPower += power;
- } else if (isOtherUser) {
- Double userPower = mUserPower.get(userId);
- if (userPower == null) {
- userPower = power;
- } else {
- userPower += power;
- }
- mUserPower.put(userId, userPower);
- } else {
- if (power > mMaxPower) mMaxPower = power;
- mTotalPower += power;
- }
- }
- }
-
- // The device has probably been awake for longer than the screen on
- // time and application wake lock time would account for. Assign
- // this remainder to the OS, if possible.
- if (osApp != null) {
- long wakeTimeMillis = mStats.computeBatteryUptime(
- SystemClock.uptimeMillis() * 1000, which) / 1000;
- wakeTimeMillis -= appWakelockTime + (mStats.getScreenOnTime(
- SystemClock.elapsedRealtime(), which) / 1000);
- if (wakeTimeMillis > 0) {
- double power = (wakeTimeMillis
- * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE)) / 1000;
- if (DEBUG) Log.i(TAG, "OS wakeLockTime " + wakeTimeMillis + " power " + power);
- osApp.wakeLockTime += wakeTimeMillis;
- osApp.value += power;
- osApp.values[0] += power;
- if (osApp.value > mMaxPower) mMaxPower = osApp.value;
- mTotalPower += power;
- }
- }
- }
-
- private void addPhoneUsage(long uSecNow) {
- long phoneOnTimeMs = mStats.getPhoneOnTime(uSecNow, mStatsType) / 1000;
- double phoneOnPower = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE)
- * phoneOnTimeMs / 1000;
- addEntry(mActivity.getString(R.string.power_phone), DrainType.PHONE, phoneOnTimeMs,
- R.drawable.ic_settings_voice_calls, phoneOnPower);
- }
-
- private void addScreenUsage(long uSecNow) {
- double power = 0;
- long screenOnTimeMs = mStats.getScreenOnTime(uSecNow, mStatsType) / 1000;
- power += screenOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_ON);
- final double screenFullPower =
- mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL);
- for (int i = 0; i < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; i++) {
- double screenBinPower = screenFullPower * (i + 0.5f)
- / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS;
- long brightnessTime = mStats.getScreenBrightnessTime(i, uSecNow, mStatsType) / 1000;
- power += screenBinPower * brightnessTime;
- if (DEBUG) {
- Log.i(TAG, "Screen bin power = " + (int) screenBinPower + ", time = "
- + brightnessTime);
- }
- }
- power /= 1000; // To seconds
- addEntry(mActivity.getString(R.string.power_screen), DrainType.SCREEN, screenOnTimeMs,
- R.drawable.ic_settings_display, power);
- }
-
- private void addRadioUsage(long uSecNow) {
- double power = 0;
- final int BINS = SignalStrength.NUM_SIGNAL_STRENGTH_BINS;
- long signalTimeMs = 0;
- for (int i = 0; i < BINS; i++) {
- long strengthTimeMs = mStats.getPhoneSignalStrengthTime(i, uSecNow, mStatsType) / 1000;
- power += strengthTimeMs / 1000
- * mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ON, i);
- signalTimeMs += strengthTimeMs;
- }
- long scanningTimeMs = mStats.getPhoneSignalScanningTime(uSecNow, mStatsType) / 1000;
- power += scanningTimeMs / 1000 * mPowerProfile.getAveragePower(
- PowerProfile.POWER_RADIO_SCANNING);
- BatterySipper bs =
- addEntry(mActivity.getString(R.string.power_cell), DrainType.CELL,
- signalTimeMs, R.drawable.ic_settings_cell_standby, power);
- if (signalTimeMs != 0) {
- bs.noCoveragePercent = mStats.getPhoneSignalStrengthTime(0, uSecNow, mStatsType)
- / 1000 * 100.0 / signalTimeMs;
- }
- }
-
- private void aggregateSippers(BatterySipper bs, List<BatterySipper> from, String tag) {
- for (int i=0; i<from.size(); i++) {
- BatterySipper wbs = from.get(i);
- if (DEBUG) Log.i(TAG, tag + " adding sipper " + wbs + ": cpu=" + wbs.cpuTime);
- bs.cpuTime += wbs.cpuTime;
- bs.gpsTime += wbs.gpsTime;
- bs.wifiRunningTime += wbs.wifiRunningTime;
- bs.cpuFgTime += wbs.cpuFgTime;
- bs.wakeLockTime += wbs.wakeLockTime;
- bs.mobileRxBytes += wbs.mobileRxBytes;
- bs.mobileTxBytes += wbs.mobileTxBytes;
- bs.wifiRxBytes += wbs.wifiRxBytes;
- bs.wifiTxBytes += wbs.wifiTxBytes;
- }
- }
-
- private void addWiFiUsage(long uSecNow) {
- long onTimeMs = mStats.getWifiOnTime(uSecNow, mStatsType) / 1000;
- long runningTimeMs = mStats.getGlobalWifiRunningTime(uSecNow, mStatsType) / 1000;
- if (DEBUG) Log.i(TAG, "WIFI runningTime=" + runningTimeMs
- + " app runningTime=" + mAppWifiRunning);
- runningTimeMs -= mAppWifiRunning;
- if (runningTimeMs < 0) runningTimeMs = 0;
- double wifiPower = (onTimeMs * 0 /* TODO */
- * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)
- + runningTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) / 1000;
- if (DEBUG) Log.i(TAG, "WIFI power=" + wifiPower + " from procs=" + mWifiPower);
- BatterySipper bs = addEntry(mActivity.getString(R.string.power_wifi), DrainType.WIFI,
- runningTimeMs, R.drawable.ic_settings_wifi, wifiPower + mWifiPower);
- aggregateSippers(bs, mWifiSippers, "WIFI");
- }
-
- private void addIdleUsage(long uSecNow) {
- long idleTimeMs = (uSecNow - mStats.getScreenOnTime(uSecNow, mStatsType)) / 1000;
- double idlePower = (idleTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_IDLE))
- / 1000;
- addEntry(mActivity.getString(R.string.power_idle), DrainType.IDLE, idleTimeMs,
- R.drawable.ic_settings_phone_idle, idlePower);
- }
-
- private void addBluetoothUsage(long uSecNow) {
- long btOnTimeMs = mStats.getBluetoothOnTime(uSecNow, mStatsType) / 1000;
- double btPower = btOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_ON)
- / 1000;
- int btPingCount = mStats.getBluetoothPingCount();
- btPower += (btPingCount
- * mPowerProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_AT_CMD)) / 1000;
- BatterySipper bs = addEntry(mActivity.getString(R.string.power_bluetooth),
- DrainType.BLUETOOTH, btOnTimeMs, R.drawable.ic_settings_bluetooth,
- btPower + mBluetoothPower);
- aggregateSippers(bs, mBluetoothSippers, "Bluetooth");
- }
-
- private void addUserUsage() {
- for (int i=0; i<mUserSippers.size(); i++) {
- final int userId = mUserSippers.keyAt(i);
- final List<BatterySipper> sippers = mUserSippers.valueAt(i);
- UserInfo info = mUm.getUserInfo(userId);
- Drawable icon;
- String name;
- if (info != null) {
- icon = UserUtils.getUserIcon(mActivity, mUm, info, mActivity.getResources());
- name = info != null ? info.name : null;
- if (name == null) {
- name = Integer.toString(info.id);
- }
- name = mActivity.getResources().getString(
- R.string.running_process_item_user_label, name);
- } else {
- icon = null;
- name = mActivity.getResources().getString(
- R.string.running_process_item_removed_user_label);
- }
- Double userPower = mUserPower.get(userId);
- double power = (userPower != null) ? userPower : 0.0;
- BatterySipper bs = addEntry(name, DrainType.USER, 0, 0, power);
- bs.icon = icon;
- aggregateSippers(bs, sippers, "User");
- }
- }
-
- /**
- * Return estimated power (in mAs) of sending a byte with the mobile radio.
- */
- private double getMobilePowerPerByte() {
- final long MOBILE_BPS = 200000; // TODO: Extract average bit rates from system
- final double MOBILE_POWER = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE)
- / 3600;
-
- final long mobileRx = mStats.getNetworkActivityCount(NETWORK_MOBILE_RX_BYTES, mStatsType);
- final long mobileTx = mStats.getNetworkActivityCount(NETWORK_MOBILE_TX_BYTES, mStatsType);
- final long mobileData = mobileRx + mobileTx;
-
- final long radioDataUptimeMs = mStats.getRadioDataUptime() / 1000;
- final long mobileBps = radioDataUptimeMs != 0
- ? mobileData * 8 * 1000 / radioDataUptimeMs
- : MOBILE_BPS;
-
- return MOBILE_POWER / (mobileBps / 8);
- }
-
- /**
- * Return estimated power (in mAs) of sending a byte with the Wi-Fi radio.
- */
- private double getWifiPowerPerByte() {
- final long WIFI_BPS = 1000000; // TODO: Extract average bit rates from system
- final double WIFI_POWER = mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ACTIVE)
- / 3600;
- return WIFI_POWER / (WIFI_BPS / 8);
- }
-
- private void processMiscUsage() {
- final int which = mStatsType;
- long uSecTime = SystemClock.elapsedRealtime() * 1000;
- final long uSecNow = mStats.computeBatteryRealtime(uSecTime, which);
- final long timeSinceUnplugged = uSecNow;
- if (DEBUG) {
- Log.i(TAG, "Uptime since last unplugged = " + (timeSinceUnplugged / 1000));
- }
-
- addUserUsage();
- addPhoneUsage(uSecNow);
- addScreenUsage(uSecNow);
- addWiFiUsage(uSecNow);
- addBluetoothUsage(uSecNow);
- addIdleUsage(uSecNow); // Not including cellular idle power
- // Don't compute radio usage if it's a wifi-only device
- if (!com.android.settings.Utils.isWifiOnly(mActivity)) {
- addRadioUsage(uSecNow);
- }
- }
-
- private BatterySipper addEntry(String label, DrainType drainType, long time, int iconId,
- double power) {
- if (power > mMaxPower) mMaxPower = power;
- mTotalPower += power;
- BatterySipper bs = new BatterySipper(mActivity, mRequestQueue, mHandler,
- label, drainType, iconId, null, new double[] {power});
- bs.usageTime = time;
- bs.iconId = iconId;
- mUsageList.add(bs);
- return bs;
- }
-
- public List<BatterySipper> getUsageList() {
- return mUsageList;
- }
-
- static final int MSG_UPDATE_NAME_ICON = 1;
- static final int MSG_REPORT_FULLY_DRAWN = 2;
-
- public double getMaxPower() {
- return mMaxPower;
- }
-
- public double getTotalPower() {
- return mTotalPower;
- }
-
- private void load() {
- try {
- byte[] data = mBatteryInfo.getStatistics();
- Parcel parcel = Parcel.obtain();
- parcel.unmarshall(data, 0, data.length);
- parcel.setDataPosition(0);
- mStats = com.android.internal.os.BatteryStatsImpl.CREATOR
- .createFromParcel(parcel);
- mStats.distributeWorkLocked(BatteryStats.STATS_SINCE_CHARGED);
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException:", e);
- }
- }
-}
diff --git a/src/com/android/settings/fuelgauge/PowerGaugePreference.java b/src/com/android/settings/fuelgauge/PowerGaugePreference.java
index c8bfa21..97012e4 100644
--- a/src/com/android/settings/fuelgauge/PowerGaugePreference.java
+++ b/src/com/android/settings/fuelgauge/PowerGaugePreference.java
@@ -25,31 +25,34 @@ import android.widget.ProgressBar;
import android.widget.TextView;
import com.android.settings.R;
+import com.android.settings.Utils;
/**
* Custom preference for displaying power consumption as a bar and an icon on
* the left for the subsystem/app type.
*/
public class PowerGaugePreference extends Preference {
- private BatterySipper mInfo;
+ private BatteryEntry mInfo;
private int mProgress;
private CharSequence mProgressText;
+ private final CharSequence mContentDescription;
- public PowerGaugePreference(Context context, Drawable icon, BatterySipper info) {
+ public PowerGaugePreference(Context context, Drawable icon, CharSequence contentDescription,
+ BatteryEntry info) {
super(context);
- setLayoutResource(R.layout.app_percentage_item);
+ setLayoutResource(R.layout.preference_app_percentage);
setIcon(icon != null ? icon : new ColorDrawable(0));
mInfo = info;
+ mContentDescription = contentDescription;
}
public void setPercent(double percentOfMax, double percentOfTotal) {
mProgress = (int) Math.ceil(percentOfMax);
- mProgressText = getContext().getResources().getString(
- R.string.percentage, (int) Math.ceil(percentOfTotal));
+ mProgressText = Utils.formatPercentage((int) (percentOfTotal + 0.5));
notifyChanged();
}
- BatterySipper getInfo() {
+ BatteryEntry getInfo() {
return mInfo;
}
@@ -62,5 +65,10 @@ public class PowerGaugePreference extends Preference {
final TextView text1 = (TextView) view.findViewById(android.R.id.text1);
text1.setText(mProgressText);
+
+ if (mContentDescription != null) {
+ final TextView titleView = (TextView) view.findViewById(android.R.id.title);
+ titleView.setContentDescription(mContentDescription);
+ }
}
}
diff --git a/src/com/android/settings/fuelgauge/PowerUsageDetail.java b/src/com/android/settings/fuelgauge/PowerUsageDetail.java
index 45e4516..9dca029 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageDetail.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageDetail.java
@@ -34,13 +34,11 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.BatteryStats;
import android.os.Bundle;
import android.os.Process;
import android.os.UserHandle;
-import android.preference.PreferenceActivity;
-import android.provider.Settings;
import android.text.TextUtils;
-import android.text.format.Formatter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -49,26 +47,24 @@ import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
+import com.android.internal.os.BatterySipper;
+import com.android.internal.os.BatteryStatsHelper;
+import com.android.internal.util.FastPrintWriter;
import com.android.settings.DisplaySettings;
import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.Utils;
import com.android.settings.WirelessSettings;
import com.android.settings.applications.InstalledAppDetails;
import com.android.settings.bluetooth.BluetoothSettings;
import com.android.settings.location.LocationSettings;
import com.android.settings.wifi.WifiSettings;
-public class PowerUsageDetail extends Fragment implements Button.OnClickListener {
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.Writer;
- enum DrainType {
- IDLE,
- CELL,
- PHONE,
- WIFI,
- BLUETOOTH,
- SCREEN,
- APP,
- USER
- }
+public class PowerUsageDetail extends Fragment implements Button.OnClickListener {
// Note: Must match the sequence of the DrainType
private static int[] sDrainTypeDesciptions = new int[] {
@@ -77,11 +73,202 @@ public class PowerUsageDetail extends Fragment implements Button.OnClickListener
R.string.battery_desc_voice,
R.string.battery_desc_wifi,
R.string.battery_desc_bluetooth,
+ R.string.battery_desc_flashlight,
R.string.battery_desc_display,
R.string.battery_desc_apps,
R.string.battery_desc_users,
+ R.string.battery_desc_unaccounted,
+ R.string.battery_desc_overcounted,
};
+ public static void startBatteryDetailPage(
+ SettingsActivity caller, BatteryStatsHelper helper, int statsType, BatteryEntry entry,
+ boolean showLocationButton) {
+ // Initialize mStats if necessary.
+ helper.getStats();
+
+ final int dischargeAmount = helper.getStats().getDischargeAmount(statsType);
+ Bundle args = new Bundle();
+ args.putString(PowerUsageDetail.EXTRA_TITLE, entry.name);
+ args.putInt(PowerUsageDetail.EXTRA_PERCENT, (int)
+ ((entry.sipper.value * dischargeAmount / helper.getTotalPower()) + .5));
+ args.putInt(PowerUsageDetail.EXTRA_GAUGE, (int)
+ Math.ceil(entry.sipper.value * 100 / helper.getMaxPower()));
+ args.putLong(PowerUsageDetail.EXTRA_USAGE_DURATION, helper.getStatsPeriod());
+ args.putString(PowerUsageDetail.EXTRA_ICON_PACKAGE, entry.defaultPackageName);
+ args.putInt(PowerUsageDetail.EXTRA_ICON_ID, entry.iconId);
+ args.putDouble(PowerUsageDetail.EXTRA_NO_COVERAGE, entry.sipper.noCoveragePercent);
+ if (entry.sipper.uidObj != null) {
+ args.putInt(PowerUsageDetail.EXTRA_UID, entry.sipper.uidObj.getUid());
+ }
+ args.putSerializable(PowerUsageDetail.EXTRA_DRAIN_TYPE, entry.sipper.drainType);
+ args.putBoolean(PowerUsageDetail.EXTRA_SHOW_LOCATION_BUTTON, showLocationButton);
+
+ int userId = UserHandle.myUserId();
+ int[] types;
+ double[] values;
+ switch (entry.sipper.drainType) {
+ case APP:
+ case USER:
+ {
+ BatteryStats.Uid uid = entry.sipper.uidObj;
+ types = new int[] {
+ R.string.usage_type_cpu,
+ R.string.usage_type_cpu_foreground,
+ R.string.usage_type_wake_lock,
+ R.string.usage_type_gps,
+ R.string.usage_type_wifi_running,
+ R.string.usage_type_data_recv,
+ R.string.usage_type_data_send,
+ R.string.usage_type_radio_active,
+ R.string.usage_type_data_wifi_recv,
+ R.string.usage_type_data_wifi_send,
+ R.string.usage_type_audio,
+ R.string.usage_type_video,
+ };
+ values = new double[] {
+ entry.sipper.cpuTime,
+ entry.sipper.cpuFgTime,
+ entry.sipper.wakeLockTime,
+ entry.sipper.gpsTime,
+ entry.sipper.wifiRunningTime,
+ entry.sipper.mobileRxPackets,
+ entry.sipper.mobileTxPackets,
+ entry.sipper.mobileActive,
+ entry.sipper.wifiRxPackets,
+ entry.sipper.wifiTxPackets,
+ 0,
+ 0
+ };
+
+ 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());
+ 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());
+ printWriter.flush();
+ args.putString(PowerUsageDetail.EXTRA_REPORT_CHECKIN_DETAILS,
+ result.toString());
+ userId = UserHandle.getUserId(uid.getUid());
+ }
+ }
+ break;
+ case CELL:
+ {
+ types = new int[] {
+ R.string.usage_type_on_time,
+ R.string.usage_type_no_coverage,
+ R.string.usage_type_radio_active,
+ };
+ values = new double[] {
+ entry.sipper.usageTime,
+ entry.sipper.noCoveragePercent,
+ entry.sipper.mobileActive
+ };
+ }
+ break;
+ case WIFI:
+ {
+ types = new int[] {
+ R.string.usage_type_wifi_running,
+ R.string.usage_type_cpu,
+ R.string.usage_type_cpu_foreground,
+ R.string.usage_type_wake_lock,
+ R.string.usage_type_data_recv,
+ R.string.usage_type_data_send,
+ R.string.usage_type_data_wifi_recv,
+ R.string.usage_type_data_wifi_send,
+ };
+ values = new double[] {
+ entry.sipper.usageTime,
+ entry.sipper.cpuTime,
+ entry.sipper.cpuFgTime,
+ entry.sipper.wakeLockTime,
+ entry.sipper.mobileRxPackets,
+ entry.sipper.mobileTxPackets,
+ entry.sipper.wifiRxPackets,
+ entry.sipper.wifiTxPackets,
+ };
+ } break;
+ case BLUETOOTH:
+ {
+ types = new int[] {
+ R.string.usage_type_on_time,
+ R.string.usage_type_cpu,
+ R.string.usage_type_cpu_foreground,
+ R.string.usage_type_wake_lock,
+ R.string.usage_type_data_recv,
+ R.string.usage_type_data_send,
+ R.string.usage_type_data_wifi_recv,
+ R.string.usage_type_data_wifi_send,
+ };
+ values = new double[] {
+ entry.sipper.usageTime,
+ entry.sipper.cpuTime,
+ entry.sipper.cpuFgTime,
+ entry.sipper.wakeLockTime,
+ entry.sipper.mobileRxPackets,
+ entry.sipper.mobileTxPackets,
+ entry.sipper.wifiRxPackets,
+ entry.sipper.wifiTxPackets,
+ };
+ } break;
+ case UNACCOUNTED:
+ {
+ types = new int[] {
+ R.string.usage_type_total_battery_capacity,
+ R.string.usage_type_computed_power,
+ R.string.usage_type_actual_power,
+ };
+ values = new double[] {
+ helper.getPowerProfile().getBatteryCapacity(),
+ helper.getComputedPower(),
+ helper.getMinDrainedPower(),
+ };
+ } break;
+ case OVERCOUNTED:
+ {
+ types = new int[] {
+ R.string.usage_type_total_battery_capacity,
+ R.string.usage_type_computed_power,
+ R.string.usage_type_actual_power,
+ };
+ values = new double[] {
+ helper.getPowerProfile().getBatteryCapacity(),
+ helper.getComputedPower(),
+ helper.getMaxDrainedPower(),
+ };
+ } break;
+ default:
+ {
+ types = new int[] {
+ R.string.usage_type_on_time
+ };
+ values = new double[] {
+ entry.sipper.usageTime
+ };
+ }
+ }
+ args.putIntArray(PowerUsageDetail.EXTRA_DETAIL_TYPES, types);
+ args.putDoubleArray(PowerUsageDetail.EXTRA_DETAIL_VALUES, values);
+
+ // This is a workaround, see b/17523189
+ if (userId == UserHandle.myUserId()) {
+ caller.startPreferencePanel(PowerUsageDetail.class.getName(), args,
+ R.string.details_title, null, null, 0);
+ } else {
+ caller.startPreferencePanelAsUser(PowerUsageDetail.class.getName(), args,
+ R.string.details_title, null, new UserHandle(userId));
+ }
+ }
+
public static final int ACTION_DISPLAY_SETTINGS = 1;
public static final int ACTION_WIFI_SETTINGS = 2;
public static final int ACTION_BLUETOOTH_SETTINGS = 3;
@@ -124,8 +311,9 @@ public class PowerUsageDetail extends Fragment implements Button.OnClickListener
private Button mReportButton;
private ViewGroup mDetailsParent;
private ViewGroup mControlsParent;
+ private ViewGroup mMessagesParent;
private long mStartTime;
- private DrainType mDrainType;
+ private BatterySipper.DrainType mDrainType;
private Drawable mAppIcon;
private double mNoCoverage; // Percentage of time that there was no coverage
@@ -175,7 +363,7 @@ public class PowerUsageDetail extends Fragment implements Button.OnClickListener
final int gaugeValue = args.getInt(EXTRA_GAUGE, 1);
mUsageSince = args.getInt(EXTRA_USAGE_SINCE, USAGE_SINCE_UNPLUGGED);
mUid = args.getInt(EXTRA_UID, 0);
- mDrainType = (DrainType) args.getSerializable(EXTRA_DRAIN_TYPE);
+ mDrainType = (BatterySipper.DrainType) args.getSerializable(EXTRA_DRAIN_TYPE);
mNoCoverage = args.getDouble(EXTRA_NO_COVERAGE, 0);
String iconPackage = args.getString(EXTRA_ICON_PACKAGE);
int iconId = args.getInt(EXTRA_ICON_ID, 0);
@@ -209,7 +397,7 @@ public class PowerUsageDetail extends Fragment implements Button.OnClickListener
mTitleView.setText(mTitle);
final TextView text1 = (TextView)mRootView.findViewById(android.R.id.text1);
- text1.setText(getString(R.string.percentage, percentage));
+ text1.setText(Utils.formatPercentage(percentage));
mTwoButtonsPanel = (ViewGroup)mRootView.findViewById(R.id.two_buttons_panel);
mForceStopButton = (Button)mRootView.findViewById(R.id.left_button);
@@ -224,10 +412,12 @@ public class PowerUsageDetail extends Fragment implements Button.OnClickListener
mDetailsParent = (ViewGroup)mRootView.findViewById(R.id.details);
mControlsParent = (ViewGroup)mRootView.findViewById(R.id.controls);
+ mMessagesParent = (ViewGroup)mRootView.findViewById(R.id.messages);
fillDetailsSection();
fillPackagesSection(mUid);
fillControlsSection(mUid);
+ fillMessagesSection(mUid);
if (mUid >= Process.FIRST_APPLICATION_UID) {
mForceStopButton.setText(R.string.force_stop);
@@ -238,8 +428,8 @@ public class PowerUsageDetail extends Fragment implements Button.OnClickListener
mReportButton.setOnClickListener(this);
// check if error reporting is enabled in secure settings
- int enabled = Settings.Global.getInt(getActivity().getContentResolver(),
- Settings.Global.SEND_ACTION_APP_ERROR, 0);
+ int enabled = android.provider.Settings.Global.getInt(getActivity().getContentResolver(),
+ android.provider.Settings.Global.SEND_ACTION_APP_ERROR, 0);
if (enabled != 0) {
if (mPackages != null && mPackages.length > 0) {
try {
@@ -269,35 +459,35 @@ public class PowerUsageDetail extends Fragment implements Button.OnClickListener
Bundle args = new Bundle();
args.putString(InstalledAppDetails.ARG_PACKAGE_NAME, mPackages[0]);
- PreferenceActivity pa = (PreferenceActivity)getActivity();
- pa.startPreferencePanel(InstalledAppDetails.class.getName(), args,
+ SettingsActivity sa = (SettingsActivity) getActivity();
+ sa.startPreferencePanel(InstalledAppDetails.class.getName(), args,
R.string.application_info_label, null, null, 0);
}
private void doAction(int action) {
- PreferenceActivity pa = (PreferenceActivity)getActivity();
+ SettingsActivity sa = (SettingsActivity)getActivity();
switch (action) {
case ACTION_DISPLAY_SETTINGS:
- pa.startPreferencePanel(DisplaySettings.class.getName(), null,
+ sa.startPreferencePanel(DisplaySettings.class.getName(), null,
R.string.display_settings_title, null, null, 0);
break;
case ACTION_WIFI_SETTINGS:
- pa.startPreferencePanel(WifiSettings.class.getName(), null,
+ sa.startPreferencePanel(WifiSettings.class.getName(), null,
R.string.wifi_settings, null, null, 0);
break;
case ACTION_BLUETOOTH_SETTINGS:
- pa.startPreferencePanel(BluetoothSettings.class.getName(), null,
+ sa.startPreferencePanel(BluetoothSettings.class.getName(), null,
R.string.bluetooth_settings, null, null, 0);
break;
case ACTION_WIRELESS_SETTINGS:
- pa.startPreferencePanel(WirelessSettings.class.getName(), null,
+ sa.startPreferencePanel(WirelessSettings.class.getName(), null,
R.string.radio_controls_title, null, null, 0);
break;
case ACTION_APP_DETAILS:
startApplicationDetailsActivity();
break;
case ACTION_LOCATION_SETTINGS:
- pa.startPreferencePanel(LocationSettings.class.getName(), null,
+ sa.startPreferencePanel(LocationSettings.class.getName(), null,
R.string.location_settings_title, null, null, 0);
break;
case ACTION_FORCE_STOP:
@@ -322,12 +512,17 @@ public class PowerUsageDetail extends Fragment implements Button.OnClickListener
case R.string.usage_type_data_send:
case R.string.usage_type_data_wifi_recv:
case R.string.usage_type_data_wifi_send:
- final long bytes = (long) (mValues[i]);
- value = Formatter.formatFileSize(getActivity(), bytes);
+ final long packets = (long) (mValues[i]);
+ value = Long.toString(packets);
break;
case R.string.usage_type_no_coverage:
final int percentage = (int) Math.floor(mValues[i]);
- value = getActivity().getString(R.string.percentage, percentage);
+ value = Utils.formatPercentage(percentage);
+ break;
+ case R.string.usage_type_total_battery_capacity:
+ case R.string.usage_type_computed_power:
+ case R.string.usage_type_actual_power:
+ value = getActivity().getString(R.string.mah, (long)(mValues[i]));
break;
case R.string.usage_type_gps:
mUsesGps = true;
@@ -419,6 +614,28 @@ public class PowerUsageDetail extends Fragment implements Button.OnClickListener
actionButton.setTag(new Integer(action));
}
+ private void fillMessagesSection(int uid) {
+ boolean removeHeader = true;
+ switch (mDrainType) {
+ case UNACCOUNTED:
+ addMessage(R.string.battery_msg_unaccounted);
+ removeHeader = false;
+ break;
+ }
+ if (removeHeader) {
+ mMessagesParent.setVisibility(View.GONE);
+ }
+ }
+
+ private void addMessage(int message) {
+ final Resources res = getResources();
+ LayoutInflater inflater = getActivity().getLayoutInflater();
+ View item = inflater.inflate(R.layout.power_usage_message_item, null);
+ mMessagesParent.addView(item);
+ TextView messageView = (TextView) item.findViewById(R.id.message);
+ messageView.setText(res.getText(message));
+ }
+
private void removePackagesSection() {
View view;
if ((view = mRootView.findViewById(R.id.packages_section_title)) != null) {
@@ -433,8 +650,9 @@ public class PowerUsageDetail extends Fragment implements Button.OnClickListener
if (mPackages == null) return;
ActivityManager am = (ActivityManager)getActivity().getSystemService(
Context.ACTIVITY_SERVICE);
+ final int userId = UserHandle.getUserId(mUid);
for (int i = 0; i < mPackages.length; i++) {
- am.forceStopPackage(mPackages[i]);
+ am.forceStopPackageAsUser(mPackages[i], userId);
}
checkForceStop();
}
@@ -531,8 +749,7 @@ public class PowerUsageDetail extends Fragment implements Button.OnClickListener
//if (ai.icon != 0) {
// icon = ai.loadIcon(pm);
//}
- ViewGroup item = (ViewGroup) inflater.inflate(R.layout.power_usage_package_item,
- null);
+ View item = inflater.inflate(R.layout.power_usage_package_item, null);
packagesParent.addView(item);
TextView labelView = (TextView) item.findViewById(R.id.label);
labelView.setText(mPackages[i]);
diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/PowerUsageSummary.java
index dc86b8d..5ee4fa0 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageSummary.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageSummary.java
@@ -21,13 +21,15 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.graphics.drawable.Drawable;
import android.os.BatteryStats;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
-import android.os.Parcel;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.preference.Preference;
-import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;
import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
@@ -36,9 +38,12 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import com.android.internal.os.BatterySipper;
+import com.android.internal.os.BatteryStatsHelper;
import com.android.internal.os.PowerProfile;
import com.android.settings.HelpUtils;
import com.android.settings.R;
+import com.android.settings.SettingsActivity;
import java.util.List;
@@ -50,22 +55,30 @@ public class PowerUsageSummary extends PreferenceFragment {
private static final boolean DEBUG = false;
- private static final String TAG = "PowerUsageSummary";
+ static final String TAG = "PowerUsageSummary";
private static final String KEY_APP_LIST = "app_list";
- private static final String KEY_BATTERY_STATUS = "battery_status";
+
+ private static final String BATTERY_HISTORY_FILE = "tmp_bat_history.bin";
private static final int MENU_STATS_TYPE = Menu.FIRST;
private static final int MENU_STATS_REFRESH = Menu.FIRST + 1;
- private static final int MENU_HELP = Menu.FIRST + 2;
+ private static final int MENU_BATTERY_SAVER = Menu.FIRST + 2;
+ private static final int MENU_HELP = Menu.FIRST + 3;
+
+ private UserManager mUm;
+ private BatteryHistoryPreference mHistPref;
private PreferenceGroup mAppListGroup;
- private Preference mBatteryStatusPref;
+ private String mBatteryLevel;
+ private String mBatteryStatus;
private int mStatsType = BatteryStats.STATS_SINCE_CHARGED;
- private static final int MIN_POWER_THRESHOLD = 5;
+ private static final int MIN_POWER_THRESHOLD_MILLI_AMP = 5;
private static final int MAX_ITEMS_TO_LIST = 10;
+ private static final int MIN_AVERAGE_POWER_THRESHOLD_MILLI_AMP = 10;
+ private static final int SECONDS_IN_HOUR = 60 * 60;
private BatteryStatsHelper mStatsHelper;
@@ -74,15 +87,11 @@ public class PowerUsageSummary extends PreferenceFragment {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
- if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
- String batteryLevel = com.android.settings.Utils.getBatteryPercentage(intent);
- String batteryStatus = com.android.settings.Utils.getBatteryStatus(getResources(),
- intent);
- String batterySummary = context.getResources().getString(
- R.string.power_usage_level_and_status, batteryLevel, batteryStatus);
- mBatteryStatusPref.setTitle(batterySummary);
- mStatsHelper.clearStats();
- refreshStats();
+ if (Intent.ACTION_BATTERY_CHANGED.equals(action)
+ && updateBatteryStatus(intent)) {
+ if (!mHandler.hasMessages(MSG_REFRESH_STATS)) {
+ mHandler.sendEmptyMessageDelayed(MSG_REFRESH_STATS, 500);
+ }
}
}
};
@@ -90,7 +99,8 @@ public class PowerUsageSummary extends PreferenceFragment {
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
- mStatsHelper = new BatteryStatsHelper(activity, mHandler);
+ mUm = (UserManager) activity.getSystemService(Context.USER_SERVICE);
+ mStatsHelper = new BatteryStatsHelper(activity, true);
}
@Override
@@ -100,42 +110,61 @@ public class PowerUsageSummary extends PreferenceFragment {
addPreferencesFromResource(R.xml.power_usage_summary);
mAppListGroup = (PreferenceGroup) findPreference(KEY_APP_LIST);
- mBatteryStatusPref = mAppListGroup.findPreference(KEY_BATTERY_STATUS);
setHasOptionsMenu(true);
}
@Override
+ public void onStart() {
+ super.onStart();
+ mStatsHelper.clearStats();
+ }
+
+ @Override
public void onResume() {
super.onResume();
- getActivity().registerReceiver(mBatteryInfoReceiver,
- new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+ BatteryStatsHelper.dropFile(getActivity(), BATTERY_HISTORY_FILE);
+ updateBatteryStatus(getActivity().registerReceiver(mBatteryInfoReceiver,
+ new IntentFilter(Intent.ACTION_BATTERY_CHANGED)));
+ if (mHandler.hasMessages(MSG_REFRESH_STATS)) {
+ mHandler.removeMessages(MSG_REFRESH_STATS);
+ mStatsHelper.clearStats();
+ }
refreshStats();
}
@Override
public void onPause() {
- mStatsHelper.pause();
- mHandler.removeMessages(BatteryStatsHelper.MSG_UPDATE_NAME_ICON);
+ BatteryEntry.stopRequestQueue();
+ mHandler.removeMessages(BatteryEntry.MSG_UPDATE_NAME_ICON);
getActivity().unregisterReceiver(mBatteryInfoReceiver);
super.onPause();
}
@Override
+ public void onStop() {
+ super.onStop();
+ mHandler.removeMessages(MSG_REFRESH_STATS);
+ }
+
+ @Override
public void onDestroy() {
super.onDestroy();
- mStatsHelper.destroy();
+ if (getActivity().isChangingConfigurations()) {
+ mStatsHelper.storeState();
+ BatteryEntry.clearUidCache();
+ }
}
@Override
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
if (preference instanceof BatteryHistoryPreference) {
- Parcel hist = Parcel.obtain();
- mStatsHelper.getStats().writeToParcelWithoutUids(hist, 0);
- byte[] histData = hist.marshall();
+ mStatsHelper.storeStatsHistoryInFile(BATTERY_HISTORY_FILE);
Bundle args = new Bundle();
- args.putByteArray(BatteryHistoryDetail.EXTRA_STATS, histData);
- PreferenceActivity pa = (PreferenceActivity)getActivity();
- pa.startPreferencePanel(BatteryHistoryDetail.class.getName(), args,
+ args.putString(BatteryHistoryDetail.EXTRA_STATS, BATTERY_HISTORY_FILE);
+ args.putParcelable(BatteryHistoryDetail.EXTRA_BROADCAST,
+ mStatsHelper.getBatteryBroadcast());
+ SettingsActivity sa = (SettingsActivity) getActivity();
+ sa.startPreferencePanel(BatteryHistoryDetail.class.getName(), args,
R.string.history_details_title, null, null, 0);
return super.onPreferenceTreeClick(preferenceScreen, preference);
}
@@ -143,8 +172,9 @@ public class PowerUsageSummary extends PreferenceFragment {
return false;
}
PowerGaugePreference pgp = (PowerGaugePreference) preference;
- BatterySipper sipper = pgp.getInfo();
- mStatsHelper.startBatteryDetailPage((PreferenceActivity) getActivity(), sipper, true);
+ BatteryEntry entry = pgp.getInfo();
+ PowerUsageDetail.startBatteryDetailPage((SettingsActivity) getActivity(), mStatsHelper,
+ mStatsType, entry, true);
return super.onPreferenceTreeClick(preferenceScreen, preference);
}
@@ -156,11 +186,14 @@ public class PowerUsageSummary extends PreferenceFragment {
.setAlphabeticShortcut('t');
}
MenuItem refresh = menu.add(0, MENU_STATS_REFRESH, 0, R.string.menu_stats_refresh)
- .setIcon(R.drawable.ic_menu_refresh_holo_dark)
+ .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);
+ MenuItem batterySaver = menu.add(0, MENU_BATTERY_SAVER, 0, R.string.battery_saver);
+ batterySaver.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+
String helpUrl;
if (!TextUtils.isEmpty(helpUrl = getResources().getString(R.string.help_url_battery))) {
final MenuItem help = menu.add(0, MENU_HELP, 0, R.string.help_label);
@@ -182,6 +215,12 @@ public class PowerUsageSummary extends PreferenceFragment {
case MENU_STATS_REFRESH:
mStatsHelper.clearStats();
refreshStats();
+ mHandler.removeMessages(MSG_REFRESH_STATS);
+ return true;
+ case MENU_BATTERY_SAVER:
+ final SettingsActivity sa = (SettingsActivity) getActivity();
+ sa.startPreferencePanel(BatterySaverSettings.class.getName(), null,
+ R.string.battery_saver, null, null, 0);
return true;
default:
return false;
@@ -191,69 +230,140 @@ public class PowerUsageSummary extends PreferenceFragment {
private void addNotAvailableMessage() {
Preference notAvailable = new Preference(getActivity());
notAvailable.setTitle(R.string.power_usage_not_available);
+ mHistPref.setHideLabels(true);
mAppListGroup.addPreference(notAvailable);
}
+ private boolean updateBatteryStatus(Intent intent) {
+ if (intent != null) {
+ String batteryLevel = com.android.settings.Utils.getBatteryPercentage(intent);
+ String batteryStatus = com.android.settings.Utils.getBatteryStatus(getResources(),
+ intent);
+ if (!batteryLevel.equals(mBatteryLevel) || !batteryStatus.equals(mBatteryStatus)) {
+ mBatteryLevel = batteryLevel;
+ mBatteryStatus = batteryStatus;
+ return true;
+ }
+ }
+ return false;
+ }
+
private void refreshStats() {
mAppListGroup.removeAll();
mAppListGroup.setOrderingAsAdded(false);
+ mHistPref = new BatteryHistoryPreference(getActivity(), mStatsHelper.getStats(),
+ mStatsHelper.getBatteryBroadcast());
+ mHistPref.setOrder(-1);
+ mAppListGroup.addPreference(mHistPref);
+ boolean addedSome = false;
- mBatteryStatusPref.setOrder(-2);
- mAppListGroup.addPreference(mBatteryStatusPref);
- BatteryHistoryPreference hist = new BatteryHistoryPreference(
- getActivity(), mStatsHelper.getStats());
- hist.setOrder(-1);
- mAppListGroup.addPreference(hist);
+ final PowerProfile powerProfile = mStatsHelper.getPowerProfile();
+ final BatteryStats stats = mStatsHelper.getStats();
+ final double averagePower = powerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL);
+ if (averagePower >= MIN_AVERAGE_POWER_THRESHOLD_MILLI_AMP) {
+ final List<UserHandle> profiles = mUm.getUserProfiles();
- if (mStatsHelper.getPowerProfile().getAveragePower(
- PowerProfile.POWER_SCREEN_FULL) < 10) {
- addNotAvailableMessage();
- return;
- }
- mStatsHelper.refreshStats(false);
- List<BatterySipper> usageList = mStatsHelper.getUsageList();
- for (BatterySipper sipper : usageList) {
- if (sipper.getSortValue() < MIN_POWER_THRESHOLD) continue;
- final double percentOfTotal =
- ((sipper.getSortValue() / mStatsHelper.getTotalPower()) * 100);
- if (percentOfTotal < 1) continue;
- PowerGaugePreference pref =
- new PowerGaugePreference(getActivity(), sipper.getIcon(), sipper);
- final double percentOfMax =
- (sipper.getSortValue() * 100) / mStatsHelper.getMaxPower();
- sipper.percent = percentOfTotal;
- pref.setTitle(sipper.name);
- pref.setOrder(Integer.MAX_VALUE - (int) sipper.getSortValue()); // Invert the order
- pref.setPercent(percentOfMax, percentOfTotal);
- if (sipper.uidObj != null) {
- pref.setKey(Integer.toString(sipper.uidObj.getUid()));
+ mStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, profiles);
+
+ final List<BatterySipper> usageList = mStatsHelper.getUsageList();
+
+ final int dischargeAmount = stats != null ? stats.getDischargeAmount(mStatsType) : 0;
+ final int numSippers = usageList.size();
+ for (int i = 0; i < numSippers; i++) {
+ final BatterySipper sipper = usageList.get(i);
+ if ((sipper.value * SECONDS_IN_HOUR) < MIN_POWER_THRESHOLD_MILLI_AMP) {
+ continue;
+ }
+ final double percentOfTotal =
+ ((sipper.value / mStatsHelper.getTotalPower()) * dischargeAmount);
+ 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
+ // the largest real entry, and its percent of total is more significant
+ if (sipper.value < ((mStatsHelper.getMaxRealPower()*2)/3)) {
+ continue;
+ }
+ if (percentOfTotal < 10) {
+ continue;
+ }
+ if ("user".equals(Build.TYPE)) {
+ continue;
+ }
+ }
+ if (sipper.drainType == BatterySipper.DrainType.UNACCOUNTED) {
+ // Don't show over-counted unless it is at least 1/2 the size of
+ // the largest real entry, and its percent of total is more significant
+ if (sipper.value < (mStatsHelper.getMaxRealPower()/2)) {
+ continue;
+ }
+ if (percentOfTotal < 5) {
+ continue;
+ }
+ if ("user".equals(Build.TYPE)) {
+ continue;
+ }
+ }
+ final UserHandle userHandle = new UserHandle(UserHandle.getUserId(sipper.getUid()));
+ final BatteryEntry entry = new BatteryEntry(getActivity(), mHandler, mUm, sipper);
+ final Drawable badgedIcon = mUm.getBadgedIconForUser(entry.getIcon(),
+ userHandle);
+ final CharSequence contentDescription = mUm.getBadgedLabelForUser(entry.getLabel(),
+ userHandle);
+ final PowerGaugePreference pref = new PowerGaugePreference(getActivity(),
+ badgedIcon, contentDescription, entry);
+
+ final double percentOfMax = (sipper.value * 100) / mStatsHelper.getMaxPower();
+ sipper.percent = percentOfTotal;
+ pref.setTitle(entry.getLabel());
+ pref.setOrder(i + 1);
+ pref.setPercent(percentOfMax, percentOfTotal);
+ if (sipper.uidObj != null) {
+ pref.setKey(Integer.toString(sipper.uidObj.getUid()));
+ }
+ addedSome = true;
+ mAppListGroup.addPreference(pref);
+ if (mAppListGroup.getPreferenceCount() > (MAX_ITEMS_TO_LIST + 1)) {
+ break;
+ }
}
- mAppListGroup.addPreference(pref);
- if (mAppListGroup.getPreferenceCount() > (MAX_ITEMS_TO_LIST+1)) break;
}
+ if (!addedSome) {
+ addNotAvailableMessage();
+ }
+
+ BatteryEntry.startRequestQueue();
}
+ static final int MSG_REFRESH_STATS = 100;
+
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case BatteryStatsHelper.MSG_UPDATE_NAME_ICON:
- BatterySipper bs = (BatterySipper) msg.obj;
+ case BatteryEntry.MSG_UPDATE_NAME_ICON:
+ BatteryEntry entry = (BatteryEntry) msg.obj;
PowerGaugePreference pgp =
(PowerGaugePreference) findPreference(
- Integer.toString(bs.uidObj.getUid()));
+ Integer.toString(entry.sipper.uidObj.getUid()));
if (pgp != null) {
- pgp.setIcon(bs.icon);
- pgp.setTitle(bs.name);
+ final int userId = UserHandle.getUserId(entry.sipper.getUid());
+ final UserHandle userHandle = new UserHandle(userId);
+ pgp.setIcon(mUm.getBadgedIconForUser(entry.getIcon(), userHandle));
+ pgp.setTitle(entry.name);
}
break;
- case BatteryStatsHelper.MSG_REPORT_FULLY_DRAWN:
+ case BatteryEntry.MSG_REPORT_FULLY_DRAWN:
Activity activity = getActivity();
if (activity != null) {
activity.reportFullyDrawn();
}
break;
+ case MSG_REFRESH_STATS:
+ mStatsHelper.clearStats();
+ refreshStats();
}
super.handleMessage(msg);
}
diff --git a/src/com/android/settings/fuelgauge/Utils.java b/src/com/android/settings/fuelgauge/Utils.java
deleted file mode 100644
index 9a06c9f..0000000
--- a/src/com/android/settings/fuelgauge/Utils.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.fuelgauge;
-
-import android.content.Context;
-
-import com.android.settings.R;
-
-/**
- * Contains utility functions for formatting elapsed time and consumed bytes
- */
-public class Utils {
- private static final int SECONDS_PER_MINUTE = 60;
- private static final int SECONDS_PER_HOUR = 60 * 60;
- private static final int SECONDS_PER_DAY = 24 * 60 * 60;
-
- /**
- * Returns elapsed time for the given millis, in the following format:
- * 2d 5h 40m 29s
- * @param context the application context
- * @param millis the elapsed time in milli seconds
- * @return the formatted elapsed time
- */
- public static String formatElapsedTime(Context context, double millis, boolean inclSeconds) {
- StringBuilder sb = new StringBuilder();
- int seconds = (int) Math.floor(millis / 1000);
- if (!inclSeconds) {
- // Round up.
- seconds += 30;
- }
-
- int days = 0, hours = 0, minutes = 0;
- if (seconds >= SECONDS_PER_DAY) {
- days = seconds / SECONDS_PER_DAY;
- seconds -= days * SECONDS_PER_DAY;
- }
- if (seconds >= SECONDS_PER_HOUR) {
- hours = seconds / SECONDS_PER_HOUR;
- seconds -= hours * SECONDS_PER_HOUR;
- }
- if (seconds >= SECONDS_PER_MINUTE) {
- minutes = seconds / SECONDS_PER_MINUTE;
- seconds -= minutes * SECONDS_PER_MINUTE;
- }
- if (inclSeconds) {
- if (days > 0) {
- sb.append(context.getString(R.string.battery_history_days,
- days, hours, minutes, seconds));
- } else if (hours > 0) {
- sb.append(context.getString(R.string.battery_history_hours,
- hours, minutes, seconds));
- } else if (minutes > 0) {
- sb.append(context.getString(R.string.battery_history_minutes, minutes, seconds));
- } else {
- sb.append(context.getString(R.string.battery_history_seconds, seconds));
- }
- } else {
- if (days > 0) {
- sb.append(context.getString(R.string.battery_history_days_no_seconds,
- days, hours, minutes));
- } else if (hours > 0) {
- sb.append(context.getString(R.string.battery_history_hours_no_seconds,
- hours, minutes));
- } else {
- sb.append(context.getString(R.string.battery_history_minutes_no_seconds, minutes));
- }
- }
- return sb.toString();
- }
-}
diff --git a/src/com/android/settings/inputmethod/CheckBoxAndSettingsPreference.java b/src/com/android/settings/inputmethod/CheckBoxAndSettingsPreference.java
deleted file mode 100644
index f440bc8..0000000
--- a/src/com/android/settings/inputmethod/CheckBoxAndSettingsPreference.java
+++ /dev/null
@@ -1,117 +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.inputmethod;
-
-import com.android.settings.R;
-import com.android.settings.SettingsPreferenceFragment;
-import com.android.settings.Utils;
-
-import android.content.Context;
-import android.content.Intent;
-import android.preference.CheckBoxPreference;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-public class CheckBoxAndSettingsPreference extends CheckBoxPreference {
-
- private SettingsPreferenceFragment mFragment;
- private TextView mTitleText;
- private TextView mSummaryText;
- private ImageView mSettingsButton;
- private Intent mSettingsIntent;
-
- public CheckBoxAndSettingsPreference(Context context, AttributeSet attrs) {
- super(context, attrs);
- setLayoutResource(R.layout.preference_inputmethod);
- setWidgetLayoutResource(R.layout.preference_inputmethod_widget);
- }
-
- @Override
- protected void onBindView(View view) {
- super.onBindView(view);
- View textLayout = view.findViewById(R.id.inputmethod_pref);
- textLayout.setOnClickListener(
- new OnClickListener() {
- @Override
- public void onClick(View arg0) {
- onCheckBoxClicked();
- }
- });
-
- mSettingsButton = (ImageView) view.findViewById(R.id.inputmethod_settings);
- mTitleText = (TextView)view.findViewById(android.R.id.title);
- mSummaryText = (TextView)view.findViewById(android.R.id.summary);
- mSettingsButton.setOnClickListener(
- new OnClickListener() {
- @Override
- public void onClick(View clickedView) {
- onSettingsButtonClicked();
- }
- });
- enableSettingsButton();
- }
-
- @Override
- public void setEnabled(boolean enabled) {
- super.setEnabled(enabled);
- enableSettingsButton();
- }
-
- public void setFragmentIntent(SettingsPreferenceFragment fragment, Intent intent) {
- mFragment = fragment;
- mSettingsIntent = intent;
- }
-
- protected void onCheckBoxClicked() {
- if (isChecked()) {
- setChecked(false);
- } else {
- setChecked(true);
- }
- }
-
- protected void onSettingsButtonClicked() {
- if (mFragment != null && mSettingsIntent != null) {
- mFragment.startActivity(mSettingsIntent);
- }
- }
-
- private void enableSettingsButton() {
- if (mSettingsButton != null) {
- if (mSettingsIntent == null) {
- mSettingsButton.setVisibility(View.GONE);
- } else {
- final boolean checked = isChecked();
- mSettingsButton.setEnabled(checked);
- mSettingsButton.setClickable(checked);
- mSettingsButton.setFocusable(checked);
- if (!checked) {
- mSettingsButton.setAlpha(Utils.DISABLED_ALPHA);
- }
- }
- }
- if (mTitleText != null) {
- mTitleText.setEnabled(true);
- }
- if (mSummaryText != null) {
- mSummaryText.setEnabled(true);
- }
- }
-}
diff --git a/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java b/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java
index dbfa1bc..bae9dbc 100644
--- a/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java
+++ b/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java
@@ -16,20 +16,17 @@
package com.android.settings.inputmethod;
-import com.android.settings.R;
-import com.android.settings.Settings.KeyboardLayoutPickerActivity;
-import com.android.settings.Settings.SpellCheckersSettingsActivity;
-import com.android.settings.SettingsPreferenceFragment;
-import com.android.settings.UserDictionarySettings;
-import com.android.settings.Utils;
-import com.android.settings.VoiceInputOutputSettings;
-
import android.app.Activity;
import android.app.Fragment;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -38,74 +35,78 @@ import android.hardware.input.InputManager;
import android.hardware.input.KeyboardLayout;
import android.os.Bundle;
import android.os.Handler;
+import android.os.UserHandle;
import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
import android.preference.Preference;
-import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceCategory;
+import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import android.provider.Settings;
import android.provider.Settings.System;
+import android.speech.RecognitionService;
+import android.speech.tts.TtsEngines;
import android.text.TextUtils;
import android.view.InputDevice;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
-import android.widget.BaseAdapter;
+import android.view.inputmethod.InputMethodSubtype;
+import android.view.textservice.SpellCheckerInfo;
+import android.view.textservice.TextServicesManager;
+
+import com.android.internal.app.LocalePicker;
+import com.android.settings.R;
+import com.android.settings.Settings.KeyboardLayoutPickerActivity;
+import com.android.settings.SettingsActivity;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.SubSettings;
+import com.android.settings.UserDictionarySettings;
+import com.android.settings.Utils;
+import com.android.settings.VoiceInputOutputSettings;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.search.Indexable;
+import com.android.settings.search.SearchIndexableRaw;
+import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.TreeSet;
public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
implements Preference.OnPreferenceChangeListener, InputManager.InputDeviceListener,
- KeyboardLayoutDialogFragment.OnSetupKeyboardLayoutsListener {
-
+ KeyboardLayoutDialogFragment.OnSetupKeyboardLayoutsListener, Indexable,
+ InputMethodPreference.OnSavePreferenceListener {
+ 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_PREVIOUSLY_ENABLED_SUBTYPES = "previously_enabled_subtypes";
// 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 PreferenceCategory mKeyboardSettingsCategory;
private PreferenceCategory mHardKeyboardCategory;
private PreferenceCategory mGameControllerCategory;
private Preference mLanguagePref;
- private final ArrayList<InputMethodPreference> mInputMethodPreferenceList =
- new ArrayList<InputMethodPreference>();
- private final ArrayList<PreferenceScreen> mHardKeyboardPreferenceList =
- new ArrayList<PreferenceScreen>();
+ private final ArrayList<InputMethodPreference> mInputMethodPreferenceList = new ArrayList<>();
+ private final ArrayList<PreferenceScreen> mHardKeyboardPreferenceList = new ArrayList<>();
private InputManager mIm;
private InputMethodManager mImm;
- private boolean mIsOnlyImeSettings;
+ private boolean mShowsOnlyFullImeAndKeyboardList;
private Handler mHandler;
private SettingsObserver mSettingsObserver;
private Intent mIntentWaitingForResult;
private InputMethodSettingValuesWrapper mInputMethodSettingValues;
-
- private final OnPreferenceChangeListener mOnImePreferenceChangedListener =
- new OnPreferenceChangeListener() {
- @Override
- public boolean onPreferenceChange(Preference arg0, Object arg1) {
- InputMethodSettingValuesWrapper.getInstance(
- arg0.getContext()).refreshAllInputMethodAndSubtypes();
- ((BaseAdapter)getPreferenceScreen().getRootAdapter()).notifyDataSetChanged();
- updateInputMethodPreferenceViews();
- return true;
- }
- };
+ private DevicePolicyManager mDpm;
@Override
public void onCreate(Bundle icicle) {
@@ -113,13 +114,17 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
addPreferencesFromResource(R.xml.language_settings);
+ final Activity activity = getActivity();
+ mImm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+ mInputMethodSettingValues = InputMethodSettingValuesWrapper.getInstance(activity);
+
try {
mDefaultInputMethodSelectorVisibility = Integer.valueOf(
getString(R.string.input_method_selector_visibility_default_value));
} catch (NumberFormatException e) {
}
- if (getActivity().getAssets().getLocales().length == 1) {
+ if (activity.getAssets().getLocales().length == 1) {
// No "Select language" pref if there's only one system locale available.
getPreferenceScreen().removePreference(findPreference(KEY_PHONE_LANGUAGE));
} else {
@@ -142,46 +147,50 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
mGameControllerCategory = (PreferenceCategory)findPreference(
"game_controller_settings_category");
+ final Intent startingIntent = activity.getIntent();
// Filter out irrelevant features if invoked from IME settings button.
- mIsOnlyImeSettings = Settings.ACTION_INPUT_METHOD_SETTINGS.equals(
- getActivity().getIntent().getAction());
- getActivity().getIntent().setAction(null);
- if (mIsOnlyImeSettings) {
+ mShowsOnlyFullImeAndKeyboardList = Settings.ACTION_INPUT_METHOD_SETTINGS.equals(
+ startingIntent.getAction());
+ if (mShowsOnlyFullImeAndKeyboardList) {
getPreferenceScreen().removeAll();
getPreferenceScreen().addPreference(mHardKeyboardCategory);
if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) {
getPreferenceScreen().addPreference(mShowInputMethodSelectorPref);
}
+ mKeyboardSettingsCategory.removeAll();
getPreferenceScreen().addPreference(mKeyboardSettingsCategory);
}
- // Build IME preference category.
- mImm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
- mInputMethodSettingValues = InputMethodSettingValuesWrapper.getInstance(getActivity());
-
- mKeyboardSettingsCategory.removeAll();
- if (!mIsOnlyImeSettings) {
- final PreferenceScreen currentIme = new PreferenceScreen(getActivity(), null);
- currentIme.setKey(KEY_CURRENT_INPUT_METHOD);
- currentIme.setTitle(getResources().getString(R.string.current_input_method));
- mKeyboardSettingsCategory.addPreference(currentIme);
- }
-
// Build hard keyboard and game controller preference categories.
- mIm = (InputManager)getActivity().getSystemService(Context.INPUT_SERVICE);
+ mIm = (InputManager)activity.getSystemService(Context.INPUT_SERVICE);
updateInputDevices();
// Spell Checker
- final Intent intent = new Intent(Intent.ACTION_MAIN);
- intent.setClass(getActivity(), SpellCheckersSettingsActivity.class);
- final SpellCheckersPreference scp = ((SpellCheckersPreference)findPreference(
- "spellcheckers_settings"));
- if (scp != null) {
- scp.setFragmentIntent(this, intent);
+ final Preference spellChecker = findPreference(KEY_SPELL_CHECKERS);
+ if (spellChecker != null) {
+ // Note: KEY_SPELL_CHECKERS preference is marked as persistent="false" in XML.
+ InputMethodAndSubtypeUtil.removeUnnecessaryNonPersistentPreference(spellChecker);
+ final Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setClass(activity, SubSettings.class);
+ intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT,
+ SpellCheckersSettings.class.getName());
+ intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID,
+ R.string.spellcheckers_settings_title);
+ spellChecker.setIntent(intent);
}
mHandler = new Handler();
- mSettingsObserver = new SettingsObserver(mHandler, getActivity());
+ mSettingsObserver = new SettingsObserver(mHandler, activity);
+ mDpm = (DevicePolicyManager) (getActivity().
+ getSystemService(Context.DEVICE_POLICY_SERVICE));
+
+ // If we've launched from the keyboard layout notification, go ahead and just show the
+ // keyboard layout dialog.
+ final InputDeviceIdentifier identifier =
+ startingIntent.getParcelableExtra(Settings.EXTRA_INPUT_DEVICE_IDENTIFIER);
+ if (mShowsOnlyFullImeAndKeyboardList && identifier != null) {
+ showKeyboardLayoutDialog(identifier);
+ }
}
private void updateInputMethodSelectorSummary(int value) {
@@ -226,7 +235,7 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
targetFragment = UserDictionaryList.class;
}
startFragment(InputMethodAndLanguageSettings.this,
- targetFragment.getCanonicalName(), -1, extras);
+ targetFragment.getCanonicalName(), -1, -1, extras);
return true;
}
});
@@ -240,36 +249,22 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
mSettingsObserver.resume();
mIm.registerInputDeviceListener(this, null);
- if (!mIsOnlyImeSettings) {
+ final Preference spellChecker = findPreference(KEY_SPELL_CHECKERS);
+ if (spellChecker != null) {
+ final TextServicesManager tsm = (TextServicesManager) getSystemService(
+ Context.TEXT_SERVICES_MANAGER_SERVICE);
+ if (tsm.isSpellCheckerEnabled()) {
+ final SpellCheckerInfo sci = tsm.getCurrentSpellChecker();
+ spellChecker.setSummary(sci.loadLabel(getPackageManager()));
+ } else {
+ spellChecker.setSummary(R.string.switch_off_text);
+ }
+ }
+
+ if (!mShowsOnlyFullImeAndKeyboardList) {
if (mLanguagePref != null) {
- Configuration conf = getResources().getConfiguration();
- String language = conf.locale.getLanguage();
- String localeString;
- // TODO: This is not an accurate way to display the locale, as it is
- // just working around the fact that we support limited dialects
- // and want to pretend that the language is valid for all locales.
- // We need a way to support languages that aren't tied to a particular
- // locale instead of hiding the locale qualifier.
- if (language.equals("zz")) {
- String country = conf.locale.getCountry();
- if (country.equals("ZZ")) {
- localeString = "[Developer] Accented English (zz_ZZ)";
- } else if (country.equals("ZY")) {
- localeString = "[Developer] Fake Bi-Directional (zz_ZY)";
- } else {
- localeString = "";
- }
- } else if (hasOnlyOneLanguageInstance(language,
- Resources.getSystem().getAssets().getLocales())) {
- localeString = conf.locale.getDisplayLanguage(conf.locale);
- } else {
- localeString = conf.locale.getDisplayName(conf.locale);
- }
- if (localeString.length() > 1) {
- localeString = Character.toUpperCase(localeString.charAt(0))
- + localeString.substring(1);
- mLanguagePref.setSummary(localeString);
- }
+ String localeName = getLocaleName(getActivity());
+ mLanguagePref.setSummary(localeName);
}
updateUserDictionaryPreference(findPreference(KEY_USER_DICTIONARY_SETTINGS));
@@ -278,16 +273,6 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
}
}
- // Hard keyboard
- if (!mHardKeyboardPreferenceList.isEmpty()) {
- for (int i = 0; i < sHardKeyboardKeys.length; ++i) {
- CheckBoxPreference chkPref = (CheckBoxPreference)
- mHardKeyboardCategory.findPreference(sHardKeyboardKeys[i]);
- chkPref.setChecked(
- System.getInt(getContentResolver(), sSystemSettingNames[i], 1) > 0);
- }
- }
-
updateInputDevices();
// Refresh internal states in mInputMethodSettingValues to keep the latest
@@ -343,15 +328,6 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
}
} else if (preference instanceof CheckBoxPreference) {
final CheckBoxPreference chkPref = (CheckBoxPreference) preference;
- if (!mHardKeyboardPreferenceList.isEmpty()) {
- for (int i = 0; i < sHardKeyboardKeys.length; ++i) {
- if (chkPref == mHardKeyboardCategory.findPreference(sHardKeyboardKeys[i])) {
- System.putInt(getContentResolver(), sSystemSettingNames[i],
- chkPref.isChecked() ? 1 : 0);
- return true;
- }
- }
- }
if (chkPref == mGameControllerCategory.findPreference("vibrate_input_devices")) {
System.putInt(getContentResolver(), Settings.System.VIBRATE_INPUT_DEVICES,
chkPref.isChecked() ? 1 : 0);
@@ -361,18 +337,19 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
return super.onPreferenceTreeClick(preferenceScreen, preference);
}
- private boolean hasOnlyOneLanguageInstance(String languageCode, String[] locales) {
- int count = 0;
- for (String localeCode : locales) {
- if (localeCode.length() > 2
- && localeCode.startsWith(languageCode)) {
- count++;
- if (count > 1) {
- return false;
- }
+ private static String getLocaleName(Context context) {
+ // We want to show the same string that the LocalePicker used.
+ // TODO: should this method be in LocalePicker instead?
+ Locale currentLocale = context.getResources().getConfiguration().locale;
+ List<LocalePicker.LocaleInfo> locales = LocalePicker.getAllAssetLocales(context, true);
+ for (LocalePicker.LocaleInfo locale : locales) {
+ if (locale.getLocale().equals(currentLocale)) {
+ return locale.getLabel();
}
}
- return count == 1;
+ // This can't happen as long as the locale was one set by Settings.
+ // Fall back in case a developer is testing an unsupported locale.
+ return currentLocale.getDisplayName(currentLocale);
}
private void saveInputMethodSelectorVisibility(String value) {
@@ -406,31 +383,37 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
private void updateInputMethodPreferenceViews() {
synchronized (mInputMethodPreferenceList) {
// Clear existing "InputMethodPreference"s
- for (final InputMethodPreference imp : mInputMethodPreferenceList) {
- mKeyboardSettingsCategory.removePreference(imp);
+ for (final InputMethodPreference pref : mInputMethodPreferenceList) {
+ mKeyboardSettingsCategory.removePreference(pref);
}
mInputMethodPreferenceList.clear();
- final List<InputMethodInfo> imis = mInputMethodSettingValues.getInputMethodList();
+ List<String> permittedList = mDpm.getPermittedInputMethodsForCurrentUser();
+ final Context context = getActivity();
+ final List<InputMethodInfo> imis = mShowsOnlyFullImeAndKeyboardList
+ ? mInputMethodSettingValues.getInputMethodList()
+ : mImm.getEnabledInputMethodList();
final int N = (imis == null ? 0 : imis.size());
for (int i = 0; i < N; ++i) {
final InputMethodInfo imi = imis.get(i);
- final InputMethodPreference pref = getInputMethodPreference(imi);
- pref.setOnImePreferenceChangeListener(mOnImePreferenceChangedListener);
+ final boolean isAllowedByOrganization = permittedList == null
+ || permittedList.contains(imi.getPackageName());
+ final InputMethodPreference pref = new InputMethodPreference(
+ context, imi, mShowsOnlyFullImeAndKeyboardList /* hasSwitch */,
+ isAllowedByOrganization, this);
mInputMethodPreferenceList.add(pref);
}
-
- if (!mInputMethodPreferenceList.isEmpty()) {
- Collections.sort(mInputMethodPreferenceList);
- for (int i = 0; i < N; ++i) {
- mKeyboardSettingsCategory.addPreference(mInputMethodPreferenceList.get(i));
- }
- }
-
- // update views status
- for (Preference pref : mInputMethodPreferenceList) {
- if (pref instanceof InputMethodPreference) {
- ((InputMethodPreference) pref).updatePreferenceViews();
+ final Collator collator = Collator.getInstance();
+ Collections.sort(mInputMethodPreferenceList, new Comparator<InputMethodPreference>() {
+ @Override
+ public int compare(InputMethodPreference lhs, InputMethodPreference rhs) {
+ return lhs.compareTo(rhs, collator);
}
+ });
+ for (int i = 0; i < N; ++i) {
+ final InputMethodPreference pref = mInputMethodPreferenceList.get(i);
+ mKeyboardSettingsCategory.addPreference(pref);
+ InputMethodAndSubtypeUtil.removeUnnecessaryNonPersistentPreference(pref);
+ pref.updatePreferenceViews();
}
}
updateCurrentImeName();
@@ -443,6 +426,74 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
mInputMethodSettingValues.getInputMethodList(), null);
}
+ @Override
+ public void onSaveInputMethodPreference(final InputMethodPreference pref) {
+ final InputMethodInfo imi = pref.getInputMethodInfo();
+ if (!pref.isChecked()) {
+ // An IME is being disabled. Save enabled subtypes of the IME to shared preference to be
+ // able to re-enable these subtypes when the IME gets re-enabled.
+ saveEnabledSubtypesOf(imi);
+ }
+ final boolean hasHardwareKeyboard = getResources().getConfiguration().keyboard
+ == Configuration.KEYBOARD_QWERTY;
+ InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(this, getContentResolver(),
+ mImm.getInputMethodList(), hasHardwareKeyboard);
+ // Update input method settings and preference list.
+ mInputMethodSettingValues.refreshAllInputMethodAndSubtypes();
+ if (pref.isChecked()) {
+ // An IME is being enabled. Load the previously enabled subtypes from shared preference
+ // and enable these subtypes.
+ restorePreviouslyEnabledSubtypesOf(imi);
+ }
+ for (final InputMethodPreference p : mInputMethodPreferenceList) {
+ p.updatePreferenceViews();
+ }
+ }
+
+ private void saveEnabledSubtypesOf(final InputMethodInfo imi) {
+ final HashSet<String> enabledSubtypeIdSet = new HashSet<>();
+ final List<InputMethodSubtype> enabledSubtypes = mImm.getEnabledInputMethodSubtypeList(
+ imi, true /* allowsImplicitlySelectedSubtypes */);
+ for (final InputMethodSubtype subtype : enabledSubtypes) {
+ final String subtypeId = Integer.toString(subtype.hashCode());
+ enabledSubtypeIdSet.add(subtypeId);
+ }
+ final HashMap<String, HashSet<String>> imeToEnabledSubtypeIdsMap =
+ loadPreviouslyEnabledSubtypeIdsMap();
+ final String imiId = imi.getId();
+ imeToEnabledSubtypeIdsMap.put(imiId, enabledSubtypeIdSet);
+ savePreviouslyEnabledSubtypeIdsMap(imeToEnabledSubtypeIdsMap);
+ }
+
+ private void restorePreviouslyEnabledSubtypesOf(final InputMethodInfo imi) {
+ final HashMap<String, HashSet<String>> imeToEnabledSubtypeIdsMap =
+ loadPreviouslyEnabledSubtypeIdsMap();
+ final String imiId = imi.getId();
+ final HashSet<String> enabledSubtypeIdSet = imeToEnabledSubtypeIdsMap.remove(imiId);
+ if (enabledSubtypeIdSet == null) {
+ return;
+ }
+ savePreviouslyEnabledSubtypeIdsMap(imeToEnabledSubtypeIdsMap);
+ InputMethodAndSubtypeUtil.enableInputMethodSubtypesOf(
+ getContentResolver(), imiId, enabledSubtypeIdSet);
+ }
+
+ private HashMap<String, HashSet<String>> loadPreviouslyEnabledSubtypeIdsMap() {
+ final Context context = getActivity();
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ final String imesAndSubtypesString = prefs.getString(KEY_PREVIOUSLY_ENABLED_SUBTYPES, null);
+ return InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString(imesAndSubtypesString);
+ }
+
+ private void savePreviouslyEnabledSubtypeIdsMap(
+ final HashMap<String, HashSet<String>> subtypesMap) {
+ final Context context = getActivity();
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ final String imesAndSubtypesString = InputMethodAndSubtypeUtil
+ .buildInputMethodsAndSubtypesString(subtypesMap);
+ prefs.edit().putString(KEY_PREVIOUSLY_ENABLED_SUBTYPES, imesAndSubtypesString).apply();
+ }
+
private void updateCurrentImeName() {
final Context context = getActivity();
if (context == null || mImm == null) return;
@@ -451,34 +502,13 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
final CharSequence curIme =
mInputMethodSettingValues.getCurrentInputMethodName(context);
if (!TextUtils.isEmpty(curIme)) {
- synchronized(this) {
+ synchronized (this) {
curPref.setSummary(curIme);
}
}
}
}
- private InputMethodPreference getInputMethodPreference(InputMethodInfo imi) {
- final PackageManager pm = getPackageManager();
- final CharSequence label = imi.loadLabel(pm);
- // IME settings
- final Intent intent;
- final String settingsActivity = imi.getSettingsActivity();
- if (!TextUtils.isEmpty(settingsActivity)) {
- intent = new Intent(Intent.ACTION_MAIN);
- intent.setClassName(imi.getPackageName(), settingsActivity);
- } else {
- intent = null;
- }
-
- // Add a check box for enabling/disabling IME
- final InputMethodPreference pref =
- new InputMethodPreference(this, intent, mImm, imi);
- pref.setKey(imi.getId());
- pref.setTitle(label);
- return pref;
- }
-
private void updateInputDevices() {
updateHardKeyboards();
updateGameControllers();
@@ -486,35 +516,33 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
private void updateHardKeyboards() {
mHardKeyboardPreferenceList.clear();
- if (getResources().getConfiguration().keyboard == Configuration.KEYBOARD_QWERTY) {
- final int[] devices = InputDevice.getDeviceIds();
- for (int i = 0; i < devices.length; i++) {
- InputDevice device = InputDevice.getDevice(devices[i]);
- if (device != null
- && !device.isVirtual()
- && device.isFullKeyboard()) {
- final InputDeviceIdentifier identifier = device.getIdentifier();
- final String keyboardLayoutDescriptor =
- mIm.getCurrentKeyboardLayoutForInputDevice(identifier);
- final KeyboardLayout keyboardLayout = keyboardLayoutDescriptor != null ?
- mIm.getKeyboardLayout(keyboardLayoutDescriptor) : null;
-
- final PreferenceScreen pref = new PreferenceScreen(getActivity(), null);
- pref.setTitle(device.getName());
- if (keyboardLayout != null) {
- pref.setSummary(keyboardLayout.toString());
- } else {
- pref.setSummary(R.string.keyboard_layout_default_label);
- }
- pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
- @Override
- public boolean onPreferenceClick(Preference preference) {
- showKeyboardLayoutDialog(identifier);
- return true;
- }
- });
- mHardKeyboardPreferenceList.add(pref);
+ final int[] devices = InputDevice.getDeviceIds();
+ for (int i = 0; i < devices.length; i++) {
+ InputDevice device = InputDevice.getDevice(devices[i]);
+ if (device != null
+ && !device.isVirtual()
+ && device.isFullKeyboard()) {
+ final InputDeviceIdentifier identifier = device.getIdentifier();
+ final String keyboardLayoutDescriptor =
+ mIm.getCurrentKeyboardLayoutForInputDevice(identifier);
+ final KeyboardLayout keyboardLayout = keyboardLayoutDescriptor != null ?
+ mIm.getKeyboardLayout(keyboardLayoutDescriptor) : null;
+
+ final PreferenceScreen pref = new PreferenceScreen(getActivity(), null);
+ pref.setTitle(device.getName());
+ if (keyboardLayout != null) {
+ pref.setSummary(keyboardLayout.toString());
+ } else {
+ pref.setSummary(R.string.keyboard_layout_default_label);
}
+ pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ showKeyboardLayoutDialog(identifier);
+ return true;
+ }
+ });
+ mHardKeyboardPreferenceList.add(pref);
}
}
@@ -541,8 +569,8 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
}
private void showKeyboardLayoutDialog(InputDeviceIdentifier inputDeviceIdentifier) {
- KeyboardLayoutDialogFragment fragment =
- new KeyboardLayoutDialogFragment(inputDeviceIdentifier);
+ KeyboardLayoutDialogFragment fragment = new KeyboardLayoutDialogFragment(
+ inputDeviceIdentifier);
fragment.setTargetFragment(this, 0);
fragment.show(getActivity().getFragmentManager(), "keyboardLayout");
}
@@ -582,7 +610,7 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
}
}
- private boolean haveInputDeviceWithVibrator() {
+ private static boolean haveInputDeviceWithVibrator() {
final int[] devices = InputDevice.getDeviceIds();
for (int i = 0; i < devices.length; i++) {
InputDevice device = InputDevice.getDevice(devices[i]);
@@ -617,4 +645,190 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
mContext.getContentResolver().unregisterContentObserver(this);
}
}
+
+ public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider() {
+ @Override
+ public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
+ List<SearchIndexableRaw> indexables = new ArrayList<>();
+
+ final String screenTitle = context.getString(R.string.language_keyboard_settings_title);
+
+ // Locale picker.
+ if (context.getAssets().getLocales().length > 1) {
+ String localeName = getLocaleName(context);
+ SearchIndexableRaw indexable = new SearchIndexableRaw(context);
+ indexable.key = KEY_PHONE_LANGUAGE;
+ indexable.title = context.getString(R.string.phone_language);
+ indexable.summaryOn = localeName;
+ indexable.summaryOff = localeName;
+ indexable.screenTitle = screenTitle;
+ indexables.add(indexable);
+ }
+
+ // Spell checker.
+ SearchIndexableRaw indexable = new SearchIndexableRaw(context);
+ indexable.key = KEY_SPELL_CHECKERS;
+ indexable.title = context.getString(R.string.spellcheckers_settings_title);
+ indexable.screenTitle = screenTitle;
+ indexables.add(indexable);
+
+ // User dictionary.
+ if (UserDictionaryList.getUserDictionaryLocalesSet(context) != null) {
+ indexable = new SearchIndexableRaw(context);
+ indexable.key = "user_dict_settings";
+ indexable.title = context.getString(R.string.user_dict_settings_title);
+ indexable.screenTitle = screenTitle;
+ indexables.add(indexable);
+ }
+
+ // Keyboard settings.
+ indexable = new SearchIndexableRaw(context);
+ indexable.key = "keyboard_settings";
+ indexable.title = context.getString(R.string.keyboard_settings_category);
+ indexable.screenTitle = screenTitle;
+ indexables.add(indexable);
+
+ InputMethodSettingValuesWrapper immValues = InputMethodSettingValuesWrapper
+ .getInstance(context);
+ immValues.refreshAllInputMethodAndSubtypes();
+
+ // Current IME.
+ String currImeName = immValues.getCurrentInputMethodName(context).toString();
+ indexable = new SearchIndexableRaw(context);
+ indexable.key = KEY_CURRENT_INPUT_METHOD;
+ indexable.title = context.getString(R.string.current_input_method);
+ indexable.summaryOn = currImeName;
+ indexable.summaryOff = currImeName;
+ indexable.screenTitle = screenTitle;
+ indexables.add(indexable);
+
+ InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(
+ Context.INPUT_METHOD_SERVICE);
+
+ // All other IMEs.
+ List<InputMethodInfo> inputMethods = immValues.getInputMethodList();
+ final int inputMethodCount = (inputMethods == null ? 0 : inputMethods.size());
+ for (int i = 0; i < inputMethodCount; ++i) {
+ InputMethodInfo inputMethod = inputMethods.get(i);
+
+ StringBuilder builder = new StringBuilder();
+ List<InputMethodSubtype> subtypes = inputMethodManager
+ .getEnabledInputMethodSubtypeList(inputMethod, true);
+ final int subtypeCount = subtypes.size();
+ for (int j = 0; j < subtypeCount; j++) {
+ InputMethodSubtype subtype = subtypes.get(j);
+ if (builder.length() > 0) {
+ builder.append(',');
+ }
+ CharSequence subtypeLabel = subtype.getDisplayName(context,
+ inputMethod.getPackageName(), inputMethod.getServiceInfo()
+ .applicationInfo);
+ builder.append(subtypeLabel);
+ }
+ String summary = builder.toString();
+
+ ServiceInfo serviceInfo = inputMethod.getServiceInfo();
+ ComponentName componentName = new ComponentName(serviceInfo.packageName,
+ serviceInfo.name);
+
+ indexable = new SearchIndexableRaw(context);
+ indexable.key = componentName.flattenToString();
+ indexable.title = inputMethod.loadLabel(context.getPackageManager()).toString();
+ indexable.summaryOn = summary;
+ indexable.summaryOff = summary;
+ indexable.screenTitle = screenTitle;
+ indexables.add(indexable);
+ }
+
+ // Hard keyboards
+ InputManager inputManager = (InputManager) context.getSystemService(
+ Context.INPUT_SERVICE);
+ boolean hasHardKeyboards = false;
+
+ final int[] devices = InputDevice.getDeviceIds();
+ for (int i = 0; i < devices.length; i++) {
+ InputDevice device = InputDevice.getDevice(devices[i]);
+ if (device == null || device.isVirtual() || !device.isFullKeyboard()) {
+ continue;
+ }
+
+ hasHardKeyboards = true;
+
+ InputDeviceIdentifier identifier = device.getIdentifier();
+ String keyboardLayoutDescriptor =
+ inputManager.getCurrentKeyboardLayoutForInputDevice(identifier);
+ KeyboardLayout keyboardLayout = keyboardLayoutDescriptor != null ?
+ inputManager.getKeyboardLayout(keyboardLayoutDescriptor) : null;
+
+ String summary;
+ if (keyboardLayout != null) {
+ summary = keyboardLayout.toString();
+ } else {
+ summary = context.getString(R.string.keyboard_layout_default_label);
+ }
+
+ indexable = new SearchIndexableRaw(context);
+ indexable.key = device.getName();
+ indexable.title = device.getName();
+ indexable.summaryOn = summary;
+ indexable.summaryOff = summary;
+ indexable.screenTitle = screenTitle;
+ indexables.add(indexable);
+ }
+
+ if (hasHardKeyboards) {
+ // Hard keyboard category.
+ indexable = new SearchIndexableRaw(context);
+ indexable.key = "builtin_keyboard_settings";
+ indexable.title = context.getString(
+ R.string.builtin_keyboard_settings_title);
+ indexable.screenTitle = screenTitle;
+ indexables.add(indexable);
+ }
+
+ // Voice input
+ indexable = new SearchIndexableRaw(context);
+ indexable.key = "voice_input_settings";
+ indexable.title = context.getString(R.string.voice_input_settings);
+ indexable.screenTitle = screenTitle;
+ indexables.add(indexable);
+
+ // Text-to-speech.
+ TtsEngines ttsEngines = new TtsEngines(context);
+ if (!ttsEngines.getEngines().isEmpty()) {
+ indexable = new SearchIndexableRaw(context);
+ indexable.key = "tts_settings";
+ indexable.title = context.getString(R.string.tts_settings_title);
+ indexable.screenTitle = screenTitle;
+ indexables.add(indexable);
+ }
+
+ // Pointer settings.
+ indexable = new SearchIndexableRaw(context);
+ indexable.key = "pointer_settings_category";
+ indexable.title = context.getString(R.string.pointer_settings_category);
+ indexable.screenTitle = screenTitle;
+ indexables.add(indexable);
+
+ indexable = new SearchIndexableRaw(context);
+ indexable.key = "pointer_speed";
+ indexable.title = context.getString(R.string.pointer_speed);
+ indexable.screenTitle = screenTitle;
+ indexables.add(indexable);
+
+ // Game controllers.
+ if (haveInputDeviceWithVibrator()) {
+ indexable = new SearchIndexableRaw(context);
+ indexable.key = "vibrate_input_devices";
+ indexable.title = context.getString(R.string.vibrate_input_devices);
+ indexable.summaryOn = context.getString(R.string.vibrate_input_devices_summary);
+ indexable.summaryOff = context.getString(R.string.vibrate_input_devices_summary);
+ indexable.screenTitle = screenTitle;
+ indexables.add(indexable);
+ }
+
+ return indexables;
+ }
+ };
}
diff --git a/src/com/android/settings/inputmethod/InputMethodAndSubtypeEnabler.java b/src/com/android/settings/inputmethod/InputMethodAndSubtypeEnabler.java
index 419a877..146f512 100644
--- a/src/com/android/settings/inputmethod/InputMethodAndSubtypeEnabler.java
+++ b/src/com/android/settings/inputmethod/InputMethodAndSubtypeEnabler.java
@@ -16,91 +16,87 @@
package com.android.settings.inputmethod;
-import com.android.internal.inputmethod.InputMethodUtils;
-import com.android.settings.R;
-import com.android.settings.SettingsPreferenceFragment;
-
-import android.app.AlertDialog;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.os.Bundle;
-import android.preference.CheckBoxPreference;
import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.PreferenceCategory;
import android.preference.PreferenceScreen;
+import android.preference.TwoStatePreference;
import android.text.TextUtils;
-import android.util.Log;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
-import java.util.Locale;
-public class InputMethodAndSubtypeEnabler extends SettingsPreferenceFragment {
- private static final String TAG =InputMethodAndSubtypeEnabler.class.getSimpleName();
- private AlertDialog mDialog = null;
+public class InputMethodAndSubtypeEnabler extends SettingsPreferenceFragment
+ implements OnPreferenceChangeListener {
private boolean mHaveHardKeyboard;
- final private HashMap<String, List<Preference>> mInputMethodAndSubtypePrefsMap =
- new HashMap<String, List<Preference>>();
- final private HashMap<String, CheckBoxPreference> mSubtypeAutoSelectionCBMap =
- new HashMap<String, CheckBoxPreference>();
+ private final HashMap<String, List<Preference>> mInputMethodAndSubtypePrefsMap =
+ new HashMap<>();
+ private final HashMap<String, TwoStatePreference> mAutoSelectionPrefsMap = new HashMap<>();
private InputMethodManager mImm;
- private List<InputMethodInfo> mInputMethodProperties;
- private String mInputMethodId;
- private String mTitle;
- private String mSystemLocale = "";
- private Collator mCollator = Collator.getInstance();
+ // TODO: Change mInputMethodInfoList to Map
+ private List<InputMethodInfo> mInputMethodInfoList;
+ private Collator mCollator;
@Override
- public void onCreate(Bundle icicle) {
+ public void onCreate(final Bundle icicle) {
super.onCreate(icicle);
mImm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
final Configuration config = getResources().getConfiguration();
mHaveHardKeyboard = (config.keyboard == Configuration.KEYBOARD_QWERTY);
- final Bundle arguments = getArguments();
// Input method id should be available from an Intent when this preference is launched as a
// single Activity (see InputMethodAndSubtypeEnablerActivity). It should be available
// from a preference argument when the preference is launched as a part of the other
// Activity (like a right pane of 2-pane Settings app)
- mInputMethodId = getActivity().getIntent().getStringExtra(
+ final String targetImi = getStringExtraFromIntentOrArguments(
android.provider.Settings.EXTRA_INPUT_METHOD_ID);
- if (mInputMethodId == null && (arguments != null)) {
- final String inputMethodId =
- arguments.getString(android.provider.Settings.EXTRA_INPUT_METHOD_ID);
- if (inputMethodId != null) {
- mInputMethodId = inputMethodId;
- }
- }
- mTitle = getActivity().getIntent().getStringExtra(Intent.EXTRA_TITLE);
- if (mTitle == null && (arguments != null)) {
- final String title = arguments.getString(Intent.EXTRA_TITLE);
- if (title != null) {
- mTitle = title;
+
+ mInputMethodInfoList = mImm.getInputMethodList();
+ mCollator = Collator.getInstance();
+
+ final PreferenceScreen root = getPreferenceManager().createPreferenceScreen(getActivity());
+ final int imiCount = mInputMethodInfoList.size();
+ for (int index = 0; index < imiCount; ++index) {
+ final InputMethodInfo imi = mInputMethodInfoList.get(index);
+ // Add subtype preferences of this IME when it is specified or no IME is specified.
+ if (imi.getId().equals(targetImi) || TextUtils.isEmpty(targetImi)) {
+ addInputMethodSubtypePreferences(imi, root);
}
}
+ setPreferenceScreen(root);
+ }
- final Locale locale = config.locale;
- mSystemLocale = locale.toString();
- mCollator = Collator.getInstance(locale);
- onCreateIMM();
- setPreferenceScreen(createPreferenceHierarchy());
+ private String getStringExtraFromIntentOrArguments(final String name) {
+ final Intent intent = getActivity().getIntent();
+ final String fromIntent = intent.getStringExtra(name);
+ if (fromIntent != null) {
+ return fromIntent;
+ }
+ final Bundle arguments = getArguments();
+ return (arguments == null) ? null : arguments.getString(name);
}
@Override
- public void onActivityCreated(Bundle icicle) {
+ public void onActivityCreated(final Bundle icicle) {
super.onActivityCreated(icicle);
- if (!TextUtils.isEmpty(mTitle)) {
- getActivity().setTitle(mTitle);
+ final String title = getStringExtraFromIntentOrArguments(Intent.EXTRA_TITLE);
+ if (!TextUtils.isEmpty(title)) {
+ getActivity().setTitle(title);
}
}
@@ -112,323 +108,196 @@ public class InputMethodAndSubtypeEnabler extends SettingsPreferenceFragment {
InputMethodSettingValuesWrapper
.getInstance(getActivity()).refreshAllInputMethodAndSubtypes();
InputMethodAndSubtypeUtil.loadInputMethodSubtypeList(
- this, getContentResolver(), mInputMethodProperties, mInputMethodAndSubtypePrefsMap);
- updateAutoSelectionCB();
+ this, getContentResolver(), mInputMethodInfoList, mInputMethodAndSubtypePrefsMap);
+ updateAutoSelectionPreferences();
}
@Override
public void onPause() {
super.onPause();
// Clear all subtypes of all IMEs to make sure
- clearImplicitlyEnabledSubtypes(null);
+ updateImplicitlyEnabledSubtypes(null /* targetImiId */, false /* check */);
InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(this, getContentResolver(),
- mInputMethodProperties, mHaveHardKeyboard);
+ mInputMethodInfoList, mHaveHardKeyboard);
}
@Override
- public boolean onPreferenceTreeClick(
- PreferenceScreen preferenceScreen, Preference preference) {
-
- if (preference instanceof CheckBoxPreference) {
- final CheckBoxPreference chkPref = (CheckBoxPreference) preference;
-
- for (String imiId: mSubtypeAutoSelectionCBMap.keySet()) {
- if (mSubtypeAutoSelectionCBMap.get(imiId) == chkPref) {
- // We look for the first preference item in subtype enabler.
- // The first item is used for turning on/off subtype auto selection.
- // We are in the subtype enabler and trying selecting subtypes automatically.
- setSubtypeAutoSelectionEnabled(imiId, chkPref.isChecked());
- return super.onPreferenceTreeClick(preferenceScreen, preference);
- }
- }
-
- final String id = chkPref.getKey();
- if (chkPref.isChecked()) {
- InputMethodInfo selImi = null;
- final int N = mInputMethodProperties.size();
- for (int i = 0; i < N; i++) {
- InputMethodInfo imi = mInputMethodProperties.get(i);
- if (id.equals(imi.getId())) {
- selImi = imi;
- if (InputMethodUtils.isSystemIme(imi)) {
- InputMethodAndSubtypeUtil.setSubtypesPreferenceEnabled(
- this, mInputMethodProperties, id, true);
- // This is a built-in IME, so no need to warn.
- return super.onPreferenceTreeClick(preferenceScreen, preference);
- }
- break;
- }
- }
- if (selImi == null) {
- return super.onPreferenceTreeClick(preferenceScreen, preference);
- }
- chkPref.setChecked(false);
- if (mDialog == null) {
- mDialog = (new AlertDialog.Builder(getActivity()))
- .setTitle(android.R.string.dialog_alert_title)
- .setIconAttribute(android.R.attr.alertDialogIcon)
- .setCancelable(true)
- .setPositiveButton(android.R.string.ok,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- chkPref.setChecked(true);
- InputMethodAndSubtypeUtil.setSubtypesPreferenceEnabled(
- InputMethodAndSubtypeEnabler.this,
- mInputMethodProperties, id, true);
- }
-
- })
- .setNegativeButton(android.R.string.cancel,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- }
-
- })
- .create();
- } else {
- if (mDialog.isShowing()) {
- mDialog.dismiss();
- }
- }
- mDialog.setMessage(getResources().getString(
- R.string.ime_security_warning,
- selImi.getServiceInfo().applicationInfo.loadLabel(getPackageManager())));
- mDialog.show();
- } else {
- InputMethodAndSubtypeUtil.setSubtypesPreferenceEnabled(
- this, mInputMethodProperties, id, false);
- updateAutoSelectionCB();
+ public boolean onPreferenceChange(final Preference pref, final Object newValue) {
+ if (!(newValue instanceof Boolean)) {
+ return true; // Invoke default behavior.
+ }
+ final boolean isChecking = (Boolean) newValue;
+ for (final String imiId : mAutoSelectionPrefsMap.keySet()) {
+ // An auto select subtype preference is changing.
+ if (mAutoSelectionPrefsMap.get(imiId) == pref) {
+ final TwoStatePreference autoSelectionPref = (TwoStatePreference) pref;
+ autoSelectionPref.setChecked(isChecking);
+ // Enable or disable subtypes depending on the auto selection preference.
+ setAutoSelectionSubtypesEnabled(imiId, autoSelectionPref.isChecked());
+ return false;
}
}
- return super.onPreferenceTreeClick(preferenceScreen, preference);
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- if (mDialog != null) {
- mDialog.dismiss();
- mDialog = null;
+ // A subtype preference is changing.
+ if (pref instanceof InputMethodSubtypePreference) {
+ final InputMethodSubtypePreference subtypePref = (InputMethodSubtypePreference) pref;
+ subtypePref.setChecked(isChecking);
+ if (!subtypePref.isChecked()) {
+ // It takes care of the case where no subtypes are explicitly enabled then the auto
+ // selection preference is going to be checked.
+ updateAutoSelectionPreferences();
+ }
+ return false;
}
+ return true; // Invoke default behavior.
}
- private void onCreateIMM() {
- InputMethodManager imm = (InputMethodManager) getSystemService(
- Context.INPUT_METHOD_SERVICE);
-
- // TODO: Change mInputMethodProperties to Map
- mInputMethodProperties = imm.getInputMethodList();
- }
-
- private PreferenceScreen createPreferenceHierarchy() {
- // Root
- final PreferenceScreen root = getPreferenceManager().createPreferenceScreen(getActivity());
+ private void addInputMethodSubtypePreferences(final InputMethodInfo imi,
+ final PreferenceScreen root) {
final Context context = getActivity();
-
- int N = (mInputMethodProperties == null ? 0 : mInputMethodProperties.size());
-
- for (int i = 0; i < N; ++i) {
- final InputMethodInfo imi = mInputMethodProperties.get(i);
- final int subtypeCount = imi.getSubtypeCount();
- if (subtypeCount <= 1) continue;
- final String imiId = imi.getId();
- // Add this subtype to the list when no IME is specified or when the IME of this
- // subtype is the specified IME.
- if (!TextUtils.isEmpty(mInputMethodId) && !mInputMethodId.equals(imiId)) {
- continue;
- }
- final PreferenceCategory keyboardSettingsCategory = new PreferenceCategory(context);
- root.addPreference(keyboardSettingsCategory);
- final PackageManager pm = getPackageManager();
- final CharSequence label = imi.loadLabel(pm);
-
- keyboardSettingsCategory.setTitle(label);
- keyboardSettingsCategory.setKey(imiId);
- // TODO: Use toggle Preference if images are ready.
- final CheckBoxPreference autoCB = new CheckBoxPreference(context);
- mSubtypeAutoSelectionCBMap.put(imiId, autoCB);
- keyboardSettingsCategory.addPreference(autoCB);
-
- final PreferenceCategory activeInputMethodsCategory = new PreferenceCategory(context);
- activeInputMethodsCategory.setTitle(R.string.active_input_method_subtypes);
- root.addPreference(activeInputMethodsCategory);
-
- boolean isAutoSubtype = false;
- CharSequence autoSubtypeLabel = null;
- final ArrayList<Preference> subtypePreferences = new ArrayList<Preference>();
- if (subtypeCount > 0) {
- for (int j = 0; j < subtypeCount; ++j) {
- final InputMethodSubtype subtype = imi.getSubtypeAt(j);
- final CharSequence subtypeLabel = subtype.getDisplayName(context,
- imi.getPackageName(), imi.getServiceInfo().applicationInfo);
- if (subtype.overridesImplicitlyEnabledSubtype()) {
- if (!isAutoSubtype) {
- isAutoSubtype = true;
- autoSubtypeLabel = subtypeLabel;
- }
- } else {
- final CheckBoxPreference chkbxPref = new SubtypeCheckBoxPreference(
- context, subtype.getLocale(), mSystemLocale, mCollator);
- chkbxPref.setKey(imiId + subtype.hashCode());
- chkbxPref.setTitle(subtypeLabel);
- subtypePreferences.add(chkbxPref);
- }
- }
- Collections.sort(subtypePreferences);
- for (int j = 0; j < subtypePreferences.size(); ++j) {
- activeInputMethodsCategory.addPreference(subtypePreferences.get(j));
+ final int subtypeCount = imi.getSubtypeCount();
+ if (subtypeCount <= 1) {
+ return;
+ }
+ final String imiId = imi.getId();
+ final PreferenceCategory keyboardSettingsCategory = new PreferenceCategory(context);
+ root.addPreference(keyboardSettingsCategory);
+ final PackageManager pm = getPackageManager();
+ final CharSequence label = imi.loadLabel(pm);
+
+ keyboardSettingsCategory.setTitle(label);
+ keyboardSettingsCategory.setKey(imiId);
+ // TODO: Use toggle Preference if images are ready.
+ final TwoStatePreference autoSelectionPref = new SwitchWithNoTextPreference(context);
+ mAutoSelectionPrefsMap.put(imiId, autoSelectionPref);
+ keyboardSettingsCategory.addPreference(autoSelectionPref);
+ autoSelectionPref.setOnPreferenceChangeListener(this);
+
+ final PreferenceCategory activeInputMethodsCategory = new PreferenceCategory(context);
+ activeInputMethodsCategory.setTitle(R.string.active_input_method_subtypes);
+ root.addPreference(activeInputMethodsCategory);
+
+ CharSequence autoSubtypeLabel = null;
+ final ArrayList<Preference> subtypePreferences = new ArrayList<>();
+ for (int index = 0; index < subtypeCount; ++index) {
+ final InputMethodSubtype subtype = imi.getSubtypeAt(index);
+ if (subtype.overridesImplicitlyEnabledSubtype()) {
+ if (autoSubtypeLabel == null) {
+ autoSubtypeLabel = subtype.getDisplayName(
+ context, imi.getPackageName(), imi.getServiceInfo().applicationInfo);
}
- mInputMethodAndSubtypePrefsMap.put(imiId, subtypePreferences);
+ } else {
+ final Preference subtypePref = new InputMethodSubtypePreference(
+ context, subtype, imi);
+ subtypePreferences.add(subtypePref);
}
- if (isAutoSubtype) {
- if (TextUtils.isEmpty(autoSubtypeLabel)) {
- Log.w(TAG, "Title for auto subtype is empty.");
- autoCB.setTitle("---");
- } else {
- autoCB.setTitle(autoSubtypeLabel);
+ }
+ Collections.sort(subtypePreferences, new Comparator<Preference>() {
+ @Override
+ public int compare(final Preference lhs, final Preference rhs) {
+ if (lhs instanceof InputMethodSubtypePreference) {
+ return ((InputMethodSubtypePreference) lhs).compareTo(rhs, mCollator);
}
- } else {
- autoCB.setTitle(R.string.use_system_language_to_select_input_method_subtypes);
+ return lhs.compareTo(rhs);
}
+ });
+ final int prefCount = subtypePreferences.size();
+ for (int index = 0; index < prefCount; ++index) {
+ final Preference pref = subtypePreferences.get(index);
+ activeInputMethodsCategory.addPreference(pref);
+ pref.setOnPreferenceChangeListener(this);
+ InputMethodAndSubtypeUtil.removeUnnecessaryNonPersistentPreference(pref);
+ }
+ mInputMethodAndSubtypePrefsMap.put(imiId, subtypePreferences);
+ if (TextUtils.isEmpty(autoSubtypeLabel)) {
+ autoSelectionPref.setTitle(
+ R.string.use_system_language_to_select_input_method_subtypes);
+ } else {
+ autoSelectionPref.setTitle(autoSubtypeLabel);
}
- return root;
}
- private boolean isNoSubtypesExplicitlySelected(String imiId) {
- boolean allSubtypesOff = true;
+ private boolean isNoSubtypesExplicitlySelected(final String imiId) {
final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId);
- for (Preference subtypePref: subtypePrefs) {
- if (subtypePref instanceof CheckBoxPreference
- && ((CheckBoxPreference)subtypePref).isChecked()) {
- allSubtypesOff = false;
- break;
+ for (final Preference pref : subtypePrefs) {
+ if (pref instanceof TwoStatePreference && ((TwoStatePreference)pref).isChecked()) {
+ return false;
}
}
- return allSubtypesOff;
+ return true;
}
- private void setSubtypeAutoSelectionEnabled(String imiId, boolean autoSelectionEnabled) {
- CheckBoxPreference autoSelectionCB = mSubtypeAutoSelectionCBMap.get(imiId);
- if (autoSelectionCB == null) return;
- autoSelectionCB.setChecked(autoSelectionEnabled);
+ private void setAutoSelectionSubtypesEnabled(final String imiId,
+ final boolean autoSelectionEnabled) {
+ final TwoStatePreference autoSelectionPref = mAutoSelectionPrefsMap.get(imiId);
+ if (autoSelectionPref == null) {
+ return;
+ }
+ autoSelectionPref.setChecked(autoSelectionEnabled);
final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId);
- for (Preference subtypePref: subtypePrefs) {
- if (subtypePref instanceof CheckBoxPreference) {
+ for (final Preference pref : subtypePrefs) {
+ if (pref instanceof TwoStatePreference) {
// When autoSelectionEnabled is true, all subtype prefs need to be disabled with
// implicitly checked subtypes. In case of false, all subtype prefs need to be
// enabled.
- subtypePref.setEnabled(!autoSelectionEnabled);
+ pref.setEnabled(!autoSelectionEnabled);
if (autoSelectionEnabled) {
- ((CheckBoxPreference)subtypePref).setChecked(false);
+ ((TwoStatePreference)pref).setChecked(false);
}
}
}
if (autoSelectionEnabled) {
- InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(this, getContentResolver(),
- mInputMethodProperties, mHaveHardKeyboard);
- setCheckedImplicitlyEnabledSubtypes(imiId);
+ InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(
+ this, getContentResolver(), mInputMethodInfoList, mHaveHardKeyboard);
+ updateImplicitlyEnabledSubtypes(imiId, true /* check */);
}
}
- private void setCheckedImplicitlyEnabledSubtypes(String targetImiId) {
- updateImplicitlyEnabledSubtypes(targetImiId, true);
- }
-
- private void clearImplicitlyEnabledSubtypes(String targetImiId) {
- updateImplicitlyEnabledSubtypes(targetImiId, false);
- }
-
- private void updateImplicitlyEnabledSubtypes(String targetImiId, boolean check) {
+ private void updateImplicitlyEnabledSubtypes(final String targetImiId, final boolean check) {
// When targetImiId is null, apply to all subtypes of all IMEs
- for (InputMethodInfo imi: mInputMethodProperties) {
- String imiId = imi.getId();
- if (targetImiId != null && !targetImiId.equals(imiId)) continue;
- final CheckBoxPreference autoCB = mSubtypeAutoSelectionCBMap.get(imiId);
+ for (final InputMethodInfo imi : mInputMethodInfoList) {
+ final String imiId = imi.getId();
+ final TwoStatePreference autoSelectionPref = mAutoSelectionPrefsMap.get(imiId);
// No need to update implicitly enabled subtypes when the user has unchecked the
// "subtype auto selection".
- if (autoCB == null || !autoCB.isChecked()) continue;
- final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId);
- final List<InputMethodSubtype> implicitlyEnabledSubtypes =
- mImm.getEnabledInputMethodSubtypeList(imi, true);
- if (subtypePrefs == null || implicitlyEnabledSubtypes == null) continue;
- for (Preference subtypePref: subtypePrefs) {
- if (subtypePref instanceof CheckBoxPreference) {
- CheckBoxPreference cb = (CheckBoxPreference)subtypePref;
- cb.setChecked(false);
- if (check) {
- for (InputMethodSubtype subtype: implicitlyEnabledSubtypes) {
- String implicitlyEnabledSubtypePrefKey = imiId + subtype.hashCode();
- if (cb.getKey().equals(implicitlyEnabledSubtypePrefKey)) {
- cb.setChecked(true);
- break;
- }
- }
- }
- }
+ if (autoSelectionPref == null || !autoSelectionPref.isChecked()) {
+ continue;
+ }
+ if (imiId.equals(targetImiId) || targetImiId == null) {
+ updateImplicitlyEnabledSubtypesOf(imi, check);
}
}
}
- private void updateAutoSelectionCB() {
- for (String imiId: mInputMethodAndSubtypePrefsMap.keySet()) {
- setSubtypeAutoSelectionEnabled(imiId, isNoSubtypesExplicitlySelected(imiId));
+ private void updateImplicitlyEnabledSubtypesOf(final InputMethodInfo imi, final boolean check) {
+ final String imiId = imi.getId();
+ final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId);
+ final List<InputMethodSubtype> implicitlyEnabledSubtypes =
+ mImm.getEnabledInputMethodSubtypeList(imi, true);
+ if (subtypePrefs == null || implicitlyEnabledSubtypes == null) {
+ return;
}
- setCheckedImplicitlyEnabledSubtypes(null);
- }
-
- private static class SubtypeCheckBoxPreference extends CheckBoxPreference {
- private final boolean mIsSystemLocale;
- private final boolean mIsSystemLanguage;
- private final Collator mCollator;
-
- public SubtypeCheckBoxPreference(
- Context context, String subtypeLocale, String systemLocale, Collator collator) {
- super(context);
- if (TextUtils.isEmpty(subtypeLocale)) {
- mIsSystemLocale = false;
- mIsSystemLanguage = false;
- } else {
- mIsSystemLocale = subtypeLocale.equals(systemLocale);
- mIsSystemLanguage = mIsSystemLocale
- || subtypeLocale.startsWith(systemLocale.substring(0, 2));
+ for (final Preference pref : subtypePrefs) {
+ if (!(pref instanceof TwoStatePreference)) {
+ continue;
}
- mCollator = collator;
- }
-
- @Override
- public int compareTo(Preference p) {
- if (p instanceof SubtypeCheckBoxPreference) {
- final SubtypeCheckBoxPreference pref = ((SubtypeCheckBoxPreference)p);
- final CharSequence t0 = getTitle();
- final CharSequence t1 = pref.getTitle();
- if (TextUtils.equals(t0, t1)) {
- return 0;
- }
- if (mIsSystemLocale) {
- return -1;
- }
- if (pref.mIsSystemLocale) {
- return 1;
- }
- if (mIsSystemLanguage) {
- return -1;
- }
- if (pref.mIsSystemLanguage) {
- return 1;
- }
- if (TextUtils.isEmpty(t0)) {
- return 1;
- }
- if (TextUtils.isEmpty(t1)) {
- return -1;
+ final TwoStatePreference subtypePref = (TwoStatePreference)pref;
+ subtypePref.setChecked(false);
+ if (check) {
+ for (final InputMethodSubtype subtype : implicitlyEnabledSubtypes) {
+ final String implicitlyEnabledSubtypePrefKey = imiId + subtype.hashCode();
+ if (subtypePref.getKey().equals(implicitlyEnabledSubtypePrefKey)) {
+ subtypePref.setChecked(true);
+ break;
+ }
}
- return mCollator.compare(t0.toString(), t1.toString());
- } else {
- Log.w(TAG, "Illegal preference type.");
- return super.compareTo(p);
}
}
}
+
+ private void updateAutoSelectionPreferences() {
+ for (final String imiId : mInputMethodAndSubtypePrefsMap.keySet()) {
+ setAutoSelectionSubtypesEnabled(imiId, isNoSubtypesExplicitlySelected(imiId));
+ }
+ updateImplicitlyEnabledSubtypes(null /* targetImiId */, true /* check */);
+ }
}
diff --git a/src/com/android/settings/inputmethod/InputMethodAndSubtypeEnablerActivity.java b/src/com/android/settings/inputmethod/InputMethodAndSubtypeEnablerActivity.java
index 5693e20..bafae2b 100644
--- a/src/com/android/settings/inputmethod/InputMethodAndSubtypeEnablerActivity.java
+++ b/src/com/android/settings/inputmethod/InputMethodAndSubtypeEnablerActivity.java
@@ -15,27 +15,42 @@
*/
package com.android.settings.inputmethod;
-import android.app.Fragment;
+import android.app.ActionBar;
import android.content.Intent;
-import android.preference.PreferenceActivity;
+import android.os.Bundle;
-import com.android.settings.ChooseLockPassword.ChooseLockPasswordFragment;
+import com.android.settings.SettingsActivity;
+
+public class InputMethodAndSubtypeEnablerActivity extends SettingsActivity {
+ private static final String FRAGMENT_NAME = InputMethodAndSubtypeEnabler.class.getName();
+
+ @Override
+ protected void onCreate(final Bundle savedState) {
+ super.onCreate(savedState);
+ final ActionBar actionBar = getActionBar();
+ if (actionBar != null) {
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.setHomeButtonEnabled(true);
+ }
+ }
+
+ @Override
+ public boolean onNavigateUp() {
+ finish();
+ return true;
+ }
-public class InputMethodAndSubtypeEnablerActivity extends PreferenceActivity {
@Override
public Intent getIntent() {
final Intent modIntent = new Intent(super.getIntent());
if (!modIntent.hasExtra(EXTRA_SHOW_FRAGMENT)) {
- modIntent.putExtra(EXTRA_SHOW_FRAGMENT, InputMethodAndSubtypeEnabler.class.getName());
- modIntent.putExtra(EXTRA_NO_HEADERS, true);
+ modIntent.putExtra(EXTRA_SHOW_FRAGMENT, FRAGMENT_NAME);
}
return modIntent;
}
@Override
protected boolean isValidFragment(String fragmentName) {
- if (InputMethodAndSubtypeEnabler.class.getName().equals(fragmentName)) return true;
- return false;
+ return FRAGMENT_NAME.equals(fragmentName);
}
-
}
diff --git a/src/com/android/settings/inputmethod/InputMethodAndSubtypeUtil.java b/src/com/android/settings/inputmethod/InputMethodAndSubtypeUtil.java
index 561302a..b184066 100644
--- a/src/com/android/settings/inputmethod/InputMethodAndSubtypeUtil.java
+++ b/src/com/android/settings/inputmethod/InputMethodAndSubtypeUtil.java
@@ -16,13 +16,11 @@
package com.android.settings.inputmethod;
-import com.android.internal.inputmethod.InputMethodUtils;
-import com.android.settings.SettingsPreferenceFragment;
-
import android.content.ContentResolver;
-import android.preference.CheckBoxPreference;
+import android.content.SharedPreferences;
import android.preference.Preference;
import android.preference.PreferenceScreen;
+import android.preference.TwoStatePreference;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.text.TextUtils;
@@ -30,12 +28,16 @@ import android.util.Log;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
+import com.android.internal.inputmethod.InputMethodUtils;
+import com.android.settings.SettingsPreferenceFragment;
+
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
-public class InputMethodAndSubtypeUtil {
+// TODO: Consolidate this with {@link InputMethodSettingValuesWrapper}.
+class InputMethodAndSubtypeUtil {
private static final boolean DEBUG = false;
static final String TAG = "InputMethdAndSubtypeUtil";
@@ -50,40 +52,33 @@ public class InputMethodAndSubtypeUtil {
private static final TextUtils.SimpleStringSplitter sStringInputMethodSubtypeSplitter
= new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
- private static void buildEnabledInputMethodsString(
- StringBuilder builder, String imi, HashSet<String> subtypes) {
- builder.append(imi);
- // Inputmethod and subtypes are saved in the settings as follows:
- // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
- for (String subtypeId: subtypes) {
- builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId);
- }
- }
-
- public static void buildInputMethodsAndSubtypesString(
- StringBuilder builder, HashMap<String, HashSet<String>> imsList) {
- boolean needsAppendSeparator = false;
- for (String imi: imsList.keySet()) {
- if (needsAppendSeparator) {
+ // InputMethods and subtypes are saved in the settings as follows:
+ // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
+ static String buildInputMethodsAndSubtypesString(
+ final HashMap<String, HashSet<String>> imeToSubtypesMap) {
+ final StringBuilder builder = new StringBuilder();
+ for (final String imi : imeToSubtypesMap.keySet()) {
+ if (builder.length() > 0) {
builder.append(INPUT_METHOD_SEPARATER);
- } else {
- needsAppendSeparator = true;
}
- buildEnabledInputMethodsString(builder, imi, imsList.get(imi));
+ final HashSet<String> subtypeIdSet = imeToSubtypesMap.get(imi);
+ builder.append(imi);
+ for (final String subtypeId : subtypeIdSet) {
+ builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId);
+ }
}
+ return builder.toString();
}
- public static void buildDisabledSystemInputMethods(
- StringBuilder builder, HashSet<String> imes) {
- boolean needsAppendSeparator = false;
- for (String ime: imes) {
- if (needsAppendSeparator) {
+ private static String buildInputMethodsString(final HashSet<String> imiList) {
+ final StringBuilder builder = new StringBuilder();
+ for (final String imi : imiList) {
+ if (builder.length() > 0) {
builder.append(INPUT_METHOD_SEPARATER);
- } else {
- needsAppendSeparator = true;
}
- builder.append(ime);
+ builder.append(imi);
}
+ return builder.toString();
}
private static int getInputMethodSubtypeSelected(ContentResolver resolver) {
@@ -108,34 +103,48 @@ public class InputMethodAndSubtypeUtil {
ContentResolver resolver) {
final String enabledInputMethodsStr = Settings.Secure.getString(
resolver, Settings.Secure.ENABLED_INPUT_METHODS);
- HashMap<String, HashSet<String>> imsList
- = new HashMap<String, HashSet<String>>();
if (DEBUG) {
Log.d(TAG, "--- Load enabled input methods: " + enabledInputMethodsStr);
}
+ return parseInputMethodsAndSubtypesString(enabledInputMethodsStr);
+ }
- if (TextUtils.isEmpty(enabledInputMethodsStr)) {
- return imsList;
+ static HashMap<String, HashSet<String>> parseInputMethodsAndSubtypesString(
+ final String inputMethodsAndSubtypesString) {
+ final HashMap<String, HashSet<String>> subtypesMap = new HashMap<>();
+ if (TextUtils.isEmpty(inputMethodsAndSubtypesString)) {
+ return subtypesMap;
}
- sStringInputMethodSplitter.setString(enabledInputMethodsStr);
+ sStringInputMethodSplitter.setString(inputMethodsAndSubtypesString);
while (sStringInputMethodSplitter.hasNext()) {
- String nextImsStr = sStringInputMethodSplitter.next();
+ final String nextImsStr = sStringInputMethodSplitter.next();
sStringInputMethodSubtypeSplitter.setString(nextImsStr);
if (sStringInputMethodSubtypeSplitter.hasNext()) {
- HashSet<String> subtypeHashes = new HashSet<String>();
- // The first element is ime id.
- String imeId = sStringInputMethodSubtypeSplitter.next();
+ final HashSet<String> subtypeIdSet = new HashSet<>();
+ // The first element is {@link InputMethodInfoId}.
+ final String imiId = sStringInputMethodSubtypeSplitter.next();
while (sStringInputMethodSubtypeSplitter.hasNext()) {
- subtypeHashes.add(sStringInputMethodSubtypeSplitter.next());
+ subtypeIdSet.add(sStringInputMethodSubtypeSplitter.next());
}
- imsList.put(imeId, subtypeHashes);
+ subtypesMap.put(imiId, subtypeIdSet);
}
}
- return imsList;
+ return subtypesMap;
+ }
+
+ static void enableInputMethodSubtypesOf(final ContentResolver resolver, final String imiId,
+ final HashSet<String> enabledSubtypeIdSet) {
+ final HashMap<String, HashSet<String>> enabledImeAndSubtypeIdsMap =
+ getEnabledInputMethodsAndSubtypeList(resolver);
+ enabledImeAndSubtypeIdsMap.put(imiId, enabledSubtypeIdSet);
+ final String enabledImesAndSubtypesString = buildInputMethodsAndSubtypesString(
+ enabledImeAndSubtypeIdsMap);
+ Settings.Secure.putString(resolver,
+ Settings.Secure.ENABLED_INPUT_METHODS, enabledImesAndSubtypesString);
}
private static HashSet<String> getDisabledSystemIMEs(ContentResolver resolver) {
- HashSet<String> set = new HashSet<String>();
+ HashSet<String> set = new HashSet<>();
String disabledIMEsStr = Settings.Secure.getString(
resolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS);
if (TextUtils.isEmpty(disabledIMEsStr)) {
@@ -148,49 +157,52 @@ public class InputMethodAndSubtypeUtil {
return set;
}
- public static void saveInputMethodSubtypeList(SettingsPreferenceFragment context,
+ static void saveInputMethodSubtypeList(SettingsPreferenceFragment context,
ContentResolver resolver, List<InputMethodInfo> inputMethodInfos,
boolean hasHardKeyboard) {
String currentInputMethodId = Settings.Secure.getString(resolver,
Settings.Secure.DEFAULT_INPUT_METHOD);
final int selectedInputMethodSubtype = getInputMethodSubtypeSelected(resolver);
- HashMap<String, HashSet<String>> enabledIMEAndSubtypesMap =
+ final HashMap<String, HashSet<String>> enabledIMEsAndSubtypesMap =
getEnabledInputMethodsAndSubtypeList(resolver);
- HashSet<String> disabledSystemIMEs = getDisabledSystemIMEs(resolver);
+ final HashSet<String> disabledSystemIMEs = getDisabledSystemIMEs(resolver);
- final int imiCount = inputMethodInfos.size();
boolean needsToResetSelectedSubtype = false;
- for (InputMethodInfo imi : inputMethodInfos) {
+ for (final InputMethodInfo imi : inputMethodInfos) {
final String imiId = imi.getId();
- Preference pref = context.findPreference(imiId);
- if (pref == null) continue;
- // In the Configure input method screen or in the subtype enabler screen.
- // pref is instance of CheckBoxPreference in the Configure input method screen.
- final boolean isImeChecked = (pref instanceof CheckBoxPreference) ?
- ((CheckBoxPreference) pref).isChecked()
- : enabledIMEAndSubtypesMap.containsKey(imiId);
+ final Preference pref = context.findPreference(imiId);
+ if (pref == null) {
+ continue;
+ }
+ // In the choose input method screen or in the subtype enabler screen,
+ // <code>pref</code> is an instance of TwoStatePreference.
+ final boolean isImeChecked = (pref instanceof TwoStatePreference) ?
+ ((TwoStatePreference) pref).isChecked()
+ : enabledIMEsAndSubtypesMap.containsKey(imiId);
final boolean isCurrentInputMethod = imiId.equals(currentInputMethodId);
final boolean systemIme = InputMethodUtils.isSystemIme(imi);
if ((!hasHardKeyboard && InputMethodSettingValuesWrapper.getInstance(
context.getActivity()).isAlwaysCheckedIme(imi, context.getActivity()))
|| isImeChecked) {
- if (!enabledIMEAndSubtypesMap.containsKey(imiId)) {
+ if (!enabledIMEsAndSubtypesMap.containsKey(imiId)) {
// imiId has just been enabled
- enabledIMEAndSubtypesMap.put(imiId, new HashSet<String>());
+ enabledIMEsAndSubtypesMap.put(imiId, new HashSet<String>());
}
- HashSet<String> subtypesSet = enabledIMEAndSubtypesMap.get(imiId);
+ final HashSet<String> subtypesSet = enabledIMEsAndSubtypesMap.get(imiId);
boolean subtypePrefFound = false;
final int subtypeCount = imi.getSubtypeCount();
for (int i = 0; i < subtypeCount; ++i) {
- InputMethodSubtype subtype = imi.getSubtypeAt(i);
+ final InputMethodSubtype subtype = imi.getSubtypeAt(i);
final String subtypeHashCodeStr = String.valueOf(subtype.hashCode());
- CheckBoxPreference subtypePref = (CheckBoxPreference) context.findPreference(
- imiId + subtypeHashCodeStr);
+ final TwoStatePreference subtypePref = (TwoStatePreference) context
+ .findPreference(imiId + subtypeHashCodeStr);
// In the Configure input method screen which does not have subtype preferences.
- if (subtypePref == null) continue;
+ if (subtypePref == null) {
+ continue;
+ }
if (!subtypePrefFound) {
- // Once subtype checkbox is found, subtypeSet needs to be cleared.
+ // Once subtype preference is found, subtypeSet needs to be cleared.
// Because of system change, hashCode value could have been changed.
subtypesSet.clear();
// If selected subtype preference is disabled, needs to reset.
@@ -211,7 +223,7 @@ public class InputMethodAndSubtypeUtil {
}
}
} else {
- enabledIMEAndSubtypesMap.remove(imiId);
+ enabledIMEsAndSubtypesMap.remove(imiId);
if (isCurrentInputMethod) {
// We are processing the current input method, but found that it's not enabled.
// This means that the current input method has been uninstalled.
@@ -238,14 +250,13 @@ public class InputMethodAndSubtypeUtil {
}
}
- StringBuilder builder = new StringBuilder();
- buildInputMethodsAndSubtypesString(builder, enabledIMEAndSubtypesMap);
- StringBuilder disabledSysImesBuilder = new StringBuilder();
- buildDisabledSystemInputMethods(disabledSysImesBuilder, disabledSystemIMEs);
+ final String enabledIMEsAndSubtypesString = buildInputMethodsAndSubtypesString(
+ enabledIMEsAndSubtypesMap);
+ final String disabledSystemIMEsString = buildInputMethodsString(disabledSystemIMEs);
if (DEBUG) {
- Log.d(TAG, "--- Save enabled inputmethod settings. :" + builder.toString());
- Log.d(TAG, "--- Save disable system inputmethod settings. :"
- + disabledSysImesBuilder.toString());
+ Log.d(TAG, "--- Save enabled inputmethod settings. :" + enabledIMEsAndSubtypesString);
+ Log.d(TAG, "--- Save disabled system inputmethod settings. :"
+ + disabledSystemIMEsString);
Log.d(TAG, "--- Save default inputmethod settings. :" + currentInputMethodId);
Log.d(TAG, "--- Needs to reset the selected subtype :" + needsToResetSelectedSubtype);
Log.d(TAG, "--- Subtype is selected :" + isInputMethodSubtypeSelected(resolver));
@@ -262,10 +273,10 @@ public class InputMethodAndSubtypeUtil {
}
Settings.Secure.putString(resolver,
- Settings.Secure.ENABLED_INPUT_METHODS, builder.toString());
- if (disabledSysImesBuilder.length() > 0) {
+ Settings.Secure.ENABLED_INPUT_METHODS, enabledIMEsAndSubtypesString);
+ if (disabledSystemIMEsString.length() > 0) {
Settings.Secure.putString(resolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS,
- disabledSysImesBuilder.toString());
+ disabledSystemIMEsString);
}
// If the current input method is unset, InputMethodManagerService will find the applicable
// IME from the history and the system locale.
@@ -273,22 +284,21 @@ public class InputMethodAndSubtypeUtil {
currentInputMethodId != null ? currentInputMethodId : "");
}
- public static void loadInputMethodSubtypeList(
- SettingsPreferenceFragment context, ContentResolver resolver,
- List<InputMethodInfo> inputMethodInfos,
+ static void loadInputMethodSubtypeList(final SettingsPreferenceFragment context,
+ final ContentResolver resolver, final List<InputMethodInfo> inputMethodInfos,
final Map<String, List<Preference>> inputMethodPrefsMap) {
- HashMap<String, HashSet<String>> enabledSubtypes =
+ final HashMap<String, HashSet<String>> enabledSubtypes =
getEnabledInputMethodsAndSubtypeList(resolver);
- for (InputMethodInfo imi : inputMethodInfos) {
+ for (final InputMethodInfo imi : inputMethodInfos) {
final String imiId = imi.getId();
- Preference pref = context.findPreference(imiId);
- if (pref != null && pref instanceof CheckBoxPreference) {
- CheckBoxPreference checkBoxPreference = (CheckBoxPreference) pref;
- boolean isEnabled = enabledSubtypes.containsKey(imiId);
- checkBoxPreference.setChecked(isEnabled);
+ final Preference pref = context.findPreference(imiId);
+ if (pref instanceof TwoStatePreference) {
+ final TwoStatePreference subtypePref = (TwoStatePreference) pref;
+ final boolean isEnabled = enabledSubtypes.containsKey(imiId);
+ subtypePref.setChecked(isEnabled);
if (inputMethodPrefsMap != null) {
- for (Preference childPref: inputMethodPrefsMap.get(imiId)) {
+ for (final Preference childPref: inputMethodPrefsMap.get(imiId)) {
childPref.setEnabled(isEnabled);
}
}
@@ -298,16 +308,17 @@ public class InputMethodAndSubtypeUtil {
updateSubtypesPreferenceChecked(context, inputMethodInfos, enabledSubtypes);
}
- public static void setSubtypesPreferenceEnabled(SettingsPreferenceFragment context,
- List<InputMethodInfo> inputMethodProperties, String id, boolean enabled) {
- PreferenceScreen preferenceScreen = context.getPreferenceScreen();
- for (InputMethodInfo imi : inputMethodProperties) {
+ static void setSubtypesPreferenceEnabled(final SettingsPreferenceFragment context,
+ final List<InputMethodInfo> inputMethodProperties, final String id,
+ final boolean enabled) {
+ final PreferenceScreen preferenceScreen = context.getPreferenceScreen();
+ for (final InputMethodInfo imi : inputMethodProperties) {
if (id.equals(imi.getId())) {
final int subtypeCount = imi.getSubtypeCount();
for (int i = 0; i < subtypeCount; ++i) {
- InputMethodSubtype subtype = imi.getSubtypeAt(i);
- CheckBoxPreference pref = (CheckBoxPreference) preferenceScreen.findPreference(
- id + subtype.hashCode());
+ final InputMethodSubtype subtype = imi.getSubtypeAt(i);
+ final TwoStatePreference pref = (TwoStatePreference) preferenceScreen
+ .findPreference(id + subtype.hashCode());
if (pref != null) {
pref.setEnabled(enabled);
}
@@ -316,28 +327,42 @@ public class InputMethodAndSubtypeUtil {
}
}
- public static void updateSubtypesPreferenceChecked(SettingsPreferenceFragment context,
- List<InputMethodInfo> inputMethodProperties,
- HashMap<String, HashSet<String>> enabledSubtypes) {
- PreferenceScreen preferenceScreen = context.getPreferenceScreen();
- for (InputMethodInfo imi : inputMethodProperties) {
- String id = imi.getId();
- if (!enabledSubtypes.containsKey(id)) break;
+ private static void updateSubtypesPreferenceChecked(final SettingsPreferenceFragment context,
+ final List<InputMethodInfo> inputMethodProperties,
+ final HashMap<String, HashSet<String>> enabledSubtypes) {
+ final PreferenceScreen preferenceScreen = context.getPreferenceScreen();
+ for (final InputMethodInfo imi : inputMethodProperties) {
+ final String id = imi.getId();
+ if (!enabledSubtypes.containsKey(id)) {
+ // There is no need to enable/disable subtypes of disabled IMEs.
+ continue;
+ }
final HashSet<String> enabledSubtypesSet = enabledSubtypes.get(id);
final int subtypeCount = imi.getSubtypeCount();
for (int i = 0; i < subtypeCount; ++i) {
- InputMethodSubtype subtype = imi.getSubtypeAt(i);
- String hashCode = String.valueOf(subtype.hashCode());
+ final InputMethodSubtype subtype = imi.getSubtypeAt(i);
+ final String hashCode = String.valueOf(subtype.hashCode());
if (DEBUG) {
Log.d(TAG, "--- Set checked state: " + "id" + ", " + hashCode + ", "
+ enabledSubtypesSet.contains(hashCode));
}
- CheckBoxPreference pref = (CheckBoxPreference) preferenceScreen.findPreference(
- id + hashCode);
+ final TwoStatePreference pref = (TwoStatePreference) preferenceScreen
+ .findPreference(id + hashCode);
if (pref != null) {
pref.setChecked(enabledSubtypesSet.contains(hashCode));
}
}
}
}
+
+ static void removeUnnecessaryNonPersistentPreference(final Preference pref) {
+ final String key = pref.getKey();
+ if (pref.isPersistent() || key == null) {
+ return;
+ }
+ final SharedPreferences prefs = pref.getSharedPreferences();
+ if (prefs != null && prefs.contains(key)) {
+ prefs.edit().remove(key).apply();
+ }
+ }
}
diff --git a/src/com/android/settings/inputmethod/InputMethodPreference.java b/src/com/android/settings/inputmethod/InputMethodPreference.java
index aa6430f..5cf5d6a 100644..100755
--- a/src/com/android/settings/inputmethod/InputMethodPreference.java
+++ b/src/com/android/settings/inputmethod/InputMethodPreference.java
@@ -16,306 +16,248 @@
package com.android.settings.inputmethod;
-import com.android.internal.inputmethod.InputMethodUtils;
-import com.android.settings.R;
-import com.android.settings.SettingsPreferenceFragment;
-import com.android.settings.Utils;
-
import android.app.AlertDialog;
-import android.app.Fragment;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
-import android.content.res.Configuration;
-import android.os.Bundle;
-import android.preference.CheckBoxPreference;
import android.preference.Preference;
-import android.preference.PreferenceActivity;
-import android.provider.Settings;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.Preference.OnPreferenceClickListener;
+import android.preference.SwitchPreference;
import android.text.TextUtils;
import android.util.Log;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnLongClickListener;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
-import android.widget.ImageView;
-import android.widget.TextView;
import android.widget.Toast;
+import com.android.internal.inputmethod.InputMethodUtils;
+import com.android.settings.R;
+
import java.text.Collator;
+import java.util.ArrayList;
import java.util.List;
-public class InputMethodPreference extends CheckBoxPreference {
+/**
+ * Input method preference.
+ *
+ * This preference represents an IME. It is used for two purposes. 1) An instance with a switch
+ * is used to enable or disable the IME. 2) An instance without a switch is used to invoke the
+ * setting activity of the IME.
+ */
+class InputMethodPreference extends SwitchPreference implements OnPreferenceClickListener,
+ OnPreferenceChangeListener {
private static final String TAG = InputMethodPreference.class.getSimpleName();
- private final SettingsPreferenceFragment mFragment;
+ private static final String EMPTY_TEXT = "";
+
+ interface OnSavePreferenceListener {
+ /**
+ * Called when this preference needs to be saved its state.
+ *
+ * Note that this preference is non-persistent and needs explicitly to be saved its state.
+ * Because changing one IME state may change other IMEs' state, this is a place to update
+ * other IMEs' state as well.
+ *
+ * @param pref This preference.
+ */
+ public void onSaveInputMethodPreference(InputMethodPreference pref);
+ }
+
private final InputMethodInfo mImi;
- private final InputMethodManager mImm;
- private final boolean mIsValidSystemNonAuxAsciiCapableIme;
- private final Intent mSettingsIntent;
- private final boolean mIsSystemIme;
- private final Collator mCollator;
+ private final boolean mHasPriorityInSorting;
+ private final OnSavePreferenceListener mOnSaveListener;
+ private final InputMethodSettingValuesWrapper mInputMethodSettingValues;
+ private final boolean mIsAllowedByOrganization;
private AlertDialog mDialog = null;
- private ImageView mInputMethodSettingsButton;
- private TextView mTitleText;
- private TextView mSummaryText;
- private View mInputMethodPref;
- private OnPreferenceChangeListener mOnImePreferenceChangeListener;
-
- private final OnClickListener mPrefOnclickListener = new OnClickListener() {
- @Override
- public void onClick(View arg0) {
- if (!isEnabled()) {
- return;
- }
- if (isChecked()) {
- setChecked(false, true /* save */);
- } else {
- if (mIsSystemIme) {
- setChecked(true, true /* save */);
- } else {
- showSecurityWarnDialog(mImi, InputMethodPreference.this);
- }
- }
- }
- };
- public InputMethodPreference(SettingsPreferenceFragment fragment, Intent settingsIntent,
- InputMethodManager imm, InputMethodInfo imi) {
- super(fragment.getActivity(), null, R.style.InputMethodPreferenceStyle);
- setLayoutResource(R.layout.preference_inputmethod);
- setWidgetLayoutResource(R.layout.preference_inputmethod_widget);
- mFragment = fragment;
- mSettingsIntent = settingsIntent;
- mImm = imm;
+ /**
+ * A preference entry of an input method.
+ *
+ * @param context The Context this is associated with.
+ * @param imi The {@link InputMethodInfo} of this preference.
+ * @param isImeEnabler true if this preference is the IME enabler that has enable/disable
+ * switches for all available IMEs, not the list of enabled IMEs.
+ * @param isAllowedByOrganization false if the IME has been disabled by a device or profile
+ owner.
+ * @param onSaveListener The listener called when this preference has been changed and needs
+ * to save the state to shared preference.
+ */
+ InputMethodPreference(final Context context, final InputMethodInfo imi,
+ final boolean isImeEnabler, final boolean isAllowedByOrganization,
+ final OnSavePreferenceListener onSaveListener) {
+ super(context);
+ setPersistent(false);
mImi = imi;
- mIsSystemIme = InputMethodUtils.isSystemIme(imi);
- mCollator = Collator.getInstance(fragment.getResources().getConfiguration().locale);
- final Context context = fragment.getActivity();
- mIsValidSystemNonAuxAsciiCapableIme = InputMethodSettingValuesWrapper
- .getInstance(context).isValidSystemNonAuxAsciiCapableIme(imi, context);
- updatePreferenceViews();
- }
-
- @Override
- protected void onBindView(View view) {
- super.onBindView(view);
- mInputMethodPref = view.findViewById(R.id.inputmethod_pref);
- mInputMethodPref.setOnClickListener(mPrefOnclickListener);
- mInputMethodSettingsButton = (ImageView)view.findViewById(R.id.inputmethod_settings);
- mTitleText = (TextView)view.findViewById(android.R.id.title);
- mSummaryText = (TextView)view.findViewById(android.R.id.summary);
- final boolean hasSubtypes = mImi.getSubtypeCount() > 1;
- final String imiId = mImi.getId();
- if (hasSubtypes) {
- mInputMethodPref.setOnLongClickListener(new OnLongClickListener() {
- @Override
- public boolean onLongClick(View arg0) {
- final Bundle bundle = new Bundle();
- bundle.putString(Settings.EXTRA_INPUT_METHOD_ID, imiId);
- startFragment(mFragment, InputMethodAndSubtypeEnabler.class.getName(),
- 0, bundle);
- return true;
- }
- });
- }
-
- if (mSettingsIntent != null) {
- mInputMethodSettingsButton.setOnClickListener(
- new OnClickListener() {
- @Override
- public void onClick(View arg0) {
- try {
- mFragment.startActivity(mSettingsIntent);
- } catch (ActivityNotFoundException e) {
- Log.d(TAG, "IME's Settings Activity Not Found: " + e);
- final String msg = mFragment.getString(
- R.string.failed_to_open_app_settings_toast,
- mImi.loadLabel(
- mFragment.getActivity().getPackageManager()));
- Toast.makeText(
- mFragment.getActivity(), msg, Toast.LENGTH_LONG).show();
- }
- }
- });
- }
- if (hasSubtypes) {
- final OnLongClickListener listener = new OnLongClickListener() {
- @Override
- public boolean onLongClick(View arg0) {
- final Bundle bundle = new Bundle();
- bundle.putString(Settings.EXTRA_INPUT_METHOD_ID, imiId);
- startFragment(mFragment, InputMethodAndSubtypeEnabler.class.getName(),
- 0, bundle);
- return true;
- }
- };
- mInputMethodSettingsButton.setOnLongClickListener(listener);
+ mIsAllowedByOrganization = isAllowedByOrganization;
+ mOnSaveListener = onSaveListener;
+ if (!isImeEnabler) {
+ // Hide switch widget.
+ setWidgetLayoutResource(0 /* widgetLayoutResId */);
}
- if (mSettingsIntent == null) {
- mInputMethodSettingsButton.setVisibility(View.GONE);
+ // Disable on/off switch texts.
+ setSwitchTextOn(EMPTY_TEXT);
+ setSwitchTextOff(EMPTY_TEXT);
+ setKey(imi.getId());
+ setTitle(imi.loadLabel(context.getPackageManager()));
+ final String settingsActivity = imi.getSettingsActivity();
+ if (TextUtils.isEmpty(settingsActivity)) {
+ setIntent(null);
+ } else {
+ // Set an intent to invoke settings activity of an input method.
+ final Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setClassName(imi.getPackageName(), settingsActivity);
+ setIntent(intent);
}
- updatePreferenceViews();
+ mInputMethodSettingValues = InputMethodSettingValuesWrapper.getInstance(context);
+ mHasPriorityInSorting = InputMethodUtils.isSystemIme(imi)
+ && mInputMethodSettingValues.isValidSystemNonAuxAsciiCapableIme(imi, context);
+ setOnPreferenceClickListener(this);
+ setOnPreferenceChangeListener(this);
}
- @Override
- public void setEnabled(boolean enabled) {
- super.setEnabled(enabled);
- updatePreferenceViews();
+ public InputMethodInfo getInputMethodInfo() {
+ return mImi;
}
- public void updatePreferenceViews() {
- final boolean isAlwaysChecked =
- InputMethodSettingValuesWrapper.getInstance(getContext()).isAlwaysCheckedIme(
- mImi, getContext());
- if (isAlwaysChecked) {
- super.setChecked(true);
- super.setEnabled(false);
- } else {
- super.setEnabled(true);
- }
- final boolean checked = isChecked();
- if (mInputMethodSettingsButton != null) {
- mInputMethodSettingsButton.setEnabled(checked);
- mInputMethodSettingsButton.setClickable(checked);
- mInputMethodSettingsButton.setFocusable(checked);
- if (!checked) {
- mInputMethodSettingsButton.setAlpha(Utils.DISABLED_ALPHA);
- }
- }
- if (mTitleText != null) {
- mTitleText.setEnabled(true);
+ private boolean isImeEnabler() {
+ // If this {@link SwitchPreference} doesn't have a widget layout, we explicitly hide the
+ // switch widget at constructor.
+ return getWidgetLayoutResource() != 0;
+ }
+
+ @Override
+ public boolean onPreferenceChange(final Preference preference, final Object newValue) {
+ // Always returns false to prevent default behavior.
+ // See {@link TwoStatePreference#onClick()}.
+ if (!isImeEnabler()) {
+ // Prevent disabling an IME because this preference is for invoking a settings activity.
+ return false;
}
- if (mSummaryText != null) {
- mSummaryText.setEnabled(checked);
+ if (isChecked()) {
+ // Disable this IME.
+ setChecked(false);
+ mOnSaveListener.onSaveInputMethodPreference(this);
+ return false;
}
- if (mInputMethodPref != null) {
- mInputMethodPref.setEnabled(true);
- mInputMethodPref.setLongClickable(checked);
- final boolean enabled = isEnabled();
- mInputMethodPref.setOnClickListener(enabled ? mPrefOnclickListener : null);
- if (!enabled) {
- mInputMethodPref.setBackgroundColor(0);
- }
+ if (InputMethodUtils.isSystemIme(mImi)) {
+ // Enable a system IME. No need to show a security warning dialog.
+ setChecked(true);
+ mOnSaveListener.onSaveInputMethodPreference(this);
+ return false;
}
- updateSummary();
+ // Enable a 3rd party IME.
+ showSecurityWarnDialog(mImi);
+ return false;
}
- public static boolean startFragment(
- Fragment fragment, String fragmentClass, int requestCode, Bundle extras) {
- if (fragment.getActivity() instanceof PreferenceActivity) {
- PreferenceActivity preferenceActivity = (PreferenceActivity)fragment.getActivity();
- preferenceActivity.startPreferencePanel(fragmentClass, extras, 0, null, fragment,
- requestCode);
+ @Override
+ public boolean onPreferenceClick(final Preference preference) {
+ // Always returns true to prevent invoking an intent without catching exceptions.
+ // See {@link Preference#performClick(PreferenceScreen)}/
+ if (isImeEnabler()) {
+ // Prevent invoking a settings activity because this preference is for enabling and
+ // disabling an input method.
return true;
- } else {
- Log.w(TAG, "Parent isn't PreferenceActivity, thus there's no way to launch the "
- + "given Fragment (name: " + fragmentClass + ", requestCode: " + requestCode
- + ")");
- return false;
}
- }
-
- private String getSummaryString() {
- final StringBuilder builder = new StringBuilder();
- final List<InputMethodSubtype> subtypes = mImm.getEnabledInputMethodSubtypeList(mImi, true);
- for (InputMethodSubtype subtype : subtypes) {
- if (builder.length() > 0) {
- builder.append(", ");
+ final Context context = getContext();
+ try {
+ final Intent intent = getIntent();
+ if (intent != null) {
+ // Invoke a settings activity of an input method.
+ context.startActivity(intent);
}
- final CharSequence subtypeLabel = subtype.getDisplayName(mFragment.getActivity(),
- mImi.getPackageName(), mImi.getServiceInfo().applicationInfo);
- builder.append(subtypeLabel);
+ } catch (final ActivityNotFoundException e) {
+ Log.d(TAG, "IME's Settings Activity Not Found", e);
+ final String message = context.getString(
+ R.string.failed_to_open_app_settings_toast,
+ mImi.loadLabel(context.getPackageManager()));
+ Toast.makeText(context, message, Toast.LENGTH_LONG).show();
}
- return builder.toString();
+ return true;
}
- private void updateSummary() {
- final String summary = getSummaryString();
- if (TextUtils.isEmpty(summary)) {
- return;
- }
- setSummary(summary);
+ void updatePreferenceViews() {
+ final boolean isAlwaysChecked = mInputMethodSettingValues.isAlwaysCheckedIme(
+ mImi, getContext());
+ // Only when this preference has a switch and an input method should be always enabled,
+ // this preference should be disabled to prevent accidentally disabling an input method.
+ setEnabled(!((isAlwaysChecked && isImeEnabler()) || (!mIsAllowedByOrganization)));
+ setChecked(mInputMethodSettingValues.isEnabledImi(mImi));
+ setSummary(getSummaryString());
}
- /**
- * Sets the checkbox state and optionally saves the settings.
- * @param checked whether to check the box
- * @param save whether to save IME settings
- */
- private void setChecked(boolean checked, boolean save) {
- final boolean wasChecked = isChecked();
- super.setChecked(checked);
- if (save) {
- saveImeSettings();
- if (wasChecked != checked && mOnImePreferenceChangeListener != null) {
- mOnImePreferenceChangeListener.onPreferenceChange(this, checked);
- }
- }
+ private InputMethodManager getInputMethodManager() {
+ return (InputMethodManager)getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
}
- public void setOnImePreferenceChangeListener(OnPreferenceChangeListener listener) {
- mOnImePreferenceChangeListener = listener;
+ private String getSummaryString() {
+ final Context context = getContext();
+ if (!mIsAllowedByOrganization) {
+ return context.getString(R.string.accessibility_feature_or_input_method_not_allowed);
+ }
+ final InputMethodManager imm = getInputMethodManager();
+ final List<InputMethodSubtype> subtypes = imm.getEnabledInputMethodSubtypeList(mImi, true);
+ final ArrayList<CharSequence> subtypeLabels = new ArrayList<>();
+ for (final InputMethodSubtype subtype : subtypes) {
+ final CharSequence label = subtype.getDisplayName(
+ context, mImi.getPackageName(), mImi.getServiceInfo().applicationInfo);
+ subtypeLabels.add(label);
+ }
+ // TODO: A delimiter of subtype labels should be localized.
+ return TextUtils.join(", ", subtypeLabels);
}
- private void showSecurityWarnDialog(InputMethodInfo imi, final InputMethodPreference chkPref) {
+ private void showSecurityWarnDialog(final InputMethodInfo imi) {
if (mDialog != null && mDialog.isShowing()) {
mDialog.dismiss();
}
- mDialog = (new AlertDialog.Builder(mFragment.getActivity()))
- .setTitle(android.R.string.dialog_alert_title)
- .setIconAttribute(android.R.attr.alertDialogIcon)
- .setCancelable(true)
- .setPositiveButton(android.R.string.ok,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- chkPref.setChecked(true, true);
- }
- })
- .setNegativeButton(android.R.string.cancel,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- }
- })
- .create();
- mDialog.setMessage(mFragment.getResources().getString(R.string.ime_security_warning,
- imi.getServiceInfo().applicationInfo.loadLabel(
- mFragment.getActivity().getPackageManager())));
+ final Context context = getContext();
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setCancelable(true /* cancelable */);
+ builder.setTitle(android.R.string.dialog_alert_title);
+ final CharSequence label = imi.getServiceInfo().applicationInfo.loadLabel(
+ context.getPackageManager());
+ builder.setMessage(context.getString(R.string.ime_security_warning, label));
+ builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ // The user confirmed to enable a 3rd party IME.
+ setChecked(true);
+ mOnSaveListener.onSaveInputMethodPreference(InputMethodPreference.this);
+ notifyChanged();
+ }
+ });
+ builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ // The user canceled to enable a 3rd party IME.
+ setChecked(false);
+ mOnSaveListener.onSaveInputMethodPreference(InputMethodPreference.this);
+ notifyChanged();
+ }
+ });
+ mDialog = builder.create();
mDialog.show();
}
- @Override
- public int compareTo(Preference p) {
- if (!(p instanceof InputMethodPreference)) {
- return super.compareTo(p);
+ int compareTo(final InputMethodPreference rhs, final Collator collator) {
+ if (this == rhs) {
+ return 0;
}
- final InputMethodPreference imp = (InputMethodPreference) p;
- final boolean priority0 = mIsSystemIme && mIsValidSystemNonAuxAsciiCapableIme;
- final boolean priority1 = imp.mIsSystemIme && imp.mIsValidSystemNonAuxAsciiCapableIme;
- if (priority0 == priority1) {
+ if (mHasPriorityInSorting == rhs.mHasPriorityInSorting) {
final CharSequence t0 = getTitle();
- final CharSequence t1 = imp.getTitle();
+ final CharSequence t1 = rhs.getTitle();
if (TextUtils.isEmpty(t0)) {
return 1;
}
if (TextUtils.isEmpty(t1)) {
return -1;
}
- return mCollator.compare(t0.toString(), t1.toString());
+ return collator.compare(t0.toString(), t1.toString());
}
// Prefer always checked system IMEs
- return priority0 ? -1 : 1;
- }
-
- private void saveImeSettings() {
- InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(
- mFragment, mFragment.getActivity().getContentResolver(), mImm.getInputMethodList(),
- mFragment.getResources().getConfiguration().keyboard
- == Configuration.KEYBOARD_QWERTY);
+ return mHasPriorityInSorting ? -1 : 1;
}
}
diff --git a/src/com/android/settings/inputmethod/InputMethodSettingValuesWrapper.java b/src/com/android/settings/inputmethod/InputMethodSettingValuesWrapper.java
index ccba143..ada476e 100644
--- a/src/com/android/settings/inputmethod/InputMethodSettingValuesWrapper.java
+++ b/src/com/android/settings/inputmethod/InputMethodSettingValuesWrapper.java
@@ -39,22 +39,20 @@ import java.util.Locale;
* manually on some events when "InputMethodInfo"s and "InputMethodSubtype"s can be
* changed.
*/
-public class InputMethodSettingValuesWrapper {
+// TODO: Consolidate this with {@link InputMethodAndSubtypeUtil}.
+class InputMethodSettingValuesWrapper {
private static final String TAG = InputMethodSettingValuesWrapper.class.getSimpleName();
- private static final Locale ENGLISH_LOCALE = new Locale("en");
private static volatile InputMethodSettingValuesWrapper sInstance;
- private final ArrayList<InputMethodInfo> mMethodList = new ArrayList<InputMethodInfo>();
- private final HashMap<String, InputMethodInfo> mMethodMap =
- new HashMap<String, InputMethodInfo>();
+ private final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>();
+ private final HashMap<String, InputMethodInfo> mMethodMap = new HashMap<>();
private final InputMethodSettings mSettings;
private final InputMethodManager mImm;
- private final HashSet<InputMethodInfo> mAsciiCapableEnabledImis =
- new HashSet<InputMethodInfo>();
+ private final HashSet<InputMethodInfo> mAsciiCapableEnabledImis = new HashSet<>();
- public static InputMethodSettingValuesWrapper getInstance(Context context) {
+ static InputMethodSettingValuesWrapper getInstance(Context context) {
if (sInstance == null) {
- synchronized(TAG) {
+ synchronized (TAG) {
if (sInstance == null) {
sInstance = new InputMethodSettingValuesWrapper(context);
}
@@ -74,14 +72,13 @@ public class InputMethodSettingValuesWrapper {
// Ensure singleton
private InputMethodSettingValuesWrapper(Context context) {
- mSettings =
- new InputMethodSettings(context.getResources(), context.getContentResolver(),
- mMethodMap, mMethodList, getDefaultCurrentUserId());
+ mSettings = new InputMethodSettings(context.getResources(), context.getContentResolver(),
+ mMethodMap, mMethodList, getDefaultCurrentUserId());
mImm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
refreshAllInputMethodAndSubtypes();
}
- public void refreshAllInputMethodAndSubtypes() {
+ void refreshAllInputMethodAndSubtypes() {
synchronized (mMethodMap) {
mMethodList.clear();
mMethodMap.clear();
@@ -113,13 +110,13 @@ public class InputMethodSettingValuesWrapper {
}
}
- public List<InputMethodInfo> getInputMethodList() {
+ List<InputMethodInfo> getInputMethodList() {
synchronized (mMethodMap) {
return mMethodList;
}
}
- public CharSequence getCurrentInputMethodName(Context context) {
+ CharSequence getCurrentInputMethodName(Context context) {
synchronized (mMethodMap) {
final InputMethodInfo imi = mMethodMap.get(mSettings.getSelectedInputMethod());
if (imi == null) {
@@ -131,7 +128,7 @@ public class InputMethodSettingValuesWrapper {
}
}
- public boolean isAlwaysCheckedIme(InputMethodInfo imi, Context context) {
+ boolean isAlwaysCheckedIme(InputMethodInfo imi, Context context) {
final boolean isEnabled = isEnabledImi(imi);
synchronized (mMethodMap) {
if (mSettings.getEnabledInputMethodListLocked().size() <= 1 && isEnabled) {
@@ -158,7 +155,7 @@ public class InputMethodSettingValuesWrapper {
private int getEnabledValidSystemNonAuxAsciiCapableImeCount(Context context) {
int count = 0;
final List<InputMethodInfo> enabledImis;
- synchronized(mMethodMap) {
+ synchronized (mMethodMap) {
enabledImis = mSettings.getEnabledInputMethodListLocked();
}
for (final InputMethodInfo imi : enabledImis) {
@@ -172,9 +169,9 @@ public class InputMethodSettingValuesWrapper {
return count;
}
- private boolean isEnabledImi(InputMethodInfo imi) {
+ boolean isEnabledImi(InputMethodInfo imi) {
final List<InputMethodInfo> enabledImis;
- synchronized(mMethodMap) {
+ synchronized (mMethodMap) {
enabledImis = mSettings.getEnabledInputMethodListLocked();
}
for (final InputMethodInfo tempImi : enabledImis) {
@@ -185,8 +182,7 @@ public class InputMethodSettingValuesWrapper {
return false;
}
- public boolean isValidSystemNonAuxAsciiCapableIme(InputMethodInfo imi,
- Context context) {
+ boolean isValidSystemNonAuxAsciiCapableIme(InputMethodInfo imi, Context context) {
if (imi.isAuxiliaryIme()) {
return false;
}
@@ -196,7 +192,7 @@ public class InputMethodSettingValuesWrapper {
if (mAsciiCapableEnabledImis.isEmpty()) {
Log.w(TAG, "ascii capable subtype enabled imi not found. Fall back to English"
+ " Keyboard subtype.");
- return InputMethodUtils.containsSubtypeOf(imi, ENGLISH_LOCALE.getLanguage(),
+ return InputMethodUtils.containsSubtypeOf(imi, Locale.ENGLISH.getLanguage(),
InputMethodUtils.SUBTYPE_MODE_KEYBOARD);
}
return mAsciiCapableEnabledImis.contains(imi);
diff --git a/src/com/android/settings/inputmethod/InputMethodSubtypePreference.java b/src/com/android/settings/inputmethod/InputMethodSubtypePreference.java
new file mode 100644
index 0000000..6ded6ad
--- /dev/null
+++ b/src/com/android/settings/inputmethod/InputMethodSubtypePreference.java
@@ -0,0 +1,93 @@
+/*
+ * 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.inputmethod;
+
+import android.content.Context;
+import android.preference.Preference;
+import android.text.TextUtils;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.internal.inputmethod.InputMethodUtils;
+
+import java.text.Collator;
+import java.util.Locale;
+
+/**
+ * Input method subtype preference.
+ *
+ * This preference represents a subtype of an IME. It is used to enable or disable the subtype.
+ */
+class InputMethodSubtypePreference extends SwitchWithNoTextPreference {
+ private final boolean mIsSystemLocale;
+ private final boolean mIsSystemLanguage;
+
+ InputMethodSubtypePreference(final Context context, final InputMethodSubtype subtype,
+ final InputMethodInfo imi) {
+ super(context);
+ setPersistent(false);
+ setKey(imi.getId() + subtype.hashCode());
+ final CharSequence subtypeLabel = subtype.getDisplayName(context,
+ imi.getPackageName(), imi.getServiceInfo().applicationInfo);
+ setTitle(subtypeLabel);
+ final String subtypeLocaleString = subtype.getLocale();
+ if (TextUtils.isEmpty(subtypeLocaleString)) {
+ mIsSystemLocale = false;
+ mIsSystemLanguage = false;
+ } else {
+ final Locale systemLocale = context.getResources().getConfiguration().locale;
+ mIsSystemLocale = subtypeLocaleString.equals(systemLocale.toString());
+ mIsSystemLanguage = mIsSystemLocale
+ || InputMethodUtils.getLanguageFromLocaleString(subtypeLocaleString)
+ .equals(systemLocale.getLanguage());
+ }
+ }
+
+ int compareTo(final Preference rhs, final Collator collator) {
+ if (this == rhs) {
+ return 0;
+ }
+ if (rhs instanceof InputMethodSubtypePreference) {
+ final InputMethodSubtypePreference pref = (InputMethodSubtypePreference) rhs;
+ final CharSequence t0 = getTitle();
+ final CharSequence t1 = rhs.getTitle();
+ if (TextUtils.equals(t0, t1)) {
+ return 0;
+ }
+ if (mIsSystemLocale) {
+ return -1;
+ }
+ if (pref.mIsSystemLocale) {
+ return 1;
+ }
+ if (mIsSystemLanguage) {
+ return -1;
+ }
+ if (pref.mIsSystemLanguage) {
+ return 1;
+ }
+ if (TextUtils.isEmpty(t0)) {
+ return 1;
+ }
+ if (TextUtils.isEmpty(t1)) {
+ return -1;
+ }
+ return collator.compare(t0.toString(), t1.toString());
+ }
+ return super.compareTo(rhs);
+ }
+}
diff --git a/src/com/android/settings/inputmethod/KeyboardLayoutDialogFragment.java b/src/com/android/settings/inputmethod/KeyboardLayoutDialogFragment.java
index 451b36e..c77b2c9 100644
--- a/src/com/android/settings/inputmethod/KeyboardLayoutDialogFragment.java
+++ b/src/com/android/settings/inputmethod/KeyboardLayoutDialogFragment.java
@@ -17,7 +17,6 @@
package com.android.settings.inputmethod;
import com.android.settings.R;
-import com.android.settings.Settings.KeyboardLayoutPickerActivity;
import android.app.AlertDialog;
import android.app.Activity;
@@ -29,7 +28,6 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.Loader;
-import android.content.res.Resources;
import android.hardware.input.InputDeviceIdentifier;
import android.hardware.input.InputManager;
import android.hardware.input.KeyboardLayout;
diff --git a/src/com/android/settings/inputmethod/SingleSpellCheckerPreference.java b/src/com/android/settings/inputmethod/SingleSpellCheckerPreference.java
deleted file mode 100644
index 5ea8bd7..0000000
--- a/src/com/android/settings/inputmethod/SingleSpellCheckerPreference.java
+++ /dev/null
@@ -1,218 +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.inputmethod;
-
-import com.android.settings.R;
-import com.android.settings.Utils;
-
-import android.app.AlertDialog;
-import android.content.ActivityNotFoundException;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.preference.Preference;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.textservice.SpellCheckerInfo;
-import android.view.textservice.SpellCheckerSubtype;
-import android.view.textservice.TextServicesManager;
-import android.widget.ImageView;
-import android.widget.RadioButton;
-import android.widget.TextView;
-import android.widget.Toast;
-
-public class SingleSpellCheckerPreference extends Preference {
- private static final String TAG = SingleSpellCheckerPreference.class.getSimpleName();
- private static final boolean DBG = false;
-
- private final SpellCheckerInfo mSpellCheckerInfo;
-
- private final SpellCheckersSettings mFragment;
- private final Resources mRes;
- private final TextServicesManager mTsm;
- private AlertDialog mDialog = null;
- private TextView mTitleText;
- private TextView mSummaryText;
- private View mPrefAll;
- private RadioButton mRadioButton;
- private View mPrefLeftButton;
- private View mSettingsButton;
- private ImageView mSubtypeButton;
- private Intent mSettingsIntent;
- private boolean mSelected;
-
- public SingleSpellCheckerPreference(SpellCheckersSettings fragment, Intent settingsIntent,
- SpellCheckerInfo sci, TextServicesManager tsm) {
- super(fragment.getActivity(), null, 0);
- mFragment = fragment;
- mRes = fragment.getActivity().getResources();
- mTsm = tsm;
- setLayoutResource(R.layout.preference_spellchecker);
- mSpellCheckerInfo = sci;
- mSelected = false;
- final String settingsActivity = mSpellCheckerInfo.getSettingsActivity();
- if (!TextUtils.isEmpty(settingsActivity)) {
- mSettingsIntent = new Intent(Intent.ACTION_MAIN);
- mSettingsIntent.setClassName(mSpellCheckerInfo.getPackageName(), settingsActivity);
- } else {
- mSettingsIntent = null;
- }
- }
-
- @Override
- protected void onBindView(View view) {
- super.onBindView(view);
- mPrefAll = view.findViewById(R.id.pref_all);
- mRadioButton = (RadioButton)view.findViewById(R.id.pref_radio);
- mPrefLeftButton = view.findViewById(R.id.pref_left_button);
- mPrefLeftButton.setOnClickListener(
- new OnClickListener() {
- @Override
- public void onClick(View arg0) {
- onLeftButtonClicked(arg0);
- }
- });
- mTitleText = (TextView)view.findViewById(android.R.id.title);
- mSummaryText = (TextView)view.findViewById(android.R.id.summary);
- mSubtypeButton = (ImageView)view.findViewById(R.id.pref_right_button2);
- mSubtypeButton.setOnClickListener(
- new OnClickListener() {
- @Override
- public void onClick(View arg0) {
- onSubtypeButtonClicked(arg0);
- }
- });
- mSettingsButton = view.findViewById(R.id.pref_right_button1);
- mSettingsButton.setOnClickListener(
- new OnClickListener() {
- @Override
- public void onClick(View arg0) {
- onSettingsButtonClicked(arg0);
- }
- });
- updateSelectedState(mSelected);
- }
-
- private void onLeftButtonClicked(View arg0) {
- mFragment.onPreferenceClick(this);
- }
-
- public SpellCheckerInfo getSpellCheckerInfo() {
- return mSpellCheckerInfo;
- }
-
- private void updateSelectedState(boolean selected) {
- if (mPrefAll != null) {
- mRadioButton.setChecked(selected);
- enableButtons(selected);
- }
- }
-
- public void setSelected(boolean selected) {
- mSelected = selected;
- updateSelectedState(selected);
- }
-
- private void onSubtypeButtonClicked(View arg0) {
- if (mDialog != null && mDialog.isShowing()) {
- mDialog.dismiss();
- }
- final AlertDialog.Builder builder = new AlertDialog.Builder(mFragment.getActivity());
- builder.setTitle(R.string.phone_language);
- final int size = mSpellCheckerInfo.getSubtypeCount();
- final CharSequence[] items = new CharSequence[size + 1];
- items[0] = mRes.getString(R.string.use_system_language_to_select_input_method_subtypes);
- for (int i = 0; i < size; ++i) {
- final SpellCheckerSubtype subtype = mSpellCheckerInfo.getSubtypeAt(i);
- final CharSequence label = subtype.getDisplayName(
- mFragment.getActivity(), mSpellCheckerInfo.getPackageName(),
- mSpellCheckerInfo.getServiceInfo().applicationInfo);
- items[i + 1] = label;
- }
- // default: "Use system language"
- int checkedItem = 0;
- // Allow no implicitly selected subtypes
- final SpellCheckerSubtype currentScs = mTsm.getCurrentSpellCheckerSubtype(false);
- if (currentScs != null) {
- for (int i = 0; i < size; ++i) {
- if (mSpellCheckerInfo.getSubtypeAt(i).equals(currentScs)) {
- checkedItem = i + 1;
- break;
- }
- }
- }
- builder.setSingleChoiceItems(items, checkedItem, new AlertDialog.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- if (which == 0) {
- mTsm.setSpellCheckerSubtype(null);
- } else {
- mTsm.setSpellCheckerSubtype(mSpellCheckerInfo.getSubtypeAt(which - 1));
- }
- if (DBG) {
- final SpellCheckerSubtype subtype = mTsm.getCurrentSpellCheckerSubtype(true);
- Log.d(TAG, "Current spell check locale is "
- + subtype == null ? "null" : subtype.getLocale());
- }
- dialog.dismiss();
- }
- });
- mDialog = builder.create();
- mDialog.show();
- }
-
- private void onSettingsButtonClicked(View arg0) {
- if (mFragment != null && mSettingsIntent != null) {
- try {
- mFragment.startActivity(mSettingsIntent);
- } catch (ActivityNotFoundException e) {
- final String msg = mFragment.getString(R.string.failed_to_open_app_settings_toast,
- mSpellCheckerInfo.loadLabel(mFragment.getActivity().getPackageManager()));
- Toast.makeText(mFragment.getActivity(), msg, Toast.LENGTH_LONG).show();
- }
- }
- }
-
- private void enableButtons(boolean enabled) {
- if (mSettingsButton != null) {
- if (mSettingsIntent == null) {
- mSettingsButton.setVisibility(View.GONE);
- } else {
- mSettingsButton.setEnabled(enabled);
- mSettingsButton.setClickable(enabled);
- mSettingsButton.setFocusable(enabled);
- if (!enabled) {
- mSettingsButton.setAlpha(Utils.DISABLED_ALPHA);
- }
- }
- }
- if (mSubtypeButton != null) {
- if (mSpellCheckerInfo.getSubtypeCount() <= 0) {
- mSubtypeButton.setVisibility(View.GONE);
- } else {
- mSubtypeButton.setEnabled(enabled);
- mSubtypeButton.setClickable(enabled);
- mSubtypeButton.setFocusable(enabled);
- if (!enabled) {
- mSubtypeButton.setAlpha(Utils.DISABLED_ALPHA);
- }
- }
- }
- }
-}
diff --git a/src/com/android/settings/inputmethod/SpellCheckerPreference.java b/src/com/android/settings/inputmethod/SpellCheckerPreference.java
new file mode 100644
index 0000000..3787803
--- /dev/null
+++ b/src/com/android/settings/inputmethod/SpellCheckerPreference.java
@@ -0,0 +1,151 @@
+/*
+ * 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.inputmethod;
+
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.preference.Preference;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.textservice.SpellCheckerInfo;
+import android.widget.RadioButton;
+import android.widget.Toast;
+
+import com.android.settings.R;
+import com.android.settings.Utils;
+
+/**
+ * Spell checker service preference.
+ *
+ * This preference represents a spell checker service. It is used for two purposes. 1) A radio
+ * button on the left side is used to choose the current spell checker service. 2) A settings
+ * icon on the right side is used to invoke the setting activity of the spell checker service.
+ */
+class SpellCheckerPreference extends Preference implements OnClickListener {
+ interface OnRadioButtonPreferenceListener {
+ /**
+ * Called when this preference needs to be saved its state.
+ *
+ * Note that this preference is non-persistent and needs explicitly to be saved its state.
+ * Because changing one IME state may change other IMEs' state, this is a place to update
+ * other IMEs' state as well.
+ *
+ * @param pref This preference.
+ */
+ public void onRadioButtonClicked(SpellCheckerPreference pref);
+ }
+
+ private final SpellCheckerInfo mSci;
+ private final OnRadioButtonPreferenceListener mOnRadioButtonListener;
+
+ private RadioButton mRadioButton;
+ private View mPrefLeftButton;
+ private View mSettingsButton;
+ private boolean mSelected;
+
+ public SpellCheckerPreference(final Context context, final SpellCheckerInfo sci,
+ final OnRadioButtonPreferenceListener onRadioButtonListener) {
+ super(context, null, 0);
+ setPersistent(false);
+ setLayoutResource(R.layout.preference_spellchecker);
+ setWidgetLayoutResource(R.layout.preference_spellchecker_widget);
+ mSci = sci;
+ mOnRadioButtonListener = onRadioButtonListener;
+ setKey(sci.getId());
+ setTitle(sci.loadLabel(context.getPackageManager()));
+ final String settingsActivity = mSci.getSettingsActivity();
+ if (TextUtils.isEmpty(settingsActivity)) {
+ setIntent(null);
+ } else {
+ final Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setClassName(mSci.getPackageName(), settingsActivity);
+ setIntent(intent);
+ }
+ }
+
+ @Override
+ protected void onBindView(View view) {
+ super.onBindView(view);
+ mRadioButton = (RadioButton)view.findViewById(R.id.pref_radio);
+ mPrefLeftButton = view.findViewById(R.id.pref_left_button);
+ mPrefLeftButton.setOnClickListener(this);
+ mSettingsButton = view.findViewById(R.id.pref_right_button);
+ mSettingsButton.setOnClickListener(this);
+ updateSelectedState(mSelected);
+ }
+
+ @Override
+ public void onClick(final View v) {
+ if (v == mPrefLeftButton) {
+ mOnRadioButtonListener.onRadioButtonClicked(this);
+ return;
+ }
+ if (v == mSettingsButton) {
+ onSettingsButtonClicked();
+ return;
+ }
+ }
+
+ private void onSettingsButtonClicked() {
+ final Context context = getContext();
+ try {
+ final Intent intent = getIntent();
+ if (intent != null) {
+ // Invoke a settings activity of an spell checker.
+ context.startActivity(intent);
+ }
+ } catch (final ActivityNotFoundException e) {
+ final String message = context.getString(R.string.failed_to_open_app_settings_toast,
+ mSci.loadLabel(context.getPackageManager()));
+ Toast.makeText(context, message, Toast.LENGTH_LONG).show();
+ }
+ }
+
+ public SpellCheckerInfo getSpellCheckerInfo() {
+ return mSci;
+ }
+
+ public void setSelected(final boolean selected) {
+ mSelected = selected;
+ updateSelectedState(selected);
+ }
+
+ private void updateSelectedState(final boolean selected) {
+ if (mRadioButton != null) {
+ mRadioButton.setChecked(selected);
+ enableSettingsButton(isEnabled() && selected);
+ }
+ }
+
+ private void enableSettingsButton(final boolean enabled) {
+ if (mSettingsButton == null) {
+ return;
+ }
+ if (getIntent() == null) {
+ mSettingsButton.setVisibility(View.GONE);
+ } else {
+ mSettingsButton.setEnabled(enabled);
+ mSettingsButton.setClickable(enabled);
+ mSettingsButton.setFocusable(enabled);
+ if (!enabled) {
+ mSettingsButton.setAlpha(Utils.DISABLED_ALPHA);
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/inputmethod/SpellCheckerUtils.java b/src/com/android/settings/inputmethod/SpellCheckerUtils.java
deleted file mode 100644
index fe761a6..0000000
--- a/src/com/android/settings/inputmethod/SpellCheckerUtils.java
+++ /dev/null
@@ -1,47 +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.inputmethod;
-
-import android.util.Log;
-import android.view.textservice.SpellCheckerInfo;
-import android.view.textservice.TextServicesManager;
-
-public class SpellCheckerUtils {
- private static final String TAG = SpellCheckerUtils.class.getSimpleName();
- private static final boolean DBG = false;
- public static void setSpellCheckersEnabled(TextServicesManager tsm, boolean enable) {
- }
- public static boolean getSpellCheckersEnabled(TextServicesManager tsm) {
- return true;
- }
- public static void setCurrentSpellChecker(TextServicesManager tsm, SpellCheckerInfo info) {
- }
- public static SpellCheckerInfo getCurrentSpellChecker(TextServicesManager tsm) {
- final SpellCheckerInfo retval = tsm.getCurrentSpellChecker();
- if (DBG) {
- Log.d(TAG, "getCurrentSpellChecker: " + retval);
- }
- return retval;
- }
- public static SpellCheckerInfo[] getEnabledSpellCheckers(TextServicesManager tsm) {
- final SpellCheckerInfo[] retval = tsm.getEnabledSpellCheckers();
- if (DBG) {
- Log.d(TAG, "get spell checkers: " + retval.length);
- }
- return retval;
- }
-}
diff --git a/src/com/android/settings/inputmethod/SpellCheckersPreference.java b/src/com/android/settings/inputmethod/SpellCheckersPreference.java
deleted file mode 100644
index 5e4ebba..0000000
--- a/src/com/android/settings/inputmethod/SpellCheckersPreference.java
+++ /dev/null
@@ -1,39 +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.inputmethod;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.textservice.TextServicesManager;
-
-public class SpellCheckersPreference extends CheckBoxAndSettingsPreference {
- private final TextServicesManager mTsm;
-
- public SpellCheckersPreference(Context context, AttributeSet attrs) {
- super(context, attrs);
- mTsm = (TextServicesManager) context.getSystemService(
- Context.TEXT_SERVICES_MANAGER_SERVICE);
- setChecked(mTsm.isSpellCheckerEnabled());
- }
-
- @Override
- protected void onCheckBoxClicked() {
- super.onCheckBoxClicked();
- final boolean checked = isChecked();
- mTsm.setSpellCheckerEnabled(checked);
- }
-}
diff --git a/src/com/android/settings/inputmethod/SpellCheckersSettings.java b/src/com/android/settings/inputmethod/SpellCheckersSettings.java
index 8b1b867..5a8ccea 100644
--- a/src/com/android/settings/inputmethod/SpellCheckersSettings.java
+++ b/src/com/android/settings/inputmethod/SpellCheckersSettings.java
@@ -16,144 +16,221 @@
package com.android.settings.inputmethod;
-import com.android.settings.R;
-import com.android.settings.SettingsPreferenceFragment;
-
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
import android.os.Bundle;
import android.preference.Preference;
+import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceScreen;
import android.util.Log;
import android.view.textservice.SpellCheckerInfo;
+import android.view.textservice.SpellCheckerSubtype;
import android.view.textservice.TextServicesManager;
+import android.widget.Switch;
-import java.util.ArrayList;
+import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.inputmethod.SpellCheckerPreference.OnRadioButtonPreferenceListener;
+import com.android.settings.widget.SwitchBar;
+import com.android.settings.widget.SwitchBar.OnSwitchChangeListener;
public class SpellCheckersSettings extends SettingsPreferenceFragment
- implements Preference.OnPreferenceClickListener {
+ implements OnSwitchChangeListener, OnPreferenceClickListener,
+ OnRadioButtonPreferenceListener {
private static final String TAG = SpellCheckersSettings.class.getSimpleName();
private static final boolean DBG = false;
+ private static final String KEY_SPELL_CHECKER_LANGUAGE = "spellchecker_language";
+ private static final int ITEM_ID_USE_SYSTEM_LANGUAGE = 0;
+
+ private SwitchBar mSwitchBar;
+ private Preference mSpellCheckerLanaguagePref;
private AlertDialog mDialog = null;
private SpellCheckerInfo mCurrentSci;
private SpellCheckerInfo[] mEnabledScis;
private TextServicesManager mTsm;
- private final ArrayList<SingleSpellCheckerPreference> mSpellCheckers =
- new ArrayList<SingleSpellCheckerPreference>();
@Override
- public void onCreate(Bundle icicle) {
+ public void onCreate(final Bundle icicle) {
super.onCreate(icicle);
- mTsm = (TextServicesManager) getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
+
addPreferencesFromResource(R.xml.spellchecker_prefs);
- updateScreen();
+ mSpellCheckerLanaguagePref = findPreference(KEY_SPELL_CHECKER_LANGUAGE);
+ mSpellCheckerLanaguagePref.setOnPreferenceClickListener(this);
+
+ mTsm = (TextServicesManager) getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
+ mCurrentSci = mTsm.getCurrentSpellChecker();
+ mEnabledScis = mTsm.getEnabledSpellCheckers();
+ populatePreferenceScreen();
}
- @Override
- public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
- return false;
+ private void populatePreferenceScreen() {
+ final PreferenceScreen screen = getPreferenceScreen();
+ final Context context = getActivity();
+ final int count = (mEnabledScis == null) ? 0 : mEnabledScis.length;
+ for (int index = 0; index < count; ++index) {
+ final SpellCheckerInfo sci = mEnabledScis[index];
+ final SpellCheckerPreference pref = new SpellCheckerPreference(context, sci, this);
+ screen.addPreference(pref);
+ InputMethodAndSubtypeUtil.removeUnnecessaryNonPersistentPreference(pref);
+ }
}
@Override
public void onResume() {
super.onResume();
- updateScreen();
+ mSwitchBar = ((SettingsActivity)getActivity()).getSwitchBar();
+ mSwitchBar.show();
+ mSwitchBar.addOnSwitchChangeListener(this);
+ updatePreferenceScreen();
}
@Override
public void onPause() {
super.onPause();
- saveState();
+ mSwitchBar.removeOnSwitchChangeListener(this);
}
- private void saveState() {
- SpellCheckerUtils.setCurrentSpellChecker(mTsm, mCurrentSci);
+ @Override
+ public void onSwitchChanged(final Switch switchView, final boolean isChecked) {
+ mTsm.setSpellCheckerEnabled(isChecked);
+ updatePreferenceScreen();
}
- private void updateScreen() {
- getPreferenceScreen().removeAll();
- updateEnabledSpellCheckers();
+ private void updatePreferenceScreen() {
+ mCurrentSci = mTsm.getCurrentSpellChecker();
+ final boolean isSpellCheckerEnabled = mTsm.isSpellCheckerEnabled();
+ mSwitchBar.setChecked(isSpellCheckerEnabled);
+
+ final SpellCheckerSubtype currentScs = mTsm.getCurrentSpellCheckerSubtype(
+ false /* allowImplicitlySelectedSubtype */);
+ mSpellCheckerLanaguagePref.setSummary(getSpellCheckerSubtypeLabel(mCurrentSci, currentScs));
+
+ final PreferenceScreen screen = getPreferenceScreen();
+ final int count = screen.getPreferenceCount();
+ for (int index = 0; index < count; index++) {
+ final Preference preference = screen.getPreference(index);
+ preference.setEnabled(isSpellCheckerEnabled);
+ if (preference instanceof SpellCheckerPreference) {
+ final SpellCheckerPreference pref = (SpellCheckerPreference)preference;
+ final SpellCheckerInfo sci = pref.getSpellCheckerInfo();
+ pref.setSelected(mCurrentSci != null && mCurrentSci.getId().equals(sci.getId()));
+ }
+ }
}
- private void updateEnabledSpellCheckers() {
- final PackageManager pm = getPackageManager();
- mCurrentSci = SpellCheckerUtils.getCurrentSpellChecker(mTsm);
- mEnabledScis = SpellCheckerUtils.getEnabledSpellCheckers(mTsm);
- if (mCurrentSci == null || mEnabledScis == null) {
- return;
+ private CharSequence getSpellCheckerSubtypeLabel(final SpellCheckerInfo sci,
+ final SpellCheckerSubtype subtype) {
+ if (sci == null) {
+ return null;
}
- mSpellCheckers.clear();
- for (int i = 0; i < mEnabledScis.length; ++i) {
- final SpellCheckerInfo sci = mEnabledScis[i];
- final SingleSpellCheckerPreference scPref = new SingleSpellCheckerPreference(
- this, null, sci, mTsm);
- mSpellCheckers.add(scPref);
- scPref.setTitle(sci.loadLabel(pm));
- scPref.setSelected(mCurrentSci != null && mCurrentSci.getId().equals(sci.getId()));
- getPreferenceScreen().addPreference(scPref);
+ if (subtype == null) {
+ return getString(R.string.use_system_language_to_select_input_method_subtypes);
}
+ return subtype.getDisplayName(
+ getActivity(), sci.getPackageName(), sci.getServiceInfo().applicationInfo);
}
@Override
- public boolean onPreferenceClick(Preference pref) {
- SingleSpellCheckerPreference targetScp = null;
- for (SingleSpellCheckerPreference scp : mSpellCheckers) {
- if (pref.equals(scp)) {
- targetScp = scp;
- }
+ public boolean onPreferenceClick(final Preference pref) {
+ if (pref == mSpellCheckerLanaguagePref) {
+ showChooseLanguageDialog();
+ return true;
}
- if (targetScp != null) {
- if (!isSystemApp(targetScp.getSpellCheckerInfo())) {
- showSecurityWarnDialog(targetScp);
- } else {
- changeCurrentSpellChecker(targetScp);
- }
+ return false;
+ }
+
+ @Override
+ public void onRadioButtonClicked(final SpellCheckerPreference pref) {
+ final SpellCheckerInfo sci = pref.getSpellCheckerInfo();
+ final boolean isSystemApp =
+ (sci.getServiceInfo().applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ if (isSystemApp) {
+ changeCurrentSpellChecker(sci);
+ } else {
+ showSecurityWarnDialog(pref);
}
- return true;
}
- private void showSecurityWarnDialog(final SingleSpellCheckerPreference scp) {
+ private static int convertSubtypeIndexToDialogItemId(final int index) { return index + 1; }
+ private static int convertDialogItemIdToSubtypeIndex(final int item) { return item - 1; }
+
+ private void showChooseLanguageDialog() {
if (mDialog != null && mDialog.isShowing()) {
mDialog.dismiss();
}
- mDialog = (new AlertDialog.Builder(getActivity()))
- .setTitle(android.R.string.dialog_alert_title)
- .setIconAttribute(android.R.attr.alertDialogIcon)
- .setCancelable(true)
- .setPositiveButton(android.R.string.ok,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- changeCurrentSpellChecker(scp);
- }
- })
- .setNegativeButton(android.R.string.cancel,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- }
- })
- .create();
- mDialog.setMessage(getResources().getString(R.string.spellchecker_security_warning,
- scp.getSpellCheckerInfo().getServiceInfo().applicationInfo.loadLabel(
- getActivity().getPackageManager())));
+ final SpellCheckerInfo currentSci = mTsm.getCurrentSpellChecker();
+ final SpellCheckerSubtype currentScs = mTsm.getCurrentSpellCheckerSubtype(
+ false /* allowImplicitlySelectedSubtype */);
+ final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setTitle(R.string.phone_language);
+ final int subtypeCount = currentSci.getSubtypeCount();
+ final CharSequence[] items = new CharSequence[subtypeCount + 1 /* default */ ];
+ items[ITEM_ID_USE_SYSTEM_LANGUAGE] = getSpellCheckerSubtypeLabel(currentSci, null);
+ int checkedItemId = ITEM_ID_USE_SYSTEM_LANGUAGE;
+ for (int index = 0; index < subtypeCount; ++index) {
+ final SpellCheckerSubtype subtype = currentSci.getSubtypeAt(index);
+ final int itemId = convertSubtypeIndexToDialogItemId(index);
+ items[itemId] = getSpellCheckerSubtypeLabel(currentSci, subtype);
+ if (subtype.equals(currentScs)) {
+ checkedItemId = itemId;
+ }
+ }
+ builder.setSingleChoiceItems(items, checkedItemId, new AlertDialog.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int item) {
+ if (item == ITEM_ID_USE_SYSTEM_LANGUAGE) {
+ mTsm.setSpellCheckerSubtype(null);
+ } else {
+ final int index = convertDialogItemIdToSubtypeIndex(item);
+ mTsm.setSpellCheckerSubtype(currentSci.getSubtypeAt(index));
+ }
+ if (DBG) {
+ final SpellCheckerSubtype subtype = mTsm.getCurrentSpellCheckerSubtype(
+ true /* allowImplicitlySelectedSubtype */);
+ Log.d(TAG, "Current spell check locale is "
+ + subtype == null ? "null" : subtype.getLocale());
+ }
+ dialog.dismiss();
+ updatePreferenceScreen();
+ }
+ });
+ mDialog = builder.create();
mDialog.show();
}
- private void changeCurrentSpellChecker(SingleSpellCheckerPreference scp) {
- mTsm.setCurrentSpellChecker(scp.getSpellCheckerInfo());
- if (DBG) {
- Log.d(TAG, "Current spell check is "
- + SpellCheckerUtils.getCurrentSpellChecker(mTsm).getId());
+ private void showSecurityWarnDialog(final SpellCheckerPreference pref) {
+ if (mDialog != null && mDialog.isShowing()) {
+ mDialog.dismiss();
}
- updateScreen();
+ final SpellCheckerInfo sci = pref.getSpellCheckerInfo();
+ final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setTitle(android.R.string.dialog_alert_title);
+ builder.setMessage(getString(R.string.spellchecker_security_warning, pref.getTitle()));
+ builder.setCancelable(true);
+ builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ changeCurrentSpellChecker(sci);
+ }
+ });
+ builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ }
+ });
+ mDialog = builder.create();
+ mDialog.show();
}
- private static boolean isSystemApp(SpellCheckerInfo sci) {
- return (sci.getServiceInfo().applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ private void changeCurrentSpellChecker(final SpellCheckerInfo sci) {
+ mTsm.setCurrentSpellChecker(sci);
+ if (DBG) {
+ Log.d(TAG, "Current spell check is " + mTsm.getCurrentSpellChecker().getId());
+ }
+ updatePreferenceScreen();
}
}
diff --git a/src/com/android/settings/wifi/AccessPointCategoryForSetupWizardXL.java b/src/com/android/settings/inputmethod/SwitchWithNoTextPreference.java
index 7a1623b..677c031 100644
--- a/src/com/android/settings/wifi/AccessPointCategoryForSetupWizardXL.java
+++ b/src/com/android/settings/inputmethod/SwitchWithNoTextPreference.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 The Android Open Source Project
+ * 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.
@@ -14,22 +14,17 @@
* limitations under the License.
*/
-package com.android.settings.wifi;
-
-import com.android.settings.ProgressCategoryBase;
-import com.android.settings.R;
+package com.android.settings.inputmethod;
import android.content.Context;
-import android.util.AttributeSet;
+import android.preference.SwitchPreference;
-public class AccessPointCategoryForSetupWizardXL extends ProgressCategoryBase {
- public AccessPointCategoryForSetupWizardXL(Context context, AttributeSet attrs) {
- super(context, attrs);
- setLayoutResource(R.layout.access_point_category_for_setup_wizard_xl);
- }
+class SwitchWithNoTextPreference extends SwitchPreference {
+ private static final String EMPTY_TEXT = "";
- @Override
- public void setProgress(boolean progressOn) {
- notifyChanged();
+ SwitchWithNoTextPreference(final Context context) {
+ super(context);
+ setSwitchTextOn(EMPTY_TEXT);
+ setSwitchTextOff(EMPTY_TEXT);
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/settings/inputmethod/UserDictionaryAddWordContents.java b/src/com/android/settings/inputmethod/UserDictionaryAddWordContents.java
index d81703e..638818a 100644
--- a/src/com/android/settings/inputmethod/UserDictionaryAddWordContents.java
+++ b/src/com/android/settings/inputmethod/UserDictionaryAddWordContents.java
@@ -146,7 +146,9 @@ public class UserDictionaryAddWordContents {
// should not insert, because either A. the word exists with no shortcut, in which
// case the exact same thing we want to insert is already there, or B. the word
// exists with at least one shortcut, in which case it has priority on our word.
- if (hasWord(newWord, context)) return UserDictionaryAddWordActivity.CODE_ALREADY_PRESENT;
+ if (TextUtils.isEmpty(newShortcut) && hasWord(newWord, context)) {
+ return UserDictionaryAddWordActivity.CODE_ALREADY_PRESENT;
+ }
// Disallow duplicates. If the same word with no shortcut is defined, remove it; if
// the same word with the same shortcut is defined, remove it; but we don't mind if
diff --git a/src/com/android/settings/inputmethod/UserDictionaryAddWordFragment.java b/src/com/android/settings/inputmethod/UserDictionaryAddWordFragment.java
index 86c3e79..4f231cb 100644
--- a/src/com/android/settings/inputmethod/UserDictionaryAddWordFragment.java
+++ b/src/com/android/settings/inputmethod/UserDictionaryAddWordFragment.java
@@ -18,7 +18,6 @@ package com.android.settings.inputmethod;
import android.app.Fragment;
import android.os.Bundle;
-import android.preference.PreferenceActivity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -29,6 +28,7 @@ import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import com.android.settings.R;
+import com.android.settings.SettingsActivity;
import com.android.settings.inputmethod.UserDictionaryAddWordContents.LocaleRenderer;
import java.util.ArrayList;
@@ -137,8 +137,8 @@ public class UserDictionaryAddWordFragment extends Fragment
final long id) {
final LocaleRenderer locale = (LocaleRenderer)parent.getItemAtPosition(pos);
if (locale.isMoreLanguages()) {
- PreferenceActivity preferenceActivity = (PreferenceActivity)getActivity();
- preferenceActivity.startPreferenceFragment(new UserDictionaryLocalePicker(this), true);
+ SettingsActivity sa = (SettingsActivity)getActivity();
+ sa.startPreferenceFragment(new UserDictionaryLocalePicker(this), true);
} else {
mContents.updateLocale(locale.getLocaleString());
}
diff --git a/src/com/android/settings/inputmethod/UserDictionaryList.java b/src/com/android/settings/inputmethod/UserDictionaryList.java
index 24acb4a..7439001 100644
--- a/src/com/android/settings/inputmethod/UserDictionaryList.java
+++ b/src/com/android/settings/inputmethod/UserDictionaryList.java
@@ -72,22 +72,27 @@ public class UserDictionaryList extends SettingsPreferenceFragment {
mLocale = locale;
}
- public static TreeSet<String> getUserDictionaryLocalesSet(Activity activity) {
- @SuppressWarnings("deprecation")
- final Cursor cursor = activity.managedQuery(UserDictionary.Words.CONTENT_URI,
- new String[] { UserDictionary.Words.LOCALE },
+ public static TreeSet<String> getUserDictionaryLocalesSet(Context context) {
+ final Cursor cursor = context.getContentResolver().query(
+ UserDictionary.Words.CONTENT_URI, new String[] { UserDictionary.Words.LOCALE },
null, null, null);
final TreeSet<String> localeSet = new TreeSet<String>();
if (null == cursor) {
// The user dictionary service is not present or disabled. Return null.
return null;
- } else if (cursor.moveToFirst()) {
- final int columnIndex = cursor.getColumnIndex(UserDictionary.Words.LOCALE);
- do {
- final String locale = cursor.getString(columnIndex);
- localeSet.add(null != locale ? locale : "");
- } while (cursor.moveToNext());
}
+ try {
+ if (cursor.moveToFirst()) {
+ final int columnIndex = cursor.getColumnIndex(UserDictionary.Words.LOCALE);
+ do {
+ final String locale = cursor.getString(columnIndex);
+ localeSet.add(null != locale ? locale : "");
+ } while (cursor.moveToNext());
+ }
+ } finally {
+ cursor.close();
+ }
+
// CAVEAT: Keep this for consistency of the implementation between Keyboard and Settings
// if (!UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) {
// // For ICS, we need to show "For all languages" in case that the keyboard locale
@@ -96,7 +101,7 @@ public class UserDictionaryList extends SettingsPreferenceFragment {
// }
final InputMethodManager imm =
- (InputMethodManager)activity.getSystemService(Context.INPUT_METHOD_SERVICE);
+ (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
final List<InputMethodInfo> imis = imm.getEnabledInputMethodList();
for (final InputMethodInfo imi : imis) {
final List<InputMethodSubtype> subtypes =
diff --git a/src/com/android/settings/location/DimmableIconPreference.java b/src/com/android/settings/location/DimmableIconPreference.java
new file mode 100644
index 0000000..acde1c1
--- /dev/null
+++ b/src/com/android/settings/location/DimmableIconPreference.java
@@ -0,0 +1,63 @@
+/*
+ * 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.location;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.preference.Preference;
+import android.util.AttributeSet;
+
+/**
+ * A preference item that can dim the icon when it's disabled, either directly or because its parent
+ * is disabled.
+ */
+public class DimmableIconPreference extends Preference {
+ private static final int ICON_ALPHA_ENABLED = 255;
+ private static final int ICON_ALPHA_DISABLED = 102;
+
+ public DimmableIconPreference(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public DimmableIconPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public DimmableIconPreference(Context context) {
+ super(context);
+ }
+
+ private void dimIcon(boolean dimmed) {
+ Drawable icon = getIcon();
+ if (icon != null) {
+ icon.mutate().setAlpha(dimmed ? ICON_ALPHA_DISABLED : ICON_ALPHA_ENABLED);
+ setIcon(icon);
+ }
+ }
+
+ @Override
+ public void onParentChanged(Preference parent, boolean disableChild) {
+ dimIcon(disableChild);
+ super.onParentChanged(parent, disableChild);
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ dimIcon(!enabled);
+ super.setEnabled(enabled);
+ }
+}
diff --git a/src/com/android/settings/location/InjectedSetting.java b/src/com/android/settings/location/InjectedSetting.java
index d8a3f49..5f4440a 100644
--- a/src/com/android/settings/location/InjectedSetting.java
+++ b/src/com/android/settings/location/InjectedSetting.java
@@ -23,7 +23,7 @@ import com.android.internal.annotations.Immutable;
import com.android.internal.util.Preconditions;
/**
- * Specifies a setting that is being injected into Settings > Location > Location services.
+ * Specifies a setting that is being injected into Settings &gt; Location &gt; Location services.
*
* @see android.location.SettingInjectorService
*/
diff --git a/src/com/android/settings/location/LocationSettings.java b/src/com/android/settings/location/LocationSettings.java
index 06a6650..c0a13c1 100644
--- a/src/com/android/settings/location/LocationSettings.java
+++ b/src/com/android/settings/location/LocationSettings.java
@@ -16,27 +16,22 @@
package com.android.settings.location;
-import android.app.ActionBar;
-import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.location.LocationManager;
import android.location.SettingInjectorService;
import android.os.Bundle;
import android.preference.Preference;
-import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
-import android.provider.Settings;
import android.util.Log;
-import android.view.Gravity;
-import android.widget.CompoundButton;
import android.widget.Switch;
import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.widget.SwitchBar;
import java.util.Collections;
import java.util.Comparator;
@@ -46,7 +41,7 @@ import java.util.List;
* Location access settings.
*/
public class LocationSettings extends LocationSettingsBase
- implements CompoundButton.OnCheckedChangeListener {
+ implements SwitchBar.OnSwitchChangeListener {
private static final String TAG = "LocationSettings";
@@ -57,24 +52,40 @@ public class LocationSettings extends LocationSettingsBase
/** Key for preference category "Location services" */
private static final String KEY_LOCATION_SERVICES = "location_services";
+ private SwitchBar mSwitchBar;
private Switch mSwitch;
- private boolean mValidListener;
+ private boolean mValidListener = false;
private Preference mLocationMode;
private PreferenceCategory mCategoryRecentLocationRequests;
/** Receives UPDATE_INTENT */
private BroadcastReceiver mReceiver;
+ private SettingsInjector injector;
- public LocationSettings() {
- mValidListener = false;
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ final SettingsActivity activity = (SettingsActivity) getActivity();
+
+ mSwitchBar = activity.getSwitchBar();
+ mSwitch = mSwitchBar.getSwitch();
+ mSwitchBar.show();
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ mSwitchBar.hide();
}
@Override
public void onResume() {
super.onResume();
- mSwitch = new Switch(getActivity());
- mSwitch.setOnCheckedChangeListener(this);
- mValidListener = true;
createPreferenceHierarchy();
+ if (!mValidListener) {
+ mSwitchBar.addOnSwitchChangeListener(this);
+ mValidListener = true;
+ }
}
@Override
@@ -83,10 +94,15 @@ public class LocationSettings extends LocationSettingsBase
getActivity().unregisterReceiver(mReceiver);
} catch (RuntimeException e) {
// Ignore exceptions caused by race condition
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Swallowing " + e);
+ }
+ }
+ if (mValidListener) {
+ mSwitchBar.removeOnSwitchChangeListener(this);
+ mValidListener = false;
}
super.onPause();
- mValidListener = false;
- mSwitch.setOnCheckedChangeListener(null);
}
private void addPreferencesSorted(List<Preference> prefs, PreferenceGroup container) {
@@ -103,7 +119,7 @@ public class LocationSettings extends LocationSettingsBase
}
private PreferenceScreen createPreferenceHierarchy() {
- final PreferenceActivity activity = (PreferenceActivity) getActivity();
+ final SettingsActivity activity = (SettingsActivity) getActivity();
PreferenceScreen root = getPreferenceScreen();
if (root != null) {
root.removeAll();
@@ -141,22 +157,6 @@ public class LocationSettings extends LocationSettingsBase
addLocationServices(activity, root);
- // Only show the master switch when we're not in multi-pane mode, and not being used as
- // Setup Wizard.
- if (activity.onIsHidingHeaders() || !activity.onIsMultiPane()) {
- final int padding = activity.getResources().getDimensionPixelSize(
- R.dimen.action_bar_switch_padding);
- mSwitch.setPaddingRelative(0, 0, padding, 0);
- activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
- ActionBar.DISPLAY_SHOW_CUSTOM);
- activity.getActionBar().setCustomView(mSwitch, new ActionBar.LayoutParams(
- ActionBar.LayoutParams.WRAP_CONTENT,
- ActionBar.LayoutParams.WRAP_CONTENT,
- Gravity.CENTER_VERTICAL | Gravity.END));
- }
-
- setHasOptionsMenu(true);
-
refreshLocationMode();
return root;
}
@@ -166,15 +166,12 @@ public class LocationSettings extends LocationSettingsBase
* category if there are no injected settings.
*
* Reloads the settings whenever receives
- * {@link SettingInjectorService#ACTION_INJECTED_SETTING_CHANGED}. As a safety measure,
- * also reloads on {@link LocationManager#MODE_CHANGED_ACTION} to ensure the settings are
- * up-to-date after mode changes even if an affected app doesn't send the setting changed
- * broadcast.
+ * {@link SettingInjectorService#ACTION_INJECTED_SETTING_CHANGED}.
*/
private void addLocationServices(Context context, PreferenceScreen root) {
PreferenceCategory categoryLocationServices =
(PreferenceCategory) root.findPreference(KEY_LOCATION_SERVICES);
- final SettingsInjector injector = new SettingsInjector(context);
+ injector = new SettingsInjector(context);
List<Preference> locationServices = injector.getInjectedSettings();
mReceiver = new BroadcastReceiver() {
@@ -189,7 +186,6 @@ public class LocationSettings extends LocationSettingsBase
IntentFilter filter = new IntentFilter();
filter.addAction(SettingInjectorService.ACTION_INJECTED_SETTING_CHANGED);
- filter.addAction(LocationManager.MODE_CHANGED_ACTION);
context.registerReceiver(mReceiver, filter);
if (locationServices.size() > 0) {
@@ -208,16 +204,16 @@ public class LocationSettings extends LocationSettingsBase
@Override
public void onModeChanged(int mode, boolean restricted) {
switch (mode) {
- case Settings.Secure.LOCATION_MODE_OFF:
+ case android.provider.Settings.Secure.LOCATION_MODE_OFF:
mLocationMode.setSummary(R.string.location_mode_location_off_title);
break;
- case Settings.Secure.LOCATION_MODE_SENSORS_ONLY:
+ case android.provider.Settings.Secure.LOCATION_MODE_SENSORS_ONLY:
mLocationMode.setSummary(R.string.location_mode_sensors_only_title);
break;
- case Settings.Secure.LOCATION_MODE_BATTERY_SAVING:
+ case android.provider.Settings.Secure.LOCATION_MODE_BATTERY_SAVING:
mLocationMode.setSummary(R.string.location_mode_battery_saving_title);
break;
- case Settings.Secure.LOCATION_MODE_HIGH_ACCURACY:
+ case android.provider.Settings.Secure.LOCATION_MODE_HIGH_ACCURACY:
mLocationMode.setSummary(R.string.location_mode_high_accuracy_title);
break;
default:
@@ -227,32 +223,37 @@ public class LocationSettings extends LocationSettingsBase
// Restricted user can't change the location mode, so disable the master switch. But in some
// corner cases, the location might still be enabled. In such case the master switch should
// be disabled but checked.
- boolean enabled = (mode != Settings.Secure.LOCATION_MODE_OFF);
- mSwitch.setEnabled(!restricted);
+ boolean enabled = (mode != android.provider.Settings.Secure.LOCATION_MODE_OFF);
+ // 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);
mLocationMode.setEnabled(enabled && !restricted);
mCategoryRecentLocationRequests.setEnabled(enabled);
if (enabled != mSwitch.isChecked()) {
// set listener to null so that that code below doesn't trigger onCheckedChanged()
if (mValidListener) {
- mSwitch.setOnCheckedChangeListener(null);
+ mSwitchBar.removeOnSwitchChangeListener(this);
}
mSwitch.setChecked(enabled);
if (mValidListener) {
- mSwitch.setOnCheckedChangeListener(this);
+ mSwitchBar.addOnSwitchChangeListener(this);
}
}
+ // As a safety measure, also reloads on location mode change to ensure the settings are
+ // up-to-date even if an affected app doesn't send the setting changed broadcast.
+ injector.reloadStatusMessages();
}
/**
* Listens to the state change of the location master switch.
*/
@Override
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ public void onSwitchChanged(Switch switchView, boolean isChecked) {
if (isChecked) {
- setLocationMode(Settings.Secure.LOCATION_MODE_HIGH_ACCURACY);
+ setLocationMode(android.provider.Settings.Secure.LOCATION_MODE_HIGH_ACCURACY);
} else {
- setLocationMode(Settings.Secure.LOCATION_MODE_OFF);
+ setLocationMode(android.provider.Settings.Secure.LOCATION_MODE_OFF);
}
}
}
diff --git a/src/com/android/settings/location/LocationSettingsBase.java b/src/com/android/settings/location/LocationSettingsBase.java
index 86c2ee5..69fbd5c 100644
--- a/src/com/android/settings/location/LocationSettingsBase.java
+++ b/src/com/android/settings/location/LocationSettingsBase.java
@@ -16,12 +16,11 @@
package com.android.settings.location;
-import android.app.LoaderManager.LoaderCallbacks;
+import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.CursorLoader;
import android.content.Intent;
-import android.content.Loader;
-import android.database.Cursor;
+import android.content.IntentFilter;
+import android.location.LocationManager;
import android.os.Bundle;
import android.os.UserManager;
import android.provider.Settings;
@@ -33,8 +32,7 @@ import com.android.settings.SettingsPreferenceFragment;
* A base class that listens to location settings change and modifies location
* settings.
*/
-public abstract class LocationSettingsBase extends SettingsPreferenceFragment
- implements LoaderCallbacks<Cursor> {
+public abstract class LocationSettingsBase extends SettingsPreferenceFragment {
private static final String TAG = "LocationSettingsBase";
/** Broadcast intent action when the location mode is about to change. */
private static final String MODE_CHANGING_ACTION =
@@ -42,8 +40,8 @@ public abstract class LocationSettingsBase extends SettingsPreferenceFragment
private static final String CURRENT_MODE_KEY = "CURRENT_MODE";
private static final String NEW_MODE_KEY = "NEW_MODE";
- private static final int LOADER_ID_LOCATION_MODE = 1;
private int mCurrentMode;
+ private BroadcastReceiver mReceiver;
/**
* Whether the fragment is actively running.
@@ -53,17 +51,33 @@ public abstract class LocationSettingsBase extends SettingsPreferenceFragment
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
- getLoaderManager().initLoader(LOADER_ID_LOCATION_MODE, null, this);
+ mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Received location mode change intent: " + intent);
+ }
+ refreshLocationMode();
+ }
+ };
}
@Override
public void onResume() {
super.onResume();
mActive = true;
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(LocationManager.MODE_CHANGED_ACTION);
+ getActivity().registerReceiver(mReceiver, filter);
}
@Override
public void onPause() {
+ try {
+ getActivity().unregisterReceiver(mReceiver);
+ } catch (RuntimeException e) {
+ // Ignore exceptions caused by race condition
+ }
super.onPause();
mActive = false;
}
@@ -103,29 +117,10 @@ public abstract class LocationSettingsBase extends SettingsPreferenceFragment
int mode = Settings.Secure.getInt(getContentResolver(), Settings.Secure.LOCATION_MODE,
Settings.Secure.LOCATION_MODE_OFF);
mCurrentMode = mode;
+ if (Log.isLoggable(TAG, Log.INFO)) {
+ Log.i(TAG, "Location mode has been changed");
+ }
onModeChanged(mode, isRestricted());
}
}
-
- @Override
- public Loader<Cursor> onCreateLoader(int id, Bundle args) {
- switch (id) {
- case LOADER_ID_LOCATION_MODE:
- return new CursorLoader(getActivity(), Settings.Secure.CONTENT_URI, null,
- "(" + Settings.System.NAME + "=?)",
- new String[] { Settings.Secure.LOCATION_MODE }, null);
- default:
- return null;
- }
- }
-
- @Override
- public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
- refreshLocationMode();
- }
-
- @Override
- public void onLoaderReset(Loader<Cursor> loader) {
- // Nothing to do here.
- }
}
diff --git a/src/com/android/settings/location/RecentLocationApps.java b/src/com/android/settings/location/RecentLocationApps.java
index 5708434..7e99725 100644
--- a/src/com/android/settings/location/RecentLocationApps.java
+++ b/src/com/android/settings/location/RecentLocationApps.java
@@ -16,20 +16,26 @@
package com.android.settings.location;
-import android.app.ActivityManager;
+import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.IPackageManager;
+import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Process;
+import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.UserManager;
import android.preference.Preference;
-import android.preference.PreferenceActivity;
import android.util.Log;
+import android.view.View;
+import android.widget.TextView;
import com.android.settings.R;
+import com.android.settings.SettingsActivity;
import com.android.settings.applications.InstalledAppDetails;
import java.util.ArrayList;
@@ -44,10 +50,10 @@ public class RecentLocationApps {
private static final int RECENT_TIME_INTERVAL_MILLIS = 15 * 60 * 1000;
- private final PreferenceActivity mActivity;
+ private final SettingsActivity mActivity;
private final PackageManager mPackageManager;
- public RecentLocationApps(PreferenceActivity activity) {
+ public RecentLocationApps(SettingsActivity activity) {
mActivity = activity;
mPackageManager = activity.getPackageManager();
}
@@ -71,12 +77,35 @@ public class RecentLocationApps {
}
}
- private Preference createRecentLocationEntry(
+ /**
+ * Subclass of {@link Preference} to intercept views and allow content description to be set on
+ * them for accessibility purposes.
+ */
+ private static class AccessiblePreference extends DimmableIconPreference {
+ public CharSequence mContentDescription;
+
+ public AccessiblePreference(Context context, CharSequence contentDescription) {
+ super(context);
+ mContentDescription = contentDescription;
+ }
+
+ @Override
+ protected void onBindView(View view) {
+ super.onBindView(view);
+ if (mContentDescription != null) {
+ final TextView titleView = (TextView) view.findViewById(android.R.id.title);
+ titleView.setContentDescription(mContentDescription);
+ }
+ }
+ }
+
+ private AccessiblePreference createRecentLocationEntry(
Drawable icon,
CharSequence label,
boolean isHighBattery,
+ CharSequence contentDescription,
Preference.OnPreferenceClickListener listener) {
- Preference pref = new Preference(mActivity);
+ AccessiblePreference pref = new AccessiblePreference(mActivity, contentDescription);
pref.setIcon(icon);
pref.setTitle(label);
if (isHighBattery) {
@@ -89,33 +118,37 @@ public class RecentLocationApps {
}
/**
- * Fills a list of applications which queried location recently within
- * specified time.
+ * Fills a list of applications which queried location recently within specified time.
*/
public List<Preference> getAppList() {
// Retrieve a location usage list from AppOps
AppOpsManager aoManager =
(AppOpsManager) mActivity.getSystemService(Context.APP_OPS_SERVICE);
- List<AppOpsManager.PackageOps> appOps = aoManager.getPackagesForOps(
- new int[] {
- AppOpsManager.OP_MONITOR_LOCATION,
- AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION,
- });
+ List<AppOpsManager.PackageOps> appOps = aoManager.getPackagesForOps(new int[] {
+ AppOpsManager.OP_MONITOR_LOCATION, AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION, });
// Process the AppOps list and generate a preference list.
ArrayList<Preference> prefs = new ArrayList<Preference>();
- long now = System.currentTimeMillis();
- for (AppOpsManager.PackageOps ops : appOps) {
+ final long now = System.currentTimeMillis();
+ final UserManager um = (UserManager) mActivity.getSystemService(Context.USER_SERVICE);
+ final List<UserHandle> profiles = um.getUserProfiles();
+
+ final int appOpsN = appOps.size();
+ for (int i = 0; i < appOpsN; ++i) {
+ AppOpsManager.PackageOps ops = appOps.get(i);
// Don't show the Android System in the list - it's not actionable for the user.
- // Also don't show apps belonging to background users.
+ // Also don't show apps belonging to background users except managed users.
+ String packageName = ops.getPackageName();
int uid = ops.getUid();
- boolean isAndroidOs = (uid == Process.SYSTEM_UID)
- && ANDROID_SYSTEM_PACKAGE_NAME.equals(ops.getPackageName());
- if (!isAndroidOs && ActivityManager.getCurrentUser() == UserHandle.getUserId(uid)) {
- Preference pref = getPreferenceFromOps(now, ops);
- if (pref != null) {
- prefs.add(pref);
- }
+ int userId = UserHandle.getUserId(uid);
+ boolean isAndroidOs =
+ (uid == Process.SYSTEM_UID) && ANDROID_SYSTEM_PACKAGE_NAME.equals(packageName);
+ if (isAndroidOs || !profiles.contains(new UserHandle(userId))) {
+ continue;
+ }
+ Preference preference = getPreferenceFromOps(um, now, ops);
+ if (preference != null) {
+ prefs.add(preference);
}
}
@@ -130,7 +163,8 @@ public class RecentLocationApps {
* When the PackageOps is fresh enough, this method returns a Preference pointing to the App
* Info page for that package.
*/
- private Preference getPreferenceFromOps(long now, AppOpsManager.PackageOps ops) {
+ private Preference getPreferenceFromOps(final UserManager um, long now,
+ AppOpsManager.PackageOps ops) {
String packageName = ops.getPackageName();
List<AppOpsManager.OpEntry> entries = ops.getOps();
boolean highBattery = false;
@@ -161,30 +195,34 @@ public class RecentLocationApps {
// The package is fresh enough, continue.
- Preference pref = null;
+ int uid = ops.getUid();
+ int userId = UserHandle.getUserId(uid);
+
+ AccessiblePreference preference = null;
try {
- ApplicationInfo appInfo = mPackageManager.getApplicationInfo(
- packageName, PackageManager.GET_META_DATA);
- // Multiple users can install the same package. Each user gets a different Uid for
- // the same package.
- //
- // Here we retrieve the Uid with package name, that will be the Uid for that package
- // associated with the current active user. If the Uid differs from the Uid in ops,
- // that means this entry belongs to another inactive user and we should ignore that.
- if (appInfo.uid == ops.getUid()) {
- pref = createRecentLocationEntry(
- mPackageManager.getApplicationIcon(appInfo),
- mPackageManager.getApplicationLabel(appInfo),
- highBattery,
- new PackageEntryClickedListener(packageName));
- } else if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "package " + packageName + " with Uid " + ops.getUid() +
- " belongs to another inactive account, ignored.");
- }
- } catch (PackageManager.NameNotFoundException e) {
- Log.wtf(TAG, "Package not found: " + packageName, e);
+ IPackageManager ipm = AppGlobals.getPackageManager();
+ ApplicationInfo appInfo =
+ ipm.getApplicationInfo(packageName, PackageManager.GET_META_DATA, userId);
+ if (appInfo == null) {
+ Log.w(TAG, "Null application info retrieved for package " + packageName
+ + ", userId " + userId);
+ return null;
+ }
+ Resources res = mActivity.getResources();
+
+ final UserHandle userHandle = new UserHandle(userId);
+ Drawable appIcon = mPackageManager.getApplicationIcon(appInfo);
+ Drawable icon = mPackageManager.getUserBadgedIcon(appIcon, userHandle);
+ CharSequence appLabel = mPackageManager.getApplicationLabel(appInfo);
+ CharSequence badgedAppLabel = mPackageManager.getUserBadgedLabel(appLabel, userHandle);
+ preference = createRecentLocationEntry(icon,
+ appLabel, highBattery, badgedAppLabel,
+ new PackageEntryClickedListener(packageName));
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error while retrieving application info for package " + packageName
+ + ", userId " + userId, e);
}
- return pref;
+ return preference;
}
}
diff --git a/src/com/android/settings/location/SettingsInjector.java b/src/com/android/settings/location/SettingsInjector.java
index b919080..edf67b8 100644
--- a/src/com/android/settings/location/SettingsInjector.java
+++ b/src/com/android/settings/location/SettingsInjector.java
@@ -244,18 +244,21 @@ class SettingsInjector {
}
/**
- * Adds an injected setting to the root with status "Loading...".
+ * Adds an injected setting to the root.
*/
private Preference addServiceSetting(List<Preference> prefs, InjectedSetting info) {
- Preference pref = new Preference(mContext);
+ Preference pref = new DimmableIconPreference(mContext);
pref.setTitle(info.title);
- pref.setSummary(R.string.location_loading_injected_setting);
+ pref.setSummary(null);
PackageManager pm = mContext.getPackageManager();
Drawable icon = pm.getDrawable(info.packageName, info.iconId, null);
pref.setIcon(icon);
+ // Activity to start if they click on the preference. Must start in new task to ensure
+ // that "android.settings.LOCATION_SOURCE_SETTINGS" brings user back to Settings > Location.
Intent settingIntent = new Intent();
settingIntent.setClassName(info.packageName, info.settingsActivity);
+ settingIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
pref.setIntent(settingIntent);
prefs.add(pref);
@@ -425,12 +428,11 @@ class SettingsInjector {
@Override
public void handleMessage(Message msg) {
Bundle bundle = msg.getData();
- String summary = bundle.getString(SettingInjectorService.SUMMARY_KEY);
boolean enabled = bundle.getBoolean(SettingInjectorService.ENABLED_KEY, true);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, setting + ": received " + msg + ", bundle: " + bundle);
}
- preference.setSummary(summary);
+ preference.setSummary(null);
preference.setEnabled(enabled);
mHandler.sendMessage(
mHandler.obtainMessage(WHAT_RECEIVED_STATUS, Setting.this));
diff --git a/src/com/android/settings/net/DataUsageMeteredSettings.java b/src/com/android/settings/net/DataUsageMeteredSettings.java
index fb000db..d567c7e 100644
--- a/src/com/android/settings/net/DataUsageMeteredSettings.java
+++ b/src/com/android/settings/net/DataUsageMeteredSettings.java
@@ -22,24 +22,31 @@ import static com.android.settings.DataUsageSummary.hasReadyMobileRadio;
import static com.android.settings.DataUsageSummary.hasWifiRadio;
import android.content.Context;
+import android.content.res.Resources;
import android.net.NetworkPolicy;
import android.net.NetworkPolicyManager;
import android.net.NetworkTemplate;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.os.Bundle;
-import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceCategory;
+import android.preference.SwitchPreference;
import android.telephony.TelephonyManager;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.search.Indexable;
+import com.android.settings.search.SearchIndexableRaw;
+
+import java.util.ArrayList;
+import java.util.List;
/**
* Panel to configure {@link NetworkPolicy#metered} for networks.
*/
-public class DataUsageMeteredSettings extends SettingsPreferenceFragment {
+public class DataUsageMeteredSettings extends SettingsPreferenceFragment implements Indexable {
private static final boolean SHOW_MOBILE_CATEGORY = false;
@@ -108,7 +115,7 @@ public class DataUsageMeteredSettings extends SettingsPreferenceFragment {
return pref;
}
- private class MeteredPreference extends CheckBoxPreference {
+ private class MeteredPreference extends SwitchPreference {
private final NetworkTemplate mTemplate;
private boolean mBinding;
@@ -141,4 +148,82 @@ public class DataUsageMeteredSettings extends SettingsPreferenceFragment {
}
}
}
+
+ /**
+ * For search
+ */
+ public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider() {
+ @Override
+ public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
+ final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>();
+ final Resources res = context.getResources();
+
+ // Add fragment title
+ SearchIndexableRaw data = new SearchIndexableRaw(context);
+ data.title = res.getString(R.string.data_usage_menu_metered);
+ data.screenTitle = res.getString(R.string.data_usage_menu_metered);
+ result.add(data);
+
+ // Body
+ data = new SearchIndexableRaw(context);
+ data.title = res.getString(R.string.data_usage_metered_body);
+ data.screenTitle = res.getString(R.string.data_usage_menu_metered);
+ result.add(data);
+
+ if (SHOW_MOBILE_CATEGORY && hasReadyMobileRadio(context)) {
+ // Mobile networks category
+ data = new SearchIndexableRaw(context);
+ data.title = res.getString(R.string.data_usage_metered_mobile);
+ data.screenTitle = res.getString(R.string.data_usage_menu_metered);
+ result.add(data);
+
+ final TelephonyManager tele = TelephonyManager.from(context);
+
+ data = new SearchIndexableRaw(context);
+ data.title = tele.getNetworkOperatorName();
+ data.screenTitle = res.getString(R.string.data_usage_menu_metered);
+ result.add(data);
+ }
+
+ // Wi-Fi networks category
+ data = new SearchIndexableRaw(context);
+ data.title = res.getString(R.string.data_usage_metered_wifi);
+ data.screenTitle = res.getString(R.string.data_usage_menu_metered);
+ result.add(data);
+
+ final WifiManager wifiManager =
+ (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ if (hasWifiRadio(context) && wifiManager.isWifiEnabled()) {
+ for (WifiConfiguration config : wifiManager.getConfiguredNetworks()) {
+ if (config.SSID != null) {
+ final String networkId = config.SSID;
+
+ data = new SearchIndexableRaw(context);
+ data.title = removeDoubleQuotes(networkId);
+ data.screenTitle = res.getString(R.string.data_usage_menu_metered);
+ result.add(data);
+ }
+ }
+ } else {
+ data = new SearchIndexableRaw(context);
+ data.title = res.getString(R.string.data_usage_metered_wifi_disabled);
+ data.screenTitle = res.getString(R.string.data_usage_menu_metered);
+ result.add(data);
+ }
+
+ return result;
+ }
+
+ @Override
+ public List<String> getNonIndexableKeys(Context context) {
+ final ArrayList<String> result = new ArrayList<String>();
+ if (!SHOW_MOBILE_CATEGORY || !hasReadyMobileRadio(context)) {
+ result.add("mobile");
+ }
+
+ return result;
+ }
+ };
+
}
diff --git a/src/com/android/settings/net/UidDetail.java b/src/com/android/settings/net/UidDetail.java
index fd44d47..0b14254 100644
--- a/src/com/android/settings/net/UidDetail.java
+++ b/src/com/android/settings/net/UidDetail.java
@@ -20,6 +20,8 @@ import android.graphics.drawable.Drawable;
public class UidDetail {
public CharSequence label;
+ public CharSequence contentDescription;
public CharSequence[] detailLabels;
+ public CharSequence[] detailContentDescriptions;
public Drawable icon;
}
diff --git a/src/com/android/settings/net/UidDetailProvider.java b/src/com/android/settings/net/UidDetailProvider.java
index aca3e5d..4b54137 100644
--- a/src/com/android/settings/net/UidDetailProvider.java
+++ b/src/com/android/settings/net/UidDetailProvider.java
@@ -16,8 +16,10 @@
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;
@@ -27,23 +29,36 @@ 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;
-import com.android.settings.users.UserUtils;
/**
* 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 -(2000 + 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) {
@@ -114,14 +129,22 @@ public class UidDetailProvider {
return detail;
}
+ final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+
// Handle keys that are actually user handles
- if (uid <= -2000) {
- final int userHandle = (-uid) - 2000;
- final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ if (isKeyForUser(uid)) {
+ final int userHandle = getUserIdForKey(uid);
final UserInfo info = um.getUserInfo(userHandle);
if (info != null) {
- detail.label = res.getString(R.string.running_process_item_user_label, info.name);
- detail.icon = UserUtils.getUserIcon(mContext, um, info, res);
+ if (info.isManagedProfile()) {
+ detail.label = res.getString(R.string.managed_user_title);
+ detail.icon = Resources.getSystem().getDrawable(
+ com.android.internal.R.drawable.ic_corp_icon);
+ } else {
+ detail.label = res.getString(R.string.running_process_item_user_label,
+ info.name);
+ detail.icon = Utils.getUserIcon(mContext, um, info);
+ }
return detail;
}
}
@@ -130,26 +153,43 @@ public class UidDetailProvider {
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 = pm.getApplicationInfo(packageNames[0], 0);
- detail.label = info.loadLabel(pm).toString();
- detail.icon = info.loadIcon(pm);
+ 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 = pm.getApplicationInfo(packageName, 0);
-
- detail.detailLabels[i] = appInfo.loadLabel(pm).toString();
- if (packageInfo.sharedUserLabel != 0) {
- detail.label = pm.getText(packageName, packageInfo.sharedUserLabel,
- packageInfo.applicationInfo).toString();
- detail.icon = appInfo.loadIcon(pm);
+ 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)) {
diff --git a/src/com/android/settings/nfc/AndroidBeam.java b/src/com/android/settings/nfc/AndroidBeam.java
index 158ca78..20201f4 100644
--- a/src/com/android/settings/nfc/AndroidBeam.java
+++ b/src/com/android/settings/nfc/AndroidBeam.java
@@ -17,89 +17,85 @@
package com.android.settings.nfc;
import android.app.ActionBar;
-import android.app.Activity;
import android.app.Fragment;
+import android.content.Context;
import android.nfc.NfcAdapter;
import android.os.Bundle;
-import android.os.Handler;
-import android.preference.PreferenceActivity;
-import android.view.Gravity;
+import android.os.UserManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.CompoundButton;
-import android.widget.ImageView;
import android.widget.Switch;
+
import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.widget.SwitchBar;
public class AndroidBeam extends Fragment
- implements CompoundButton.OnCheckedChangeListener {
+ implements SwitchBar.OnSwitchChangeListener {
private View mView;
private NfcAdapter mNfcAdapter;
- private Switch mActionBarSwitch;
+ private SwitchBar mSwitchBar;
private CharSequence mOldActivityTitle;
+ private boolean mBeamDisallowed;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- Activity activity = getActivity();
-
- mActionBarSwitch = new Switch(activity);
- if (activity instanceof PreferenceActivity) {
- final int padding = activity.getResources().getDimensionPixelSize(
- R.dimen.action_bar_switch_padding);
- mActionBarSwitch.setPaddingRelative(0, 0, padding, 0);
- activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
- ActionBar.DISPLAY_SHOW_CUSTOM);
- activity.getActionBar().setCustomView(mActionBarSwitch, new ActionBar.LayoutParams(
- ActionBar.LayoutParams.WRAP_CONTENT,
- ActionBar.LayoutParams.WRAP_CONTENT,
- Gravity.CENTER_VERTICAL | Gravity.END));
- mOldActivityTitle = activity.getActionBar().getTitle();
- activity.getActionBar().setTitle(R.string.android_beam_settings_title);
- }
+ final ActionBar actionBar = getActivity().getActionBar();
- mActionBarSwitch.setOnCheckedChangeListener(this);
+ mOldActivityTitle = actionBar.getTitle();
+ actionBar.setTitle(R.string.android_beam_settings_title);
mNfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
- mActionBarSwitch.setChecked(mNfcAdapter.isNdefPushEnabled());
+ mBeamDisallowed = ((UserManager) getActivity().getSystemService(Context.USER_SERVICE))
+ .hasUserRestriction(UserManager.DISALLOW_OUTGOING_BEAM);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mView = inflater.inflate(R.layout.android_beam, container, false);
- initView(mView);
+
return mView;
}
@Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ SettingsActivity activity = (SettingsActivity) getActivity();
+
+ mSwitchBar = activity.getSwitchBar();
+ mSwitchBar.setChecked(!mBeamDisallowed && mNfcAdapter.isNdefPushEnabled());
+ mSwitchBar.addOnSwitchChangeListener(this);
+ mSwitchBar.setEnabled(!mBeamDisallowed);
+ mSwitchBar.show();
+ }
+
+ @Override
public void onDestroyView() {
super.onDestroyView();
- getActivity().getActionBar().setCustomView(null);
if (mOldActivityTitle != null) {
getActivity().getActionBar().setTitle(mOldActivityTitle);
}
- }
-
- private void initView(View view) {
- mActionBarSwitch.setOnCheckedChangeListener(this);
- mActionBarSwitch.setChecked(mNfcAdapter.isNdefPushEnabled());
+ mSwitchBar.removeOnSwitchChangeListener(this);
+ mSwitchBar.hide();
}
@Override
- public void onCheckedChanged(CompoundButton buttonView, boolean desiredState) {
+ public void onSwitchChanged(Switch switchView, boolean desiredState) {
boolean success = false;
- mActionBarSwitch.setEnabled(false);
+ mSwitchBar.setEnabled(false);
if (desiredState) {
success = mNfcAdapter.enableNdefPush();
} else {
success = mNfcAdapter.disableNdefPush();
}
if (success) {
- mActionBarSwitch.setChecked(desiredState);
+ mSwitchBar.setChecked(desiredState);
}
- mActionBarSwitch.setEnabled(true);
+ mSwitchBar.setEnabled(true);
}
}
diff --git a/src/com/android/settings/nfc/NfcEnabler.java b/src/com/android/settings/nfc/NfcEnabler.java
index 018b8ae..ae61b13 100644
--- a/src/com/android/settings/nfc/NfcEnabler.java
+++ b/src/com/android/settings/nfc/NfcEnabler.java
@@ -21,9 +21,10 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.nfc.NfcAdapter;
-import android.preference.CheckBoxPreference;
+import android.os.UserManager;
import android.preference.Preference;
import android.preference.PreferenceScreen;
+import android.preference.SwitchPreference;
import com.android.settings.R;
@@ -34,10 +35,11 @@ import com.android.settings.R;
*/
public class NfcEnabler implements Preference.OnPreferenceChangeListener {
private final Context mContext;
- private final CheckBoxPreference mCheckbox;
+ private final SwitchPreference mSwitch;
private final PreferenceScreen mAndroidBeam;
private final NfcAdapter mNfcAdapter;
private final IntentFilter mIntentFilter;
+ private boolean mBeamDisallowed;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
@@ -50,20 +52,25 @@ public class NfcEnabler implements Preference.OnPreferenceChangeListener {
}
};
- public NfcEnabler(Context context, CheckBoxPreference checkBoxPreference,
+ public NfcEnabler(Context context, SwitchPreference switchPreference,
PreferenceScreen androidBeam) {
mContext = context;
- mCheckbox = checkBoxPreference;
+ mSwitch = switchPreference;
mAndroidBeam = androidBeam;
mNfcAdapter = NfcAdapter.getDefaultAdapter(context);
+ mBeamDisallowed = ((UserManager) mContext.getSystemService(Context.USER_SERVICE))
+ .hasUserRestriction(UserManager.DISALLOW_OUTGOING_BEAM);
if (mNfcAdapter == null) {
// NFC is not supported
- mCheckbox.setEnabled(false);
+ mSwitch.setEnabled(false);
mAndroidBeam.setEnabled(false);
mIntentFilter = null;
return;
}
+ if (mBeamDisallowed) {
+ mAndroidBeam.setEnabled(false);
+ }
mIntentFilter = new IntentFilter(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED);
}
@@ -73,7 +80,7 @@ public class NfcEnabler implements Preference.OnPreferenceChangeListener {
}
handleNfcStateChanged(mNfcAdapter.getAdapterState());
mContext.registerReceiver(mReceiver, mIntentFilter);
- mCheckbox.setOnPreferenceChangeListener(this);
+ mSwitch.setOnPreferenceChangeListener(this);
}
public void pause() {
@@ -81,14 +88,14 @@ public class NfcEnabler implements Preference.OnPreferenceChangeListener {
return;
}
mContext.unregisterReceiver(mReceiver);
- mCheckbox.setOnPreferenceChangeListener(null);
+ mSwitch.setOnPreferenceChangeListener(null);
}
public boolean onPreferenceChange(Preference preference, Object value) {
// Turn NFC on/off
final boolean desiredState = (Boolean) value;
- mCheckbox.setEnabled(false);
+ mSwitch.setEnabled(false);
if (desiredState) {
mNfcAdapter.enable();
@@ -102,29 +109,29 @@ public class NfcEnabler implements Preference.OnPreferenceChangeListener {
private void handleNfcStateChanged(int newState) {
switch (newState) {
case NfcAdapter.STATE_OFF:
- mCheckbox.setChecked(false);
- mCheckbox.setEnabled(true);
+ mSwitch.setChecked(false);
+ mSwitch.setEnabled(true);
mAndroidBeam.setEnabled(false);
mAndroidBeam.setSummary(R.string.android_beam_disabled_summary);
break;
case NfcAdapter.STATE_ON:
- mCheckbox.setChecked(true);
- mCheckbox.setEnabled(true);
- mAndroidBeam.setEnabled(true);
- if (mNfcAdapter.isNdefPushEnabled()) {
+ mSwitch.setChecked(true);
+ mSwitch.setEnabled(true);
+ mAndroidBeam.setEnabled(!mBeamDisallowed);
+ if (mNfcAdapter.isNdefPushEnabled() && !mBeamDisallowed) {
mAndroidBeam.setSummary(R.string.android_beam_on_summary);
} else {
mAndroidBeam.setSummary(R.string.android_beam_off_summary);
}
break;
case NfcAdapter.STATE_TURNING_ON:
- mCheckbox.setChecked(true);
- mCheckbox.setEnabled(false);
+ mSwitch.setChecked(true);
+ mSwitch.setEnabled(false);
mAndroidBeam.setEnabled(false);
break;
case NfcAdapter.STATE_TURNING_OFF:
- mCheckbox.setChecked(false);
- mCheckbox.setEnabled(false);
+ mSwitch.setChecked(false);
+ mSwitch.setEnabled(false);
mAndroidBeam.setEnabled(false);
break;
}
diff --git a/src/com/android/settings/nfc/PaymentBackend.java b/src/com/android/settings/nfc/PaymentBackend.java
index f84bc74..25572a7 100644
--- a/src/com/android/settings/nfc/PaymentBackend.java
+++ b/src/com/android/settings/nfc/PaymentBackend.java
@@ -24,6 +24,7 @@ import android.nfc.NfcAdapter;
import android.nfc.cardemulation.ApduServiceInfo;
import android.nfc.cardemulation.CardEmulation;
import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
import java.util.ArrayList;
import java.util.List;
@@ -74,6 +75,20 @@ public class PaymentBackend {
return appInfos;
}
+ boolean isForegroundMode() {
+ try {
+ return Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.NFC_PAYMENT_FOREGROUND) != 0;
+ } catch (SettingNotFoundException e) {
+ return false;
+ }
+ }
+
+ void setForegroundMode(boolean foreground) {
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.NFC_PAYMENT_FOREGROUND, foreground ? 1 : 0) ;
+ }
+
ComponentName getDefaultPaymentApp() {
String componentString = Settings.Secure.getString(mContext.getContentResolver(),
Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT);
diff --git a/src/com/android/settings/nfc/PaymentDefaultDialog.java b/src/com/android/settings/nfc/PaymentDefaultDialog.java
index 61c6fdb..33ac947 100644
--- a/src/com/android/settings/nfc/PaymentDefaultDialog.java
+++ b/src/com/android/settings/nfc/PaymentDefaultDialog.java
@@ -19,14 +19,7 @@ package com.android.settings.nfc;
import android.content.ComponentName;
import android.content.DialogInterface;
import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.nfc.cardemulation.ApduServiceInfo;
import android.nfc.cardemulation.CardEmulation;
-import android.nfc.cardemulation.HostApduService;
-import android.nfc.cardemulation.OffHostApduService;
import android.os.Bundle;
import android.util.Log;
@@ -35,15 +28,13 @@ import com.android.internal.app.AlertController;
import com.android.settings.R;
import com.android.settings.nfc.PaymentBackend.PaymentAppInfo;
-import java.io.IOException;
import java.util.List;
-import org.xmlpull.v1.XmlPullParserException;
-
public final class PaymentDefaultDialog extends AlertActivity implements
DialogInterface.OnClickListener {
public static final String TAG = "PaymentDefaultDialog";
+ private static final int PAYMENT_APP_MAX_CAPTION_LENGTH = 40;
private PaymentBackend mBackend;
private ComponentName mNewDefault;
@@ -119,12 +110,14 @@ public final class PaymentDefaultDialog extends AlertActivity implements
p.mTitle = getString(R.string.nfc_payment_set_default_label);
if (defaultPaymentApp == null) {
String formatString = getString(R.string.nfc_payment_set_default);
- String msg = String.format(formatString, requestedPaymentApp.caption);
+ String msg = String.format(formatString,
+ sanitizePaymentAppCaption(requestedPaymentApp.caption.toString()));
p.mMessage = msg;
} else {
String formatString = getString(R.string.nfc_payment_set_default_instead_of);
- String msg = String.format(formatString, requestedPaymentApp.caption,
- defaultPaymentApp.caption);
+ String msg = String.format(formatString,
+ sanitizePaymentAppCaption(requestedPaymentApp.caption.toString()),
+ sanitizePaymentAppCaption(defaultPaymentApp.caption.toString()));
p.mMessage = msg;
}
p.mPositiveButtonText = getString(R.string.yes);
@@ -136,4 +129,15 @@ public final class PaymentDefaultDialog extends AlertActivity implements
return true;
}
+ private String sanitizePaymentAppCaption(String input) {
+ String sanitizedString = input.replace('\n', ' ').replace('\r', ' ').trim();
+
+
+ if (sanitizedString.length() > PAYMENT_APP_MAX_CAPTION_LENGTH) {
+ return sanitizedString.substring(0, PAYMENT_APP_MAX_CAPTION_LENGTH);
+ }
+
+ return sanitizedString;
+ }
+
}
diff --git a/src/com/android/settings/nfc/PaymentSettings.java b/src/com/android/settings/nfc/PaymentSettings.java
index 7548c50..df4e396 100644
--- a/src/com/android/settings/nfc/PaymentSettings.java
+++ b/src/com/android/settings/nfc/PaymentSettings.java
@@ -22,7 +22,9 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.preference.CheckBoxPreference;
import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import android.provider.Settings;
@@ -48,7 +50,7 @@ import com.android.settings.nfc.PaymentBackend.PaymentAppInfo;
import java.util.List;
public class PaymentSettings extends SettingsPreferenceFragment implements
- OnClickListener {
+ OnClickListener, OnPreferenceChangeListener {
public static final String TAG = "PaymentSettings";
private LayoutInflater mInflater;
private PaymentBackend mPaymentBackend;
@@ -67,6 +69,7 @@ public class PaymentSettings extends SettingsPreferenceFragment implements
public void refresh() {
PreferenceManager manager = getPreferenceManager();
PreferenceScreen screen = manager.createPreferenceScreen(getActivity());
+
// Get all payment services
List<PaymentAppInfo> appInfos = mPaymentBackend.getPaymentAppInfos();
if (appInfos != null && appInfos.size() > 0) {
@@ -92,6 +95,13 @@ public class PaymentSettings extends SettingsPreferenceFragment implements
emptyImage.setVisibility(View.VISIBLE);
getListView().setVisibility(View.GONE);
} else {
+ CheckBoxPreference foreground = new CheckBoxPreference(getActivity());
+ boolean foregroundMode = mPaymentBackend.isForegroundMode();
+ foreground.setPersistent(false);
+ foreground.setTitle(getString(R.string.nfc_payment_favor_foreground));
+ foreground.setChecked(foregroundMode);
+ foreground.setOnPreferenceChangeListener(this);
+ screen.addPreference(foreground);
emptyText.setVisibility(View.GONE);
learnMore.setVisibility(View.GONE);
emptyImage.setVisibility(View.GONE);
@@ -207,14 +217,25 @@ public class PaymentSettings extends SettingsPreferenceFragment implements
protected void onBindView(View view) {
super.onBindView(view);
- view.setOnClickListener(listener);
- view.setTag(appInfo);
-
RadioButton radioButton = (RadioButton) view.findViewById(android.R.id.button1);
radioButton.setChecked(appInfo.isDefault);
+ radioButton.setOnClickListener(listener);
+ radioButton.setTag(appInfo);
ImageView banner = (ImageView) view.findViewById(R.id.banner);
banner.setImageDrawable(appInfo.banner);
+ banner.setOnClickListener(listener);
+ banner.setTag(appInfo);
+ }
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if (preference instanceof CheckBoxPreference) {
+ mPaymentBackend.setForegroundMode(((Boolean) newValue).booleanValue());
+ return true;
+ } else {
+ return false;
}
}
}
diff --git a/src/com/android/settings/notification/AppNotificationSettings.java b/src/com/android/settings/notification/AppNotificationSettings.java
new file mode 100644
index 0000000..0eeefa9
--- /dev/null
+++ b/src/com/android/settings/notification/AppNotificationSettings.java
@@ -0,0 +1,230 @@
+/*
+ * 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.notification;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.SwitchPreference;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.Utils;
+import com.android.settings.notification.NotificationAppList.AppRow;
+import com.android.settings.notification.NotificationAppList.Backend;
+
+/** These settings are per app, so should not be returned in global search results. */
+public class AppNotificationSettings extends SettingsPreferenceFragment {
+ private static final String TAG = "AppNotificationSettings";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private static final String KEY_BLOCK = "block";
+ private static final String KEY_PRIORITY = "priority";
+ private static final String KEY_SENSITIVE = "sensitive";
+
+ static final String EXTRA_HAS_SETTINGS_INTENT = "has_settings_intent";
+ static final String EXTRA_SETTINGS_INTENT = "settings_intent";
+
+ private final Backend mBackend = new Backend();
+
+ private Context mContext;
+ private SwitchPreference mBlock;
+ private SwitchPreference mPriority;
+ private SwitchPreference mSensitive;
+ private AppRow mAppRow;
+ private boolean mCreated;
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ if (DEBUG) Log.d(TAG, "onActivityCreated mCreated=" + mCreated);
+ if (mCreated) {
+ Log.w(TAG, "onActivityCreated: ignoring duplicate call");
+ return;
+ }
+ mCreated = true;
+ if (mAppRow == null) return;
+ final View content = getActivity().findViewById(R.id.main_content);
+ final ViewGroup contentParent = (ViewGroup) content.getParent();
+ final View bar = getActivity().getLayoutInflater().inflate(R.layout.app_notification_header,
+ contentParent, false);
+
+ final ImageView appIcon = (ImageView) bar.findViewById(R.id.app_icon);
+ appIcon.setImageDrawable(mAppRow.icon);
+
+ final TextView appName = (TextView) bar.findViewById(R.id.app_name);
+ appName.setText(mAppRow.label);
+
+ final View appSettings = bar.findViewById(R.id.app_settings);
+ if (mAppRow.settingsIntent == null) {
+ appSettings.setVisibility(View.GONE);
+ } else {
+ appSettings.setClickable(true);
+ appSettings.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mContext.startActivity(mAppRow.settingsIntent);
+ }
+ });
+ }
+ contentParent.addView(bar, 0);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mContext = getActivity();
+ Intent intent = getActivity().getIntent();
+ if (DEBUG) Log.d(TAG, "onCreate getIntent()=" + intent);
+ if (intent == null) {
+ Log.w(TAG, "No intent");
+ toastAndFinish();
+ return;
+ }
+
+ final int uid = intent.getIntExtra(Settings.EXTRA_APP_UID, -1);
+ final String pkg = intent.getStringExtra(Settings.EXTRA_APP_PACKAGE);
+ if (uid == -1 || TextUtils.isEmpty(pkg)) {
+ Log.w(TAG, "Missing extras: " + Settings.EXTRA_APP_PACKAGE + " was " + pkg + ", "
+ + Settings.EXTRA_APP_UID + " was " + uid);
+ toastAndFinish();
+ return;
+ }
+
+ if (DEBUG) Log.d(TAG, "Load details for pkg=" + pkg + " uid=" + uid);
+ final PackageManager pm = getPackageManager();
+ final PackageInfo info = findPackageInfo(pm, pkg, uid);
+ if (info == null) {
+ Log.w(TAG, "Failed to find package info: " + Settings.EXTRA_APP_PACKAGE + " was " + pkg
+ + ", " + Settings.EXTRA_APP_UID + " was " + uid);
+ toastAndFinish();
+ return;
+ }
+
+ addPreferencesFromResource(R.xml.app_notification_settings);
+ mBlock = (SwitchPreference) findPreference(KEY_BLOCK);
+ mPriority = (SwitchPreference) findPreference(KEY_PRIORITY);
+ mSensitive = (SwitchPreference) findPreference(KEY_SENSITIVE);
+
+ final boolean secure = new LockPatternUtils(getActivity()).isSecure();
+ final boolean enabled = getLockscreenNotificationsEnabled();
+ final boolean allowPrivate = getLockscreenAllowPrivateNotifications();
+ if (!secure || !enabled || !allowPrivate) {
+ getPreferenceScreen().removePreference(mSensitive);
+ }
+
+ mAppRow = NotificationAppList.loadAppRow(pm, info.applicationInfo, mBackend);
+ if (intent.hasExtra(EXTRA_HAS_SETTINGS_INTENT)) {
+ // use settings intent from extra
+ if (intent.getBooleanExtra(EXTRA_HAS_SETTINGS_INTENT, false)) {
+ mAppRow.settingsIntent = intent.getParcelableExtra(EXTRA_SETTINGS_INTENT);
+ }
+ } else {
+ // load settings intent
+ ArrayMap<String, AppRow> rows = new ArrayMap<String, AppRow>();
+ rows.put(mAppRow.pkg, mAppRow);
+ NotificationAppList.collectConfigActivities(getPackageManager(), rows);
+ }
+
+ mBlock.setChecked(mAppRow.banned);
+ mPriority.setChecked(mAppRow.priority);
+ if (mSensitive != null) {
+ mSensitive.setChecked(mAppRow.sensitive);
+ }
+
+ mBlock.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ final boolean block = (Boolean) newValue;
+ return mBackend.setNotificationsBanned(pkg, uid, block);
+ }
+ });
+
+ mPriority.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ final boolean priority = (Boolean) newValue;
+ return mBackend.setHighPriority(pkg, uid, priority);
+ }
+ });
+
+ if (mSensitive != null) {
+ mSensitive.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ final boolean sensitive = (Boolean) newValue;
+ return mBackend.setSensitive(pkg, uid, sensitive);
+ }
+ });
+ }
+
+ // Users cannot block notifications from system/signature packages
+ if (Utils.isSystemPackage(pm, info)) {
+ getPreferenceScreen().removePreference(mBlock);
+ mPriority.setDependency(null); // don't have it depend on a preference that's gone
+ }
+ }
+
+ 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;
+ }
+
+ private void toastAndFinish() {
+ Toast.makeText(mContext, R.string.app_not_found_dlg_text, Toast.LENGTH_SHORT).show();
+ getActivity().finish();
+ }
+
+ private static PackageInfo findPackageInfo(PackageManager pm, String pkg, int uid) {
+ final String[] packages = pm.getPackagesForUid(uid);
+ if (packages != null && pkg != null) {
+ final int N = packages.length;
+ for (int i = 0; i < N; i++) {
+ final String p = packages[i];
+ if (pkg.equals(p)) {
+ try {
+ return pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES);
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Failed to load package " + pkg, e);
+ }
+ }
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/com/android/settings/notification/ConditionProviderSettings.java b/src/com/android/settings/notification/ConditionProviderSettings.java
new file mode 100644
index 0000000..259e53c
--- /dev/null
+++ b/src/com/android/settings/notification/ConditionProviderSettings.java
@@ -0,0 +1,55 @@
+/*
+ * 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.notification;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.provider.Settings;
+import android.service.notification.ConditionProviderService;
+
+import com.android.settings.R;
+
+public class ConditionProviderSettings extends ManagedServiceSettings {
+ private static final String TAG = ConditionProviderSettings.class.getSimpleName();
+ private static final Config CONFIG = getConditionProviderConfig();
+
+ private static Config getConditionProviderConfig() {
+ final Config c = new Config();
+ c.tag = TAG;
+ c.setting = Settings.Secure.ENABLED_CONDITION_PROVIDERS;
+ c.intentAction = ConditionProviderService.SERVICE_INTERFACE;
+ c.permission = android.Manifest.permission.BIND_CONDITION_PROVIDER_SERVICE;
+ c.noun = "condition provider";
+ c.warningDialogTitle = R.string.condition_provider_security_warning_title;
+ c.warningDialogSummary = R.string.condition_provider_security_warning_summary;
+ c.emptyText = R.string.no_condition_providers;
+ return c;
+ }
+
+ @Override
+ protected Config getConfig() {
+ return CONFIG;
+ }
+
+ public static int getProviderCount(PackageManager pm) {
+ return getServicesCount(CONFIG, pm);
+ }
+
+ public static int getEnabledProviderCount(Context context) {
+ return getEnabledServicesCount(CONFIG, context);
+ }
+}
diff --git a/src/com/android/settings/notification/DropDownPreference.java b/src/com/android/settings/notification/DropDownPreference.java
new file mode 100644
index 0000000..1d1b366
--- /dev/null
+++ b/src/com/android/settings/notification/DropDownPreference.java
@@ -0,0 +1,131 @@
+/*
+ * 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.notification;
+
+import android.content.Context;
+import android.preference.Preference;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Spinner;
+import android.widget.AdapterView.OnItemSelectedListener;
+
+import java.util.ArrayList;
+
+public class DropDownPreference extends Preference {
+ private final Context mContext;
+ private final ArrayAdapter<String> mAdapter;
+ private final Spinner mSpinner;
+ private final ArrayList<Object> mValues = new ArrayList<Object>();
+
+ private Callback mCallback;
+
+ public DropDownPreference(Context context) {
+ this(context, null);
+ }
+
+ public DropDownPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mContext = context;
+ mAdapter = new ArrayAdapter<String>(mContext,
+ android.R.layout.simple_spinner_dropdown_item);
+
+ mSpinner = new Spinner(mContext);
+
+ mSpinner.setVisibility(View.INVISIBLE);
+ mSpinner.setAdapter(mAdapter);
+ mSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View v, int position, long id) {
+ setSelectedItem(position);
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ // noop
+ }
+ });
+ setPersistent(false);
+ setOnPreferenceClickListener(new OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ mSpinner.performClick();
+ return true;
+ }
+ });
+ }
+
+ public void setDropDownWidth(int dimenResId) {
+ mSpinner.setDropDownWidth(mContext.getResources().getDimensionPixelSize(dimenResId));
+ }
+
+ public void setCallback(Callback callback) {
+ mCallback = callback;
+ }
+
+ public void setSelectedItem(int position) {
+ final Object value = mValues.get(position);
+ if (mCallback != null && !mCallback.onItemSelected(position, value)) {
+ return;
+ }
+ mSpinner.setSelection(position);
+ setSummary(mAdapter.getItem(position));
+ final boolean disableDependents = value == null;
+ notifyDependencyChange(disableDependents);
+ }
+
+ public void setSelectedValue(Object value) {
+ final int i = mValues.indexOf(value);
+ if (i > -1) {
+ setSelectedItem(i);
+ }
+ }
+
+ public void addItem(int captionResid, Object value) {
+ addItem(mContext.getResources().getString(captionResid), value);
+ }
+
+ public void addItem(String caption, Object value) {
+ mAdapter.add(caption);
+ mValues.add(value);
+ }
+
+ public void clearItems(){
+ mAdapter.clear();
+ mValues.clear();
+ }
+
+ @Override
+ protected void onBindView(View view) {
+ super.onBindView(view);
+ if (view.equals(mSpinner.getParent())) return;
+ if (mSpinner.getParent() != null) {
+ ((ViewGroup)mSpinner.getParent()).removeView(mSpinner);
+ }
+ final ViewGroup vg = (ViewGroup)view;
+ vg.addView(mSpinner, 0);
+ final ViewGroup.LayoutParams lp = mSpinner.getLayoutParams();
+ lp.width = 0;
+ mSpinner.setLayoutParams(lp);
+ }
+
+ public interface Callback {
+ boolean onItemSelected(int pos, Object value);
+ }
+}
diff --git a/src/com/android/settings/NotificationAccessSettings.java b/src/com/android/settings/notification/ManagedServiceSettings.java
index 07d4353..7be644e 100644
--- a/src/com/android/settings/NotificationAccessSettings.java
+++ b/src/com/android/settings/notification/ManagedServiceSettings.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 The Android Open Source Project
+ * 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.
@@ -14,56 +14,59 @@
* limitations under the License.
*/
-package com.android.settings;
+package com.android.settings.notification;
import android.app.ActivityManager;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
+import android.app.ListFragment;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.ContentResolver;
+import android.content.Context;
import android.content.DialogInterface;
+import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.database.ContentObserver;
import android.net.Uri;
-import android.os.Handler;
-import android.service.notification.NotificationListenerService;
-import android.util.Slog;
-import android.widget.ArrayAdapter;
-
-import android.app.ListFragment;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.os.Bundle;
+import android.os.Handler;
import android.provider.Settings;
+import android.util.Slog;
import android.view.LayoutInflater;
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 java.util.HashSet;
import java.util.List;
-public class NotificationAccessSettings extends ListFragment {
- static final String TAG = NotificationAccessSettings.class.getSimpleName();
+public abstract class ManagedServiceSettings extends ListFragment {
private static final boolean SHOW_PACKAGE_NAME = false;
+ private final Config mConfig;
private PackageManager mPM;
private ContentResolver mCR;
- private final HashSet<ComponentName> mEnabledListeners = new HashSet<ComponentName>();
- private ListenerListAdapter mList;
+ private final HashSet<ComponentName> mEnabledServices = new HashSet<ComponentName>();
+ private ServiceListAdapter mListAdapter;
+
+ abstract protected Config getConfig();
- private final Uri ENABLED_NOTIFICATION_LISTENERS_URI
- = Settings.Secure.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
+ public ManagedServiceSettings() {
+ mConfig = getConfig();
+ }
private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) {
@Override
@@ -79,18 +82,18 @@ public class NotificationAccessSettings extends ListFragment {
}
};
- public class ListenerWarningDialogFragment extends DialogFragment {
+ public class ScaryWarningDialogFragment extends DialogFragment {
static final String KEY_COMPONENT = "c";
static final String KEY_LABEL = "l";
- public ListenerWarningDialogFragment setListenerInfo(ComponentName cn, String label) {
+ public ScaryWarningDialogFragment setServiceInfo(ComponentName cn, String label) {
Bundle args = new Bundle();
args.putString(KEY_COMPONENT, cn.flattenToString());
args.putString(KEY_LABEL, label);
setArguments(args);
-
return this;
}
+
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -98,20 +101,17 @@ public class NotificationAccessSettings extends ListFragment {
final String label = args.getString(KEY_LABEL);
final ComponentName cn = ComponentName.unflattenFromString(args.getString(KEY_COMPONENT));
- final String title = getResources().getString(
- R.string.notification_listener_security_warning_title, label);
- final String summary = getResources().getString(
- R.string.notification_listener_security_warning_summary, label);
+ final String title = getResources().getString(mConfig.warningDialogTitle, label);
+ final String summary = getResources().getString(mConfig.warningDialogSummary, label);
return new AlertDialog.Builder(getActivity())
.setMessage(summary)
.setTitle(title)
- .setIconAttribute(android.R.attr.alertDialogIcon)
.setCancelable(true)
.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
- mEnabledListeners.add(cn);
- saveEnabledListeners();
+ mEnabledServices.add(cn);
+ saveEnabledServices();
}
})
.setNegativeButton(android.R.string.cancel,
@@ -130,13 +130,16 @@ public class NotificationAccessSettings extends ListFragment {
mPM = getActivity().getPackageManager();
mCR = getActivity().getContentResolver();
- mList = new ListenerListAdapter(getActivity());
+ mListAdapter = new ServiceListAdapter(getActivity());
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
- return inflater.inflate(R.layout.notification_access_settings, container, false);
+ View v = inflater.inflate(R.layout.managed_service_settings, container, false);
+ TextView empty = (TextView) v.findViewById(android.R.id.empty);
+ empty.setText(mConfig.emptyText);
+ return v;
}
@Override
@@ -153,7 +156,8 @@ public class NotificationAccessSettings extends ListFragment {
filter.addDataScheme("package");
getActivity().registerReceiver(mPackageReceiver, filter);
- mCR.registerContentObserver(ENABLED_NOTIFICATION_LISTENERS_URI, false, mSettingsObserver);
+ mCR.registerContentObserver(Settings.Secure.getUriFor(mConfig.setting),
+ false, mSettingsObserver);
}
@Override
@@ -164,24 +168,23 @@ public class NotificationAccessSettings extends ListFragment {
mCR.unregisterContentObserver(mSettingsObserver);
}
- void loadEnabledListeners() {
- mEnabledListeners.clear();
- final String flat = Settings.Secure.getString(mCR,
- Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
+ private void loadEnabledServices() {
+ mEnabledServices.clear();
+ final String flat = Settings.Secure.getString(mCR, mConfig.setting);
if (flat != null && !"".equals(flat)) {
final String[] names = flat.split(":");
for (int i = 0; i < names.length; i++) {
final ComponentName cn = ComponentName.unflattenFromString(names[i]);
if (cn != null) {
- mEnabledListeners.add(cn);
+ mEnabledServices.add(cn);
}
}
}
}
- void saveEnabledListeners() {
+ private void saveEnabledServices() {
StringBuilder sb = null;
- for (ComponentName cn : mEnabledListeners) {
+ for (ComponentName cn : mEnabledServices) {
if (sb == null) {
sb = new StringBuilder();
} else {
@@ -190,32 +193,39 @@ public class NotificationAccessSettings extends ListFragment {
sb.append(cn.flattenToString());
}
Settings.Secure.putString(mCR,
- Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
+ mConfig.setting,
sb != null ? sb.toString() : "");
}
- void updateList() {
- loadEnabledListeners();
+ private void updateList() {
+ loadEnabledServices();
- getListeners(mList, mPM);
- mList.sort(new PackageItemInfo.DisplayNameComparator(mPM));
+ getServices(mConfig, mListAdapter, mPM);
+ mListAdapter.sort(new PackageItemInfo.DisplayNameComparator(mPM));
- getListView().setAdapter(mList);
+ getListView().setAdapter(mListAdapter);
}
- static int getListenersCount(PackageManager pm) {
- return getListeners(null, pm);
+ protected static int getEnabledServicesCount(Config config, Context context) {
+ final String flat = Settings.Secure.getString(context.getContentResolver(), config.setting);
+ if (flat == null || "".equals(flat)) return 0;
+ final String[] components = flat.split(":");
+ return components.length;
}
- private static int getListeners(ArrayAdapter<ServiceInfo> adapter, PackageManager pm) {
- int listeners = 0;
+ protected static int getServicesCount(Config c, PackageManager pm) {
+ return getServices(c, null, pm);
+ }
+
+ private static int getServices(Config c, ArrayAdapter<ServiceInfo> adapter, PackageManager pm) {
+ int services = 0;
if (adapter != null) {
adapter.clear();
}
final int user = ActivityManager.getCurrentUser();
List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser(
- new Intent(NotificationListenerService.SERVICE_INTERFACE),
+ new Intent(c.intentAction),
PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
user);
@@ -223,54 +233,53 @@ public class NotificationAccessSettings extends ListFragment {
ResolveInfo resolveInfo = installedServices.get(i);
ServiceInfo info = resolveInfo.serviceInfo;
- if (!android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE.equals(
- info.permission)) {
- Slog.w(TAG, "Skipping notification listener service "
+ if (!c.permission.equals(info.permission)) {
+ Slog.w(c.tag, "Skipping " + c.noun + " service "
+ info.packageName + "/" + info.name
+ ": it does not require the permission "
- + android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE);
+ + c.permission);
continue;
}
if (adapter != null) {
adapter.add(info);
}
- listeners++;
+ services++;
}
- return listeners;
+ return services;
}
- boolean isListenerEnabled(ServiceInfo info) {
+ private boolean isServiceEnabled(ServiceInfo info) {
final ComponentName cn = new ComponentName(info.packageName, info.name);
- return mEnabledListeners.contains(cn);
+ return mEnabledServices.contains(cn);
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
- ServiceInfo info = mList.getItem(position);
+ ServiceInfo info = mListAdapter.getItem(position);
final ComponentName cn = new ComponentName(info.packageName, info.name);
- if (mEnabledListeners.contains(cn)) {
+ if (mEnabledServices.contains(cn)) {
// the simple version: disabling
- mEnabledListeners.remove(cn);
- saveEnabledListeners();
+ mEnabledServices.remove(cn);
+ saveEnabledServices();
} else {
// show a scary dialog
- new ListenerWarningDialogFragment()
- .setListenerInfo(cn, info.loadLabel(mPM).toString())
+ new ScaryWarningDialogFragment()
+ .setServiceInfo(cn, info.loadLabel(mPM).toString())
.show(getFragmentManager(), "dialog");
}
}
- static class ViewHolder {
+ private static class ViewHolder {
ImageView icon;
TextView name;
CheckBox checkbox;
TextView description;
}
- class ListenerListAdapter extends ArrayAdapter<ServiceInfo> {
+ private class ServiceListAdapter extends ArrayAdapter<ServiceInfo> {
final LayoutInflater mInflater;
- ListenerListAdapter(Context context) {
+ ServiceListAdapter(Context context) {
super(context, 0, 0);
mInflater = (LayoutInflater)
getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
@@ -296,7 +305,7 @@ public class NotificationAccessSettings extends ListFragment {
}
public View newView(ViewGroup parent) {
- View v = mInflater.inflate(R.layout.notification_listener_item, parent, false);
+ View v = mInflater.inflate(R.layout.managed_service_item, parent, false);
ViewHolder h = new ViewHolder();
h.icon = (ImageView) v.findViewById(R.id.icon);
h.name = (TextView) v.findViewById(R.id.name);
@@ -318,7 +327,18 @@ public class NotificationAccessSettings extends ListFragment {
} else {
vh.description.setVisibility(View.GONE);
}
- vh.checkbox.setChecked(isListenerEnabled(info));
+ vh.checkbox.setChecked(isServiceEnabled(info));
}
}
+
+ protected static class Config {
+ String tag;
+ String setting;
+ String intentAction;
+ String permission;
+ String noun;
+ int warningDialogTitle;
+ int warningDialogSummary;
+ int emptyText;
+ }
}
diff --git a/src/com/android/settings/notification/NotificationAccessSettings.java b/src/com/android/settings/notification/NotificationAccessSettings.java
new file mode 100644
index 0000000..ced71a4
--- /dev/null
+++ b/src/com/android/settings/notification/NotificationAccessSettings.java
@@ -0,0 +1,55 @@
+/*
+ * 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.notification;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.provider.Settings;
+import android.service.notification.NotificationListenerService;
+
+import com.android.settings.R;
+
+public class NotificationAccessSettings extends ManagedServiceSettings {
+ private static final String TAG = NotificationAccessSettings.class.getSimpleName();
+ private static final Config CONFIG = getNotificationListenerConfig();
+
+ private static Config getNotificationListenerConfig() {
+ final Config c = new Config();
+ c.tag = TAG;
+ c.setting = Settings.Secure.ENABLED_NOTIFICATION_LISTENERS;
+ c.intentAction = NotificationListenerService.SERVICE_INTERFACE;
+ c.permission = android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE;
+ c.noun = "notification listener";
+ c.warningDialogTitle = R.string.notification_listener_security_warning_title;
+ c.warningDialogSummary = R.string.notification_listener_security_warning_summary;
+ c.emptyText = R.string.no_notification_listeners;
+ return c;
+ }
+
+ @Override
+ protected Config getConfig() {
+ return CONFIG;
+ }
+
+ public static int getListenersCount(PackageManager pm) {
+ return getServicesCount(CONFIG, pm);
+ }
+
+ public static int getEnabledListenersCount(Context context) {
+ return getEnabledServicesCount(CONFIG, context);
+ }
+}
diff --git a/src/com/android/settings/notification/NotificationAppList.java b/src/com/android/settings/notification/NotificationAppList.java
new file mode 100644
index 0000000..3c44196
--- /dev/null
+++ b/src/com/android/settings/notification/NotificationAppList.java
@@ -0,0 +1,594 @@
+/*
+ * 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.notification;
+
+import static com.android.settings.notification.AppNotificationSettings.EXTRA_HAS_SETTINGS_INTENT;
+import static com.android.settings.notification.AppNotificationSettings.EXTRA_SETTINGS_INTENT;
+
+import android.animation.LayoutTransition;
+import android.app.INotificationManager;
+import android.app.Notification;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.Signature;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Parcelable;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.service.notification.NotificationListenerService;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.SectionIndexer;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import com.android.settings.PinnedHeaderListFragment;
+import com.android.settings.R;
+import com.android.settings.Settings.NotificationAppListActivity;
+import com.android.settings.UserSpinnerAdapter;
+import com.android.settings.Utils;
+
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/** Just a sectioned list of installed applications, nothing else to index **/
+public class NotificationAppList extends PinnedHeaderListFragment
+ implements OnItemSelectedListener {
+ private static final String TAG = "NotificationAppList";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private static final String EMPTY_SUBTITLE = "";
+ private static final String SECTION_BEFORE_A = "*";
+ private static final String SECTION_AFTER_Z = "**";
+ private static final Intent APP_NOTIFICATION_PREFS_CATEGORY_INTENT
+ = new Intent(Intent.ACTION_MAIN)
+ .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES);
+
+ private final Handler mHandler = new Handler();
+ private final ArrayMap<String, AppRow> mRows = new ArrayMap<String, AppRow>();
+ private final ArrayList<AppRow> mSortedRows = new ArrayList<AppRow>();
+ private final ArrayList<String> mSections = new ArrayList<String>();
+
+ private Context mContext;
+ private LayoutInflater mInflater;
+ private NotificationAppAdapter mAdapter;
+ private Signature[] mSystemSignature;
+ private Parcelable mListViewState;
+ private Backend mBackend = new Backend();
+ private UserSpinnerAdapter mProfileSpinnerAdapter;
+
+ private PackageManager mPM;
+ private UserManager mUM;
+ private LauncherApps mLauncherApps;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mContext = getActivity();
+ mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mAdapter = new NotificationAppAdapter(mContext);
+ mUM = UserManager.get(mContext);
+ mPM = mContext.getPackageManager();
+ mLauncherApps = (LauncherApps) mContext.getSystemService(Context.LAUNCHER_APPS_SERVICE);
+ getActivity().setTitle(R.string.app_notifications_title);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.notification_app_list, container, false);
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ mProfileSpinnerAdapter = Utils.createUserSpinnerAdapter(mUM, mContext);
+ if (mProfileSpinnerAdapter != null) {
+ Spinner spinner = (Spinner) getActivity().getLayoutInflater().inflate(
+ R.layout.spinner_view, null);
+ spinner.setAdapter(mProfileSpinnerAdapter);
+ spinner.setOnItemSelectedListener(this);
+ setPinnedHeaderView(spinner);
+ }
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ repositionScrollbar();
+ getListView().setAdapter(mAdapter);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ if (DEBUG) Log.d(TAG, "Saving listView state");
+ mListViewState = getListView().onSaveInstanceState();
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ mListViewState = null; // you're dead to me
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ loadAppsList();
+ }
+
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ UserHandle selectedUser = mProfileSpinnerAdapter.getUserHandle(position);
+ if (selectedUser.getIdentifier() != UserHandle.myUserId()) {
+ Intent intent = new Intent(getActivity(), NotificationAppListActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ mContext.startActivityAsUser(intent, selectedUser);
+ }
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ }
+
+ public void setBackend(Backend backend) {
+ mBackend = backend;
+ }
+
+ private void loadAppsList() {
+ AsyncTask.execute(mCollectAppsRunnable);
+ }
+
+ private String getSection(CharSequence label) {
+ if (label == null || label.length() == 0) return SECTION_BEFORE_A;
+ final char c = Character.toUpperCase(label.charAt(0));
+ if (c < 'A') return SECTION_BEFORE_A;
+ if (c > 'Z') return SECTION_AFTER_Z;
+ return Character.toString(c);
+ }
+
+ private void repositionScrollbar() {
+ final int sbWidthPx = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ getListView().getScrollBarSize(),
+ getResources().getDisplayMetrics());
+ final View parent = (View)getView().getParent();
+ final int eat = Math.min(sbWidthPx, parent.getPaddingEnd());
+ if (eat <= 0) return;
+ if (DEBUG) Log.d(TAG, String.format("Eating %dpx into %dpx padding for %dpx scroll, ld=%d",
+ eat, parent.getPaddingEnd(), sbWidthPx, getListView().getLayoutDirection()));
+ parent.setPaddingRelative(parent.getPaddingStart(), parent.getPaddingTop(),
+ parent.getPaddingEnd() - eat, parent.getPaddingBottom());
+ }
+
+ private static class ViewHolder {
+ ViewGroup row;
+ ImageView icon;
+ TextView title;
+ TextView subtitle;
+ View rowDivider;
+ }
+
+ private class NotificationAppAdapter extends ArrayAdapter<Row> implements SectionIndexer {
+ public NotificationAppAdapter(Context context) {
+ super(context, 0, 0);
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 2;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ Row r = getItem(position);
+ return r instanceof AppRow ? 1 : 0;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ Row r = getItem(position);
+ View v;
+ if (convertView == null) {
+ v = newView(parent, r);
+ } else {
+ v = convertView;
+ }
+ bindView(v, r, false /*animate*/);
+ return v;
+ }
+
+ public View newView(ViewGroup parent, Row r) {
+ if (!(r instanceof AppRow)) {
+ return mInflater.inflate(R.layout.notification_app_section, parent, false);
+ }
+ final View v = mInflater.inflate(R.layout.notification_app, parent, false);
+ final ViewHolder vh = new ViewHolder();
+ vh.row = (ViewGroup) v;
+ vh.row.setLayoutTransition(new LayoutTransition());
+ vh.row.setLayoutTransition(new LayoutTransition());
+ vh.icon = (ImageView) v.findViewById(android.R.id.icon);
+ vh.title = (TextView) v.findViewById(android.R.id.title);
+ vh.subtitle = (TextView) v.findViewById(android.R.id.text1);
+ vh.rowDivider = v.findViewById(R.id.row_divider);
+ v.setTag(vh);
+ return v;
+ }
+
+ private void enableLayoutTransitions(ViewGroup vg, boolean enabled) {
+ if (enabled) {
+ vg.getLayoutTransition().enableTransitionType(LayoutTransition.APPEARING);
+ vg.getLayoutTransition().enableTransitionType(LayoutTransition.DISAPPEARING);
+ } else {
+ vg.getLayoutTransition().disableTransitionType(LayoutTransition.APPEARING);
+ vg.getLayoutTransition().disableTransitionType(LayoutTransition.DISAPPEARING);
+ }
+ }
+
+ public void bindView(final View view, Row r, boolean animate) {
+ if (!(r instanceof AppRow)) {
+ // it's a section row
+ final TextView tv = (TextView)view.findViewById(android.R.id.title);
+ tv.setText(r.section);
+ return;
+ }
+
+ final AppRow row = (AppRow)r;
+ final ViewHolder vh = (ViewHolder) view.getTag();
+ enableLayoutTransitions(vh.row, animate);
+ vh.rowDivider.setVisibility(row.first ? View.GONE : View.VISIBLE);
+ vh.row.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mContext.startActivity(new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+ .putExtra(Settings.EXTRA_APP_PACKAGE, row.pkg)
+ .putExtra(Settings.EXTRA_APP_UID, row.uid)
+ .putExtra(EXTRA_HAS_SETTINGS_INTENT, row.settingsIntent != null)
+ .putExtra(EXTRA_SETTINGS_INTENT, row.settingsIntent));
+ }
+ });
+ enableLayoutTransitions(vh.row, animate);
+ vh.icon.setImageDrawable(row.icon);
+ vh.title.setText(row.label);
+ final String sub = getSubtitle(row);
+ vh.subtitle.setText(sub);
+ vh.subtitle.setVisibility(!sub.isEmpty() ? View.VISIBLE : View.GONE);
+ }
+
+ private String getSubtitle(AppRow row) {
+ if (row.banned) {
+ return mContext.getString(R.string.app_notification_row_banned);
+ }
+ if (!row.priority && !row.sensitive) {
+ return EMPTY_SUBTITLE;
+ }
+ final String priString = mContext.getString(R.string.app_notification_row_priority);
+ final String senString = mContext.getString(R.string.app_notification_row_sensitive);
+ if (row.priority != row.sensitive) {
+ return row.priority ? priString : senString;
+ }
+ return priString + mContext.getString(R.string.summary_divider_text) + senString;
+ }
+
+ @Override
+ public Object[] getSections() {
+ return mSections.toArray(new Object[mSections.size()]);
+ }
+
+ @Override
+ public int getPositionForSection(int sectionIndex) {
+ final String section = mSections.get(sectionIndex);
+ final int n = getCount();
+ for (int i = 0; i < n; i++) {
+ final Row r = getItem(i);
+ if (r.section.equals(section)) {
+ return i;
+ }
+ }
+ return 0;
+ }
+
+ @Override
+ public int getSectionForPosition(int position) {
+ Row row = getItem(position);
+ return mSections.indexOf(row.section);
+ }
+ }
+
+ private static class Row {
+ public String section;
+ }
+
+ public static class AppRow extends Row {
+ public String pkg;
+ public int uid;
+ public Drawable icon;
+ public CharSequence label;
+ public Intent settingsIntent;
+ public boolean banned;
+ public boolean priority;
+ public boolean sensitive;
+ public boolean first; // first app in section
+ }
+
+ private static final Comparator<AppRow> mRowComparator = new Comparator<AppRow>() {
+ private final Collator sCollator = Collator.getInstance();
+ @Override
+ public int compare(AppRow lhs, AppRow rhs) {
+ return sCollator.compare(lhs.label, rhs.label);
+ }
+ };
+
+
+ public static AppRow loadAppRow(PackageManager pm, ApplicationInfo app,
+ Backend backend) {
+ final AppRow row = new AppRow();
+ row.pkg = app.packageName;
+ row.uid = app.uid;
+ try {
+ row.label = app.loadLabel(pm);
+ } catch (Throwable t) {
+ Log.e(TAG, "Error loading application label for " + row.pkg, t);
+ row.label = row.pkg;
+ }
+ row.icon = app.loadIcon(pm);
+ row.banned = backend.getNotificationsBanned(row.pkg, row.uid);
+ row.priority = backend.getHighPriority(row.pkg, row.uid);
+ row.sensitive = backend.getSensitive(row.pkg, row.uid);
+ return row;
+ }
+
+ public static List<ResolveInfo> queryNotificationConfigActivities(PackageManager pm) {
+ if (DEBUG) Log.d(TAG, "APP_NOTIFICATION_PREFS_CATEGORY_INTENT is "
+ + APP_NOTIFICATION_PREFS_CATEGORY_INTENT);
+ final List<ResolveInfo> resolveInfos = pm.queryIntentActivities(
+ APP_NOTIFICATION_PREFS_CATEGORY_INTENT,
+ 0 //PackageManager.MATCH_DEFAULT_ONLY
+ );
+ return resolveInfos;
+ }
+ public static void collectConfigActivities(PackageManager pm, ArrayMap<String, AppRow> rows) {
+ final List<ResolveInfo> resolveInfos = queryNotificationConfigActivities(pm);
+ applyConfigActivities(pm, rows, resolveInfos);
+ }
+
+ public static void applyConfigActivities(PackageManager pm, ArrayMap<String, AppRow> rows,
+ List<ResolveInfo> resolveInfos) {
+ if (DEBUG) Log.d(TAG, "Found " + resolveInfos.size() + " preference activities"
+ + (resolveInfos.size() == 0 ? " ;_;" : ""));
+ for (ResolveInfo ri : resolveInfos) {
+ final ActivityInfo activityInfo = ri.activityInfo;
+ final ApplicationInfo appInfo = activityInfo.applicationInfo;
+ final AppRow row = rows.get(appInfo.packageName);
+ if (row == null) {
+ Log.v(TAG, "Ignoring notification preference activity ("
+ + activityInfo.name + ") for unknown package "
+ + activityInfo.packageName);
+ continue;
+ }
+ if (row.settingsIntent != null) {
+ Log.v(TAG, "Ignoring duplicate notification preference activity ("
+ + activityInfo.name + ") for package "
+ + activityInfo.packageName);
+ continue;
+ }
+ row.settingsIntent = new Intent(APP_NOTIFICATION_PREFS_CATEGORY_INTENT)
+ .setClassName(activityInfo.packageName, activityInfo.name);
+ }
+ }
+
+ private final Runnable mCollectAppsRunnable = new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mRows) {
+ final long start = SystemClock.uptimeMillis();
+ if (DEBUG) Log.d(TAG, "Collecting apps...");
+ mRows.clear();
+ mSortedRows.clear();
+
+ // collect all launchable apps, plus any packages that have notification settings
+ final List<ApplicationInfo> appInfos = new ArrayList<ApplicationInfo>();
+
+ final List<LauncherActivityInfo> lais
+ = mLauncherApps.getActivityList(null /* all */,
+ UserHandle.getCallingUserHandle());
+ if (DEBUG) Log.d(TAG, " launchable activities:");
+ for (LauncherActivityInfo lai : lais) {
+ if (DEBUG) Log.d(TAG, " " + lai.getComponentName().toString());
+ appInfos.add(lai.getApplicationInfo());
+ }
+
+ final List<ResolveInfo> resolvedConfigActivities
+ = queryNotificationConfigActivities(mPM);
+ if (DEBUG) Log.d(TAG, " config activities:");
+ for (ResolveInfo ri : resolvedConfigActivities) {
+ if (DEBUG) Log.d(TAG, " "
+ + ri.activityInfo.packageName + "/" + ri.activityInfo.name);
+ appInfos.add(ri.activityInfo.applicationInfo);
+ }
+
+ for (ApplicationInfo info : appInfos) {
+ final String key = info.packageName;
+ if (mRows.containsKey(key)) {
+ // we already have this app, thanks
+ continue;
+ }
+
+ final AppRow row = loadAppRow(mPM, info, mBackend);
+ mRows.put(key, row);
+ }
+
+ // add config activities to the list
+ applyConfigActivities(mPM, mRows, resolvedConfigActivities);
+
+ // sort rows
+ mSortedRows.addAll(mRows.values());
+ Collections.sort(mSortedRows, mRowComparator);
+ // compute sections
+ mSections.clear();
+ String section = null;
+ for (AppRow r : mSortedRows) {
+ r.section = getSection(r.label);
+ if (!r.section.equals(section)) {
+ section = r.section;
+ mSections.add(section);
+ }
+ }
+ mHandler.post(mRefreshAppsListRunnable);
+ final long elapsed = SystemClock.uptimeMillis() - start;
+ if (DEBUG) Log.d(TAG, "Collected " + mRows.size() + " apps in " + elapsed + "ms");
+ }
+ }
+ };
+
+ private void refreshDisplayedItems() {
+ if (DEBUG) Log.d(TAG, "Refreshing apps...");
+ mAdapter.clear();
+ synchronized (mSortedRows) {
+ String section = null;
+ final int N = mSortedRows.size();
+ boolean first = true;
+ for (int i = 0; i < N; i++) {
+ final AppRow row = mSortedRows.get(i);
+ if (!row.section.equals(section)) {
+ section = row.section;
+ Row r = new Row();
+ r.section = section;
+ mAdapter.add(r);
+ first = true;
+ }
+ row.first = first;
+ mAdapter.add(row);
+ first = false;
+ }
+ }
+ if (mListViewState != null) {
+ if (DEBUG) Log.d(TAG, "Restoring listView state");
+ getListView().onRestoreInstanceState(mListViewState);
+ mListViewState = null;
+ }
+ if (DEBUG) Log.d(TAG, "Refreshed " + mSortedRows.size() + " displayed items");
+ }
+
+ private final Runnable mRefreshAppsListRunnable = new Runnable() {
+ @Override
+ public void run() {
+ refreshDisplayedItems();
+ }
+ };
+
+ public static class Backend {
+ static INotificationManager sINM = INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+
+ public boolean setNotificationsBanned(String pkg, int uid, boolean banned) {
+ try {
+ sINM.setNotificationsEnabledForPackage(pkg, uid, !banned);
+ return true;
+ } catch (Exception e) {
+ Log.w(TAG, "Error calling NoMan", e);
+ return false;
+ }
+ }
+
+ public boolean getNotificationsBanned(String pkg, int uid) {
+ try {
+ final boolean enabled = sINM.areNotificationsEnabledForPackage(pkg, uid);
+ return !enabled;
+ } catch (Exception e) {
+ Log.w(TAG, "Error calling NoMan", e);
+ return false;
+ }
+ }
+
+ public boolean getHighPriority(String pkg, int uid) {
+ try {
+ return sINM.getPackagePriority(pkg, uid) == Notification.PRIORITY_MAX;
+ } catch (Exception e) {
+ Log.w(TAG, "Error calling NoMan", e);
+ return false;
+ }
+ }
+
+ public boolean setHighPriority(String pkg, int uid, boolean highPriority) {
+ try {
+ sINM.setPackagePriority(pkg, uid,
+ highPriority ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT);
+ return true;
+ } catch (Exception e) {
+ Log.w(TAG, "Error calling NoMan", e);
+ return false;
+ }
+ }
+
+ public boolean getSensitive(String pkg, int uid) {
+ try {
+ return sINM.getPackageVisibilityOverride(pkg, uid) == Notification.VISIBILITY_PRIVATE;
+ } catch (Exception e) {
+ Log.w(TAG, "Error calling NoMan", e);
+ return false;
+ }
+ }
+
+ public boolean setSensitive(String pkg, int uid, boolean sensitive) {
+ try {
+ sINM.setPackageVisibilityOverride(pkg, uid,
+ sensitive ? Notification.VISIBILITY_PRIVATE
+ : NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
+ return true;
+ } catch (Exception e) {
+ Log.w(TAG, "Error calling NoMan", e);
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/notification/NotificationSettings.java b/src/com/android/settings/notification/NotificationSettings.java
new file mode 100644
index 0000000..6899440
--- /dev/null
+++ b/src/com/android/settings/notification/NotificationSettings.java
@@ -0,0 +1,515 @@
+/*
+ * 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.notification;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.media.AudioManager;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Vibrator;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.PreferenceCategory;
+import android.preference.SeekBarVolumizer;
+import android.preference.TwoStatePreference;
+import android.provider.MediaStore;
+import android.provider.OpenableColumns;
+import android.provider.SearchIndexableResource;
+import android.provider.Settings;
+import android.util.Log;
+
+import android.widget.SeekBar;
+import com.android.internal.widget.LockPatternUtils;
+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.Arrays;
+import java.util.List;
+
+public class NotificationSettings extends SettingsPreferenceFragment implements Indexable {
+ private static final String TAG = "NotificationSettings";
+
+ private static final String KEY_SOUND = "sound";
+ 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";
+ private static final String KEY_NOTIFICATION_VOLUME = "notification_volume";
+ private static final String KEY_PHONE_RINGTONE = "ringtone";
+ private static final String KEY_NOTIFICATION_RINGTONE = "notification_ringtone";
+ private static final String KEY_VIBRATE_WHEN_RINGING = "vibrate_when_ringing";
+ 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 int SAMPLE_CUTOFF = 2000; // manually cap sample playback at 2 seconds
+
+ private final VolumePreferenceCallback mVolumeCallback = new VolumePreferenceCallback();
+ private final H mHandler = new H();
+ private final SettingsObserver mSettingsObserver = new SettingsObserver();
+
+ private Context mContext;
+ private PackageManager mPM;
+ private boolean mVoiceCapable;
+ private Vibrator mVibrator;
+ private VolumeSeekBarPreference mRingOrNotificationPreference;
+
+ private Preference mPhoneRingtonePreference;
+ private Preference mNotificationRingtonePreference;
+ private TwoStatePreference mVibrateWhenRinging;
+ private TwoStatePreference mNotificationPulse;
+ private DropDownPreference mLockscreen;
+ private Preference mNotificationAccess;
+ private boolean mSecure;
+ private int mLockscreenSelectedValue;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mContext = getActivity();
+ mPM = mContext.getPackageManager();
+ mVoiceCapable = Utils.isVoiceCapable(mContext);
+ mSecure = new LockPatternUtils(getActivity()).isSecure();
+
+ mVibrator = (Vibrator) getActivity().getSystemService(Context.VIBRATOR_SERVICE);
+ if (mVibrator != null && !mVibrator.hasVibrator()) {
+ mVibrator = null;
+ }
+
+ addPreferencesFromResource(R.xml.notification_settings);
+
+ final PreferenceCategory sound = (PreferenceCategory) findPreference(KEY_SOUND);
+ initVolumePreference(KEY_MEDIA_VOLUME, AudioManager.STREAM_MUSIC);
+ initVolumePreference(KEY_ALARM_VOLUME, AudioManager.STREAM_ALARM);
+ if (mVoiceCapable) {
+ mRingOrNotificationPreference =
+ initVolumePreference(KEY_RING_VOLUME, AudioManager.STREAM_RING);
+ sound.removePreference(sound.findPreference(KEY_NOTIFICATION_VOLUME));
+ } else {
+ mRingOrNotificationPreference =
+ initVolumePreference(KEY_NOTIFICATION_VOLUME, AudioManager.STREAM_NOTIFICATION);
+ sound.removePreference(sound.findPreference(KEY_RING_VOLUME));
+ }
+ initRingtones(sound);
+ initVibrateWhenRinging(sound);
+
+ final PreferenceCategory notification = (PreferenceCategory)
+ findPreference(KEY_NOTIFICATION);
+ initPulse(notification);
+ initLockscreenNotifications(notification);
+
+ mNotificationAccess = findPreference(KEY_NOTIFICATION_ACCESS);
+ refreshNotificationListeners();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ refreshNotificationListeners();
+ lookupRingtoneNames();
+ mSettingsObserver.register(true);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ mVolumeCallback.stopSample();
+ mSettingsObserver.register(false);
+ }
+
+ // === Volumes ===
+
+ private VolumeSeekBarPreference initVolumePreference(String key, int stream) {
+ final VolumeSeekBarPreference volumePref = (VolumeSeekBarPreference) findPreference(key);
+ volumePref.setCallback(mVolumeCallback);
+ volumePref.setStream(stream);
+ return volumePref;
+ }
+
+ private void updateRingOrNotificationIcon(int progress) {
+ mRingOrNotificationPreference.showIcon(progress > 0
+ ? R.drawable.ring_notif
+ : (mVibrator == null
+ ? R.drawable.ring_notif_mute
+ : R.drawable.ring_notif_vibrate));
+ }
+
+ private final class VolumePreferenceCallback implements VolumeSeekBarPreference.Callback {
+ private SeekBarVolumizer mCurrent;
+
+ @Override
+ public void onSampleStarting(SeekBarVolumizer sbv) {
+ if (mCurrent != null && mCurrent != sbv) {
+ mCurrent.stopSample();
+ }
+ mCurrent = sbv;
+ if (mCurrent != null) {
+ mHandler.removeMessages(H.STOP_SAMPLE);
+ mHandler.sendEmptyMessageDelayed(H.STOP_SAMPLE, SAMPLE_CUTOFF);
+ }
+ }
+
+ @Override
+ public void onStreamValueChanged(int stream, int progress) {
+ if (stream == AudioManager.STREAM_RING) {
+ mHandler.removeMessages(H.UPDATE_RINGER_ICON);
+ mHandler.obtainMessage(H.UPDATE_RINGER_ICON, progress, 0).sendToTarget();
+ }
+ }
+
+ public void stopSample() {
+ if (mCurrent != null) {
+ mCurrent.stopSample();
+ }
+ }
+ };
+
+
+ // === Phone & notification ringtone ===
+
+ private void initRingtones(PreferenceCategory root) {
+ mPhoneRingtonePreference = root.findPreference(KEY_PHONE_RINGTONE);
+ if (mPhoneRingtonePreference != null && !mVoiceCapable) {
+ root.removePreference(mPhoneRingtonePreference);
+ mPhoneRingtonePreference = null;
+ }
+ mNotificationRingtonePreference = root.findPreference(KEY_NOTIFICATION_RINGTONE);
+ }
+
+ private void lookupRingtoneNames() {
+ AsyncTask.execute(mLookupRingtoneNames);
+ }
+
+ 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 (mNotificationRingtonePreference != null) {
+ final CharSequence summary = updateRingtoneName(
+ mContext, RingtoneManager.TYPE_NOTIFICATION);
+ if (summary != null) {
+ mHandler.obtainMessage(H.UPDATE_NOTIFICATION_RINGTONE, summary).sendToTarget();
+ }
+ }
+ }
+ };
+
+ private static CharSequence updateRingtoneName(Context context, int type) {
+ if (context == null) {
+ Log.e(TAG, "Unable to update ringtone name, no context provided");
+ return null;
+ }
+ Uri ringtoneUri = RingtoneManager.getActualDefaultRingtoneUri(context, type);
+ CharSequence summary = context.getString(com.android.internal.R.string.ringtone_unknown);
+ // Is it a silent ringtone?
+ if (ringtoneUri == null) {
+ summary = context.getString(com.android.internal.R.string.ringtone_silent);
+ } else {
+ Cursor cursor = null;
+ try {
+ if (MediaStore.AUTHORITY.equals(ringtoneUri.getAuthority())) {
+ // Fetch the ringtone title from the media provider
+ cursor = context.getContentResolver().query(ringtoneUri,
+ new String[] { MediaStore.Audio.Media.TITLE }, null, null, null);
+ } else if (ContentResolver.SCHEME_CONTENT.equals(ringtoneUri.getScheme())) {
+ cursor = context.getContentResolver().query(ringtoneUri,
+ new String[] { OpenableColumns.DISPLAY_NAME }, null, null, null);
+ }
+ if (cursor != null) {
+ if (cursor.moveToFirst()) {
+ summary = cursor.getString(0);
+ }
+ }
+ } catch (SQLiteException sqle) {
+ // Unknown title for the ringtone
+ } catch (IllegalArgumentException iae) {
+ // Some other error retrieving the column from the provider
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+ return summary;
+ }
+
+ // === Vibrate when ringing ===
+
+ private void initVibrateWhenRinging(PreferenceCategory root) {
+ mVibrateWhenRinging = (TwoStatePreference) root.findPreference(KEY_VIBRATE_WHEN_RINGING);
+ if (mVibrateWhenRinging == null) {
+ Log.i(TAG, "Preference not found: " + KEY_VIBRATE_WHEN_RINGING);
+ return;
+ }
+ if (!mVoiceCapable) {
+ root.removePreference(mVibrateWhenRinging);
+ mVibrateWhenRinging = null;
+ return;
+ }
+ mVibrateWhenRinging.setPersistent(false);
+ updateVibrateWhenRinging();
+ mVibrateWhenRinging.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ final boolean val = (Boolean) newValue;
+ return Settings.System.putInt(getContentResolver(),
+ Settings.System.VIBRATE_WHEN_RINGING,
+ val ? 1 : 0);
+ }
+ });
+ }
+
+ private void updateVibrateWhenRinging() {
+ if (mVibrateWhenRinging == null) return;
+ mVibrateWhenRinging.setChecked(Settings.System.getInt(getContentResolver(),
+ 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;
+ }
+
+ 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;
+ }
+
+ // === Notification listeners ===
+
+ private void refreshNotificationListeners() {
+ if (mNotificationAccess != null) {
+ final int total = NotificationAccessSettings.getListenersCount(mPM);
+ if (total == 0) {
+ getPreferenceScreen().removePreference(mNotificationAccess);
+ } else {
+ final int n = NotificationAccessSettings.getEnabledListenersCount(mContext);
+ if (n == 0) {
+ mNotificationAccess.setSummary(getResources().getString(
+ R.string.manage_notification_access_summary_zero));
+ } else {
+ mNotificationAccess.setSummary(String.format(getResources().getQuantityString(
+ R.plurals.manage_notification_access_summary_nonzero,
+ n, n)));
+ }
+ }
+ }
+ }
+
+ // === Callbacks ===
+
+ private final class SettingsObserver extends ContentObserver {
+ private final Uri VIBRATE_WHEN_RINGING_URI =
+ Settings.System.getUriFor(Settings.System.VIBRATE_WHEN_RINGING);
+ private final Uri NOTIFICATION_LIGHT_PULSE_URI =
+ Settings.System.getUriFor(Settings.System.NOTIFICATION_LIGHT_PULSE);
+ private final Uri LOCK_SCREEN_PRIVATE_URI =
+ 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);
+
+ public SettingsObserver() {
+ super(mHandler);
+ }
+
+ public void register(boolean register) {
+ final ContentResolver cr = getContentResolver();
+ if (register) {
+ cr.registerContentObserver(VIBRATE_WHEN_RINGING_URI, false, this);
+ cr.registerContentObserver(NOTIFICATION_LIGHT_PULSE_URI, false, this);
+ cr.registerContentObserver(LOCK_SCREEN_PRIVATE_URI, false, this);
+ cr.registerContentObserver(LOCK_SCREEN_SHOW_URI, false, this);
+ } else {
+ cr.unregisterContentObserver(this);
+ }
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ super.onChange(selfChange, uri);
+ 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();
+ }
+ }
+ }
+
+ private final class H extends Handler {
+ private static final int UPDATE_PHONE_RINGTONE = 1;
+ private static final int UPDATE_NOTIFICATION_RINGTONE = 2;
+ private static final int STOP_SAMPLE = 3;
+ private static final int UPDATE_RINGER_ICON = 4;
+
+ private H() {
+ super(Looper.getMainLooper());
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case UPDATE_PHONE_RINGTONE:
+ mPhoneRingtonePreference.setSummary((CharSequence) msg.obj);
+ break;
+ case UPDATE_NOTIFICATION_RINGTONE:
+ mNotificationRingtonePreference.setSummary((CharSequence) msg.obj);
+ break;
+ case STOP_SAMPLE:
+ mVolumeCallback.stopSample();
+ break;
+ case UPDATE_RINGER_ICON:
+ updateRingOrNotificationIcon(msg.arg1);
+ break;
+ }
+ }
+ }
+
+ // === Indexing ===
+
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider() {
+
+ public List<SearchIndexableResource> getXmlResourcesToIndex(
+ Context context, boolean enabled) {
+ final SearchIndexableResource sir = new SearchIndexableResource(context);
+ sir.xmlResId = R.xml.notification_settings;
+ return Arrays.asList(sir);
+ }
+
+ public List<String> getNonIndexableKeys(Context context) {
+ final ArrayList<String> rt = new ArrayList<String>();
+ if (Utils.isVoiceCapable(context)) {
+ rt.add(KEY_NOTIFICATION_VOLUME);
+ } else {
+ rt.add(KEY_RING_VOLUME);
+ rt.add(KEY_PHONE_RINGTONE);
+ rt.add(KEY_VIBRATE_WHEN_RINGING);
+ }
+ return rt;
+ }
+ };
+}
diff --git a/src/com/android/settings/NotificationStation.java b/src/com/android/settings/notification/NotificationStation.java
index 8d75579..c61f91e 100644
--- a/src/com/android/settings/NotificationStation.java
+++ b/src/com/android/settings/notification/NotificationStation.java
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.settings;
+package com.android.settings.notification;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.INotificationManager;
import android.app.Notification;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
@@ -35,8 +33,7 @@ import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
-import android.service.notification.INotificationListener;
-import android.service.notification.IStatusBarNotificationHolder;
+import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.view.LayoutInflater;
@@ -49,17 +46,31 @@ import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.Utils;
+
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class NotificationStation extends SettingsPreferenceFragment {
private static final String TAG = NotificationStation.class.getSimpleName();
- static final boolean DEBUG = true;
- private static final String PACKAGE_SCHEME = "package";
- private static final boolean SHOW_HISTORICAL_NOTIFICATIONS = true;
- private final PackageReceiver mPackageReceiver = new PackageReceiver();
+ private static final boolean DEBUG = false;
+
+ private static class HistoricalNotificationInfo {
+ public String pkg;
+ public Drawable pkgicon;
+ public CharSequence pkgname;
+ public Drawable icon;
+ public CharSequence title;
+ public int priority;
+ public int user;
+ public long timestamp;
+ public boolean active;
+ }
+
private PackageManager mPm;
private INotificationManager mNoMan;
@@ -70,23 +81,17 @@ public class NotificationStation extends SettingsPreferenceFragment {
}
};
- private INotificationListener.Stub mListener = new INotificationListener.Stub() {
- @Override
- public void onListenerConnected(String[] notificationKeys) throws RemoteException {
- // noop
- }
+ private NotificationListenerService mListener = new NotificationListenerService() {
@Override
- public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder)
- throws RemoteException {
- Log.v(TAG, "onNotificationPosted: " + sbnHolder.get());
+ public void onNotificationPosted(StatusBarNotification notification) {
+ logd("onNotificationPosted: %s", notification);
final Handler h = getListView().getHandler();
h.removeCallbacks(mRefreshListRunnable);
h.postDelayed(mRefreshListRunnable, 100);
}
@Override
- public void onNotificationRemoved(IStatusBarNotificationHolder sbnHolder)
- throws RemoteException {
+ public void onNotificationRemoved(StatusBarNotification notification) {
final Handler h = getListView().getHandler();
h.removeCallbacks(mRefreshListRunnable);
h.postDelayed(mRefreshListRunnable, 100);
@@ -114,25 +119,21 @@ public class NotificationStation extends SettingsPreferenceFragment {
mNoMan = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
try {
- mNoMan.registerListener(mListener,
- new ComponentName(mContext.getPackageName(),
- this.getClass().getCanonicalName()),
- ActivityManager.getCurrentUser());
+ mListener.registerAsSystemService(mContext, new ComponentName(mContext.getPackageName(),
+ this.getClass().getCanonicalName()), ActivityManager.getCurrentUser());
} catch (RemoteException e) {
- // well, that didn't work out
+ Log.e(TAG, "Cannot register listener", e);
}
}
@Override
- public void onCreate(Bundle icicle) {
- logd("onCreate(%s)", icicle);
- super.onCreate(icicle);
- Activity activity = getActivity();
- }
-
- @Override
- public void onDestroyView() {
- super.onDestroyView();
+ public void onDetach() {
+ try {
+ mListener.unregisterAsSystemService();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Cannot unregister listener", e);
+ }
+ super.onDetach();
}
@Override
@@ -141,36 +142,17 @@ public class NotificationStation extends SettingsPreferenceFragment {
super.onActivityCreated(savedInstanceState);
ListView listView = getListView();
-
-// TextView emptyView = (TextView) getView().findViewById(android.R.id.empty);
-// emptyView.setText(R.string.screensaver_settings_disabled_prompt);
-// listView.setEmptyView(emptyView);
+ Utils.forceCustomPadding(listView, false /* non additive padding */);
mAdapter = new NotificationHistoryAdapter(mContext);
listView.setAdapter(mAdapter);
}
@Override
- public void onPause() {
- logd("onPause()");
- super.onPause();
- mContext.unregisterReceiver(mPackageReceiver);
- }
-
- @Override
public void onResume() {
logd("onResume()");
super.onResume();
refreshList();
-
- // 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 refreshList() {
@@ -184,27 +166,18 @@ public class NotificationStation extends SettingsPreferenceFragment {
}
private static void logd(String msg, Object... args) {
- if (DEBUG)
+ if (DEBUG) {
Log.d(TAG, args == null || args.length == 0 ? msg : String.format(msg, args));
- }
-
- private static class HistoricalNotificationInfo {
- public String pkg;
- public Drawable pkgicon;
- public CharSequence pkgname;
- public Drawable icon;
- public CharSequence title;
- public int priority;
- public int user;
- public long timestamp;
- public boolean active;
+ }
}
private List<HistoricalNotificationInfo> loadNotifications() {
final int currentUserId = ActivityManager.getCurrentUser();
try {
- StatusBarNotification[] active = mNoMan.getActiveNotifications(mContext.getPackageName());
- StatusBarNotification[] dismissed = mNoMan.getHistoricalNotifications(mContext.getPackageName(), 50);
+ StatusBarNotification[] active = mNoMan.getActiveNotifications(
+ mContext.getPackageName());
+ StatusBarNotification[] dismissed = mNoMan.getHistoricalNotifications(
+ mContext.getPackageName(), 50);
List<HistoricalNotificationInfo> list
= new ArrayList<HistoricalNotificationInfo>(active.length + dismissed.length);
@@ -219,9 +192,11 @@ public class NotificationStation extends SettingsPreferenceFragment {
info.pkgicon = loadPackageIconDrawable(info.pkg, info.user);
info.pkgname = loadPackageName(info.pkg);
if (sbn.getNotification().extras != null) {
- info.title = sbn.getNotification().extras.getString(Notification.EXTRA_TITLE);
+ info.title = sbn.getNotification().extras.getString(
+ Notification.EXTRA_TITLE);
if (info.title == null || "".equals(info.title)) {
- info.title = sbn.getNotification().extras.getString(Notification.EXTRA_TEXT);
+ info.title = sbn.getNotification().extras.getString(
+ Notification.EXTRA_TEXT);
}
}
if (info.title == null || "".equals(info.title)) {
@@ -246,7 +221,7 @@ public class NotificationStation extends SettingsPreferenceFragment {
return list;
} catch (RemoteException e) {
- e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
+ Log.e(TAG, "Cannot load Notifications: ", e);
}
return null;
}
@@ -261,7 +236,7 @@ public class NotificationStation extends SettingsPreferenceFragment {
}
r = mPm.getResourcesForApplicationAsUser(pkg, userId);
} catch (PackageManager.NameNotFoundException ex) {
- Log.e(TAG, "Icon package not found: " + pkg);
+ Log.e(TAG, "Icon package not found: " + pkg, ex);
return null;
}
} else {
@@ -275,6 +250,7 @@ public class NotificationStation extends SettingsPreferenceFragment {
try {
icon = mPm.getApplicationIcon(pkg);
} catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Cannot get application icon", e);
}
return icon;
@@ -286,6 +262,7 @@ public class NotificationStation extends SettingsPreferenceFragment {
PackageManager.GET_UNINSTALLED_PACKAGES);
if (info != null) return mPm.getApplicationLabel(info);
} catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Cannot load package name", e);
}
return pkg;
}
@@ -302,7 +279,7 @@ public class NotificationStation extends SettingsPreferenceFragment {
} catch (RuntimeException e) {
Log.w(TAG, "Icon not found in "
+ (pkg != null ? resId : "<system>")
- + ": " + Integer.toHexString(resId));
+ + ": " + Integer.toHexString(resId), e);
}
return null;
@@ -320,6 +297,7 @@ public class NotificationStation extends SettingsPreferenceFragment {
public View getView(int position, View convertView, ViewGroup parent) {
final HistoricalNotificationInfo info = getItem(position);
logd("getView(%s/%s)", info.pkg, info.title);
+
final View row = convertView != null ? convertView : createRow(parent);
row.setTag(info);
@@ -332,19 +310,10 @@ public class NotificationStation extends SettingsPreferenceFragment {
}
((DateTimeView) row.findViewById(R.id.timestamp)).setTime(info.timestamp);
-
- // bind caption
((TextView) row.findViewById(android.R.id.title)).setText(info.title);
-
- // app name
((TextView) row.findViewById(R.id.pkgname)).setText(info.pkgname);
- // extra goodies -- not implemented yet
-// ((TextView) row.findViewById(R.id.extra)).setText(
-// ...
-// );
row.findViewById(R.id.extra).setVisibility(View.GONE);
-
row.setAlpha(info.active ? 1.0f : 0.5f);
// set up click handler
@@ -355,38 +324,11 @@ public class NotificationStation extends SettingsPreferenceFragment {
startApplicationDetailsActivity(info.pkg);
}});
-// // bind radio button
-// RadioButton radioButton = (RadioButton) row.findViewById(android.R.id.button1);
-// radioButton.setChecked(dreamInfo.isActive);
-// radioButton.setOnTouchListener(new OnTouchListener() {
-// @Override
-// public boolean onTouch(View v, MotionEvent event) {
-// row.onTouchEvent(event);
-// return false;
-// }});
-
- // bind settings button + divider
-// boolean showSettings = info.
-// settingsComponentName != null;
-// View settingsDivider = row.findViewById(R.id.divider);
-// settingsDivider.setVisibility(false ? View.VISIBLE : View.INVISIBLE);
-//
-// ImageView settingsButton = (ImageView) row.findViewById(android.R.id.button2);
-// settingsButton.setVisibility(false ? View.VISIBLE : View.INVISIBLE);
-// settingsButton.setAlpha(info.isActive ? 1f : Utils.DISABLED_ALPHA);
-// settingsButton.setEnabled(info.isActive);
-// settingsButton.setOnClickListener(new OnClickListener(){
-// @Override
-// public void onClick(View v) {
-// mBackend.launchSettings((DreamInfo) row.getTag());
-// }});
-
return row;
}
private View createRow(ViewGroup parent) {
- final View row = mInflater.inflate(R.layout.notification_log_row, parent, false);
- return row;
+ return mInflater.inflate(R.layout.notification_log_row, parent, false);
}
}
@@ -397,12 +339,4 @@ public class NotificationStation extends SettingsPreferenceFragment {
intent.setComponent(intent.resolveActivity(mPm));
startActivity(intent);
}
-
- private class PackageReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- logd("PackageReceiver.onReceive");
- //refreshList();
- }
- }
}
diff --git a/src/com/android/settings/notification/OtherSoundSettings.java b/src/com/android/settings/notification/OtherSoundSettings.java
new file mode 100644
index 0000000..8528ec7
--- /dev/null
+++ b/src/com/android/settings/notification/OtherSoundSettings.java
@@ -0,0 +1,254 @@
+/*
+ * 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.notification;
+
+import static com.android.settings.notification.SettingPref.TYPE_GLOBAL;
+import static com.android.settings.notification.SettingPref.TYPE_SYSTEM;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Vibrator;
+import android.provider.SearchIndexableResource;
+import android.provider.Settings.Global;
+import android.provider.Settings.System;
+import android.telephony.TelephonyManager;
+
+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.Arrays;
+import java.util.List;
+
+public class OtherSoundSettings extends SettingsPreferenceFragment implements Indexable {
+ private static final String TAG = "OtherSoundSettings";
+
+ private static final int DEFAULT_ON = 1;
+
+ private static final int EMERGENCY_TONE_SILENT = 0;
+ private static final int EMERGENCY_TONE_ALERT = 1;
+ private static final int EMERGENCY_TONE_VIBRATE = 2;
+ private static final int DEFAULT_EMERGENCY_TONE = EMERGENCY_TONE_SILENT;
+
+ private static final int DOCK_AUDIO_MEDIA_DISABLED = 0;
+ private static final int DOCK_AUDIO_MEDIA_ENABLED = 1;
+ private static final int DEFAULT_DOCK_AUDIO_MEDIA = DOCK_AUDIO_MEDIA_DISABLED;
+
+ private static final String KEY_DIAL_PAD_TONES = "dial_pad_tones";
+ private static final String KEY_SCREEN_LOCKING_SOUNDS = "screen_locking_sounds";
+ private static final String KEY_DOCKING_SOUNDS = "docking_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 SettingPref PREF_DIAL_PAD_TONES = new SettingPref(
+ TYPE_SYSTEM, KEY_DIAL_PAD_TONES, System.DTMF_TONE_WHEN_DIALING, DEFAULT_ON) {
+ @Override
+ public boolean isApplicable(Context context) {
+ return Utils.isVoiceCapable(context);
+ }
+ };
+
+ private static final SettingPref PREF_SCREEN_LOCKING_SOUNDS = new SettingPref(
+ TYPE_SYSTEM, KEY_SCREEN_LOCKING_SOUNDS, System.LOCKSCREEN_SOUNDS_ENABLED, DEFAULT_ON);
+
+ private static final SettingPref PREF_DOCKING_SOUNDS = new SettingPref(
+ TYPE_GLOBAL, KEY_DOCKING_SOUNDS, Global.DOCK_SOUNDS_ENABLED, DEFAULT_ON) {
+ @Override
+ public boolean isApplicable(Context context) {
+ return hasDockSettings(context);
+ }
+ };
+
+ private static final SettingPref PREF_TOUCH_SOUNDS = new SettingPref(
+ TYPE_SYSTEM, KEY_TOUCH_SOUNDS, System.SOUND_EFFECTS_ENABLED, DEFAULT_ON) {
+ @Override
+ protected boolean setSetting(Context context, int value) {
+ final AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ if (value != 0) {
+ am.loadSoundEffects();
+ } else {
+ am.unloadSoundEffects();
+ }
+ return super.setSetting(context, value);
+ }
+ };
+
+ 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) {
+ @Override
+ public boolean isApplicable(Context context) {
+ return hasDockSettings(context);
+ }
+
+ @Override
+ protected String getCaption(Resources res, int value) {
+ switch(value) {
+ case DOCK_AUDIO_MEDIA_DISABLED:
+ return res.getString(R.string.dock_audio_media_disabled);
+ case DOCK_AUDIO_MEDIA_ENABLED:
+ return res.getString(R.string.dock_audio_media_enabled);
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+ };
+
+ private static final SettingPref PREF_EMERGENCY_TONE = new SettingPref(
+ TYPE_GLOBAL, KEY_EMERGENCY_TONE, Global.EMERGENCY_TONE, DEFAULT_EMERGENCY_TONE,
+ EMERGENCY_TONE_ALERT, EMERGENCY_TONE_VIBRATE, EMERGENCY_TONE_SILENT) {
+ @Override
+ public boolean isApplicable(Context context) {
+ final int activePhoneType = TelephonyManager.getDefault().getCurrentPhoneType();
+ return activePhoneType == TelephonyManager.PHONE_TYPE_CDMA;
+ }
+
+ @Override
+ protected String getCaption(Resources res, int value) {
+ switch(value) {
+ case EMERGENCY_TONE_SILENT:
+ return res.getString(R.string.emergency_tone_silent);
+ case EMERGENCY_TONE_ALERT:
+ return res.getString(R.string.emergency_tone_alert);
+ case EMERGENCY_TONE_VIBRATE:
+ return res.getString(R.string.emergency_tone_vibrate);
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+ };
+
+ private static final SettingPref[] PREFS = {
+ PREF_DIAL_PAD_TONES,
+ PREF_SCREEN_LOCKING_SOUNDS,
+ PREF_DOCKING_SOUNDS,
+ PREF_TOUCH_SOUNDS,
+ PREF_VIBRATE_ON_TOUCH,
+ PREF_DOCK_AUDIO_MEDIA,
+ PREF_EMERGENCY_TONE,
+ };
+
+ private final SettingsObserver mSettingsObserver = new SettingsObserver();
+
+ private Context mContext;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ addPreferencesFromResource(R.xml.other_sound_settings);
+
+ mContext = getActivity();
+
+ for (SettingPref pref : PREFS) {
+ pref.init(this);
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mSettingsObserver.register(true);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ mSettingsObserver.register(false);
+ }
+
+ private static boolean hasDockSettings(Context context) {
+ return context.getResources().getBoolean(R.bool.has_dock_settings);
+ }
+
+ private static boolean hasHaptic(Context context) {
+ final Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
+ return vibrator != null && vibrator.hasVibrator();
+ }
+
+ // === Callbacks ===
+
+ private final class SettingsObserver extends ContentObserver {
+ public SettingsObserver() {
+ super(new Handler());
+ }
+
+ public void register(boolean register) {
+ final ContentResolver cr = getContentResolver();
+ if (register) {
+ for (SettingPref pref : PREFS) {
+ cr.registerContentObserver(pref.getUri(), false, this);
+ }
+ } else {
+ cr.unregisterContentObserver(this);
+ }
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ super.onChange(selfChange, uri);
+ for (SettingPref pref : PREFS) {
+ if (pref.getUri().equals(uri)) {
+ pref.update(mContext);
+ return;
+ }
+ }
+ }
+ }
+
+ // === Indexing ===
+
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider() {
+
+ public List<SearchIndexableResource> getXmlResourcesToIndex(
+ Context context, boolean enabled) {
+ final SearchIndexableResource sir = new SearchIndexableResource(context);
+ sir.xmlResId = R.xml.other_sound_settings;
+ return Arrays.asList(sir);
+ }
+
+ public List<String> getNonIndexableKeys(Context context) {
+ final ArrayList<String> rt = new ArrayList<String>();
+ for (SettingPref pref : PREFS) {
+ if (!pref.isApplicable(context)) {
+ rt.add(pref.getKey());
+ }
+ }
+ return rt;
+ }
+ };
+}
diff --git a/src/com/android/settings/notification/RedactionInterstitial.java b/src/com/android/settings/notification/RedactionInterstitial.java
new file mode 100644
index 0000000..2bfad1a
--- /dev/null
+++ b/src/com/android/settings/notification/RedactionInterstitial.java
@@ -0,0 +1,102 @@
+/*
+ * 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.notification;
+
+import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.SettingsPreferenceFragment;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.RadioButton;
+
+public class RedactionInterstitial extends SettingsActivity {
+
+ @Override
+ public Intent getIntent() {
+ Intent modIntent = new Intent(super.getIntent());
+ modIntent.putExtra(EXTRA_SHOW_FRAGMENT, RedactionInterstitialFragment.class.getName());
+ return modIntent;
+ }
+
+ @Override
+ protected boolean isValidFragment(String fragmentName) {
+ return RedactionInterstitialFragment.class.getName().equals(fragmentName);
+ }
+
+ public static Intent createStartIntent(Context ctx) {
+ return new Intent(ctx, RedactionInterstitial.class)
+ .putExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, true)
+ .putExtra(EXTRA_PREFS_SET_BACK_TEXT, (String) null)
+ .putExtra(EXTRA_PREFS_SET_NEXT_TEXT, ctx.getString(
+ R.string.app_notifications_dialog_done));
+ }
+
+ public static class RedactionInterstitialFragment extends SettingsPreferenceFragment
+ implements View.OnClickListener {
+
+ private RadioButton mShowAllButton;
+ private RadioButton mRedactSensitiveButton;
+ private RadioButton mHideAllButton;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.redaction_interstitial, container, false);
+ mShowAllButton = (RadioButton) view.findViewById(R.id.show_all);
+ mRedactSensitiveButton = (RadioButton) view.findViewById(R.id.redact_sensitive);
+ mHideAllButton = (RadioButton) view.findViewById(R.id.hide_all);
+
+ mShowAllButton.setOnClickListener(this);
+ mRedactSensitiveButton.setOnClickListener(this);
+ mHideAllButton.setOnClickListener(this);
+ return view;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ loadFromSettings();
+ }
+
+ private void loadFromSettings() {
+ 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;
+ mShowAllButton.setChecked(enabled && show);
+ mRedactSensitiveButton.setChecked(enabled && !show);
+ mHideAllButton.setChecked(!enabled);
+ }
+
+ @Override
+ public void onClick(View v) {
+ final boolean show = (v == mShowAllButton);
+ final boolean enabled = (v != mHideAllButton);
+
+ 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);
+ }
+ }
+}
diff --git a/src/com/android/settings/notification/RedactionSettingsStandalone.java b/src/com/android/settings/notification/RedactionSettingsStandalone.java
new file mode 100644
index 0000000..26c05c1
--- /dev/null
+++ b/src/com/android/settings/notification/RedactionSettingsStandalone.java
@@ -0,0 +1,43 @@
+/*
+ * 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.notification;
+
+import com.android.settings.R;
+
+import android.content.Intent;
+import com.android.settings.SettingsActivity;
+import com.android.settings.notification.RedactionInterstitial.RedactionInterstitialFragment;
+
+/** Wrapper to allow external activites to jump directly to the {@link RedactionInterstitial} */
+public class RedactionSettingsStandalone extends SettingsActivity {
+
+ @Override
+ public Intent getIntent() {
+ Intent modIntent = new Intent(super.getIntent());
+ modIntent.putExtra(EXTRA_SHOW_FRAGMENT, RedactionInterstitialFragment.class.getName())
+ .putExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, true)
+ .putExtra(EXTRA_PREFS_SET_BACK_TEXT, (String) null)
+ .putExtra(EXTRA_PREFS_SET_NEXT_TEXT, getString(
+ R.string.app_notifications_dialog_done));
+ return modIntent;
+ }
+
+ @Override
+ protected boolean isValidFragment(String fragmentName) {
+ return RedactionInterstitialFragment.class.getName().equals(fragmentName);
+ }
+}
diff --git a/src/com/android/settings/notification/SettingPref.java b/src/com/android/settings/notification/SettingPref.java
new file mode 100644
index 0000000..a06c35a
--- /dev/null
+++ b/src/com/android/settings/notification/SettingPref.java
@@ -0,0 +1,151 @@
+/*
+ * 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.notification;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.preference.Preference;
+import android.preference.TwoStatePreference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.provider.Settings.Global;
+import android.provider.Settings.System;
+
+import com.android.settings.SettingsPreferenceFragment;
+
+/** Helper to manage a two-state or dropdown preference bound to a global or system setting. */
+public class SettingPref {
+ public static final int TYPE_GLOBAL = 1;
+ public static final int TYPE_SYSTEM = 2;
+
+ protected final int mType;
+ private final String mKey;
+ protected final String mSetting;
+ protected final int mDefault;
+ private final int[] mValues;
+ private final Uri mUri;
+
+ protected TwoStatePreference mTwoState;
+ protected DropDownPreference mDropDown;
+
+ public SettingPref(int type, String key, String setting, int def, int... values) {
+ mType = type;
+ mKey = key;
+ mSetting = setting;
+ mDefault = def;
+ mValues = values;
+ mUri = getUriFor(mType, mSetting);
+ }
+
+ public boolean isApplicable(Context context) {
+ return true;
+ }
+
+ protected String getCaption(Resources res, int value) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Preference init(SettingsPreferenceFragment settings) {
+ final Context context = settings.getActivity();
+ Preference p = settings.getPreferenceScreen().findPreference(mKey);
+ if (p != null && !isApplicable(context)) {
+ settings.getPreferenceScreen().removePreference(p);
+ p = null;
+ }
+ if (p instanceof TwoStatePreference) {
+ mTwoState = (TwoStatePreference) p;
+ } else if (p instanceof DropDownPreference) {
+ mDropDown = (DropDownPreference) p;
+ for (int value : mValues) {
+ mDropDown.addItem(getCaption(context.getResources(), value), value);
+ }
+ }
+ update(context);
+ if (mTwoState != null) {
+ p.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ setSetting(context, (Boolean) newValue ? 1 : 0);
+ return true;
+ }
+ });
+ return mTwoState;
+ }
+ if (mDropDown != null) {
+ mDropDown.setCallback(new DropDownPreference.Callback() {
+ @Override
+ public boolean onItemSelected(int pos, Object value) {
+ return setSetting(context, (Integer) value);
+ }
+ });
+ return mDropDown;
+ }
+ return null;
+ }
+
+ protected boolean setSetting(Context context, int value) {
+ return putInt(mType, context.getContentResolver(), mSetting, value);
+ }
+
+ public Uri getUri() {
+ return mUri;
+ }
+
+ public String getKey() {
+ return mKey;
+ }
+
+ public void update(Context context) {
+ final int val = getInt(mType, context.getContentResolver(), mSetting, mDefault);
+ if (mTwoState != null) {
+ mTwoState.setChecked(val != 0);
+ } else if (mDropDown != null) {
+ mDropDown.setSelectedValue(val);
+ }
+ }
+
+ private static Uri getUriFor(int type, String setting) {
+ switch(type) {
+ case TYPE_GLOBAL:
+ return Global.getUriFor(setting);
+ case TYPE_SYSTEM:
+ return System.getUriFor(setting);
+ }
+ throw new IllegalArgumentException();
+ }
+
+ protected static boolean putInt(int type, ContentResolver cr, String setting, int value) {
+ switch(type) {
+ case TYPE_GLOBAL:
+ return Global.putInt(cr, setting, value);
+ case TYPE_SYSTEM:
+ return System.putInt(cr, setting, value);
+ }
+ throw new IllegalArgumentException();
+ }
+
+ protected static int getInt(int type, ContentResolver cr, String setting, int def) {
+ switch(type) {
+ case TYPE_GLOBAL:
+ return Global.getInt(cr, setting, def);
+ case TYPE_SYSTEM:
+ return System.getInt(cr, setting, def);
+ }
+ throw new IllegalArgumentException();
+ }
+} \ No newline at end of file
diff --git a/src/com/android/settings/notification/VolumeSeekBarPreference.java b/src/com/android/settings/notification/VolumeSeekBarPreference.java
new file mode 100644
index 0000000..f94e6a1
--- /dev/null
+++ b/src/com/android/settings/notification/VolumeSeekBarPreference.java
@@ -0,0 +1,139 @@
+/*
+ * 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.notification;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.preference.PreferenceManager;
+import android.preference.SeekBarPreference;
+import android.preference.SeekBarVolumizer;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.SeekBar;
+
+import com.android.settings.R;
+
+/** A slider preference that directly controls an audio stream volume (no dialog) **/
+public class VolumeSeekBarPreference extends SeekBarPreference
+ implements PreferenceManager.OnActivityStopListener {
+ private static final String TAG = "VolumeSeekBarPreference";
+
+ private int mStream;
+ private SeekBar mSeekBar;
+ private SeekBarVolumizer mVolumizer;
+ private Callback mCallback;
+ private ImageView mIconView;
+
+ public VolumeSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ public VolumeSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public VolumeSeekBarPreference(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public VolumeSeekBarPreference(Context context) {
+ this(context, null);
+ }
+
+ public void setStream(int stream) {
+ mStream = stream;
+ }
+
+ public void setCallback(Callback callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public void onActivityStop() {
+ if (mVolumizer != null) {
+ mVolumizer.stop();
+ }
+ }
+
+ @Override
+ protected void onBindView(View view) {
+ super.onBindView(view);
+ if (mStream == 0) {
+ Log.w(TAG, "No stream found, not binding volumizer");
+ return;
+ }
+ getPreferenceManager().registerOnActivityStopListener(this);
+ final SeekBar seekBar = (SeekBar) view.findViewById(com.android.internal.R.id.seekbar);
+ if (seekBar == mSeekBar) return;
+ mSeekBar = seekBar;
+ final SeekBarVolumizer.Callback sbvc = new SeekBarVolumizer.Callback() {
+ @Override
+ public void onSampleStarting(SeekBarVolumizer sbv) {
+ if (mCallback != null) {
+ mCallback.onSampleStarting(sbv);
+ }
+ }
+ };
+ final Uri sampleUri = mStream == AudioManager.STREAM_MUSIC ? getMediaVolumeUri() : null;
+ if (mVolumizer == null) {
+ mVolumizer = new SeekBarVolumizer(getContext(), mStream, sampleUri, sbvc) {
+ // we need to piggyback on SBV's SeekBar listener to update our icon
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress,
+ boolean fromTouch) {
+ super.onProgressChanged(seekBar, progress, fromTouch);
+ mCallback.onStreamValueChanged(mStream, progress);
+ }
+ };
+ }
+ mVolumizer.setSeekBar(mSeekBar);
+ mIconView = (ImageView) view.findViewById(com.android.internal.R.id.icon);
+ mCallback.onStreamValueChanged(mStream, mSeekBar.getProgress());
+ }
+
+ // during initialization, this preference is the SeekBar listener
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress,
+ boolean fromTouch) {
+ super.onProgressChanged(seekBar, progress, fromTouch);
+ mCallback.onStreamValueChanged(mStream, progress);
+ }
+
+ public void showIcon(int resId) {
+ // Instead of using setIcon, which will trigger listeners, this just decorates the
+ // preference temporarily with a new icon.
+ if (mIconView != null) {
+ mIconView.setImageResource(resId);
+ }
+ }
+
+ private Uri getMediaVolumeUri() {
+ return Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://"
+ + getContext().getPackageName()
+ + "/" + R.raw.media_volume);
+ }
+
+ public interface Callback {
+ void onSampleStarting(SeekBarVolumizer sbv);
+ void onStreamValueChanged(int stream, int progress);
+ }
+}
diff --git a/src/com/android/settings/notification/ZenModeAutomaticConditionSelection.java b/src/com/android/settings/notification/ZenModeAutomaticConditionSelection.java
new file mode 100644
index 0000000..0e77632
--- /dev/null
+++ b/src/com/android/settings/notification/ZenModeAutomaticConditionSelection.java
@@ -0,0 +1,157 @@
+/*
+ * 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.notification;
+
+import android.animation.LayoutTransition;
+import android.app.INotificationManager;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.service.notification.Condition;
+import android.service.notification.IConditionListener;
+import android.util.ArraySet;
+import android.util.Log;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.LinearLayout;
+
+import com.android.settings.R;
+
+public class ZenModeAutomaticConditionSelection extends LinearLayout {
+ private static final String TAG = "ZenModeAutomaticConditionSelection";
+ private static final boolean DEBUG = true;
+
+ private final INotificationManager mNoMan;
+ private final H mHandler = new H();
+ private final Context mContext;
+ private final ArraySet<Uri> mSelectedConditions = new ArraySet<Uri>();
+
+ public ZenModeAutomaticConditionSelection(Context context) {
+ super(context);
+ mContext = context;
+ setOrientation(VERTICAL);
+ setLayoutTransition(new LayoutTransition());
+ final int p = mContext.getResources().getDimensionPixelSize(R.dimen.content_margin_left);
+ setPadding(p, p, p, 0);
+ mNoMan = INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ refreshSelectedConditions();
+ }
+
+ private void refreshSelectedConditions() {
+ try {
+ final Condition[] automatic = mNoMan.getAutomaticZenModeConditions();
+ mSelectedConditions.clear();
+ if (automatic != null) {
+ for (Condition c : automatic) {
+ mSelectedConditions.add(c.id);
+ }
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error calling getAutomaticZenModeConditions", e);
+ }
+ }
+
+ private CheckBox newCheckBox(Object tag) {
+ final CheckBox button = new CheckBox(mContext);
+ button.setTag(tag);
+ button.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ setSelectedCondition((Uri)button.getTag(), isChecked);
+ }
+ });
+ addView(button);
+ return button;
+ }
+
+ private void setSelectedCondition(Uri conditionId, boolean selected) {
+ if (DEBUG) Log.d(TAG, "setSelectedCondition conditionId=" + conditionId
+ + " selected=" + selected);
+ if (selected) {
+ mSelectedConditions.add(conditionId);
+ } else {
+ mSelectedConditions.remove(conditionId);
+ }
+ final Uri[] automatic = new Uri[mSelectedConditions.size()];
+ for (int i = 0; i < automatic.length; i++) {
+ automatic[i] = mSelectedConditions.valueAt(i);
+ }
+ try {
+ mNoMan.setAutomaticZenModeConditions(automatic);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error calling setAutomaticZenModeConditions", e);
+ }
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ requestZenModeConditions(Condition.FLAG_RELEVANT_ALWAYS);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ requestZenModeConditions(0 /*none*/);
+ }
+
+ protected void requestZenModeConditions(int relevance) {
+ if (DEBUG) Log.d(TAG, "requestZenModeConditions " + Condition.relevanceToString(relevance));
+ try {
+ mNoMan.requestZenModeConditions(mListener, relevance);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error calling requestZenModeConditions", e);
+ }
+ }
+
+ protected void handleConditions(Condition[] conditions) {
+ for (final Condition c : conditions) {
+ CheckBox v = (CheckBox) findViewWithTag(c.id);
+ if (c.state != Condition.STATE_ERROR) {
+ if (v == null) {
+ v = newCheckBox(c.id);
+ }
+ }
+ if (v != null) {
+ v.setText(c.summary);
+ v.setEnabled(c.state != Condition.STATE_ERROR);
+ v.setChecked(mSelectedConditions.contains(c.id));
+ }
+ }
+ }
+
+ private final IConditionListener mListener = new IConditionListener.Stub() {
+ @Override
+ public void onConditionsReceived(Condition[] conditions) {
+ if (conditions == null || conditions.length == 0) return;
+ mHandler.obtainMessage(H.CONDITIONS, conditions).sendToTarget();
+ }
+ };
+
+ private final class H extends Handler {
+ private static final int CONDITIONS = 1;
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == CONDITIONS) handleConditions((Condition[])msg.obj);
+ }
+ }
+}
diff --git a/src/com/android/settings/notification/ZenModeConditionSelection.java b/src/com/android/settings/notification/ZenModeConditionSelection.java
new file mode 100644
index 0000000..856a7f6
--- /dev/null
+++ b/src/com/android/settings/notification/ZenModeConditionSelection.java
@@ -0,0 +1,153 @@
+/*
+ * 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.notification;
+
+import android.animation.LayoutTransition;
+import android.app.INotificationManager;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.service.notification.Condition;
+import android.service.notification.IConditionListener;
+import android.service.notification.ZenModeConfig;
+import android.util.Log;
+import android.widget.CompoundButton;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+
+import com.android.settings.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ZenModeConditionSelection extends RadioGroup {
+ private static final String TAG = "ZenModeConditionSelection";
+ private static final boolean DEBUG = true;
+
+ private final INotificationManager mNoMan;
+ private final H mHandler = new H();
+ private final Context mContext;
+ private final List<Condition> mConditions;
+ private Condition mCondition;
+
+ public ZenModeConditionSelection(Context context) {
+ super(context);
+ mContext = context;
+ mConditions = new ArrayList<Condition>();
+ setLayoutTransition(new LayoutTransition());
+ final int p = mContext.getResources().getDimensionPixelSize(R.dimen.content_margin_left);
+ setPadding(p, p, p, 0);
+ mNoMan = INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ final RadioButton b = newRadioButton(null);
+ b.setText(mContext.getString(com.android.internal.R.string.zen_mode_forever));
+ b.setChecked(true);
+ for (int i = ZenModeConfig.MINUTE_BUCKETS.length - 1; i >= 0; --i) {
+ handleCondition(ZenModeConfig.toTimeCondition(ZenModeConfig.MINUTE_BUCKETS[i]));
+ }
+ }
+
+ private RadioButton newRadioButton(Condition condition) {
+ final RadioButton button = new RadioButton(mContext);
+ button.setTag(condition);
+ button.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ if (isChecked) {
+ setCondition((Condition) button.getTag());
+ }
+ }
+ });
+ addView(button);
+ return button;
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ requestZenModeConditions(Condition.FLAG_RELEVANT_NOW);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ requestZenModeConditions(0 /*none*/);
+ }
+
+ protected void requestZenModeConditions(int relevance) {
+ if (DEBUG) Log.d(TAG, "requestZenModeConditions " + Condition.relevanceToString(relevance));
+ try {
+ mNoMan.requestZenModeConditions(mListener, relevance);
+ } catch (RemoteException e) {
+ // noop
+ }
+ }
+
+ protected void handleConditions(Condition[] conditions) {
+ for (Condition c : conditions) {
+ handleCondition(c);
+ }
+ }
+
+ protected void handleCondition(Condition c) {
+ if (mConditions.contains(c)) return;
+ RadioButton v = (RadioButton) findViewWithTag(c.id);
+ if (c.state == Condition.STATE_TRUE || c.state == Condition.STATE_UNKNOWN) {
+ if (v == null) {
+ v = newRadioButton(c);
+ }
+ }
+ if (v != null) {
+ v.setText(c.summary);
+ v.setEnabled(c.state == Condition.STATE_TRUE);
+ }
+ mConditions.add(c);
+ }
+
+ protected void setCondition(Condition c) {
+ if (DEBUG) Log.d(TAG, "setCondition " + c);
+ mCondition = c;
+ }
+
+ public void confirmCondition() {
+ if (DEBUG) Log.d(TAG, "confirmCondition " + mCondition);
+ try {
+ mNoMan.setZenModeCondition(mCondition);
+ } catch (RemoteException e) {
+ // noop
+ }
+ }
+
+ private final IConditionListener mListener = new IConditionListener.Stub() {
+ @Override
+ public void onConditionsReceived(Condition[] conditions) {
+ if (conditions == null || conditions.length == 0) return;
+ mHandler.obtainMessage(H.CONDITIONS, conditions).sendToTarget();
+ }
+ };
+
+ private final class H extends Handler {
+ private static final int CONDITIONS = 1;
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == CONDITIONS) handleConditions((Condition[]) msg.obj);
+ }
+ }
+}
diff --git a/src/com/android/settings/notification/ZenModeDowntimeDaysSelection.java b/src/com/android/settings/notification/ZenModeDowntimeDaysSelection.java
new file mode 100644
index 0000000..3361ad0
--- /dev/null
+++ b/src/com/android/settings/notification/ZenModeDowntimeDaysSelection.java
@@ -0,0 +1,96 @@
+/*
+ * 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.notification;
+
+import android.content.Context;
+import android.service.notification.ZenModeConfig;
+import android.util.SparseBooleanArray;
+import android.view.LayoutInflater;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.ScrollView;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.LinearLayout;
+
+import com.android.settings.R;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+
+public class ZenModeDowntimeDaysSelection extends ScrollView {
+ public static final int[] DAYS = {
+ Calendar.MONDAY, Calendar.TUESDAY, Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY,
+ Calendar.SATURDAY, Calendar.SUNDAY
+ };
+ private static final SimpleDateFormat DAY_FORMAT = new SimpleDateFormat("EEEE");
+
+ private final SparseBooleanArray mDays = new SparseBooleanArray();
+ private final LinearLayout mLayout;
+
+ public ZenModeDowntimeDaysSelection(Context context, String mode) {
+ super(context);
+ mLayout = new LinearLayout(mContext);
+ final int hPad = context.getResources().getDimensionPixelSize(R.dimen.zen_downtime_margin);
+ mLayout.setPadding(hPad, 0, hPad, 0);
+ addView(mLayout);
+ final int[] days = ZenModeConfig.tryParseDays(mode);
+ if (days != null) {
+ for (int i = 0; i < days.length; i++) {
+ mDays.put(days[i], true);
+ }
+ }
+ mLayout.setOrientation(LinearLayout.VERTICAL);
+ final Calendar c = Calendar.getInstance();
+ final LayoutInflater inflater = LayoutInflater.from(context);
+ for (int i = 0; i < DAYS.length; i++) {
+ final int day = DAYS[i];
+ final CheckBox checkBox = (CheckBox) inflater.inflate(R.layout.zen_downtime_day,
+ this, false);
+ c.set(Calendar.DAY_OF_WEEK, day);
+ checkBox.setText(DAY_FORMAT.format(c.getTime()));
+ checkBox.setChecked(mDays.get(day));
+ checkBox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ mDays.put(day, isChecked);
+ onChanged(getMode());
+ }
+ });
+ mLayout.addView(checkBox);
+ }
+ }
+
+ private String getMode() {
+ final StringBuilder sb = new StringBuilder(ZenModeConfig.SLEEP_MODE_DAYS_PREFIX);
+ boolean empty = true;
+ for (int i = 0; i < mDays.size(); i++) {
+ final int day = mDays.keyAt(i);
+ if (!mDays.valueAt(i)) continue;
+ if (empty) {
+ empty = false;
+ } else {
+ sb.append(',');
+ }
+ sb.append(day);
+ }
+ return empty ? null : sb.toString();
+ }
+
+ protected void onChanged(String mode) {
+ // event hook for subclasses
+ }
+}
diff --git a/src/com/android/settings/notification/ZenModeSettings.java b/src/com/android/settings/notification/ZenModeSettings.java
new file mode 100644
index 0000000..7fd3aa1
--- /dev/null
+++ b/src/com/android/settings/notification/ZenModeSettings.java
@@ -0,0 +1,725 @@
+/*
+ * 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.notification;
+
+import static com.android.settings.notification.ZenModeDowntimeDaysSelection.DAYS;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.FragmentManager;
+import android.app.INotificationManager;
+import android.app.TimePickerDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnDismissListener;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.ServiceManager;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.Preference.OnPreferenceClickListener;
+import android.preference.PreferenceCategory;
+import android.preference.PreferenceScreen;
+import android.preference.SwitchPreference;
+import android.provider.Settings.Global;
+import android.service.notification.Condition;
+import android.service.notification.ZenModeConfig;
+import android.text.format.DateFormat;
+import android.util.Log;
+import android.util.SparseArray;
+import android.widget.ScrollView;
+import android.widget.TimePicker;
+
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.Utils;
+import com.android.settings.notification.DropDownPreference.Callback;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.search.Indexable;
+import com.android.settings.search.SearchIndexableRaw;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+import java.util.Objects;
+
+public class ZenModeSettings extends SettingsPreferenceFragment implements Indexable {
+ private static final String TAG = "ZenModeSettings";
+ private static final boolean DEBUG = true;
+
+ private static final String KEY_ZEN_MODE = "zen_mode";
+ private static final String KEY_IMPORTANT = "important";
+ private static final String KEY_CALLS = "phone_calls";
+ private static final String KEY_MESSAGES = "messages";
+ private static final String KEY_STARRED = "starred";
+ private static final String KEY_EVENTS = "events";
+ private static final String KEY_ALARM_INFO = "alarm_info";
+
+ private static final String KEY_DOWNTIME = "downtime";
+ private static final String KEY_DAYS = "days";
+ private static final String KEY_START_TIME = "start_time";
+ private static final String KEY_END_TIME = "end_time";
+
+ private static final String KEY_AUTOMATION = "automation";
+ private static final String KEY_ENTRY = "entry";
+ private static final String KEY_CONDITION_PROVIDERS = "manage_condition_providers";
+
+ private static final SettingPrefWithCallback PREF_ZEN_MODE = new SettingPrefWithCallback(
+ SettingPref.TYPE_GLOBAL, KEY_ZEN_MODE, Global.ZEN_MODE, Global.ZEN_MODE_OFF,
+ Global.ZEN_MODE_OFF, Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ Global.ZEN_MODE_NO_INTERRUPTIONS) {
+ protected String getCaption(Resources res, int value) {
+ switch (value) {
+ case Global.ZEN_MODE_NO_INTERRUPTIONS:
+ return res.getString(R.string.zen_mode_option_no_interruptions);
+ case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
+ return res.getString(R.string.zen_mode_option_important_interruptions);
+ default:
+ return res.getString(R.string.zen_mode_option_off);
+ }
+ }
+ };
+
+ private static final SimpleDateFormat DAY_FORMAT = new SimpleDateFormat("EEE");
+
+ private static SparseArray<String> allKeyTitles(Context context) {
+ final SparseArray<String> rt = new SparseArray<String>();
+ rt.put(R.string.zen_mode_important_category, KEY_IMPORTANT);
+ if (Utils.isVoiceCapable(context)) {
+ rt.put(R.string.zen_mode_phone_calls, KEY_CALLS);
+ rt.put(R.string.zen_mode_option_title, KEY_ZEN_MODE);
+ } else {
+ rt.put(R.string.zen_mode_option_title_novoice, KEY_ZEN_MODE);
+ }
+ rt.put(R.string.zen_mode_messages, KEY_MESSAGES);
+ rt.put(R.string.zen_mode_from_starred, KEY_STARRED);
+ rt.put(R.string.zen_mode_events, KEY_EVENTS);
+ rt.put(R.string.zen_mode_alarm_info, KEY_ALARM_INFO);
+ rt.put(R.string.zen_mode_downtime_category, KEY_DOWNTIME);
+ rt.put(R.string.zen_mode_downtime_days, KEY_DAYS);
+ rt.put(R.string.zen_mode_start_time, KEY_START_TIME);
+ rt.put(R.string.zen_mode_end_time, KEY_END_TIME);
+ rt.put(R.string.zen_mode_automation_category, KEY_AUTOMATION);
+ rt.put(R.string.manage_condition_providers, KEY_CONDITION_PROVIDERS);
+ return rt;
+ }
+
+ private final Handler mHandler = new Handler();
+ private final SettingsObserver mSettingsObserver = new SettingsObserver();
+
+ private Context mContext;
+ private PackageManager mPM;
+ private ZenModeConfig mConfig;
+ private boolean mDisableListeners;
+ private SwitchPreference mCalls;
+ private SwitchPreference mMessages;
+ private DropDownPreference mStarred;
+ private SwitchPreference mEvents;
+ private Preference mDays;
+ private TimePickerPreference mStart;
+ private TimePickerPreference mEnd;
+ private PreferenceCategory mAutomationCategory;
+ private Preference mEntry;
+ private Preference mConditionProviders;
+ private AlertDialog mDialog;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mContext = getActivity();
+ mPM = mContext.getPackageManager();
+
+ addPreferencesFromResource(R.xml.zen_mode_settings);
+ final PreferenceScreen root = getPreferenceScreen();
+
+ mConfig = getZenModeConfig();
+ if (DEBUG) Log.d(TAG, "Loaded mConfig=" + mConfig);
+
+ final Preference zenMode = PREF_ZEN_MODE.init(this);
+ PREF_ZEN_MODE.setCallback(new SettingPrefWithCallback.Callback() {
+ @Override
+ public void onSettingSelected(int value) {
+ if (value != Global.ZEN_MODE_OFF) {
+ showConditionSelection(value);
+ }
+ }
+ });
+ if (!Utils.isVoiceCapable(mContext)) {
+ zenMode.setTitle(R.string.zen_mode_option_title_novoice);
+ }
+
+ final PreferenceCategory important =
+ (PreferenceCategory) root.findPreference(KEY_IMPORTANT);
+
+ mCalls = (SwitchPreference) important.findPreference(KEY_CALLS);
+ if (Utils.isVoiceCapable(mContext)) {
+ mCalls.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if (mDisableListeners) return true;
+ final boolean val = (Boolean) newValue;
+ if (val == mConfig.allowCalls) return true;
+ if (DEBUG) Log.d(TAG, "onPrefChange allowCalls=" + val);
+ final ZenModeConfig newConfig = mConfig.copy();
+ newConfig.allowCalls = val;
+ return setZenModeConfig(newConfig);
+ }
+ });
+ } else {
+ important.removePreference(mCalls);
+ mCalls = null;
+ }
+
+ mMessages = (SwitchPreference) important.findPreference(KEY_MESSAGES);
+ mMessages.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if (mDisableListeners) return true;
+ final boolean val = (Boolean) newValue;
+ if (val == mConfig.allowMessages) return true;
+ if (DEBUG) Log.d(TAG, "onPrefChange allowMessages=" + val);
+ final ZenModeConfig newConfig = mConfig.copy();
+ newConfig.allowMessages = val;
+ return setZenModeConfig(newConfig);
+ }
+ });
+
+ mStarred = (DropDownPreference) important.findPreference(KEY_STARRED);
+ mStarred.addItem(R.string.zen_mode_from_anyone, ZenModeConfig.SOURCE_ANYONE);
+ mStarred.addItem(R.string.zen_mode_from_starred, ZenModeConfig.SOURCE_STAR);
+ mStarred.addItem(R.string.zen_mode_from_contacts, ZenModeConfig.SOURCE_CONTACT);
+ mStarred.setCallback(new DropDownPreference.Callback() {
+ @Override
+ public boolean onItemSelected(int pos, Object newValue) {
+ if (mDisableListeners) return true;
+ final int val = (Integer) newValue;
+ if (val == mConfig.allowFrom) return true;
+ if (DEBUG) Log.d(TAG, "onPrefChange allowFrom=" +
+ ZenModeConfig.sourceToString(val));
+ final ZenModeConfig newConfig = mConfig.copy();
+ newConfig.allowFrom = val;
+ return setZenModeConfig(newConfig);
+ }
+ });
+ important.addPreference(mStarred);
+
+ mEvents = (SwitchPreference) important.findPreference(KEY_EVENTS);
+ mEvents.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if (mDisableListeners) return true;
+ final boolean val = (Boolean) newValue;
+ if (val == mConfig.allowEvents) return true;
+ if (DEBUG) Log.d(TAG, "onPrefChange allowEvents=" + val);
+ final ZenModeConfig newConfig = mConfig.copy();
+ newConfig.allowEvents = val;
+ return setZenModeConfig(newConfig);
+ }
+ });
+
+ final PreferenceCategory downtime = (PreferenceCategory) root.findPreference(KEY_DOWNTIME);
+
+ mDays = downtime.findPreference(KEY_DAYS);
+ mDays.setOnPreferenceClickListener(new OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ new AlertDialog.Builder(mContext)
+ .setTitle(R.string.zen_mode_downtime_days)
+ .setView(new ZenModeDowntimeDaysSelection(mContext, mConfig.sleepMode) {
+ @Override
+ protected void onChanged(String mode) {
+ if (mDisableListeners) return;
+ if (Objects.equals(mode, mConfig.sleepMode)) return;
+ if (DEBUG) Log.d(TAG, "days.onChanged sleepMode=" + mode);
+ final ZenModeConfig newConfig = mConfig.copy();
+ newConfig.sleepMode = mode;
+ setZenModeConfig(newConfig);
+ }
+ })
+ .setOnDismissListener(new OnDismissListener() {
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ updateDays();
+ }
+ })
+ .setPositiveButton(R.string.done_button, null)
+ .show();
+ return true;
+ }
+ });
+
+ final FragmentManager mgr = getFragmentManager();
+
+ mStart = new TimePickerPreference(mContext, mgr);
+ mStart.setKey(KEY_START_TIME);
+ mStart.setTitle(R.string.zen_mode_start_time);
+ mStart.setCallback(new TimePickerPreference.Callback() {
+ @Override
+ public boolean onSetTime(int hour, int minute) {
+ if (mDisableListeners) return true;
+ if (!ZenModeConfig.isValidHour(hour)) return false;
+ if (!ZenModeConfig.isValidMinute(minute)) return false;
+ if (hour == mConfig.sleepStartHour && minute == mConfig.sleepStartMinute) {
+ return true;
+ }
+ if (DEBUG) Log.d(TAG, "onPrefChange sleepStart h=" + hour + " m=" + minute);
+ final ZenModeConfig newConfig = mConfig.copy();
+ newConfig.sleepStartHour = hour;
+ newConfig.sleepStartMinute = minute;
+ return setZenModeConfig(newConfig);
+ }
+ });
+ downtime.addPreference(mStart);
+ mStart.setDependency(mDays.getKey());
+
+ mEnd = new TimePickerPreference(mContext, mgr);
+ mEnd.setKey(KEY_END_TIME);
+ mEnd.setTitle(R.string.zen_mode_end_time);
+ mEnd.setCallback(new TimePickerPreference.Callback() {
+ @Override
+ public boolean onSetTime(int hour, int minute) {
+ if (mDisableListeners) return true;
+ if (!ZenModeConfig.isValidHour(hour)) return false;
+ if (!ZenModeConfig.isValidMinute(minute)) return false;
+ if (hour == mConfig.sleepEndHour && minute == mConfig.sleepEndMinute) {
+ return true;
+ }
+ if (DEBUG) Log.d(TAG, "onPrefChange sleepEnd h=" + hour + " m=" + minute);
+ final ZenModeConfig newConfig = mConfig.copy();
+ newConfig.sleepEndHour = hour;
+ newConfig.sleepEndMinute = minute;
+ return setZenModeConfig(newConfig);
+ }
+ });
+ downtime.addPreference(mEnd);
+ mEnd.setDependency(mDays.getKey());
+
+ mAutomationCategory = (PreferenceCategory) findPreference(KEY_AUTOMATION);
+ mEntry = findPreference(KEY_ENTRY);
+ mEntry.setOnPreferenceClickListener(new OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ new AlertDialog.Builder(mContext)
+ .setTitle(R.string.zen_mode_entry_conditions_title)
+ .setView(new ZenModeAutomaticConditionSelection(mContext))
+ .setOnDismissListener(new OnDismissListener() {
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ refreshAutomationSection();
+ }
+ })
+ .setPositiveButton(R.string.dlg_ok, null)
+ .show();
+ return true;
+ }
+ });
+ mConditionProviders = findPreference(KEY_CONDITION_PROVIDERS);
+
+ updateControls();
+ }
+
+ private void updateDays() {
+ if (mConfig != null) {
+ final int[] days = ZenModeConfig.tryParseDays(mConfig.sleepMode);
+ if (days != null && days.length != 0) {
+ final StringBuilder sb = new StringBuilder();
+ final Calendar c = Calendar.getInstance();
+ for (int i = 0; i < DAYS.length; i++) {
+ final int day = DAYS[i];
+ for (int j = 0; j < days.length; j++) {
+ if (day == days[j]) {
+ c.set(Calendar.DAY_OF_WEEK, day);
+ if (sb.length() > 0) {
+ sb.append(mContext.getString(R.string.summary_divider_text));
+ }
+ sb.append(DAY_FORMAT.format(c.getTime()));
+ break;
+ }
+ }
+ }
+ if (sb.length() > 0) {
+ mDays.setSummary(sb);
+ mDays.notifyDependencyChange(false);
+ return;
+ }
+ }
+ }
+ mDays.setSummary(R.string.zen_mode_downtime_days_none);
+ mDays.notifyDependencyChange(true);
+ }
+
+ private void updateEndSummary() {
+ final int startMin = 60 * mConfig.sleepStartHour + mConfig.sleepStartMinute;
+ final int endMin = 60 * mConfig.sleepEndHour + mConfig.sleepEndMinute;
+ final boolean nextDay = startMin >= endMin;
+ mEnd.setSummaryFormat(nextDay ? R.string.zen_mode_end_time_summary_format : 0);
+ }
+
+ private void updateControls() {
+ mDisableListeners = true;
+ if (mCalls != null) {
+ mCalls.setChecked(mConfig.allowCalls);
+ }
+ mMessages.setChecked(mConfig.allowMessages);
+ mStarred.setSelectedValue(mConfig.allowFrom);
+ mEvents.setChecked(mConfig.allowEvents);
+ updateStarredEnabled();
+ updateDays();
+ mStart.setTime(mConfig.sleepStartHour, mConfig.sleepStartMinute);
+ mEnd.setTime(mConfig.sleepEndHour, mConfig.sleepEndMinute);
+ mDisableListeners = false;
+ refreshAutomationSection();
+ updateEndSummary();
+ }
+
+ private void updateStarredEnabled() {
+ mStarred.setEnabled(mConfig.allowCalls || mConfig.allowMessages);
+ }
+
+ private void refreshAutomationSection() {
+ if (mConditionProviders != null) {
+ final int total = ConditionProviderSettings.getProviderCount(mPM);
+ if (total == 0) {
+ getPreferenceScreen().removePreference(mAutomationCategory);
+ } else {
+ final int n = ConditionProviderSettings.getEnabledProviderCount(mContext);
+ if (n == 0) {
+ mConditionProviders.setSummary(getResources().getString(
+ R.string.manage_condition_providers_summary_zero));
+ } else {
+ mConditionProviders.setSummary(String.format(getResources().getQuantityString(
+ R.plurals.manage_condition_providers_summary_nonzero,
+ n, n)));
+ }
+ final String entrySummary = getEntryConditionSummary();
+ if (n == 0 || entrySummary == null) {
+ mEntry.setSummary(R.string.zen_mode_entry_conditions_summary_none);
+ } else {
+ mEntry.setSummary(entrySummary);
+ }
+ }
+ }
+ }
+
+ private String getEntryConditionSummary() {
+ final INotificationManager nm = INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ try {
+ final Condition[] automatic = nm.getAutomaticZenModeConditions();
+ if (automatic == null || automatic.length == 0) {
+ return null;
+ }
+ final String divider = getString(R.string.summary_divider_text);
+ final StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < automatic.length; i++) {
+ if (i > 0) sb.append(divider);
+ sb.append(automatic[i].summary);
+ }
+ return sb.toString();
+ } catch (Exception e) {
+ Log.w(TAG, "Error calling getAutomaticZenModeConditions", e);
+ return null;
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ updateControls();
+ mSettingsObserver.register();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ mSettingsObserver.unregister();
+ }
+
+ private void updateZenModeConfig() {
+ final ZenModeConfig config = getZenModeConfig();
+ if (Objects.equals(config, mConfig)) return;
+ mConfig = config;
+ if (DEBUG) Log.d(TAG, "updateZenModeConfig mConfig=" + mConfig);
+ updateControls();
+ }
+
+ private ZenModeConfig getZenModeConfig() {
+ final INotificationManager nm = INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ try {
+ return nm.getZenModeConfig();
+ } catch (Exception e) {
+ Log.w(TAG, "Error calling NoMan", e);
+ return new ZenModeConfig();
+ }
+ }
+
+ private boolean setZenModeConfig(ZenModeConfig config) {
+ final INotificationManager nm = INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ try {
+ final boolean success = nm.setZenModeConfig(config);
+ if (success) {
+ mConfig = config;
+ if (DEBUG) Log.d(TAG, "Saved mConfig=" + mConfig);
+ updateEndSummary();
+ updateStarredEnabled();
+ }
+ return success;
+ } catch (Exception e) {
+ Log.w(TAG, "Error calling NoMan", e);
+ return false;
+ }
+ }
+
+ protected void putZenModeSetting(int value) {
+ Global.putInt(getContentResolver(), Global.ZEN_MODE, value);
+ }
+
+ protected void showConditionSelection(final int newSettingsValue) {
+ if (mDialog != null) return;
+
+ final ZenModeConditionSelection zenModeConditionSelection =
+ new ZenModeConditionSelection(mContext);
+ DialogInterface.OnClickListener positiveListener = new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ zenModeConditionSelection.confirmCondition();
+ mDialog = null;
+ }
+ };
+ final int oldSettingsValue = PREF_ZEN_MODE.getValue(mContext);
+ ScrollView scrollView = new ScrollView(mContext);
+ scrollView.addView(zenModeConditionSelection);
+ mDialog = new AlertDialog.Builder(getActivity())
+ .setTitle(PREF_ZEN_MODE.getCaption(getResources(), newSettingsValue))
+ .setView(scrollView)
+ .setPositiveButton(R.string.okay, positiveListener)
+ .setNegativeButton(R.string.cancel_all_caps, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ cancelDialog(oldSettingsValue);
+ }
+ })
+ .setOnCancelListener(new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ cancelDialog(oldSettingsValue);
+ }
+ }).create();
+ mDialog.show();
+ }
+
+ protected void cancelDialog(int oldSettingsValue) {
+ // If not making a decision, reset drop down to current setting.
+ PREF_ZEN_MODE.setValueWithoutCallback(mContext, oldSettingsValue);
+ mDialog = null;
+ }
+
+ // Enable indexing of searchable data
+ public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider() {
+ @Override
+ public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
+ final SparseArray<String> keyTitles = allKeyTitles(context);
+ final int N = keyTitles.size();
+ final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>(N);
+ final Resources res = context.getResources();
+ for (int i = 0; i < N; i++) {
+ final SearchIndexableRaw data = new SearchIndexableRaw(context);
+ data.key = keyTitles.valueAt(i);
+ data.title = res.getString(keyTitles.keyAt(i));
+ data.screenTitle = res.getString(R.string.zen_mode_settings_title);
+ result.add(data);
+ }
+ return result;
+ }
+
+ public List<String> getNonIndexableKeys(Context context) {
+ final ArrayList<String> rt = new ArrayList<String>();
+ if (!Utils.isVoiceCapable(context)) {
+ rt.add(KEY_CALLS);
+ }
+ return rt;
+ }
+ };
+
+ private static class SettingPrefWithCallback extends SettingPref {
+
+ private Callback mCallback;
+ private int mValue;
+
+ public SettingPrefWithCallback(int type, String key, String setting, int def,
+ int... values) {
+ super(type, key, setting, def, values);
+ }
+
+ public void setCallback(Callback callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public void update(Context context) {
+ // Avoid callbacks from non-user changes.
+ mValue = getValue(context);
+ super.update(context);
+ }
+
+ @Override
+ protected boolean setSetting(Context context, int value) {
+ if (value == mValue) return true;
+ mValue = value;
+ if (mCallback != null) {
+ mCallback.onSettingSelected(value);
+ }
+ return super.setSetting(context, value);
+ }
+
+ @Override
+ public Preference init(SettingsPreferenceFragment settings) {
+ Preference ret = super.init(settings);
+ mValue = getValue(settings.getActivity());
+
+ return ret;
+ }
+
+ public boolean setValueWithoutCallback(Context context, int value) {
+ // Set the current value ahead of time, this way we won't trigger a callback.
+ mValue = value;
+ return putInt(mType, context.getContentResolver(), mSetting, value);
+ }
+
+ public int getValue(Context context) {
+ return getInt(mType, context.getContentResolver(), mSetting, mDefault);
+ }
+
+ public interface Callback {
+ void onSettingSelected(int value);
+ }
+ }
+
+ private final class SettingsObserver extends ContentObserver {
+ private final Uri ZEN_MODE_URI = Global.getUriFor(Global.ZEN_MODE);
+ private final Uri ZEN_MODE_CONFIG_ETAG_URI = Global.getUriFor(Global.ZEN_MODE_CONFIG_ETAG);
+
+ public SettingsObserver() {
+ super(mHandler);
+ }
+
+ public void register() {
+ getContentResolver().registerContentObserver(ZEN_MODE_URI, false, this);
+ getContentResolver().registerContentObserver(ZEN_MODE_CONFIG_ETAG_URI, false, this);
+ }
+
+ public void unregister() {
+ getContentResolver().unregisterContentObserver(this);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ super.onChange(selfChange, uri);
+ if (ZEN_MODE_URI.equals(uri)) {
+ PREF_ZEN_MODE.update(mContext);
+ }
+ if (ZEN_MODE_CONFIG_ETAG_URI.equals(uri)) {
+ updateZenModeConfig();
+ }
+ }
+ }
+
+ private static class TimePickerPreference extends Preference {
+ private final Context mContext;
+
+ private int mSummaryFormat;
+ private int mHourOfDay;
+ private int mMinute;
+ private Callback mCallback;
+
+ public TimePickerPreference(Context context, final FragmentManager mgr) {
+ super(context);
+ mContext = context;
+ setPersistent(false);
+ setOnPreferenceClickListener(new OnPreferenceClickListener(){
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ final TimePickerFragment frag = new TimePickerFragment();
+ frag.pref = TimePickerPreference.this;
+ frag.show(mgr, TimePickerPreference.class.getName());
+ return true;
+ }
+ });
+ }
+
+ public void setCallback(Callback callback) {
+ mCallback = callback;
+ }
+
+ public void setSummaryFormat(int resId) {
+ mSummaryFormat = resId;
+ updateSummary();
+ }
+
+ public void setTime(int hourOfDay, int minute) {
+ if (mCallback != null && !mCallback.onSetTime(hourOfDay, minute)) return;
+ mHourOfDay = hourOfDay;
+ mMinute = minute;
+ updateSummary();
+ }
+
+ private void updateSummary() {
+ final Calendar c = Calendar.getInstance();
+ c.set(Calendar.HOUR_OF_DAY, mHourOfDay);
+ c.set(Calendar.MINUTE, mMinute);
+ String time = DateFormat.getTimeFormat(mContext).format(c.getTime());
+ if (mSummaryFormat != 0) {
+ time = mContext.getResources().getString(mSummaryFormat, time);
+ }
+ setSummary(time);
+ }
+
+ public static class TimePickerFragment extends DialogFragment implements
+ TimePickerDialog.OnTimeSetListener {
+ public TimePickerPreference pref;
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final boolean usePref = pref != null && pref.mHourOfDay >= 0 && pref.mMinute >= 0;
+ final Calendar c = Calendar.getInstance();
+ final int hour = usePref ? pref.mHourOfDay : c.get(Calendar.HOUR_OF_DAY);
+ final int minute = usePref ? pref.mMinute : c.get(Calendar.MINUTE);
+ return new TimePickerDialog(getActivity(), this, hour, minute,
+ DateFormat.is24HourFormat(getActivity()));
+ }
+
+ public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
+ if (pref != null) {
+ pref.setTime(hourOfDay, minute);
+ }
+ }
+ }
+
+ public interface Callback {
+ boolean onSetTime(int hour, int minute);
+ }
+ }
+}
diff --git a/src/com/android/settings/print/PrintJobSettingsFragment.java b/src/com/android/settings/print/PrintJobSettingsFragment.java
index f420a82..34db97b 100644
--- a/src/com/android/settings/print/PrintJobSettingsFragment.java
+++ b/src/com/android/settings/print/PrintJobSettingsFragment.java
@@ -80,7 +80,7 @@ public class PrintJobSettingsFragment extends SettingsPreferenceFragment {
Context.PRINT_SERVICE)).getGlobalPrintManagerForUser(
ActivityManager.getCurrentUser());
- getActivity().setTitle(R.string.print_print_job);
+ getActivity().getActionBar().setTitle(R.string.print_print_job);
processArguments();
@@ -112,13 +112,18 @@ public class PrintJobSettingsFragment extends SettingsPreferenceFragment {
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
- if (!mPrintJob.getInfo().isCancelling()) {
+ PrintJob printJob = getPrintJob();
+ if (printJob == null) {
+ return;
+ }
+
+ if (!printJob.getInfo().isCancelling()) {
MenuItem cancel = menu.add(0, MENU_ITEM_ID_CANCEL, Menu.NONE,
getString(R.string.print_cancel));
cancel.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
}
- if (mPrintJob.isFailed()) {
+ if (printJob.isFailed()) {
MenuItem restart = menu.add(0, MENU_ITEM_ID_RESTART, Menu.NONE,
getString(R.string.print_restart));
restart.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
@@ -129,13 +134,13 @@ public class PrintJobSettingsFragment extends SettingsPreferenceFragment {
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case MENU_ITEM_ID_CANCEL: {
- mPrintJob.cancel();
+ getPrintJob().cancel();
finish();
return true;
}
case MENU_ITEM_ID_RESTART: {
- mPrintJob.restart();
+ getPrintJob().restart();
finish();
return true;
}
@@ -152,25 +157,32 @@ public class PrintJobSettingsFragment extends SettingsPreferenceFragment {
}
}
+ private PrintJob getPrintJob() {
+ if (mPrintJob == null) {
+ mPrintJob = mPrintManager.getPrintJob(mPrintJobId);
+ }
+ return mPrintJob;
+ }
+
private void updateUi() {
- mPrintJob = mPrintManager.getPrintJob(mPrintJobId);
+ PrintJob printJob = getPrintJob();
- if (mPrintJob == null) {
+ if (printJob == null) {
finish();
return;
}
- if (mPrintJob.isCancelled() || mPrintJob.isCompleted()) {
+ if (printJob.isCancelled() || printJob.isCompleted()) {
finish();
return;
}
- PrintJobInfo info = mPrintJob.getInfo();
+ PrintJobInfo info = printJob.getInfo();
switch (info.getState()) {
case PrintJobInfo.STATE_QUEUED:
case PrintJobInfo.STATE_STARTED: {
- if (!mPrintJob.getInfo().isCancelling()) {
+ if (!printJob.getInfo().isCancelling()) {
mPrintJobPreference.setTitle(getString(
R.string.print_printing_state_title_template, info.getLabel()));
} else {
@@ -185,7 +197,7 @@ public class PrintJobSettingsFragment extends SettingsPreferenceFragment {
} break;
case PrintJobInfo.STATE_BLOCKED: {
- if (!mPrintJob.getInfo().isCancelling()) {
+ if (!printJob.getInfo().isCancelling()) {
mPrintJobPreference.setTitle(getString(
R.string.print_blocked_state_title_template, info.getLabel()));
} else {
@@ -203,12 +215,12 @@ public class PrintJobSettingsFragment extends SettingsPreferenceFragment {
switch (info.getState()) {
case PrintJobInfo.STATE_QUEUED:
case PrintJobInfo.STATE_STARTED: {
- mPrintJobPreference.setIcon(com.android.internal.R.drawable.ic_print);
+ mPrintJobPreference.setIcon(R.drawable.ic_print);
} break;
case PrintJobInfo.STATE_FAILED:
case PrintJobInfo.STATE_BLOCKED: {
- mPrintJobPreference.setIcon(com.android.internal.R.drawable.ic_print_error);
+ mPrintJobPreference.setIcon(R.drawable.ic_print_error);
} break;
}
diff --git a/src/com/android/settings/print/PrintServiceSettingsFragment.java b/src/com/android/settings/print/PrintServiceSettingsFragment.java
index 326dd78..49fd6df 100644
--- a/src/com/android/settings/print/PrintServiceSettingsFragment.java
+++ b/src/com/android/settings/print/PrintServiceSettingsFragment.java
@@ -16,7 +16,6 @@
package com.android.settings.print;
-import android.app.ActionBar;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
@@ -38,16 +37,13 @@ import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
-import android.preference.PreferenceActivity;
import android.print.PrintManager;
import android.print.PrinterDiscoverySession;
import android.print.PrinterDiscoverySession.OnPrintersChangeListener;
import android.print.PrinterId;
import android.print.PrinterInfo;
-import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
-import android.view.Gravity;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
@@ -55,29 +51,31 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
import android.widget.BaseAdapter;
-import android.widget.CompoundButton;
-import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.SearchView;
+import android.widget.Switch;
import android.widget.TextView;
import com.android.settings.R;
+import com.android.settings.SettingsActivity;
import com.android.settings.SettingsPreferenceFragment;
-import com.android.settings.print.PrintSettingsFragment.ToggleSwitch;
-import com.android.settings.print.PrintSettingsFragment.ToggleSwitch.OnBeforeCheckedChangeListener;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+
+import com.android.settings.widget.SwitchBar;
+import com.android.settings.widget.ToggleSwitch;
+
/**
* Fragment with print service settings.
*/
public class PrintServiceSettingsFragment extends SettingsPreferenceFragment
- implements DialogInterface.OnClickListener {
+ implements DialogInterface.OnClickListener, SwitchBar.OnSwitchChangeListener {
private static final int LOADER_ID_PRINTERS_LOADER = 1;
@@ -113,6 +111,7 @@ public class PrintServiceSettingsFragment extends SettingsPreferenceFragment
}
};
+ private SwitchBar mSwitchBar;
private ToggleSwitch mToggleSwitch;
private String mPreferenceKey;
@@ -130,12 +129,14 @@ public class PrintServiceSettingsFragment extends SettingsPreferenceFragment
private PrintersAdapter mPrintersAdapter;
+ // TODO: Showing sub-sub fragment does not handle the activity title
+ // so we do it but this is wrong. Do a real fix when there is time.
+ private CharSequence mOldActivityTitle;
+
private int mLastUnfilteredItemCount;
private boolean mServiceEnabled;
- private AnnounceFilterResult mAnnounceFilterResult;
-
@Override
public void onResume() {
super.onResume();
@@ -147,9 +148,6 @@ public class PrintServiceSettingsFragment extends SettingsPreferenceFragment
@Override
public void onPause() {
mSettingsContentObserver.unregister(getContentResolver());
- if (mAnnounceFilterResult != null) {
- mAnnounceFilterResult.remove();
- }
super.onPause();
}
@@ -162,20 +160,23 @@ public class PrintServiceSettingsFragment extends SettingsPreferenceFragment
@Override
public void onDestroyView() {
- getActivity().getActionBar().setCustomView(null);
- mToggleSwitch.setOnBeforeCheckedChangeListener(null);
+ if (mOldActivityTitle != null) {
+ getActivity().getActionBar().setTitle(mOldActivityTitle);
+ }
super.onDestroyView();
+ mSwitchBar.removeOnSwitchChangeListener(this);
+ mSwitchBar.hide();
}
private void onPreferenceToggled(String preferenceKey, boolean enabled) {
ComponentName service = ComponentName.unflattenFromString(preferenceKey);
- List<ComponentName> services = SettingsUtils.readEnabledPrintServices(getActivity());
+ List<ComponentName> services = PrintSettingsUtils.readEnabledPrintServices(getActivity());
if (enabled) {
services.add(service);
} else {
services.remove(service);
}
- SettingsUtils.writeEnabledPrintServices(getActivity(), services);
+ PrintSettingsUtils.writeEnabledPrintServices(getActivity(), services);
}
@Override
@@ -192,7 +193,6 @@ public class PrintServiceSettingsFragment extends SettingsPreferenceFragment
}
return new AlertDialog.Builder(getActivity())
.setTitle(title)
- .setIconAttribute(android.R.attr.alertDialogIcon)
.setMessage(message)
.setCancelable(true)
.setPositiveButton(android.R.string.ok, this)
@@ -206,13 +206,13 @@ public class PrintServiceSettingsFragment extends SettingsPreferenceFragment
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
checked = true;
- mToggleSwitch.setCheckedInternal(checked);
+ mSwitchBar.setCheckedInternal(checked);
getArguments().putBoolean(PrintSettingsFragment.EXTRA_CHECKED, checked);
onPreferenceToggled(mPreferenceKey, checked);
break;
case DialogInterface.BUTTON_NEGATIVE:
checked = false;
- mToggleSwitch.setCheckedInternal(checked);
+ mSwitchBar.setCheckedInternal(checked);
getArguments().putBoolean(PrintSettingsFragment.EXTRA_CHECKED, checked);
onPreferenceToggled(mPreferenceKey, checked);
break;
@@ -233,7 +233,8 @@ public class PrintServiceSettingsFragment extends SettingsPreferenceFragment
if (emptyView == null) {
emptyView = getActivity().getLayoutInflater().inflate(
R.layout.empty_print_state, contentRoot, false);
- emptyView.setContentDescription(getString(R.string.print_service_disabled));
+ ImageView iconView = (ImageView) emptyView.findViewById(R.id.icon);
+ iconView.setContentDescription(getString(R.string.print_service_disabled));
TextView textView = (TextView) emptyView.findViewById(R.id.message);
textView.setText(R.string.print_service_disabled);
contentRoot.addView(emptyView);
@@ -259,7 +260,8 @@ public class PrintServiceSettingsFragment extends SettingsPreferenceFragment
if (emptyView == null) {
emptyView = getActivity().getLayoutInflater().inflate(
R.layout.empty_print_state, contentRoot, false);
- emptyView.setContentDescription(getString(R.string.print_no_printers_found));
+ ImageView iconView = (ImageView) emptyView.findViewById(R.id.icon);
+ iconView.setContentDescription(getString(R.string.print_no_printers_found));
TextView textView = (TextView) emptyView.findViewById(R.id.message);
textView.setText(R.string.print_no_printers_found);
contentRoot.addView(emptyView);
@@ -269,13 +271,13 @@ public class PrintServiceSettingsFragment extends SettingsPreferenceFragment
}
private void updateUiForServiceState() {
- List<ComponentName> services = SettingsUtils.readEnabledPrintServices(getActivity());
+ List<ComponentName> services = PrintSettingsUtils.readEnabledPrintServices(getActivity());
mServiceEnabled = services.contains(mComponentName);
if (mServiceEnabled) {
- mToggleSwitch.setCheckedInternal(true);
+ mSwitchBar.setCheckedInternal(true);
mPrintersAdapter.enable();
} else {
- mToggleSwitch.setCheckedInternal(false);
+ mSwitchBar.setCheckedInternal(false);
mPrintersAdapter.disable();
}
getActivity().invalidateOptionsMenu();
@@ -285,13 +287,19 @@ public class PrintServiceSettingsFragment extends SettingsPreferenceFragment
mPrintersAdapter = new PrintersAdapter();
mPrintersAdapter.registerDataSetObserver(mDataObserver);
- mToggleSwitch = createAndAddActionBarToggleSwitch(getActivity());
- mToggleSwitch.setOnBeforeCheckedChangeListener(new OnBeforeCheckedChangeListener() {
+ final SettingsActivity activity = (SettingsActivity) getActivity();
+
+ mSwitchBar = activity.getSwitchBar();
+ mSwitchBar.addOnSwitchChangeListener(this);
+ mSwitchBar.show();
+
+ mToggleSwitch = mSwitchBar.getSwitch();
+ mToggleSwitch.setOnBeforeCheckedChangeListener(new ToggleSwitch.OnBeforeCheckedChangeListener() {
@Override
public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) {
if (checked) {
if (!TextUtils.isEmpty(mEnableWarningMessage)) {
- toggleSwitch.setCheckedInternal(false);
+ mSwitchBar.setCheckedInternal(false);
getArguments().putBoolean(PrintSettingsFragment.EXTRA_CHECKED, false);
showDialog(DIALOG_ID_ENABLE_WARNING);
return true;
@@ -303,17 +311,17 @@ public class PrintServiceSettingsFragment extends SettingsPreferenceFragment
return false;
}
});
- mToggleSwitch.setOnCheckedChangeListener(new OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- updateEmptyView();
- }
- });
getListView().setSelector(new ColorDrawable(Color.TRANSPARENT));
getListView().setAdapter(mPrintersAdapter);
}
+
+ @Override
+ public void onSwitchChanged(Switch switchView, boolean isChecked) {
+ updateEmptyView();
+ }
+
private void updateUiForArguments() {
Bundle arguments = getArguments();
@@ -322,17 +330,7 @@ public class PrintServiceSettingsFragment extends SettingsPreferenceFragment
// Enabled.
final boolean enabled = arguments.getBoolean(PrintSettingsFragment.EXTRA_CHECKED);
- mToggleSwitch.setCheckedInternal(enabled);
-
- // Title.
- PreferenceActivity activity = (PreferenceActivity) getActivity();
- if (!activity.onIsMultiPane() || activity.onIsHidingHeaders()) {
- // PreferenceActivity allows passing as an extra only title by its
- // resource id but we do not have the resource id for the print
- // service label. Therefore, we do it ourselves.
- String title = arguments.getString(PrintSettingsFragment.EXTRA_TITLE);
- getActivity().setTitle(title);
- }
+ mSwitchBar.setCheckedInternal(enabled);
// Settings title and intent.
String settingsTitle = arguments.getString(PrintSettingsFragment.EXTRA_SETTINGS_TITLE);
@@ -446,20 +444,6 @@ public class PrintServiceSettingsFragment extends SettingsPreferenceFragment
}
}
- private ToggleSwitch createAndAddActionBarToggleSwitch(Activity activity) {
- ToggleSwitch toggleSwitch = new ToggleSwitch(activity);
- final int padding = activity.getResources().getDimensionPixelSize(
- R.dimen.action_bar_switch_padding);
- toggleSwitch.setPaddingRelative(0, 0, padding, 0);
- activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
- ActionBar.DISPLAY_SHOW_CUSTOM);
- activity.getActionBar().setCustomView(toggleSwitch,
- new ActionBar.LayoutParams(ActionBar.LayoutParams.WRAP_CONTENT,
- ActionBar.LayoutParams.WRAP_CONTENT,
- Gravity.CENTER_VERTICAL | Gravity.END));
- return toggleSwitch;
- }
-
private static abstract class SettingsContentObserver extends ContentObserver {
public SettingsContentObserver(Handler handler) {
@@ -467,8 +451,8 @@ public class PrintServiceSettingsFragment extends SettingsPreferenceFragment
}
public void register(ContentResolver contentResolver) {
- contentResolver.registerContentObserver(Settings.Secure.getUriFor(
- Settings.Secure.ENABLED_PRINT_SERVICES), false, this);
+ contentResolver.registerContentObserver(android.provider.Settings.Secure.getUriFor(
+ android.provider.Settings.Secure.ENABLED_PRINT_SERVICES), false, this);
}
public void unregister(ContentResolver contentResolver) {
@@ -479,39 +463,6 @@ public class PrintServiceSettingsFragment extends SettingsPreferenceFragment
public abstract void onChange(boolean selfChange, Uri uri);
}
- private final class AnnounceFilterResult implements Runnable {
- private static final int SEARCH_RESULT_ANNOUNCEMENT_DELAY = 1000; // 1 sec
-
- public void post() {
- remove();
- getListView().postDelayed(this, SEARCH_RESULT_ANNOUNCEMENT_DELAY);
- }
-
- public void remove() {
- getListView().removeCallbacks(this);
- }
-
- @Override
- public void run() {
- final int count = getListView().getAdapter().getCount();
- final String text;
- if (count <= 0) {
- text = getString(R.string.print_no_printers_found);
- } else {
- text = getActivity().getResources().getQuantityString(
- R.plurals.print_search_result_count_utterance, count, count);
- }
- getListView().announceForAccessibility(text);
- }
- }
-
- private void announceSearchResult() {
- if (mAnnounceFilterResult == null) {
- mAnnounceFilterResult = new AnnounceFilterResult();
- }
- mAnnounceFilterResult.post();
- }
-
private final class PrintersAdapter extends BaseAdapter
implements LoaderManager.LoaderCallbacks<List<PrinterInfo>>, Filterable {
private final Object mLock = new Object();
@@ -563,9 +514,7 @@ public class PrintServiceSettingsFragment extends SettingsPreferenceFragment
@Override
@SuppressWarnings("unchecked")
protected void publishResults(CharSequence constraint, FilterResults results) {
- final boolean resultCountChanged;
synchronized (mLock) {
- final int oldPrinterCount = mFilteredPrinters.size();
mLastSearchString = constraint;
mFilteredPrinters.clear();
if (results == null) {
@@ -574,10 +523,6 @@ public class PrintServiceSettingsFragment extends SettingsPreferenceFragment
List<PrinterInfo> printers = (List<PrinterInfo>) results.values;
mFilteredPrinters.addAll(printers);
}
- resultCountChanged = (oldPrinterCount != mFilteredPrinters.size());
- }
- if (resultCountChanged) {
- announceSearchResult();
}
notifyDataSetChanged();
}
@@ -794,7 +739,7 @@ public class PrintServiceSettingsFragment extends SettingsPreferenceFragment
}
});
}
- mDiscoverySession.startPrinterDisovery(null);
+ mDiscoverySession.startPrinterDiscovery(null);
}
}
}
diff --git a/src/com/android/settings/print/PrintSettingsFragment.java b/src/com/android/settings/print/PrintSettingsFragment.java
index df38db4..4a34875 100644
--- a/src/com/android/settings/print/PrintSettingsFragment.java
+++ b/src/com/android/settings/print/PrintSettingsFragment.java
@@ -31,6 +31,9 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.Process;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.preference.PreferenceScreen;
@@ -40,6 +43,7 @@ import android.print.PrintJobInfo;
import android.print.PrintManager;
import android.print.PrintManager.PrintJobStateChangeListener;
import android.printservice.PrintServiceInfo;
+import android.provider.SearchIndexableResource;
import android.provider.Settings;
import android.text.TextUtils;
import android.text.format.DateUtils;
@@ -49,24 +53,32 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.Switch;
+import android.widget.AdapterView;
import android.widget.TextView;
import com.android.internal.content.PackageMonitor;
+import com.android.settings.UserSpinnerAdapter;
+import com.android.settings.UserSpinnerAdapter.UserDetails;
import com.android.settings.DialogCreatable;
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 com.android.settings.search.SearchIndexableRaw;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.List;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.Spinner;
+
/**
* Fragment with the top level print settings.
*/
-public class PrintSettingsFragment extends SettingsPreferenceFragment implements DialogCreatable {
-
- static final char ENABLED_PRINT_SERVICES_SEPARATOR = ':';
+public class PrintSettingsFragment extends SettingsPreferenceFragment
+ implements DialogCreatable, Indexable, OnItemSelectedListener {
private static final int LOADER_ID_PRINT_JOBS_LOADER = 1;
@@ -111,6 +123,7 @@ public class PrintSettingsFragment extends SettingsPreferenceFragment implements
private PreferenceCategory mPrintServicesCategory;
private PrintJobsController mPrintJobsController;
+ private UserSpinnerAdapter mProfileSpinnerAdapter;
@Override
public void onCreate(Bundle icicle) {
@@ -119,7 +132,7 @@ public class PrintSettingsFragment extends SettingsPreferenceFragment implements
mActivePrintJobsCategory = (PreferenceCategory) findPreference(
PRINT_JOBS_CATEGORY);
- mPrintServicesCategory= (PreferenceCategory) findPreference(
+ mPrintServicesCategory = (PreferenceCategory) findPreference(
PRINT_SERVICES_CATEGORY);
getPreferenceScreen().removePreference(mActivePrintJobsCategory);
@@ -152,8 +165,8 @@ public class PrintSettingsFragment extends SettingsPreferenceFragment implements
Settings.Secure.PRINT_SERVICE_SEARCH_URI);
if (!TextUtils.isEmpty(searchUri)) {
MenuItem menuItem = menu.add(R.string.print_menu_item_add_service);
- menuItem.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_IF_ROOM);
- menuItem.setIntent(new Intent(Intent.ACTION_VIEW,Uri.parse(searchUri)));
+ menuItem.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_NEVER);
+ menuItem.setIntent(new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri)));
}
}
@@ -162,11 +175,21 @@ public class PrintSettingsFragment extends SettingsPreferenceFragment implements
super.onViewCreated(view, savedInstanceState);
ViewGroup contentRoot = (ViewGroup) getListView().getParent();
View emptyView = getActivity().getLayoutInflater().inflate(
- R.layout.empty_print_state, contentRoot, false);
+ R.layout.empty_print_state, contentRoot, false);
TextView textView = (TextView) emptyView.findViewById(R.id.message);
textView.setText(R.string.print_no_services_installed);
contentRoot.addView(emptyView);
getListView().setEmptyView(emptyView);
+
+ final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
+ mProfileSpinnerAdapter = Utils.createUserSpinnerAdapter(um, getActivity());
+ if (mProfileSpinnerAdapter != null) {
+ Spinner spinner = (Spinner) getActivity().getLayoutInflater().inflate(
+ R.layout.spinner_view, null);
+ spinner.setAdapter(mProfileSpinnerAdapter);
+ spinner.setOnItemSelectedListener(this);
+ setPinnedHeaderView(spinner);
+ }
}
private void updateServicesPreferences() {
@@ -178,7 +201,7 @@ public class PrintSettingsFragment extends SettingsPreferenceFragment implements
mPrintServicesCategory.removeAll();
}
- List<ComponentName> enabledServices = SettingsUtils
+ List<ComponentName> enabledServices = PrintSettingsUtils
.readEnabledPrintServices(getActivity());
List<ResolveInfo> installedServices = getActivity().getPackageManager()
@@ -269,10 +292,26 @@ public class PrintSettingsFragment extends SettingsPreferenceFragment implements
}
}
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ UserHandle selectedUser = mProfileSpinnerAdapter.getUserHandle(position);
+ if (selectedUser.getIdentifier() != UserHandle.myUserId()) {
+ Intent intent = new Intent(Settings.ACTION_PRINT_SETTINGS);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ getActivity().startActivityAsUser(intent, selectedUser);
+ }
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ // Nothing to do
+ }
+
private class SettingsPackageMonitor extends PackageMonitor {
@Override
public void onPackageAdded(String packageName, int uid) {
- mHandler.obtainMessage().sendToTarget();
+ mHandler.obtainMessage().sendToTarget();
}
@Override
@@ -291,36 +330,6 @@ public class PrintSettingsFragment extends SettingsPreferenceFragment implements
}
}
- public static class ToggleSwitch extends Switch {
-
- private OnBeforeCheckedChangeListener mOnBeforeListener;
-
- public static interface OnBeforeCheckedChangeListener {
- public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked);
- }
-
- public ToggleSwitch(Context context) {
- super(context);
- }
-
- public void setOnBeforeCheckedChangeListener(OnBeforeCheckedChangeListener listener) {
- mOnBeforeListener = listener;
- }
-
- @Override
- public void setChecked(boolean checked) {
- if (mOnBeforeListener != null
- && mOnBeforeListener.onBeforeCheckedChanged(this, checked)) {
- return;
- }
- super.setChecked(checked);
- }
-
- public void setCheckedInternal(boolean checked) {
- super.setChecked(checked);
- }
- }
-
private static abstract class SettingsContentObserver extends ContentObserver {
public SettingsContentObserver(Handler handler) {
@@ -414,12 +423,12 @@ public class PrintSettingsFragment extends SettingsPreferenceFragment implements
switch (printJob.getState()) {
case PrintJobInfo.STATE_QUEUED:
case PrintJobInfo.STATE_STARTED: {
- preference.setIcon(com.android.internal.R.drawable.ic_print);
+ preference.setIcon(R.drawable.ic_print);
} break;
case PrintJobInfo.STATE_FAILED:
case PrintJobInfo.STATE_BLOCKED: {
- preference.setIcon(com.android.internal.R.drawable.ic_print_error);
+ preference.setIcon(R.drawable.ic_print_error);
} break;
}
@@ -443,7 +452,7 @@ public class PrintSettingsFragment extends SettingsPreferenceFragment implements
private static final boolean DEBUG = false;
- private List <PrintJobInfo> mPrintJobs = new ArrayList<PrintJobInfo>();
+ private List<PrintJobInfo> mPrintJobs = new ArrayList<PrintJobInfo>();
private final PrintManager mPrintManager;
@@ -453,7 +462,7 @@ public class PrintSettingsFragment extends SettingsPreferenceFragment implements
super(context);
mPrintManager = ((PrintManager) context.getSystemService(
Context.PRINT_SERVICE)).getGlobalPrintManagerForUser(
- ActivityManager.getCurrentUser());
+ ActivityManager.getCurrentUser());
}
@Override
@@ -544,4 +553,53 @@ public class PrintSettingsFragment extends SettingsPreferenceFragment implements
return false;
}
}
+
+ public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider() {
+ @Override
+ public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
+ List<SearchIndexableRaw> indexables = new ArrayList<SearchIndexableRaw>();
+
+ PackageManager packageManager = context.getPackageManager();
+ PrintManager printManager = (PrintManager) context.getSystemService(
+ Context.PRINT_SERVICE);
+
+ String screenTitle = context.getResources().getString(R.string.print_settings);
+ SearchIndexableRaw data = new SearchIndexableRaw(context);
+ data.title = screenTitle;
+ data.screenTitle = screenTitle;
+ indexables.add(data);
+
+ // Indexing all services, regardless if enabled.
+ List<PrintServiceInfo> services = printManager.getInstalledPrintServices();
+ final int serviceCount = services.size();
+ for (int i = 0; i < serviceCount; i++) {
+ PrintServiceInfo service = services.get(i);
+
+ ComponentName componentName = new ComponentName(
+ service.getResolveInfo().serviceInfo.packageName,
+ service.getResolveInfo().serviceInfo.name);
+
+ data = new SearchIndexableRaw(context);
+ data.key = componentName.flattenToString();
+ data.title = service.getResolveInfo().loadLabel(packageManager).toString();
+ data.summaryOn = context.getString(R.string.print_feature_state_on);
+ data.summaryOff = context.getString(R.string.print_feature_state_off);
+ data.screenTitle = screenTitle;
+ indexables.add(data);
+ }
+
+ return indexables;
+ }
+
+ @Override
+ public List<SearchIndexableResource> getXmlResourcesToIndex(Context context,
+ boolean enabled) {
+ List<SearchIndexableResource> indexables = new ArrayList<SearchIndexableResource>();
+ SearchIndexableResource indexable = new SearchIndexableResource(context);
+ indexable.xmlResId = R.xml.print_settings;
+ indexables.add(indexable);
+ return indexables;
+ }
+ };
}
diff --git a/src/com/android/settings/print/SettingsUtils.java b/src/com/android/settings/print/PrintSettingsUtils.java
index 37827e6..24f20d5 100644
--- a/src/com/android/settings/print/SettingsUtils.java
+++ b/src/com/android/settings/print/PrintSettingsUtils.java
@@ -26,11 +26,11 @@ import java.util.ArrayList;import java.util.List;
/**
* Helper methods for reading and writing to print settings.
*/
-public class SettingsUtils {
+public class PrintSettingsUtils {
private static final char ENABLED_PRINT_SERVICES_SEPARATOR = ':';
- private SettingsUtils() {
+ private PrintSettingsUtils() {
/* do nothing */
}
diff --git a/src/com/android/settings/quicklaunch/BookmarkPicker.java b/src/com/android/settings/quicklaunch/BookmarkPicker.java
index 7152abb..32594b6 100644
--- a/src/com/android/settings/quicklaunch/BookmarkPicker.java
+++ b/src/com/android/settings/quicklaunch/BookmarkPicker.java
@@ -97,7 +97,7 @@ public class BookmarkPicker extends ListActivity implements SimpleAdapter.ViewBi
updateListAndAdapter();
}
-
+
@Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.add(0, DISPLAY_MODE_LAUNCH, 0, R.string.quick_launch_display_mode_applications)
diff --git a/src/com/android/settings/quicklaunch/QuickLaunchSettings.java b/src/com/android/settings/quicklaunch/QuickLaunchSettings.java
index 5654323..1367018 100644
--- a/src/com/android/settings/quicklaunch/QuickLaunchSettings.java
+++ b/src/com/android/settings/quicklaunch/QuickLaunchSettings.java
@@ -16,6 +16,7 @@
package com.android.settings.quicklaunch;
+import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
@@ -27,7 +28,6 @@ import android.database.Cursor;
import android.os.Bundle;
import android.os.Handler;
import android.preference.Preference;
-import android.preference.PreferenceActivity;
import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
import android.provider.Settings.Bookmarks;
@@ -40,6 +40,7 @@ import android.view.View;
import android.widget.AdapterView;
import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
import java.net.URISyntaxException;
@@ -49,7 +50,7 @@ import java.net.URISyntaxException;
* Shows a list of possible shortcuts, the current application each is bound to,
* and allows choosing a new bookmark for a shortcut.
*/
-public class QuickLaunchSettings extends PreferenceActivity implements
+public class QuickLaunchSettings extends SettingsPreferenceFragment implements
AdapterView.OnItemLongClickListener, DialogInterface.OnClickListener {
private static final String TAG = "QuickLaunchSettings";
@@ -91,7 +92,7 @@ public class QuickLaunchSettings extends PreferenceActivity implements
private static final String CLEAR_DIALOG_SHORTCUT = "CLEAR_DIALOG_SHORTCUT";
@Override
- protected void onCreate(Bundle savedInstanceState) {
+ public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.quick_launch_settings);
@@ -100,35 +101,47 @@ public class QuickLaunchSettings extends PreferenceActivity implements
mShortcutToPreference = new SparseArray<ShortcutPreference>();
mBookmarksObserver = new BookmarksObserver(mUiHandler);
initShortcutPreferences();
- mBookmarksCursor = managedQuery(Bookmarks.CONTENT_URI, sProjection, null, null);
- getListView().setOnItemLongClickListener(this);
+ mBookmarksCursor = getActivity().getContentResolver().query(Bookmarks.CONTENT_URI,
+ sProjection, null, null, null);
}
@Override
- protected void onResume() {
+ public void onResume() {
super.onResume();
+ mBookmarksCursor = getActivity().getContentResolver().query(Bookmarks.CONTENT_URI,
+ sProjection, null, null, null);
getContentResolver().registerContentObserver(Bookmarks.CONTENT_URI, true,
mBookmarksObserver);
refreshShortcuts();
}
@Override
- protected void onPause() {
+ public void onPause() {
super.onPause();
getContentResolver().unregisterContentObserver(mBookmarksObserver);
}
@Override
- protected void onRestoreInstanceState(Bundle state) {
- super.onRestoreInstanceState(state);
-
- // Restore the clear dialog's info
- mClearDialogBookmarkTitle = state.getString(CLEAR_DIALOG_BOOKMARK_TITLE);
- mClearDialogShortcut = (char) state.getInt(CLEAR_DIALOG_SHORTCUT, 0);
+ public void onStop() {
+ super.onStop();
+ mBookmarksCursor.close();
+ }
+
+ @Override
+ public void onActivityCreated(Bundle state) {
+ super.onActivityCreated(state);
+
+ getListView().setOnItemLongClickListener(this);
+
+ if (state != null) {
+ // Restore the clear dialog's info
+ mClearDialogBookmarkTitle = state.getString(CLEAR_DIALOG_BOOKMARK_TITLE);
+ mClearDialogShortcut = (char) state.getInt(CLEAR_DIALOG_SHORTCUT, 0);
+ }
}
@Override
- protected void onSaveInstanceState(Bundle outState) {
+ public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Save the clear dialog's info
@@ -137,14 +150,13 @@ public class QuickLaunchSettings extends PreferenceActivity implements
}
@Override
- protected Dialog onCreateDialog(int id) {
+ public Dialog onCreateDialog(int id) {
switch (id) {
case DIALOG_CLEAR_SHORTCUT: {
// Create the dialog for clearing a shortcut
- return new AlertDialog.Builder(this)
+ return new AlertDialog.Builder(getActivity())
.setTitle(getString(R.string.quick_launch_clear_dialog_title))
- .setIconAttribute(android.R.attr.alertDialogIcon)
.setMessage(getString(R.string.quick_launch_clear_dialog_message,
mClearDialogShortcut, mClearDialogBookmarkTitle))
.setPositiveButton(R.string.quick_launch_clear_ok_button, this)
@@ -156,18 +168,6 @@ public class QuickLaunchSettings extends PreferenceActivity implements
return super.onCreateDialog(id);
}
- @Override
- protected void onPrepareDialog(int id, Dialog dialog) {
- switch (id) {
-
- case DIALOG_CLEAR_SHORTCUT: {
- AlertDialog alertDialog = (AlertDialog) dialog;
- alertDialog.setMessage(getString(R.string.quick_launch_clear_dialog_message,
- mClearDialogShortcut, mClearDialogBookmarkTitle));
- }
- }
- }
-
private void showClearDialog(ShortcutPreference pref) {
if (!pref.hasBookmark()) return;
@@ -197,7 +197,7 @@ public class QuickLaunchSettings extends PreferenceActivity implements
// Open the screen to pick a bookmark for this shortcut
ShortcutPreference pref = (ShortcutPreference) preference;
- Intent intent = new Intent(this, BookmarkPicker.class);
+ Intent intent = new Intent(getActivity(), BookmarkPicker.class);
intent.putExtra(BookmarkPicker.EXTRA_SHORTCUT, pref.getShortcut());
startActivityForResult(intent, REQUEST_PICK_BOOKMARK);
@@ -214,8 +214,8 @@ public class QuickLaunchSettings extends PreferenceActivity implements
}
@Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (resultCode != RESULT_OK) {
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (resultCode != Activity.RESULT_OK) {
return;
}
@@ -253,7 +253,7 @@ public class QuickLaunchSettings extends PreferenceActivity implements
}
private ShortcutPreference createPreference(char shortcut) {
- ShortcutPreference pref = new ShortcutPreference(QuickLaunchSettings.this, shortcut);
+ ShortcutPreference pref = new ShortcutPreference(getActivity(), shortcut);
mShortcutGroup.addPreference(pref);
mShortcutToPreference.put(shortcut, pref);
return pref;
@@ -303,7 +303,7 @@ public class QuickLaunchSettings extends PreferenceActivity implements
if (shortcut == 0) continue;
ShortcutPreference pref = getOrCreatePreference(shortcut);
- CharSequence title = Bookmarks.getTitle(this, c);
+ CharSequence title = Bookmarks.getTitle(getActivity(), c);
/*
* The title retrieved from Bookmarks.getTitle() will be in
diff --git a/src/com/android/settings/search/BaseSearchIndexProvider.java b/src/com/android/settings/search/BaseSearchIndexProvider.java
new file mode 100644
index 0000000..0fe1944
--- /dev/null
+++ b/src/com/android/settings/search/BaseSearchIndexProvider.java
@@ -0,0 +1,49 @@
+/*
+ * 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.search;
+
+import android.content.Context;
+import android.provider.SearchIndexableResource;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A basic SearchIndexProvider that returns no data to index.
+ */
+public class BaseSearchIndexProvider implements Indexable.SearchIndexProvider {
+
+ private static final List<String> EMPTY_LIST = Collections.<String>emptyList();
+
+ public BaseSearchIndexProvider() {
+ }
+
+ @Override
+ public List<SearchIndexableResource> getXmlResourcesToIndex(Context context, boolean enabled) {
+ return null;
+ }
+
+ @Override
+ public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
+ return null;
+ }
+
+ @Override
+ public List<String> getNonIndexableKeys(Context context) {
+ return EMPTY_LIST;
+ }
+}
diff --git a/src/com/android/settings/search/DynamicIndexableContentMonitor.java b/src/com/android/settings/search/DynamicIndexableContentMonitor.java
new file mode 100644
index 0000000..12bb6ef
--- /dev/null
+++ b/src/com/android/settings/search/DynamicIndexableContentMonitor.java
@@ -0,0 +1,307 @@
+/*
+ * 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.search;
+
+import android.accessibilityservice.AccessibilityService;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.database.ContentObserver;
+import android.hardware.input.InputManager;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.UserHandle;
+import android.print.PrintManager;
+import android.printservice.PrintService;
+import android.printservice.PrintServiceInfo;
+import android.provider.UserDictionary;
+import android.view.accessibility.AccessibilityManager;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import com.android.internal.content.PackageMonitor;
+import com.android.settings.accessibility.AccessibilitySettings;
+import com.android.settings.inputmethod.InputMethodAndLanguageSettings;
+import com.android.settings.print.PrintSettingsFragment;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public final class DynamicIndexableContentMonitor extends PackageMonitor implements
+ InputManager.InputDeviceListener {
+
+ private static final long DELAY_PROCESS_PACKAGE_CHANGE = 2000;
+
+ private static final int MSG_PACKAGE_AVAILABLE = 1;
+ private static final int MSG_PACKAGE_UNAVAILABLE = 2;
+
+ private final List<String> mAccessibilityServices = new ArrayList<String>();
+ private final List<String> mPrintServices = new ArrayList<String>();
+ private final List<String> mImeServices = new ArrayList<String>();
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_PACKAGE_AVAILABLE: {
+ String packageName = (String) msg.obj;
+ handlePackageAvailable(packageName);
+ } break;
+
+ case MSG_PACKAGE_UNAVAILABLE: {
+ String packageName = (String) msg.obj;
+ handlePackageUnavailable(packageName);
+ } break;
+ }
+ }
+ };
+
+ private final ContentObserver mUserDictionaryContentObserver =
+ new UserDictionaryContentObserver(mHandler);
+
+ private Context mContext;
+ private boolean mHasFeaturePrinting;
+ private boolean mHasFeatureIme;
+
+ private static Intent getAccessibilityServiceIntent(String packageName) {
+ final Intent intent = new Intent(AccessibilityService.SERVICE_INTERFACE);
+ intent.setPackage(packageName);
+ return intent;
+ }
+
+ private static Intent getPrintServiceIntent(String packageName) {
+ final Intent intent = new Intent(PrintService.SERVICE_INTERFACE);
+ intent.setPackage(packageName);
+ return intent;
+ }
+
+ private static Intent getIMEServiceIntent(String packageName) {
+ final Intent intent = new Intent("android.view.InputMethod");
+ intent.setPackage(packageName);
+ return intent;
+ }
+
+ public void register(Context context) {
+ mContext = context;
+
+ mHasFeaturePrinting = mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_PRINTING);
+ mHasFeatureIme = mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_INPUT_METHODS);
+
+ // Cache accessibility service packages to know when they go away.
+ AccessibilityManager accessibilityManager = (AccessibilityManager)
+ mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+ List<AccessibilityServiceInfo> accessibilityServices = accessibilityManager
+ .getInstalledAccessibilityServiceList();
+ final int accessibilityServiceCount = accessibilityServices.size();
+ for (int i = 0; i < accessibilityServiceCount; i++) {
+ AccessibilityServiceInfo accessibilityService = accessibilityServices.get(i);
+ ResolveInfo resolveInfo = accessibilityService.getResolveInfo();
+ if (resolveInfo == null || resolveInfo.serviceInfo == null) {
+ continue;
+ }
+ mAccessibilityServices.add(resolveInfo.serviceInfo.packageName);
+ }
+
+ if (mHasFeaturePrinting) {
+ // Cache print service packages to know when they go away.
+ PrintManager printManager = (PrintManager)
+ mContext.getSystemService(Context.PRINT_SERVICE);
+ List<PrintServiceInfo> printServices = printManager.getInstalledPrintServices();
+ final int serviceCount = printServices.size();
+ for (int i = 0; i < serviceCount; i++) {
+ PrintServiceInfo printService = printServices.get(i);
+ ResolveInfo resolveInfo = printService.getResolveInfo();
+ if (resolveInfo == null || resolveInfo.serviceInfo == null) {
+ continue;
+ }
+ mPrintServices.add(resolveInfo.serviceInfo.packageName);
+ }
+ }
+
+ // Cache IME service packages to know when they go away.
+ if (mHasFeatureIme) {
+ InputMethodManager imeManager = (InputMethodManager)
+ mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
+ List<InputMethodInfo> inputMethods = imeManager.getInputMethodList();
+ final int inputMethodCount = inputMethods.size();
+ for (int i = 0; i < inputMethodCount; i++) {
+ InputMethodInfo inputMethod = inputMethods.get(i);
+ ServiceInfo serviceInfo = inputMethod.getServiceInfo();
+ if (serviceInfo == null) continue;
+ mImeServices.add(serviceInfo.packageName);
+ }
+
+ // Watch for related content URIs.
+ mContext.getContentResolver().registerContentObserver(
+ UserDictionary.Words.CONTENT_URI, true, mUserDictionaryContentObserver);
+ }
+
+ // Watch for input device changes.
+ InputManager inputManager = (InputManager) context.getSystemService(
+ Context.INPUT_SERVICE);
+ inputManager.registerInputDeviceListener(this, mHandler);
+
+ // Start tracking packages.
+ register(context, Looper.getMainLooper(), UserHandle.CURRENT, false);
+ }
+
+ public void unregister() {
+ super.unregister();
+
+ InputManager inputManager = (InputManager) mContext.getSystemService(
+ Context.INPUT_SERVICE);
+ inputManager.unregisterInputDeviceListener(this);
+
+ if (mHasFeatureIme) {
+ mContext.getContentResolver().unregisterContentObserver(
+ mUserDictionaryContentObserver);
+ }
+
+ mAccessibilityServices.clear();
+ mPrintServices.clear();
+ mImeServices.clear();
+ }
+
+ // Covers installed, appeared external storage with the package, upgraded.
+ @Override
+ public void onPackageAppeared(String packageName, int uid) {
+ postMessage(MSG_PACKAGE_AVAILABLE, packageName);
+ }
+
+ // Covers uninstalled, removed external storage with the package.
+ @Override
+ public void onPackageDisappeared(String packageName, int uid) {
+ postMessage(MSG_PACKAGE_UNAVAILABLE, packageName);
+ }
+
+ // Covers enabled, disabled.
+ @Override
+ public void onPackageModified(String packageName) {
+ super.onPackageModified(packageName);
+ final int state = mContext.getPackageManager().getApplicationEnabledSetting(
+ packageName);
+ if (state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
+ || state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
+ postMessage(MSG_PACKAGE_AVAILABLE, packageName);
+ } else {
+ postMessage(MSG_PACKAGE_UNAVAILABLE, packageName);
+ }
+ }
+
+ @Override
+ public void onInputDeviceAdded(int deviceId) {
+ Index.getInstance(mContext).updateFromClassNameResource(
+ InputMethodAndLanguageSettings.class.getName(), false, true);
+ }
+
+ @Override
+ public void onInputDeviceRemoved(int deviceId) {
+ onInputDeviceChanged(deviceId);
+ }
+
+ @Override
+ public void onInputDeviceChanged(int deviceId) {
+ Index.getInstance(mContext).updateFromClassNameResource(
+ InputMethodAndLanguageSettings.class.getName(), true, true);
+ }
+
+ private void postMessage(int what, String packageName) {
+ Message message = mHandler.obtainMessage(what, packageName);
+ mHandler.sendMessageDelayed(message, DELAY_PROCESS_PACKAGE_CHANGE);
+ }
+
+ private void handlePackageAvailable(String packageName) {
+ if (!mAccessibilityServices.contains(packageName)) {
+ final Intent intent = getAccessibilityServiceIntent(packageName);
+ if (!mContext.getPackageManager().queryIntentServices(intent, 0).isEmpty()) {
+ mAccessibilityServices.add(packageName);
+ Index.getInstance(mContext).updateFromClassNameResource(
+ AccessibilitySettings.class.getName(), false, true);
+ }
+ }
+
+ if (mHasFeaturePrinting) {
+ if (!mPrintServices.contains(packageName)) {
+ final Intent intent = getPrintServiceIntent(packageName);
+ if (!mContext.getPackageManager().queryIntentServices(intent, 0).isEmpty()) {
+ mPrintServices.add(packageName);
+ Index.getInstance(mContext).updateFromClassNameResource(
+ PrintSettingsFragment.class.getName(), false, true);
+ }
+ }
+ }
+
+ if (mHasFeatureIme) {
+ if (!mImeServices.contains(packageName)) {
+ Intent intent = getIMEServiceIntent(packageName);
+ if (!mContext.getPackageManager().queryIntentServices(intent, 0).isEmpty()) {
+ mImeServices.add(packageName);
+ Index.getInstance(mContext).updateFromClassNameResource(
+ InputMethodAndLanguageSettings.class.getName(), false, true);
+ }
+ }
+ }
+ }
+
+ private void handlePackageUnavailable(String packageName) {
+ final int accessibilityIndex = mAccessibilityServices.indexOf(packageName);
+ if (accessibilityIndex >= 0) {
+ mAccessibilityServices.remove(accessibilityIndex);
+ Index.getInstance(mContext).updateFromClassNameResource(
+ AccessibilitySettings.class.getName(), true, true);
+ }
+
+ if (mHasFeaturePrinting) {
+ final int printIndex = mPrintServices.indexOf(packageName);
+ if (printIndex >= 0) {
+ mPrintServices.remove(printIndex);
+ Index.getInstance(mContext).updateFromClassNameResource(
+ PrintSettingsFragment.class.getName(), true, true);
+ }
+ }
+
+ if (mHasFeatureIme) {
+ final int imeIndex = mImeServices.indexOf(packageName);
+ if (imeIndex >= 0) {
+ mImeServices.remove(imeIndex);
+ Index.getInstance(mContext).updateFromClassNameResource(
+ InputMethodAndLanguageSettings.class.getName(), true, true);
+ }
+ }
+ }
+
+ private final class UserDictionaryContentObserver extends ContentObserver {
+
+ public UserDictionaryContentObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (UserDictionary.Words.CONTENT_URI.equals(uri)) {
+ Index.getInstance(mContext).updateFromClassNameResource(
+ InputMethodAndLanguageSettings.class.getName(), true, true);
+ }
+ };
+ }
+}
diff --git a/src/com/android/settings/search/Index.java b/src/com/android/settings/search/Index.java
new file mode 100644
index 0000000..3957cf6
--- /dev/null
+++ b/src/com/android/settings/search/Index.java
@@ -0,0 +1,1320 @@
+/*
+ * 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.search;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+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.ResolveInfo;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.MergeCursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.provider.SearchIndexableData;
+import android.provider.SearchIndexableResource;
+import android.provider.SearchIndexablesContract;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.TypedValue;
+import android.util.Xml;
+import com.android.settings.R;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.text.Normalizer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.regex.Pattern;
+
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_RANK;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_TITLE;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_SUMMARY_ON;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_SUMMARY_OFF;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_ENTRIES;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_KEYWORDS;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_SCREEN_TITLE;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_CLASS_NAME;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_ICON_RESID;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_INTENT_ACTION;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_INTENT_TARGET_PACKAGE;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_INTENT_TARGET_CLASS;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_KEY;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_USER_ID;
+
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_RANK;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_RESID;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_CLASS_NAME;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_ICON_RESID;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_ACTION;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS;
+
+import static com.android.settings.search.IndexDatabaseHelper.Tables;
+import static com.android.settings.search.IndexDatabaseHelper.IndexColumns;
+
+public class Index {
+
+ private static final String LOG_TAG = "Index";
+
+ // Those indices should match the indices of SELECT_COLUMNS !
+ public static final int COLUMN_INDEX_RANK = 0;
+ public static final int COLUMN_INDEX_TITLE = 1;
+ public static final int COLUMN_INDEX_SUMMARY_ON = 2;
+ public static final int COLUMN_INDEX_SUMMARY_OFF = 3;
+ public static final int COLUMN_INDEX_ENTRIES = 4;
+ public static final int COLUMN_INDEX_KEYWORDS = 5;
+ public static final int COLUMN_INDEX_CLASS_NAME = 6;
+ public static final int COLUMN_INDEX_SCREEN_TITLE = 7;
+ public static final int COLUMN_INDEX_ICON = 8;
+ public static final int COLUMN_INDEX_INTENT_ACTION = 9;
+ public static final int COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE = 10;
+ public static final int COLUMN_INDEX_INTENT_ACTION_TARGET_CLASS = 11;
+ public static final int COLUMN_INDEX_ENABLED = 12;
+ public static final int COLUMN_INDEX_KEY = 13;
+ public static final int COLUMN_INDEX_USER_ID = 14;
+
+ public static final String ENTRIES_SEPARATOR = "|";
+
+ // If you change the order of columns here, you SHOULD change the COLUMN_INDEX_XXX values
+ private static final String[] SELECT_COLUMNS = new String[] {
+ IndexColumns.DATA_RANK, // 0
+ IndexColumns.DATA_TITLE, // 1
+ IndexColumns.DATA_SUMMARY_ON, // 2
+ IndexColumns.DATA_SUMMARY_OFF, // 3
+ IndexColumns.DATA_ENTRIES, // 4
+ IndexColumns.DATA_KEYWORDS, // 5
+ IndexColumns.CLASS_NAME, // 6
+ IndexColumns.SCREEN_TITLE, // 7
+ IndexColumns.ICON, // 8
+ IndexColumns.INTENT_ACTION, // 9
+ IndexColumns.INTENT_TARGET_PACKAGE, // 10
+ IndexColumns.INTENT_TARGET_CLASS, // 11
+ IndexColumns.ENABLED, // 12
+ IndexColumns.DATA_KEY_REF // 13
+ };
+
+ private static final String[] MATCH_COLUMNS_PRIMARY = {
+ IndexColumns.DATA_TITLE,
+ IndexColumns.DATA_TITLE_NORMALIZED,
+ IndexColumns.DATA_KEYWORDS
+ };
+
+ private static final String[] MATCH_COLUMNS_SECONDARY = {
+ IndexColumns.DATA_SUMMARY_ON,
+ IndexColumns.DATA_SUMMARY_ON_NORMALIZED,
+ IndexColumns.DATA_SUMMARY_OFF,
+ IndexColumns.DATA_SUMMARY_OFF_NORMALIZED,
+ IndexColumns.DATA_ENTRIES
+ };
+
+ // Max number of saved search queries (who will be used for proposing suggestions)
+ private static long MAX_SAVED_SEARCH_QUERY = 64;
+ // Max number of proposed suggestions
+ private static final int MAX_PROPOSED_SUGGESTIONS = 5;
+
+ private static final String BASE_AUTHORITY = "com.android.settings";
+
+ private static final String EMPTY = "";
+ private static final String NON_BREAKING_HYPHEN = "\u2011";
+ private static final String HYPHEN = "-";
+
+ private static final String FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER =
+ "SEARCH_INDEX_DATA_PROVIDER";
+
+ private static final String NODE_NAME_PREFERENCE_SCREEN = "PreferenceScreen";
+ private static final String NODE_NAME_CHECK_BOX_PREFERENCE = "CheckBoxPreference";
+ private static final String NODE_NAME_LIST_PREFERENCE = "ListPreference";
+
+ private static final List<String> EMPTY_LIST = Collections.<String>emptyList();
+
+ private static Index sInstance;
+
+ private static final Pattern REMOVE_DIACRITICALS_PATTERN
+ = Pattern.compile("\\p{InCombiningDiacriticalMarks}+");
+
+ /**
+ * A private class to describe the update data for the Index database
+ */
+ private static class UpdateData {
+ public List<SearchIndexableData> dataToUpdate;
+ public List<SearchIndexableData> dataToDelete;
+ public Map<String, List<String>> nonIndexableKeys;
+
+ public boolean forceUpdate = false;
+
+ public UpdateData() {
+ dataToUpdate = new ArrayList<SearchIndexableData>();
+ dataToDelete = new ArrayList<SearchIndexableData>();
+ nonIndexableKeys = new HashMap<String, List<String>>();
+ }
+
+ public UpdateData(UpdateData other) {
+ dataToUpdate = new ArrayList<SearchIndexableData>(other.dataToUpdate);
+ dataToDelete = new ArrayList<SearchIndexableData>(other.dataToDelete);
+ nonIndexableKeys = new HashMap<String, List<String>>(other.nonIndexableKeys);
+ forceUpdate = other.forceUpdate;
+ }
+
+ public UpdateData copy() {
+ return new UpdateData(this);
+ }
+
+ public void clear() {
+ dataToUpdate.clear();
+ dataToDelete.clear();
+ nonIndexableKeys.clear();
+ forceUpdate = false;
+ }
+ }
+
+ private final AtomicBoolean mIsAvailable = new AtomicBoolean(false);
+ private final UpdateData mDataToProcess = new UpdateData();
+ private Context mContext;
+ private final String mBaseAuthority;
+
+ /**
+ * A basic singleton
+ */
+ public static Index getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new Index(context, BASE_AUTHORITY);
+ } else {
+ sInstance.setContext(context);
+ }
+ return sInstance;
+ }
+
+ public Index(Context context, String baseAuthority) {
+ mContext = context;
+ mBaseAuthority = baseAuthority;
+ }
+
+ public void setContext(Context context) {
+ mContext = context;
+ }
+
+ public boolean isAvailable() {
+ return mIsAvailable.get();
+ }
+
+ public Cursor search(String query) {
+ final SQLiteDatabase database = getReadableDatabase();
+ final Cursor[] cursors = new Cursor[2];
+
+ final String primarySql = buildSearchSQL(query, MATCH_COLUMNS_PRIMARY, true);
+ Log.d(LOG_TAG, "Search primary query: " + primarySql);
+ cursors[0] = database.rawQuery(primarySql, null);
+
+ // We need to use an EXCEPT operator as negate MATCH queries do not work.
+ StringBuilder sql = new StringBuilder(
+ buildSearchSQL(query, MATCH_COLUMNS_SECONDARY, false));
+ sql.append(" EXCEPT ");
+ sql.append(primarySql);
+
+ final String secondarySql = sql.toString();
+ Log.d(LOG_TAG, "Search secondary query: " + secondarySql);
+ cursors[1] = database.rawQuery(secondarySql, null);
+
+ return new MergeCursor(cursors);
+ }
+
+ public Cursor getSuggestions(String query) {
+ final String sql = buildSuggestionsSQL(query);
+ Log.d(LOG_TAG, "Suggestions query: " + sql);
+ return getReadableDatabase().rawQuery(sql, null);
+ }
+
+ private String buildSuggestionsSQL(String query) {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("SELECT ");
+ sb.append(IndexDatabaseHelper.SavedQueriesColums.QUERY);
+ sb.append(" FROM ");
+ sb.append(Tables.TABLE_SAVED_QUERIES);
+
+ if (TextUtils.isEmpty(query)) {
+ sb.append(" ORDER BY rowId DESC");
+ } else {
+ sb.append(" WHERE ");
+ sb.append(IndexDatabaseHelper.SavedQueriesColums.QUERY);
+ sb.append(" LIKE ");
+ sb.append("'");
+ sb.append(query);
+ sb.append("%");
+ sb.append("'");
+ }
+
+ sb.append(" LIMIT ");
+ sb.append(MAX_PROPOSED_SUGGESTIONS);
+
+ return sb.toString();
+ }
+
+ public long addSavedQuery(String query){
+ final SaveSearchQueryTask task = new SaveSearchQueryTask();
+ task.execute(query);
+ try {
+ return task.get();
+ } catch (InterruptedException e) {
+ Log.e(LOG_TAG, "Cannot insert saved query: " + query, e);
+ return -1 ;
+ } catch (ExecutionException e) {
+ Log.e(LOG_TAG, "Cannot insert saved query: " + query, e);
+ return -1;
+ }
+ }
+
+ public void update() {
+ final Intent intent = new Intent(SearchIndexablesContract.PROVIDER_INTERFACE);
+ List<ResolveInfo> list =
+ mContext.getPackageManager().queryIntentContentProviders(intent, 0);
+
+ final int size = list.size();
+ for (int n = 0; n < size; n++) {
+ final ResolveInfo info = list.get(n);
+ if (!isWellKnownProvider(info)) {
+ continue;
+ }
+ final String authority = info.providerInfo.authority;
+ final String packageName = info.providerInfo.packageName;
+
+ addIndexablesFromRemoteProvider(packageName, authority);
+ addNonIndexablesKeysFromRemoteProvider(packageName, authority);
+ }
+
+ updateInternal();
+ }
+
+ private boolean addIndexablesFromRemoteProvider(String packageName, String authority) {
+ try {
+ final int baseRank = Ranking.getBaseRankForAuthority(authority);
+
+ final Context context = mBaseAuthority.equals(authority) ?
+ mContext : mContext.createPackageContext(packageName, 0);
+
+ final Uri uriForResources = buildUriForXmlResources(authority);
+ addIndexablesForXmlResourceUri(context, packageName, uriForResources,
+ SearchIndexablesContract.INDEXABLES_XML_RES_COLUMNS, baseRank);
+
+ final Uri uriForRawData = buildUriForRawData(authority);
+ addIndexablesForRawDataUri(context, packageName, uriForRawData,
+ SearchIndexablesContract.INDEXABLES_RAW_COLUMNS, baseRank);
+ return true;
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(LOG_TAG, "Could not create context for " + packageName + ": "
+ + Log.getStackTraceString(e));
+ return false;
+ }
+ }
+
+ private void addNonIndexablesKeysFromRemoteProvider(String packageName,
+ String authority) {
+ final List<String> keys =
+ getNonIndexablesKeysFromRemoteProvider(packageName, authority);
+ addNonIndexableKeys(packageName, keys);
+ }
+
+ private List<String> getNonIndexablesKeysFromRemoteProvider(String packageName,
+ String authority) {
+ try {
+ final Context packageContext = mContext.createPackageContext(packageName, 0);
+
+ final Uri uriForNonIndexableKeys = buildUriForNonIndexableKeys(authority);
+ return getNonIndexablesKeys(packageContext, uriForNonIndexableKeys,
+ SearchIndexablesContract.NON_INDEXABLES_KEYS_COLUMNS);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(LOG_TAG, "Could not create context for " + packageName + ": "
+ + Log.getStackTraceString(e));
+ return EMPTY_LIST;
+ }
+ }
+
+ private List<String> getNonIndexablesKeys(Context packageContext, Uri uri,
+ String[] projection) {
+
+ final ContentResolver resolver = packageContext.getContentResolver();
+ final Cursor cursor = resolver.query(uri, projection, null, null, null);
+
+ if (cursor == null) {
+ Log.w(LOG_TAG, "Cannot add index data for Uri: " + uri.toString());
+ return EMPTY_LIST;
+ }
+
+ List<String> result = new ArrayList<String>();
+ try {
+ final int count = cursor.getCount();
+ if (count > 0) {
+ while (cursor.moveToNext()) {
+ final String key = cursor.getString(COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE);
+ result.add(key);
+ }
+ }
+ return result;
+ } finally {
+ cursor.close();
+ }
+ }
+
+ public void addIndexableData(SearchIndexableData data) {
+ synchronized (mDataToProcess) {
+ mDataToProcess.dataToUpdate.add(data);
+ }
+ }
+
+ public void addIndexableData(SearchIndexableResource[] array) {
+ synchronized (mDataToProcess) {
+ final int count = array.length;
+ for (int n = 0; n < count; n++) {
+ mDataToProcess.dataToUpdate.add(array[n]);
+ }
+ }
+ }
+
+ public void deleteIndexableData(SearchIndexableData data) {
+ synchronized (mDataToProcess) {
+ mDataToProcess.dataToDelete.add(data);
+ }
+ }
+
+ public void addNonIndexableKeys(String authority, List<String> keys) {
+ synchronized (mDataToProcess) {
+ mDataToProcess.nonIndexableKeys.put(authority, keys);
+ }
+ }
+
+ /**
+ * Only allow a "well known" SearchIndexablesProvider. The provider should:
+ *
+ * - have read/write {@link android.Manifest.permission#READ_SEARCH_INDEXABLES}
+ * - be from a privileged package
+ */
+ private boolean isWellKnownProvider(ResolveInfo info) {
+ final String authority = info.providerInfo.authority;
+ final String packageName = info.providerInfo.applicationInfo.packageName;
+
+ if (TextUtils.isEmpty(authority) || TextUtils.isEmpty(packageName)) {
+ return false;
+ }
+
+ final String readPermission = info.providerInfo.readPermission;
+ final String writePermission = info.providerInfo.writePermission;
+
+ if (TextUtils.isEmpty(readPermission) || TextUtils.isEmpty(writePermission)) {
+ return false;
+ }
+
+ if (!android.Manifest.permission.READ_SEARCH_INDEXABLES.equals(readPermission) ||
+ !android.Manifest.permission.READ_SEARCH_INDEXABLES.equals(writePermission)) {
+ return false;
+ }
+
+ return isPrivilegedPackage(packageName);
+ }
+
+ private boolean isPrivilegedPackage(String packageName) {
+ final PackageManager pm = mContext.getPackageManager();
+ try {
+ PackageInfo packInfo = pm.getPackageInfo(packageName, 0);
+ return ((packInfo.applicationInfo.flags & ApplicationInfo.FLAG_PRIVILEGED) != 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+
+ private void updateFromRemoteProvider(String packageName, String authority) {
+ if (addIndexablesFromRemoteProvider(packageName, authority)) {
+ updateInternal();
+ }
+ }
+
+ /**
+ * Update the Index for a specific class name resources
+ *
+ * @param className the class name (typically a fragment name).
+ * @param rebuild true means that you want to delete the data from the Index first.
+ * @param includeInSearchResults true means that you want the bit "enabled" set so that the
+ * data will be seen included into the search results
+ */
+ public void updateFromClassNameResource(String className, boolean rebuild,
+ boolean includeInSearchResults) {
+ if (className == null) {
+ throw new IllegalArgumentException("class name cannot be null!");
+ }
+ final SearchIndexableResource res = SearchIndexableResources.getResourceByName(className);
+ if (res == null ) {
+ Log.e(LOG_TAG, "Cannot find SearchIndexableResources for class name: " + className);
+ return;
+ }
+ res.context = mContext;
+ res.enabled = includeInSearchResults;
+ if (rebuild) {
+ deleteIndexableData(res);
+ }
+ addIndexableData(res);
+ mDataToProcess.forceUpdate = true;
+ updateInternal();
+ res.enabled = false;
+ }
+
+ public void updateFromSearchIndexableData(SearchIndexableData data) {
+ addIndexableData(data);
+ mDataToProcess.forceUpdate = true;
+ updateInternal();
+ }
+
+ private SQLiteDatabase getReadableDatabase() {
+ return IndexDatabaseHelper.getInstance(mContext).getReadableDatabase();
+ }
+
+ private SQLiteDatabase getWritableDatabase() {
+ return IndexDatabaseHelper.getInstance(mContext).getWritableDatabase();
+ }
+
+ private static Uri buildUriForXmlResources(String authority) {
+ return Uri.parse("content://" + authority + "/" +
+ SearchIndexablesContract.INDEXABLES_XML_RES_PATH);
+ }
+
+ private static Uri buildUriForRawData(String authority) {
+ return Uri.parse("content://" + authority + "/" +
+ SearchIndexablesContract.INDEXABLES_RAW_PATH);
+ }
+
+ private static Uri buildUriForNonIndexableKeys(String authority) {
+ return Uri.parse("content://" + authority + "/" +
+ SearchIndexablesContract.NON_INDEXABLES_KEYS_PATH);
+ }
+
+ private void updateInternal() {
+ synchronized (mDataToProcess) {
+ final UpdateIndexTask task = new UpdateIndexTask();
+ UpdateData copy = mDataToProcess.copy();
+ task.execute(copy);
+ mDataToProcess.clear();
+ }
+ }
+
+ private void addIndexablesForXmlResourceUri(Context packageContext, String packageName,
+ Uri uri, String[] projection, int baseRank) {
+
+ final ContentResolver resolver = packageContext.getContentResolver();
+ final Cursor cursor = resolver.query(uri, projection, null, null, null);
+
+ if (cursor == null) {
+ Log.w(LOG_TAG, "Cannot add index data for Uri: " + uri.toString());
+ return;
+ }
+
+ try {
+ final int count = cursor.getCount();
+ if (count > 0) {
+ while (cursor.moveToNext()) {
+ final int providerRank = cursor.getInt(COLUMN_INDEX_XML_RES_RANK);
+ final int rank = (providerRank > 0) ? baseRank + providerRank : baseRank;
+
+ final int xmlResId = cursor.getInt(COLUMN_INDEX_XML_RES_RESID);
+
+ final String className = cursor.getString(COLUMN_INDEX_XML_RES_CLASS_NAME);
+ final int iconResId = cursor.getInt(COLUMN_INDEX_XML_RES_ICON_RESID);
+
+ final String action = cursor.getString(COLUMN_INDEX_XML_RES_INTENT_ACTION);
+ final String targetPackage = cursor.getString(
+ COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE);
+ final String targetClass = cursor.getString(
+ COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS);
+
+ SearchIndexableResource sir = new SearchIndexableResource(packageContext);
+ sir.rank = rank;
+ sir.xmlResId = xmlResId;
+ sir.className = className;
+ sir.packageName = packageName;
+ sir.iconResId = iconResId;
+ sir.intentAction = action;
+ sir.intentTargetPackage = targetPackage;
+ sir.intentTargetClass = targetClass;
+
+ addIndexableData(sir);
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+
+ private void addIndexablesForRawDataUri(Context packageContext, String packageName,
+ Uri uri, String[] projection, int baseRank) {
+
+ final ContentResolver resolver = packageContext.getContentResolver();
+ final Cursor cursor = resolver.query(uri, projection, null, null, null);
+
+ if (cursor == null) {
+ Log.w(LOG_TAG, "Cannot add index data for Uri: " + uri.toString());
+ return;
+ }
+
+ try {
+ final int count = cursor.getCount();
+ if (count > 0) {
+ while (cursor.moveToNext()) {
+ final int providerRank = cursor.getInt(COLUMN_INDEX_RAW_RANK);
+ final int rank = (providerRank > 0) ? baseRank + providerRank : baseRank;
+
+ final String title = cursor.getString(COLUMN_INDEX_RAW_TITLE);
+ final String summaryOn = cursor.getString(COLUMN_INDEX_RAW_SUMMARY_ON);
+ final String summaryOff = cursor.getString(COLUMN_INDEX_RAW_SUMMARY_OFF);
+ final String entries = cursor.getString(COLUMN_INDEX_RAW_ENTRIES);
+ final String keywords = cursor.getString(COLUMN_INDEX_RAW_KEYWORDS);
+
+ final String screenTitle = cursor.getString(COLUMN_INDEX_RAW_SCREEN_TITLE);
+
+ final String className = cursor.getString(COLUMN_INDEX_RAW_CLASS_NAME);
+ final int iconResId = cursor.getInt(COLUMN_INDEX_RAW_ICON_RESID);
+
+ final String action = cursor.getString(COLUMN_INDEX_RAW_INTENT_ACTION);
+ final String targetPackage = cursor.getString(
+ COLUMN_INDEX_RAW_INTENT_TARGET_PACKAGE);
+ final String targetClass = cursor.getString(
+ COLUMN_INDEX_RAW_INTENT_TARGET_CLASS);
+
+ final String key = cursor.getString(COLUMN_INDEX_RAW_KEY);
+ final int userId = cursor.getInt(COLUMN_INDEX_RAW_USER_ID);
+
+ SearchIndexableRaw data = new SearchIndexableRaw(packageContext);
+ data.rank = rank;
+ data.title = title;
+ data.summaryOn = summaryOn;
+ data.summaryOff = summaryOff;
+ data.entries = entries;
+ data.keywords = keywords;
+ data.screenTitle = screenTitle;
+ data.className = className;
+ data.packageName = packageName;
+ data.iconResId = iconResId;
+ data.intentAction = action;
+ data.intentTargetPackage = targetPackage;
+ data.intentTargetClass = targetClass;
+ data.key = key;
+ data.userId = userId;
+
+ addIndexableData(data);
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+
+ private String buildSearchSQL(String query, String[] colums, boolean withOrderBy) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(buildSearchSQLForColumn(query, colums));
+ if (withOrderBy) {
+ sb.append(" ORDER BY ");
+ sb.append(IndexColumns.DATA_RANK);
+ }
+ return sb.toString();
+ }
+
+ private String buildSearchSQLForColumn(String query, String[] columnNames) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("SELECT ");
+ for (int n = 0; n < SELECT_COLUMNS.length; n++) {
+ sb.append(SELECT_COLUMNS[n]);
+ if (n < SELECT_COLUMNS.length - 1) {
+ sb.append(", ");
+ }
+ }
+ sb.append(" FROM ");
+ sb.append(Tables.TABLE_PREFS_INDEX);
+ sb.append(" WHERE ");
+ sb.append(buildSearchWhereStringForColumns(query, columnNames));
+
+ return sb.toString();
+ }
+
+ private String buildSearchWhereStringForColumns(String query, String[] columnNames) {
+ final StringBuilder sb = new StringBuilder(Tables.TABLE_PREFS_INDEX);
+ sb.append(" MATCH ");
+ DatabaseUtils.appendEscapedSQLString(sb,
+ buildSearchMatchStringForColumns(query, columnNames));
+ sb.append(" AND ");
+ sb.append(IndexColumns.LOCALE);
+ sb.append(" = ");
+ DatabaseUtils.appendEscapedSQLString(sb, Locale.getDefault().toString());
+ sb.append(" AND ");
+ sb.append(IndexColumns.ENABLED);
+ sb.append(" = 1");
+ return sb.toString();
+ }
+
+ private String buildSearchMatchStringForColumns(String query, String[] columnNames) {
+ final String value = query + "*";
+ StringBuilder sb = new StringBuilder();
+ final int count = columnNames.length;
+ for (int n = 0; n < count; n++) {
+ sb.append(columnNames[n]);
+ sb.append(":");
+ sb.append(value);
+ if (n < count - 1) {
+ sb.append(" OR ");
+ }
+ }
+ return sb.toString();
+ }
+
+ private void indexOneSearchIndexableData(SQLiteDatabase database, String localeStr,
+ SearchIndexableData data, Map<String, List<String>> nonIndexableKeys) {
+ if (data instanceof SearchIndexableResource) {
+ indexOneResource(database, localeStr, (SearchIndexableResource) data, nonIndexableKeys);
+ } else if (data instanceof SearchIndexableRaw) {
+ indexOneRaw(database, localeStr, (SearchIndexableRaw) data);
+ }
+ }
+
+ private void indexOneRaw(SQLiteDatabase database, String localeStr,
+ SearchIndexableRaw raw) {
+ // Should be the same locale as the one we are processing
+ if (!raw.locale.toString().equalsIgnoreCase(localeStr)) {
+ return;
+ }
+
+ updateOneRowWithFilteredData(database, localeStr,
+ raw.title,
+ raw.summaryOn,
+ raw.summaryOff,
+ raw.entries,
+ raw.className,
+ raw.screenTitle,
+ raw.iconResId,
+ raw.rank,
+ raw.keywords,
+ raw.intentAction,
+ raw.intentTargetPackage,
+ raw.intentTargetClass,
+ raw.enabled,
+ raw.key,
+ raw.userId);
+ }
+
+ private static boolean isIndexableClass(final Class<?> clazz) {
+ return (clazz != null) && Indexable.class.isAssignableFrom(clazz);
+ }
+
+ private static Class<?> getIndexableClass(String className) {
+ final Class<?> clazz;
+ try {
+ clazz = Class.forName(className);
+ } catch (ClassNotFoundException e) {
+ Log.d(LOG_TAG, "Cannot find class: " + className);
+ return null;
+ }
+ return isIndexableClass(clazz) ? clazz : null;
+ }
+
+ private void indexOneResource(SQLiteDatabase database, String localeStr,
+ SearchIndexableResource sir, Map<String, List<String>> nonIndexableKeysFromResource) {
+
+ if (sir == null) {
+ Log.e(LOG_TAG, "Cannot index a null resource!");
+ return;
+ }
+
+ final List<String> nonIndexableKeys = new ArrayList<String>();
+
+ if (sir.xmlResId > SearchIndexableResources.NO_DATA_RES_ID) {
+ List<String> resNonIndxableKeys = nonIndexableKeysFromResource.get(sir.packageName);
+ if (resNonIndxableKeys != null && resNonIndxableKeys.size() > 0) {
+ nonIndexableKeys.addAll(resNonIndxableKeys);
+ }
+
+ indexFromResource(sir.context, database, localeStr,
+ sir.xmlResId, sir.className, sir.iconResId, sir.rank,
+ sir.intentAction, sir.intentTargetPackage, sir.intentTargetClass,
+ nonIndexableKeys);
+ } else {
+ if (TextUtils.isEmpty(sir.className)) {
+ Log.w(LOG_TAG, "Cannot index an empty Search Provider name!");
+ return;
+ }
+
+ final Class<?> clazz = getIndexableClass(sir.className);
+ if (clazz == null) {
+ Log.d(LOG_TAG, "SearchIndexableResource '" + sir.className +
+ "' should implement the " + Indexable.class.getName() + " interface!");
+ return;
+ }
+
+ // Will be non null only for a Local provider implementing a
+ // SEARCH_INDEX_DATA_PROVIDER field
+ final Indexable.SearchIndexProvider provider = getSearchIndexProvider(clazz);
+ if (provider != null) {
+ List<String> providerNonIndexableKeys = provider.getNonIndexableKeys(sir.context);
+ if (providerNonIndexableKeys != null && providerNonIndexableKeys.size() > 0) {
+ nonIndexableKeys.addAll(providerNonIndexableKeys);
+ }
+
+ indexFromProvider(mContext, database, localeStr, provider, sir.className,
+ sir.iconResId, sir.rank, sir.enabled, nonIndexableKeys);
+ }
+ }
+ }
+
+ private Indexable.SearchIndexProvider getSearchIndexProvider(final Class<?> clazz) {
+ try {
+ final Field f = clazz.getField(FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER);
+ return (Indexable.SearchIndexProvider) f.get(null);
+ } catch (NoSuchFieldException e) {
+ Log.d(LOG_TAG, "Cannot find field '" + FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER + "'");
+ } catch (SecurityException se) {
+ Log.d(LOG_TAG,
+ "Security exception for field '" + FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER + "'");
+ } catch (IllegalAccessException e) {
+ Log.d(LOG_TAG,
+ "Illegal access to field '" + FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER + "'");
+ } catch (IllegalArgumentException e) {
+ Log.d(LOG_TAG,
+ "Illegal argument when accessing field '" +
+ FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER + "'");
+ }
+ return null;
+ }
+
+ private void indexFromResource(Context context, SQLiteDatabase database, String localeStr,
+ int xmlResId, String fragmentName, int iconResId, int rank,
+ String intentAction, String intentTargetPackage, String intentTargetClass,
+ List<String> nonIndexableKeys) {
+
+ XmlResourceParser parser = null;
+ try {
+ parser = context.getResources().getXml(xmlResId);
+
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ // Parse next until start tag is found
+ }
+
+ String nodeName = parser.getName();
+ if (!NODE_NAME_PREFERENCE_SCREEN.equals(nodeName)) {
+ throw new RuntimeException(
+ "XML document must start with <PreferenceScreen> tag; found"
+ + nodeName + " at " + parser.getPositionDescription());
+ }
+
+ final int outerDepth = parser.getDepth();
+ final AttributeSet attrs = Xml.asAttributeSet(parser);
+
+ final String screenTitle = getDataTitle(context, attrs);
+
+ String key = getDataKey(context, attrs);
+
+ String title;
+ String summary;
+ String keywords;
+
+ // Insert rows for the main PreferenceScreen node. Rewrite the data for removing
+ // hyphens.
+ if (!nonIndexableKeys.contains(key)) {
+ title = getDataTitle(context, attrs);
+ summary = getDataSummary(context, attrs);
+ keywords = getDataKeywords(context, attrs);
+
+ updateOneRowWithFilteredData(database, localeStr, title, summary, null, null,
+ fragmentName, screenTitle, iconResId, rank,
+ keywords, intentAction, intentTargetPackage, intentTargetClass, true,
+ key, -1 /* default user id */);
+ }
+
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ nodeName = parser.getName();
+
+ key = getDataKey(context, attrs);
+ if (nonIndexableKeys.contains(key)) {
+ continue;
+ }
+
+ title = getDataTitle(context, attrs);
+ keywords = getDataKeywords(context, attrs);
+
+ if (!nodeName.equals(NODE_NAME_CHECK_BOX_PREFERENCE)) {
+ summary = getDataSummary(context, attrs);
+
+ String entries = null;
+
+ if (nodeName.endsWith(NODE_NAME_LIST_PREFERENCE)) {
+ entries = getDataEntries(context, attrs);
+ }
+
+ // Insert rows for the child nodes of PreferenceScreen
+ updateOneRowWithFilteredData(database, localeStr, title, summary, null, entries,
+ fragmentName, screenTitle, iconResId, rank,
+ keywords, intentAction, intentTargetPackage, intentTargetClass,
+ true, key, -1 /* default user id */);
+ } else {
+ String summaryOn = getDataSummaryOn(context, attrs);
+ String summaryOff = getDataSummaryOff(context, attrs);
+
+ if (TextUtils.isEmpty(summaryOn) && TextUtils.isEmpty(summaryOff)) {
+ summaryOn = getDataSummary(context, attrs);
+ }
+
+ updateOneRowWithFilteredData(database, localeStr, title, summaryOn, summaryOff,
+ null, fragmentName, screenTitle, iconResId, rank,
+ keywords, intentAction, intentTargetPackage, intentTargetClass,
+ true, key, -1 /* default user id */);
+ }
+ }
+
+ } catch (XmlPullParserException e) {
+ throw new RuntimeException("Error parsing PreferenceScreen", e);
+ } catch (IOException e) {
+ throw new RuntimeException("Error parsing PreferenceScreen", e);
+ } finally {
+ if (parser != null) parser.close();
+ }
+ }
+
+ private void indexFromProvider(Context context, SQLiteDatabase database, String localeStr,
+ Indexable.SearchIndexProvider provider, String className, int iconResId, int rank,
+ boolean enabled, List<String> nonIndexableKeys) {
+
+ if (provider == null) {
+ Log.w(LOG_TAG, "Cannot find provider: " + className);
+ return;
+ }
+
+ final List<SearchIndexableRaw> rawList = provider.getRawDataToIndex(context, enabled);
+
+ if (rawList != null) {
+ final int rawSize = rawList.size();
+ for (int i = 0; i < rawSize; i++) {
+ SearchIndexableRaw raw = rawList.get(i);
+
+ // Should be the same locale as the one we are processing
+ if (!raw.locale.toString().equalsIgnoreCase(localeStr)) {
+ continue;
+ }
+
+ if (nonIndexableKeys.contains(raw.key)) {
+ continue;
+ }
+
+ updateOneRowWithFilteredData(database, localeStr,
+ raw.title,
+ raw.summaryOn,
+ raw.summaryOff,
+ raw.entries,
+ className,
+ raw.screenTitle,
+ iconResId,
+ rank,
+ raw.keywords,
+ raw.intentAction,
+ raw.intentTargetPackage,
+ raw.intentTargetClass,
+ raw.enabled,
+ raw.key,
+ raw.userId);
+ }
+ }
+
+ final List<SearchIndexableResource> resList =
+ provider.getXmlResourcesToIndex(context, enabled);
+ if (resList != null) {
+ final int resSize = resList.size();
+ for (int i = 0; i < resSize; i++) {
+ SearchIndexableResource item = resList.get(i);
+
+ // Should be the same locale as the one we are processing
+ if (!item.locale.toString().equalsIgnoreCase(localeStr)) {
+ continue;
+ }
+
+ final int itemIconResId = (item.iconResId == 0) ? iconResId : item.iconResId;
+ final int itemRank = (item.rank == 0) ? rank : item.rank;
+ String itemClassName = (TextUtils.isEmpty(item.className))
+ ? className : item.className;
+
+ indexFromResource(context, database, localeStr,
+ item.xmlResId, itemClassName, itemIconResId, itemRank,
+ item.intentAction, item.intentTargetPackage,
+ item.intentTargetClass, nonIndexableKeys);
+ }
+ }
+ }
+
+ private void updateOneRowWithFilteredData(SQLiteDatabase database, String locale,
+ String title, String summaryOn, String summaryOff, String entries,
+ String className,
+ String screenTitle, int iconResId, int rank, String keywords,
+ String intentAction, String intentTargetPackage, String intentTargetClass,
+ boolean enabled, String key, int userId) {
+
+ final String updatedTitle = normalizeHyphen(title);
+ final String updatedSummaryOn = normalizeHyphen(summaryOn);
+ final String updatedSummaryOff = normalizeHyphen(summaryOff);
+
+ final String normalizedTitle = normalizeString(updatedTitle);
+ final String normalizedSummaryOn = normalizeString(updatedSummaryOn);
+ final String normalizedSummaryOff = normalizeString(updatedSummaryOff);
+
+ updateOneRow(database, locale,
+ updatedTitle, normalizedTitle, updatedSummaryOn, normalizedSummaryOn,
+ updatedSummaryOff, normalizedSummaryOff, entries,
+ className, screenTitle, iconResId,
+ rank, keywords, intentAction, intentTargetPackage, intentTargetClass, enabled,
+ key, userId);
+ }
+
+ private static String normalizeHyphen(String input) {
+ return (input != null) ? input.replaceAll(NON_BREAKING_HYPHEN, HYPHEN) : EMPTY;
+ }
+
+ private static String normalizeString(String input) {
+ final String nohyphen = (input != null) ? input.replaceAll(HYPHEN, EMPTY) : EMPTY;
+ final String normalized = Normalizer.normalize(nohyphen, Normalizer.Form.NFD);
+
+ return REMOVE_DIACRITICALS_PATTERN.matcher(normalized).replaceAll("").toLowerCase();
+ }
+
+ private void updateOneRow(SQLiteDatabase database, String locale,
+ String updatedTitle, String normalizedTitle,
+ String updatedSummaryOn, String normalizedSummaryOn,
+ String updatedSummaryOff, String normalizedSummaryOff, String entries,
+ String className, String screenTitle, int iconResId, int rank, String keywords,
+ String intentAction, String intentTargetPackage, String intentTargetClass,
+ boolean enabled, String key, int userId) {
+
+ if (TextUtils.isEmpty(updatedTitle)) {
+ return;
+ }
+
+ // The DocID should contains more than the title string itself (you may have two settings
+ // with the same title). So we need to use a combination of the title and the screenTitle.
+ StringBuilder sb = new StringBuilder(updatedTitle);
+ sb.append(screenTitle);
+ int docId = sb.toString().hashCode();
+
+ ContentValues values = new ContentValues();
+ values.put(IndexColumns.DOCID, docId);
+ values.put(IndexColumns.LOCALE, locale);
+ values.put(IndexColumns.DATA_RANK, rank);
+ values.put(IndexColumns.DATA_TITLE, updatedTitle);
+ values.put(IndexColumns.DATA_TITLE_NORMALIZED, normalizedTitle);
+ values.put(IndexColumns.DATA_SUMMARY_ON, updatedSummaryOn);
+ values.put(IndexColumns.DATA_SUMMARY_ON_NORMALIZED, normalizedSummaryOn);
+ values.put(IndexColumns.DATA_SUMMARY_OFF, updatedSummaryOff);
+ values.put(IndexColumns.DATA_SUMMARY_OFF_NORMALIZED, normalizedSummaryOff);
+ values.put(IndexColumns.DATA_ENTRIES, entries);
+ values.put(IndexColumns.DATA_KEYWORDS, keywords);
+ values.put(IndexColumns.CLASS_NAME, className);
+ values.put(IndexColumns.SCREEN_TITLE, screenTitle);
+ values.put(IndexColumns.INTENT_ACTION, intentAction);
+ values.put(IndexColumns.INTENT_TARGET_PACKAGE, intentTargetPackage);
+ values.put(IndexColumns.INTENT_TARGET_CLASS, intentTargetClass);
+ values.put(IndexColumns.ICON, iconResId);
+ values.put(IndexColumns.ENABLED, enabled);
+ values.put(IndexColumns.DATA_KEY_REF, key);
+ values.put(IndexColumns.USER_ID, userId);
+
+ database.replaceOrThrow(Tables.TABLE_PREFS_INDEX, null, values);
+ }
+
+ private String getDataKey(Context context, AttributeSet attrs) {
+ return getData(context, attrs,
+ com.android.internal.R.styleable.Preference,
+ com.android.internal.R.styleable.Preference_key);
+ }
+
+ private String getDataTitle(Context context, AttributeSet attrs) {
+ return getData(context, attrs,
+ com.android.internal.R.styleable.Preference,
+ com.android.internal.R.styleable.Preference_title);
+ }
+
+ private String getDataSummary(Context context, AttributeSet attrs) {
+ return getData(context, attrs,
+ com.android.internal.R.styleable.Preference,
+ com.android.internal.R.styleable.Preference_summary);
+ }
+
+ private String getDataSummaryOn(Context context, AttributeSet attrs) {
+ return getData(context, attrs,
+ com.android.internal.R.styleable.CheckBoxPreference,
+ com.android.internal.R.styleable.CheckBoxPreference_summaryOn);
+ }
+
+ private String getDataSummaryOff(Context context, AttributeSet attrs) {
+ return getData(context, attrs,
+ com.android.internal.R.styleable.CheckBoxPreference,
+ com.android.internal.R.styleable.CheckBoxPreference_summaryOff);
+ }
+
+ private String getDataEntries(Context context, AttributeSet attrs) {
+ return getDataEntries(context, attrs,
+ com.android.internal.R.styleable.ListPreference,
+ com.android.internal.R.styleable.ListPreference_entries);
+ }
+
+ private String getDataKeywords(Context context, AttributeSet attrs) {
+ return getData(context, attrs, R.styleable.Preference, R.styleable.Preference_keywords);
+ }
+
+ private String getData(Context context, AttributeSet set, int[] attrs, int resId) {
+ final TypedArray sa = context.obtainStyledAttributes(set, attrs);
+ final TypedValue tv = sa.peekValue(resId);
+
+ CharSequence data = null;
+ if (tv != null && tv.type == TypedValue.TYPE_STRING) {
+ if (tv.resourceId != 0) {
+ data = context.getText(tv.resourceId);
+ } else {
+ data = tv.string;
+ }
+ }
+ return (data != null) ? data.toString() : null;
+ }
+
+ private String getDataEntries(Context context, AttributeSet set, int[] attrs, int resId) {
+ final TypedArray sa = context.obtainStyledAttributes(set, attrs);
+ final TypedValue tv = sa.peekValue(resId);
+
+ String[] data = null;
+ if (tv != null && tv.type == TypedValue.TYPE_REFERENCE) {
+ if (tv.resourceId != 0) {
+ data = context.getResources().getStringArray(tv.resourceId);
+ }
+ }
+ final int count = (data == null ) ? 0 : data.length;
+ if (count == 0) {
+ return null;
+ }
+ final StringBuilder result = new StringBuilder();
+ for (int n = 0; n < count; n++) {
+ result.append(data[n]);
+ result.append(ENTRIES_SEPARATOR);
+ }
+ return result.toString();
+ }
+
+ private int getResId(Context context, AttributeSet set, int[] attrs, int resId) {
+ final TypedArray sa = context.obtainStyledAttributes(set, attrs);
+ final TypedValue tv = sa.peekValue(resId);
+
+ if (tv != null && tv.type == TypedValue.TYPE_STRING) {
+ return tv.resourceId;
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * A private class for updating the Index database
+ */
+ private class UpdateIndexTask extends AsyncTask<UpdateData, Integer, Void> {
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ mIsAvailable.set(false);
+ }
+
+ @Override
+ protected void onPostExecute(Void aVoid) {
+ super.onPostExecute(aVoid);
+ mIsAvailable.set(true);
+ }
+
+ @Override
+ protected Void doInBackground(UpdateData... params) {
+ final List<SearchIndexableData> dataToUpdate = params[0].dataToUpdate;
+ final List<SearchIndexableData> dataToDelete = params[0].dataToDelete;
+ final Map<String, List<String>> nonIndexableKeys = params[0].nonIndexableKeys;
+
+ final boolean forceUpdate = params[0].forceUpdate;
+
+ final SQLiteDatabase database = getWritableDatabase();
+ final String localeStr = Locale.getDefault().toString();
+
+ try {
+ database.beginTransaction();
+ if (dataToDelete.size() > 0) {
+ processDataToDelete(database, localeStr, dataToDelete);
+ }
+ if (dataToUpdate.size() > 0) {
+ processDataToUpdate(database, localeStr, dataToUpdate, nonIndexableKeys,
+ forceUpdate);
+ }
+ database.setTransactionSuccessful();
+ } finally {
+ database.endTransaction();
+ }
+
+ return null;
+ }
+
+ private boolean processDataToUpdate(SQLiteDatabase database, String localeStr,
+ List<SearchIndexableData> dataToUpdate, Map<String, List<String>> nonIndexableKeys,
+ boolean forceUpdate) {
+
+ if (!forceUpdate && isLocaleAlreadyIndexed(database, localeStr)) {
+ Log.d(LOG_TAG, "Locale '" + localeStr + "' is already indexed");
+ return true;
+ }
+
+ boolean result = false;
+ final long current = System.currentTimeMillis();
+
+ final int count = dataToUpdate.size();
+ for (int n = 0; n < count; n++) {
+ final SearchIndexableData data = dataToUpdate.get(n);
+ try {
+ indexOneSearchIndexableData(database, localeStr, data, nonIndexableKeys);
+ } catch (Exception e) {
+ Log.e(LOG_TAG,
+ "Cannot index: " + data.className + " for locale: " + localeStr, e);
+ }
+ }
+
+ final long now = System.currentTimeMillis();
+ Log.d(LOG_TAG, "Indexing locale '" + localeStr + "' took " +
+ (now - current) + " millis");
+ return result;
+ }
+
+ private boolean processDataToDelete(SQLiteDatabase database, String localeStr,
+ List<SearchIndexableData> dataToDelete) {
+
+ boolean result = false;
+ final long current = System.currentTimeMillis();
+
+ final int count = dataToDelete.size();
+ for (int n = 0; n < count; n++) {
+ final SearchIndexableData data = dataToDelete.get(n);
+ if (data == null) {
+ continue;
+ }
+ if (!TextUtils.isEmpty(data.className)) {
+ delete(database, IndexColumns.CLASS_NAME, data.className);
+ } else {
+ if (data instanceof SearchIndexableRaw) {
+ final SearchIndexableRaw raw = (SearchIndexableRaw) data;
+ if (!TextUtils.isEmpty(raw.title)) {
+ delete(database, IndexColumns.DATA_TITLE, raw.title);
+ }
+ }
+ }
+ }
+
+ final long now = System.currentTimeMillis();
+ Log.d(LOG_TAG, "Deleting data for locale '" + localeStr + "' took " +
+ (now - current) + " millis");
+ return result;
+ }
+
+ private int delete(SQLiteDatabase database, String columName, String value) {
+ final String whereClause = columName + "=?";
+ final String[] whereArgs = new String[] { value };
+
+ return database.delete(Tables.TABLE_PREFS_INDEX, whereClause, whereArgs);
+ }
+
+ private boolean isLocaleAlreadyIndexed(SQLiteDatabase database, String locale) {
+ Cursor cursor = null;
+ boolean result = false;
+ final StringBuilder sb = new StringBuilder(IndexColumns.LOCALE);
+ sb.append(" = ");
+ DatabaseUtils.appendEscapedSQLString(sb, locale);
+ try {
+ // We care only for 1 row
+ cursor = database.query(Tables.TABLE_PREFS_INDEX, null,
+ sb.toString(), null, null, null, null, "1");
+ final int count = cursor.getCount();
+ result = (count >= 1);
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return result;
+ }
+ }
+
+ /**
+ * A basic AsyncTask for saving a Search query into the database
+ */
+ private class SaveSearchQueryTask extends AsyncTask<String, Void, Long> {
+
+ @Override
+ protected Long doInBackground(String... params) {
+ final long now = new Date().getTime();
+
+ final ContentValues values = new ContentValues();
+ values.put(IndexDatabaseHelper.SavedQueriesColums.QUERY, params[0]);
+ values.put(IndexDatabaseHelper.SavedQueriesColums.TIME_STAMP, now);
+
+ final SQLiteDatabase database = getWritableDatabase();
+
+ long lastInsertedRowId = -1;
+ try {
+ // First, delete all saved queries that are the same
+ database.delete(Tables.TABLE_SAVED_QUERIES,
+ IndexDatabaseHelper.SavedQueriesColums.QUERY + " = ?",
+ new String[] { params[0] });
+
+ // Second, insert the saved query
+ lastInsertedRowId =
+ database.insertOrThrow(Tables.TABLE_SAVED_QUERIES, null, values);
+
+ // Last, remove "old" saved queries
+ final long delta = lastInsertedRowId - MAX_SAVED_SEARCH_QUERY;
+ if (delta > 0) {
+ int count = database.delete(Tables.TABLE_SAVED_QUERIES, "rowId <= ?",
+ new String[] { Long.toString(delta) });
+ Log.d(LOG_TAG, "Deleted '" + count + "' saved Search query(ies)");
+ }
+ } catch (Exception e) {
+ Log.d(LOG_TAG, "Cannot update saved Search queries", e);
+ }
+
+ return lastInsertedRowId;
+ }
+ }
+}
diff --git a/src/com/android/settings/search/IndexDatabaseHelper.java b/src/com/android/settings/search/IndexDatabaseHelper.java
new file mode 100644
index 0000000..152cbf3
--- /dev/null
+++ b/src/com/android/settings/search/IndexDatabaseHelper.java
@@ -0,0 +1,223 @@
+/*
+ * 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.search;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.os.Build;
+import android.util.Log;
+
+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;
+
+ public interface Tables {
+ public static final String TABLE_PREFS_INDEX = "prefs_index";
+ public static final String TABLE_META_INDEX = "meta_index";
+ public static final String TABLE_SAVED_QUERIES = "saved_queries";
+ }
+
+ public interface IndexColumns {
+ public static final String DOCID = "docid";
+ public static final String LOCALE = "locale";
+ public static final String DATA_RANK = "data_rank";
+ public static final String DATA_TITLE = "data_title";
+ public static final String DATA_TITLE_NORMALIZED = "data_title_normalized";
+ public static final String DATA_SUMMARY_ON = "data_summary_on";
+ public static final String DATA_SUMMARY_ON_NORMALIZED = "data_summary_on_normalized";
+ public static final String DATA_SUMMARY_OFF = "data_summary_off";
+ public static final String DATA_SUMMARY_OFF_NORMALIZED = "data_summary_off_normalized";
+ public static final String DATA_ENTRIES = "data_entries";
+ public static final String DATA_KEYWORDS = "data_keywords";
+ public static final String CLASS_NAME = "class_name";
+ public static final String SCREEN_TITLE = "screen_title";
+ public static final String INTENT_ACTION = "intent_action";
+ public static final String INTENT_TARGET_PACKAGE = "intent_target_package";
+ public static final String INTENT_TARGET_CLASS = "intent_target_class";
+ public static final String ICON = "icon";
+ public static final String ENABLED = "enabled";
+ public static final String DATA_KEY_REF = "data_key_reference";
+ public static final String USER_ID = "user_id";
+ }
+
+ public interface MetaColumns {
+ public static final String BUILD = "build";
+ }
+
+ public interface SavedQueriesColums {
+ public static final String QUERY = "query";
+ public static final String TIME_STAMP = "timestamp";
+ }
+
+ private static final String CREATE_INDEX_TABLE =
+ "CREATE VIRTUAL TABLE " + Tables.TABLE_PREFS_INDEX + " USING fts4" +
+ "(" +
+ IndexColumns.LOCALE +
+ ", " +
+ IndexColumns.DATA_RANK +
+ ", " +
+ IndexColumns.DATA_TITLE +
+ ", " +
+ IndexColumns.DATA_TITLE_NORMALIZED +
+ ", " +
+ IndexColumns.DATA_SUMMARY_ON +
+ ", " +
+ IndexColumns.DATA_SUMMARY_ON_NORMALIZED +
+ ", " +
+ IndexColumns.DATA_SUMMARY_OFF +
+ ", " +
+ IndexColumns.DATA_SUMMARY_OFF_NORMALIZED +
+ ", " +
+ IndexColumns.DATA_ENTRIES +
+ ", " +
+ IndexColumns.DATA_KEYWORDS +
+ ", " +
+ IndexColumns.SCREEN_TITLE +
+ ", " +
+ IndexColumns.CLASS_NAME +
+ ", " +
+ IndexColumns.ICON +
+ ", " +
+ IndexColumns.INTENT_ACTION +
+ ", " +
+ IndexColumns.INTENT_TARGET_PACKAGE +
+ ", " +
+ IndexColumns.INTENT_TARGET_CLASS +
+ ", " +
+ IndexColumns.ENABLED +
+ ", " +
+ IndexColumns.DATA_KEY_REF +
+ ", " +
+ IndexColumns.USER_ID +
+ ");";
+
+ private static final String CREATE_META_TABLE =
+ "CREATE TABLE " + Tables.TABLE_META_INDEX +
+ "(" +
+ MetaColumns.BUILD + " VARCHAR(32) NOT NULL" +
+ ")";
+
+ private static final String CREATE_SAVED_QUERIES_TABLE =
+ "CREATE TABLE " + Tables.TABLE_SAVED_QUERIES +
+ "(" +
+ SavedQueriesColums.QUERY + " VARCHAR(64) NOT NULL" +
+ ", " +
+ SavedQueriesColums.TIME_STAMP + " INTEGER" +
+ ")";
+
+ private static final String INSERT_BUILD_VERSION =
+ "INSERT INTO " + Tables.TABLE_META_INDEX +
+ " VALUES ('" + Build.VERSION.INCREMENTAL + "');";
+
+ private static final String SELECT_BUILD_VERSION =
+ "SELECT " + MetaColumns.BUILD + " FROM " + Tables.TABLE_META_INDEX + " LIMIT 1;";
+
+ private static IndexDatabaseHelper sSingleton;
+
+ public static synchronized IndexDatabaseHelper getInstance(Context context) {
+ if (sSingleton == null) {
+ sSingleton = new IndexDatabaseHelper(context);
+ }
+ return sSingleton;
+ }
+
+ public IndexDatabaseHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ bootstrapDB(db);
+ }
+
+ private void bootstrapDB(SQLiteDatabase db) {
+ db.execSQL(CREATE_INDEX_TABLE);
+ db.execSQL(CREATE_META_TABLE);
+ db.execSQL(CREATE_SAVED_QUERIES_TABLE);
+ db.execSQL(INSERT_BUILD_VERSION);
+ Log.i(TAG, "Bootstrapped database");
+ }
+
+ @Override
+ public void onOpen(SQLiteDatabase db) {
+ super.onOpen(db);
+
+ Log.i(TAG, "Using schema version: " + db.getVersion());
+
+ if (!Build.VERSION.INCREMENTAL.equals(getBuildVersion(db))) {
+ Log.w(TAG, "Index needs to be rebuilt as build-version is not the same");
+ // We need to drop the tables and recreate them
+ reconstruct(db);
+ } else {
+ Log.i(TAG, "Index is fine");
+ }
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ if (oldVersion < DATABASE_VERSION) {
+ Log.w(TAG, "Detected schema version '" + oldVersion + "'. " +
+ "Index needs to be rebuilt for schema version '" + newVersion + "'.");
+ // We need to drop the tables and recreate them
+ reconstruct(db);
+ }
+ }
+
+ @Override
+ public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ Log.w(TAG, "Detected schema version '" + oldVersion + "'. " +
+ "Index needs to be rebuilt for schema version '" + newVersion + "'.");
+ // We need to drop the tables and recreate them
+ reconstruct(db);
+ }
+
+ private void reconstruct(SQLiteDatabase db) {
+ dropTables(db);
+ bootstrapDB(db);
+ }
+
+ private String getBuildVersion(SQLiteDatabase db) {
+ String version = null;
+ Cursor cursor = null;
+ try {
+ cursor = db.rawQuery(SELECT_BUILD_VERSION, null);
+ if (cursor.moveToFirst()) {
+ version = cursor.getString(0);
+ }
+ }
+ catch (Exception e) {
+ Log.e(TAG, "Cannot get build version from Index metadata");
+ }
+ finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return version;
+ }
+
+ private void dropTables(SQLiteDatabase db) {
+ db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_META_INDEX);
+ db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_PREFS_INDEX);
+ db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_SAVED_QUERIES);
+ }
+}
diff --git a/src/com/android/settings/search/Indexable.java b/src/com/android/settings/search/Indexable.java
new file mode 100644
index 0000000..19f88ae
--- /dev/null
+++ b/src/com/android/settings/search/Indexable.java
@@ -0,0 +1,69 @@
+/*
+ * 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.search;
+
+import android.content.Context;
+import android.provider.SearchIndexableResource;
+
+import java.util.List;
+
+/**
+ * Interface for classes whose instances can provide data for indexing.
+ *
+ * Classes implementing the Indexable interface must have a static field called
+ * <code>SEARCH_INDEX_DATA_PROVIDER</code>, which is an object implementing the
+ * {@link Indexable.SearchIndexProvider} interface.
+ *
+ * See {@link android.provider.SearchIndexableResource} and {@link SearchIndexableRaw}.
+ *
+ */
+public interface Indexable {
+
+ public interface SearchIndexProvider {
+ /**
+ * Return a list of references for indexing.
+ *
+ * See {@link android.provider.SearchIndexableResource}
+ *
+ *
+ * @param context the context.
+ * @param enabled hint telling if the data needs to be considered into the search results
+ * or not.
+ * @return a list of {@link android.provider.SearchIndexableResource} references.
+ * Can be null.
+ */
+ List<SearchIndexableResource> getXmlResourcesToIndex(Context context, boolean enabled);
+
+ /**
+ * Return a list of raw data for indexing. See {@link SearchIndexableRaw}
+ *
+ * @param context the context.
+ * @param enabled hint telling if the data needs to be considered into the search results
+ * or not.
+ * @return a list of {@link SearchIndexableRaw} references. Can be null.
+ */
+ List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled);
+
+ /**
+ * Return a list of data keys that cannot be indexed. See {@link SearchIndexableRaw}
+ *
+ * @param context the context.
+ * @return a list of {@link SearchIndexableRaw} references. Can be null.
+ */
+ List<String> getNonIndexableKeys(Context context);
+ }
+}
diff --git a/src/com/android/settings/search/Ranking.java b/src/com/android/settings/search/Ranking.java
new file mode 100644
index 0000000..2c76002
--- /dev/null
+++ b/src/com/android/settings/search/Ranking.java
@@ -0,0 +1,180 @@
+/*
+ * 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.search;
+
+import com.android.settings.ChooseLockGeneric;
+import com.android.settings.DataUsageSummary;
+import com.android.settings.DateTimeSettings;
+import com.android.settings.DevelopmentSettings;
+import com.android.settings.DeviceInfoSettings;
+import com.android.settings.DisplaySettings;
+import com.android.settings.HomeSettings;
+import com.android.settings.ScreenPinningSettings;
+import com.android.settings.PrivacySettings;
+import com.android.settings.SecuritySettings;
+import com.android.settings.WallpaperTypeSettings;
+import com.android.settings.WirelessSettings;
+import com.android.settings.accessibility.AccessibilitySettings;
+import com.android.settings.bluetooth.BluetoothSettings;
+import com.android.settings.deviceinfo.Memory;
+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.net.DataUsageMeteredSettings;
+import com.android.settings.notification.NotificationSettings;
+import com.android.settings.notification.OtherSoundSettings;
+import com.android.settings.notification.ZenModeSettings;
+import com.android.settings.print.PrintSettingsFragment;
+import com.android.settings.sim.SimSettings;
+import com.android.settings.users.UserSettings;
+import com.android.settings.voice.VoiceInputSettings;
+import com.android.settings.wifi.AdvancedWifiSettings;
+import com.android.settings.wifi.SavedAccessPointsWifiSettings;
+import com.android.settings.wifi.WifiSettings;
+
+import java.util.HashMap;
+
+/**
+ * Utility class for dealing with Search Ranking.
+ */
+public final class Ranking {
+
+ public static final int RANK_WIFI = 1;
+ public static final int RANK_BT = 2;
+ public static final int RANK_SIM = 3;
+ public static final int RANK_DATA_USAGE = 4;
+ public static final int RANK_WIRELESS = 5;
+ public static final int RANK_HOME = 6;
+ public static final int RANK_DISPLAY = 7;
+ public static final int RANK_WALLPAPER = 8;
+ public static final int RANK_NOTIFICATIONS = 9;
+ public static final int RANK_MEMORY = 10;
+ public static final int RANK_POWER_USAGE = 11;
+ public static final int RANK_USERS = 12;
+ public static final int RANK_LOCATION = 13;
+ public static final int RANK_SECURITY = 14;
+ public static final int RANK_IME = 15;
+ public static final int RANK_PRIVACY = 16;
+ public static final int RANK_DATE_TIME = 17;
+ public static final int RANK_ACCESSIBILITY = 18;
+ public static final int RANK_PRINTING = 19;
+ public static final int RANK_DEVELOPEMENT = 20;
+ public static final int RANK_DEVICE_INFO = 21;
+
+ public static final int RANK_UNDEFINED = -1;
+ public static final int RANK_OTHERS = 1024;
+ public static final int BASE_RANK_DEFAULT = 2048;
+
+ public static int sCurrentBaseRank = BASE_RANK_DEFAULT;
+
+ private static HashMap<String, Integer> sRankMap = new HashMap<String, Integer>();
+ private static HashMap<String, Integer> sBaseRankMap = new HashMap<String, Integer>();
+
+ static {
+ // Wi-Fi
+ sRankMap.put(WifiSettings.class.getName(), RANK_WIFI);
+ sRankMap.put(AdvancedWifiSettings.class.getName(), RANK_WIFI);
+ sRankMap.put(SavedAccessPointsWifiSettings.class.getName(), RANK_WIFI);
+
+ // BT
+ sRankMap.put(BluetoothSettings.class.getName(), RANK_BT);
+
+ // SIM Cards
+ sRankMap.put(SimSettings.class.getName(), RANK_SIM);
+
+ // DataUsage
+ sRankMap.put(DataUsageSummary.class.getName(), RANK_DATA_USAGE);
+ sRankMap.put(DataUsageMeteredSettings.class.getName(), RANK_DATA_USAGE);
+
+ // Other wireless settinfs
+ sRankMap.put(WirelessSettings.class.getName(), RANK_WIRELESS);
+
+ // Home
+ sRankMap.put(HomeSettings.class.getName(), RANK_HOME);
+
+ // Display
+ sRankMap.put(DisplaySettings.class.getName(), RANK_DISPLAY);
+
+ // Wallpapers
+ sRankMap.put(WallpaperTypeSettings.class.getName(), RANK_WALLPAPER);
+
+ // Notifications
+ sRankMap.put(NotificationSettings.class.getName(), RANK_NOTIFICATIONS);
+ sRankMap.put(OtherSoundSettings.class.getName(), RANK_NOTIFICATIONS);
+ sRankMap.put(ZenModeSettings.class.getName(), RANK_NOTIFICATIONS);
+
+ // Memory
+ sRankMap.put(Memory.class.getName(), RANK_MEMORY);
+
+ // Battery
+ sRankMap.put(PowerUsageSummary.class.getName(), RANK_POWER_USAGE);
+ sRankMap.put(BatterySaverSettings.class.getName(), RANK_POWER_USAGE);
+
+ // Users
+ sRankMap.put(UserSettings.class.getName(), RANK_USERS);
+
+ // Location
+ sRankMap.put(LocationSettings.class.getName(), RANK_LOCATION);
+
+ // Security
+ sRankMap.put(SecuritySettings.class.getName(), RANK_SECURITY);
+ sRankMap.put(ChooseLockGeneric.ChooseLockGenericFragment.class.getName(), RANK_SECURITY);
+ sRankMap.put(ScreenPinningSettings.class.getName(), RANK_SECURITY);
+
+ // IMEs
+ sRankMap.put(InputMethodAndLanguageSettings.class.getName(), RANK_IME);
+ sRankMap.put(VoiceInputSettings.class.getName(), RANK_IME);
+
+ // Privacy
+ sRankMap.put(PrivacySettings.class.getName(), RANK_PRIVACY);
+
+ // Date / Time
+ sRankMap.put(DateTimeSettings.class.getName(), RANK_DATE_TIME);
+
+ // Accessibility
+ sRankMap.put(AccessibilitySettings.class.getName(), RANK_ACCESSIBILITY);
+
+ // Print
+ sRankMap.put(PrintSettingsFragment.class.getName(), RANK_PRINTING);
+
+ // Development
+ sRankMap.put(DevelopmentSettings.class.getName(), RANK_DEVELOPEMENT);
+
+ // Device infos
+ sRankMap.put(DeviceInfoSettings.class.getName(), RANK_DEVICE_INFO);
+
+ sBaseRankMap.put("com.android.settings", 0);
+ }
+
+ public static int getRankForClassName(String className) {
+ Integer rank = sRankMap.get(className);
+ return (rank != null) ? (int) rank: RANK_OTHERS;
+ }
+
+ public static int getBaseRankForAuthority(String authority) {
+ synchronized (sBaseRankMap) {
+ Integer base = sBaseRankMap.get(authority);
+ if (base != null) {
+ return base;
+ }
+ sCurrentBaseRank++;
+ sBaseRankMap.put(authority, sCurrentBaseRank);
+ return sCurrentBaseRank;
+ }
+ }
+}
diff --git a/src/com/android/settings/search/SearchIndexableRaw.java b/src/com/android/settings/search/SearchIndexableRaw.java
new file mode 100644
index 0000000..b8a1699
--- /dev/null
+++ b/src/com/android/settings/search/SearchIndexableRaw.java
@@ -0,0 +1,64 @@
+/*
+ * 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.search;
+
+import android.content.Context;
+import android.provider.SearchIndexableData;
+
+/**
+ * Indexable raw data for Search.
+ *
+ * This is the raw data used by the Indexer and should match its data model.
+ *
+ * See {@link Indexable} and {@link android.provider.SearchIndexableResource}.
+ */
+public class SearchIndexableRaw extends SearchIndexableData {
+
+ /**
+ * Title's raw data.
+ */
+ public String title;
+
+ /**
+ * Summary's raw data when the data is "ON".
+ */
+ public String summaryOn;
+
+ /**
+ * Summary's raw data when the data is "OFF".
+ */
+ public String summaryOff;
+
+ /**
+ * Entries associated with the raw data (when the data can have several values).
+ */
+ public String entries;
+
+ /**
+ * Keywords' raw data.
+ */
+ public String keywords;
+
+ /**
+ * Fragment's or Activity's title associated with the raw data.
+ */
+ public String screenTitle;
+
+ public SearchIndexableRaw(Context context) {
+ super(context);
+ }
+}
diff --git a/src/com/android/settings/search/SearchIndexableResources.java b/src/com/android/settings/search/SearchIndexableResources.java
new file mode 100644
index 0000000..7b3fa77
--- /dev/null
+++ b/src/com/android/settings/search/SearchIndexableResources.java
@@ -0,0 +1,281 @@
+/*
+ * 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.search;
+
+import android.provider.SearchIndexableResource;
+
+import com.android.settings.DataUsageSummary;
+import com.android.settings.DateTimeSettings;
+import com.android.settings.DevelopmentSettings;
+import com.android.settings.DeviceInfoSettings;
+import com.android.settings.DisplaySettings;
+import com.android.settings.HomeSettings;
+import com.android.settings.ScreenPinningSettings;
+import com.android.settings.PrivacySettings;
+import com.android.settings.R;
+import com.android.settings.SecuritySettings;
+import com.android.settings.WallpaperTypeSettings;
+import com.android.settings.WirelessSettings;
+import com.android.settings.accessibility.AccessibilitySettings;
+import com.android.settings.bluetooth.BluetoothSettings;
+import com.android.settings.deviceinfo.Memory;
+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.net.DataUsageMeteredSettings;
+import com.android.settings.notification.NotificationSettings;
+import com.android.settings.notification.OtherSoundSettings;
+import com.android.settings.notification.ZenModeSettings;
+import com.android.settings.print.PrintSettingsFragment;
+import com.android.settings.sim.SimSettings;
+import com.android.settings.users.UserSettings;
+import com.android.settings.voice.VoiceInputSettings;
+import com.android.settings.wifi.AdvancedWifiSettings;
+import com.android.settings.wifi.SavedAccessPointsWifiSettings;
+import com.android.settings.wifi.WifiSettings;
+
+import java.util.Collection;
+import java.util.HashMap;
+
+public final class SearchIndexableResources {
+
+ public static int NO_DATA_RES_ID = 0;
+
+ private static HashMap<String, SearchIndexableResource> sResMap =
+ new HashMap<String, SearchIndexableResource>();
+
+ static {
+ sResMap.put(WifiSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(WifiSettings.class.getName()),
+ NO_DATA_RES_ID,
+ WifiSettings.class.getName(),
+ R.drawable.ic_settings_wireless));
+
+ sResMap.put(AdvancedWifiSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(AdvancedWifiSettings.class.getName()),
+ R.xml.wifi_advanced_settings,
+ AdvancedWifiSettings.class.getName(),
+ R.drawable.ic_settings_wireless));
+
+ sResMap.put(SavedAccessPointsWifiSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(SavedAccessPointsWifiSettings.class.getName()),
+ R.xml.wifi_display_saved_access_points,
+ SavedAccessPointsWifiSettings.class.getName(),
+ R.drawable.ic_settings_wireless));
+
+ sResMap.put(BluetoothSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(BluetoothSettings.class.getName()),
+ NO_DATA_RES_ID,
+ BluetoothSettings.class.getName(),
+ R.drawable.ic_settings_bluetooth2));
+
+ sResMap.put(SimSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(SimSettings.class.getName()),
+ NO_DATA_RES_ID,
+ SimSettings.class.getName(),
+ R.drawable.ic_sim_sd));
+
+ sResMap.put(DataUsageSummary.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(DataUsageSummary.class.getName()),
+ NO_DATA_RES_ID,
+ DataUsageSummary.class.getName(),
+ R.drawable.ic_settings_data_usage));
+
+ sResMap.put(DataUsageMeteredSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(DataUsageMeteredSettings.class.getName()),
+ NO_DATA_RES_ID,
+ DataUsageMeteredSettings.class.getName(),
+ R.drawable.ic_settings_data_usage));
+
+ sResMap.put(WirelessSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(WirelessSettings.class.getName()),
+ NO_DATA_RES_ID,
+ WirelessSettings.class.getName(),
+ R.drawable.ic_settings_more));
+
+ sResMap.put(HomeSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(HomeSettings.class.getName()),
+ NO_DATA_RES_ID,
+ HomeSettings.class.getName(),
+ R.drawable.ic_settings_home));
+
+ sResMap.put(DisplaySettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(DisplaySettings.class.getName()),
+ NO_DATA_RES_ID,
+ DisplaySettings.class.getName(),
+ R.drawable.ic_settings_display));
+
+ sResMap.put(WallpaperTypeSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(WallpaperTypeSettings.class.getName()),
+ NO_DATA_RES_ID,
+ WallpaperTypeSettings.class.getName(),
+ R.drawable.ic_settings_display));
+
+ sResMap.put(NotificationSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(NotificationSettings.class.getName()),
+ NO_DATA_RES_ID,
+ NotificationSettings.class.getName(),
+ R.drawable.ic_settings_notifications));
+
+ sResMap.put(OtherSoundSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(OtherSoundSettings.class.getName()),
+ NO_DATA_RES_ID,
+ OtherSoundSettings.class.getName(),
+ R.drawable.ic_settings_notifications));
+
+ sResMap.put(ZenModeSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(ZenModeSettings.class.getName()),
+ NO_DATA_RES_ID,
+ ZenModeSettings.class.getName(),
+ R.drawable.ic_settings_notifications));
+
+ sResMap.put(Memory.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(Memory.class.getName()),
+ NO_DATA_RES_ID,
+ Memory.class.getName(),
+ R.drawable.ic_settings_storage));
+
+ sResMap.put(PowerUsageSummary.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(PowerUsageSummary.class.getName()),
+ R.xml.power_usage_summary,
+ 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(UserSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(UserSettings.class.getName()),
+ R.xml.user_settings,
+ UserSettings.class.getName(),
+ R.drawable.ic_settings_multiuser));
+
+ sResMap.put(LocationSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(LocationSettings.class.getName()),
+ R.xml.location_settings,
+ LocationSettings.class.getName(),
+ R.drawable.ic_settings_location));
+
+ 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(ScreenPinningSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(ScreenPinningSettings.class.getName()),
+ NO_DATA_RES_ID,
+ ScreenPinningSettings.class.getName(),
+ R.drawable.ic_settings_security));
+
+ sResMap.put(InputMethodAndLanguageSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(InputMethodAndLanguageSettings.class.getName()),
+ NO_DATA_RES_ID,
+ InputMethodAndLanguageSettings.class.getName(),
+ R.drawable.ic_settings_language));
+
+ sResMap.put(VoiceInputSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(VoiceInputSettings.class.getName()),
+ NO_DATA_RES_ID,
+ VoiceInputSettings.class.getName(),
+ R.drawable.ic_settings_language));
+
+ sResMap.put(PrivacySettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(PrivacySettings.class.getName()),
+ NO_DATA_RES_ID,
+ PrivacySettings.class.getName(),
+ R.drawable.ic_settings_backup));
+
+ sResMap.put(DateTimeSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(DateTimeSettings.class.getName()),
+ R.xml.date_time_prefs,
+ DateTimeSettings.class.getName(),
+ R.drawable.ic_settings_date_time));
+
+ sResMap.put(AccessibilitySettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(AccessibilitySettings.class.getName()),
+ NO_DATA_RES_ID,
+ AccessibilitySettings.class.getName(),
+ R.drawable.ic_settings_accessibility));
+
+ sResMap.put(PrintSettingsFragment.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(PrintSettingsFragment.class.getName()),
+ NO_DATA_RES_ID,
+ PrintSettingsFragment.class.getName(),
+ R.drawable.ic_settings_print));
+
+ sResMap.put(DevelopmentSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(DevelopmentSettings.class.getName()),
+ NO_DATA_RES_ID,
+ DevelopmentSettings.class.getName(),
+ R.drawable.ic_settings_development));
+
+ sResMap.put(DeviceInfoSettings.class.getName(),
+ new SearchIndexableResource(
+ Ranking.getRankForClassName(DeviceInfoSettings.class.getName()),
+ NO_DATA_RES_ID,
+ DeviceInfoSettings.class.getName(),
+ R.drawable.ic_settings_about));
+ }
+
+ private SearchIndexableResources() {
+ }
+
+ public static int size() {
+ return sResMap.size();
+ }
+
+ public static SearchIndexableResource getResourceByName(String className) {
+ return sResMap.get(className);
+ }
+
+ public static Collection<SearchIndexableResource> values() {
+ return sResMap.values();
+ }
+}
diff --git a/src/com/android/settings/search/SettingsSearchIndexablesProvider.java b/src/com/android/settings/search/SettingsSearchIndexablesProvider.java
new file mode 100644
index 0000000..c0afcaf
--- /dev/null
+++ b/src/com/android/settings/search/SettingsSearchIndexablesProvider.java
@@ -0,0 +1,75 @@
+/*
+ * 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.search;
+
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.provider.SearchIndexableResource;
+import android.provider.SearchIndexablesProvider;
+
+import java.util.Collection;
+
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_RANK;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_RESID;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_CLASS_NAME;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_ICON_RESID;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_ACTION;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS;
+
+import static android.provider.SearchIndexablesContract.INDEXABLES_XML_RES_COLUMNS;
+import static android.provider.SearchIndexablesContract.INDEXABLES_RAW_COLUMNS;
+import static android.provider.SearchIndexablesContract.NON_INDEXABLES_KEYS_COLUMNS;
+
+public class SettingsSearchIndexablesProvider extends SearchIndexablesProvider {
+ private static final String TAG = "SettingsSearchIndexablesProvider";
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Cursor queryXmlResources(String[] projection) {
+ MatrixCursor cursor = new MatrixCursor(INDEXABLES_XML_RES_COLUMNS);
+ Collection<SearchIndexableResource> values = SearchIndexableResources.values();
+ for (SearchIndexableResource val : values) {
+ Object[] ref = new Object[7];
+ ref[COLUMN_INDEX_XML_RES_RANK] = val.rank;
+ ref[COLUMN_INDEX_XML_RES_RESID] = val.xmlResId;
+ ref[COLUMN_INDEX_XML_RES_CLASS_NAME] = val.className;
+ ref[COLUMN_INDEX_XML_RES_ICON_RESID] = val.iconResId;
+ ref[COLUMN_INDEX_XML_RES_INTENT_ACTION] = null; // intent action
+ ref[COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE] = null; // intent target package
+ ref[COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS] = null; // intent target class
+ cursor.addRow(ref);
+ }
+ return cursor;
+ }
+
+ @Override
+ public Cursor queryRawData(String[] projection) {
+ MatrixCursor result = new MatrixCursor(INDEXABLES_RAW_COLUMNS);
+ return result;
+ }
+
+ @Override
+ public Cursor queryNonIndexableKeys(String[] projection) {
+ MatrixCursor cursor = new MatrixCursor(NON_INDEXABLES_KEYS_COLUMNS);
+ return cursor;
+ }
+}
diff --git a/src/com/android/settings/sim/SimSettings.java b/src/com/android/settings/sim/SimSettings.java
new file mode 100644
index 0000000..836ea24
--- /dev/null
+++ b/src/com/android/settings/sim/SimSettings.java
@@ -0,0 +1,393 @@
+/*
+ * 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.sim;
+
+import android.provider.SearchIndexableResource;
+import com.android.settings.R;
+
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.DialogInterface;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.PreferenceCategory;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.PreferenceScreen;
+import android.telephony.SubInfoRecord;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telecom.PhoneAccount;
+import android.telephony.CellInfo;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.settings.RestrictedSettingsFragment;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.Utils;
+import com.android.settings.notification.DropDownPreference;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.search.Indexable;
+import com.android.settings.search.Indexable.SearchIndexProvider;
+import com.android.settings.search.SearchIndexableRaw;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SimSettings extends RestrictedSettingsFragment implements Indexable {
+ private static final String TAG = "SimSettings";
+
+ 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";
+ private static final String KEY_ACTIVITIES = "activities";
+
+ /**
+ * By UX design we have use only one Subscription Information(SubInfo) record per SIM slot.
+ * mAvalableSubInfos is the list of SubInfos we present to the user.
+ * mSubInfoList is the list of all SubInfos.
+ */
+ private List<SubInfoRecord> mAvailableSubInfos = null;
+ private List<SubInfoRecord> mSubInfoList = null;
+
+ private SubInfoRecord mCellularData = null;
+ private SubInfoRecord mCalls = null;
+ private SubInfoRecord mSMS = null;
+
+ private int mNumSims;
+
+ public SimSettings() {
+ super(DISALLOW_CONFIG_SIM);
+ }
+
+ @Override
+ public void onCreate(final Bundle bundle) {
+ super.onCreate(bundle);
+
+ if (mSubInfoList == null) {
+ mSubInfoList = SubscriptionManager.getActiveSubInfoList();
+ }
+
+ createPreferences();
+ updateAllOptions();
+ }
+
+ private void createPreferences() {
+ final TelephonyManager tm =
+ (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE);
+
+ addPreferencesFromResource(R.xml.sim_settings);
+
+ final PreferenceCategory simCards = (PreferenceCategory)findPreference(SIM_CARD_CATEGORY);
+
+ final int numSlots = tm.getSimCount();
+ mAvailableSubInfos = new ArrayList<SubInfoRecord>(numSlots);
+ mNumSims = 0;
+ for (int i = 0; i < numSlots; ++i) {
+ final SubInfoRecord sir = findRecordBySlotId(i);
+ simCards.addPreference(new SimPreference(getActivity(), sir, i));
+ mAvailableSubInfos.add(sir);
+ if (sir != null) {
+ mNumSims++;
+ }
+ }
+
+ updateActivitesCategory();
+ }
+
+ private void updateAllOptions() {
+ updateSimSlotValues();
+ updateActivitesCategory();
+ }
+
+ private void updateSimSlotValues() {
+ SubscriptionManager.getAllSubInfoList();
+ final PreferenceCategory simCards = (PreferenceCategory)findPreference(SIM_CARD_CATEGORY);
+ final PreferenceScreen prefScreen = getPreferenceScreen();
+
+ final int prefSize = prefScreen.getPreferenceCount();
+ for (int i = 0; i < prefSize; ++i) {
+ Preference pref = prefScreen.getPreference(i);
+ if (pref instanceof SimPreference) {
+ ((SimPreference)pref).update();
+ }
+ }
+ }
+
+ private void updateActivitesCategory() {
+ createDropDown((DropDownPreference) findPreference(KEY_CELLULAR_DATA));
+ createDropDown((DropDownPreference) findPreference(KEY_CALLS));
+ createDropDown((DropDownPreference) findPreference(KEY_SMS));
+
+ updateCellularDataValues();
+ updateCallValues();
+ updateSmsValues();
+ }
+
+ /**
+ * finds a record with subId.
+ * Since the number of SIMs are few, an array is fine.
+ */
+ private SubInfoRecord findRecordBySubId(final long subId) {
+ final int availableSubInfoLength = mAvailableSubInfos.size();
+
+ for (int i = 0; i < availableSubInfoLength; ++i) {
+ final SubInfoRecord sir = mAvailableSubInfos.get(i);
+ if (sir != null && sir.subId == subId) {
+ return sir;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * finds a record with slotId.
+ * Since the number of SIMs are few, an array is fine.
+ */
+ private SubInfoRecord findRecordBySlotId(final int slotId) {
+ if (mSubInfoList != null){
+ final int availableSubInfoLength = mSubInfoList.size();
+
+ for (int i = 0; i < availableSubInfoLength; ++i) {
+ final SubInfoRecord sir = mSubInfoList.get(i);
+ if (sir.slotId == slotId) {
+ //Right now we take the first subscription on a SIM.
+ return sir;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private void updateSmsValues() {
+ final DropDownPreference simPref = (DropDownPreference) findPreference(KEY_SMS);
+ final SubInfoRecord sir = findRecordBySubId(SubscriptionManager.getDefaultSmsSubId());
+ if (sir != null) {
+ simPref.setSelectedItem(sir.slotId + 1);
+ }
+ simPref.setEnabled(mNumSims > 1);
+ }
+
+ private void updateCellularDataValues() {
+ final DropDownPreference simPref = (DropDownPreference) findPreference(KEY_CELLULAR_DATA);
+ final SubInfoRecord sir = findRecordBySubId(SubscriptionManager.getDefaultDataSubId());
+ if (sir != null) {
+ simPref.setSelectedItem(sir.slotId);
+ }
+ simPref.setEnabled(mNumSims > 1);
+ }
+
+ private void updateCallValues() {
+ final DropDownPreference simPref = (DropDownPreference) findPreference(KEY_CALLS);
+ final SubInfoRecord sir = findRecordBySubId(SubscriptionManager.getDefaultVoiceSubId());
+ if (sir != null) {
+ simPref.setSelectedItem(sir.slotId + 1);
+ }
+ simPref.setEnabled(mNumSims > 1);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ updateAllOptions();
+ }
+
+ @Override
+ public boolean onPreferenceTreeClick(final PreferenceScreen preferenceScreen,
+ final Preference preference) {
+ if (preference instanceof SimPreference) {
+ ((SimPreference)preference).createEditDialog((SimPreference)preference);
+ }
+
+ return true;
+ }
+
+ public void createDropDown(DropDownPreference preference) {
+ final DropDownPreference simPref = preference;
+ final String keyPref = simPref.getKey();
+ final boolean askFirst = keyPref.equals(KEY_CALLS) || keyPref.equals(KEY_SMS);
+
+ simPref.clearItems();
+
+ if (askFirst) {
+ simPref.addItem(getResources().getString(
+ R.string.sim_calls_ask_first_prefs_title), null);
+ }
+
+ final int subAvailableSize = mAvailableSubInfos.size();
+ for (int i = 0; i < subAvailableSize; ++i) {
+ final SubInfoRecord sir = mAvailableSubInfos.get(i);
+ if(sir != null){
+ simPref.addItem(sir.displayName, sir);
+ }
+ }
+
+ simPref.setCallback(new DropDownPreference.Callback() {
+ @Override
+ public boolean onItemSelected(int pos, Object value) {
+ final long subId = value == null ? 0 : ((SubInfoRecord)value).subId;
+
+ if (simPref.getKey().equals(KEY_CELLULAR_DATA)) {
+ SubscriptionManager.setDefaultDataSubId(subId);
+ } else if (simPref.getKey().equals(KEY_CALLS)) {
+ SubscriptionManager.setDefaultVoiceSubId(subId);
+ } else if (simPref.getKey().equals(KEY_SMS)) {
+ // TODO: uncomment once implemented. Bug: 16520931
+ // SubscriptionManager.setDefaultSMSSubId(subId);
+ }
+
+ return true;
+ }
+ });
+ }
+
+ private void setActivity(Preference preference, SubInfoRecord sir) {
+ final String key = preference.getKey();
+
+ if (key.equals(KEY_CELLULAR_DATA)) {
+ mCellularData = sir;
+ } else if (key.equals(KEY_CALLS)) {
+ mCalls = sir;
+ } else if (key.equals(KEY_SMS)) {
+ mSMS = sir;
+ }
+
+ updateActivitesCategory();
+ }
+
+ private class SimPreference extends Preference{
+ private SubInfoRecord mSubInfoRecord;
+ private int mSlotId;
+
+ public SimPreference(Context context, SubInfoRecord subInfoRecord, int slotId) {
+ super(context);
+
+ mSubInfoRecord = subInfoRecord;
+ mSlotId = slotId;
+ setKey("sim" + mSlotId);
+ update();
+ }
+
+ public void update() {
+ final Resources res = getResources();
+
+ setTitle(res.getString(R.string.sim_card_number_title, mSlotId + 1));
+ if (mSubInfoRecord != null) {
+ setSummary(res.getString(R.string.sim_settings_summary,
+ mSubInfoRecord.displayName, mSubInfoRecord.number));
+ setEnabled(true);
+ } else {
+ setSummary(R.string.sim_slot_empty);
+ setFragment(null);
+ setEnabled(false);
+ }
+ }
+
+ public void createEditDialog(SimPreference simPref) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+
+ final View dialogLayout = getActivity().getLayoutInflater().inflate(
+ R.layout.multi_sim_dialog, null);
+ builder.setView(dialogLayout);
+
+ EditText nameText = (EditText)dialogLayout.findViewById(R.id.sim_name);
+ nameText.setText(mSubInfoRecord.displayName);
+
+ TextView numberView = (TextView)dialogLayout.findViewById(R.id.number);
+ numberView.setText(mSubInfoRecord.number);
+
+ TextView carrierView = (TextView)dialogLayout.findViewById(R.id.carrier);
+ carrierView.setText(mSubInfoRecord.displayName);
+
+ builder.setTitle(R.string.sim_editor_title);
+
+ builder.setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int whichButton) {
+ final EditText nameText = (EditText)dialogLayout.findViewById(R.id.sim_name);
+ final Spinner displayNumbers =
+ (Spinner)dialogLayout.findViewById(R.id.display_numbers);
+
+ SubscriptionManager.setDisplayNumberFormat(
+ displayNumbers.getSelectedItemPosition() == 0
+ ? SubscriptionManager.DISPLAY_NUMBER_LAST
+ : SubscriptionManager.DISPLAY_NUMBER_FIRST, mSubInfoRecord.subId);
+
+ mSubInfoRecord.displayName = nameText.getText().toString();
+ SubscriptionManager.setDisplayName(mSubInfoRecord.displayName,
+ mSubInfoRecord.subId);
+
+ updateAllOptions();
+ update();
+ }
+ });
+
+ builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int whichButton) {
+ dialog.dismiss();
+ }
+ });
+
+ builder.create().show();
+ }
+ }
+
+ /**
+ * For search
+ */
+ public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider() {
+ @Override
+ public List<SearchIndexableResource> getXmlResourcesToIndex(Context context,
+ boolean enabled) {
+ ArrayList<SearchIndexableResource> result =
+ new ArrayList<SearchIndexableResource>();
+
+ if (Utils.showSimCardTile(context)) {
+ SearchIndexableResource sir = new SearchIndexableResource(context);
+ sir.xmlResId = R.xml.sim_settings;
+ result.add(sir);
+ }
+
+ return result;
+ }
+ };
+
+}
diff --git a/src/com/android/settings/tts/TextToSpeechSettings.java b/src/com/android/settings/tts/TextToSpeechSettings.java
index 0ff7f4f..b16ab56 100644
--- a/src/com/android/settings/tts/TextToSpeechSettings.java
+++ b/src/com/android/settings/tts/TextToSpeechSettings.java
@@ -20,6 +20,7 @@ import static android.provider.Settings.Secure.TTS_DEFAULT_RATE;
import static android.provider.Settings.Secure.TTS_DEFAULT_SYNTH;
import com.android.settings.R;
+import com.android.settings.SettingsActivity;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.tts.TtsEnginePreference.RadioButtonGroupState;
@@ -27,12 +28,11 @@ import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
import android.content.Intent;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.Preference;
-import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
-import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.speech.tts.TextToSpeech;
import android.speech.tts.UtteranceProgressListener;
@@ -46,6 +46,8 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.Objects;
import java.util.Set;
public class TextToSpeechSettings extends SettingsPreferenceFragment implements
@@ -103,7 +105,7 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
private TextToSpeech mTts = null;
private TtsEngines mEnginesHelper = null;
- private String mSampleText = "";
+ private String mSampleText = null;
/**
* Default locale used by selected TTS engine, null if not connected to any engine.
@@ -164,6 +166,9 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
setTtsUtteranceProgressListener();
initSettings();
+
+ // Prevent restarting the TTS connection on rotation
+ setRetainInstance(true);
}
@Override
@@ -212,7 +217,7 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
// Set up the default rate.
try {
- mDefaultRate = Settings.Secure.getInt(resolver, TTS_DEFAULT_RATE);
+ mDefaultRate = android.provider.Settings.Secure.getInt(resolver, TTS_DEFAULT_RATE);
} catch (SettingNotFoundException e) {
// Default rate setting not found, initialize it
mDefaultRate = TextToSpeech.Engine.DEFAULT_RATE;
@@ -222,12 +227,12 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
mCurrentEngine = mTts.getCurrentEngine();
- PreferenceActivity preferenceActivity = null;
- if (getActivity() instanceof PreferenceActivity) {
- preferenceActivity = (PreferenceActivity) getActivity();
+ SettingsActivity activity = null;
+ if (getActivity() instanceof SettingsActivity) {
+ activity = (SettingsActivity) getActivity();
} else {
throw new IllegalStateException("TextToSpeechSettings used outside a " +
- "PreferenceActivity");
+ "Settings");
}
mEnginePreferenceCategory.removeAll();
@@ -235,7 +240,7 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
List<EngineInfo> engines = mEnginesHelper.getEngines();
for (EngineInfo engine : engines) {
TtsEnginePreference enginePref = new TtsEnginePreference(getActivity(), engine,
- this, preferenceActivity);
+ this, activity);
mEnginePreferenceCategory.addPreference(enginePref);
}
@@ -264,10 +269,16 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
return;
}
- mCurrentDefaultLocale = defaultLocale;
+ // ISO-3166 alpha 3 country codes are out of spec. If we won't normalize,
+ // we may end up with English (USA)and German (DEU).
+ final Locale oldDefaultLocale = mCurrentDefaultLocale;
+ mCurrentDefaultLocale = mEnginesHelper.parseLocaleString(defaultLocale.toString());
+ if (!Objects.equals(oldDefaultLocale, mCurrentDefaultLocale)) {
+ mSampleText = null;
+ }
int defaultAvailable = mTts.setLanguage(defaultLocale);
- if (evaluateDefaultLocale()) {
+ if (evaluateDefaultLocale() && mSampleText == null) {
getSampleText();
}
}
@@ -278,25 +289,32 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
if (mCurrentDefaultLocale == null || mAvailableStrLocals == null) {
return false;
}
- int defaultAvailable = mTts.setLanguage(mCurrentDefaultLocale);
- // Check if language is listed in CheckVoices Action result as available voice.
- String defaultLocaleStr = mCurrentDefaultLocale.getISO3Language();
boolean notInAvailableLangauges = true;
- if (!TextUtils.isEmpty(mCurrentDefaultLocale.getISO3Country())) {
- defaultLocaleStr += "-" + mCurrentDefaultLocale.getISO3Country();
- }
- if (!TextUtils.isEmpty(mCurrentDefaultLocale.getVariant())) {
- defaultLocaleStr += "-" + mCurrentDefaultLocale.getVariant();
- }
+ try {
+ // Check if language is listed in CheckVoices Action result as available voice.
+ String defaultLocaleStr = mCurrentDefaultLocale.getISO3Language();
+ if (!TextUtils.isEmpty(mCurrentDefaultLocale.getISO3Country())) {
+ defaultLocaleStr += "-" + mCurrentDefaultLocale.getISO3Country();
+ }
+ if (!TextUtils.isEmpty(mCurrentDefaultLocale.getVariant())) {
+ defaultLocaleStr += "-" + mCurrentDefaultLocale.getVariant();
+ }
- for (String loc : mAvailableStrLocals) {
- if (loc.equalsIgnoreCase(defaultLocaleStr)) {
- notInAvailableLangauges = false;
- break;
+ for (String loc : mAvailableStrLocals) {
+ if (loc.equalsIgnoreCase(defaultLocaleStr)) {
+ notInAvailableLangauges = false;
+ break;
+ }
}
+ } catch (MissingResourceException e) {
+ if (DBG) Log.wtf(TAG, "MissingResourceException", e);
+ updateEngineStatus(R.string.tts_status_not_supported);
+ updateWidgetState(false);
+ return false;
}
+ int defaultAvailable = mTts.setLanguage(mCurrentDefaultLocale);
if (defaultAvailable == TextToSpeech.LANG_NOT_SUPPORTED ||
defaultAvailable == TextToSpeech.LANG_MISSING_DATA ||
notInAvailableLangauges) {
@@ -315,7 +333,6 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
}
}
-
/**
* Ask the current default engine to return a string of sample text to be
* spoken to the user.
@@ -358,16 +375,21 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
private String getDefaultSampleString() {
if (mTts != null && mTts.getLanguage() != null) {
- final String currentLang = mTts.getLanguage().getISO3Language();
- String[] strings = getActivity().getResources().getStringArray(
- R.array.tts_demo_strings);
- String[] langs = getActivity().getResources().getStringArray(
- R.array.tts_demo_string_langs);
-
- for (int i = 0; i < strings.length; ++i) {
- if (langs[i].equals(currentLang)) {
- return strings[i];
+ try {
+ final String currentLang = mTts.getLanguage().getISO3Language();
+ String[] strings = getActivity().getResources().getStringArray(
+ R.array.tts_demo_strings);
+ String[] langs = getActivity().getResources().getStringArray(
+ R.array.tts_demo_string_langs);
+
+ for (int i = 0; i < strings.length; ++i) {
+ if (langs[i].equals(currentLang)) {
+ return strings[i];
+ }
}
+ } catch (MissingResourceException e) {
+ if (DBG) Log.wtf(TAG, "MissingResourceException", e);
+ // Ignore and fall back to default sample string
}
}
return getString(R.string.tts_default_sample_string);
@@ -422,7 +444,8 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
// Default rate
mDefaultRate = Integer.parseInt((String) objValue);
try {
- Settings.Secure.putInt(getContentResolver(), TTS_DEFAULT_RATE, mDefaultRate);
+ android.provider.Settings.Secure.putInt(getContentResolver(),
+ TTS_DEFAULT_RATE, mDefaultRate);
if (mTts != null) {
mTts.setSpeechRate(mDefaultRate / 100.0f);
}
@@ -466,11 +489,10 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
private void displayNetworkAlert() {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- builder.setTitle(android.R.string.dialog_alert_title);
- builder.setIconAttribute(android.R.attr.alertDialogIcon);
- builder.setMessage(getActivity().getString(R.string.tts_engine_network_required));
- builder.setCancelable(false);
- builder.setPositiveButton(android.R.string.ok, null);
+ builder.setTitle(android.R.string.dialog_alert_title)
+ .setMessage(getActivity().getString(R.string.tts_engine_network_required))
+ .setCancelable(false)
+ .setPositiveButton(android.R.string.ok, null);
AlertDialog dialog = builder.create();
dialog.show();
@@ -565,7 +587,7 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
return;
}
- Settings.Secure.putString(getContentResolver(), TTS_DEFAULT_SYNTH, engine);
+ android.provider.Settings.Secure.putString(getContentResolver(), TTS_DEFAULT_SYNTH, engine);
mAvailableStrLocals = data.getStringArrayListExtra(
TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES);
diff --git a/src/com/android/settings/tts/TtsEnginePreference.java b/src/com/android/settings/tts/TtsEnginePreference.java
index 486fdf8..ae921f8 100644
--- a/src/com/android/settings/tts/TtsEnginePreference.java
+++ b/src/com/android/settings/tts/TtsEnginePreference.java
@@ -22,7 +22,6 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.preference.Preference;
-import android.preference.PreferenceActivity;
import android.speech.tts.TextToSpeech.EngineInfo;
import android.util.Log;
import android.view.View;
@@ -33,6 +32,7 @@ import android.widget.RadioButton;
import com.android.settings.R;
+import com.android.settings.SettingsActivity;
import com.android.settings.Utils;
@@ -63,7 +63,7 @@ public class TtsEnginePreference extends Preference {
* The preference activity that owns this preference. Required
* for instantiating the engine specific settings screen.
*/
- private final PreferenceActivity mPreferenceActivity;
+ private final SettingsActivity mSettingsActivity;
/**
* The engine information for the engine this preference represents.
@@ -95,12 +95,12 @@ public class TtsEnginePreference extends Preference {
};
public TtsEnginePreference(Context context, EngineInfo info, RadioButtonGroupState state,
- PreferenceActivity prefActivity) {
+ SettingsActivity prefActivity) {
super(context);
setLayoutResource(R.layout.preference_tts_engine);
mSharedState = state;
- mPreferenceActivity = prefActivity;
+ mSettingsActivity = prefActivity;
mEngineInfo = info;
mPreventRadioButtonCallbacks = false;
@@ -156,10 +156,10 @@ public class TtsEnginePreference extends Preference {
}
// Note that we use this instead of the (easier to use)
- // PreferenceActivity.startPreferenceFragment because the
+ // SettingsActivity.startPreferenceFragment because the
// title will not be updated correctly in the fragment
// breadcrumb since it isn't inflated from the XML layout.
- mPreferenceActivity.startPreferencePanel(
+ mSettingsActivity.startPreferencePanel(
TtsEngineSettingsFragment.class.getName(),
args, 0, mEngineInfo.label, null, 0);
}
@@ -198,13 +198,12 @@ public class TtsEnginePreference extends Preference {
Log.i(TAG, "Displaying data alert for :" + mEngineInfo.name);
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
- builder.setTitle(android.R.string.dialog_alert_title);
- builder.setIconAttribute(android.R.attr.alertDialogIcon);
- builder.setMessage(getContext().getString(
- R.string.tts_engine_security_warning, mEngineInfo.label));
- builder.setCancelable(true);
- builder.setPositiveButton(android.R.string.ok, positiveOnClickListener);
- builder.setNegativeButton(android.R.string.cancel, negativeOnClickListener);
+ builder.setTitle(android.R.string.dialog_alert_title)
+ .setMessage(getContext().getString(
+ R.string.tts_engine_security_warning, mEngineInfo.label))
+ .setCancelable(true)
+ .setPositiveButton(android.R.string.ok, positiveOnClickListener)
+ .setNegativeButton(android.R.string.cancel, negativeOnClickListener);
AlertDialog dialog = builder.create();
dialog.show();
diff --git a/src/com/android/settings/tts/TtsEngineSettingsFragment.java b/src/com/android/settings/tts/TtsEngineSettingsFragment.java
index bb5ac7a..2449353 100644
--- a/src/com/android/settings/tts/TtsEngineSettingsFragment.java
+++ b/src/com/android/settings/tts/TtsEngineSettingsFragment.java
@@ -52,6 +52,10 @@ public class TtsEngineSettingsFragment extends SettingsPreferenceFragment implem
private static final String KEY_ENGINE_SETTINGS = "tts_engine_settings";
private static final String KEY_INSTALL_DATA = "tts_install_data";
+ private static final String STATE_KEY_LOCALE_ENTRIES = "locale_entries";
+ private static final String STATE_KEY_LOCALE_ENTRY_VALUES= "locale_entry_values";
+ private static final String STATE_KEY_LOCALE_VALUE = "locale_value";
+
private static final int VOICE_DATA_INTEGRITY_CHECK = 1977;
private TtsEngines mEnginesHelper;
@@ -64,7 +68,6 @@ public class TtsEngineSettingsFragment extends SettingsPreferenceFragment implem
private TextToSpeech mTts;
private int mSelectedLocaleIndex = -1;
- private int mSystemLocaleIndex = -1;
private final TextToSpeech.OnInitListener mTtsInitListener = new TextToSpeech.OnInitListener() {
@Override
@@ -120,10 +123,26 @@ public class TtsEngineSettingsFragment extends SettingsPreferenceFragment implem
mEngineSettingsPreference.setEnabled(false);
}
mInstallVoicesPreference.setEnabled(false);
- mLocalePreference.setEnabled(false);
- mLocalePreference.setEntries(new CharSequence[0]);
- mLocalePreference.setEntryValues(new CharSequence[0]);
+ if (savedInstanceState == null) {
+ mLocalePreference.setEnabled(false);
+ mLocalePreference.setEntries(new CharSequence[0]);
+ mLocalePreference.setEntryValues(new CharSequence[0]);
+ } else {
+ // Repopulate mLocalePreference with saved state. Will be updated later with
+ // up-to-date values when checkTtsData() calls back with results.
+ final CharSequence[] entries =
+ savedInstanceState.getCharSequenceArray(STATE_KEY_LOCALE_ENTRIES);
+ final CharSequence[] entryValues =
+ savedInstanceState.getCharSequenceArray(STATE_KEY_LOCALE_ENTRY_VALUES);
+ final CharSequence value =
+ savedInstanceState.getCharSequence(STATE_KEY_LOCALE_VALUE);
+
+ mLocalePreference.setEntries(entries);
+ mLocalePreference.setEntryValues(entryValues);
+ mLocalePreference.setValue(value != null ? value.toString() : null);
+ mLocalePreference.setEnabled(entries.length > 0);
+ }
mVoiceDataDetails = getArguments().getParcelable(TtsEnginePreference.FRAGMENT_ARGS_VOICES);
@@ -144,6 +163,19 @@ public class TtsEngineSettingsFragment extends SettingsPreferenceFragment implem
super.onDestroy();
}
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ // Save the mLocalePreference values, so we can repopulate it with entries.
+ outState.putCharSequenceArray(STATE_KEY_LOCALE_ENTRIES,
+ mLocalePreference.getEntries());
+ outState.putCharSequenceArray(STATE_KEY_LOCALE_ENTRY_VALUES,
+ mLocalePreference.getEntryValues());
+ outState.putCharSequence(STATE_KEY_LOCALE_VALUE,
+ mLocalePreference.getValue());
+ }
+
private final void checkTtsData() {
Intent intent = new Intent(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
intent.setPackage(getEngineName());
@@ -158,12 +190,22 @@ public class TtsEngineSettingsFragment extends SettingsPreferenceFragment implem
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == VOICE_DATA_INTEGRITY_CHECK) {
- mVoiceDataDetails = data;
- updateVoiceDetails();
+ if (resultCode != TextToSpeech.Engine.CHECK_VOICE_DATA_FAIL) {
+ updateVoiceDetails(data);
+ } else {
+ Log.e(TAG, "CheckVoiceData activity failed");
+ }
}
}
- private void updateVoiceDetails() {
+ private void updateVoiceDetails(Intent data) {
+ if (data == null){
+ Log.e(TAG, "Engine failed voice data integrity check (null return)" +
+ mTts.getCurrentEngine());
+ return;
+ }
+ mVoiceDataDetails = data;
+
if (DBG) Log.d(TAG, "Parsing voice data details, data: " + mVoiceDataDetails.toUri(0));
final ArrayList<String> available = mVoiceDataDetails.getStringArrayListExtra(
@@ -191,53 +233,44 @@ public class TtsEngineSettingsFragment extends SettingsPreferenceFragment implem
mLocalePreference.setEnabled(false);
return;
}
- String currentLocale = mEnginesHelper.getLocalePrefForEngine(
- getEngineName());
+ Locale currentLocale = null;
+ if (!mEnginesHelper.isLocaleSetToDefaultForEngine(getEngineName())) {
+ currentLocale = mEnginesHelper.getLocalePrefForEngine(getEngineName());
+ }
- ArrayList<Pair<String, String>> entryPairs =
- new ArrayList<Pair<String, String>>(availableLangs.size());
+ ArrayList<Pair<String, Locale>> entryPairs =
+ new ArrayList<Pair<String, Locale>>(availableLangs.size());
for (int i = 0; i < availableLangs.size(); i++) {
- String[] langCountryVariant = availableLangs.get(i).split("-");
- Locale loc = null;
- if (langCountryVariant.length == 1){
- loc = new Locale(langCountryVariant[0]);
- } else if (langCountryVariant.length == 2){
- loc = new Locale(langCountryVariant[0], langCountryVariant[1]);
- } else if (langCountryVariant.length == 3){
- loc = new Locale(langCountryVariant[0], langCountryVariant[1],
- langCountryVariant[2]);
- }
- if (loc != null){
- entryPairs.add(new Pair<String, String>(
- loc.getDisplayName(), availableLangs.get(i)));
+ Locale locale = mEnginesHelper.parseLocaleString(availableLangs.get(i));
+ if (locale != null){
+ entryPairs.add(new Pair<String, Locale>(
+ locale.getDisplayName(), locale));
}
}
// Sort it
- Collections.sort(entryPairs, new Comparator<Pair<String, String>>() {
+ Collections.sort(entryPairs, new Comparator<Pair<String, Locale>>() {
@Override
- public int compare(Pair<String, String> lhs, Pair<String, String> rhs) {
+ public int compare(Pair<String, Locale> lhs, Pair<String, Locale> rhs) {
return lhs.first.compareToIgnoreCase(rhs.first);
}
});
- String defaultLocaleStr = mEnginesHelper.getDefaultLocale();
-
// Get two arrays out of one of pairs
- mSelectedLocaleIndex = -1;
- mSystemLocaleIndex = -1;
- CharSequence[] entries = new CharSequence[availableLangs.size()];
- CharSequence[] entryValues = new CharSequence[availableLangs.size()];
- int i = 0;
- for (Pair<String, String> entry : entryPairs) {
- if (entry.second.equalsIgnoreCase(currentLocale)) {
+ mSelectedLocaleIndex = 0; // Will point to the R.string.tts_lang_use_system value
+ CharSequence[] entries = new CharSequence[availableLangs.size()+1];
+ CharSequence[] entryValues = new CharSequence[availableLangs.size()+1];
+
+ entries[0] = getActivity().getString(R.string.tts_lang_use_system);
+ entryValues[0] = "";
+
+ int i = 1;
+ for (Pair<String, Locale> entry : entryPairs) {
+ if (entry.second.equals(currentLocale)) {
mSelectedLocaleIndex = i;
}
- if (entry.second.equalsIgnoreCase(defaultLocaleStr)) {
- mSystemLocaleIndex = i;
- }
entries[i] = entry.first;
- entryValues[i++] = entry.second;
+ entryValues[i++] = entry.second.toString();
}
mLocalePreference.setEntries(entries);
@@ -264,7 +297,6 @@ public class TtsEngineSettingsFragment extends SettingsPreferenceFragment implem
private void installVoiceData() {
if (TextUtils.isEmpty(getEngineName())) return;
Intent intent = new Intent(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setPackage(getEngineName());
try {
Log.v(TAG, "Installing voice data: " + intent.toUri(0));
@@ -290,16 +322,19 @@ public class TtsEngineSettingsFragment extends SettingsPreferenceFragment implem
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (preference == mLocalePreference) {
- updateLanguageTo((String) newValue);
+ String localeString = (String) newValue;
+ updateLanguageTo((!TextUtils.isEmpty(localeString) ?
+ mEnginesHelper.parseLocaleString(localeString) : null));
return true;
}
return false;
}
- private void updateLanguageTo(String locale) {
+ private void updateLanguageTo(Locale locale) {
int selectedLocaleIndex = -1;
+ String localeString = (locale != null) ? locale.toString() : "";
for (int i=0; i < mLocalePreference.getEntryValues().length; i++) {
- if (locale.equalsIgnoreCase(mLocalePreference.getEntryValues()[i].toString())) {
+ if (localeString.equalsIgnoreCase(mLocalePreference.getEntryValues()[i].toString())) {
selectedLocaleIndex = i;
break;
}
@@ -312,18 +347,11 @@ public class TtsEngineSettingsFragment extends SettingsPreferenceFragment implem
mLocalePreference.setSummary(mLocalePreference.getEntries()[selectedLocaleIndex]);
mSelectedLocaleIndex = selectedLocaleIndex;
- if (mSelectedLocaleIndex == mSystemLocaleIndex) {
- // Use empty locale, it will default to the system language
- mEnginesHelper.updateLocalePrefForEngine(getEngineName(), "");
- } else {
- mEnginesHelper.updateLocalePrefForEngine(getEngineName(), locale);
- }
+ mEnginesHelper.updateLocalePrefForEngine(getEngineName(), locale);
if (getEngineName().equals(mTts.getCurrentEngine())) {
- String[] localeArray = TtsEngines.parseLocalePref(locale);
- if (localeArray != null) {
- mTts.setLanguage(new Locale(localeArray[0], localeArray[1], localeArray[2]));
- }
+ // Null locale means "use system default"
+ mTts.setLanguage((locale != null) ? locale : Locale.getDefault());
}
}
diff --git a/src/com/android/settings/users/AppRestrictionsFragment.java b/src/com/android/settings/users/AppRestrictionsFragment.java
index fcaf18f..d717489 100644
--- a/src/com/android/settings/users/AppRestrictionsFragment.java
+++ b/src/com/android/settings/users/AppRestrictionsFragment.java
@@ -17,7 +17,6 @@
package com.android.settings.users;
import android.app.Activity;
-import android.app.AppGlobals;
import android.appwidget.AppWidgetManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -58,13 +57,13 @@ import android.view.View.OnClickListener;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.ViewGroup;
-import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.Switch;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.drawable.CircleFramedDrawable;
import java.util.ArrayList;
import java.util.Collections;
@@ -161,34 +160,17 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen
private boolean panelOpen;
private boolean immutable;
private List<Preference> mChildren = new ArrayList<Preference>();
- private final ColorFilter grayscaleFilter;
AppRestrictionsPreference(Context context, OnClickListener listener) {
super(context);
setLayoutResource(R.layout.preference_app_restrictions);
this.listener = listener;
-
- ColorMatrix colorMatrix = new ColorMatrix();
- colorMatrix.setSaturation(0f);
- float[] matrix = colorMatrix.getArray();
- matrix[18] = 0.5f;
- grayscaleFilter = new ColorMatrixColorFilter(colorMatrix);
}
private void setSettingsEnabled(boolean enable) {
hasSettings = enable;
}
- @Override
- public void setChecked(boolean checked) {
- if (checked) {
- getIcon().setColorFilter(null);
- } else {
- getIcon().setColorFilter(grayscaleFilter);
- }
- super.setChecked(checked);
- }
-
void setRestrictions(ArrayList<RestrictionEntry> restrictions) {
this.restrictions = restrictions;
}
@@ -248,6 +230,8 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen
final Switch toggle = (Switch) widget.getChildAt(0);
toggle.setEnabled(!isImmutable());
toggle.setTag(this);
+ toggle.setClickable(true);
+ toggle.setFocusable(true);
toggle.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
@@ -346,7 +330,7 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen
return getPreferenceScreen();
}
- protected Drawable getCircularUserIcon() {
+ Drawable getCircularUserIcon() {
Bitmap userIcon = mUserManager.getUserIcon(mUser.getIdentifier());
if (userIcon == null) {
return null;
@@ -387,12 +371,12 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen
Log.d(TAG, "Installing " + packageName);
}
}
- if (info != null && (info.flags&ApplicationInfo.FLAG_BLOCKED) != 0
+ if (info != null && (info.flags&ApplicationInfo.FLAG_HIDDEN) != 0
&& (info.flags&ApplicationInfo.FLAG_INSTALLED) != 0) {
disableUiForPackage(packageName);
- mIPm.setApplicationBlockedSettingAsUser(packageName, false, userId);
+ mIPm.setApplicationHiddenSettingAsUser(packageName, false, userId);
if (DEBUG) {
- Log.d(TAG, "Unblocking " + packageName);
+ Log.d(TAG, "Unhiding " + packageName);
}
}
} catch (RemoteException re) {
@@ -410,9 +394,9 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen
}
} else {
disableUiForPackage(packageName);
- mIPm.setApplicationBlockedSettingAsUser(packageName, true, userId);
+ mIPm.setApplicationHiddenSettingAsUser(packageName, true, userId);
if (DEBUG) {
- Log.d(TAG, "Blocking " + packageName);
+ Log.d(TAG, "Hiding " + packageName);
}
}
}
@@ -654,9 +638,9 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen
private boolean isAppEnabledForUser(PackageInfo pi) {
if (pi == null) return false;
final int flags = pi.applicationInfo.flags;
- // Return true if it is installed and not blocked
+ // Return true if it is installed and not hidden
return ((flags&ApplicationInfo.FLAG_INSTALLED) != 0
- && (flags&ApplicationInfo.FLAG_BLOCKED) == 0);
+ && (flags&ApplicationInfo.FLAG_HIDDEN) == 0);
}
private void populateApps() {
@@ -684,7 +668,7 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen
app.masterEntry.activityName));
}
p.setKey(getKeyForPackage(packageName));
- p.setSettingsEnabled(hasSettings || isSettingsApp);
+ p.setSettingsEnabled((hasSettings || isSettingsApp) && app.masterEntry == null);
p.setPersistent(false);
p.setOnPreferenceChangeListener(this);
p.setOnPreferenceClickListener(this);
@@ -703,7 +687,8 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen
// Get and populate the defaults, since the user is not going to be
// able to toggle this app ON (it's ON by default and immutable).
// Only do this for restricted profiles, not single-user restrictions
- if (hasSettings) {
+ // Also don't do this for slave icons
+ if (hasSettings && app.masterEntry == null) {
requestRestrictionsForApp(packageName, p, false);
}
} else if (!mNewUser && isAppEnabledForUser(pi)) {
@@ -1008,6 +993,7 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen
+ entry.getKey());
mAppList.addPreference(p);
p.setOnPreferenceChangeListener(AppRestrictionsFragment.this);
+ p.setIcon(R.drawable.empty_icon);
preference.mChildren.add(p);
count++;
}
diff --git a/src/com/android/settings/users/EditUserInfoController.java b/src/com/android/settings/users/EditUserInfoController.java
new file mode 100644
index 0000000..0f844a7
--- /dev/null
+++ b/src/com/android/settings/users/EditUserInfoController.java
@@ -0,0 +1,197 @@
+/*
+ * 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.users;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.Fragment;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.EditText;
+import android.widget.ImageView;
+
+import com.android.settings.R;
+import com.android.settings.drawable.CircleFramedDrawable;
+
+/**
+ * This class encapsulates a Dialog for editing the user nickname and photo.
+ */
+public class EditUserInfoController {
+
+ private static final String KEY_AWAITING_RESULT = "awaiting_result";
+ private static final String KEY_SAVED_PHOTO = "pending_photo";
+
+ private Dialog mEditUserInfoDialog;
+ private Bitmap mSavedPhoto;
+ private EditUserPhotoController mEditUserPhotoController;
+ private UserHandle mUser;
+ private UserManager mUserManager;
+ private boolean mWaitingForActivityResult = false;
+
+ public interface OnContentChangedCallback {
+ public void onPhotoChanged(Drawable photo);
+ public void onLabelChanged(CharSequence label);
+ }
+
+ public void clear() {
+ mEditUserInfoDialog = null;
+ mSavedPhoto = null;
+ }
+
+ public Dialog getDialog() {
+ return mEditUserInfoDialog;
+ }
+
+ public void onRestoreInstanceState(Bundle icicle) {
+ mSavedPhoto = (Bitmap) icicle.getParcelable(KEY_SAVED_PHOTO);
+ mWaitingForActivityResult = icicle.getBoolean(KEY_AWAITING_RESULT, false);
+ }
+
+ public void onSaveInstanceState(Bundle outState) {
+ if (mEditUserInfoDialog != null && mEditUserInfoDialog.isShowing()
+ && mEditUserPhotoController != null) {
+ outState.putParcelable(KEY_SAVED_PHOTO,
+ mEditUserPhotoController.getNewUserPhotoBitmap());
+ }
+ if (mWaitingForActivityResult) {
+ outState.putBoolean(KEY_AWAITING_RESULT,
+ mWaitingForActivityResult);
+ }
+ }
+
+ public void startingActivityForResult() {
+ mWaitingForActivityResult = true;
+ }
+
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ mWaitingForActivityResult = false;
+
+ if (mEditUserInfoDialog != null && mEditUserInfoDialog.isShowing()
+ && mEditUserPhotoController.onActivityResult(requestCode, resultCode, data)) {
+ return;
+ }
+ }
+
+ Drawable getCircularUserIcon(Activity activity) {
+ Bitmap userIcon = mUserManager.getUserIcon(mUser.getIdentifier());
+ if (userIcon == null) {
+ return null;
+ }
+ CircleFramedDrawable circularIcon =
+ CircleFramedDrawable.getInstance(activity, userIcon);
+ return circularIcon;
+ }
+
+ public Dialog createDialog(final Fragment fragment, final Drawable currentUserIcon,
+ final CharSequence currentUserName,
+ int titleResId, final OnContentChangedCallback callback, UserHandle user) {
+ Activity activity = fragment.getActivity();
+ mUser = user;
+ if (mUserManager == null) {
+ mUserManager = UserManager.get(activity);
+ }
+ LayoutInflater inflater = activity.getLayoutInflater();
+ View content = inflater.inflate(R.layout.edit_user_info_dialog_content, null);
+
+ UserInfo info = mUserManager.getUserInfo(mUser.getIdentifier());
+
+ final EditText userNameView = (EditText) content.findViewById(R.id.user_name);
+ userNameView.setText(info.name);
+
+ final ImageView userPhotoView = (ImageView) content.findViewById(R.id.user_photo);
+ Drawable drawable = null;
+ if (mSavedPhoto != null) {
+ drawable = CircleFramedDrawable.getInstance(activity, mSavedPhoto);
+ } else {
+ drawable = currentUserIcon;
+ if (drawable == null) {
+ drawable = getCircularUserIcon(activity);
+ }
+ }
+ userPhotoView.setImageDrawable(drawable);
+ mEditUserPhotoController = new EditUserPhotoController(fragment, userPhotoView,
+ mSavedPhoto, drawable, mWaitingForActivityResult);
+ mEditUserInfoDialog = new AlertDialog.Builder(activity)
+ .setTitle(R.string.profile_info_settings_title)
+ .setView(content)
+ .setCancelable(true)
+ .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ // Update the name if changed.
+ CharSequence userName = userNameView.getText();
+ if (!TextUtils.isEmpty(userName)) {
+ if (currentUserName == null
+ || !userName.toString().equals(currentUserName.toString())) {
+ if (callback != null) {
+ callback.onLabelChanged(userName.toString());
+ }
+ mUserManager.setUserName(mUser.getIdentifier(),
+ userName.toString());
+ }
+ }
+ // Update the photo if changed.
+ Drawable drawable = mEditUserPhotoController.getNewUserPhotoDrawable();
+ Bitmap bitmap = mEditUserPhotoController.getNewUserPhotoBitmap();
+ if (drawable != null && bitmap != null
+ && !drawable.equals(currentUserIcon)) {
+ if (callback != null) {
+ callback.onPhotoChanged(drawable);
+ }
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ mUserManager.setUserIcon(mUser.getIdentifier(),
+ mEditUserPhotoController.getNewUserPhotoBitmap());
+ return null;
+ }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+ }
+ fragment.getActivity().removeDialog(
+ RestrictedProfileSettings.DIALOG_ID_EDIT_USER_INFO);
+ }
+ clear();
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ clear();
+ }
+ })
+ .create();
+
+ // Make sure the IME is up.
+ mEditUserInfoDialog.getWindow().setSoftInputMode(
+ WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
+
+ return mEditUserInfoDialog;
+ }
+}
diff --git a/src/com/android/settings/users/EditUserPhotoController.java b/src/com/android/settings/users/EditUserPhotoController.java
new file mode 100644
index 0000000..82e550e
--- /dev/null
+++ b/src/com/android/settings/users/EditUserPhotoController.java
@@ -0,0 +1,346 @@
+/*
+ * 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.users;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.content.ClipData;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Bitmap.Config;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.provider.MediaStore;
+import android.provider.ContactsContract.DisplayPhoto;
+import android.support.v4.content.FileProvider;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.ListAdapter;
+import android.widget.ListPopupWindow;
+
+import com.android.settings.R;
+import com.android.settings.drawable.CircleFramedDrawable;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+public class EditUserPhotoController {
+ private static final String TAG = "EditUserPhotoController";
+
+ private static final int POPUP_LIST_ITEM_ID_CHOOSE_PHOTO = 1;
+ private static final int POPUP_LIST_ITEM_ID_TAKE_PHOTO = 2;
+
+ // It seems that this class generates custom request codes and they may
+ // collide with ours, these values are very unlikely to have a conflict.
+ private static final int REQUEST_CODE_CHOOSE_PHOTO = 1001;
+ private static final int REQUEST_CODE_TAKE_PHOTO = 1002;
+ private static final int REQUEST_CODE_CROP_PHOTO = 1003;
+
+ private static final String CROP_PICTURE_FILE_NAME = "CropEditUserPhoto.jpg";
+ private static final String TAKE_PICTURE_FILE_NAME = "TakeEditUserPhoto2.jpg";
+
+ private final int mPhotoSize;
+
+ private final Context mContext;
+ private final Fragment mFragment;
+ private final ImageView mImageView;
+
+ private final Uri mCropPictureUri;
+ private final Uri mTakePictureUri;
+
+ private Bitmap mNewUserPhotoBitmap;
+ private Drawable mNewUserPhotoDrawable;
+
+ public EditUserPhotoController(Fragment fragment, ImageView view,
+ Bitmap bitmap, Drawable drawable, boolean waiting) {
+ mContext = view.getContext();
+ mFragment = fragment;
+ mImageView = view;
+ mCropPictureUri = createTempImageUri(mContext, CROP_PICTURE_FILE_NAME, !waiting);
+ mTakePictureUri = createTempImageUri(mContext, TAKE_PICTURE_FILE_NAME, !waiting);
+ mPhotoSize = getPhotoSize(mContext);
+ mImageView.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ showUpdatePhotoPopup();
+ }
+ });
+ mNewUserPhotoBitmap = bitmap;
+ mNewUserPhotoDrawable = drawable;
+ }
+
+ public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (resultCode != Activity.RESULT_OK) {
+ return false;
+ }
+ final Uri pictureUri = data != null && data.getData() != null
+ ? data.getData() : mTakePictureUri;
+ switch (requestCode) {
+ case REQUEST_CODE_CROP_PHOTO:
+ onPhotoCropped(pictureUri, true);
+ return true;
+ case REQUEST_CODE_TAKE_PHOTO:
+ case REQUEST_CODE_CHOOSE_PHOTO:
+ cropPhoto(pictureUri);
+ return true;
+ }
+ return false;
+ }
+
+ public Bitmap getNewUserPhotoBitmap() {
+ return mNewUserPhotoBitmap;
+ }
+
+ public Drawable getNewUserPhotoDrawable() {
+ return mNewUserPhotoDrawable;
+ }
+
+ private void showUpdatePhotoPopup() {
+ final boolean canTakePhoto = canTakePhoto();
+ final boolean canChoosePhoto = canChoosePhoto();
+
+ if (!canTakePhoto && !canChoosePhoto) {
+ return;
+ }
+
+ Context context = mImageView.getContext();
+ final List<EditUserPhotoController.AdapterItem> items = new ArrayList<EditUserPhotoController.AdapterItem>();
+
+ if (canTakePhoto()) {
+ String title = mImageView.getContext().getString( R.string.user_image_take_photo);
+ EditUserPhotoController.AdapterItem item = new AdapterItem(title, POPUP_LIST_ITEM_ID_TAKE_PHOTO);
+ items.add(item);
+ }
+
+ if (canChoosePhoto) {
+ String title = context.getString(R.string.user_image_choose_photo);
+ EditUserPhotoController.AdapterItem item = new AdapterItem(title, POPUP_LIST_ITEM_ID_CHOOSE_PHOTO);
+ items.add(item);
+ }
+
+ final ListPopupWindow listPopupWindow = new ListPopupWindow(context);
+
+ listPopupWindow.setAnchorView(mImageView);
+ listPopupWindow.setModal(true);
+ listPopupWindow.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
+
+ ListAdapter adapter = new ArrayAdapter<EditUserPhotoController.AdapterItem>(context,
+ R.layout.edit_user_photo_popup_item, items);
+ listPopupWindow.setAdapter(adapter);
+
+ final int width = Math.max(mImageView.getWidth(), context.getResources()
+ .getDimensionPixelSize(R.dimen.update_user_photo_popup_min_width));
+ listPopupWindow.setWidth(width);
+
+ listPopupWindow.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ EditUserPhotoController.AdapterItem item = items.get(position);
+ switch (item.id) {
+ case POPUP_LIST_ITEM_ID_CHOOSE_PHOTO: {
+ choosePhoto();
+ listPopupWindow.dismiss();
+ } break;
+ case POPUP_LIST_ITEM_ID_TAKE_PHOTO: {
+ takePhoto();
+ listPopupWindow.dismiss();
+ } break;
+ }
+ }
+ });
+
+ listPopupWindow.show();
+ }
+
+ private boolean canTakePhoto() {
+ return mImageView.getContext().getPackageManager().queryIntentActivities(
+ new Intent(MediaStore.ACTION_IMAGE_CAPTURE),
+ PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
+ }
+
+ private boolean canChoosePhoto() {
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.setType("image/*");
+ return mImageView.getContext().getPackageManager().queryIntentActivities(
+ intent, 0).size() > 0;
+ }
+
+ private void takePhoto() {
+ Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
+ appendOutputExtra(intent, mTakePictureUri);
+ mFragment.startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO);
+ }
+
+ private void choosePhoto() {
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
+ intent.setType("image/*");
+ appendOutputExtra(intent, mTakePictureUri);
+ mFragment.startActivityForResult(intent, REQUEST_CODE_CHOOSE_PHOTO);
+ }
+
+ private void cropPhoto(Uri pictureUri) {
+ // TODO: Use a public intent, when there is one.
+ Intent intent = new Intent("com.android.camera.action.CROP");
+ intent.setDataAndType(pictureUri, "image/*");
+ appendOutputExtra(intent, mCropPictureUri);
+ appendCropExtras(intent);
+ if (intent.resolveActivity(mContext.getPackageManager()) != null) {
+ mFragment.startActivityForResult(intent, REQUEST_CODE_CROP_PHOTO);
+ } else {
+ onPhotoCropped(pictureUri, false);
+ }
+ }
+
+ private void appendOutputExtra(Intent intent, Uri pictureUri) {
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, pictureUri);
+ intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ | Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ intent.setClipData(ClipData.newRawUri(MediaStore.EXTRA_OUTPUT, pictureUri));
+ }
+
+ private void appendCropExtras(Intent intent) {
+ intent.putExtra("crop", "true");
+ intent.putExtra("scale", true);
+ intent.putExtra("scaleUpIfNeeded", true);
+ intent.putExtra("aspectX", 1);
+ intent.putExtra("aspectY", 1);
+ intent.putExtra("outputX", mPhotoSize);
+ intent.putExtra("outputY", mPhotoSize);
+ }
+
+ private void onPhotoCropped(final Uri data, final boolean cropped) {
+ new AsyncTask<Void, Void, Bitmap>() {
+ @Override
+ protected Bitmap doInBackground(Void... params) {
+ if (cropped) {
+ InputStream imageStream = null;
+ try {
+ imageStream = mContext.getContentResolver()
+ .openInputStream(data);
+ return BitmapFactory.decodeStream(imageStream);
+ } catch (FileNotFoundException fe) {
+ Log.w(TAG, "Cannot find image file", fe);
+ return null;
+ } finally {
+ if (imageStream != null) {
+ try {
+ imageStream.close();
+ } catch (IOException ioe) {
+ Log.w(TAG, "Cannot close image stream", ioe);
+ }
+ }
+ }
+ } else {
+ // Scale and crop to a square aspect ratio
+ Bitmap croppedImage = Bitmap.createBitmap(mPhotoSize, mPhotoSize,
+ Config.ARGB_8888);
+ Canvas canvas = new Canvas(croppedImage);
+ Bitmap fullImage = null;
+ try {
+ InputStream imageStream = mContext.getContentResolver()
+ .openInputStream(data);
+ fullImage = BitmapFactory.decodeStream(imageStream);
+ } catch (FileNotFoundException fe) {
+ return null;
+ }
+ if (fullImage != null) {
+ final int squareSize = Math.min(fullImage.getWidth(),
+ fullImage.getHeight());
+ final int left = (fullImage.getWidth() - squareSize) / 2;
+ final int top = (fullImage.getHeight() - squareSize) / 2;
+ Rect rectSource = new Rect(left, top,
+ left + squareSize, top + squareSize);
+ Rect rectDest = new Rect(0, 0, mPhotoSize, mPhotoSize);
+ Paint paint = new Paint();
+ canvas.drawBitmap(fullImage, rectSource, rectDest, paint);
+ return croppedImage;
+ } else {
+ // Bah! Got nothin.
+ return null;
+ }
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ if (bitmap != null) {
+ mNewUserPhotoBitmap = bitmap;
+ mNewUserPhotoDrawable = CircleFramedDrawable
+ .getInstance(mImageView.getContext(), mNewUserPhotoBitmap);
+ mImageView.setImageDrawable(mNewUserPhotoDrawable);
+ }
+ new File(mContext.getCacheDir(), TAKE_PICTURE_FILE_NAME).delete();
+ new File(mContext.getCacheDir(), CROP_PICTURE_FILE_NAME).delete();
+ }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+ }
+
+ private static int getPhotoSize(Context context) {
+ Cursor cursor = context.getContentResolver().query(
+ DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
+ new String[]{DisplayPhoto.DISPLAY_MAX_DIM}, null, null, null);
+ try {
+ cursor.moveToFirst();
+ return cursor.getInt(0);
+ } finally {
+ cursor.close();
+ }
+ }
+
+ private Uri createTempImageUri(Context context, String fileName, boolean purge) {
+ final File folder = context.getCacheDir();
+ folder.mkdirs();
+ final File fullPath = new File(folder, fileName);
+ if (purge) {
+ fullPath.delete();
+ }
+ final Uri fileUri =
+ FileProvider.getUriForFile(context, RestrictedProfileSettings.FILE_PROVIDER_AUTHORITY, fullPath);
+ return fileUri;
+ }
+
+ private static final class AdapterItem {
+ final String title;
+ final int id;
+
+ public AdapterItem(String title, int id) {
+ this.title = title;
+ this.id = id;
+ }
+
+ @Override
+ public String toString() {
+ 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 c293536..8db911c 100644
--- a/src/com/android/settings/users/RestrictedProfileSettings.java
+++ b/src/com/android/settings/users/RestrictedProfileSettings.java
@@ -16,77 +16,44 @@
package com.android.settings.users;
-import android.app.Activity;
-import android.app.AlertDialog;
import android.app.Dialog;
-import android.app.Fragment;
-import android.content.ClipData;
-import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
-import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.AsyncTask;
import android.os.Bundle;
import android.os.UserHandle;
-import android.provider.MediaStore;
-import android.provider.ContactsContract.DisplayPhoto;
-import android.support.v4.content.FileProvider;
-import android.text.TextUtils;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.View.OnClickListener;
-import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
-import android.widget.EditText;
import android.widget.ImageView;
-import android.widget.ListAdapter;
-import android.widget.ListPopupWindow;
import android.widget.TextView;
import com.android.settings.R;
+import com.android.settings.Utils;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.InputStream;
-import java.util.ArrayList;
import java.util.List;
-public class RestrictedProfileSettings extends AppRestrictionsFragment {
+public class RestrictedProfileSettings extends AppRestrictionsFragment
+ implements EditUserInfoController.OnContentChangedCallback {
- private static final String KEY_SAVED_PHOTO = "pending_photo";
- private static final String KEY_AWAITING_RESULT = "awaiting_result";
- private static final int DIALOG_ID_EDIT_USER_INFO = 1;
public static final String FILE_PROVIDER_AUTHORITY = "com.android.settings.files";
+ static final int DIALOG_ID_EDIT_USER_INFO = 1;
+ private static final int DIALOG_CONFIRM_REMOVE = 2;
private View mHeaderView;
private ImageView mUserIconView;
private TextView mUserNameView;
+ private ImageView mDeleteButton;
- private Dialog mEditUserInfoDialog;
- private EditUserPhotoController mEditUserPhotoController;
- private Bitmap mSavedPhoto;
- private boolean mWaitingForActivityResult;
+ private EditUserInfoController mEditUserInfoController =
+ new EditUserInfoController();
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
if (icicle != null) {
- mSavedPhoto = (Bitmap) icicle.getParcelable(KEY_SAVED_PHOTO);
- mWaitingForActivityResult = icicle.getBoolean(KEY_AWAITING_RESULT, false);
+ mEditUserInfoController.onRestoreInstanceState(icicle);
}
init(icicle);
@@ -97,10 +64,12 @@ public class RestrictedProfileSettings extends AppRestrictionsFragment {
if (mHeaderView == null) {
mHeaderView = LayoutInflater.from(getActivity()).inflate(
R.layout.user_info_header, null);
- ((ViewGroup) getListView().getParent()).addView(mHeaderView, 0);
+ setPinnedHeaderView(mHeaderView);
mHeaderView.setOnClickListener(this);
mUserIconView = (ImageView) mHeaderView.findViewById(android.R.id.icon);
mUserNameView = (TextView) mHeaderView.findViewById(android.R.id.title);
+ mDeleteButton = (ImageView) mHeaderView.findViewById(R.id.delete);
+ mDeleteButton.setOnClickListener(this);
getListView().setFastScrollEnabled(true);
}
// This is going to bind the preferences.
@@ -110,14 +79,7 @@ public class RestrictedProfileSettings extends AppRestrictionsFragment {
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
- if (mEditUserInfoDialog != null && mEditUserInfoDialog.isShowing()
- && mEditUserPhotoController != null) {
- outState.putParcelable(KEY_SAVED_PHOTO,
- mEditUserPhotoController.getNewUserPhotoBitmap());
- }
- if (mWaitingForActivityResult) {
- outState.putBoolean(KEY_AWAITING_RESULT, mWaitingForActivityResult);
- }
+ mEditUserInfoController.onSaveInstanceState(outState);
}
@Override
@@ -147,25 +109,23 @@ public class RestrictedProfileSettings extends AppRestrictionsFragment {
@Override
public void startActivityForResult(Intent intent, int requestCode) {
- mWaitingForActivityResult = true;
+ mEditUserInfoController.startingActivityForResult();
super.startActivityForResult(intent, requestCode);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
- mWaitingForActivityResult = false;
- if (mEditUserInfoDialog != null && mEditUserInfoDialog.isShowing()
- && mEditUserPhotoController.onActivityResult(requestCode, resultCode, data)) {
- return;
- }
+ mEditUserInfoController.onActivityResult(requestCode, resultCode, data);
}
@Override
public void onClick(View view) {
if (view == mHeaderView) {
showDialog(DIALOG_ID_EDIT_USER_INFO);
+ } else if (view == mDeleteButton) {
+ showDialog(DIALOG_CONFIRM_REMOVE);
} else {
super.onClick(view); // in AppRestrictionsFragment
}
@@ -174,373 +134,40 @@ public class RestrictedProfileSettings extends AppRestrictionsFragment {
@Override
public Dialog onCreateDialog(int dialogId) {
if (dialogId == DIALOG_ID_EDIT_USER_INFO) {
- if (mEditUserInfoDialog != null) {
- return mEditUserInfoDialog;
- }
-
- LayoutInflater inflater = getActivity().getLayoutInflater();
- View content = inflater.inflate(R.layout.edit_user_info_dialog_content, null);
-
- UserInfo info = mUserManager.getUserInfo(mUser.getIdentifier());
-
- final EditText userNameView = (EditText) content.findViewById(R.id.user_name);
- userNameView.setText(info.name);
-
- final ImageView userPhotoView = (ImageView) content.findViewById(R.id.user_photo);
- Drawable drawable = null;
- if (mSavedPhoto != null) {
- drawable = CircleFramedDrawable.getInstance(getActivity(), mSavedPhoto);
- } else {
- drawable = mUserIconView.getDrawable();
- if (drawable == null) {
- drawable = getCircularUserIcon();
- }
- }
- userPhotoView.setImageDrawable(drawable);
- mEditUserPhotoController = new EditUserPhotoController(this, userPhotoView,
- mSavedPhoto, drawable, mWaitingForActivityResult);
-
- mEditUserInfoDialog = new AlertDialog.Builder(getActivity())
- .setTitle(R.string.profile_info_settings_title)
- .setIconAttribute(R.drawable.ic_settings_multiuser)
- .setView(content)
- .setCancelable(true)
- .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- if (which == DialogInterface.BUTTON_POSITIVE) {
- // Update the name if changed.
- CharSequence userName = userNameView.getText();
- if (!TextUtils.isEmpty(userName)) {
- CharSequence oldUserName = mUserNameView.getText();
- if (oldUserName == null
- || !userName.toString().equals(oldUserName.toString())) {
- ((TextView) mHeaderView.findViewById(android.R.id.title))
- .setText(userName.toString());
- mUserManager.setUserName(mUser.getIdentifier(),
- userName.toString());
+ return mEditUserInfoController.createDialog(this, mUserIconView.getDrawable(),
+ mUserNameView.getText(), R.string.profile_info_settings_title,
+ this, mUser);
+ } else if (dialogId == DIALOG_CONFIRM_REMOVE) {
+ Dialog dlg =
+ Utils.createRemoveConfirmationDialog(getActivity(), mUser.getIdentifier(),
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ removeUser();
}
}
- // Update the photo if changed.
- Drawable drawable = mEditUserPhotoController.getNewUserPhotoDrawable();
- Bitmap bitmap = mEditUserPhotoController.getNewUserPhotoBitmap();
- if (drawable != null && bitmap != null
- && !drawable.equals(mUserIconView.getDrawable())) {
- mUserIconView.setImageDrawable(drawable);
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- mUserManager.setUserIcon(mUser.getIdentifier(),
- mEditUserPhotoController.getNewUserPhotoBitmap());
- return null;
- }
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
- }
- removeDialog(DIALOG_ID_EDIT_USER_INFO);
- }
- clearEditUserInfoDialog();
- }
- })
- .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- clearEditUserInfoDialog();
- }
- })
- .create();
-
- // Make sure the IME is up.
- mEditUserInfoDialog.getWindow().setSoftInputMode(
- WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
-
- return mEditUserInfoDialog;
+ );
+ return dlg;
}
return null;
}
- private void clearEditUserInfoDialog() {
- mEditUserInfoDialog = null;
- mSavedPhoto = null;
- }
-
- private static class EditUserPhotoController {
- private static final int POPUP_LIST_ITEM_ID_CHOOSE_PHOTO = 1;
- private static final int POPUP_LIST_ITEM_ID_TAKE_PHOTO = 2;
-
- // It seems that this class generates custom request codes and they may
- // collide with ours, these values are very unlikely to have a conflict.
- private static final int REQUEST_CODE_CHOOSE_PHOTO = 1;
- private static final int REQUEST_CODE_TAKE_PHOTO = 2;
- private static final int REQUEST_CODE_CROP_PHOTO = 3;
-
- private static final String CROP_PICTURE_FILE_NAME = "CropEditUserPhoto.jpg";
- private static final String TAKE_PICTURE_FILE_NAME = "TakeEditUserPhoto2.jpg";
-
- private final int mPhotoSize;
-
- private final Context mContext;
- private final Fragment mFragment;
- private final ImageView mImageView;
-
- private final Uri mCropPictureUri;
- private final Uri mTakePictureUri;
-
- private Bitmap mNewUserPhotoBitmap;
- private Drawable mNewUserPhotoDrawable;
-
- public EditUserPhotoController(Fragment fragment, ImageView view,
- Bitmap bitmap, Drawable drawable, boolean waiting) {
- mContext = view.getContext();
- mFragment = fragment;
- mImageView = view;
- mCropPictureUri = createTempImageUri(mContext, CROP_PICTURE_FILE_NAME, !waiting);
- mTakePictureUri = createTempImageUri(mContext, TAKE_PICTURE_FILE_NAME, !waiting);
- mPhotoSize = getPhotoSize(mContext);
- mImageView.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- showUpdatePhotoPopup();
- }
- });
- mNewUserPhotoBitmap = bitmap;
- mNewUserPhotoDrawable = drawable;
- }
-
- public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
- if (resultCode != Activity.RESULT_OK) {
- return false;
- }
- final Uri pictureUri = data != null && data.getData() != null
- ? data.getData() : mTakePictureUri;
- switch (requestCode) {
- case REQUEST_CODE_CROP_PHOTO:
- onPhotoCropped(pictureUri, true);
- return true;
- case REQUEST_CODE_TAKE_PHOTO:
- case REQUEST_CODE_CHOOSE_PHOTO:
- cropPhoto(pictureUri);
- return true;
- }
- return false;
- }
-
- public Bitmap getNewUserPhotoBitmap() {
- return mNewUserPhotoBitmap;
- }
-
- public Drawable getNewUserPhotoDrawable() {
- return mNewUserPhotoDrawable;
- }
-
- private void showUpdatePhotoPopup() {
- final boolean canTakePhoto = canTakePhoto();
- final boolean canChoosePhoto = canChoosePhoto();
-
- if (!canTakePhoto && !canChoosePhoto) {
- return;
- }
-
- Context context = mImageView.getContext();
- final List<AdapterItem> items = new ArrayList<AdapterItem>();
-
- if (canTakePhoto()) {
- String title = mImageView.getContext().getString( R.string.user_image_take_photo);
- AdapterItem item = new AdapterItem(title, POPUP_LIST_ITEM_ID_TAKE_PHOTO);
- items.add(item);
- }
-
- if (canChoosePhoto) {
- String title = context.getString(R.string.user_image_choose_photo);
- AdapterItem item = new AdapterItem(title, POPUP_LIST_ITEM_ID_CHOOSE_PHOTO);
- items.add(item);
- }
-
- final ListPopupWindow listPopupWindow = new ListPopupWindow(context);
-
- listPopupWindow.setAnchorView(mImageView);
- listPopupWindow.setModal(true);
- listPopupWindow.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
-
- ListAdapter adapter = new ArrayAdapter<AdapterItem>(context,
- R.layout.edit_user_photo_popup_item, items);
- listPopupWindow.setAdapter(adapter);
-
- final int width = Math.max(mImageView.getWidth(), context.getResources()
- .getDimensionPixelSize(R.dimen.update_user_photo_popup_min_width));
- listPopupWindow.setWidth(width);
-
- listPopupWindow.setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- AdapterItem item = items.get(position);
- switch (item.id) {
- case POPUP_LIST_ITEM_ID_CHOOSE_PHOTO: {
- choosePhoto();
- listPopupWindow.dismiss();
- } break;
- case POPUP_LIST_ITEM_ID_TAKE_PHOTO: {
- takePhoto();
- listPopupWindow.dismiss();
- } break;
- }
- }
- });
-
- listPopupWindow.show();
- }
-
- private boolean canTakePhoto() {
- return mImageView.getContext().getPackageManager().queryIntentActivities(
- new Intent(MediaStore.ACTION_IMAGE_CAPTURE),
- PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
- }
-
- private boolean canChoosePhoto() {
- Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
- intent.setType("image/*");
- return mImageView.getContext().getPackageManager().queryIntentActivities(
- intent, 0).size() > 0;
- }
-
- private void takePhoto() {
- Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
- appendOutputExtra(intent, mTakePictureUri);
- mFragment.startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO);
- }
-
- private void choosePhoto() {
- Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
- intent.setType("image/*");
- appendOutputExtra(intent, mTakePictureUri);
- mFragment.startActivityForResult(intent, REQUEST_CODE_CHOOSE_PHOTO);
- }
-
- private void cropPhoto(Uri pictureUri) {
- // TODO: Use a public intent, when there is one.
- Intent intent = new Intent("com.android.camera.action.CROP");
- intent.setDataAndType(pictureUri, "image/*");
- appendOutputExtra(intent, mCropPictureUri);
- appendCropExtras(intent);
- if (intent.resolveActivity(mContext.getPackageManager()) != null) {
- mFragment.startActivityForResult(intent, REQUEST_CODE_CROP_PHOTO);
- } else {
- onPhotoCropped(pictureUri, false);
- }
- }
-
- private void appendOutputExtra(Intent intent, Uri pictureUri) {
- intent.putExtra(MediaStore.EXTRA_OUTPUT, pictureUri);
- intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION
- | Intent.FLAG_GRANT_READ_URI_PERMISSION);
- intent.setClipData(ClipData.newRawUri(MediaStore.EXTRA_OUTPUT, pictureUri));
- }
-
- private void appendCropExtras(Intent intent) {
- intent.putExtra("crop", "true");
- intent.putExtra("scale", true);
- intent.putExtra("scaleUpIfNeeded", true);
- intent.putExtra("aspectX", 1);
- intent.putExtra("aspectY", 1);
- intent.putExtra("outputX", mPhotoSize);
- intent.putExtra("outputY", mPhotoSize);
- }
-
- private void onPhotoCropped(final Uri data, final boolean cropped) {
- new AsyncTask<Void, Void, Bitmap>() {
- @Override
- protected Bitmap doInBackground(Void... params) {
- if (cropped) {
- try {
- InputStream imageStream = mContext.getContentResolver()
- .openInputStream(data);
- return BitmapFactory.decodeStream(imageStream);
- } catch (FileNotFoundException fe) {
- return null;
- }
- } else {
- // Scale and crop to a square aspect ratio
- Bitmap croppedImage = Bitmap.createBitmap(mPhotoSize, mPhotoSize,
- Config.ARGB_8888);
- Canvas canvas = new Canvas(croppedImage);
- Bitmap fullImage = null;
- try {
- InputStream imageStream = mContext.getContentResolver()
- .openInputStream(data);
- fullImage = BitmapFactory.decodeStream(imageStream);
- } catch (FileNotFoundException fe) {
- return null;
- }
- if (fullImage != null) {
- final int squareSize = Math.min(fullImage.getWidth(),
- fullImage.getHeight());
- final int left = (fullImage.getWidth() - squareSize) / 2;
- final int top = (fullImage.getHeight() - squareSize) / 2;
- Rect rectSource = new Rect(left, top,
- left + squareSize, top + squareSize);
- Rect rectDest = new Rect(0, 0, mPhotoSize, mPhotoSize);
- Paint paint = new Paint();
- canvas.drawBitmap(fullImage, rectSource, rectDest, paint);
- return croppedImage;
- } else {
- // Bah! Got nothin.
- return null;
- }
- }
- }
-
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- if (bitmap != null) {
- mNewUserPhotoBitmap = bitmap;
- mNewUserPhotoDrawable = CircleFramedDrawable
- .getInstance(mImageView.getContext(), mNewUserPhotoBitmap);
- mImageView.setImageDrawable(mNewUserPhotoDrawable);
- }
- new File(mContext.getCacheDir(), TAKE_PICTURE_FILE_NAME).delete();
- new File(mContext.getCacheDir(), CROP_PICTURE_FILE_NAME).delete();
- }
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
- }
-
- private static int getPhotoSize(Context context) {
- Cursor cursor = context.getContentResolver().query(
- DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
- new String[]{DisplayPhoto.DISPLAY_MAX_DIM}, null, null, null);
- try {
- cursor.moveToFirst();
- return cursor.getInt(0);
- } finally {
- cursor.close();
- }
- }
-
- private Uri createTempImageUri(Context context, String fileName, boolean purge) {
- final File folder = context.getCacheDir();
- folder.mkdirs();
- final File fullPath = new File(folder, fileName);
- if (purge) {
- fullPath.delete();
- }
- final Uri fileUri =
- FileProvider.getUriForFile(context, FILE_PROVIDER_AUTHORITY, fullPath);
- return fileUri;
- }
-
- private static final class AdapterItem {
- final String title;
- final int id;
-
- public AdapterItem(String title, int id) {
- this.title = title;
- this.id = id;
+ private void removeUser() {
+ getView().post(new Runnable() {
+ public void run() {
+ mUserManager.removeUser(mUser.getIdentifier());
+ finishFragment();
}
+ });
+ }
- @Override
- public String toString() {
- return title;
- }
- }
+ @Override
+ public void onPhotoChanged(Drawable photo) {
+ mUserIconView.setImageDrawable(photo);
}
+ @Override
+ public void onLabelChanged(CharSequence label) {
+ mUserNameView.setText(label);
+ }
}
diff --git a/src/com/android/settings/users/RestrictionUtils.java b/src/com/android/settings/users/RestrictionUtils.java
index 3ee6d51..e8d46e9 100644
--- a/src/com/android/settings/users/RestrictionUtils.java
+++ b/src/com/android/settings/users/RestrictionUtils.java
@@ -84,8 +84,8 @@ public class RestrictionUtils {
userRestrictions.putBoolean(entry.getKey(), !entry.getSelectedState());
if (entry.getKey().equals(UserManager.DISALLOW_SHARE_LOCATION)
&& !entry.getSelectedState()) {
- Secure.putStringForUser(context.getContentResolver(),
- Secure.LOCATION_PROVIDERS_ALLOWED, "", user.getIdentifier());
+ Secure.putIntForUser(context.getContentResolver(),
+ Secure.LOCATION_MODE, Secure.LOCATION_MODE_OFF, user.getIdentifier());
}
}
um.setUserRestrictions(userRestrictions, user);
diff --git a/src/com/android/settings/users/UserDetailsSettings.java b/src/com/android/settings/users/UserDetailsSettings.java
new file mode 100644
index 0000000..366b628
--- /dev/null
+++ b/src/com/android/settings/users/UserDetailsSettings.java
@@ -0,0 +1,173 @@
+/*
+ * 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.users;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.pm.UserInfo;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.preference.Preference;
+import android.preference.SwitchPreference;
+
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.Utils;
+
+import java.util.List;
+
+/**
+ * Settings screen for configuring a specific user. It can contain user restrictions
+ * and deletion controls. It is shown when you tap on the settings icon in the
+ * user management (UserSettings) screen.
+ *
+ * Arguments to this fragment must include the userId of the user (in EXTRA_USER_ID) for whom
+ * to display controls, or should contain the EXTRA_USER_GUEST = true.
+ */
+public class UserDetailsSettings extends SettingsPreferenceFragment
+ implements Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener {
+
+ private static final String TAG = UserDetailsSettings.class.getSimpleName();
+
+ private static final String KEY_ENABLE_TELEPHONY = "enable_calling";
+ private static final String KEY_REMOVE_USER = "remove_user";
+
+ /** Integer extra containing the userId to manage */
+ static final String EXTRA_USER_ID = "user_id";
+ /** Boolean extra to indicate guest preferences */
+ static final String EXTRA_USER_GUEST = "guest_user";
+
+ private static final int DIALOG_CONFIRM_REMOVE = 1;
+ private static final int DIALOG_CONFIRM_ENABLE_CALLING = 2;
+ private static final int DIALOG_CONFIRM_ENABLE_CALLING_SMS = 3;
+
+ private UserManager mUserManager;
+ private SwitchPreference mPhonePref;
+ private Preference mRemoveUserPref;
+
+ private UserInfo mUserInfo;
+ private boolean mGuestUser;
+ private Bundle mDefaultGuestRestrictions;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ final Context context = getActivity();
+ mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+
+ addPreferencesFromResource(R.xml.user_details_settings);
+ mPhonePref = (SwitchPreference) findPreference(KEY_ENABLE_TELEPHONY);
+ mRemoveUserPref = findPreference(KEY_REMOVE_USER);
+
+ mGuestUser = getArguments().getBoolean(EXTRA_USER_GUEST, false);
+
+ if (!mGuestUser) {
+ // Regular user. Get the user id from the caller.
+ final int userId = getArguments().getInt(EXTRA_USER_ID, -1);
+ if (userId == -1) {
+ throw new RuntimeException("Arguments to this fragment must contain the user id");
+ }
+ mUserInfo = mUserManager.getUserInfo(userId);
+ mPhonePref.setChecked(!mUserManager.hasUserRestriction(
+ UserManager.DISALLOW_OUTGOING_CALLS, new UserHandle(userId)));
+ mRemoveUserPref.setOnPreferenceClickListener(this);
+ } else {
+ // These are not for an existing user, just general Guest settings.
+ removePreference(KEY_REMOVE_USER);
+ // Default title is for calling and SMS. Change to calling-only here
+ mPhonePref.setTitle(R.string.user_enable_calling);
+ mDefaultGuestRestrictions = mUserManager.getDefaultGuestRestrictions();
+ mPhonePref.setChecked(
+ !mDefaultGuestRestrictions.getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
+ }
+ if (mUserManager.hasUserRestriction(UserManager.DISALLOW_REMOVE_USER)) {
+ removePreference(KEY_REMOVE_USER);
+ }
+ mPhonePref.setOnPreferenceChangeListener(this);
+ }
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ if (preference == mRemoveUserPref) {
+ if (UserHandle.myUserId() != UserHandle.USER_OWNER) {
+ throw new RuntimeException("Only the owner can remove a user");
+ }
+ showDialog(DIALOG_CONFIRM_REMOVE);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if (mGuestUser) {
+ // TODO: Show confirmation dialog: b/15761405
+ mDefaultGuestRestrictions.putBoolean(UserManager.DISALLOW_OUTGOING_CALLS,
+ !((Boolean) newValue));
+ // SMS is always disabled for guest
+ mDefaultGuestRestrictions.putBoolean(UserManager.DISALLOW_SMS, true);
+ mUserManager.setDefaultGuestRestrictions(mDefaultGuestRestrictions);
+ // Update the guest's restrictions, if there is a guest
+ List<UserInfo> users = mUserManager.getUsers(true);
+ for (UserInfo user: users) {
+ if (user.isGuest()) {
+ UserHandle userHandle = new UserHandle(user.id);
+ Bundle userRestrictions = mUserManager.getUserRestrictions(userHandle);
+ userRestrictions.putAll(mDefaultGuestRestrictions);
+ mUserManager.setUserRestrictions(userRestrictions, userHandle);
+ }
+ }
+ } else {
+ // TODO: Show confirmation dialog: b/15761405
+ UserHandle userHandle = new UserHandle(mUserInfo.id);
+ mUserManager.setUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
+ !((Boolean) newValue), userHandle);
+ mUserManager.setUserRestriction(UserManager.DISALLOW_SMS,
+ !((Boolean) newValue), userHandle);
+ }
+ return true;
+ }
+
+ @Override
+ public Dialog onCreateDialog(int dialogId) {
+ Context context = getActivity();
+ if (context == null) return null;
+ switch (dialogId) {
+ case DIALOG_CONFIRM_REMOVE: {
+ Dialog dlg = Utils.createRemoveConfirmationDialog(getActivity(), mUserInfo.id,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ removeUser();
+ }
+ });
+ return dlg;
+ }
+ case DIALOG_CONFIRM_ENABLE_CALLING:
+ case DIALOG_CONFIRM_ENABLE_CALLING_SMS:
+ // TODO: b/15761405
+ }
+ return null;
+ }
+
+ void removeUser() {
+ mUserManager.removeUser(mUserInfo.id);
+ finishFragment();
+ }
+}
diff --git a/src/com/android/settings/users/UserPreference.java b/src/com/android/settings/users/UserPreference.java
index 9f53aa5..23359ec 100644
--- a/src/com/android/settings/users/UserPreference.java
+++ b/src/com/android/settings/users/UserPreference.java
@@ -16,7 +16,6 @@
package com.android.settings.users;
-import com.android.internal.util.CharSequences;
import com.android.settings.R;
import android.content.Context;
@@ -30,13 +29,12 @@ import android.view.View.OnClickListener;
public class UserPreference extends Preference {
public static final int USERID_UNKNOWN = -10;
+ public static final int USERID_GUEST_DEFAULTS = -11;
private OnClickListener mDeleteClickListener;
private OnClickListener mSettingsClickListener;
private int mSerialNumber = -1;
private int mUserId = USERID_UNKNOWN;
- private boolean mRestricted;
- private boolean mSelf;
static final int SETTINGS_ID = R.id.manage_user;
static final int DELETE_ID = R.id.trash_user;
@@ -58,11 +56,13 @@ public class UserPreference extends Preference {
@Override
protected void onBindView(View view) {
+ UserManager um = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
View deleteDividerView = view.findViewById(R.id.divider_delete);
View manageDividerView = view.findViewById(R.id.divider_manage);
View deleteView = view.findViewById(R.id.trash_user);
if (deleteView != null) {
- if (mDeleteClickListener != null) {
+ if (mDeleteClickListener != null
+ && !um.hasUserRestriction(UserManager.DISALLOW_REMOVE_USER)) {
deleteView.setOnClickListener(mDeleteClickListener);
deleteView.setTag(this);
} else {
@@ -90,7 +90,11 @@ public class UserPreference extends Preference {
if (mUserId == UserHandle.myUserId()) return Integer.MIN_VALUE;
if (mSerialNumber < 0) {
// If the userId is unknown
- if (mUserId == USERID_UNKNOWN) return Integer.MAX_VALUE;
+ if (mUserId == USERID_UNKNOWN) {
+ return Integer.MAX_VALUE;
+ } else if (mUserId == USERID_GUEST_DEFAULTS) {
+ return Integer.MAX_VALUE - 1;
+ }
mSerialNumber = ((UserManager) getContext().getSystemService(Context.USER_SERVICE))
.getUserSerialNumber(mUserId);
if (mSerialNumber < 0) return mUserId;
diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java
index bbae37d..b95c397 100644
--- a/src/com/android/settings/users/UserSettings.java
+++ b/src/com/android/settings/users/UserSettings.java
@@ -16,16 +16,13 @@
package com.android.settings.users;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.Activity;
import android.app.ActivityManagerNative;
import android.app.AlertDialog;
import android.app.Dialog;
+import android.app.Fragment;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -36,7 +33,6 @@ import android.content.SharedPreferences;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
@@ -47,10 +43,8 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
-import android.preference.PreferenceActivity;
import android.preference.PreferenceGroup;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.Contacts;
+import android.provider.Settings;
import android.provider.Settings.Secure;
import android.util.Log;
import android.util.SparseArray;
@@ -61,17 +55,33 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.widget.SimpleAdapter;
+import com.android.internal.util.UserIcons;
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.ChooseLockGeneric;
import com.android.settings.OwnerInfoSettings;
import com.android.settings.R;
-import com.android.settings.RestrictedSettingsFragment;
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;
-public class UserSettings extends RestrictedSettingsFragment
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Screen that manages the list of users on the device.
+ * Guest user is an always visible entry, even if the guest is not currently
+ * active/created. It is meant for controlling properties of a guest user.
+ *
+ * The first one is always the current user.
+ * Owner is the primary user.
+ */
+public class UserSettings extends SettingsPreferenceFragment
implements OnPreferenceClickListener, OnClickListener, DialogInterface.OnDismissListener,
- Preference.OnPreferenceChangeListener {
+ Preference.OnPreferenceChangeListener,
+ EditUserInfoController.OnContentChangedCallback {
private static final String TAG = "UserSettings";
@@ -85,6 +95,7 @@ public class UserSettings extends RestrictedSettingsFragment
private static final String KEY_ADD_USER = "user_add";
private static final int MENU_REMOVE_USER = Menu.FIRST;
+ private static final int MENU_ADD_ON_LOCKSCREEN = Menu.FIRST + 1;
private static final int DIALOG_CONFIRM_REMOVE = 1;
private static final int DIALOG_ADD_USER = 2;
@@ -93,6 +104,8 @@ public class UserSettings extends RestrictedSettingsFragment
private static final int DIALOG_USER_CANNOT_MANAGE = 5;
private static final int DIALOG_CHOOSE_USER_TYPE = 6;
private static final int DIALOG_NEED_LOCKSCREEN = 7;
+ private static final int DIALOG_CONFIRM_EXIT_GUEST = 8;
+ private static final int DIALOG_USER_PROFILE_EDITOR = 9;
private static final int MESSAGE_UPDATE_LIST = 1;
private static final int MESSAGE_SETUP_USER = 2;
@@ -106,17 +119,6 @@ public class UserSettings extends RestrictedSettingsFragment
private static final String KEY_ADD_USER_LONG_MESSAGE_DISPLAYED =
"key_add_user_long_message_displayed";
- static final int[] USER_DRAWABLES = {
- R.drawable.avatar_default_1,
- R.drawable.avatar_default_2,
- R.drawable.avatar_default_3,
- R.drawable.avatar_default_4,
- R.drawable.avatar_default_5,
- R.drawable.avatar_default_6,
- R.drawable.avatar_default_7,
- R.drawable.avatar_default_8
- };
-
private static final String KEY_TITLE = "title";
private static final String KEY_SUMMARY = "summary";
@@ -127,16 +129,20 @@ public class UserSettings extends RestrictedSettingsFragment
private int mRemovingUserId = -1;
private int mAddedUserId = 0;
private boolean mAddingUser;
- private boolean mProfileExists;
+ private boolean mEnabled = true;
+ private boolean mCanAddRestrictedProfile = true;
private final Object mUserLock = new Object();
private UserManager mUserManager;
private SparseArray<Bitmap> mUserIcons = new SparseArray<Bitmap>();
private boolean mIsOwner = UserHandle.myUserId() == UserHandle.USER_OWNER;
+ private boolean mIsGuest;
- public UserSettings() {
- super(RestrictedSettingsFragment.RESTRICTIONS_PIN_SET);
- }
+ private EditUserInfoController mEditUserInfoController =
+ new EditUserInfoController();
+
+ // A place to cache the generated default avatar
+ private Drawable mDefaultIconDrawable;
private Handler mHandler = new Handler() {
@Override
@@ -181,34 +187,59 @@ public class UserSettings extends RestrictedSettingsFragment
if (icicle.containsKey(SAVE_REMOVING_USER)) {
mRemovingUserId = icicle.getInt(SAVE_REMOVING_USER);
}
+ mEditUserInfoController.onRestoreInstanceState(icicle);
+ }
+ final Context context = getActivity();
+ mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ boolean hasMultipleUsers = mUserManager.getUserCount() > 1;
+ if ((!UserManager.supportsMultipleUsers() && !hasMultipleUsers)
+ || Utils.isMonkeyRunning()) {
+ mEnabled = false;
+ return;
}
- mUserManager = (UserManager) getActivity().getSystemService(Context.USER_SERVICE);
+ final int myUserId = UserHandle.myUserId();
+ mIsGuest = mUserManager.getUserInfo(myUserId).isGuest();
+
addPreferencesFromResource(R.xml.user_settings);
mUserListCategory = (PreferenceGroup) findPreference(KEY_USER_LIST);
- mMePreference = new UserPreference(getActivity(), null, UserHandle.myUserId(),
- mUserManager.isLinkedUser() ? null : this, null);
+ mMePreference = new UserPreference(context, null /* attrs */, myUserId,
+ null /* settings icon handler */,
+ null /* delete icon handler */);
mMePreference.setKey(KEY_USER_ME);
mMePreference.setOnPreferenceClickListener(this);
if (mIsOwner) {
mMePreference.setSummary(R.string.user_owner);
}
mAddUser = findPreference(KEY_ADD_USER);
- mAddUser.setOnPreferenceClickListener(this);
- if (!mIsOwner || UserManager.getMaxSupportedUsers() < 2) {
+ if (!mIsOwner || UserManager.getMaxSupportedUsers() < 2
+ || !UserManager.supportsMultipleUsers()
+ || mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER)) {
removePreference(KEY_ADD_USER);
+ } else {
+ mAddUser.setOnPreferenceClickListener(this);
+ DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
+ Context.DEVICE_POLICY_SERVICE);
+ // No restricted profiles for tablets with a device owner, or phones.
+ if (dpm.getDeviceOwner() != null || Utils.isVoiceCapable(context)) {
+ mCanAddRestrictedProfile = false;
+ mAddUser.setTitle(R.string.user_add_user_menu);
+ }
}
loadProfile();
setHasOptionsMenu(true);
IntentFilter filter = new IntentFilter(Intent.ACTION_USER_REMOVED);
filter.addAction(Intent.ACTION_USER_INFO_CHANGED);
- getActivity().registerReceiverAsUser(mUserChangeReceiver, UserHandle.ALL, filter, null,
+ context.registerReceiverAsUser(mUserChangeReceiver, UserHandle.ALL, filter, null,
mHandler);
}
@Override
public void onResume() {
super.onResume();
+
+ if (!mEnabled) return;
+
loadProfile();
updateUserList();
}
@@ -216,26 +247,43 @@ public class UserSettings extends RestrictedSettingsFragment
@Override
public void onDestroy() {
super.onDestroy();
+
+ if (!mEnabled) return;
+
getActivity().unregisterReceiver(mUserChangeReceiver);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
-
+ mEditUserInfoController.onSaveInstanceState(outState);
outState.putInt(SAVE_ADDING_USER, mAddedUserId);
outState.putInt(SAVE_REMOVING_USER, mRemovingUserId);
}
@Override
+ public void startActivityForResult(Intent intent, int requestCode) {
+ mEditUserInfoController.startingActivityForResult();
+ super.startActivityForResult(intent, requestCode);
+ }
+
+ @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ int pos = 0;
UserManager um = (UserManager) getActivity().getSystemService(Context.USER_SERVICE);
if (!mIsOwner && !um.hasUserRestriction(UserManager.DISALLOW_REMOVE_USER)) {
String nickname = mUserManager.getUserName();
- MenuItem removeThisUser = menu.add(0, MENU_REMOVE_USER, 0,
+ MenuItem removeThisUser = menu.add(0, MENU_REMOVE_USER, pos++,
getResources().getString(R.string.user_remove_user_menu, nickname));
removeThisUser.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
}
+ if (mIsOwner && !um.hasUserRestriction(UserManager.DISALLOW_ADD_USER)) {
+ MenuItem allowAddOnLockscreen = menu.add(0, MENU_ADD_ON_LOCKSCREEN, pos++,
+ R.string.user_add_on_lockscreen_menu);
+ allowAddOnLockscreen.setCheckable(true);
+ allowAddOnLockscreen.setChecked(Settings.Global.getInt(getContentResolver(),
+ Settings.Global.ADD_USERS_WHEN_LOCKED, 0) == 1);
+ }
super.onCreateOptionsMenu(menu, inflater);
}
@@ -245,13 +293,28 @@ public class UserSettings extends RestrictedSettingsFragment
if (itemId == MENU_REMOVE_USER) {
onRemoveUserClicked(UserHandle.myUserId());
return true;
+ } else if (itemId == MENU_ADD_ON_LOCKSCREEN) {
+ final boolean isChecked = item.isChecked();
+ Settings.Global.putInt(getContentResolver(), Settings.Global.ADD_USERS_WHEN_LOCKED,
+ isChecked ? 0 : 1);
+ item.setChecked(!isChecked);
+ return true;
} else {
return super.onOptionsItemSelected(item);
}
}
+ /**
+ * Loads profile information for the current user.
+ */
private void loadProfile() {
- mProfileExists = false;
+ if (mIsGuest) {
+ // No need to load profile information
+ mMePreference.setIcon(getEncircledDefaultIcon());
+ mMePreference.setTitle(R.string.user_exit_guest_title);
+ return;
+ }
+
new AsyncTask<Void, Void, String>() {
@Override
protected void onPostExecute(String result) {
@@ -264,11 +327,7 @@ public class UserSettings extends RestrictedSettingsFragment
if (user.iconPath == null || user.iconPath.equals("")) {
assignProfilePhoto(user);
}
- String profileName = getProfileName();
- if (profileName == null) {
- profileName = user.name;
- }
- return profileName;
+ return user.name;
}
}.execute();
}
@@ -303,9 +362,9 @@ public class UserSettings extends RestrictedSettingsFragment
if (requestCode == REQUEST_CHOOSE_LOCK) {
if (resultCode != Activity.RESULT_CANCELED && hasLockscreenSecurity()) {
addUserNow(USER_TYPE_RESTRICTED_PROFILE);
- } else {
- showDialog(DIALOG_NEED_LOCKSCREEN);
}
+ } else {
+ mEditUserInfoController.onActivityResult(requestCode, resultCode, data);
}
}
@@ -338,19 +397,18 @@ public class UserSettings extends RestrictedSettingsFragment
}
private UserInfo createLimitedUser() {
- UserInfo newUserInfo = mUserManager.createUser(
+ UserInfo newUserInfo = mUserManager.createSecondaryUser(
getResources().getString(R.string.user_new_profile_name),
UserInfo.FLAG_RESTRICTED);
int userId = newUserInfo.id;
UserHandle user = new UserHandle(userId);
mUserManager.setUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS, true, user);
+ // Change the setting before applying the DISALLOW_SHARE_LOCATION restriction, otherwise
+ // the putIntForUser() will fail.
+ Secure.putIntForUser(getContentResolver(),
+ Secure.LOCATION_MODE, Secure.LOCATION_MODE_OFF, userId);
mUserManager.setUserRestriction(UserManager.DISALLOW_SHARE_LOCATION, true, user);
- Secure.putStringForUser(getContentResolver(),
- Secure.LOCATION_PROVIDERS_ALLOWED, "", userId);
- Bitmap bitmap = BitmapFactory.decodeResource(getResources(),
- UserSettings.USER_DRAWABLES[
- userId % UserSettings.USER_DRAWABLES.length]);
- mUserManager.setUserIcon(userId, bitmap);
+ assignDefaultPhoto(newUserInfo);
// Add shared accounts
AccountManager am = AccountManager.get(getActivity());
Account [] accounts = am.getAccounts();
@@ -363,7 +421,7 @@ public class UserSettings extends RestrictedSettingsFragment
}
private UserInfo createTrustedUser() {
- UserInfo newUserInfo = mUserManager.createUser(
+ UserInfo newUserInfo = mUserManager.createSecondaryUser(
getResources().getString(R.string.user_new_user_name), 0);
if (newUserInfo != null) {
assignDefaultPhoto(newUserInfo);
@@ -372,12 +430,20 @@ public class UserSettings extends RestrictedSettingsFragment
}
private void onManageUserClicked(int userId, boolean newUser) {
+ if (userId == UserPreference.USERID_GUEST_DEFAULTS) {
+ Bundle extras = new Bundle();
+ extras.putBoolean(UserDetailsSettings.EXTRA_USER_GUEST, true);
+ ((SettingsActivity) getActivity()).startPreferencePanel(
+ UserDetailsSettings.class.getName(),
+ extras, R.string.user_guest, null, null, 0);
+ return;
+ }
UserInfo info = mUserManager.getUserInfo(userId);
if (info.isRestricted() && mIsOwner) {
Bundle extras = new Bundle();
extras.putInt(RestrictedProfileSettings.EXTRA_USER_ID, userId);
extras.putBoolean(RestrictedProfileSettings.EXTRA_NEW_USER, newUser);
- ((PreferenceActivity) getActivity()).startPreferencePanel(
+ ((SettingsActivity) getActivity()).startPreferencePanel(
RestrictedProfileSettings.class.getName(),
extras, R.string.user_restrictions_title, null,
null, 0);
@@ -390,9 +456,19 @@ public class UserSettings extends RestrictedSettingsFragment
int titleResId = info.id == UserHandle.USER_OWNER ? R.string.owner_info_settings_title
: (info.isRestricted() ? R.string.profile_info_settings_title
: R.string.user_info_settings_title);
- ((PreferenceActivity) getActivity()).startPreferencePanel(
+ ((SettingsActivity) getActivity()).startPreferencePanel(
OwnerInfoSettings.class.getName(),
extras, titleResId, null, null, 0);
+ } else if (mIsOwner) {
+ Bundle extras = new Bundle();
+ extras.putInt(UserDetailsSettings.EXTRA_USER_ID, userId);
+ ((SettingsActivity) getActivity()).startPreferencePanel(
+ UserDetailsSettings.class.getName(),
+ extras,
+ -1, /* No title res id */
+ info.name, /* title */
+ null, /* resultTo */
+ 0 /* resultRequestCode */);
}
}
@@ -418,25 +494,14 @@ public class UserSettings extends RestrictedSettingsFragment
if (context == null) return null;
switch (dialogId) {
case DIALOG_CONFIRM_REMOVE: {
- Dialog dlg = new AlertDialog.Builder(getActivity())
- .setTitle(UserHandle.myUserId() == mRemovingUserId
- ? R.string.user_confirm_remove_self_title
- : (mUserManager.getUserInfo(mRemovingUserId).isRestricted()
- ? R.string.user_profile_confirm_remove_title
- : R.string.user_confirm_remove_title))
- .setMessage(UserHandle.myUserId() == mRemovingUserId
- ? R.string.user_confirm_remove_self_message
- : (mUserManager.getUserInfo(mRemovingUserId).isRestricted()
- ? R.string.user_profile_confirm_remove_message
- : R.string.user_confirm_remove_message))
- .setPositiveButton(R.string.user_delete_button,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- removeUserNow();
- }
- })
- .setNegativeButton(android.R.string.cancel, null)
- .create();
+ Dialog dlg =
+ Utils.createRemoveConfirmationDialog(getActivity(), mRemovingUserId,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ removeUserNow();
+ }
+ }
+ );
return dlg;
}
case DIALOG_USER_CANNOT_MANAGE:
@@ -537,6 +602,31 @@ public class UserSettings extends RestrictedSettingsFragment
.create();
return dlg;
}
+ case DIALOG_CONFIRM_EXIT_GUEST: {
+ Dialog dlg = new AlertDialog.Builder(context)
+ .setTitle(R.string.user_exit_guest_confirm_title)
+ .setMessage(R.string.user_exit_guest_confirm_message)
+ .setPositiveButton(R.string.user_exit_guest_dialog_remove,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ exitGuest();
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, null)
+ .create();
+ return dlg;
+ }
+ case DIALOG_USER_PROFILE_EDITOR: {
+ Dialog dlg = mEditUserInfoController.createDialog(
+ (Fragment) this,
+ mMePreference.getIcon(),
+ mMePreference.getTitle(),
+ R.string.profile_info_settings_title,
+ this /* callback */,
+ android.os.Process.myUserHandle());
+ return dlg;
+ }
default:
return null;
}
@@ -604,23 +694,54 @@ public class UserSettings extends RestrictedSettingsFragment
}
}
+ /**
+ * Erase the current user (guest) and switch to another user.
+ */
+ private void exitGuest() {
+ // Just to be safe
+ if (!mIsGuest) {
+ return;
+ }
+ removeThisUser();
+ }
+
private void updateUserList() {
if (getActivity() == null) return;
List<UserInfo> users = mUserManager.getUsers(true);
+ final Context context = getActivity();
mUserListCategory.removeAll();
mUserListCategory.setOrderingAsAdded(false);
mUserListCategory.addPreference(mMePreference);
+ final boolean voiceCapable = Utils.isVoiceCapable(context);
final ArrayList<Integer> missingIcons = new ArrayList<Integer>();
for (UserInfo user : users) {
+ if (user.isManagedProfile()) {
+ // Managed profiles appear under Accounts Settings instead
+ continue;
+ }
Preference pref;
if (user.id == UserHandle.myUserId()) {
pref = mMePreference;
+ } else if (user.isGuest()) {
+ // Skip over Guest. We add generic Guest settings after this loop
+ continue;
} else {
- pref = new UserPreference(getActivity(), null, user.id,
- mIsOwner && user.isRestricted() ? this : null,
- mIsOwner ? this : null);
+ // With Telephony:
+ // Secondary user: Settings
+ // Guest: Settings
+ // Restricted Profile: There is no Restricted Profile
+ // Without Telephony:
+ // Secondary user: Delete
+ // Guest: Nothing
+ // Restricted Profile: Settings
+ final boolean showSettings = mIsOwner && (voiceCapable || user.isRestricted());
+ final boolean showDelete = mIsOwner
+ && (!voiceCapable && !user.isRestricted() && !user.isGuest());
+ pref = new UserPreference(context, null, user.id,
+ showSettings ? this : null,
+ showDelete ? this : null);
pref.setOnPreferenceClickListener(this);
pref.setKey("id=" + user.id);
mUserListCategory.addPreference(pref);
@@ -630,37 +751,69 @@ public class UserSettings extends RestrictedSettingsFragment
pref.setTitle(user.name);
}
if (!isInitialized(user)) {
- pref.setSummary(user.isRestricted()
- ? R.string.user_summary_restricted_not_set_up
- : R.string.user_summary_not_set_up);
+ if (user.isRestricted()) {
+ pref.setSummary(R.string.user_summary_restricted_not_set_up);
+ } else {
+ pref.setSummary(R.string.user_summary_not_set_up);
+ }
} else if (user.isRestricted()) {
pref.setSummary(R.string.user_summary_restricted_profile);
}
if (user.iconPath != null) {
if (mUserIcons.get(user.id) == null) {
+ // Icon not loaded yet, print a placeholder
missingIcons.add(user.id);
- pref.setIcon(encircle(R.drawable.avatar_default_1));
+ pref.setIcon(getEncircledDefaultIcon());
} else {
setPhotoId(pref, user);
}
+ } else {
+ // Icon not available yet, print a placeholder
+ pref.setIcon(getEncircledDefaultIcon());
}
}
+
// Add a temporary entry for the user being created
if (mAddingUser) {
Preference pref = new UserPreference(getActivity(), null, UserPreference.USERID_UNKNOWN,
null, null);
pref.setEnabled(false);
pref.setTitle(R.string.user_new_user_name);
- pref.setIcon(encircle(R.drawable.avatar_default_1));
+ pref.setIcon(getEncircledDefaultIcon());
+ mUserListCategory.addPreference(pref);
+ }
+
+ boolean showGuestPreference = !mIsGuest;
+ // If user has DISALLOW_ADD_USER don't allow creating a guest either.
+ if (showGuestPreference && mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER)) {
+ showGuestPreference = false;
+ // If guest already exists, no user creation needed.
+ for (UserInfo user : users) {
+ if (user.isGuest()) {
+ showGuestPreference = true;
+ break;
+ }
+ }
+ }
+ if (showGuestPreference) {
+ // Add a virtual Guest user for guest defaults
+ Preference pref = new UserPreference(getActivity(), null,
+ UserPreference.USERID_GUEST_DEFAULTS,
+ mIsOwner && voiceCapable? this : null /* settings icon handler */,
+ null /* delete icon handler */);
+ pref.setTitle(R.string.user_guest);
+ pref.setIcon(getEncircledDefaultIcon());
+ pref.setOnPreferenceClickListener(this);
mUserListCategory.addPreference(pref);
}
+
getActivity().invalidateOptionsMenu();
// Load the icons
if (missingIcons.size() > 0) {
loadIconsAsync(missingIcons);
}
- boolean moreUsers = mUserManager.getMaxSupportedUsers() > users.size();
+ boolean moreUsers = mUserManager.canAddMoreUsers();
mAddUser.setEnabled(moreUsers);
}
@@ -676,6 +829,10 @@ public class UserSettings extends RestrictedSettingsFragment
protected Void doInBackground(List<Integer>... values) {
for (int userId : values[0]) {
Bitmap bitmap = mUserManager.getUserIcon(userId);
+ if (bitmap == null) {
+ bitmap = UserIcons.convertToBitmap(UserIcons.getDefaultUserIcon(userId,
+ /* light= */ false));
+ }
mUserIcons.append(userId, bitmap);
}
return null;
@@ -689,20 +846,20 @@ public class UserSettings extends RestrictedSettingsFragment
}
}
- private String getProfileName() {
- String name = Utils.getMeProfileName(getActivity(), true);
- if (name != null) {
- mProfileExists = true;
- }
- return name;
- }
-
private void assignDefaultPhoto(UserInfo user) {
- Bitmap bitmap = BitmapFactory.decodeResource(getResources(),
- USER_DRAWABLES[user.id % USER_DRAWABLES.length]);
+ Bitmap bitmap = UserIcons.convertToBitmap(UserIcons.getDefaultUserIcon(user.id,
+ /* light= */ false));
mUserManager.setUserIcon(user.id, bitmap);
}
+ private Drawable getEncircledDefaultIcon() {
+ if (mDefaultIconDrawable == null) {
+ mDefaultIconDrawable = encircle(UserIcons.convertToBitmap(
+ UserIcons.getDefaultUserIcon(UserHandle.USER_NULL, /* light= */ false)));
+ }
+ return mDefaultIconDrawable;
+ }
+
private void setPhotoId(Preference pref, UserInfo user) {
Bitmap bitmap = mUserIcons.get(user.id);
if (bitmap != null) {
@@ -719,51 +876,67 @@ public class UserSettings extends RestrictedSettingsFragment
@Override
public boolean onPreferenceClick(Preference pref) {
if (pref == mMePreference) {
- Intent editProfile;
- if (!mProfileExists) {
- editProfile = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
- // TODO: Make this a proper API
- editProfile.putExtra("newLocalProfile", true);
- } else {
- editProfile = new Intent(Intent.ACTION_EDIT, ContactsContract.Profile.CONTENT_URI);
+ if (mIsGuest) {
+ showDialog(DIALOG_CONFIRM_EXIT_GUEST);
+ return true;
}
- // To make sure that it returns back here when done
- // TODO: Make this a proper API
- editProfile.putExtra("finishActivityOnSaveCompleted", true);
-
// If this is a limited user, launch the user info settings instead of profile editor
if (mUserManager.isLinkedUser()) {
onManageUserClicked(UserHandle.myUserId(), false);
} else {
- startActivity(editProfile);
+ showDialog(DIALOG_USER_PROFILE_EDITOR);
}
} else if (pref instanceof UserPreference) {
int userId = ((UserPreference) pref).getUserId();
- // Get the latest status of the user
- UserInfo user = mUserManager.getUserInfo(userId);
- if (UserHandle.myUserId() != UserHandle.USER_OWNER) {
- showDialog(DIALOG_USER_CANNOT_MANAGE);
+ if (userId == UserPreference.USERID_GUEST_DEFAULTS) {
+ createAndSwitchToGuestUser();
} else {
+ // Get the latest status of the user
+ UserInfo user = mUserManager.getUserInfo(userId);
if (!isInitialized(user)) {
mHandler.sendMessage(mHandler.obtainMessage(
MESSAGE_SETUP_USER, user.id, user.serialNumber));
- } else if (user.isRestricted()) {
- onManageUserClicked(user.id, false);
+ } else {
+ switchUserNow(userId);
}
}
} else if (pref == mAddUser) {
- showDialog(DIALOG_CHOOSE_USER_TYPE);
+ // If we allow both types, show a picker, otherwise directly go to
+ // flow for full user.
+ if (mCanAddRestrictedProfile) {
+ showDialog(DIALOG_CHOOSE_USER_TYPE);
+ } else {
+ onAddUserClicked(USER_TYPE_USER);
+ }
}
return false;
}
- private boolean isInitialized(UserInfo user) {
- return (user.flags & UserInfo.FLAG_INITIALIZED) != 0;
+ private void createAndSwitchToGuestUser() {
+ List<UserInfo> users = mUserManager.getUsers();
+ for (UserInfo user : users) {
+ if (user.isGuest()) {
+ switchUserNow(user.id);
+ return;
+ }
+ }
+ // No guest user. Create one, if there's no restriction.
+ // If it is not the primary user, then adding users from lockscreen must be enabled
+ if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER)
+ || (!mIsOwner && Settings.Global.getInt(getContentResolver(),
+ Settings.Global.ADD_USERS_WHEN_LOCKED, 0) != 1)) {
+ Log.i(TAG, "Blocking guest creation because it is restricted");
+ return;
+ }
+ UserInfo guestUser = mUserManager.createGuest(getActivity(),
+ getResources().getString(R.string.user_guest));
+ if (guestUser != null) {
+ switchUserNow(guestUser.id);
+ }
}
- private Drawable encircle(int iconResId) {
- Bitmap icon = BitmapFactory.decodeResource(getResources(), iconResId);
- return encircle(icon);
+ private boolean isInitialized(UserInfo user) {
+ return (user.flags & UserInfo.FLAG_INITIALIZED) != 0;
}
private Drawable encircle(Bitmap icon) {
@@ -812,4 +985,14 @@ public class UserSettings extends RestrictedSettingsFragment
public int getHelpResource() {
return R.string.help_url_users;
}
+
+ @Override
+ public void onPhotoChanged(Drawable photo) {
+ mMePreference.setIcon(photo);
+ }
+
+ @Override
+ public void onLabelChanged(CharSequence label) {
+ mMePreference.setTitle(label);
+ }
}
diff --git a/src/com/android/settings/users/UserUtils.java b/src/com/android/settings/users/UserUtils.java
deleted file mode 100644
index 946d871..0000000
--- a/src/com/android/settings/users/UserUtils.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.users;
-
-import android.content.Context;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.UserManager;
-
-public class UserUtils {
- public static Drawable getUserIcon(Context context, UserManager um, UserInfo user, Resources res) {
- if (user.iconPath == null) return null;
- Bitmap icon = um.getUserIcon(user.id);
- if (icon == null) return null;
- return CircleFramedDrawable.getInstance(context, icon);
- }
-}
diff --git a/src/com/android/settings/voice/VoiceInputHelper.java b/src/com/android/settings/voice/VoiceInputHelper.java
new file mode 100644
index 0000000..63b891a
--- /dev/null
+++ b/src/com/android/settings/voice/VoiceInputHelper.java
@@ -0,0 +1,211 @@
+/*
+ * 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.voice;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.provider.Settings;
+import android.service.voice.VoiceInteractionService;
+import android.service.voice.VoiceInteractionServiceInfo;
+import android.speech.RecognitionService;
+import android.util.ArraySet;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public final class VoiceInputHelper {
+ static final String TAG = "VoiceInputHelper";
+ final Context mContext;
+
+ final List<ResolveInfo> mAvailableVoiceInteractions;
+ final List<ResolveInfo> mAvailableRecognition;
+
+ static public class BaseInfo implements Comparable {
+ public final ServiceInfo service;
+ public final ComponentName componentName;
+ public final String key;
+ public final ComponentName settings;
+ public final CharSequence label;
+ public final String labelStr;
+ public final CharSequence appLabel;
+
+ public BaseInfo(PackageManager pm, ServiceInfo _service, String _settings) {
+ service = _service;
+ componentName = new ComponentName(_service.packageName, _service.name);
+ key = componentName.flattenToShortString();
+ settings = _settings != null
+ ? new ComponentName(_service.packageName, _settings) : null;
+ label = _service.loadLabel(pm);
+ labelStr = label.toString();
+ appLabel = _service.applicationInfo.loadLabel(pm);
+ }
+
+ @Override
+ public int compareTo(Object another) {
+ return labelStr.compareTo(((BaseInfo)another).labelStr);
+ }
+ }
+
+ static public class InteractionInfo extends BaseInfo {
+ public final VoiceInteractionServiceInfo serviceInfo;
+
+ public InteractionInfo(PackageManager pm, VoiceInteractionServiceInfo _service) {
+ super(pm, _service.getServiceInfo(), _service.getSettingsActivity());
+ serviceInfo = _service;
+ }
+ }
+
+ static public class RecognizerInfo extends BaseInfo {
+ public RecognizerInfo(PackageManager pm, ServiceInfo _service, String _settings) {
+ super(pm, _service, _settings);
+ }
+ }
+
+ final ArrayList<InteractionInfo> mAvailableInteractionInfos = new ArrayList<>();
+ final ArrayList<RecognizerInfo> mAvailableRecognizerInfos = new ArrayList<>();
+
+ ComponentName mCurrentVoiceInteraction;
+ ComponentName mCurrentRecognizer;
+
+ public VoiceInputHelper(Context context) {
+ mContext = context;
+
+ mAvailableVoiceInteractions = mContext.getPackageManager().queryIntentServices(
+ new Intent(VoiceInteractionService.SERVICE_INTERFACE),
+ PackageManager.GET_META_DATA);
+ mAvailableRecognition = mContext.getPackageManager().queryIntentServices(
+ new Intent(RecognitionService.SERVICE_INTERFACE),
+ PackageManager.GET_META_DATA);
+ }
+
+ public boolean hasItems() {
+ return mAvailableVoiceInteractions.size() > 0 || mAvailableRecognition.size() > 0;
+ }
+
+ public void buildUi() {
+ // Get the currently selected interactor from the secure setting.
+ String currentSetting = Settings.Secure.getString(
+ mContext.getContentResolver(), Settings.Secure.VOICE_INTERACTION_SERVICE);
+ if (currentSetting != null && !currentSetting.isEmpty()) {
+ mCurrentVoiceInteraction = ComponentName.unflattenFromString(currentSetting);
+ } else {
+ mCurrentVoiceInteraction = null;
+ }
+
+ ArraySet<ComponentName> interactorRecognizers = new ArraySet<>();
+
+ // Iterate through all the available interactors and load up their info to show
+ // in the preference.
+ int size = mAvailableVoiceInteractions.size();
+ for (int i = 0; i < size; i++) {
+ ResolveInfo resolveInfo = mAvailableVoiceInteractions.get(i);
+ VoiceInteractionServiceInfo info = new VoiceInteractionServiceInfo(
+ mContext.getPackageManager(), resolveInfo.serviceInfo);
+ if (info.getParseError() != null) {
+ Log.w("VoiceInteractionService", "Error in VoiceInteractionService "
+ + resolveInfo.serviceInfo.packageName + "/"
+ + resolveInfo.serviceInfo.name + ": " + info.getParseError());
+ continue;
+ }
+ mAvailableInteractionInfos.add(new InteractionInfo(mContext.getPackageManager(), info));
+ if (info.getRecognitionService() != null) {
+ interactorRecognizers.add(new ComponentName(resolveInfo.serviceInfo.packageName,
+ info.getRecognitionService()));
+ }
+ }
+ Collections.sort(mAvailableInteractionInfos);
+
+ // Get the currently selected recognizer from the secure setting.
+ currentSetting = Settings.Secure.getString(
+ mContext.getContentResolver(), Settings.Secure.VOICE_RECOGNITION_SERVICE);
+ if (currentSetting != null && !currentSetting.isEmpty()) {
+ mCurrentRecognizer = ComponentName.unflattenFromString(currentSetting);
+ } else {
+ mCurrentRecognizer = null;
+ }
+
+ // Iterate through all the available recognizers and load up their info to show
+ // in the preference.
+ size = mAvailableRecognition.size();
+ for (int i = 0; i < size; i++) {
+ ResolveInfo resolveInfo = mAvailableRecognition.get(i);
+ ComponentName comp = new ComponentName(resolveInfo.serviceInfo.packageName,
+ resolveInfo.serviceInfo.name);
+ if (interactorRecognizers.contains(comp)) {
+ //continue;
+ }
+ ServiceInfo si = resolveInfo.serviceInfo;
+ XmlResourceParser parser = null;
+ String settingsActivity = null;
+ try {
+ parser = si.loadXmlMetaData(mContext.getPackageManager(),
+ RecognitionService.SERVICE_META_DATA);
+ if (parser == null) {
+ throw new XmlPullParserException("No " + RecognitionService.SERVICE_META_DATA +
+ " meta-data for " + si.packageName);
+ }
+
+ Resources res = mContext.getPackageManager().getResourcesForApplication(
+ si.applicationInfo);
+
+ AttributeSet attrs = Xml.asAttributeSet(parser);
+
+ int type;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ }
+
+ String nodeName = parser.getName();
+ if (!"recognition-service".equals(nodeName)) {
+ throw new XmlPullParserException(
+ "Meta-data does not start with recognition-service tag");
+ }
+
+ TypedArray array = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.RecognitionService);
+ settingsActivity = array.getString(
+ com.android.internal.R.styleable.RecognitionService_settingsActivity);
+ array.recycle();
+ } catch (XmlPullParserException e) {
+ Log.e(TAG, "error parsing recognition service meta-data", e);
+ } catch (IOException e) {
+ Log.e(TAG, "error parsing recognition service meta-data", e);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "error parsing recognition service meta-data", e);
+ } finally {
+ if (parser != null) parser.close();
+ }
+ mAvailableRecognizerInfos.add(new RecognizerInfo(mContext.getPackageManager(),
+ resolveInfo.serviceInfo, settingsActivity));
+ }
+ Collections.sort(mAvailableRecognizerInfos);
+ }
+}
diff --git a/src/com/android/settings/voice/VoiceInputPreference.java b/src/com/android/settings/voice/VoiceInputPreference.java
new file mode 100644
index 0000000..0ebffbb
--- /dev/null
+++ b/src/com/android/settings/voice/VoiceInputPreference.java
@@ -0,0 +1,233 @@
+/*
+ * 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.voice;
+
+import android.app.AlertDialog;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.preference.Preference;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Checkable;
+import android.widget.CompoundButton;
+import android.widget.RadioButton;
+
+
+import com.android.settings.R;
+import com.android.settings.Utils;
+
+public final class VoiceInputPreference extends Preference {
+
+ private static final String TAG = "VoiceInputPreference";
+
+ private final CharSequence mLabel;
+
+ private final CharSequence mAppLabel;
+
+ private final CharSequence mAlertText;
+
+ private final ComponentName mSettingsComponent;
+
+ /**
+ * The shared radio button state, which button is checked etc.
+ */
+ private final RadioButtonGroupState mSharedState;
+
+ /**
+ * When true, the change callbacks on the radio button will not
+ * fire.
+ */
+ private volatile boolean mPreventRadioButtonCallbacks;
+
+ private View mSettingsIcon;
+ private RadioButton mRadioButton;
+
+ private final CompoundButton.OnCheckedChangeListener mRadioChangeListener =
+ new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ onRadioButtonClicked(buttonView, isChecked);
+ }
+ };
+
+ public VoiceInputPreference(Context context, VoiceInputHelper.BaseInfo info,
+ CharSequence summary, CharSequence alertText, RadioButtonGroupState state) {
+ super(context);
+ setLayoutResource(R.layout.preference_tts_engine);
+
+ mSharedState = state;
+ mLabel = info.label;
+ mAppLabel = info.appLabel;
+ mAlertText = alertText;
+ mSettingsComponent = info.settings;
+ mPreventRadioButtonCallbacks = false;
+
+ setKey(info.key);
+ setTitle(info.label);
+ setSummary(summary);
+ }
+
+ @Override
+ public View getView(View convertView, ViewGroup parent) {
+ if (mSharedState == null) {
+ throw new IllegalStateException("Call to getView() before a call to" +
+ "setSharedState()");
+ }
+
+ View view = super.getView(convertView, parent);
+ final RadioButton rb = (RadioButton) view.findViewById(R.id.tts_engine_radiobutton);
+ rb.setOnCheckedChangeListener(mRadioChangeListener);
+
+ boolean isChecked = getKey().equals(mSharedState.getCurrentKey());
+ if (isChecked) {
+ mSharedState.setCurrentChecked(rb);
+ }
+
+ mPreventRadioButtonCallbacks = true;
+ rb.setChecked(isChecked);
+ mPreventRadioButtonCallbacks = false;
+
+ mRadioButton = rb;
+
+ View textLayout = view.findViewById(R.id.tts_engine_pref_text);
+ textLayout.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ onRadioButtonClicked(rb, !rb.isChecked());
+ }
+ });
+
+ mSettingsIcon = view.findViewById(R.id.tts_engine_settings);
+ mSettingsIcon.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setComponent(mSettingsComponent);
+ getContext().startActivity(new Intent(intent));
+ }
+ });
+ updateCheckedState(isChecked);
+
+ return view;
+ }
+
+ private boolean shouldDisplayAlert() {
+ return mAlertText != null;
+ }
+
+ private void displayAlert(
+ final DialogInterface.OnClickListener positiveOnClickListener,
+ final DialogInterface.OnClickListener negativeOnClickListener) {
+ Log.i(TAG, "Displaying data alert for :" + getKey());
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
+ String msg = String.format(getContext().getResources().getConfiguration().locale,
+ mAlertText.toString(), mAppLabel);
+ builder.setTitle(android.R.string.dialog_alert_title)
+ .setMessage(msg)
+ .setCancelable(true)
+ .setPositiveButton(android.R.string.ok, positiveOnClickListener)
+ .setNegativeButton(android.R.string.cancel, negativeOnClickListener)
+ .setOnCancelListener(new DialogInterface.OnCancelListener() {
+ @Override public void onCancel(DialogInterface dialog) {
+ negativeOnClickListener.onClick(dialog, DialogInterface.BUTTON_NEGATIVE);
+ }
+ });
+
+ AlertDialog dialog = builder.create();
+ dialog.show();
+ }
+
+ public void doClick() {
+ mRadioButton.performClick();
+ }
+
+ void updateCheckedState(boolean isChecked) {
+ if (mSettingsComponent != null) {
+ mSettingsIcon.setVisibility(View.VISIBLE);
+ if (isChecked) {
+ mSettingsIcon.setEnabled(true);
+ mSettingsIcon.setAlpha(1);
+ } else {
+ mSettingsIcon.setEnabled(false);
+ mSettingsIcon.setAlpha(Utils.DISABLED_ALPHA);
+ }
+ } else {
+ mSettingsIcon.setVisibility(View.GONE);
+ }
+ }
+
+ void onRadioButtonClicked(final CompoundButton buttonView, boolean isChecked) {
+ if (mPreventRadioButtonCallbacks) {
+ return;
+ }
+ if (mSharedState.getCurrentChecked() == buttonView) {
+ updateCheckedState(isChecked);
+ return;
+ }
+
+ if (isChecked) {
+ // Should we alert user? if that's true, delay making engine current one.
+ if (shouldDisplayAlert()) {
+ displayAlert(new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ makeCurrentChecked(buttonView);
+ }
+ }, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ // Undo the click.
+ buttonView.setChecked(false);
+ }
+ }
+ );
+ } else {
+ // Privileged engine, set it current
+ makeCurrentChecked(buttonView);
+ }
+ } else {
+ updateCheckedState(isChecked);
+ }
+ }
+
+ void makeCurrentChecked(Checkable current) {
+ if (mSharedState.getCurrentChecked() != null) {
+ mSharedState.getCurrentChecked().setChecked(false);
+ }
+ mSharedState.setCurrentChecked(current);
+ mSharedState.setCurrentKey(getKey());
+ updateCheckedState(true);
+ callChangeListener(mSharedState.getCurrentKey());
+ }
+
+ /**
+ * Holds all state that is common to this group of radio buttons, such
+ * as the currently selected key and the currently checked compound button.
+ * (which corresponds to this key).
+ */
+ public interface RadioButtonGroupState {
+ String getCurrentKey();
+ Checkable getCurrentChecked();
+
+ void setCurrentKey(String key);
+ void setCurrentChecked(Checkable current);
+ }
+}
diff --git a/src/com/android/settings/voice/VoiceInputSettings.java b/src/com/android/settings/voice/VoiceInputSettings.java
new file mode 100644
index 0000000..262f145
--- /dev/null
+++ b/src/com/android/settings/voice/VoiceInputSettings.java
@@ -0,0 +1,242 @@
+/*
+ * 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.voice;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.preference.Preference;
+import android.provider.Settings;
+import android.service.voice.VoiceInteractionService;
+import android.service.voice.VoiceInteractionServiceInfo;
+import android.speech.RecognitionService;
+import com.android.settings.R;
+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.settings.voice.VoiceInputPreference.RadioButtonGroupState;
+
+import android.os.Bundle;
+import android.preference.PreferenceCategory;
+import android.widget.Checkable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class VoiceInputSettings extends SettingsPreferenceFragment implements
+ Preference.OnPreferenceClickListener, RadioButtonGroupState, Indexable {
+
+ private static final String TAG = "VoiceInputSettings";
+ private static final boolean DBG = false;
+
+ /**
+ * Preference key for the engine selection preference.
+ */
+ private static final String KEY_SERVICE_PREFERENCE_SECTION =
+ "voice_service_preference_section";
+
+ private PreferenceCategory mServicePreferenceCategory;
+
+ private CharSequence mInteractorSummary;
+ private CharSequence mRecognizerSummary;
+ private CharSequence mInteractorWarning;
+
+ /**
+ * The currently selected engine.
+ */
+ private String mCurrentKey;
+
+ /**
+ * The engine checkbox that is currently checked. Saves us a bit of effort
+ * in deducing the right one from the currently selected engine.
+ */
+ private Checkable mCurrentChecked;
+
+ private VoiceInputHelper mHelper;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.voice_input_settings);
+
+ mServicePreferenceCategory = (PreferenceCategory) findPreference(
+ KEY_SERVICE_PREFERENCE_SECTION);
+
+ mInteractorSummary = getActivity().getText(
+ R.string.voice_interactor_preference_summary);
+ mRecognizerSummary = getActivity().getText(
+ R.string.voice_recognizer_preference_summary);
+ mInteractorWarning = getActivity().getText(R.string.voice_interaction_security_warning);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ initSettings();
+ }
+
+ private void initSettings() {
+ mHelper = new VoiceInputHelper(getActivity());
+ mHelper.buildUi();
+
+ mServicePreferenceCategory.removeAll();
+
+ if (mHelper.mCurrentVoiceInteraction != null) {
+ mCurrentKey = mHelper.mCurrentVoiceInteraction.flattenToShortString();
+ } else if (mHelper.mCurrentRecognizer != null) {
+ mCurrentKey = mHelper.mCurrentRecognizer.flattenToShortString();
+ } else {
+ mCurrentKey = null;
+ }
+
+ for (int i=0; i<mHelper.mAvailableInteractionInfos.size(); i++) {
+ VoiceInputHelper.InteractionInfo info = mHelper.mAvailableInteractionInfos.get(i);
+ VoiceInputPreference pref = new VoiceInputPreference(getActivity(), info,
+ mInteractorSummary, mInteractorWarning, this);
+ mServicePreferenceCategory.addPreference(pref);
+ }
+
+ for (int i=0; i<mHelper.mAvailableRecognizerInfos.size(); i++) {
+ VoiceInputHelper.RecognizerInfo info = mHelper.mAvailableRecognizerInfos.get(i);
+ VoiceInputPreference pref = new VoiceInputPreference(getActivity(), info,
+ mRecognizerSummary, null, this);
+ mServicePreferenceCategory.addPreference(pref);
+ }
+ }
+
+ @Override
+ public Checkable getCurrentChecked() {
+ return mCurrentChecked;
+ }
+
+ @Override
+ public String getCurrentKey() {
+ return mCurrentKey;
+ }
+
+ @Override
+ public void setCurrentChecked(Checkable current) {
+ mCurrentChecked = current;
+ }
+
+ @Override
+ public void setCurrentKey(String key) {
+ mCurrentKey = key;
+ for (int i=0; i<mHelper.mAvailableInteractionInfos.size(); i++) {
+ VoiceInputHelper.InteractionInfo info = mHelper.mAvailableInteractionInfos.get(i);
+ if (info.key.equals(key)) {
+ // Put the new value back into secure settings.
+ Settings.Secure.putString(getActivity().getContentResolver(),
+ Settings.Secure.VOICE_INTERACTION_SERVICE, key);
+ // Eventually we will require that an interactor always specify a recognizer
+ if (info.settings != null) {
+ Settings.Secure.putString(getActivity().getContentResolver(),
+ Settings.Secure.VOICE_RECOGNITION_SERVICE,
+ info.settings.flattenToShortString());
+ }
+ return;
+ }
+ }
+
+ for (int i=0; i<mHelper.mAvailableRecognizerInfos.size(); i++) {
+ VoiceInputHelper.RecognizerInfo info = mHelper.mAvailableRecognizerInfos.get(i);
+ if (info.key.equals(key)) {
+ Settings.Secure.putString(getActivity().getContentResolver(),
+ Settings.Secure.VOICE_INTERACTION_SERVICE, "");
+ Settings.Secure.putString(getActivity().getContentResolver(),
+ Settings.Secure.VOICE_RECOGNITION_SERVICE, key);
+ return;
+ }
+ }
+ }
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ if (preference instanceof VoiceInputPreference) {
+ ((VoiceInputPreference)preference).doClick();
+ }
+ return true;
+ }
+
+ // For Search
+ public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider() {
+
+ @Override
+ public List<SearchIndexableRaw> getRawDataToIndex(Context context,
+ boolean enabled) {
+
+ List<SearchIndexableRaw> indexables = new ArrayList<>();
+
+ final String screenTitle = context.getString(R.string.voice_input_settings_title);
+
+ SearchIndexableRaw indexable = new SearchIndexableRaw(context);
+ indexable.key = "voice_service_preference_section_title";
+ indexable.title = context.getString(R.string.voice_service_preference_section_title);
+ indexable.screenTitle = screenTitle;
+ indexables.add(indexable);
+
+ final List<ResolveInfo> voiceInteractions =
+ context.getPackageManager().queryIntentServices(
+ new Intent(VoiceInteractionService.SERVICE_INTERFACE),
+ PackageManager.GET_META_DATA);
+
+ final int countInteractions = voiceInteractions.size();
+ for (int i = 0; i < countInteractions; i++) {
+ ResolveInfo info = voiceInteractions.get(i);
+ VoiceInteractionServiceInfo visInfo = new VoiceInteractionServiceInfo(
+ context.getPackageManager(), info.serviceInfo);
+ if (visInfo.getParseError() != null) {
+ continue;
+ }
+ indexables.add(getSearchIndexableRaw(context, info, screenTitle));
+ }
+
+ final List<ResolveInfo> recognitions =
+ context.getPackageManager().queryIntentServices(
+ new Intent(RecognitionService.SERVICE_INTERFACE),
+ PackageManager.GET_META_DATA);
+
+ final int countRecognitions = recognitions.size();
+ for (int i = 0; i < countRecognitions; i++) {
+ ResolveInfo info = recognitions.get(i);
+ indexables.add(getSearchIndexableRaw(context, info, screenTitle));
+ }
+
+ return indexables;
+ }
+
+ private SearchIndexableRaw getSearchIndexableRaw(Context context,
+ ResolveInfo info, String screenTitle) {
+
+ ServiceInfo serviceInfo = info.serviceInfo;
+ ComponentName componentName = new ComponentName(serviceInfo.packageName,
+ serviceInfo.name);
+
+ SearchIndexableRaw indexable = new SearchIndexableRaw(context);
+ indexable.key = componentName.flattenToString();
+ indexable.title = info.loadLabel(context.getPackageManager()).toString();
+ indexable.screenTitle = screenTitle;
+
+ return indexable;
+ }
+ };
+}
diff --git a/src/com/android/settings/vpn2/VpnSettings.java b/src/com/android/settings/vpn2/VpnSettings.java
index 73aae99..7516392 100644
--- a/src/com/android/settings/vpn2/VpnSettings.java
+++ b/src/com/android/settings/vpn2/VpnSettings.java
@@ -29,8 +29,10 @@ import android.os.Handler;
import android.os.Message;
import android.os.ServiceManager;
import android.os.SystemProperties;
+import android.os.UserManager;
import android.preference.Preference;
import android.preference.PreferenceGroup;
+import android.preference.PreferenceScreen;
import android.security.Credentials;
import android.security.KeyStore;
import android.text.TextUtils;
@@ -43,8 +45,11 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView.AdapterContextMenuInfo;
+import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
+import android.widget.Spinner;
+import android.widget.TextView;
import android.widget.Toast;
import com.android.internal.net.LegacyVpnInfo;
@@ -80,14 +85,25 @@ public class VpnSettings extends SettingsPreferenceFragment implements
private Handler mUpdater;
private LegacyVpnInfo mInfo;
+ private UserManager mUm;
// The key of the profile for the current ContextMenu.
private String mSelectedKey;
+ private boolean mUnavailable;
+
@Override
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
+ mUm = (UserManager) getSystemService(Context.USER_SERVICE);
+
+ if (mUm.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN)) {
+ mUnavailable = true;
+ setPreferenceScreen(new PreferenceScreen(getActivity(), null));
+ return;
+ }
+
setHasOptionsMenu(true);
addPreferencesFromResource(R.xml.vpn_settings2);
@@ -156,6 +172,15 @@ public class VpnSettings extends SettingsPreferenceFragment implements
public void onResume() {
super.onResume();
+ if (mUnavailable) {
+ TextView emptyView = (TextView) getView().findViewById(android.R.id.empty);
+ getListView().setEmptyView(emptyView);
+ if (emptyView != null) {
+ emptyView.setText(R.string.vpn_settings_not_available);
+ }
+ return;
+ }
+
final boolean pickLockdown = getActivity()
.getIntent().getBooleanExtra(EXTRA_PICK_LOCKDOWN, false);
if (pickLockdown) {
@@ -214,6 +239,10 @@ public class VpnSettings extends SettingsPreferenceFragment implements
public void onPause() {
super.onPause();
+ if (mUnavailable) {
+ return;
+ }
+
// Hide the dialog if there is one.
if (mDialog != null) {
mDialog.setOnDismissListener(null);
@@ -463,7 +492,7 @@ public class VpnSettings extends SettingsPreferenceFragment implements
private static class TitleAdapter extends ArrayAdapter<CharSequence> {
public TitleAdapter(Context context, List<CharSequence> objects) {
- super(context, com.android.internal.R.layout.select_dialog_singlechoice_holo,
+ super(context, com.android.internal.R.layout.select_dialog_singlechoice_material,
android.R.id.text1, objects);
}
}
diff --git a/src/com/android/settings/wfd/WifiDisplaySettings.java b/src/com/android/settings/wfd/WifiDisplaySettings.java
index d7f4708..c3f22a7 100755
--- a/src/com/android/settings/wfd/WifiDisplaySettings.java
+++ b/src/com/android/settings/wfd/WifiDisplaySettings.java
@@ -42,15 +42,12 @@ import android.os.Looper;
import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
import android.preference.Preference;
-import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
import android.provider.Settings;
-import android.text.Html;
import android.util.Slog;
import android.util.TypedValue;
-import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -59,14 +56,11 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
-import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.ImageView;
-import android.widget.Switch;
import android.widget.TextView;
import com.android.internal.app.MediaRouteDialogPresenter;
-import com.android.settings.ProgressCategory;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
diff --git a/src/com/android/settings/widget/ChartDataUsageView.java b/src/com/android/settings/widget/ChartDataUsageView.java
index 4e16bfc..c20a8db 100644
--- a/src/com/android/settings/widget/ChartDataUsageView.java
+++ b/src/com/android/settings/widget/ChartDataUsageView.java
@@ -50,26 +50,24 @@ public class ChartDataUsageView extends ChartView {
private static final int MSG_UPDATE_AXIS = 100;
private static final long DELAY_MILLIS = 250;
- private static final boolean LIMIT_SWEEPS_TO_VALID_DATA = false;
-
private ChartGridView mGrid;
private ChartNetworkSeriesView mSeries;
private ChartNetworkSeriesView mDetailSeries;
private NetworkStatsHistory mHistory;
- private ChartSweepView mSweepLeft;
- private ChartSweepView mSweepRight;
private ChartSweepView mSweepWarning;
private ChartSweepView mSweepLimit;
+ private long mInspectStart;
+ private long mInspectEnd;
+
private Handler mHandler;
/** Current maximum value of {@link #mVert}. */
private long mVertMax;
public interface DataUsageChartListener {
- public void onInspectRangeChanged();
public void onWarningChanged();
public void onLimitChanged();
public void requestWarningEdit();
@@ -112,43 +110,27 @@ public class ChartDataUsageView extends ChartView {
mDetailSeries = (ChartNetworkSeriesView) findViewById(R.id.detail_series);
mDetailSeries.setVisibility(View.GONE);
- mSweepLeft = (ChartSweepView) findViewById(R.id.sweep_left);
- mSweepRight = (ChartSweepView) findViewById(R.id.sweep_right);
mSweepLimit = (ChartSweepView) findViewById(R.id.sweep_limit);
mSweepWarning = (ChartSweepView) findViewById(R.id.sweep_warning);
// prevent sweeps from crossing each other
- mSweepLeft.setValidRangeDynamic(null, mSweepRight);
- mSweepRight.setValidRangeDynamic(mSweepLeft, null);
mSweepWarning.setValidRangeDynamic(null, mSweepLimit);
mSweepLimit.setValidRangeDynamic(mSweepWarning, null);
// mark neighbors for checking touch events against
- mSweepLeft.setNeighbors(mSweepRight);
- mSweepRight.setNeighbors(mSweepLeft);
- mSweepLimit.setNeighbors(mSweepWarning, mSweepLeft, mSweepRight);
- mSweepWarning.setNeighbors(mSweepLimit, mSweepLeft, mSweepRight);
+ mSweepLimit.setNeighbors(mSweepWarning);
+ mSweepWarning.setNeighbors(mSweepLimit);
- mSweepLeft.addOnSweepListener(mHorizListener);
- mSweepRight.addOnSweepListener(mHorizListener);
mSweepWarning.addOnSweepListener(mVertListener);
mSweepLimit.addOnSweepListener(mVertListener);
mSweepWarning.setDragInterval(5 * MB_IN_BYTES);
mSweepLimit.setDragInterval(5 * MB_IN_BYTES);
- // TODO: make time sweeps adjustable through dpad
- mSweepLeft.setClickable(false);
- mSweepLeft.setFocusable(false);
- mSweepRight.setClickable(false);
- mSweepRight.setFocusable(false);
-
// tell everyone about our axis
mGrid.init(mHoriz, mVert);
mSeries.init(mHoriz, mVert);
mDetailSeries.init(mHoriz, mVert);
- mSweepLeft.init(mHoriz);
- mSweepRight.init(mHoriz);
mSweepWarning.init(mVert);
mSweepLimit.init(mVert);
@@ -194,7 +176,7 @@ public class ChartDataUsageView extends ChartView {
mSweepLimit.setEnabled(true);
mSweepLimit.setValue(policy.limitBytes);
} else {
- mSweepLimit.setVisibility(View.VISIBLE);
+ mSweepLimit.setVisibility(View.INVISIBLE);
mSweepLimit.setEnabled(false);
mSweepLimit.setValue(-1);
}
@@ -295,23 +277,6 @@ public class ChartDataUsageView extends ChartView {
mSeries.setEstimateVisible(estimateVisible);
}
- private OnSweepListener mHorizListener = new OnSweepListener() {
- @Override
- public void onSweep(ChartSweepView sweep, boolean sweepDone) {
- updatePrimaryRange();
-
- // update detail list only when done sweeping
- if (sweepDone && mListener != null) {
- mListener.onInspectRangeChanged();
- }
- }
-
- @Override
- public void requestEdit(ChartSweepView sweep) {
- // ignored
- }
- };
-
private void sendUpdateAxisDelayed(ChartSweepView sweep, boolean force) {
if (force || !mHandler.hasMessages(MSG_UPDATE_AXIS, sweep)) {
mHandler.sendMessageDelayed(
@@ -369,11 +334,11 @@ public class ChartDataUsageView extends ChartView {
}
public long getInspectStart() {
- return mSweepLeft.getValue();
+ return mInspectStart;
}
public long getInspectEnd() {
- return mSweepRight.getValue();
+ return mInspectEnd;
}
public long getWarningBytes() {
@@ -384,14 +349,6 @@ public class ChartDataUsageView extends ChartView {
return mSweepLimit.getLabelValue();
}
- private long getHistoryStart() {
- return mHistory != null ? mHistory.getStart() : Long.MAX_VALUE;
- }
-
- private long getHistoryEnd() {
- return mHistory != null ? mHistory.getEnd() : Long.MIN_VALUE;
- }
-
/**
* Set the exact time range that should be displayed, updating how
* {@link ChartNetworkSeriesView} paints. Moves inspection ranges to be the
@@ -403,30 +360,8 @@ public class ChartDataUsageView extends ChartView {
mSeries.setBounds(visibleStart, visibleEnd);
mDetailSeries.setBounds(visibleStart, visibleEnd);
- final long historyStart = getHistoryStart();
- final long historyEnd = getHistoryEnd();
-
- final long validStart = historyStart == Long.MAX_VALUE ? visibleStart
- : Math.max(visibleStart, historyStart);
- final long validEnd = historyEnd == Long.MIN_VALUE ? visibleEnd
- : Math.min(visibleEnd, historyEnd);
-
- if (LIMIT_SWEEPS_TO_VALID_DATA) {
- // prevent time sweeps from leaving valid data
- mSweepLeft.setValidRange(validStart, validEnd);
- mSweepRight.setValidRange(validStart, validEnd);
- } else {
- mSweepLeft.setValidRange(visibleStart, visibleEnd);
- mSweepRight.setValidRange(visibleStart, visibleEnd);
- }
-
- // default sweeps to last week of data
- final long halfRange = (visibleEnd + visibleStart) / 2;
- final long sweepMax = validEnd;
- final long sweepMin = Math.max(visibleStart, (sweepMax - DateUtils.WEEK_IN_MILLIS));
-
- mSweepLeft.setValue(sweepMin);
- mSweepRight.setValue(sweepMax);
+ mInspectStart = visibleStart;
+ mInspectEnd = visibleEnd;
requestLayout();
if (changed) {
@@ -440,15 +375,11 @@ public class ChartDataUsageView extends ChartView {
}
private void updatePrimaryRange() {
- final long left = mSweepLeft.getValue();
- final long right = mSweepRight.getValue();
-
// prefer showing primary range on detail series, when available
if (mDetailSeries.getVisibility() == View.VISIBLE) {
- mDetailSeries.setPrimaryRange(left, right);
- mSeries.setPrimaryRange(0, 0);
+ mSeries.setSecondary(true);
} else {
- mSeries.setPrimaryRange(left, right);
+ mSeries.setSecondary(false);
}
}
diff --git a/src/com/android/settings/widget/ChartGridView.java b/src/com/android/settings/widget/ChartGridView.java
index ec5882c..4cd6f5f 100644
--- a/src/com/android/settings/widget/ChartGridView.java
+++ b/src/com/android/settings/widget/ChartGridView.java
@@ -19,22 +19,25 @@ package com.android.settings.widget;
import static com.android.settings.DataUsageSummary.formatDateRange;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
+import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import com.android.internal.util.Preconditions;
import com.android.settings.R;
+import java.util.Locale;
+
/**
* Background of {@link ChartView} that renders grid lines as requested by
* {@link ChartAxis#getTickPoints()}.
@@ -47,10 +50,13 @@ public class ChartGridView extends View {
private Drawable mPrimary;
private Drawable mSecondary;
private Drawable mBorder;
+
+ private int mLabelSize;
private int mLabelColor;
- private Layout mLayoutStart;
- private Layout mLayoutEnd;
+ private Layout mLabelStart;
+ private Layout mLabelMid;
+ private Layout mLabelEnd;
public ChartGridView(Context context) {
this(context, null, 0);
@@ -71,7 +77,17 @@ public class ChartGridView extends View {
mPrimary = a.getDrawable(R.styleable.ChartGridView_primaryDrawable);
mSecondary = a.getDrawable(R.styleable.ChartGridView_secondaryDrawable);
mBorder = a.getDrawable(R.styleable.ChartGridView_borderDrawable);
- mLabelColor = a.getColor(R.styleable.ChartGridView_labelColor, Color.RED);
+
+ final int taId = a.getResourceId(R.styleable.ChartGridView_android_textAppearance, -1);
+ final TypedArray ta = context.obtainStyledAttributes(taId,
+ com.android.internal.R.styleable.TextAppearance);
+ mLabelSize = ta.getDimensionPixelSize(
+ com.android.internal.R.styleable.TextAppearance_textSize, 0);
+ ta.recycle();
+
+ final ColorStateList labelColor = a.getColorStateList(
+ R.styleable.ChartGridView_android_textColor);
+ mLabelColor = labelColor.getDefaultColor();
a.recycle();
}
@@ -83,71 +99,83 @@ public class ChartGridView extends View {
void setBounds(long start, long end) {
final Context context = getContext();
- mLayoutStart = makeLayout(formatDateRange(context, start, start));
- mLayoutEnd = makeLayout(formatDateRange(context, end, end));
+ final long mid = (start + end) / 2;
+ mLabelStart = makeLabel(formatDateRange(context, start, start));
+ mLabelMid = makeLabel(formatDateRange(context, mid, mid));
+ mLabelEnd = makeLabel(formatDateRange(context, end, end));
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
final int width = getWidth();
- final int height = getHeight();
+ final int height = getHeight() - getPaddingBottom();
final Drawable secondary = mSecondary;
- final int secondaryHeight = mSecondary.getIntrinsicHeight();
-
- final float[] vertTicks = mVert.getTickPoints();
- for (float y : vertTicks) {
- final int bottom = (int) Math.min(y + secondaryHeight, height);
- secondary.setBounds(0, (int) y, width, bottom);
- secondary.draw(canvas);
+ if (secondary != null) {
+ final int secondaryHeight = secondary.getIntrinsicHeight();
+
+ final float[] vertTicks = mVert.getTickPoints();
+ for (float y : vertTicks) {
+ final int bottom = (int) Math.min(y + secondaryHeight, height);
+ secondary.setBounds(0, (int) y, width, bottom);
+ secondary.draw(canvas);
+ }
}
final Drawable primary = mPrimary;
- final int primaryWidth = mPrimary.getIntrinsicWidth();
- final int primaryHeight = mPrimary.getIntrinsicHeight();
-
- final float[] horizTicks = mHoriz.getTickPoints();
- for (float x : horizTicks) {
- final int right = (int) Math.min(x + primaryWidth, width);
- primary.setBounds((int) x, 0, right, height);
- primary.draw(canvas);
+ if (primary != null) {
+ final int primaryWidth = primary.getIntrinsicWidth();
+ final int primaryHeight = primary.getIntrinsicHeight();
+
+ final float[] horizTicks = mHoriz.getTickPoints();
+ for (float x : horizTicks) {
+ final int right = (int) Math.min(x + primaryWidth, width);
+ primary.setBounds((int) x, 0, right, height);
+ primary.draw(canvas);
+ }
}
mBorder.setBounds(0, 0, width, height);
mBorder.draw(canvas);
- final int padding = mLayoutStart != null ? mLayoutStart.getHeight() / 8 : 0;
+ final int padding = mLabelStart != null ? mLabelStart.getHeight() / 8 : 0;
- final Layout start = mLayoutStart;
+ final Layout start = mLabelStart;
if (start != null) {
- canvas.save();
+ final int saveCount = canvas.save();
canvas.translate(0, height + padding);
start.draw(canvas);
- canvas.restore();
+ canvas.restoreToCount(saveCount);
+ }
+
+ final Layout mid = mLabelMid;
+ if (mid != null) {
+ final int saveCount = canvas.save();
+ canvas.translate((width - mid.getWidth()) / 2, height + padding);
+ mid.draw(canvas);
+ canvas.restoreToCount(saveCount);
}
- final Layout end = mLayoutEnd;
+ final Layout end = mLabelEnd;
if (end != null) {
- canvas.save();
+ final int saveCount = canvas.save();
canvas.translate(width - end.getWidth(), height + padding);
end.draw(canvas);
- canvas.restore();
+ canvas.restoreToCount(saveCount);
}
}
- private Layout makeLayout(CharSequence text) {
+ private Layout makeLabel(CharSequence text) {
final Resources res = getResources();
final TextPaint paint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
paint.density = res.getDisplayMetrics().density;
paint.setCompatibilityScaling(res.getCompatibilityInfo().applicationScale);
paint.setColor(mLabelColor);
- paint.setTextSize(
- TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10, res.getDisplayMetrics()));
+ paint.setTextSize(mLabelSize);
return new StaticLayout(text, paint,
(int) Math.ceil(Layout.getDesiredWidth(text, paint)),
Layout.Alignment.ALIGN_NORMAL, 1.f, 0, true);
}
-
}
diff --git a/src/com/android/settings/widget/ChartNetworkSeriesView.java b/src/com/android/settings/widget/ChartNetworkSeriesView.java
index 6250a25..7aaba66 100644
--- a/src/com/android/settings/widget/ChartNetworkSeriesView.java
+++ b/src/com/android/settings/widget/ChartNetworkSeriesView.java
@@ -60,17 +60,17 @@ public class ChartNetworkSeriesView extends View {
private Path mPathFill;
private Path mPathEstimate;
+ private int mSafeRegion;
+
private long mStart;
private long mEnd;
- private long mPrimaryLeft;
- private long mPrimaryRight;
-
/** Series will be extended to reach this end time. */
private long mEndTime = Long.MIN_VALUE;
private boolean mPathValid = false;
private boolean mEstimateVisible = false;
+ private boolean mSecondary = false;
private long mMax;
private long mMaxEstimate;
@@ -93,8 +93,11 @@ public class ChartNetworkSeriesView extends View {
final int fill = a.getColor(R.styleable.ChartNetworkSeriesView_fillColor, Color.RED);
final int fillSecondary = a.getColor(
R.styleable.ChartNetworkSeriesView_fillColorSecondary, Color.RED);
+ final int safeRegion = a.getDimensionPixelSize(
+ R.styleable.ChartNetworkSeriesView_safeRegion, 0);
setChartColor(stroke, fill, fillSecondary);
+ setSafeRegion(safeRegion);
setWillNotDraw(false);
a.recycle();
@@ -134,6 +137,10 @@ public class ChartNetworkSeriesView extends View {
mPaintEstimate.setPathEffect(new DashPathEffect(new float[] { 10, 10 }, 1));
}
+ public void setSafeRegion(int safeRegion) {
+ mSafeRegion = safeRegion;
+ }
+
public void bindNetworkStats(NetworkStatsHistory stats) {
mStats = stats;
invalidatePath();
@@ -145,14 +152,8 @@ public class ChartNetworkSeriesView extends View {
mEnd = end;
}
- /**
- * Set the range to paint with {@link #mPaintFill}, leaving the remaining
- * area to be painted with {@link #mPaintFillSecondary}.
- */
- public void setPrimaryRange(long left, long right) {
- mPrimaryLeft = left;
- mPrimaryRight = right;
- invalidate();
+ public void setSecondary(boolean secondary) {
+ mSecondary = secondary;
}
public void invalidatePath() {
@@ -322,9 +323,6 @@ public class ChartNetworkSeriesView extends View {
generatePath();
}
- final float primaryLeftPoint = mHoriz.convertToPoint(mPrimaryLeft);
- final float primaryRightPoint = mHoriz.convertToPoint(mPrimaryRight);
-
if (mEstimateVisible) {
save = canvas.save();
canvas.clipRect(0, 0, getWidth(), getHeight());
@@ -332,21 +330,11 @@ public class ChartNetworkSeriesView extends View {
canvas.restoreToCount(save);
}
- save = canvas.save();
- canvas.clipRect(0, 0, primaryLeftPoint, getHeight());
- canvas.drawPath(mPathFill, mPaintFillSecondary);
- canvas.restoreToCount(save);
+ final Paint paintFill = mSecondary ? mPaintFillSecondary : mPaintFill;
save = canvas.save();
- canvas.clipRect(primaryRightPoint, 0, getWidth(), getHeight());
- canvas.drawPath(mPathFill, mPaintFillSecondary);
+ canvas.clipRect(mSafeRegion, 0, getWidth(), getHeight() - mSafeRegion);
+ canvas.drawPath(mPathFill, paintFill);
canvas.restoreToCount(save);
-
- save = canvas.save();
- canvas.clipRect(primaryLeftPoint, 0, primaryRightPoint, getHeight());
- canvas.drawPath(mPathFill, mPaintFill);
- canvas.drawPath(mPathStroke, mPaintStroke);
- canvas.restoreToCount(save);
-
}
}
diff --git a/src/com/android/settings/widget/ChartSweepView.java b/src/com/android/settings/widget/ChartSweepView.java
index 774e5d8..04fc862 100644
--- a/src/com/android/settings/widget/ChartSweepView.java
+++ b/src/com/android/settings/widget/ChartSweepView.java
@@ -58,6 +58,7 @@ public class ChartSweepView extends View {
private Rect mMargins = new Rect();
private float mNeighborMargin;
+ private int mSafeRegion;
private int mFollowAxis;
@@ -125,6 +126,7 @@ public class ChartSweepView extends View {
setSweepDrawable(a.getDrawable(R.styleable.ChartSweepView_sweepDrawable));
setFollowAxis(a.getInt(R.styleable.ChartSweepView_followAxis, -1));
setNeighborMargin(a.getDimensionPixelSize(R.styleable.ChartSweepView_neighborMargin, 0));
+ setSafeRegion(a.getDimensionPixelSize(R.styleable.ChartSweepView_safeRegion, 0));
setLabelMinSize(a.getDimensionPixelSize(R.styleable.ChartSweepView_labelSize, 0));
setLabelTemplate(a.getResourceId(R.styleable.ChartSweepView_labelTemplate, 0));
@@ -259,7 +261,6 @@ public class ChartSweepView extends View {
paint.density = getResources().getDisplayMetrics().density;
paint.setCompatibilityScaling(getResources().getCompatibilityInfo().applicationScale);
paint.setColor(mLabelColor);
- paint.setShadowLayer(4 * paint.density, 0, 0, Color.BLACK);
mLabelTemplate = new SpannableStringBuilder(template);
mLabelLayout = new DynamicLayout(
@@ -383,6 +384,10 @@ public class ChartSweepView extends View {
mNeighborMargin = neighborMargin;
}
+ public void setSafeRegion(int safeRegion) {
+ mSafeRegion = safeRegion;
+ }
+
/**
* Set valid range this sweep can move within, defined by the given
* {@link ChartSweepView}. The most restrictive combination of all valid
@@ -709,7 +714,7 @@ public class ChartSweepView extends View {
mLabelLayout.draw(canvas);
}
canvas.restoreToCount(count);
- labelSize = (int) mLabelSize;
+ labelSize = (int) mLabelSize + mSafeRegion;
} else {
labelSize = 0;
}
diff --git a/src/com/android/settings/widget/ChartView.java b/src/com/android/settings/widget/ChartView.java
index 69e6e94..30284bc 100644
--- a/src/com/android/settings/widget/ChartView.java
+++ b/src/com/android/settings/widget/ChartView.java
@@ -112,12 +112,18 @@ public class ChartView extends FrameLayout {
parentRect.set(mContent);
- if (child instanceof ChartNetworkSeriesView || child instanceof ChartGridView) {
+ if (child instanceof ChartNetworkSeriesView) {
// series are always laid out to fill entire graph area
// TODO: handle scrolling for series larger than content area
Gravity.apply(params.gravity, width, height, parentRect, childRect);
child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
+ } else if (child instanceof ChartGridView) {
+ // Grid uses some extra room for labels
+ Gravity.apply(params.gravity, width, height, parentRect, childRect);
+ child.layout(childRect.left, childRect.top, childRect.right,
+ childRect.bottom + child.getPaddingBottom());
+
} else if (child instanceof ChartSweepView) {
layoutSweep((ChartSweepView) child, parentRect, childRect);
child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
@@ -154,5 +160,4 @@ public class ChartView extends FrameLayout {
parentRect, childRect);
}
}
-
}
diff --git a/src/com/android/settings/widget/PieChartView.java b/src/com/android/settings/widget/PieChartView.java
deleted file mode 100644
index 6070190..0000000
--- a/src/com/android/settings/widget/PieChartView.java
+++ /dev/null
@@ -1,244 +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.widget;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.Paint.Style;
-import android.graphics.Path;
-import android.graphics.Path.Direction;
-import android.graphics.RadialGradient;
-import android.graphics.RectF;
-import android.graphics.Shader.TileMode;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.View;
-
-import com.google.android.collect.Lists;
-
-import java.util.ArrayList;
-
-/**
- * Pie chart with multiple items.
- */
-public class PieChartView extends View {
- public static final String TAG = "PieChartView";
- public static final boolean LOGD = false;
-
- private static final boolean FILL_GRADIENT = false;
-
- private ArrayList<Slice> mSlices = Lists.newArrayList();
-
- private int mOriginAngle;
- private Matrix mMatrix = new Matrix();
-
- private Paint mPaintOutline = new Paint();
-
- private Path mPathSide = new Path();
- private Path mPathSideOutline = new Path();
-
- private Path mPathOutline = new Path();
-
- private int mSideWidth;
-
- public class Slice {
- public long value;
-
- public Path path = new Path();
- public Path pathSide = new Path();
- public Path pathOutline = new Path();
-
- public Paint paint;
-
- public Slice(long value, int color) {
- this.value = value;
- this.paint = buildFillPaint(color, getResources());
- }
- }
-
- public PieChartView(Context context) {
- this(context, null);
- }
-
- public PieChartView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public PieChartView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- mPaintOutline.setColor(Color.BLACK);
- mPaintOutline.setStyle(Style.STROKE);
- mPaintOutline.setStrokeWidth(3f * getResources().getDisplayMetrics().density);
- mPaintOutline.setAntiAlias(true);
-
- mSideWidth = (int) (20 * getResources().getDisplayMetrics().density);
-
- setWillNotDraw(false);
- }
-
- private static Paint buildFillPaint(int color, Resources res) {
- final Paint paint = new Paint();
-
- paint.setColor(color);
- paint.setStyle(Style.FILL_AND_STROKE);
- paint.setAntiAlias(true);
-
- if (FILL_GRADIENT) {
- final int width = (int) (280 * res.getDisplayMetrics().density);
- paint.setShader(new RadialGradient(0, 0, width, color, darken(color), TileMode.MIRROR));
- }
-
- return paint;
- }
-
- public void setOriginAngle(int originAngle) {
- mOriginAngle = originAngle;
- }
-
- public void addSlice(long value, int color) {
- mSlices.add(new Slice(value, color));
- }
-
- public void removeAllSlices() {
- mSlices.clear();
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- final float centerX = getWidth() / 2;
- final float centerY = getHeight() / 2;
-
- mMatrix.reset();
- mMatrix.postScale(0.665f, 0.95f, centerX, centerY);
- mMatrix.postRotate(-40, centerX, centerY);
-
- generatePath();
- }
-
- public void generatePath() {
- if (LOGD) Log.d(TAG, "generatePath()");
-
- long total = 0;
- for (Slice slice : mSlices) {
- slice.path.reset();
- slice.pathSide.reset();
- slice.pathOutline.reset();
- total += slice.value;
- }
-
- mPathSide.reset();
- mPathSideOutline.reset();
- mPathOutline.reset();
-
- // bail when not enough stats to render
- if (total == 0) {
- invalidate();
- return;
- }
-
- final int width = getWidth();
- final int height = getHeight();
-
- final RectF rect = new RectF(0, 0, width, height);
- final RectF rectSide = new RectF();
- rectSide.set(rect);
- rectSide.offset(-mSideWidth, 0);
-
- mPathSide.addOval(rectSide, Direction.CW);
- mPathSideOutline.addOval(rectSide, Direction.CW);
- mPathOutline.addOval(rect, Direction.CW);
-
- int startAngle = mOriginAngle;
- for (Slice slice : mSlices) {
- final int sweepAngle = (int) (slice.value * 360 / total);
- final int endAngle = startAngle + sweepAngle;
-
- final float startAngleMod = startAngle % 360;
- final float endAngleMod = endAngle % 360;
- final boolean startSideVisible = startAngleMod > 90 && startAngleMod < 270;
- final boolean endSideVisible = endAngleMod > 90 && endAngleMod < 270;
-
- // draw slice
- slice.path.moveTo(rect.centerX(), rect.centerY());
- slice.path.arcTo(rect, startAngle, sweepAngle);
- slice.path.lineTo(rect.centerX(), rect.centerY());
-
- if (startSideVisible || endSideVisible) {
-
- // when start is beyond horizon, push until visible
- final float startAngleSide = startSideVisible ? startAngle : 450;
- final float endAngleSide = endSideVisible ? endAngle : 270;
- final float sweepAngleSide = endAngleSide - startAngleSide;
-
- // draw slice side
- slice.pathSide.moveTo(rect.centerX(), rect.centerY());
- slice.pathSide.arcTo(rect, startAngleSide, 0);
- slice.pathSide.rLineTo(-mSideWidth, 0);
- slice.pathSide.arcTo(rectSide, startAngleSide, sweepAngleSide);
- slice.pathSide.rLineTo(mSideWidth, 0);
- slice.pathSide.arcTo(rect, endAngleSide, -sweepAngleSide);
- }
-
- // draw slice outline
- slice.pathOutline.moveTo(rect.centerX(), rect.centerY());
- slice.pathOutline.arcTo(rect, startAngle, 0);
- if (startSideVisible) {
- slice.pathOutline.rLineTo(-mSideWidth, 0);
- }
- slice.pathOutline.moveTo(rect.centerX(), rect.centerY());
- slice.pathOutline.arcTo(rect, startAngle + sweepAngle, 0);
- if (endSideVisible) {
- slice.pathOutline.rLineTo(-mSideWidth, 0);
- }
-
- startAngle += sweepAngle;
- }
-
- invalidate();
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
-
- canvas.concat(mMatrix);
-
- for (Slice slice : mSlices) {
- canvas.drawPath(slice.pathSide, slice.paint);
- }
- canvas.drawPath(mPathSideOutline, mPaintOutline);
-
- for (Slice slice : mSlices) {
- canvas.drawPath(slice.path, slice.paint);
- canvas.drawPath(slice.pathOutline, mPaintOutline);
- }
- canvas.drawPath(mPathOutline, mPaintOutline);
- }
-
- public static int darken(int color) {
- float[] hsv = new float[3];
- Color.colorToHSV(color, hsv);
- hsv[2] /= 2;
- hsv[1] /= 2;
- return Color.HSVToColor(hsv);
- }
-
-}
diff --git a/src/com/android/settings/widget/SetupWizardIllustration.java b/src/com/android/settings/widget/SetupWizardIllustration.java
new file mode 100644
index 0000000..717ec35
--- /dev/null
+++ b/src/com/android/settings/widget/SetupWizardIllustration.java
@@ -0,0 +1,161 @@
+/*
+ * 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.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.ViewOutlineProvider;
+import android.widget.FrameLayout;
+
+import com.android.settings.R;
+
+/**
+ * Class to draw the illustration of setup wizard. The aspectRatio attribute determines the aspect
+ * ratio of the top padding, which is leaving space for the illustration. Draws an illustration
+ * (foreground) to fit the width of the view and fills the rest with the background.
+ *
+ * Copied from com.google.android.setupwizard.util.SetupWizardIllustration
+ */
+public class SetupWizardIllustration extends FrameLayout {
+
+ private static final String TAG = "SetupWizardIllustration";
+
+ // Size of the baseline grid in pixels
+ private float mBaselineGridSize;
+ private Drawable mBackground;
+ private Drawable mForeground;
+ private final Rect mViewBounds = new Rect();
+ private final Rect mForegroundBounds = new Rect();
+ private float mScale = 1.0f;
+ private float mAspectRatio = 0.0f;
+
+ public SetupWizardIllustration(Context context) {
+ this(context, null);
+ }
+
+ public SetupWizardIllustration(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public SetupWizardIllustration(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public SetupWizardIllustration(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ if (attrs != null) {
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.SetupWizardIllustration, 0, 0);
+ mAspectRatio = a.getFloat(R.styleable.SetupWizardIllustration_aspectRatio, 0.0f);
+ a.recycle();
+ }
+ // Number of pixels of the 8dp baseline grid as defined in material design specs
+ mBaselineGridSize = getResources().getDisplayMetrics().density * 8;
+ setWillNotDraw(false);
+ }
+
+ /**
+ * The background will be drawn to fill up the rest of the view. It will also be scaled by the
+ * same amount as the foreground so their textures look the same.
+ */
+ @Override
+ public void setBackground(Drawable background) {
+ mBackground = background;
+ }
+
+ /**
+ * Sets the drawable used as the illustration. THe drawable is expected to have intrinsic
+ * width and height defined and will be scaled to fit the width of the view.
+ */
+ @Override
+ public void setForeground(Drawable foreground) {
+ mForeground = foreground;
+ }
+
+ @Override
+ public void onResolveDrawables(int layoutDirection) {
+ mBackground.setLayoutDirection(layoutDirection);
+ mForeground.setLayoutDirection(layoutDirection);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (mAspectRatio != 0.0f) {
+ int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
+ int illustrationHeight = (int) (parentWidth / mAspectRatio);
+ illustrationHeight -= illustrationHeight % mBaselineGridSize;
+ setPaddingRelative(0, illustrationHeight, 0, 0);
+ }
+ setOutlineProvider(ViewOutlineProvider.BOUNDS);
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ final int layoutWidth = right - left;
+ final int layoutHeight = bottom - top;
+ if (mForeground != null) {
+ int intrinsicWidth = mForeground.getIntrinsicWidth();
+ int intrinsicHeight = mForeground.getIntrinsicHeight();
+ final int layoutDirection = getLayoutDirection();
+
+ mViewBounds.set(0, 0, layoutWidth, layoutHeight);
+ if (mAspectRatio != 0f) {
+ mScale = layoutWidth / (float) intrinsicWidth;
+ intrinsicWidth = layoutWidth;
+ intrinsicHeight = (int) (intrinsicHeight * mScale);
+ }
+ Gravity.apply(Gravity.FILL_HORIZONTAL | Gravity.TOP, intrinsicWidth, intrinsicHeight,
+ mViewBounds, mForegroundBounds, layoutDirection);
+ mForeground.setBounds(mForegroundBounds);
+ }
+ if (mBackground != null) {
+ // Scale the bounds by mScale to compensate for the scale done to the canvas before
+ // drawing.
+ mBackground.setBounds(0, 0, (int) Math.ceil(layoutWidth / mScale),
+ (int) Math.ceil((layoutHeight - mForegroundBounds.height()) / mScale));
+ }
+ super.onLayout(changed, left, top, right, bottom);
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ if (mBackground != null) {
+ canvas.save();
+ // Draw the background filling parts not covered by the illustration
+ canvas.translate(0, mForegroundBounds.height());
+ // Scale the background so its size matches the foreground
+ canvas.scale(mScale, mScale, 0, 0);
+ mBackground.draw(canvas);
+ canvas.restore();
+ }
+ if (mForeground != null) {
+ canvas.save();
+ // Draw the illustration
+ mForeground.draw(canvas);
+ canvas.restore();
+ }
+ super.onDraw(canvas);
+ }
+}
diff --git a/src/com/android/settings/widget/StickyHeaderListView.java b/src/com/android/settings/widget/StickyHeaderListView.java
new file mode 100644
index 0000000..3d9f158
--- /dev/null
+++ b/src/com/android/settings/widget/StickyHeaderListView.java
@@ -0,0 +1,139 @@
+/*
+ * 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.widget;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.WindowInsets;
+import android.widget.ListView;
+
+/**
+ * This class provides sticky header functionality in a list view, to use with
+ * SetupWizardIllustration. To use this, add a header tagged with "sticky", or a header tagged with
+ * "stickyContainer" and one of its child tagged as "sticky". The sticky container will be drawn
+ * when the sticky element hits the top of the view.
+ *
+ * There are a few things to note:
+ * 1. The two supported scenarios are StickyHeaderListView -> Header (stickyContainer) -> sticky,
+ * and StickyHeaderListView -> Header (sticky). The arrow (->) represents parent/child
+ * relationship and must be immediate child.
+ * 2. The view does not work well with padding. b/16190933
+ * 3. If fitsSystemWindows is true, then this will offset the sticking position by the height of
+ * the system decorations at the top of the screen.
+ *
+ * @see SetupWizardIllustration
+ * @see com.google.android.setupwizard.util.StickyHeaderScrollView
+ *
+ * Copied from com.google.android.setupwizard.util.StickyHeaderListView
+ */
+public class StickyHeaderListView extends ListView {
+
+ private View mSticky;
+ private View mStickyContainer;
+ private boolean mDrawScrollBar;
+ private int mStatusBarInset = 0;
+ private RectF mStickyRect = new RectF();
+
+ public StickyHeaderListView(Context context) {
+ super(context);
+ }
+
+ public StickyHeaderListView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public StickyHeaderListView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public StickyHeaderListView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+ if (mSticky == null) {
+ updateStickyView();
+ }
+ }
+
+ public void updateStickyView() {
+ mSticky = findViewWithTag("sticky");
+ mStickyContainer = findViewWithTag("stickyContainer");
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ if (mStickyRect.contains(ev.getX(), ev.getY())) {
+ ev.offsetLocation(-mStickyRect.left, -mStickyRect.top);
+ return mStickyContainer.dispatchTouchEvent(ev);
+ } else {
+ return super.dispatchTouchEvent(ev);
+ }
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ mDrawScrollBar = false;
+ super.draw(canvas);
+ if (mSticky != null) {
+ final int saveCount = canvas.save();
+ // The view to draw when sticking to the top
+ final View drawTarget = mStickyContainer != null ? mStickyContainer : mSticky;
+ // The offset to draw the view at when sticky
+ final int drawOffset = mStickyContainer != null ? mSticky.getTop() : 0;
+ // Position of the draw target, relative to the outside of the scrollView
+ final int drawTop = drawTarget.getTop();
+ if (drawTop + drawOffset < mStatusBarInset || !drawTarget.isShown()) {
+ // ListView does not translate the canvas, so we can simply draw at the top
+ canvas.translate(0, -drawOffset + mStatusBarInset);
+ canvas.clipRect(0, 0, drawTarget.getWidth(), drawTarget.getHeight());
+ drawTarget.draw(canvas);
+ mStickyRect.set(0, -drawOffset + mStatusBarInset, drawTarget.getWidth(),
+ drawTarget.getHeight() - drawOffset + mStatusBarInset);
+ } else {
+ mStickyRect.setEmpty();
+ }
+ canvas.restoreToCount(saveCount);
+ }
+ // Draw the scrollbars last so they are on top of the header
+ mDrawScrollBar = true;
+ onDrawScrollBars(canvas);
+ }
+
+ @Override
+ protected boolean isVerticalScrollBarHidden() {
+ return super.isVerticalScrollBarHidden() || !mDrawScrollBar;
+ }
+
+ @Override
+ public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+ if (getFitsSystemWindows()) {
+ mStatusBarInset = insets.getSystemWindowInsetTop();
+ insets.consumeSystemWindowInsets(false, true, false, false);
+ }
+ return insets;
+ }
+}
diff --git a/src/com/android/settings/widget/SwitchBar.java b/src/com/android/settings/widget/SwitchBar.java
new file mode 100644
index 0000000..c15ac41
--- /dev/null
+++ b/src/com/android/settings/widget/SwitchBar.java
@@ -0,0 +1,251 @@
+/*
+ * 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.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CompoundButton;
+import android.widget.LinearLayout;
+
+import android.widget.Switch;
+import android.widget.TextView;
+import com.android.settings.R;
+
+import java.util.ArrayList;
+
+public class SwitchBar extends LinearLayout implements CompoundButton.OnCheckedChangeListener,
+ View.OnClickListener {
+
+ public static interface OnSwitchChangeListener {
+ /**
+ * Called when the checked state of the Switch has changed.
+ *
+ * @param switchView The Switch view whose state has changed.
+ * @param isChecked The new checked state of switchView.
+ */
+ void onSwitchChanged(Switch switchView, boolean isChecked);
+ }
+
+ private ToggleSwitch mSwitch;
+ private TextView mTextView;
+
+ private ArrayList<OnSwitchChangeListener> mSwitchChangeListeners =
+ new ArrayList<OnSwitchChangeListener>();
+
+ private static int[] MARGIN_ATTRIBUTES = {
+ R.attr.switchBarMarginStart, R.attr.switchBarMarginEnd};
+
+ public SwitchBar(Context context) {
+ this(context, null);
+ }
+
+ public SwitchBar(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public SwitchBar(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public SwitchBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ LayoutInflater.from(context).inflate(R.layout.switch_bar, this);
+
+ final TypedArray a = context.obtainStyledAttributes(attrs, MARGIN_ATTRIBUTES);
+ int switchBarMarginStart = (int) a.getDimension(0, 0);
+ int switchBarMarginEnd = (int) a.getDimension(1, 0);
+ a.recycle();
+
+ mTextView = (TextView) findViewById(R.id.switch_text);
+ mTextView.setText(R.string.switch_off_text);
+ ViewGroup.MarginLayoutParams lp = (MarginLayoutParams) mTextView.getLayoutParams();
+ lp.setMarginStart(switchBarMarginStart);
+
+ mSwitch = (ToggleSwitch) findViewById(R.id.switch_widget);
+ // Prevent onSaveInstanceState() to be called as we are managing the state of the Switch
+ // on our own
+ mSwitch.setSaveEnabled(false);
+ lp = (MarginLayoutParams) mSwitch.getLayoutParams();
+ lp.setMarginEnd(switchBarMarginEnd);
+
+ addOnSwitchChangeListener(new OnSwitchChangeListener() {
+ @Override
+ public void onSwitchChanged(Switch switchView, boolean isChecked) {
+ setTextViewLabel(isChecked);
+ }
+ });
+
+ setOnClickListener(this);
+
+ // Default is hide
+ setVisibility(View.GONE);
+ }
+
+ public void setTextViewLabel(boolean isChecked) {
+ mTextView.setText(isChecked ? R.string.switch_on_text : R.string.switch_off_text);
+ }
+
+ public void setChecked(boolean checked) {
+ setTextViewLabel(checked);
+ mSwitch.setChecked(checked);
+ }
+
+ public void setCheckedInternal(boolean checked) {
+ setTextViewLabel(checked);
+ mSwitch.setCheckedInternal(checked);
+ }
+
+ public boolean isChecked() {
+ return mSwitch.isChecked();
+ }
+
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ mTextView.setEnabled(enabled);
+ mSwitch.setEnabled(enabled);
+ }
+
+ public final ToggleSwitch getSwitch() {
+ return mSwitch;
+ }
+
+ public void show() {
+ if (!isShowing()) {
+ setVisibility(View.VISIBLE);
+ mSwitch.setOnCheckedChangeListener(this);
+ }
+ }
+
+ public void hide() {
+ if (isShowing()) {
+ setVisibility(View.GONE);
+ mSwitch.setOnCheckedChangeListener(null);
+ }
+ }
+
+ public boolean isShowing() {
+ return (getVisibility() == View.VISIBLE);
+ }
+
+ @Override
+ public void onClick(View v) {
+ final boolean isChecked = !mSwitch.isChecked();
+ setChecked(isChecked);
+ }
+
+ public void propagateChecked(boolean isChecked) {
+ final int count = mSwitchChangeListeners.size();
+ for (int n = 0; n < count; n++) {
+ mSwitchChangeListeners.get(n).onSwitchChanged(mSwitch, isChecked);
+ }
+ }
+
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ propagateChecked(isChecked);
+ }
+
+ public void addOnSwitchChangeListener(OnSwitchChangeListener listener) {
+ if (mSwitchChangeListeners.contains(listener)) {
+ throw new IllegalStateException("Cannot add twice the same OnSwitchChangeListener");
+ }
+ mSwitchChangeListeners.add(listener);
+ }
+
+ public void removeOnSwitchChangeListener(OnSwitchChangeListener listener) {
+ if (!mSwitchChangeListeners.contains(listener)) {
+ throw new IllegalStateException("Cannot remove OnSwitchChangeListener");
+ }
+ mSwitchChangeListeners.remove(listener);
+ }
+
+ static class SavedState extends BaseSavedState {
+ boolean checked;
+ boolean visible;
+
+ SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ /**
+ * Constructor called from {@link #CREATOR}
+ */
+ private SavedState(Parcel in) {
+ super(in);
+ checked = (Boolean)in.readValue(null);
+ visible = (Boolean)in.readValue(null);
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeValue(checked);
+ out.writeValue(visible);
+ }
+
+ @Override
+ public String toString() {
+ return "SwitchBar.SavedState{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " checked=" + checked
+ + " visible=" + visible + "}";
+ }
+
+ 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];
+ }
+ };
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState() {
+ Parcelable superState = super.onSaveInstanceState();
+
+ SavedState ss = new SavedState(superState);
+ ss.checked = mSwitch.isChecked();
+ ss.visible = isShowing();
+ return ss;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ SavedState ss = (SavedState) state;
+
+ super.onRestoreInstanceState(ss.getSuperState());
+
+ mSwitch.setCheckedInternal(ss.checked);
+ setTextViewLabel(ss.checked);
+ setVisibility(ss.visible ? View.VISIBLE : View.GONE);
+ mSwitch.setOnCheckedChangeListener(ss.visible ? this : null);
+
+ requestLayout();
+ }
+}
diff --git a/src/com/android/settings/accessibility/ToggleSwitch.java b/src/com/android/settings/widget/ToggleSwitch.java
index e7c39e4..8232ff1 100644
--- a/src/com/android/settings/accessibility/ToggleSwitch.java
+++ b/src/com/android/settings/widget/ToggleSwitch.java
@@ -14,12 +14,14 @@
* limitations under the License.
*/
-package com.android.settings.accessibility;
+package com.android.settings.widget;
import android.content.Context;
+import android.util.AttributeSet;
import android.widget.Switch;
public class ToggleSwitch extends Switch {
+
private ToggleSwitch.OnBeforeCheckedChangeListener mOnBeforeListener;
public static interface OnBeforeCheckedChangeListener {
@@ -30,6 +32,18 @@ public class ToggleSwitch extends Switch {
super(context);
}
+ public ToggleSwitch(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public ToggleSwitch(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public ToggleSwitch(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
public void setOnBeforeCheckedChangeListener(OnBeforeCheckedChangeListener listener) {
mOnBeforeListener = listener;
}
diff --git a/src/com/android/settings/wifi/AccessPoint.java b/src/com/android/settings/wifi/AccessPoint.java
index c4d1f7c..8e71819 100644
--- a/src/com/android/settings/wifi/AccessPoint.java
+++ b/src/com/android/settings/wifi/AccessPoint.java
@@ -19,6 +19,8 @@ package com.android.settings.wifi;
import com.android.settings.R;
import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.StateListDrawable;
import android.net.NetworkInfo.DetailedState;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
@@ -28,12 +30,46 @@ import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.preference.Preference;
import android.util.Log;
+import android.util.LruCache;
import android.view.View;
import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.util.Map;
+
class AccessPoint extends Preference {
static final String TAG = "Settings.AccessPoint";
+ /**
+ * Lower bound on the 2.4 GHz (802.11b/g/n) WLAN channels
+ */
+ public static final int LOWER_FREQ_24GHZ = 2400;
+
+ /**
+ * Upper bound on the 2.4 GHz (802.11b/g/n) WLAN channels
+ */
+ public static final int HIGHER_FREQ_24GHZ = 2500;
+
+ /**
+ * Lower bound on the 5.0 GHz (802.11a/h/j/n/ac) WLAN channels
+ */
+ public static final int LOWER_FREQ_5GHZ = 4900;
+
+ /**
+ * Upper bound on the 5.0 GHz (802.11a/h/j/n/ac) WLAN channels
+ */
+ public static final int HIGHER_FREQ_5GHZ = 5900;
+
+ /**
+ * Experimental: we should be able to show the user the list of BSSIDs and bands
+ * for that SSID.
+ * For now this data is used only with Verbose Logging so as to show the band and number
+ * of BSSIDs on which that network is seen.
+ */
+ public LruCache<String, ScanResult> mScanResultCache;
+
+
private static final String KEY_DETAILEDSTATE = "key_detailedstate";
private static final String KEY_WIFIINFO = "key_wifiinfo";
private static final String KEY_SCANRESULT = "key_scanresult";
@@ -44,7 +80,11 @@ class AccessPoint extends Preference {
};
private static final int[] STATE_NONE = {};
- /** These values are matched in string arrays -- changes must be kept in sync */
+ private static int[] wifi_signal_attributes = { R.attr.wifi_signal };
+
+ /**
+ * These values are matched in string arrays -- changes must be kept in sync
+ */
static final int SECURITY_NONE = 0;
static final int SECURITY_WEP = 1;
static final int SECURITY_PSK = 2;
@@ -60,18 +100,25 @@ class AccessPoint extends Preference {
String ssid;
String bssid;
int security;
- int networkId;
+ int networkId = -1;
boolean wpsAvailable = false;
+ boolean showSummary = true;
PskType pskType = PskType.UNKNOWN;
private WifiConfiguration mConfig;
/* package */ScanResult mScanResult;
- private int mRssi;
+ private int mRssi = Integer.MAX_VALUE;
+ private long mSeen = 0;
+
private WifiInfo mInfo;
private DetailedState mState;
+ private static final int VISIBILITY_MAX_AGE_IN_MILLI = 1000000;
+ private static final int VISIBILITY_OUTDATED_AGE_IN_MILLI = 20000;
+ private static final int SECOND_TO_MILLI = 1000;
+
static int getSecurity(WifiConfiguration config) {
if (config.allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
return SECURITY_PSK;
@@ -142,21 +189,18 @@ class AccessPoint extends Preference {
AccessPoint(Context context, WifiConfiguration config) {
super(context);
- setWidgetLayoutResource(R.layout.preference_widget_wifi_signal);
loadConfig(config);
refresh();
}
AccessPoint(Context context, ScanResult result) {
super(context);
- setWidgetLayoutResource(R.layout.preference_widget_wifi_signal);
loadResult(result);
refresh();
}
AccessPoint(Context context, Bundle savedState) {
super(context);
- setWidgetLayoutResource(R.layout.preference_widget_wifi_signal);
mConfig = savedState.getParcelable(KEY_CONFIG);
if (mConfig != null) {
@@ -187,7 +231,6 @@ class AccessPoint extends Preference {
bssid = config.BSSID;
security = getSecurity(config);
networkId = config.networkId;
- mRssi = Integer.MAX_VALUE;
mConfig = config;
}
@@ -198,23 +241,48 @@ class AccessPoint extends Preference {
wpsAvailable = security != SECURITY_EAP && result.capabilities.contains("WPS");
if (security == SECURITY_PSK)
pskType = getPskType(result);
- networkId = -1;
mRssi = result.level;
mScanResult = result;
+ if (result.seen > mSeen) {
+ mSeen = result.seen;
+ }
}
@Override
protected void onBindView(View view) {
super.onBindView(view);
- ImageView signal = (ImageView) view.findViewById(R.id.signal);
- if (mRssi == Integer.MAX_VALUE) {
- signal.setImageDrawable(null);
+ updateIcon(getLevel(), getContext());
+
+ final TextView summaryView = (TextView) view.findViewById(
+ com.android.internal.R.id.summary);
+ summaryView.setVisibility(showSummary ? View.VISIBLE : View.GONE);
+
+ notifyChanged();
+ }
+
+ protected void updateIcon(int level, Context context) {
+ if (level == -1) {
+ setIcon(null);
} else {
- signal.setImageLevel(getLevel());
- signal.setImageDrawable(getContext().getTheme().obtainStyledAttributes(
- new int[] {R.attr.wifi_signal}).getDrawable(0));
- signal.setImageState((security != SECURITY_NONE) ?
- STATE_SECURED : STATE_NONE, true);
+ Drawable drawable = getIcon();
+
+ if (drawable == null) {
+ // To avoid a drawing race condition, we first set the state (SECURE/NONE) and then
+ // set the icon (drawable) to that state's drawable.
+ StateListDrawable sld = (StateListDrawable) context.getTheme()
+ .obtainStyledAttributes(wifi_signal_attributes).getDrawable(0);
+ // If sld is null then we are indexing and therefore do not have access to
+ // (nor need to display) the drawable.
+ if (sld != null) {
+ sld.setState((security != SECURITY_NONE) ? STATE_SECURED : STATE_NONE);
+ drawable = sld.getCurrent();
+ setIcon(drawable);
+ }
+ }
+
+ if (drawable != null) {
+ drawable.setLevel(level);
+ }
}
}
@@ -231,6 +299,7 @@ class AccessPoint extends Preference {
// Reachable one goes before unreachable one.
if (mRssi != Integer.MAX_VALUE && other.mRssi == Integer.MAX_VALUE) return -1;
if (mRssi == Integer.MAX_VALUE && other.mRssi != Integer.MAX_VALUE) return 1;
+ if (mRssi == Integer.MAX_VALUE && other.mRssi != Integer.MAX_VALUE) return 1;
// Configured one goes before unconfigured one.
if (networkId != WifiConfiguration.INVALID_NETWORK_ID
@@ -264,6 +333,16 @@ class AccessPoint extends Preference {
}
boolean update(ScanResult result) {
+ if (result.seen > mSeen) {
+ mSeen = result.seen;
+ }
+ if (WifiSettings.mVerboseLogging > 0) {
+ if (mScanResultCache == null) {
+ mScanResultCache = new LruCache<String, ScanResult>(32);
+ }
+ mScanResultCache.put(result.BSSID, result);
+ }
+
if (ssid.equals(result.SSID) && security == getSecurity(result)) {
if (WifiManager.compareSignalLevel(result.level, mRssi) > 0) {
int oldLevel = getLevel();
@@ -276,6 +355,7 @@ class AccessPoint extends Preference {
if (security == SECURITY_PSK) {
pskType = getPskType(result);
}
+ mScanResult = result;
refresh();
return true;
}
@@ -334,51 +414,241 @@ class AccessPoint extends Preference {
return "\"" + string + "\"";
}
- /** Updates the title and summary; may indirectly call notifyChanged() */
+ /**
+ * Shows or Hides the Summary of an AccessPoint.
+ *
+ * @param showSummary true will show the summary, false will hide the summary
+ */
+ public void setShowSummary(boolean showSummary){
+ this.showSummary = showSummary;
+ }
+
+ /**
+ * Returns the visibility status of the WifiConfiguration.
+ *
+ * @return autojoin debugging information
+ * TODO: use a string formatter
+ * ["rssi 5Ghz", "num results on 5GHz" / "rssi 5Ghz", "num results on 5GHz"]
+ * For instance [-40,5/-30,2]
+ */
+ private String getVisibilityStatus() {
+ StringBuilder visibility = new StringBuilder();
+ StringBuilder scans24GHz = null;
+ StringBuilder scans5GHz = null;
+ String bssid = null;
+
+ long now = System.currentTimeMillis();
+
+ if (mInfo != null) {
+ bssid = mInfo.getBSSID();
+ if (bssid != null) {
+ visibility.append(" ").append(bssid);
+ }
+ visibility.append(" score=").append(mInfo.score);
+ visibility.append(" ");
+ visibility.append(String.format("tx=%.1f,", mInfo.txSuccessRate));
+ visibility.append(String.format("%.1f,", mInfo.txRetriesRate));
+ visibility.append(String.format("%.1f ", mInfo.txBadRate));
+ visibility.append(String.format("rx=%.1f", mInfo.rxSuccessRate));
+ }
+
+ if (mScanResultCache != null) {
+ int rssi5 = WifiConfiguration.INVALID_RSSI;
+ int rssi24 = WifiConfiguration.INVALID_RSSI;
+ int num5 = 0;
+ int num24 = 0;
+ int numBlackListed = 0;
+ int n24 = 0; // Number scan results we included in the string
+ int n5 = 0; // Number scan results we included in the string
+ Map<String, ScanResult> list = mScanResultCache.snapshot();
+ // TODO: sort list by RSSI or age
+ for (ScanResult result : list.values()) {
+ if (result.seen == 0)
+ continue;
+
+ if (result.autoJoinStatus != ScanResult.ENABLED) numBlackListed++;
+
+ if (result.frequency >= LOWER_FREQ_5GHZ
+ && result.frequency <= HIGHER_FREQ_5GHZ) {
+ // Strictly speaking: [4915, 5825]
+ // number of known BSSID on 5GHz band
+ num5 = num5 + 1;
+ } else if (result.frequency >= LOWER_FREQ_24GHZ
+ && result.frequency <= HIGHER_FREQ_24GHZ) {
+ // Strictly speaking: [2412, 2482]
+ // number of known BSSID on 2.4Ghz band
+ num24 = num24 + 1;
+ }
+
+ // Ignore results seen, older than 20 seconds
+ if (now - result.seen > VISIBILITY_OUTDATED_AGE_IN_MILLI) continue;
+
+ if (result.frequency >= LOWER_FREQ_5GHZ
+ && result.frequency <= HIGHER_FREQ_5GHZ) {
+ if (result.level > rssi5) {
+ rssi5 = result.level;
+ }
+ if (n5 < 4) {
+ if (scans5GHz == null) scans5GHz = new StringBuilder();
+ scans5GHz.append(" {").append(result.BSSID);
+ if (bssid != null && result.BSSID.equals(bssid)) scans5GHz.append("*");
+ scans5GHz.append("=").append(result.frequency);
+ scans5GHz.append(",").append(result.level);
+ if (result.autoJoinStatus != 0) {
+ scans5GHz.append(",st=").append(result.autoJoinStatus);
+ }
+ if (result.numIpConfigFailures != 0) {
+ scans5GHz.append(",ipf=").append(result.numIpConfigFailures);
+ }
+ scans5GHz.append("}");
+ n5++;
+ }
+ } else if (result.frequency >= LOWER_FREQ_24GHZ
+ && result.frequency <= HIGHER_FREQ_24GHZ) {
+ if (result.level > rssi24) {
+ rssi24 = result.level;
+ }
+ if (n24 < 4) {
+ if (scans24GHz == null) scans24GHz = new StringBuilder();
+ scans24GHz.append(" {").append(result.BSSID);
+ if (bssid != null && result.BSSID.equals(bssid)) scans24GHz.append("*");
+ scans24GHz.append("=").append(result.frequency);
+ scans24GHz.append(",").append(result.level);
+ if (result.autoJoinStatus != 0) {
+ scans24GHz.append(",st=").append(result.autoJoinStatus);
+ }
+ if (result.numIpConfigFailures != 0) {
+ scans24GHz.append(",ipf=").append(result.numIpConfigFailures);
+ }
+ scans24GHz.append("}");
+ n24++;
+ }
+ }
+ }
+ visibility.append(" [");
+ if (num24 > 0) {
+ visibility.append("(").append(num24).append(")");
+ if (n24 <= 4) {
+ if (scans24GHz != null) {
+ visibility.append(scans24GHz.toString());
+ }
+ } else {
+ visibility.append("max=").append(rssi24);
+ if (scans24GHz != null) {
+ visibility.append(",").append(scans24GHz.toString());
+ }
+ }
+ }
+ visibility.append(";");
+ if (num5 > 0) {
+ visibility.append("(").append(num5).append(")");
+ if (n5 <= 4) {
+ if (scans5GHz != null) {
+ visibility.append(scans5GHz.toString());
+ }
+ } else {
+ visibility.append("max=").append(rssi5);
+ if (scans5GHz != null) {
+ visibility.append(",").append(scans5GHz.toString());
+ }
+ }
+ }
+ if (numBlackListed > 0)
+ visibility.append("!").append(numBlackListed);
+ visibility.append("]");
+ } else {
+ if (mRssi != Integer.MAX_VALUE) {
+ visibility.append(" rssi=");
+ visibility.append(mRssi);
+ if (mScanResult != null) {
+ visibility.append(", f=");
+ visibility.append(mScanResult.frequency);
+ }
+ }
+ }
+
+ return visibility.toString();
+ }
+
+ /**
+ * Updates the title and summary; may indirectly call notifyChanged().
+ */
private void refresh() {
setTitle(ssid);
- Context context = getContext();
- if (mConfig != null && mConfig.status == WifiConfiguration.Status.DISABLED) {
- switch (mConfig.disableReason) {
- case WifiConfiguration.DISABLED_AUTH_FAILURE:
- setSummary(context.getString(R.string.wifi_disabled_password_failure));
- break;
- case WifiConfiguration.DISABLED_DHCP_FAILURE:
- case WifiConfiguration.DISABLED_DNS_FAILURE:
- setSummary(context.getString(R.string.wifi_disabled_network_failure));
- break;
- case WifiConfiguration.DISABLED_UNKNOWN_REASON:
- setSummary(context.getString(R.string.wifi_disabled_generic));
+ final Context context = getContext();
+ updateIcon(getLevel(), context);
+
+ // Force new summary
+ setSummary(null);
+
+ // Update to new summary
+ StringBuilder summary = new StringBuilder();
+
+ if (mState != null) { // This is the active connection
+ summary.append(Summary.get(context, mState));
+ } else if (mConfig != null && ((mConfig.status == WifiConfiguration.Status.DISABLED &&
+ mConfig.disableReason != WifiConfiguration.DISABLED_UNKNOWN_REASON)
+ || mConfig.autoJoinStatus
+ >= WifiConfiguration.AUTO_JOIN_DISABLED_ON_AUTH_FAILURE)) {
+ if (mConfig.autoJoinStatus
+ >= WifiConfiguration.AUTO_JOIN_DISABLED_ON_AUTH_FAILURE) {
+ if (mConfig.disableReason == WifiConfiguration.DISABLED_DHCP_FAILURE) {
+ summary.append(context.getString(R.string.wifi_disabled_network_failure));
+ } else {
+ summary.append(context.getString(R.string.wifi_disabled_password_failure));
+ }
+ } else {
+ switch (mConfig.disableReason) {
+ case WifiConfiguration.DISABLED_AUTH_FAILURE:
+ summary.append(context.getString(R.string.wifi_disabled_password_failure));
+ break;
+ case WifiConfiguration.DISABLED_DHCP_FAILURE:
+ case WifiConfiguration.DISABLED_DNS_FAILURE:
+ summary.append(context.getString(R.string.wifi_disabled_network_failure));
+ break;
+ case WifiConfiguration.DISABLED_UNKNOWN_REASON:
+ case WifiConfiguration.DISABLED_ASSOCIATION_REJECT:
+ summary.append(context.getString(R.string.wifi_disabled_generic));
+ break;
+ }
}
} else if (mRssi == Integer.MAX_VALUE) { // Wifi out of range
- setSummary(context.getString(R.string.wifi_not_in_range));
- } else if (mState != null) { // This is the active connection
- setSummary(Summary.get(context, mState));
+ summary.append(context.getString(R.string.wifi_not_in_range));
} else { // In range, not disabled.
- StringBuilder summary = new StringBuilder();
if (mConfig != null) { // Is saved network
summary.append(context.getString(R.string.wifi_remembered));
}
+ }
- if (security != SECURITY_NONE) {
- String securityStrFormat;
- if (summary.length() == 0) {
- securityStrFormat = context.getString(R.string.wifi_secured_first_item);
- } else {
- securityStrFormat = context.getString(R.string.wifi_secured_second_item);
- }
- summary.append(String.format(securityStrFormat, getSecurityString(true)));
+ if (WifiSettings.mVerboseLogging > 0) {
+ //add RSSI/band information for this config, what was seen up to 6 seconds ago
+ //verbose WiFi Logging is only turned on thru developers settings
+ if (mInfo != null && mState != null) { // This is the active connection
+ summary.append(" (f=" + Integer.toString(mInfo.getFrequency()) + ")");
}
-
- if (mConfig == null && wpsAvailable) { // Only list WPS available for unsaved networks
- if (summary.length() == 0) {
- summary.append(context.getString(R.string.wifi_wps_available_first_item));
- } else {
- summary.append(context.getString(R.string.wifi_wps_available_second_item));
+ summary.append(" " + getVisibilityStatus());
+ if (mConfig != null && mConfig.autoJoinStatus > 0) {
+ summary.append(" (" + mConfig.autoJoinStatus);
+ if (mConfig.blackListTimestamp > 0) {
+ long now = System.currentTimeMillis();
+ long diff = (now - mConfig.blackListTimestamp)/1000;
+ long sec = diff%60; //seconds
+ long min = (diff/60)%60; //minutes
+ long hour = (min/60)%60; //hours
+ summary.append(", ");
+ if (hour > 0) summary.append(Long.toString(hour) + "h ");
+ summary.append( Long.toString(min) + "m ");
+ summary.append( Long.toString(sec) + "s ");
}
+ summary.append(")");
}
+ }
+
+ if (summary.length() > 0) {
setSummary(summary.toString());
+ } else {
+ showSummary = false;
}
}
diff --git a/src/com/android/settings/wifi/AdvancedWifiSettings.java b/src/com/android/settings/wifi/AdvancedWifiSettings.java
index bbcd50d..bda13ff 100644
--- a/src/com/android/settings/wifi/AdvancedWifiSettings.java
+++ b/src/com/android/settings/wifi/AdvancedWifiSettings.java
@@ -16,18 +16,25 @@
package com.android.settings.wifi;
+import android.app.Dialog;
+import android.app.DialogFragment;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.net.NetworkScoreManager;
+import android.net.NetworkScorerAppManager;
+import android.net.NetworkScorerAppManager.NetworkScorerAppData;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
-import android.net.wifi.WifiWatchdogStateMachine;
+import android.net.wifi.WpsInfo;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
import android.preference.Preference;
+import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceScreen;
+import android.preference.SwitchPreference;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.security.Credentials;
@@ -39,6 +46,8 @@ import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.Utils;
+import java.util.Collection;
+
public class AdvancedWifiSettings extends SettingsPreferenceFragment
implements Preference.OnPreferenceChangeListener {
@@ -48,12 +57,15 @@ public class AdvancedWifiSettings extends SettingsPreferenceFragment
private static final String KEY_FREQUENCY_BAND = "frequency_band";
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_POOR_NETWORK_DETECTION = "wifi_poor_network_detection";
private static final String KEY_SCAN_ALWAYS_AVAILABLE = "wifi_scan_always_available";
private static final String KEY_INSTALL_CREDENTIALS = "install_credentials";
- private static final String KEY_SUSPEND_OPTIMIZATIONS = "suspend_optimizations";
+ private static final String KEY_WIFI_ASSISTANT = "wifi_assistant";
+ private static final String KEY_WIFI_DIRECT = "wifi_direct";
+ private static final String KEY_WPS_PUSH = "wps_push_button";
+ private static final String KEY_WPS_PIN = "wps_pin_entry";
private WifiManager mWifiManager;
+ private NetworkScoreManager mNetworkScoreManager;
private IntentFilter mFilter;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -80,14 +92,15 @@ public class AdvancedWifiSettings extends SettingsPreferenceFragment
mFilter = new IntentFilter();
mFilter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ mNetworkScoreManager =
+ (NetworkScoreManager) getSystemService(Context.NETWORK_SCORE_SERVICE);
}
@Override
public void onResume() {
super.onResume();
initPreferences();
- getActivity().registerReceiver(mReceiver, mFilter,
- android.Manifest.permission.CHANGE_NETWORK_STATE, null);
+ getActivity().registerReceiver(mReceiver, mFilter);
refreshWifiInfo();
}
@@ -98,41 +111,63 @@ public class AdvancedWifiSettings extends SettingsPreferenceFragment
}
private void initPreferences() {
- CheckBoxPreference notifyOpenNetworks =
- (CheckBoxPreference) findPreference(KEY_NOTIFY_OPEN_NETWORKS);
+ SwitchPreference notifyOpenNetworks =
+ (SwitchPreference) findPreference(KEY_NOTIFY_OPEN_NETWORKS);
notifyOpenNetworks.setChecked(Settings.Global.getInt(getContentResolver(),
Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0) == 1);
notifyOpenNetworks.setEnabled(mWifiManager.isWifiEnabled());
- CheckBoxPreference poorNetworkDetection =
- (CheckBoxPreference) findPreference(KEY_POOR_NETWORK_DETECTION);
- if (poorNetworkDetection != null) {
- if (Utils.isWifiOnly(getActivity())) {
- getPreferenceScreen().removePreference(poorNetworkDetection);
- } else {
- poorNetworkDetection.setChecked(Global.getInt(getContentResolver(),
- Global.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED,
- WifiWatchdogStateMachine.DEFAULT_POOR_NETWORK_AVOIDANCE_ENABLED ?
- 1 : 0) == 1);
- }
- }
-
- CheckBoxPreference scanAlwaysAvailable =
- (CheckBoxPreference) findPreference(KEY_SCAN_ALWAYS_AVAILABLE);
+ SwitchPreference scanAlwaysAvailable =
+ (SwitchPreference) findPreference(KEY_SCAN_ALWAYS_AVAILABLE);
scanAlwaysAvailable.setChecked(Global.getInt(getContentResolver(),
Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1);
- Intent intent=new Intent(Credentials.INSTALL_AS_USER_ACTION);
+ Intent intent = new Intent(Credentials.INSTALL_AS_USER_ACTION);
intent.setClassName("com.android.certinstaller",
"com.android.certinstaller.CertInstallerMain");
intent.putExtra(Credentials.EXTRA_INSTALL_AS_UID, android.os.Process.WIFI_UID);
Preference pref = findPreference(KEY_INSTALL_CREDENTIALS);
pref.setIntent(intent);
- CheckBoxPreference suspendOptimizations =
- (CheckBoxPreference) findPreference(KEY_SUSPEND_OPTIMIZATIONS);
- suspendOptimizations.setChecked(Global.getInt(getContentResolver(),
- Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED, 1) == 1);
+ final Context context = getActivity();
+ NetworkScorerAppData scorer = WifiSettings.getWifiAssistantApp(context);
+ SwitchPreference wifiAssistant = (SwitchPreference)findPreference(KEY_WIFI_ASSISTANT);
+ if (scorer != null) {
+ final boolean checked = NetworkScorerAppManager.getActiveScorer(context) != null;
+ wifiAssistant.setSummary(getResources().getString(
+ R.string.wifi_automatically_manage_summary, scorer.mScorerName));
+ wifiAssistant.setOnPreferenceChangeListener(this);
+ wifiAssistant.setChecked(checked);
+ } else {
+ if (wifiAssistant != null) {
+ getPreferenceScreen().removePreference(wifiAssistant);
+ }
+ }
+
+ Intent wifiDirectIntent = new Intent(context,
+ com.android.settings.Settings.WifiP2pSettingsActivity.class);
+ Preference wifiDirectPref = findPreference(KEY_WIFI_DIRECT);
+ wifiDirectPref.setIntent(wifiDirectIntent);
+
+ // WpsDialog: Create the dialog like WifiSettings does.
+ Preference wpsPushPref = findPreference(KEY_WPS_PUSH);
+ wpsPushPref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
+ public boolean onPreferenceClick(Preference arg0) {
+ WpsFragment wpsFragment = new WpsFragment(WpsInfo.PBC);
+ wpsFragment.show(getFragmentManager(), KEY_WPS_PUSH);
+ return true;
+ }
+ });
+
+ // WpsDialog: Create the dialog like WifiSettings does.
+ Preference wpsPinPref = findPreference(KEY_WPS_PIN);
+ wpsPinPref.setOnPreferenceClickListener(new OnPreferenceClickListener(){
+ public boolean onPreferenceClick(Preference arg0) {
+ WpsFragment wpsFragment = new WpsFragment(WpsInfo.DISPLAY);
+ wpsFragment.show(getFragmentManager(), KEY_WPS_PIN);
+ return true;
+ }
+ });
ListPreference frequencyPref = (ListPreference) findPreference(KEY_FREQUENCY_BAND);
@@ -154,7 +189,7 @@ public class AdvancedWifiSettings extends SettingsPreferenceFragment
ListPreference sleepPolicyPref = (ListPreference) findPreference(KEY_SLEEP_POLICY);
if (sleepPolicyPref != null) {
- if (Utils.isWifiOnly(getActivity())) {
+ if (Utils.isWifiOnly(context)) {
sleepPolicyPref.setEntries(R.array.wifi_sleep_policy_entries_wifi_only);
}
sleepPolicyPref.setOnPreferenceChangeListener(this);
@@ -199,19 +234,11 @@ public class AdvancedWifiSettings extends SettingsPreferenceFragment
if (KEY_NOTIFY_OPEN_NETWORKS.equals(key)) {
Global.putInt(getContentResolver(),
Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
- ((CheckBoxPreference) preference).isChecked() ? 1 : 0);
- } else if (KEY_POOR_NETWORK_DETECTION.equals(key)) {
- Global.putInt(getContentResolver(),
- Global.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED,
- ((CheckBoxPreference) preference).isChecked() ? 1 : 0);
- } else if (KEY_SUSPEND_OPTIMIZATIONS.equals(key)) {
- Global.putInt(getContentResolver(),
- Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED,
- ((CheckBoxPreference) preference).isChecked() ? 1 : 0);
+ ((SwitchPreference) preference).isChecked() ? 1 : 0);
} else if (KEY_SCAN_ALWAYS_AVAILABLE.equals(key)) {
Global.putInt(getContentResolver(),
Global.WIFI_SCAN_ALWAYS_AVAILABLE,
- ((CheckBoxPreference) preference).isChecked() ? 1 : 0);
+ ((SwitchPreference) preference).isChecked() ? 1 : 0);
} else {
return super.onPreferenceTreeClick(screen, preference);
}
@@ -220,6 +247,7 @@ public class AdvancedWifiSettings extends SettingsPreferenceFragment
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
+ final Context context = getActivity();
String key = preference.getKey();
if (KEY_FREQUENCY_BAND.equals(key)) {
@@ -228,10 +256,32 @@ public class AdvancedWifiSettings extends SettingsPreferenceFragment
mWifiManager.setFrequencyBand(value, true);
updateFrequencyBandSummary(preference, value);
} catch (NumberFormatException e) {
- Toast.makeText(getActivity(), R.string.wifi_setting_frequency_band_error,
+ Toast.makeText(context, R.string.wifi_setting_frequency_band_error,
Toast.LENGTH_SHORT).show();
return false;
}
+ } else if (KEY_WIFI_ASSISTANT.equals(key)) {
+ if (((Boolean)newValue).booleanValue() == false) {
+ mNetworkScoreManager.setActiveScorer(null);
+ return true;
+ }
+
+ NetworkScorerAppData wifiAssistant = WifiSettings.getWifiAssistantApp(context);
+ Intent intent = new Intent();
+ if (wifiAssistant.mConfigurationActivityClassName != null) {
+ // App has a custom configuration activity; launch that.
+ // This custom activity will be responsible for launching the system
+ // dialog.
+ intent.setClassName(wifiAssistant.mPackageName,
+ wifiAssistant.mConfigurationActivityClassName);
+ } else {
+ // Fall back on the system dialog.
+ intent.setAction(NetworkScoreManager.ACTION_CHANGE_ACTIVE);
+ intent.putExtra(NetworkScoreManager.EXTRA_PACKAGE_NAME,
+ wifiAssistant.mPackageName);
+ }
+
+ startActivity(intent);
}
if (KEY_SLEEP_POLICY.equals(key)) {
@@ -241,7 +291,7 @@ public class AdvancedWifiSettings extends SettingsPreferenceFragment
Integer.parseInt(stringValue));
updateSleepPolicySummary(preference, stringValue);
} catch (NumberFormatException e) {
- Toast.makeText(getActivity(), R.string.wifi_setting_sleep_policy_error,
+ Toast.makeText(context, R.string.wifi_setting_sleep_policy_error,
Toast.LENGTH_SHORT).show();
return false;
}
@@ -251,17 +301,40 @@ public class AdvancedWifiSettings extends SettingsPreferenceFragment
}
private void refreshWifiInfo() {
+ final Context context = getActivity();
WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
Preference wifiMacAddressPref = findPreference(KEY_MAC_ADDRESS);
String macAddress = wifiInfo == null ? null : wifiInfo.getMacAddress();
wifiMacAddressPref.setSummary(!TextUtils.isEmpty(macAddress) ? macAddress
- : getActivity().getString(R.string.status_unavailable));
+ : context.getString(R.string.status_unavailable));
+ wifiMacAddressPref.setSelectable(false);
Preference wifiIpAddressPref = findPreference(KEY_CURRENT_IP_ADDRESS);
- String ipAddress = Utils.getWifiIpAddresses(getActivity());
+ String ipAddress = Utils.getWifiIpAddresses(context);
wifiIpAddressPref.setSummary(ipAddress == null ?
- getActivity().getString(R.string.status_unavailable) : ipAddress);
+ context.getString(R.string.status_unavailable) : ipAddress);
+ wifiIpAddressPref.setSelectable(false);
+ }
+
+ /* Wrapper class for the WPS dialog to properly handle life cycle events like rotation. */
+ public static class WpsFragment extends DialogFragment {
+ private static int mWpsSetup;
+
+ // Public default constructor is required for rotation.
+ public WpsFragment() {
+ super();
+ }
+
+ public WpsFragment(int wpsSetup) {
+ super();
+ mWpsSetup = wpsSetup;
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ return new WpsDialog(getActivity(), mWpsSetup);
+ }
}
}
diff --git a/src/com/android/settings/wifi/SavedAccessPointsWifiSettings.java b/src/com/android/settings/wifi/SavedAccessPointsWifiSettings.java
new file mode 100644
index 0000000..bea720c
--- /dev/null
+++ b/src/com/android/settings/wifi/SavedAccessPointsWifiSettings.java
@@ -0,0 +1,253 @@
+/*
+ * 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.wifi;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.Resources;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceClickListener;
+import android.preference.PreferenceScreen;
+
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.search.Indexable;
+import com.android.settings.search.SearchIndexableRaw;
+
+import android.util.Log;
+import android.view.View;
+
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * UI to manage saved networks/access points.
+ */
+public class SavedAccessPointsWifiSettings extends SettingsPreferenceFragment
+ implements DialogInterface.OnClickListener, Indexable {
+ private static final String TAG = "SavedAccessPointsWifiSettings";
+
+ private WifiDialog mDialog;
+ private WifiManager mWifiManager;
+ private AccessPoint mDlgAccessPoint;
+ private Bundle mAccessPointSavedState;
+ private AccessPoint mSelectedAccessPoint;
+
+ // Instance state key
+ private static final String SAVE_DIALOG_ACCESS_POINT_STATE = "wifi_ap_state";
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.wifi_display_saved_access_points);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ initPreferences();
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
+
+ if (savedInstanceState != null) {
+ if (savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) {
+ mAccessPointSavedState =
+ savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE);
+ }
+ }
+ }
+
+ private void initPreferences() {
+ PreferenceScreen preferenceScreen = getPreferenceScreen();
+ final Context context = getActivity();
+
+ mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ final List<AccessPoint> accessPoints = constructSavedAccessPoints(context, mWifiManager);
+
+ preferenceScreen.removeAll();
+
+ final int accessPointsSize = accessPoints.size();
+ for (int i = 0; i < accessPointsSize; ++i){
+ preferenceScreen.addPreference(accessPoints.get(i));
+ }
+
+ if(getPreferenceScreen().getPreferenceCount() < 1) {
+ Log.w(TAG, "Saved networks activity loaded, but there are no saved networks!");
+ }
+ }
+
+ private static List<AccessPoint> constructSavedAccessPoints(Context context,
+ WifiManager wifiManager){
+ List<AccessPoint> accessPoints = new ArrayList<AccessPoint>();
+ Map<String, List<ScanResult>> resultsMap = new HashMap<String, List<ScanResult>>();
+
+ final List<WifiConfiguration> configs = wifiManager.getConfiguredNetworks();
+ final List<ScanResult> scanResults = wifiManager.getScanResults();
+
+ if (configs != null) {
+ //Construct a Map for quick searching of a wifi network via ssid.
+ final int scanResultsSize = scanResults.size();
+ for (int i = 0; i < scanResultsSize; ++i){
+ final ScanResult result = scanResults.get(i);
+ List<ScanResult> res = resultsMap.get(result.SSID);
+
+ if(res == null){
+ res = new ArrayList<ScanResult>();
+ resultsMap.put(result.SSID, res);
+ }
+
+ res.add(result);
+ }
+
+ final int configsSize = configs.size();
+ for (int i = 0; i < configsSize; ++i){
+ WifiConfiguration config = configs.get(i);
+ if (config.selfAdded && config.numAssociation == 0) {
+ continue;
+ }
+ AccessPoint accessPoint = new AccessPoint(context, config);
+ final List<ScanResult> results = resultsMap.get(accessPoint.ssid);
+
+ accessPoint.setShowSummary(false);
+ if(results != null){
+ final int resultsSize = results.size();
+ for (int j = 0; j < resultsSize; ++j){
+ accessPoint.update(results.get(j));
+ accessPoint.setIcon(null);
+ }
+ }
+
+ accessPoints.add(accessPoint);
+ }
+ }
+
+ return accessPoints;
+ }
+
+ private void showDialog(AccessPoint accessPoint, boolean edit) {
+ if (mDialog != null) {
+ removeDialog(WifiSettings.WIFI_DIALOG_ID);
+ mDialog = null;
+ }
+
+ // Save the access point and edit mode
+ mDlgAccessPoint = accessPoint;
+
+ showDialog(WifiSettings.WIFI_DIALOG_ID);
+ }
+
+ @Override
+ 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;
+ mDialog = new WifiDialog(getActivity(), this, mDlgAccessPoint,
+ false /* not editting */, true /* hide the submit button */);
+ return mDialog;
+
+ }
+ return super.onCreateDialog(dialogId);
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ // If the dialog is showing, save its state.
+ if (mDialog != null && mDialog.isShowing()) {
+ if (mDlgAccessPoint != null) {
+ mAccessPointSavedState = new Bundle();
+ mDlgAccessPoint.saveWifiState(mAccessPointSavedState);
+ outState.putBundle(SAVE_DIALOG_ACCESS_POINT_STATE, mAccessPointSavedState);
+ }
+ }
+ }
+
+ @Override
+ public void onClick(DialogInterface dialogInterface, int button) {
+ if (button == WifiDialog.BUTTON_FORGET && mSelectedAccessPoint != null) {
+ mWifiManager.forget(mSelectedAccessPoint.networkId, null);
+ getPreferenceScreen().removePreference(mSelectedAccessPoint);
+ mSelectedAccessPoint = null;
+ }
+ }
+
+ @Override
+ public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
+ if (preference instanceof AccessPoint) {
+ showDialog((AccessPoint) preference, false);
+ return true;
+ } else{
+ return super.onPreferenceTreeClick(screen, preference);
+ }
+ }
+
+ /**
+ * For search.
+ */
+ public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider() {
+ @Override
+ public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
+ final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>();
+ final Resources res = context.getResources();
+ final String title = res.getString(R.string.wifi_saved_access_points_titlebar);
+
+ // Add fragment title
+ SearchIndexableRaw data = new SearchIndexableRaw(context);
+ data.title = title;
+ data.screenTitle = title;
+ data.enabled = enabled;
+ result.add(data);
+
+ // Add available Wi-Fi access points
+ WifiManager wifiManager =
+ (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ final List<AccessPoint> accessPoints =
+ constructSavedAccessPoints(context, wifiManager);
+
+ final int accessPointsSize = accessPoints.size();
+ for (int i = 0; i < accessPointsSize; ++i){
+ data = new SearchIndexableRaw(context);
+ data.title = accessPoints.get(i).getTitle().toString();
+ data.screenTitle = title;
+ data.enabled = enabled;
+ result.add(data);
+ }
+
+ return result;
+ }
+ };
+}
diff --git a/src/com/android/settings/wifi/WifiApDialog.java b/src/com/android/settings/wifi/WifiApDialog.java
index 211e85d..fb8026a 100644
--- a/src/com/android/settings/wifi/WifiApDialog.java
+++ b/src/com/android/settings/wifi/WifiApDialog.java
@@ -47,8 +47,7 @@ public class WifiApDialog extends AlertDialog implements View.OnClickListener,
private final DialogInterface.OnClickListener mListener;
public static final int OPEN_INDEX = 0;
- public static final int WPA_INDEX = 1;
- public static final int WPA2_INDEX = 2;
+ public static final int WPA2_INDEX = 1;
private View mView;
private TextView mSsid;
@@ -68,9 +67,7 @@ public class WifiApDialog extends AlertDialog implements View.OnClickListener,
}
public static int getSecurityTypeIndex(WifiConfiguration wifiConfig) {
- if (wifiConfig.allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
- return WPA_INDEX;
- } else if (wifiConfig.allowedKeyManagement.get(KeyMgmt.WPA2_PSK)) {
+ if (wifiConfig.allowedKeyManagement.get(KeyMgmt.WPA2_PSK)) {
return WPA2_INDEX;
}
return OPEN_INDEX;
@@ -93,15 +90,6 @@ public class WifiApDialog extends AlertDialog implements View.OnClickListener,
config.allowedKeyManagement.set(KeyMgmt.NONE);
return config;
- case WPA_INDEX:
- config.allowedKeyManagement.set(KeyMgmt.WPA_PSK);
- config.allowedAuthAlgorithms.set(AuthAlgorithm.OPEN);
- if (mPassword.length() != 0) {
- String password = mPassword.getText().toString();
- config.preSharedKey = password;
- }
- return config;
-
case WPA2_INDEX:
config.allowedKeyManagement.set(KeyMgmt.WPA2_PSK);
config.allowedAuthAlgorithms.set(AuthAlgorithm.OPEN);
@@ -137,8 +125,7 @@ public class WifiApDialog extends AlertDialog implements View.OnClickListener,
if (mWifiConfig != null) {
mSsid.setText(mWifiConfig.SSID);
mSecurity.setSelection(mSecurityTypeIndex);
- if (mSecurityTypeIndex == WPA_INDEX ||
- mSecurityTypeIndex == WPA2_INDEX) {
+ if (mSecurityTypeIndex == WPA2_INDEX) {
mPassword.setText(mWifiConfig.preSharedKey);
}
}
@@ -156,7 +143,7 @@ public class WifiApDialog extends AlertDialog implements View.OnClickListener,
private void validate() {
if ((mSsid != null && mSsid.length() == 0) ||
- (((mSecurityTypeIndex == WPA_INDEX) || (mSecurityTypeIndex == WPA2_INDEX))&&
+ ((mSecurityTypeIndex == WPA2_INDEX)&&
mPassword.length() < 8)) {
getButton(BUTTON_SUBMIT).setEnabled(false);
} else {
diff --git a/src/com/android/settings/wifi/WifiApEnabler.java b/src/com/android/settings/wifi/WifiApEnabler.java
index 9a3b49d..fc34f3b 100644
--- a/src/com/android/settings/wifi/WifiApEnabler.java
+++ b/src/com/android/settings/wifi/WifiApEnabler.java
@@ -33,7 +33,7 @@ import android.net.wifi.SupplicantState;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
-import android.preference.CheckBoxPreference;
+import android.preference.SwitchPreference;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
@@ -41,7 +41,7 @@ import android.widget.Toast;
public class WifiApEnabler {
private final Context mContext;
- private final CheckBoxPreference mCheckBox;
+ private final SwitchPreference mSwitch;
private final CharSequence mOriginalSummary;
private WifiManager mWifiManager;
@@ -66,17 +66,17 @@ public class WifiApEnabler {
ConnectivityManager.EXTRA_ERRORED_TETHER);
updateTetherState(available.toArray(), active.toArray(), errored.toArray());
} else if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(action)) {
- enableWifiCheckBox();
+ enableWifiSwitch();
}
}
};
- public WifiApEnabler(Context context, CheckBoxPreference checkBox) {
+ public WifiApEnabler(Context context, SwitchPreference switchPreference) {
mContext = context;
- mCheckBox = checkBox;
- mOriginalSummary = checkBox.getSummary();
- checkBox.setPersistent(false);
+ mSwitch = switchPreference;
+ mOriginalSummary = switchPreference.getSummary();
+ switchPreference.setPersistent(false);
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
mCm = (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
@@ -90,21 +90,21 @@ public class WifiApEnabler {
public void resume() {
mContext.registerReceiver(mReceiver, mIntentFilter);
- enableWifiCheckBox();
+ enableWifiSwitch();
}
public void pause() {
mContext.unregisterReceiver(mReceiver);
}
- private void enableWifiCheckBox() {
+ private void enableWifiSwitch() {
boolean isAirplaneMode = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
if(!isAirplaneMode) {
- mCheckBox.setEnabled(true);
+ mSwitch.setEnabled(true);
} else {
- mCheckBox.setSummary(mOriginalSummary);
- mCheckBox.setEnabled(false);
+ mSwitch.setSummary(mOriginalSummary);
+ mSwitch.setEnabled(false);
}
}
@@ -122,9 +122,9 @@ public class WifiApEnabler {
if (mWifiManager.setWifiApEnabled(null, enable)) {
/* Disable here, enabled on receiving success broadcast */
- mCheckBox.setEnabled(false);
+ mSwitch.setEnabled(false);
} else {
- mCheckBox.setSummary(R.string.wifi_error);
+ mSwitch.setSummary(R.string.wifi_error);
}
/**
@@ -147,7 +147,7 @@ public class WifiApEnabler {
public void updateConfigSummary(WifiConfiguration wifiConfig) {
String s = mContext.getString(
com.android.internal.R.string.wifi_tether_configure_ssid_default);
- mCheckBox.setSummary(String.format(
+ mSwitch.setSummary(String.format(
mContext.getString(R.string.wifi_tether_enabled_subtext),
(wifiConfig == null) ? s : wifiConfig.SSID));
}
@@ -173,38 +173,38 @@ public class WifiApEnabler {
WifiConfiguration wifiConfig = mWifiManager.getWifiApConfiguration();
updateConfigSummary(wifiConfig);
} else if (wifiErrored) {
- mCheckBox.setSummary(R.string.wifi_error);
+ mSwitch.setSummary(R.string.wifi_error);
}
}
private void handleWifiApStateChanged(int state) {
switch (state) {
case WifiManager.WIFI_AP_STATE_ENABLING:
- mCheckBox.setSummary(R.string.wifi_tether_starting);
- mCheckBox.setEnabled(false);
+ mSwitch.setSummary(R.string.wifi_tether_starting);
+ mSwitch.setEnabled(false);
break;
case WifiManager.WIFI_AP_STATE_ENABLED:
/**
* Summary on enable is handled by tether
* broadcast notice
*/
- mCheckBox.setChecked(true);
+ mSwitch.setChecked(true);
/* Doesnt need the airplane check */
- mCheckBox.setEnabled(true);
+ mSwitch.setEnabled(true);
break;
case WifiManager.WIFI_AP_STATE_DISABLING:
- mCheckBox.setSummary(R.string.wifi_tether_stopping);
- mCheckBox.setEnabled(false);
+ mSwitch.setSummary(R.string.wifi_tether_stopping);
+ mSwitch.setEnabled(false);
break;
case WifiManager.WIFI_AP_STATE_DISABLED:
- mCheckBox.setChecked(false);
- mCheckBox.setSummary(mOriginalSummary);
- enableWifiCheckBox();
+ mSwitch.setChecked(false);
+ mSwitch.setSummary(mOriginalSummary);
+ enableWifiSwitch();
break;
default:
- mCheckBox.setChecked(false);
- mCheckBox.setSummary(R.string.wifi_error);
- enableWifiCheckBox();
+ mSwitch.setChecked(false);
+ mSwitch.setSummary(R.string.wifi_error);
+ enableWifiSwitch();
}
}
}
diff --git a/src/com/android/settings/wifi/WifiConfigController.java b/src/com/android/settings/wifi/WifiConfigController.java
index 55dc033..e6dd9c7 100644
--- a/src/com/android/settings/wifi/WifiConfigController.java
+++ b/src/com/android/settings/wifi/WifiConfigController.java
@@ -18,24 +18,28 @@ package com.android.settings.wifi;
import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
+import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Resources;
+import android.net.IpConfiguration;
+import android.net.IpConfiguration.IpAssignment;
+import android.net.IpConfiguration.ProxySettings;
import android.net.LinkAddress;
-import android.net.LinkProperties;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkUtils;
-import android.net.ProxyProperties;
+import android.net.ProxyInfo;
import android.net.RouteInfo;
+import android.net.StaticIpConfiguration;
+import android.net.Uri;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiConfiguration.AuthAlgorithm;
-import android.net.wifi.WifiConfiguration.IpAssignment;
import android.net.wifi.WifiConfiguration.KeyMgmt;
-import android.net.wifi.WifiConfiguration.ProxySettings;
import android.net.wifi.WifiEnterpriseConfig;
import android.net.wifi.WifiEnterpriseConfig.Eap;
import android.net.wifi.WifiEnterpriseConfig.Phase2;
import android.net.wifi.WifiInfo;
import android.os.Handler;
+import android.os.UserHandle;
import android.security.Credentials;
import android.security.KeyStore;
import android.text.Editable;
@@ -59,6 +63,7 @@ import com.android.settings.ProxySelector;
import com.android.settings.R;
import java.net.InetAddress;
+import java.net.Inet4Address;
import java.util.Iterator;
/**
@@ -67,36 +72,12 @@ import java.util.Iterator;
*/
public class WifiConfigController implements TextWatcher,
AdapterView.OnItemSelectedListener, OnCheckedChangeListener {
+ private static final String TAG = "WifiConfigController";
+
private final WifiConfigUiBase mConfigUi;
private final View mView;
private final AccessPoint mAccessPoint;
- private boolean mEdit;
-
- private TextView mSsidView;
-
- // e.g. AccessPoint.SECURITY_NONE
- private int mAccessPointSecurity;
- private TextView mPasswordView;
-
- private String unspecifiedCert = "unspecified";
- private static final int unspecifiedCertIndex = 0;
-
- /* Phase2 methods supported by PEAP are limited */
- private final ArrayAdapter<String> PHASE2_PEAP_ADAPTER;
- /* Full list of phase2 methods */
- private final ArrayAdapter<String> PHASE2_FULL_ADAPTER;
-
- private Spinner mSecuritySpinner;
- private Spinner mEapMethodSpinner;
- private Spinner mEapCaCertSpinner;
- private Spinner mPhase2Spinner;
- // Associated with mPhase2Spinner, one of PHASE2_FULL_ADAPTER or PHASE2_PEAP_ADAPTER
- private ArrayAdapter<String> mPhase2Adapter;
- private Spinner mEapUserCertSpinner;
- private TextView mEapIdentityView;
- private TextView mEapAnonymousView;
-
/* This value comes from "wifi_ip_settings" resource array */
private static final int DHCP = 0;
private static final int STATIC_IP = 1;
@@ -104,6 +85,7 @@ public class WifiConfigController implements TextWatcher,
/* These values come from "wifi_proxy_settings" resource array */
public static final int PROXY_NONE = 0;
public static final int PROXY_STATIC = 1;
+ public static final int PROXY_PAC = 2;
/* These values come from "wifi_eap_method" resource array */
public static final int WIFI_EAP_METHOD_PEAP = 0;
@@ -116,7 +98,32 @@ public class WifiConfigController implements TextWatcher,
public static final int WIFI_PEAP_PHASE2_MSCHAPV2 = 1;
public static final int WIFI_PEAP_PHASE2_GTC = 2;
- private static final String TAG = "WifiConfigController";
+ /* Phase2 methods supported by PEAP are limited */
+ private final ArrayAdapter<String> PHASE2_PEAP_ADAPTER;
+ /* Full list of phase2 methods */
+ private final ArrayAdapter<String> PHASE2_FULL_ADAPTER;
+
+ // True when this instance is used in SetupWizard XL context.
+ private final boolean mInXlSetupWizard;
+
+ private final Handler mTextViewChangedHandler;
+
+ // e.g. AccessPoint.SECURITY_NONE
+ private int mAccessPointSecurity;
+ private TextView mPasswordView;
+
+ private String unspecifiedCert = "unspecified";
+ private static final int unspecifiedCertIndex = 0;
+
+ private Spinner mSecuritySpinner;
+ private Spinner mEapMethodSpinner;
+ private Spinner mEapCaCertSpinner;
+ private Spinner mPhase2Spinner;
+ // Associated with mPhase2Spinner, one of PHASE2_FULL_ADAPTER or PHASE2_PEAP_ADAPTER
+ private ArrayAdapter<String> mPhase2Adapter;
+ private Spinner mEapUserCertSpinner;
+ private TextView mEapIdentityView;
+ private TextView mEapAnonymousView;
private Spinner mIpSettingsSpinner;
private TextView mIpAddressView;
@@ -129,15 +136,18 @@ public class WifiConfigController implements TextWatcher,
private TextView mProxyHostView;
private TextView mProxyPortView;
private TextView mProxyExclusionListView;
+ private TextView mProxyPacView;
private IpAssignment mIpAssignment = IpAssignment.UNASSIGNED;
private ProxySettings mProxySettings = ProxySettings.UNASSIGNED;
- private LinkProperties mLinkProperties = new LinkProperties();
+ private ProxyInfo mHttpProxy = null;
+ private StaticIpConfiguration mStaticIpConfiguration = null;
- // True when this instance is used in SetupWizard XL context.
- private final boolean mInXlSetupWizard;
+ private String[] mLevels;
+ private boolean mEdit;
+ private TextView mSsidView;
- private final Handler mTextViewChangedHandler;
+ private Context mContext;
public WifiConfigController(
WifiConfigUiBase parent, View view, AccessPoint accessPoint, boolean edit) {
@@ -151,20 +161,21 @@ public class WifiConfigController implements TextWatcher,
mEdit = edit;
mTextViewChangedHandler = new Handler();
- final Context context = mConfigUi.getContext();
- final Resources resources = context.getResources();
+ mContext = mConfigUi.getContext();
+ final Resources res = mContext.getResources();
+ mLevels = res.getStringArray(R.array.wifi_signal);
PHASE2_PEAP_ADAPTER = new ArrayAdapter<String>(
- context, android.R.layout.simple_spinner_item,
- context.getResources().getStringArray(R.array.wifi_peap_phase2_entries));
+ mContext, android.R.layout.simple_spinner_item,
+ res.getStringArray(R.array.wifi_peap_phase2_entries));
PHASE2_PEAP_ADAPTER.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
PHASE2_FULL_ADAPTER = new ArrayAdapter<String>(
- context, android.R.layout.simple_spinner_item,
- context.getResources().getStringArray(R.array.wifi_phase2_entries));
+ mContext, android.R.layout.simple_spinner_item,
+ res.getStringArray(R.array.wifi_phase2_entries));
PHASE2_FULL_ADAPTER.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- unspecifiedCert = context.getString(R.string.wifi_unspecified);
+ unspecifiedCert = mContext.getString(R.string.wifi_unspecified);
mIpSettingsSpinner = (Spinner) mView.findViewById(R.id.ip_settings);
mIpSettingsSpinner.setOnItemSelectedListener(this);
mProxySettingsSpinner = (Spinner) mView.findViewById(R.id.proxy_settings);
@@ -182,9 +193,9 @@ public class WifiConfigController implements TextWatcher,
mView.findViewById(R.id.type_security).setVisibility(View.VISIBLE);
// We want custom layout. The content must be same as the other cases.
- ArrayAdapter<String> adapter = new ArrayAdapter<String>(context,
+ ArrayAdapter<String> adapter = new ArrayAdapter<String>(mContext,
R.layout.wifi_setup_custom_list_item_1, android.R.id.text1,
- context.getResources().getStringArray(R.array.wifi_security_no_eap));
+ res.getStringArray(R.array.wifi_security_no_eap));
mSecuritySpinner.setAdapter(adapter);
} else {
mView.findViewById(R.id.type).setVisibility(View.VISIBLE);
@@ -197,54 +208,34 @@ public class WifiConfigController implements TextWatcher,
.setOnCheckedChangeListener(this);
- mConfigUi.setSubmitButton(context.getString(R.string.wifi_save));
+ mConfigUi.setSubmitButton(res.getString(R.string.wifi_save));
} else {
mConfigUi.setTitle(mAccessPoint.ssid);
ViewGroup group = (ViewGroup) mView.findViewById(R.id.info);
- DetailedState state = mAccessPoint.getState();
- if (state != null) {
- addRow(group, R.string.wifi_status, Summary.get(mConfigUi.getContext(), state));
- }
-
- int level = mAccessPoint.getLevel();
- if (level != -1) {
- String[] signal = resources.getStringArray(R.array.wifi_signal);
- addRow(group, R.string.wifi_signal, signal[level]);
- }
-
- WifiInfo info = mAccessPoint.getInfo();
- if (info != null && info.getLinkSpeed() != -1) {
- addRow(group, R.string.wifi_speed, info.getLinkSpeed() + WifiInfo.LINK_SPEED_UNITS);
- }
-
- addRow(group, R.string.wifi_security, mAccessPoint.getSecurityString(false));
-
boolean showAdvancedFields = false;
if (mAccessPoint.networkId != INVALID_NETWORK_ID) {
WifiConfiguration config = mAccessPoint.getConfig();
- if (config.ipAssignment == IpAssignment.STATIC) {
+ if (config.getIpAssignment() == IpAssignment.STATIC) {
mIpSettingsSpinner.setSelection(STATIC_IP);
showAdvancedFields = true;
+ // Display IP address.
+ StaticIpConfiguration staticConfig = config.getStaticIpConfiguration();
+ if (staticConfig != null && staticConfig.ipAddress != null) {
+ addRow(group, R.string.wifi_ip_address,
+ staticConfig.ipAddress.getAddress().getHostAddress());
+ }
} else {
mIpSettingsSpinner.setSelection(DHCP);
}
- //Display IP addresses
- for(InetAddress a : config.linkProperties.getAddresses()) {
- addRow(group, R.string.wifi_ip_address, a.getHostAddress());
- }
- if (config.proxySettings == ProxySettings.STATIC) {
+ if (config.getProxySettings() == ProxySettings.STATIC) {
mProxySettingsSpinner.setSelection(PROXY_STATIC);
showAdvancedFields = true;
- } else if (config.proxySettings == ProxySettings.PAC) {
- mProxySettingsSpinner.setVisibility(View.GONE);
- TextView textView = (TextView)mView.findViewById(R.id.proxy_pac_info);
- textView.setVisibility(View.VISIBLE);
- textView.setText(context.getString(R.string.proxy_url) +
- config.linkProperties.getHttpProxy().getPacFileUrl());
+ } else if (config.getProxySettings() == ProxySettings.PAC) {
+ mProxySettingsSpinner.setSelection(PROXY_PAC);
showAdvancedFields = true;
} else {
mProxySettingsSpinner.setSelection(PROXY_NONE);
@@ -265,21 +256,62 @@ public class WifiConfigController implements TextWatcher,
}
if (mEdit) {
- mConfigUi.setSubmitButton(context.getString(R.string.wifi_save));
+ mConfigUi.setSubmitButton(res.getString(R.string.wifi_save));
} else {
- if (state == null && level != -1) {
- mConfigUi.setSubmitButton(context.getString(R.string.wifi_connect));
+ final DetailedState state = mAccessPoint.getState();
+ final String signalLevel = getSignalString();
+
+ if (state == null && signalLevel != null) {
+ mConfigUi.setSubmitButton(res.getString(R.string.wifi_connect));
} else {
+ if (state != null) {
+ addRow(group, R.string.wifi_status, Summary.get(mConfigUi.getContext(),
+ state));
+ }
+
+ if (signalLevel != null) {
+ addRow(group, R.string.wifi_signal, signalLevel);
+ }
+
+ WifiInfo info = mAccessPoint.getInfo();
+ if (info != null && info.getLinkSpeed() != -1) {
+ addRow(group, R.string.wifi_speed, info.getLinkSpeed()
+ + WifiInfo.LINK_SPEED_UNITS);
+ }
+
+ if (info != null && info.getFrequency() != -1) {
+ final int frequency = info.getFrequency();
+ String band = null;
+
+ if (frequency >= AccessPoint.LOWER_FREQ_24GHZ
+ && frequency < AccessPoint.HIGHER_FREQ_24GHZ) {
+ band = res.getString(R.string.wifi_band_24ghz);
+ } else if (frequency >= AccessPoint.LOWER_FREQ_5GHZ
+ && frequency < AccessPoint.HIGHER_FREQ_5GHZ) {
+ band = res.getString(R.string.wifi_band_5ghz);
+ } else {
+ Log.e(TAG, "Unexpected frequency " + frequency);
+ }
+ if (band != null) {
+ addRow(group, R.string.wifi_frequency, band);
+ }
+ }
+
+ addRow(group, R.string.wifi_security, mAccessPoint.getSecurityString(false));
mView.findViewById(R.id.ip_fields).setVisibility(View.GONE);
}
- if (mAccessPoint.networkId != INVALID_NETWORK_ID) {
- mConfigUi.setForgetButton(context.getString(R.string.wifi_forget));
+ if (mAccessPoint.networkId != INVALID_NETWORK_ID
+ && ActivityManager.getCurrentUser() == UserHandle.USER_OWNER) {
+ mConfigUi.setForgetButton(res.getString(R.string.wifi_forget));
}
}
}
-
- mConfigUi.setCancelButton(context.getString(R.string.wifi_cancel));
+ if (mEdit || (mAccessPoint.getState() == null && mAccessPoint.getLevel() != -1)){
+ mConfigUi.setCancelButton(res.getString(R.string.wifi_cancel));
+ }else{
+ mConfigUi.setCancelButton(res.getString(R.string.wifi_display_options_done));
+ }
if (mConfigUi.getSubmitButton() != null) {
enableSubmitIfAppropriate();
}
@@ -292,6 +324,19 @@ public class WifiConfigController implements TextWatcher,
group.addView(row);
}
+ private String getSignalString(){
+ final int level = mAccessPoint.getLevel();
+
+ return (level > -1 && level < mLevels.length) ? mLevels[level] : null;
+ }
+
+ void hideSubmitButton() {
+ Button submit = mConfigUi.getSubmitButton();
+ if (submit == null) return;
+
+ submit.setVisibility(View.GONE);
+ }
+
/* show submit button if password, ip and proxy settings are valid */
void enableSubmitIfAppropriate() {
Button submit = mConfigUi.getSubmitButton();
@@ -430,31 +475,31 @@ public class WifiConfigController implements TextWatcher,
return null;
}
- config.proxySettings = mProxySettings;
- config.ipAssignment = mIpAssignment;
- config.linkProperties = new LinkProperties(mLinkProperties);
+ config.setIpConfiguration(
+ new IpConfiguration(mIpAssignment, mProxySettings,
+ mStaticIpConfiguration, mHttpProxy));
return config;
}
private boolean ipAndProxyFieldsAreValid() {
- mLinkProperties.clear();
mIpAssignment = (mIpSettingsSpinner != null &&
mIpSettingsSpinner.getSelectedItemPosition() == STATIC_IP) ?
IpAssignment.STATIC : IpAssignment.DHCP;
if (mIpAssignment == IpAssignment.STATIC) {
- int result = validateIpConfigFields(mLinkProperties);
+ mStaticIpConfiguration = new StaticIpConfiguration();
+ int result = validateIpConfigFields(mStaticIpConfiguration);
if (result != 0) {
return false;
}
}
- mProxySettings = (mProxySettingsSpinner != null &&
- mProxySettingsSpinner.getSelectedItemPosition() == PROXY_STATIC) ?
- ProxySettings.STATIC : ProxySettings.NONE;
-
- if (mProxySettings == ProxySettings.STATIC && mProxyHostView != null) {
+ final int selectedPosition = mProxySettingsSpinner.getSelectedItemPosition();
+ mProxySettings = ProxySettings.NONE;
+ mHttpProxy = null;
+ if (selectedPosition == PROXY_STATIC && mProxyHostView != null) {
+ mProxySettings = ProxySettings.STATIC;
String host = mProxyHostView.getText().toString();
String portStr = mProxyPortView.getText().toString();
String exclusionList = mProxyExclusionListView.getText().toString();
@@ -467,25 +512,41 @@ public class WifiConfigController implements TextWatcher,
result = R.string.proxy_error_invalid_port;
}
if (result == 0) {
- ProxyProperties proxyProperties= new ProxyProperties(host, port, exclusionList);
- mLinkProperties.setHttpProxy(proxyProperties);
+ mHttpProxy = new ProxyInfo(host, port, exclusionList);
} else {
return false;
}
+ } else if (selectedPosition == PROXY_PAC && mProxyPacView != null) {
+ mProxySettings = ProxySettings.PAC;
+ CharSequence uriSequence = mProxyPacView.getText();
+ if (TextUtils.isEmpty(uriSequence)) {
+ return false;
+ }
+ Uri uri = Uri.parse(uriSequence.toString());
+ if (uri == null) {
+ return false;
+ }
+ mHttpProxy = new ProxyInfo(uri);
}
return true;
}
- private int validateIpConfigFields(LinkProperties linkProperties) {
+ private Inet4Address getIPv4Address(String text) {
+ try {
+ return (Inet4Address) NetworkUtils.numericToInetAddress(text);
+ } catch (IllegalArgumentException|ClassCastException e) {
+ return null;
+ }
+ }
+
+ private int validateIpConfigFields(StaticIpConfiguration staticIpConfiguration) {
if (mIpAddressView == null) return 0;
String ipAddr = mIpAddressView.getText().toString();
if (TextUtils.isEmpty(ipAddr)) return R.string.wifi_ip_settings_invalid_ip_address;
- InetAddress inetAddr = null;
- try {
- inetAddr = NetworkUtils.numericToInetAddress(ipAddr);
- } catch (IllegalArgumentException e) {
+ Inet4Address inetAddr = getIPv4Address(ipAddr);
+ if (inetAddr == null) {
return R.string.wifi_ip_settings_invalid_ip_address;
}
@@ -495,7 +556,7 @@ public class WifiConfigController implements TextWatcher,
if (networkPrefixLength < 0 || networkPrefixLength > 32) {
return R.string.wifi_ip_settings_invalid_network_prefix_length;
}
- linkProperties.addLinkAddress(new LinkAddress(inetAddr, networkPrefixLength));
+ staticIpConfiguration.ipAddress = new LinkAddress(inetAddr, networkPrefixLength);
} catch (NumberFormatException e) {
// Set the hint as default after user types in ip address
mNetworkPrefixLengthView.setText(mConfigUi.getContext().getString(
@@ -514,13 +575,11 @@ public class WifiConfigController implements TextWatcher,
} catch (java.net.UnknownHostException u) {
}
} else {
- InetAddress gatewayAddr = null;
- try {
- gatewayAddr = NetworkUtils.numericToInetAddress(gateway);
- } catch (IllegalArgumentException e) {
+ InetAddress gatewayAddr = getIPv4Address(gateway);
+ if (gatewayAddr == null) {
return R.string.wifi_ip_settings_invalid_gateway;
}
- linkProperties.addRoute(new RouteInfo(gatewayAddr));
+ staticIpConfiguration.gateway = gatewayAddr;
}
String dns = mDns1View.getText().toString();
@@ -530,22 +589,20 @@ public class WifiConfigController implements TextWatcher,
//If everything else is valid, provide hint as a default option
mDns1View.setText(mConfigUi.getContext().getString(R.string.wifi_dns1_hint));
} else {
- try {
- dnsAddr = NetworkUtils.numericToInetAddress(dns);
- } catch (IllegalArgumentException e) {
+ dnsAddr = getIPv4Address(dns);
+ if (dnsAddr == null) {
return R.string.wifi_ip_settings_invalid_dns;
}
- linkProperties.addDns(dnsAddr);
+ staticIpConfiguration.dnsServers.add(dnsAddr);
}
if (mDns2View.length() > 0) {
dns = mDns2View.getText().toString();
- try {
- dnsAddr = NetworkUtils.numericToInetAddress(dns);
- } catch (IllegalArgumentException e) {
+ dnsAddr = getIPv4Address(dns);
+ if (dnsAddr == null) {
return R.string.wifi_ip_settings_invalid_dns;
}
- linkProperties.addDns(dnsAddr);
+ staticIpConfiguration.dnsServers.add(dnsAddr);
}
return 0;
}
@@ -756,28 +813,26 @@ public class WifiConfigController implements TextWatcher,
mDns2View.addTextChangedListener(this);
}
if (config != null) {
- LinkProperties linkProperties = config.linkProperties;
- Iterator<LinkAddress> iterator = linkProperties.getLinkAddresses().iterator();
- if (iterator.hasNext()) {
- LinkAddress linkAddress = iterator.next();
- mIpAddressView.setText(linkAddress.getAddress().getHostAddress());
- mNetworkPrefixLengthView.setText(Integer.toString(linkAddress
- .getNetworkPrefixLength()));
- }
+ StaticIpConfiguration staticConfig = config.getStaticIpConfiguration();
+ if (staticConfig != null) {
+ if (staticConfig.ipAddress != null) {
+ mIpAddressView.setText(
+ staticConfig.ipAddress.getAddress().getHostAddress());
+ mNetworkPrefixLengthView.setText(Integer.toString(staticConfig.ipAddress
+ .getNetworkPrefixLength()));
+ }
- for (RouteInfo route : linkProperties.getRoutes()) {
- if (route.isDefaultRoute()) {
- mGatewayView.setText(route.getGateway().getHostAddress());
- break;
+ if (staticConfig.gateway != null) {
+ mGatewayView.setText(staticConfig.gateway.getHostAddress());
}
- }
- Iterator<InetAddress> dnsIterator = linkProperties.getDnses().iterator();
- if (dnsIterator.hasNext()) {
- mDns1View.setText(dnsIterator.next().getHostAddress());
- }
- if (dnsIterator.hasNext()) {
- mDns2View.setText(dnsIterator.next().getHostAddress());
+ Iterator<InetAddress> dnsIterator = staticConfig.dnsServers.iterator();
+ if (dnsIterator.hasNext()) {
+ mDns1View.setText(dnsIterator.next().getHostAddress());
+ }
+ if (dnsIterator.hasNext()) {
+ mDns2View.setText(dnsIterator.next().getHostAddress());
+ }
}
}
} else {
@@ -795,8 +850,9 @@ public class WifiConfigController implements TextWatcher,
}
if (mProxySettingsSpinner.getSelectedItemPosition() == PROXY_STATIC) {
- mView.findViewById(R.id.proxy_warning_limited_support).setVisibility(View.VISIBLE);
- mView.findViewById(R.id.proxy_fields).setVisibility(View.VISIBLE);
+ setVisibility(R.id.proxy_warning_limited_support, View.VISIBLE);
+ setVisibility(R.id.proxy_fields, View.VISIBLE);
+ setVisibility(R.id.proxy_pac_field, View.GONE);
if (mProxyHostView == null) {
mProxyHostView = (TextView) mView.findViewById(R.id.proxy_hostname);
mProxyHostView.addTextChangedListener(this);
@@ -806,20 +862,41 @@ public class WifiConfigController implements TextWatcher,
mProxyExclusionListView.addTextChangedListener(this);
}
if (config != null) {
- ProxyProperties proxyProperties = config.linkProperties.getHttpProxy();
+ ProxyInfo proxyProperties = config.getHttpProxy();
if (proxyProperties != null) {
mProxyHostView.setText(proxyProperties.getHost());
mProxyPortView.setText(Integer.toString(proxyProperties.getPort()));
- mProxyExclusionListView.setText(proxyProperties.getExclusionList());
+ mProxyExclusionListView.setText(proxyProperties.getExclusionListAsString());
+ }
+ }
+ } else if (mProxySettingsSpinner.getSelectedItemPosition() == PROXY_PAC) {
+ setVisibility(R.id.proxy_warning_limited_support, View.GONE);
+ setVisibility(R.id.proxy_fields, View.GONE);
+ setVisibility(R.id.proxy_pac_field, View.VISIBLE);
+
+ if (mProxyPacView == null) {
+ mProxyPacView = (TextView) mView.findViewById(R.id.proxy_pac);
+ mProxyPacView.addTextChangedListener(this);
+ }
+ if (config != null) {
+ ProxyInfo proxyInfo = config.getHttpProxy();
+ if (proxyInfo != null) {
+ mProxyPacView.setText(proxyInfo.getPacFileUrl().toString());
}
}
} else {
- mView.findViewById(R.id.proxy_warning_limited_support).setVisibility(View.GONE);
- mView.findViewById(R.id.proxy_fields).setVisibility(View.GONE);
+ setVisibility(R.id.proxy_warning_limited_support, View.GONE);
+ setVisibility(R.id.proxy_fields, View.GONE);
+ setVisibility(R.id.proxy_pac_field, View.GONE);
}
}
-
+ private void setVisibility(int id, int visibility) {
+ final View v = mView.findViewById(id);
+ if (v != null) {
+ v.setVisibility(visibility);
+ }
+ }
private void loadCertificates(Spinner spinner, String prefix) {
final Context context = mConfigUi.getContext();
diff --git a/src/com/android/settings/wifi/WifiDialog.java b/src/com/android/settings/wifi/WifiDialog.java
index f1720c1..942c5dd 100644
--- a/src/com/android/settings/wifi/WifiDialog.java
+++ b/src/com/android/settings/wifi/WifiDialog.java
@@ -35,6 +35,13 @@ class WifiDialog extends AlertDialog implements WifiConfigUiBase {
private View mView;
private WifiConfigController mController;
+ private boolean mHideSubmitButton;
+
+ public WifiDialog(Context context, DialogInterface.OnClickListener listener,
+ AccessPoint accessPoint, boolean edit, boolean hideSubmitButton) {
+ this(context, listener, accessPoint, edit);
+ mHideSubmitButton = hideSubmitButton;
+ }
public WifiDialog(Context context, DialogInterface.OnClickListener listener,
AccessPoint accessPoint, boolean edit) {
@@ -42,6 +49,7 @@ class WifiDialog extends AlertDialog implements WifiConfigUiBase {
mEdit = edit;
mListener = listener;
mAccessPoint = accessPoint;
+ mHideSubmitButton = false;
}
@Override
@@ -56,9 +64,14 @@ class WifiDialog extends AlertDialog implements WifiConfigUiBase {
setInverseBackgroundForced(true);
mController = new WifiConfigController(this, mView, mAccessPoint, mEdit);
super.onCreate(savedInstanceState);
- /* During creation, the submit button can be unavailable to determine
- * visibility. Right after creation, update button visibility */
- mController.enableSubmitIfAppropriate();
+
+ if (mHideSubmitButton) {
+ mController.hideSubmitButton();
+ } else {
+ /* During creation, the submit button can be unavailable to determine
+ * visibility. Right after creation, update button visibility */
+ mController.enableSubmitIfAppropriate();
+ }
}
@Override
diff --git a/src/com/android/settings/wifi/WifiEnabler.java b/src/com/android/settings/wifi/WifiEnabler.java
index a6989f0..0952941 100644
--- a/src/com/android/settings/wifi/WifiEnabler.java
+++ b/src/com/android/settings/wifi/WifiEnabler.java
@@ -20,25 +20,27 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.database.ContentObserver;
import android.net.NetworkInfo;
import android.net.wifi.SupplicantState;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
-import android.os.UserHandle;
+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.settings.R;
import com.android.settings.WirelessSettings;
+import com.android.settings.search.Index;
+import com.android.settings.widget.SwitchBar;
import java.util.concurrent.atomic.AtomicBoolean;
-public class WifiEnabler implements CompoundButton.OnCheckedChangeListener {
- private final Context mContext;
- private Switch mSwitch;
+public class WifiEnabler implements SwitchBar.OnSwitchChangeListener {
+ private Context mContext;
+ private SwitchBar mSwitchBar;
+ private boolean mListeningToOnSwitchChange = false;
private AtomicBoolean mConnected = new AtomicBoolean(false);
private final WifiManager mWifiManager;
@@ -65,98 +67,110 @@ public class WifiEnabler implements CompoundButton.OnCheckedChangeListener {
}
};
- public WifiEnabler(Context context, Switch switch_) {
+ private static final String EVENT_DATA_IS_WIFI_ON = "is_wifi_on";
+ private static final int EVENT_UPDATE_INDEX = 0;
+
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_UPDATE_INDEX:
+ final boolean isWiFiOn = msg.getData().getBoolean(EVENT_DATA_IS_WIFI_ON);
+ Index.getInstance(mContext).updateFromClassNameResource(
+ WifiSettings.class.getName(), true, isWiFiOn);
+ break;
+ }
+ }
+ };
+
+ public WifiEnabler(Context context, SwitchBar switchBar) {
mContext = context;
- mSwitch = switch_;
+ mSwitchBar = switchBar;
mWifiManager = (WifiManager) context.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);
- }
- public void resume() {
- // Wi-Fi state is sticky, so just let the receiver update UI
- mContext.registerReceiver(mReceiver, mIntentFilter);
- mSwitch.setOnCheckedChangeListener(this);
- }
-
- public void pause() {
- mContext.unregisterReceiver(mReceiver);
- mSwitch.setOnCheckedChangeListener(null);
+ setupSwitchBar();
}
- public void setSwitch(Switch switch_) {
- if (mSwitch == switch_) return;
- mSwitch.setOnCheckedChangeListener(null);
- mSwitch = switch_;
- mSwitch.setOnCheckedChangeListener(this);
-
- final int wifiState = mWifiManager.getWifiState();
- boolean isEnabled = wifiState == WifiManager.WIFI_STATE_ENABLED;
- boolean isDisabled = wifiState == WifiManager.WIFI_STATE_DISABLED;
- mSwitch.setChecked(isEnabled);
- mSwitch.setEnabled(isEnabled || isDisabled);
+ public void setupSwitchBar() {
+ final int state = mWifiManager.getWifiState();
+ handleWifiStateChanged(state);
+ if (!mListeningToOnSwitchChange) {
+ mSwitchBar.addOnSwitchChangeListener(this);
+ mListeningToOnSwitchChange = true;
+ }
+ mSwitchBar.show();
}
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- //Do nothing if called as a result of a state machine event
- if (mStateMachineEvent) {
- return;
- }
- // Show toast message if Wi-Fi is not allowed in airplane mode
- if (isChecked && !WirelessSettings.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.
- buttonView.setChecked(false);
- return;
+ public void teardownSwitchBar() {
+ if (mListeningToOnSwitchChange) {
+ mSwitchBar.removeOnSwitchChangeListener(this);
+ mListeningToOnSwitchChange = false;
}
+ mSwitchBar.hide();
+ }
- // Disable tethering if enabling Wifi
- int wifiApState = mWifiManager.getWifiApState();
- if (isChecked && ((wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) ||
- (wifiApState == WifiManager.WIFI_AP_STATE_ENABLED))) {
- mWifiManager.setWifiApEnabled(null, false);
+ public void resume(Context context) {
+ mContext = context;
+ // Wi-Fi state is sticky, so just let the receiver update UI
+ mContext.registerReceiver(mReceiver, mIntentFilter);
+ if (!mListeningToOnSwitchChange) {
+ mSwitchBar.addOnSwitchChangeListener(this);
+ mListeningToOnSwitchChange = true;
}
+ }
- mSwitch.setEnabled(false);
- if (!mWifiManager.setWifiEnabled(isChecked)) {
- // Error
- mSwitch.setEnabled(true);
- Toast.makeText(mContext, R.string.wifi_error, Toast.LENGTH_SHORT).show();
+ public void pause() {
+ mContext.unregisterReceiver(mReceiver);
+ if (mListeningToOnSwitchChange) {
+ mSwitchBar.removeOnSwitchChangeListener(this);
+ mListeningToOnSwitchChange = false;
}
}
private void handleWifiStateChanged(int state) {
switch (state) {
case WifiManager.WIFI_STATE_ENABLING:
- mSwitch.setEnabled(false);
+ mSwitchBar.setEnabled(false);
break;
case WifiManager.WIFI_STATE_ENABLED:
- setSwitchChecked(true);
- mSwitch.setEnabled(true);
+ setSwitchBarChecked(true);
+ mSwitchBar.setEnabled(true);
+ updateSearchIndex(true);
break;
case WifiManager.WIFI_STATE_DISABLING:
- mSwitch.setEnabled(false);
+ mSwitchBar.setEnabled(false);
break;
case WifiManager.WIFI_STATE_DISABLED:
- setSwitchChecked(false);
- mSwitch.setEnabled(true);
+ setSwitchBarChecked(false);
+ mSwitchBar.setEnabled(true);
+ updateSearchIndex(false);
break;
default:
- setSwitchChecked(false);
- mSwitch.setEnabled(true);
- break;
+ setSwitchBarChecked(false);
+ mSwitchBar.setEnabled(true);
+ updateSearchIndex(false);
}
}
- private void setSwitchChecked(boolean checked) {
- if (checked != mSwitch.isChecked()) {
- mStateMachineEvent = true;
- mSwitch.setChecked(checked);
- mStateMachineEvent = false;
- }
+ private void updateSearchIndex(boolean isWiFiOn) {
+ mHandler.removeMessages(EVENT_UPDATE_INDEX);
+
+ Message msg = new Message();
+ msg.what = EVENT_UPDATE_INDEX;
+ msg.getData().putBoolean(EVENT_DATA_IS_WIFI_ON, isWiFiOn);
+ mHandler.sendMessage(msg);
+ }
+
+ private void setSwitchBarChecked(boolean checked) {
+ mStateMachineEvent = true;
+ mSwitchBar.setChecked(checked);
+ mStateMachineEvent = false;
}
private void handleStateChanged(@SuppressWarnings("unused") NetworkInfo.DetailedState state) {
@@ -174,4 +188,32 @@ public class WifiEnabler implements CompoundButton.OnCheckedChangeListener {
}
*/
}
+
+ @Override
+ public void onSwitchChanged(Switch switchView, boolean isChecked) {
+ //Do nothing if called as a result of a state machine event
+ if (mStateMachineEvent) {
+ return;
+ }
+ // Show toast message if Wi-Fi is not allowed in airplane mode
+ if (isChecked && !WirelessSettings.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);
+ return;
+ }
+
+ // Disable tethering if enabling Wifi
+ int wifiApState = mWifiManager.getWifiApState();
+ if (isChecked && ((wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) ||
+ (wifiApState == WifiManager.WIFI_AP_STATE_ENABLED))) {
+ mWifiManager.setWifiApEnabled(null, false);
+ }
+
+ if (!mWifiManager.setWifiEnabled(isChecked)) {
+ // Error
+ mSwitchBar.setEnabled(true);
+ Toast.makeText(mContext, R.string.wifi_error, Toast.LENGTH_SHORT).show();
+ }
+ }
}
diff --git a/src/com/android/settings/wifi/WifiPickerActivity.java b/src/com/android/settings/wifi/WifiPickerActivity.java
index e1e7c51..5bdceb9 100644
--- a/src/com/android/settings/wifi/WifiPickerActivity.java
+++ b/src/com/android/settings/wifi/WifiPickerActivity.java
@@ -16,31 +16,24 @@
package com.android.settings.wifi;
import com.android.settings.ButtonBarHandler;
-import com.android.settings.ChooseLockGeneric.ChooseLockGenericFragment;
+import com.android.settings.SettingsActivity;
import com.android.settings.wifi.p2p.WifiP2pSettings;
+import com.android.settings.R;
-import android.app.Fragment;
import android.content.Intent;
-import android.os.Bundle;
-import android.preference.PreferenceActivity;
-import android.widget.Button;
+import android.preference.PreferenceFragment;
-public class WifiPickerActivity extends PreferenceActivity implements ButtonBarHandler {
+import java.lang.Class;
- // Same as what are in PreferenceActivity as private.
- private static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar";
- private static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text";
- private static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text";
- private static final String EXTRA_WIFI_SHOW_ACTION_BAR = "wifi_show_action_bar";
- private static final String EXTRA_WIFI_SHOW_MENUS = "wifi_show_menus";
+public class WifiPickerActivity extends SettingsActivity implements ButtonBarHandler {
@Override
public Intent getIntent() {
Intent modIntent = new Intent(super.getIntent());
if (!modIntent.hasExtra(EXTRA_SHOW_FRAGMENT)) {
- modIntent.putExtra(EXTRA_SHOW_FRAGMENT, WifiSettings.class.getName());
+ modIntent.putExtra(EXTRA_SHOW_FRAGMENT, getWifiSettingsClass().getName());
+ modIntent.putExtra(EXTRA_SHOW_FRAGMENT_TITLE_RESID, R.string.wifi_select_network);
}
- modIntent.putExtra(EXTRA_NO_HEADERS, true);
return modIntent;
}
@@ -48,62 +41,12 @@ public class WifiPickerActivity extends PreferenceActivity implements ButtonBarH
protected boolean isValidFragment(String fragmentName) {
if (WifiSettings.class.getName().equals(fragmentName)
|| WifiP2pSettings.class.getName().equals(fragmentName)
+ || SavedAccessPointsWifiSettings.class.getName().equals(fragmentName)
|| AdvancedWifiSettings.class.getName().equals(fragmentName)) return true;
return false;
}
- /**
- * Almost dead copy of
- * {@link PreferenceActivity#startWithFragment(String, Bundle, Fragment, int)}, except
- * this has additional codes for button bar handling.
- */
- @Override
- public void startWithFragment(String fragmentName, Bundle args,
- Fragment resultTo, int resultRequestCode) {
- Intent intent = new Intent(Intent.ACTION_MAIN);
- intent.setClass(this, getClass());
- intent.putExtra(EXTRA_SHOW_FRAGMENT, fragmentName);
- intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
- intent.putExtra(EXTRA_NO_HEADERS, true);
-
- final Intent orgIntent = getIntent();
- if (orgIntent.hasExtra(EXTRA_PREFS_SHOW_BUTTON_BAR)) {
- intent.putExtra(EXTRA_PREFS_SHOW_BUTTON_BAR,
- orgIntent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false));
- }
- if (orgIntent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
- intent.putExtra(EXTRA_PREFS_SET_NEXT_TEXT,
- orgIntent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT));
- }
- if (orgIntent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
- intent.putExtra(EXTRA_PREFS_SET_BACK_TEXT,
- orgIntent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT));
- }
- if (orgIntent.hasExtra(EXTRA_WIFI_SHOW_ACTION_BAR)) {
- intent.putExtra(EXTRA_WIFI_SHOW_ACTION_BAR,
- orgIntent.getBooleanExtra(EXTRA_WIFI_SHOW_ACTION_BAR, true));
- }
- if (orgIntent.hasExtra(EXTRA_WIFI_SHOW_MENUS)) {
- intent.putExtra(EXTRA_WIFI_SHOW_MENUS,
- orgIntent.getBooleanExtra(EXTRA_WIFI_SHOW_MENUS, true));
- }
-
- if (resultTo == null) {
- startActivity(intent);
- } else {
- resultTo.startActivityForResult(intent, resultRequestCode);
- }
- }
-
- @Override
- public boolean hasNextButton() {
- // PreferenceActivity#hasNextButton() is protected, so we need to expose it here.
- return super.hasNextButton();
- }
-
- @Override
- public Button getNextButton() {
- // PreferenceActivity#getNextButton() is protected, so we need to expose it here.
- return super.getNextButton();
+ /* package */ Class<? extends PreferenceFragment> getWifiSettingsClass() {
+ return WifiSettings.class;
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/settings/wifi/WifiSettings.java b/src/com/android/settings/wifi/WifiSettings.java
index 1caf58b..6c58bc1 100644
--- a/src/com/android/settings/wifi/WifiSettings.java
+++ b/src/com/android/settings/wifi/WifiSettings.java
@@ -19,61 +19,57 @@ package com.android.settings.wifi;
import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
import static android.os.UserManager.DISALLOW_CONFIG_WIFI;
-import com.android.settings.R;
-import com.android.settings.RestrictedSettingsFragment;
-import com.android.settings.wifi.p2p.WifiP2pSettings;
-
-import android.app.ActionBar;
import android.app.Activity;
-import android.app.AlertDialog;
+import android.app.ActivityManager;
import android.app.Dialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.PackageManager;
+import android.content.SharedPreferences;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.location.LocationManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkScoreManager;
+import android.net.NetworkScorerAppManager;
+import android.net.NetworkScorerAppManager.NetworkScorerAppData;
import android.net.wifi.ScanResult;
-import android.net.wifi.SupplicantState;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WpsInfo;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.os.UserHandle;
import android.preference.Preference;
-import android.preference.PreferenceActivity;
import android.preference.PreferenceScreen;
-import android.provider.Settings;
-import android.util.AttributeSet;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
-import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
-import android.view.ViewGroup;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.Button;
-import android.widget.ImageButton;
-import android.widget.PopupMenu;
-import android.widget.PopupMenu.OnMenuItemClickListener;
-import android.widget.RelativeLayout;
-import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;
+import com.android.settings.R;
+import com.android.settings.RestrictedSettingsFragment;
+import com.android.settings.SettingsActivity;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.search.Indexable;
+import com.android.settings.search.SearchIndexableRaw;
+
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -90,23 +86,29 @@ import java.util.concurrent.atomic.AtomicBoolean;
* and menus.
*/
public class WifiSettings extends RestrictedSettingsFragment
- implements DialogInterface.OnClickListener {
+ implements DialogInterface.OnClickListener, Indexable {
+
private static final String TAG = "WifiSettings";
- private static final int MENU_ID_WPS_PBC = Menu.FIRST;
+
+ private static final int REQUEST_ENABLE_WIFI_ASSISTANT = 1;
+
+ /* package */ static final int MENU_ID_WPS_PBC = Menu.FIRST;
private static final int MENU_ID_WPS_PIN = Menu.FIRST + 1;
- private static final int MENU_ID_P2P = Menu.FIRST + 2;
- private static final int MENU_ID_ADD_NETWORK = Menu.FIRST + 3;
+ private static final int MENU_ID_SAVED_NETWORK = Menu.FIRST + 2;
+ /* package */ static final int MENU_ID_ADD_NETWORK = Menu.FIRST + 3;
private static final int MENU_ID_ADVANCED = Menu.FIRST + 4;
private static final int MENU_ID_SCAN = Menu.FIRST + 5;
private static final int MENU_ID_CONNECT = Menu.FIRST + 6;
private static final int MENU_ID_FORGET = Menu.FIRST + 7;
private static final int MENU_ID_MODIFY = Menu.FIRST + 8;
+ private static final int MENU_ID_WRITE_NFC = Menu.FIRST + 9;
- private static final int WIFI_DIALOG_ID = 1;
- private static final int WPS_PBC_DIALOG_ID = 2;
+ private static final String KEY_ASSISTANT_DISMISS_PLATFORM = "assistant_dismiss_platform";
+
+ public static final int WIFI_DIALOG_ID = 1;
+ /* package */ static final int WPS_PBC_DIALOG_ID = 2;
private static final int WPS_PIN_DIALOG_ID = 3;
- private static final int WIFI_SKIPPED_DIALOG_ID = 4;
- private static final int WIFI_AND_MOBILE_SKIPPED_DIALOG_ID = 5;
+ private static final int WRITE_NFC_DIALOG_ID = 6;
// Combo scans can take 5-6s to complete - set to 10s.
private static final int WIFI_RESCAN_INTERVAL_MS = 10 * 1000;
@@ -115,18 +117,16 @@ public class WifiSettings extends RestrictedSettingsFragment
private static final String SAVE_DIALOG_EDIT_MODE = "edit_mode";
private static final String SAVE_DIALOG_ACCESS_POINT_STATE = "wifi_ap_state";
- // Activity result when pressing the Skip button
- private static final int RESULT_SKIP = Activity.RESULT_FIRST_USER;
+ private static boolean savedNetworksExist;
private final IntentFilter mFilter;
private final BroadcastReceiver mReceiver;
private final Scanner mScanner;
- private WifiManager mWifiManager;
+ /* package */ WifiManager mWifiManager;
private WifiManager.ActionListener mConnectListener;
private WifiManager.ActionListener mSaveListener;
private WifiManager.ActionListener mForgetListener;
- private boolean mP2pSupported;
private WifiEnabler mWifiEnabler;
// An access point being editted is stored here.
@@ -138,42 +138,89 @@ public class WifiSettings extends RestrictedSettingsFragment
private final AtomicBoolean mConnected = new AtomicBoolean(false);
private WifiDialog mDialog;
+ private WriteWifiConfigToNfcDialog mWifiToNfcDialog;
private TextView mEmptyView;
- /* Used in Wifi Setup context */
-
- // this boolean extra specifies whether to disable the Next button when not connected
+ // this boolean extra specifies whether to disable the Next button when not connected. Used by
+ // account creation outside of setup wizard.
private static final String EXTRA_ENABLE_NEXT_ON_CONNECT = "wifi_enable_next_on_connect";
- // this boolean extra specifies whether to auto finish when connection is established
- private static final String EXTRA_AUTO_FINISH_ON_CONNECT = "wifi_auto_finish_on_connect";
-
- // this boolean extra shows a custom button that we can control
- protected static final String EXTRA_SHOW_CUSTOM_BUTTON = "wifi_show_custom_button";
-
- // show a text regarding data charges when wifi connection is required during setup wizard
- protected static final String EXTRA_SHOW_WIFI_REQUIRED_INFO = "wifi_show_wifi_required_info";
-
- // this boolean extra is set if we are being invoked by the Setup Wizard
- private static final String EXTRA_IS_FIRST_RUN = "firstRun";
-
// should Next button only be enabled when we have a connection?
private boolean mEnableNextOnConnection;
- // should activity finish once we have a connection?
- private boolean mAutoFinishOnConnection;
-
// Save the dialog details
private boolean mDlgEdit;
private AccessPoint mDlgAccessPoint;
private Bundle mAccessPointSavedState;
+ private View mWifiAssistantCard;
+ private NetworkScorerAppData mWifiAssistantApp;
- // the action bar uses a different set of controls for Setup Wizard
- private boolean mSetupWizardMode;
+ /** verbose logging flag. this flag is set thru developer debugging options
+ * and used so as to assist with in-the-field WiFi connectivity debugging */
+ public static int mVerboseLogging = 0;
/* End of "used in Wifi Setup context" */
+ /** A restricted multimap for use in constructAccessPoints */
+ private static class Multimap<K,V> {
+ private final HashMap<K,List<V>> store = new HashMap<K,List<V>>();
+ /** retrieve a non-null list of values with key K */
+ List<V> getAll(K key) {
+ List<V> values = store.get(key);
+ return values != null ? values : Collections.<V>emptyList();
+ }
+
+ void put(K key, V val) {
+ List<V> curVals = store.get(key);
+ if (curVals == null) {
+ curVals = new ArrayList<V>(3);
+ store.put(key, curVals);
+ }
+ curVals.add(val);
+ }
+ }
+
+ private static class Scanner extends Handler {
+ private int mRetry = 0;
+ private WifiSettings mWifiSettings = null;
+
+ Scanner(WifiSettings wifiSettings) {
+ mWifiSettings = wifiSettings;
+ }
+
+ void resume() {
+ if (!hasMessages(0)) {
+ sendEmptyMessage(0);
+ }
+ }
+
+ void forceScan() {
+ removeMessages(0);
+ sendEmptyMessage(0);
+ }
+
+ void pause() {
+ mRetry = 0;
+ removeMessages(0);
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ if (mWifiSettings.mWifiManager.startScan()) {
+ mRetry = 0;
+ } else if (++mRetry >= 3) {
+ mRetry = 0;
+ Activity activity = mWifiSettings.getActivity();
+ if (activity != null) {
+ Toast.makeText(activity, R.string.wifi_fail_to_scan, Toast.LENGTH_LONG).show();
+ }
+ return;
+ }
+ sendEmptyMessageDelayed(0, WIFI_RESCAN_INTERVAL_MS);
+ }
+ }
+
public WifiSettings() {
super(DISALLOW_CONFIG_WIFI);
mFilter = new IntentFilter();
@@ -189,105 +236,17 @@ public class WifiSettings extends RestrictedSettingsFragment
mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- handleEvent(context, intent);
+ handleEvent(intent);
}
};
- mScanner = new Scanner();
- }
-
- @Override
- public void onCreate(Bundle icicle) {
- // Set this flag early, as it's needed by getHelpResource(), which is called by super
- mSetupWizardMode = getActivity().getIntent().getBooleanExtra(EXTRA_IS_FIRST_RUN, false);
-
- super.onCreate(icicle);
- }
-
- @Override
- public View onCreateView(final LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- if (mSetupWizardMode) {
- View view = inflater.inflate(R.layout.setup_preference, container, false);
- View other = view.findViewById(R.id.other_network);
- other.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- if (mWifiManager.isWifiEnabled()) {
- onAddNetworkPressed();
- }
- }
- });
- final ImageButton b = (ImageButton) view.findViewById(R.id.more);
- if (b != null) {
- b.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- if (mWifiManager.isWifiEnabled()) {
- PopupMenu pm = new PopupMenu(inflater.getContext(), b);
- pm.inflate(R.menu.wifi_setup);
- pm.setOnMenuItemClickListener(new OnMenuItemClickListener() {
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- if (R.id.wifi_wps == item.getItemId()) {
- showDialog(WPS_PBC_DIALOG_ID);
- return true;
- }
- return false;
- }
- });
- pm.show();
- }
- }
- });
- }
-
- Intent intent = getActivity().getIntent();
- if (intent.getBooleanExtra(EXTRA_SHOW_CUSTOM_BUTTON, false)) {
- view.findViewById(R.id.button_bar).setVisibility(View.VISIBLE);
- view.findViewById(R.id.back_button).setVisibility(View.INVISIBLE);
- view.findViewById(R.id.skip_button).setVisibility(View.INVISIBLE);
- view.findViewById(R.id.next_button).setVisibility(View.INVISIBLE);
-
- Button customButton = (Button) view.findViewById(R.id.custom_button);
- customButton.setVisibility(View.VISIBLE);
- customButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- boolean isConnected = false;
- Activity activity = getActivity();
- final ConnectivityManager connectivity = (ConnectivityManager)
- activity.getSystemService(Context.CONNECTIVITY_SERVICE);
- if (connectivity != null) {
- final NetworkInfo info = connectivity.getActiveNetworkInfo();
- isConnected = (info != null) && info.isConnected();
- }
- if (isConnected) {
- // Warn of possible data charges
- showDialog(WIFI_SKIPPED_DIALOG_ID);
- } else {
- // Warn of lack of updates
- showDialog(WIFI_AND_MOBILE_SKIPPED_DIALOG_ID);
- }
- }
- });
- }
-
- if (intent.getBooleanExtra(EXTRA_SHOW_WIFI_REQUIRED_INFO, false)) {
- view.findViewById(R.id.wifi_required_info).setVisibility(View.VISIBLE);
- }
-
- return view;
- } else {
- return super.onCreateView(inflater, container, savedInstanceState);
- }
+ mScanner = new Scanner(this);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
- mP2pSupported = getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT);
mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
mConnectListener = new WifiManager.ActionListener() {
@@ -335,42 +294,23 @@ public class WifiSettings extends RestrictedSettingsFragment
}
};
- if (savedInstanceState != null
- && savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) {
+ if (savedInstanceState != null) {
mDlgEdit = savedInstanceState.getBoolean(SAVE_DIALOG_EDIT_MODE);
- mAccessPointSavedState = savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE);
- }
-
- final Activity activity = getActivity();
- final Intent intent = activity.getIntent();
-
- // first if we're supposed to finish once we have a connection
- mAutoFinishOnConnection = intent.getBooleanExtra(EXTRA_AUTO_FINISH_ON_CONNECT, false);
-
- if (mAutoFinishOnConnection) {
- // Hide the next button
- if (hasNextButton()) {
- getNextButton().setVisibility(View.GONE);
- }
-
- final ConnectivityManager connectivity = (ConnectivityManager)
- activity.getSystemService(Context.CONNECTIVITY_SERVICE);
- if (connectivity != null
- && connectivity.getNetworkInfo(ConnectivityManager.TYPE_WIFI).isConnected()) {
- activity.setResult(Activity.RESULT_OK);
- activity.finish();
- return;
+ if (savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) {
+ mAccessPointSavedState =
+ savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE);
}
}
// if we're supposed to enable/disable the Next button based on our current connection
// state, start it off in the right state
+ Intent intent = getActivity().getIntent();
mEnableNextOnConnection = intent.getBooleanExtra(EXTRA_ENABLE_NEXT_ON_CONNECT, false);
if (mEnableNextOnConnection) {
if (hasNextButton()) {
final ConnectivityManager connectivity = (ConnectivityManager)
- activity.getSystemService(Context.CONNECTIVITY_SERVICE);
+ getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivity != null) {
NetworkInfo info = connectivity.getNetworkInfo(
ConnectivityManager.TYPE_WIFI);
@@ -381,54 +321,60 @@ public class WifiSettings extends RestrictedSettingsFragment
addPreferencesFromResource(R.xml.wifi_settings);
- if (mSetupWizardMode) {
- getView().setSystemUiVisibility(
-// View.STATUS_BAR_DISABLE_BACK |
- View.STATUS_BAR_DISABLE_HOME |
- View.STATUS_BAR_DISABLE_RECENT |
- View.STATUS_BAR_DISABLE_NOTIFICATION_ALERTS |
- View.STATUS_BAR_DISABLE_CLOCK);
- }
+ prepareWifiAssistantCard();
- // On/off switch is hidden for Setup Wizard
- if (!mSetupWizardMode) {
- Switch actionBarSwitch = new Switch(activity);
-
- if (activity instanceof PreferenceActivity) {
- PreferenceActivity preferenceActivity = (PreferenceActivity) activity;
- if (preferenceActivity.onIsHidingHeaders() || !preferenceActivity.onIsMultiPane()) {
- final int padding = activity.getResources().getDimensionPixelSize(
- R.dimen.action_bar_switch_padding);
- actionBarSwitch.setPaddingRelative(0, 0, padding, 0);
- activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
- ActionBar.DISPLAY_SHOW_CUSTOM);
- activity.getActionBar().setCustomView(actionBarSwitch, new ActionBar.LayoutParams(
- ActionBar.LayoutParams.WRAP_CONTENT,
- ActionBar.LayoutParams.WRAP_CONTENT,
- Gravity.CENTER_VERTICAL | Gravity.END));
- }
- }
+ mEmptyView = initEmptyView();
+ registerForContextMenu(getListView());
+ setHasOptionsMenu(true);
+ }
- mWifiEnabler = new WifiEnabler(activity, actionBarSwitch);
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
+ if (requestCode == REQUEST_ENABLE_WIFI_ASSISTANT) {
+ if (resultCode == Activity.RESULT_OK) {
+ disableWifiAssistantCardUntilPlatformUpgrade();
+ getListView().removeHeaderView(mWifiAssistantCard);
+ mWifiAssistantApp = null;
+ }
+ } else {
+ super.onActivityResult(requestCode, resultCode, resultData);
}
+ }
- mEmptyView = (TextView) getView().findViewById(android.R.id.empty);
- getListView().setEmptyView(mEmptyView);
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
- if (!mSetupWizardMode) {
- registerForContextMenu(getListView());
+ if (mWifiEnabler != null) {
+ mWifiEnabler.teardownSwitchBar();
}
- setHasOptionsMenu(true);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ // On/off switch is hidden for Setup Wizard (returns null)
+ mWifiEnabler = createWifiEnabler();
+ }
+
+ /**
+ * @return new WifiEnabler or null (as overridden by WifiSettingsForSetupWizard)
+ */
+ /* package */ WifiEnabler createWifiEnabler() {
+ final SettingsActivity activity = (SettingsActivity) getActivity();
+ return new WifiEnabler(activity, activity.getSwitchBar());
}
@Override
public void onResume() {
+ final Activity activity = getActivity();
super.onResume();
if (mWifiEnabler != null) {
- mWifiEnabler.resume();
+ mWifiEnabler.resume(activity);
}
- getActivity().registerReceiver(mReceiver, mFilter);
+ activity.registerReceiver(mReceiver, mFilter);
updateAccessPoints();
}
@@ -438,6 +384,7 @@ public class WifiSettings extends RestrictedSettingsFragment
if (mWifiEnabler != null) {
mWifiEnabler.pause();
}
+
getActivity().unregisterReceiver(mReceiver);
mScanner.pause();
}
@@ -445,46 +392,35 @@ public class WifiSettings extends RestrictedSettingsFragment
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
// If the user is not allowed to configure wifi, do not show the menu.
- if (isRestrictedAndNotPinProtected()) return;
+ if (isUiRestricted()) return;
+ addOptionsMenuItems(menu);
+ super.onCreateOptionsMenu(menu, inflater);
+ }
+
+ /**
+ * @param menu
+ */
+ void addOptionsMenuItems(Menu menu) {
final boolean wifiIsEnabled = mWifiManager.isWifiEnabled();
TypedArray ta = getActivity().getTheme().obtainStyledAttributes(
new int[] {R.attr.ic_menu_add, R.attr.ic_wps});
- if (mSetupWizardMode) {
- menu.add(Menu.NONE, MENU_ID_WPS_PBC, 0, R.string.wifi_menu_wps_pbc)
- .setIcon(ta.getDrawable(1))
- .setEnabled(wifiIsEnabled)
- .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
- menu.add(Menu.NONE, MENU_ID_ADD_NETWORK, 0, R.string.wifi_add_network)
- .setEnabled(wifiIsEnabled)
- .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
- } else {
- menu.add(Menu.NONE, MENU_ID_WPS_PBC, 0, R.string.wifi_menu_wps_pbc)
- .setIcon(ta.getDrawable(1))
- .setEnabled(wifiIsEnabled)
- .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
- menu.add(Menu.NONE, MENU_ID_ADD_NETWORK, 0, R.string.wifi_add_network)
+ menu.add(Menu.NONE, MENU_ID_ADD_NETWORK, 0, R.string.wifi_add_network)
+ .setIcon(ta.getDrawable(0))
+ .setEnabled(wifiIsEnabled)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+ if (savedNetworksExist) {
+ menu.add(Menu.NONE, MENU_ID_SAVED_NETWORK, 0, R.string.wifi_saved_access_points_label)
.setIcon(ta.getDrawable(0))
.setEnabled(wifiIsEnabled)
- .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
- menu.add(Menu.NONE, MENU_ID_SCAN, 0, R.string.wifi_menu_scan)
- //.setIcon(R.drawable.ic_menu_scan_network)
- .setEnabled(wifiIsEnabled)
- .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
- menu.add(Menu.NONE, MENU_ID_WPS_PIN, 0, R.string.wifi_menu_wps_pin)
- .setEnabled(wifiIsEnabled)
- .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
- if (mP2pSupported) {
- menu.add(Menu.NONE, MENU_ID_P2P, 0, R.string.wifi_menu_p2p)
- .setEnabled(wifiIsEnabled)
- .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
- }
- menu.add(Menu.NONE, MENU_ID_ADVANCED, 0, R.string.wifi_menu_advanced)
- //.setIcon(android.R.drawable.ic_menu_manage)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
}
+ menu.add(Menu.NONE, MENU_ID_SCAN, 0, R.string.menu_stats_refresh)
+ .setEnabled(wifiIsEnabled)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+ menu.add(Menu.NONE, MENU_ID_ADVANCED, 0, R.string.wifi_menu_advanced)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
ta.recycle();
- super.onCreateOptionsMenu(menu, inflater);
}
@Override
@@ -505,23 +441,26 @@ public class WifiSettings extends RestrictedSettingsFragment
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// If the user is not allowed to configure wifi, do not handle menu selections.
- if (isRestrictedAndNotPinProtected()) return false;
+ if (isUiRestricted()) return false;
switch (item.getItemId()) {
case MENU_ID_WPS_PBC:
showDialog(WPS_PBC_DIALOG_ID);
return true;
+ /*
case MENU_ID_P2P:
- if (getActivity() instanceof PreferenceActivity) {
- ((PreferenceActivity) getActivity()).startPreferencePanel(
+ if (getActivity() instanceof SettingsActivity) {
+ ((SettingsActivity) getActivity()).startPreferencePanel(
WifiP2pSettings.class.getCanonicalName(),
null,
R.string.wifi_p2p_settings_title, null,
this, 0);
} else {
- startFragment(this, WifiP2pSettings.class.getCanonicalName(), -1, null);
+ startFragment(this, WifiP2pSettings.class.getCanonicalName(),
+ R.string.wifi_p2p_settings_title, -1, null);
}
return true;
+ */
case MENU_ID_WPS_PIN:
showDialog(WPS_PIN_DIALOG_ID);
return true;
@@ -535,15 +474,26 @@ public class WifiSettings extends RestrictedSettingsFragment
onAddNetworkPressed();
}
return true;
+ case MENU_ID_SAVED_NETWORK:
+ if (getActivity() instanceof SettingsActivity) {
+ ((SettingsActivity) getActivity()).startPreferencePanel(
+ SavedAccessPointsWifiSettings.class.getCanonicalName(), null,
+ R.string.wifi_saved_access_points_titlebar, null, this, 0);
+ } else {
+ startFragment(this, SavedAccessPointsWifiSettings.class.getCanonicalName(),
+ R.string.wifi_saved_access_points_titlebar,
+ -1 /* Do not request a result */, null);
+ }
+ return true;
case MENU_ID_ADVANCED:
- if (getActivity() instanceof PreferenceActivity) {
- ((PreferenceActivity) getActivity()).startPreferencePanel(
- AdvancedWifiSettings.class.getCanonicalName(),
- null,
- R.string.wifi_advanced_titlebar, null,
- this, 0);
+ if (getActivity() instanceof SettingsActivity) {
+ ((SettingsActivity) getActivity()).startPreferencePanel(
+ AdvancedWifiSettings.class.getCanonicalName(), null,
+ R.string.wifi_advanced_titlebar, null, this, 0);
} else {
- startFragment(this, AdvancedWifiSettings.class.getCanonicalName(), -1, null);
+ startFragment(this, AdvancedWifiSettings.class.getCanonicalName(),
+ R.string.wifi_advanced_titlebar, -1 /* Do not request a results */,
+ null);
}
return true;
}
@@ -564,8 +514,15 @@ public class WifiSettings extends RestrictedSettingsFragment
menu.add(Menu.NONE, MENU_ID_CONNECT, 0, R.string.wifi_menu_connect);
}
if (mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) {
- menu.add(Menu.NONE, MENU_ID_FORGET, 0, R.string.wifi_menu_forget);
+ if (ActivityManager.getCurrentUser() == UserHandle.USER_OWNER) {
+ menu.add(Menu.NONE, MENU_ID_FORGET, 0, R.string.wifi_menu_forget);
+ }
menu.add(Menu.NONE, MENU_ID_MODIFY, 0, R.string.wifi_menu_modify);
+
+ if (mSelectedAccessPoint.security != AccessPoint.SECURITY_NONE) {
+ // Only allow writing of NFC tags for password-protected networks.
+ menu.add(Menu.NONE, MENU_ID_WRITE_NFC, 0, R.string.wifi_menu_write_to_nfc);
+ }
}
}
}
@@ -579,13 +536,11 @@ public class WifiSettings extends RestrictedSettingsFragment
switch (item.getItemId()) {
case MENU_ID_CONNECT: {
if (mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) {
- mWifiManager.connect(mSelectedAccessPoint.networkId,
- mConnectListener);
+ connect(mSelectedAccessPoint.networkId);
} else if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE) {
/** Bypass dialog for unsecured networks */
mSelectedAccessPoint.generateOpenNetworkConfig();
- mWifiManager.connect(mSelectedAccessPoint.getConfig(),
- mConnectListener);
+ connect(mSelectedAccessPoint.getConfig());
} else {
showDialog(mSelectedAccessPoint, true);
}
@@ -599,6 +554,10 @@ public class WifiSettings extends RestrictedSettingsFragment
showDialog(mSelectedAccessPoint, true);
return true;
}
+ case MENU_ID_WRITE_NFC:
+ showDialog(WRITE_NFC_DIALOG_ID);
+ return true;
+
}
return super.onContextItemSelected(item);
}
@@ -611,7 +570,11 @@ public class WifiSettings extends RestrictedSettingsFragment
if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE &&
mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) {
mSelectedAccessPoint.generateOpenNetworkConfig();
- mWifiManager.connect(mSelectedAccessPoint.getConfig(), mConnectListener);
+ if (!savedNetworksExist) {
+ savedNetworksExist = true;
+ getActivity().invalidateOptionsMenu();
+ }
+ connect(mSelectedAccessPoint.getConfig());
} else {
showDialog(mSelectedAccessPoint, false);
}
@@ -648,7 +611,7 @@ public class WifiSettings extends RestrictedSettingsFragment
mAccessPointSavedState = null;
}
}
- // If it's still null, fine, it's for Add Network
+ // If it's null, fine, it's for Add Network
mSelectedAccessPoint = ap;
mDialog = new WifiDialog(getActivity(), this, ap, mDlgEdit);
return mDialog;
@@ -656,73 +619,54 @@ public class WifiSettings extends RestrictedSettingsFragment
return new WpsDialog(getActivity(), WpsInfo.PBC);
case WPS_PIN_DIALOG_ID:
return new WpsDialog(getActivity(), WpsInfo.DISPLAY);
- case WIFI_SKIPPED_DIALOG_ID:
- return new AlertDialog.Builder(getActivity())
- .setMessage(R.string.wifi_skipped_message)
- .setCancelable(false)
- .setNegativeButton(R.string.wifi_skip_anyway,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int id) {
- getActivity().setResult(RESULT_SKIP);
- getActivity().finish();
- }
- })
- .setPositiveButton(R.string.wifi_dont_skip,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int id) {
- }
- })
- .create();
- case WIFI_AND_MOBILE_SKIPPED_DIALOG_ID:
- return new AlertDialog.Builder(getActivity())
- .setMessage(R.string.wifi_and_mobile_skipped_message)
- .setCancelable(false)
- .setNegativeButton(R.string.wifi_skip_anyway,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int id) {
- getActivity().setResult(RESULT_SKIP);
- getActivity().finish();
- }
- })
- .setPositiveButton(R.string.wifi_dont_skip,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int id) {
- }
- })
- .create();
+ case WRITE_NFC_DIALOG_ID:
+ if (mSelectedAccessPoint != null) {
+ mWifiToNfcDialog = new WriteWifiConfigToNfcDialog(
+ getActivity(), mSelectedAccessPoint, mWifiManager);
+ return mWifiToNfcDialog;
+ }
}
return super.onCreateDialog(dialogId);
}
/**
- * Shows the latest access points available with supplimental information like
+ * Shows the latest access points available with supplemental information like
* the strength of network and the security for it.
*/
private void updateAccessPoints() {
// Safeguard from some delayed event handling
if (getActivity() == null) return;
- if (isRestrictedAndNotPinProtected()) {
+ if (isUiRestricted()) {
addMessagePreference(R.string.wifi_empty_list_user_restricted);
return;
}
final int wifiState = mWifiManager.getWifiState();
+ //when we update the screen, check if verbose logging has been turned on or off
+ mVerboseLogging = mWifiManager.getVerboseLoggingLevel();
+
switch (wifiState) {
case WifiManager.WIFI_STATE_ENABLED:
// AccessPoints are automatically sorted with TreeSet.
- final Collection<AccessPoint> accessPoints = constructAccessPoints();
+ final Collection<AccessPoint> accessPoints =
+ constructAccessPoints(getActivity(), mWifiManager, mLastInfo, mLastState);
getPreferenceScreen().removeAll();
- if(accessPoints.size() == 0) {
+ if (accessPoints.size() == 0) {
addMessagePreference(R.string.wifi_empty_list_wifi_on);
}
+
+ getListView().removeHeaderView(mWifiAssistantCard);
+ if (mWifiAssistantApp != null) {
+ getListView().addHeaderView(mWifiAssistantCard);
+ }
+
for (AccessPoint accessPoint : accessPoints) {
- getPreferenceScreen().addPreference(accessPoint);
+ // Ignore access points that are out of range.
+ if (accessPoint.getLevel() != -1) {
+ getPreferenceScreen().addPreference(accessPoint);
+ }
}
break;
@@ -740,15 +684,117 @@ public class WifiSettings extends RestrictedSettingsFragment
}
}
+ /**
+ * Returns the Network Scorer for the Wifi Assistant App.
+ */
+ public static NetworkScorerAppData getWifiAssistantApp(Context context) {
+ Collection<NetworkScorerAppData> scorers =
+ NetworkScorerAppManager.getAllValidScorers(context);
+
+ if (scorers.isEmpty()) {
+ return null;
+ }
+
+ // TODO: b/13780935 - Implement proper scorer selection. Rather than pick the first
+ // scorer on the system, we should allow the user to select one.
+ return scorers.iterator().next();
+ }
+
+ private void prepareWifiAssistantCard() {
+ if (getActivity() instanceof WifiPickerActivity) {
+ return;
+ }
+
+ if (NetworkScorerAppManager.getActiveScorer(getActivity()) != null) {
+ // A scorer is already enabled; don't show the card.
+ return;
+ }
+
+ Collection<NetworkScorerAppData> scorers =
+ NetworkScorerAppManager.getAllValidScorers(getActivity());
+ if (scorers.isEmpty()) {
+ // No scorers are available to enable; don't show the card.
+ return;
+ }
+
+ SharedPreferences sharedPreferences = getPreferenceScreen().getSharedPreferences();
+ int lastDismissPlatform = sharedPreferences.getInt(KEY_ASSISTANT_DISMISS_PLATFORM, 0);
+
+ if (Build.VERSION.SDK_INT <= lastDismissPlatform) {
+ // User has dismissed the Wi-Fi assistant card on this SDK release. Suppress the card
+ // until the next major platform upgrade.
+ return;
+ }
+
+ // TODO: b/13780935 - Implement proper scorer selection. Rather than pick the first
+ // scorer on the system, we should allow the user to select one.
+ mWifiAssistantApp = scorers.iterator().next();
+
+ if (mWifiAssistantCard == null) {
+ mWifiAssistantCard = LayoutInflater.from(getActivity())
+ .inflate(R.layout.wifi_assistant_card, getListView(), false);
+ Button setup = (Button) mWifiAssistantCard.findViewById(R.id.setup);
+ Button noThanks = (Button) mWifiAssistantCard.findViewById(R.id.no_thanks_button);
+ TextView assistantText =
+ (TextView) mWifiAssistantCard.findViewById(R.id.wifi_assistant_text);
+ assistantText.setText(getResources().getString(
+ R.string.wifi_assistant_title_message, mWifiAssistantApp.mScorerName));
+
+ if (setup != null && noThanks != null) {
+ setup.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent();
+ if (mWifiAssistantApp.mConfigurationActivityClassName != null) {
+ // App has a custom configuration activity; launch that.
+ // This custom activity will be responsible for launching the system
+ // dialog.
+ intent.setClassName(mWifiAssistantApp.mPackageName,
+ mWifiAssistantApp.mConfigurationActivityClassName);
+ } else {
+ // Fall back on the system dialog.
+ intent.setAction(NetworkScoreManager.ACTION_CHANGE_ACTIVE);
+ intent.putExtra(NetworkScoreManager.EXTRA_PACKAGE_NAME,
+ mWifiAssistantApp.mPackageName);
+ }
+ startActivityForResult(intent, REQUEST_ENABLE_WIFI_ASSISTANT);
+ }
+ });
+
+ noThanks.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ disableWifiAssistantCardUntilPlatformUpgrade();
+ getListView().removeHeaderView(mWifiAssistantCard);
+ mWifiAssistantApp = null;
+ }
+ });
+ }
+ }
+ }
+
+ private void disableWifiAssistantCardUntilPlatformUpgrade() {
+ SharedPreferences sharedPreferences = getPreferenceScreen().getSharedPreferences();
+ SharedPreferences.Editor editor = sharedPreferences.edit();
+ editor.putInt(KEY_ASSISTANT_DISMISS_PLATFORM, Build.VERSION.SDK_INT);
+ editor.apply();
+ }
+
+ protected TextView initEmptyView() {
+ TextView emptyView = (TextView) getActivity().findViewById(android.R.id.empty);
+ getListView().setEmptyView(emptyView);
+ return emptyView;
+ }
+
private void setOffMessage() {
if (mEmptyView != null) {
mEmptyView.setText(R.string.wifi_empty_list_wifi_off);
- if (Settings.Global.getInt(getActivity().getContentResolver(),
- Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1) {
+ if (android.provider.Settings.Global.getInt(getActivity().getContentResolver(),
+ android.provider.Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1) {
mEmptyView.append("\n\n");
int resId;
- if (Settings.Secure.isLocationProviderEnabled(getActivity().getContentResolver(),
- LocationManager.NETWORK_PROVIDER)) {
+ if (android.provider.Settings.Secure.isLocationProviderEnabled(
+ getActivity().getContentResolver(), LocationManager.NETWORK_PROVIDER)) {
resId = R.string.wifi_scan_notify_text_location_on;
} else {
resId = R.string.wifi_scan_notify_text_location_off;
@@ -766,23 +812,36 @@ public class WifiSettings extends RestrictedSettingsFragment
}
/** Returns sorted list of access points */
- private List<AccessPoint> constructAccessPoints() {
+ private static List<AccessPoint> constructAccessPoints(Context context,
+ WifiManager wifiManager, WifiInfo lastInfo, DetailedState lastState) {
ArrayList<AccessPoint> accessPoints = new ArrayList<AccessPoint>();
/** Lookup table to more quickly update AccessPoints by only considering objects with the
* correct SSID. Maps SSID -> List of AccessPoints with the given SSID. */
Multimap<String, AccessPoint> apMap = new Multimap<String, AccessPoint>();
- final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
+ final List<WifiConfiguration> configs = wifiManager.getConfiguredNetworks();
if (configs != null) {
+ // Update "Saved Networks" menu option.
+ if (savedNetworksExist != (configs.size() > 0)) {
+ savedNetworksExist = !savedNetworksExist;
+ if (context instanceof Activity) {
+ ((Activity) context).invalidateOptionsMenu();
+ }
+ }
for (WifiConfiguration config : configs) {
- AccessPoint accessPoint = new AccessPoint(getActivity(), config);
- accessPoint.update(mLastInfo, mLastState);
+ if (config.selfAdded && config.numAssociation == 0) {
+ continue;
+ }
+ AccessPoint accessPoint = new AccessPoint(context, config);
+ if (lastInfo != null && lastState != null) {
+ accessPoint.update(lastInfo, lastState);
+ }
accessPoints.add(accessPoint);
apMap.put(accessPoint.ssid, accessPoint);
}
}
- final List<ScanResult> results = mWifiManager.getScanResults();
+ final List<ScanResult> results = wifiManager.getScanResults();
if (results != null) {
for (ScanResult result : results) {
// Ignore hidden and ad-hoc networks.
@@ -797,7 +856,7 @@ public class WifiSettings extends RestrictedSettingsFragment
found = true;
}
if (!found) {
- AccessPoint accessPoint = new AccessPoint(getActivity(), result);
+ AccessPoint accessPoint = new AccessPoint(context, result);
accessPoints.add(accessPoint);
apMap.put(accessPoint.ssid, accessPoint);
}
@@ -809,26 +868,7 @@ public class WifiSettings extends RestrictedSettingsFragment
return accessPoints;
}
- /** A restricted multimap for use in constructAccessPoints */
- private class Multimap<K,V> {
- private final HashMap<K,List<V>> store = new HashMap<K,List<V>>();
- /** retrieve a non-null list of values with key K */
- List<V> getAll(K key) {
- List<V> values = store.get(key);
- return values != null ? values : Collections.<V>emptyList();
- }
-
- void put(K key, V val) {
- List<V> curVals = store.get(key);
- if (curVals == null) {
- curVals = new ArrayList<V>(3);
- store.put(key, curVals);
- }
- curVals.add(val);
- }
- }
-
- private void handleEvent(Context context, Intent intent) {
+ private void handleEvent(Intent intent) {
String action = intent.getAction();
if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
@@ -837,23 +877,6 @@ public class WifiSettings extends RestrictedSettingsFragment
WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action) ||
WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) {
updateAccessPoints();
- } else if (WifiManager.SUPPLICANT_STATE_CHANGED_ACTION.equals(action)) {
- //Ignore supplicant state changes when network is connected
- //TODO: we should deprecate SUPPLICANT_STATE_CHANGED_ACTION and
- //introduce a broadcast that combines the supplicant and network
- //network state change events so the apps dont have to worry about
- //ignoring supplicant state change when network is connected
- //to get more fine grained information.
- SupplicantState state = (SupplicantState) intent.getParcelableExtra(
- WifiManager.EXTRA_NEW_STATE);
- if (!mConnected.get() && SupplicantState.isHandshakeState(state)) {
- updateConnectionState(WifiInfo.getDetailedStateOf(state));
- } else {
- // During a connect, we may have the supplicant
- // state change affect the detailed network state.
- // Make sure a lost connection is updated as well.
- updateConnectionState(null);
- }
} else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
NetworkInfo info = (NetworkInfo) intent.getParcelableExtra(
WifiManager.EXTRA_NETWORK_INFO);
@@ -861,14 +884,6 @@ public class WifiSettings extends RestrictedSettingsFragment
changeNextButtonState(info.isConnected());
updateAccessPoints();
updateConnectionState(info.getDetailedState());
- if (mAutoFinishOnConnection && info.isConnected()) {
- Activity activity = getActivity();
- if (activity != null) {
- activity.setResult(Activity.RESULT_OK);
- activity.finish();
- }
- return;
- }
} else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
updateConnectionState(null);
}
@@ -927,51 +942,15 @@ public class WifiSettings extends RestrictedSettingsFragment
mScanner.pause();
}
- private class Scanner extends Handler {
- private int mRetry = 0;
-
- void resume() {
- if (!hasMessages(0)) {
- sendEmptyMessage(0);
- }
- }
-
- void forceScan() {
- removeMessages(0);
- sendEmptyMessage(0);
- }
-
- void pause() {
- mRetry = 0;
- removeMessages(0);
- }
-
- @Override
- public void handleMessage(Message message) {
- if (mWifiManager.startScan()) {
- mRetry = 0;
- } else if (++mRetry >= 3) {
- mRetry = 0;
- Activity activity = getActivity();
- if (activity != null) {
- Toast.makeText(activity, R.string.wifi_fail_to_scan,
- Toast.LENGTH_LONG).show();
- }
- return;
- }
- sendEmptyMessageDelayed(0, WIFI_RESCAN_INTERVAL_MS);
- }
- }
-
/**
* Renames/replaces "Next" button when appropriate. "Next" button usually exists in
* Wifi setup screens, not in usual wifi settings screen.
*
- * @param connected true when the device is connected to a wifi network.
+ * @param enabled true when the device is connected to a wifi network.
*/
- private void changeNextButtonState(boolean connected) {
+ private void changeNextButtonState(boolean enabled) {
if (mEnableNextOnConnection && hasNextButton()) {
- getNextButton().setEnabled(connected);
+ getNextButton().setEnabled(enabled);
}
}
@@ -993,8 +972,7 @@ public class WifiSettings extends RestrictedSettingsFragment
if (config == null) {
if (mSelectedAccessPoint != null
&& mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) {
- mWifiManager.connect(mSelectedAccessPoint.networkId,
- mConnectListener);
+ connect(mSelectedAccessPoint.networkId);
}
} else if (config.networkId != INVALID_NETWORK_ID) {
if (mSelectedAccessPoint != null) {
@@ -1004,7 +982,7 @@ public class WifiSettings extends RestrictedSettingsFragment
if (configController.isEdit()) {
mWifiManager.save(config, mSaveListener);
} else {
- mWifiManager.connect(config, mConnectListener);
+ connect(config);
}
}
@@ -1016,7 +994,7 @@ public class WifiSettings extends RestrictedSettingsFragment
/* package */ void forget() {
if (mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) {
- // Should not happen, but a monkey seems to triger it
+ // Should not happen, but a monkey seems to trigger it
Log.e(TAG, "Failed to forget invalid network " + mSelectedAccessPoint.getConfig());
return;
}
@@ -1032,6 +1010,14 @@ public class WifiSettings extends RestrictedSettingsFragment
changeNextButtonState(false);
}
+ protected void connect(final WifiConfiguration config) {
+ mWifiManager.connect(config, mConnectListener);
+ }
+
+ protected void connect(final int networkId) {
+ mWifiManager.connect(networkId, mConnectListener);
+ }
+
/**
* Refreshes acccess points and ask Wifi module to scan networks again.
*/
@@ -1081,49 +1067,39 @@ public class WifiSettings extends RestrictedSettingsFragment
@Override
protected int getHelpResource() {
- if (mSetupWizardMode) {
- return 0;
- }
return R.string.help_url_wifi;
}
- /**
- * Used as the outer frame of all setup wizard pages that need to adjust their margins based
- * on the total size of the available display. (e.g. side margins set to 10% of total width.)
- */
- public static class ProportionalOuterFrame extends RelativeLayout {
- public ProportionalOuterFrame(Context context) {
- super(context);
- }
- public ProportionalOuterFrame(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
- public ProportionalOuterFrame(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
+ public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider() {
+ @Override
+ public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
+ final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>();
+ final Resources res = context.getResources();
+
+ // Add fragment title
+ SearchIndexableRaw data = new SearchIndexableRaw(context);
+ data.title = res.getString(R.string.wifi_settings);
+ data.screenTitle = res.getString(R.string.wifi_settings);
+ data.keywords = res.getString(R.string.keywords_wifi);
+ result.add(data);
+
+ // Add available Wi-Fi access points
+ WifiManager wifiManager =
+ (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ final Collection<AccessPoint> accessPoints =
+ constructAccessPoints(context, wifiManager, null, null);
+ for (AccessPoint accessPoint : accessPoints) {
+ // We are indexing only the saved Wi-Fi networks.
+ if (accessPoint.getConfig() == null) continue;
+ data = new SearchIndexableRaw(context);
+ data.title = accessPoint.getTitle().toString();
+ data.screenTitle = res.getString(R.string.wifi_settings);
+ data.enabled = enabled;
+ result.add(data);
+ }
- /**
- * Set our margins and title area height proportionally to the available display size
- */
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
- int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
- final Resources resources = getContext().getResources();
- float titleHeight = resources.getFraction(R.dimen.setup_title_height, 1, 1);
- float sideMargin = resources.getFraction(R.dimen.setup_border_width, 1, 1);
- int bottom = resources.getDimensionPixelSize(R.dimen.setup_margin_bottom);
- setPaddingRelative(
- (int) (parentWidth * sideMargin),
- 0,
- (int) (parentWidth * sideMargin),
- bottom);
- View title = findViewById(R.id.title_area);
- if (title != null) {
- title.setMinimumHeight((int) (parentHeight * titleHeight));
+ return result;
}
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
- }
-
+ };
}
diff --git a/src/com/android/settings/wifi/WifiSettingsForSetupWizard.java b/src/com/android/settings/wifi/WifiSettingsForSetupWizard.java
new file mode 100644
index 0000000..c4a5c96
--- /dev/null
+++ b/src/com/android/settings/wifi/WifiSettingsForSetupWizard.java
@@ -0,0 +1,177 @@
+/*
+ * 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.wifi;
+
+import android.content.Intent;
+import android.content.res.TypedArray;
+import android.database.DataSetObserver;
+import android.net.wifi.WifiConfiguration;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.AbsListView.LayoutParams;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.android.settings.R;
+
+/**
+ * This customized version of WifiSettings is shown to the user only during Setup Wizard. Menu
+ * selections are limited, clicking on an access point will auto-advance to the next screen (once
+ * connected), and, if the user opts to skip ahead without a wifi connection, a warning message
+ * alerts of possible carrier data charges or missing software updates.
+ */
+public class WifiSettingsForSetupWizard extends WifiSettings {
+
+ private static final String TAG = "WifiSettingsForSetupWizard";
+
+ // show a text regarding data charges when wifi connection is required during setup wizard
+ protected static final String EXTRA_SHOW_WIFI_REQUIRED_INFO = "wifi_show_wifi_required_info";
+
+ private View mAddOtherNetworkItem;
+ private ListAdapter mAdapter;
+ private TextView mEmptyFooter;
+ private boolean mListLastEmpty = false;
+
+ @Override
+ public View onCreateView(final LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+
+ final View view = inflater.inflate(R.layout.setup_preference, container, false);
+
+ final ListView list = (ListView) view.findViewById(android.R.id.list);
+ final View title = view.findViewById(R.id.title);
+ if (title == null) {
+ final View header = inflater.inflate(R.layout.setup_wizard_header, list, false);
+ list.addHeaderView(header, null, false);
+ }
+
+ mAddOtherNetworkItem = inflater.inflate(R.layout.setup_wifi_add_network, list, false);
+ list.addFooterView(mAddOtherNetworkItem, null, true);
+ mAddOtherNetworkItem.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mWifiManager.isWifiEnabled()) {
+ onAddNetworkPressed();
+ }
+ }
+ });
+
+ final Intent intent = getActivity().getIntent();
+ if (intent.getBooleanExtra(EXTRA_SHOW_WIFI_REQUIRED_INFO, false)) {
+ view.findViewById(R.id.wifi_required_info).setVisibility(View.VISIBLE);
+ }
+
+ return view;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ getView().setSystemUiVisibility(
+ View.STATUS_BAR_DISABLE_HOME |
+ View.STATUS_BAR_DISABLE_RECENT |
+ View.STATUS_BAR_DISABLE_NOTIFICATION_ALERTS |
+ View.STATUS_BAR_DISABLE_CLOCK);
+
+ if (hasNextButton()) {
+ getNextButton().setVisibility(View.GONE);
+ }
+
+ mAdapter = getPreferenceScreen().getRootAdapter();
+ mAdapter.registerDataSetObserver(new DataSetObserver() {
+ @Override
+ public void onChanged() {
+ super.onChanged();
+ updateFooter();
+ }
+ });
+ }
+
+ @Override
+ public void registerForContextMenu(View view) {
+ // Suppressed during setup wizard
+ }
+
+ @Override
+ /* package */ WifiEnabler createWifiEnabler() {
+ // Not shown during setup wizard
+ return null;
+ }
+
+ @Override
+ /* package */ void addOptionsMenuItems(Menu menu) {
+ final boolean wifiIsEnabled = mWifiManager.isWifiEnabled();
+ final TypedArray ta = getActivity().getTheme()
+ .obtainStyledAttributes(new int[] {R.attr.ic_wps});
+ menu.add(Menu.NONE, MENU_ID_WPS_PBC, 0, R.string.wifi_menu_wps_pbc)
+ .setIcon(ta.getDrawable(0))
+ .setEnabled(wifiIsEnabled)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
+ menu.add(Menu.NONE, MENU_ID_ADD_NETWORK, 0, R.string.wifi_add_network)
+ .setEnabled(wifiIsEnabled)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
+ ta.recycle();
+ }
+
+ @Override
+ protected void connect(final WifiConfiguration config) {
+ WifiSetupActivity activity = (WifiSetupActivity) getActivity();
+ activity.networkSelected();
+ super.connect(config);
+ }
+
+ @Override
+ protected void connect(final int networkId) {
+ WifiSetupActivity activity = (WifiSetupActivity) getActivity();
+ activity.networkSelected();
+ super.connect(networkId);
+ }
+
+ @Override
+ protected TextView initEmptyView() {
+ mEmptyFooter = new TextView(getActivity());
+ mEmptyFooter.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT));
+ mEmptyFooter.setGravity(Gravity.CENTER);
+ mEmptyFooter.setCompoundDrawablesWithIntrinsicBounds(0,
+ R.drawable.ic_wifi_emptystate, 0,0);
+ return mEmptyFooter;
+ }
+
+ protected void updateFooter() {
+ final boolean isEmpty = mAdapter.isEmpty();
+ if (isEmpty != mListLastEmpty) {
+ final ListView list = getListView();
+ if (isEmpty) {
+ list.removeFooterView(mAddOtherNetworkItem);
+ list.addFooterView(mEmptyFooter, null, false);
+ } else {
+ list.removeFooterView(mEmptyFooter);
+ list.addFooterView(mAddOtherNetworkItem, null, true);
+ }
+ mListLastEmpty = isEmpty;
+ }
+ }
+}
diff --git a/src/com/android/settings/wifi/WifiSetupActivity.java b/src/com/android/settings/wifi/WifiSetupActivity.java
index 1739750..a87c733 100644
--- a/src/com/android/settings/wifi/WifiSetupActivity.java
+++ b/src/com/android/settings/wifi/WifiSetupActivity.java
@@ -15,27 +15,293 @@
*/
package com.android.settings.wifi;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.preference.PreferenceFragment;
+import android.util.Log;
+
import com.android.settings.ButtonBarHandler;
+import com.android.settings.R;
+import com.android.setupwizard.navigationbar.SetupWizardNavBar;
+import com.android.setupwizard.navigationbar.SetupWizardNavBar.NavigationBarListener;
-import android.content.res.Resources;
+public class WifiSetupActivity extends WifiPickerActivity
+ implements ButtonBarHandler, NavigationBarListener {
+ private static final String TAG = "WifiSetupActivity";
+
+ private static final String EXTRA_ALLOW_SKIP = "allowSkip";
+ private static final String EXTRA_USE_IMMERSIVE_MODE = "useImmersiveMode";
+
+ // this boolean extra specifies whether to auto finish when connection is established
+ private static final String EXTRA_AUTO_FINISH_ON_CONNECT = "wifi_auto_finish_on_connect";
+
+ // Whether auto finish is suspended until user connects to an access point
+ private static final String EXTRA_REQUIRE_USER_NETWORK_SELECTION =
+ "wifi_require_user_network_selection";
-public class WifiSetupActivity extends WifiPickerActivity implements ButtonBarHandler {
// Extra containing the resource name of the theme to be used
private static final String EXTRA_THEME = "theme";
private static final String THEME_HOLO = "holo";
private static final String THEME_HOLO_LIGHT = "holo_light";
+ private static final String THEME_MATERIAL = "material";
+ private static final String THEME_MATERIAL_LIGHT = "material_light";
+
+ // Key for whether the user selected network in saved instance state bundle
+ private static final String PARAM_USER_SELECTED_NETWORK = "userSelectedNetwork";
+
+ // Activity result when pressing the Skip button
+ private static final int RESULT_SKIP = Activity.RESULT_FIRST_USER;
+
+ // From WizardManager (must match constants maintained there)
+ private static final String ACTION_NEXT = "com.android.wizard.NEXT";
+ private static final String EXTRA_SCRIPT_URI = "scriptUri";
+ private static final String EXTRA_ACTION_ID = "actionId";
+ private static final String EXTRA_RESULT_CODE = "com.android.setupwizard.ResultCode";
+ private static final int NEXT_REQUEST = 10000;
+
+ // Whether we allow skipping without a valid network connection
+ private boolean mAllowSkip = true;
+ // Whether to auto finish when the user selected a network and successfully connected
+ private boolean mAutoFinishOnConnection;
+ // Whether the user connected to a network. This excludes the auto-connecting by the system.
+ private boolean mUserSelectedNetwork;
+ // Whether the device is connected to WiFi
+ private boolean mWifiConnected;
+
+ private SetupWizardNavBar mNavigationBar;
+
+ private final IntentFilter mFilter = new IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // Refresh the connection state with the latest connection info. Use the connection info
+ // from ConnectivityManager instead of the one attached in the intent to make sure
+ // we have the most up-to-date connection state. b/17511772
+ refreshConnectionState();
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final Intent intent = getIntent();
+
+ mAutoFinishOnConnection = intent.getBooleanExtra(EXTRA_AUTO_FINISH_ON_CONNECT, false);
+ mAllowSkip = intent.getBooleanExtra(EXTRA_ALLOW_SKIP, true);
+ // Behave like the user already selected a network if we do not require selection
+ mUserSelectedNetwork = !intent.getBooleanExtra(EXTRA_REQUIRE_USER_NETWORK_SELECTION, false);
+ }
- // Style resources containing theme settings
- private static final String RESOURCE_THEME_DARK = "SetupWizardWifiTheme";
- private static final String RESOURCE_THEME_LIGHT = "SetupWizardWifiTheme.Light";
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putBoolean(PARAM_USER_SELECTED_NETWORK, mUserSelectedNetwork);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+ mUserSelectedNetwork = savedInstanceState.getBoolean(PARAM_USER_SELECTED_NETWORK, true);
+ }
+
+ private void refreshConnectionState() {
+ final ConnectivityManager connectivity = (ConnectivityManager)
+ getSystemService(Context.CONNECTIVITY_SERVICE);
+ boolean connected = connectivity != null &&
+ connectivity.getNetworkInfo(ConnectivityManager.TYPE_WIFI).isConnected();
+ refreshConnectionState(connected);
+ }
+
+ private void refreshConnectionState(boolean connected) {
+ mWifiConnected = connected;
+ if (connected) {
+ if (mAutoFinishOnConnection && mUserSelectedNetwork) {
+ Log.d(TAG, "Auto-finishing with connection");
+ finishOrNext(Activity.RESULT_OK);
+ // Require a user selection before auto-finishing next time we are here. The user
+ // can either connect to a different network or press "next" to proceed.
+ mUserSelectedNetwork = false;
+ }
+ if (mNavigationBar != null) {
+ mNavigationBar.getNextButton().setText(R.string.setup_wizard_next_button_label);
+ mNavigationBar.getNextButton().setEnabled(true);
+ }
+ } else {
+ if (mNavigationBar != null) {
+ mNavigationBar.getNextButton().setText(R.string.skip_label);
+ mNavigationBar.getNextButton().setEnabled(mAllowSkip);
+ }
+ }
+ }
+
+ /* package */ void networkSelected() {
+ Log.d(TAG, "Network selected by user");
+ mUserSelectedNetwork = true;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ registerReceiver(mReceiver, mFilter);
+ refreshConnectionState();
+ }
+
+ @Override
+ public void onPause() {
+ unregisterReceiver(mReceiver);
+ super.onPause();
+ }
@Override
protected void onApplyThemeResource(Resources.Theme theme, int resid, boolean first) {
String themeName = getIntent().getStringExtra(EXTRA_THEME);
- if (themeName != null && themeName.equalsIgnoreCase(THEME_HOLO_LIGHT)) {
- resid = getResources().getIdentifier(RESOURCE_THEME_LIGHT, "style",
- getPackageName());
+ if (THEME_HOLO_LIGHT.equalsIgnoreCase(themeName) ||
+ THEME_MATERIAL_LIGHT.equalsIgnoreCase(themeName)) {
+ resid = R.style.SetupWizardWifiTheme_Light;
+ } else if (THEME_HOLO.equalsIgnoreCase(themeName) ||
+ THEME_MATERIAL.equalsIgnoreCase(themeName)) {
+ resid = R.style.SetupWizardWifiTheme;
}
super.onApplyThemeResource(theme, resid, first);
}
+
+ @Override
+ protected boolean isValidFragment(String fragmentName) {
+ return WifiSettingsForSetupWizard.class.getName().equals(fragmentName);
+ }
+
+ @Override
+ /* package */ Class<? extends PreferenceFragment> getWifiSettingsClass() {
+ return WifiSettingsForSetupWizard.class;
+ }
+
+ /**
+ * Complete this activity and return the results to the caller. If using WizardManager, this
+ * will invoke the next scripted action; otherwise, we simply finish.
+ */
+ public void finishOrNext(int resultCode) {
+ Log.d(TAG, "finishOrNext resultCode=" + resultCode
+ + " isUsingWizardManager=" + isUsingWizardManager());
+ if (isUsingWizardManager()) {
+ sendResultsToSetupWizard(resultCode);
+ } else {
+ setResult(resultCode);
+ finish();
+ }
+ }
+
+ private boolean isUsingWizardManager() {
+ return getIntent().hasExtra(EXTRA_SCRIPT_URI);
+ }
+
+ /**
+ * Send the results of this activity to WizardManager, which will then send out the next
+ * scripted activity. WizardManager does not actually return an activity result, but if we
+ * invoke WizardManager without requesting a result, the framework will choose not to issue a
+ * call to onActivityResult with RESULT_CANCELED when navigating backward.
+ */
+ private void sendResultsToSetupWizard(int resultCode) {
+ final Intent intent = getIntent();
+ final Intent nextIntent = new Intent(ACTION_NEXT);
+ nextIntent.putExtra(EXTRA_SCRIPT_URI, intent.getStringExtra(EXTRA_SCRIPT_URI));
+ nextIntent.putExtra(EXTRA_ACTION_ID, intent.getStringExtra(EXTRA_ACTION_ID));
+ nextIntent.putExtra(EXTRA_THEME, intent.getStringExtra(EXTRA_THEME));
+ nextIntent.putExtra(EXTRA_RESULT_CODE, resultCode);
+ startActivityForResult(nextIntent, NEXT_REQUEST);
+ }
+
+ @Override
+ public void onNavigationBarCreated(final SetupWizardNavBar bar) {
+ mNavigationBar = bar;
+ final boolean useImmersiveMode =
+ getIntent().getBooleanExtra(EXTRA_USE_IMMERSIVE_MODE, false);
+ bar.setUseImmersiveMode(useImmersiveMode);
+ if (useImmersiveMode) {
+ getWindow().setNavigationBarColor(Color.TRANSPARENT);
+ getWindow().setStatusBarColor(Color.TRANSPARENT);
+ }
+ }
+
+ @Override
+ public void onNavigateBack() {
+ onBackPressed();
+ }
+
+ @Override
+ public void onNavigateNext() {
+ if (mWifiConnected) {
+ finishOrNext(RESULT_OK);
+ } else {
+ // Warn of possible data charges if there is a network connection, or lack of updates
+ // if there is none.
+ final int message = isNetworkConnected() ? R.string.wifi_skipped_message :
+ R.string.wifi_and_mobile_skipped_message;
+ WifiSkipDialog.newInstance(message).show(getFragmentManager(), "dialog");
+ }
+ }
+
+ /**
+ * @return True if there is a valid network connection, whether it is via WiFi, mobile data or
+ * other means.
+ */
+ private boolean isNetworkConnected() {
+ final ConnectivityManager connectivity = (ConnectivityManager)
+ getSystemService(Context.CONNECTIVITY_SERVICE);
+ if (connectivity == null) {
+ return false;
+ }
+ final NetworkInfo info = connectivity.getActiveNetworkInfo();
+ return info != null && info.isConnected();
+ }
+
+ public static class WifiSkipDialog extends DialogFragment {
+ public static WifiSkipDialog newInstance(int messageRes) {
+ final Bundle args = new Bundle();
+ args.putInt("messageRes", messageRes);
+ final WifiSkipDialog dialog = new WifiSkipDialog();
+ dialog.setArguments(args);
+ return dialog;
+ }
+
+ public WifiSkipDialog() {
+ // no-arg constructor for fragment
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ int messageRes = getArguments().getInt("messageRes");
+ return new AlertDialog.Builder(getActivity())
+ .setMessage(messageRes)
+ .setCancelable(false)
+ .setNegativeButton(R.string.wifi_skip_anyway,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ WifiSetupActivity activity = (WifiSetupActivity) getActivity();
+ activity.finishOrNext(RESULT_SKIP);
+ }
+ })
+ .setPositiveButton(R.string.wifi_dont_skip,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ }
+ })
+ .create();
+ }
+ }
}
diff --git a/src/com/android/settings/wifi/WpsDialog.java b/src/com/android/settings/wifi/WpsDialog.java
index 2a93884..d0b116b 100644
--- a/src/com/android/settings/wifi/WpsDialog.java
+++ b/src/com/android/settings/wifi/WpsDialog.java
@@ -27,7 +27,6 @@ import android.net.wifi.WifiManager;
import android.net.wifi.WpsInfo;
import android.os.Bundle;
import android.os.Handler;
-import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
@@ -45,6 +44,8 @@ import com.android.settings.R;
public class WpsDialog extends AlertDialog {
private final static String TAG = "WpsDialog";
+ private static final String DIALOG_STATE = "android:dialogState";
+ private static final String DIALOG_MSG_STRING = "android:dialogMsg";
private View mView;
private TextView mTextView;
@@ -56,7 +57,7 @@ public class WpsDialog extends AlertDialog {
private static final int WPS_TIMEOUT_S = 120;
private WifiManager mWifiManager;
- private WifiManager.WpsListener mWpsListener;
+ private WifiManager.WpsCallback mWpsListener;
private int mWpsSetup;
private final IntentFilter mFilter;
@@ -64,6 +65,7 @@ public class WpsDialog extends AlertDialog {
private Context mContext;
private Handler mHandler = new Handler();
+ private String mMsgString = "";
private enum DialogState {
WPS_INIT,
@@ -79,8 +81,9 @@ public class WpsDialog extends AlertDialog {
mContext = context;
mWpsSetup = wpsSetup;
- class WpsListener implements WifiManager.WpsListener {
- public void onStartSuccess(String pin) {
+ class WpsListener extends WifiManager.WpsCallback {
+
+ public void onStarted(String pin) {
if (pin != null) {
updateDialog(DialogState.WPS_START, String.format(
mContext.getString(R.string.wifi_wps_onstart_pin), pin));
@@ -89,12 +92,13 @@ public class WpsDialog extends AlertDialog {
R.string.wifi_wps_onstart_pbc));
}
}
- public void onCompletion() {
+
+ public void onSucceeded() {
updateDialog(DialogState.WPS_COMPLETE,
mContext.getString(R.string.wifi_wps_complete));
}
- public void onFailure(int reason) {
+ public void onFailed(int reason) {
String msg;
switch (reason) {
case WifiManager.WPS_OVERLAP_ERROR:
@@ -128,6 +132,25 @@ public class WpsDialog extends AlertDialog {
handleEvent(context, intent);
}
};
+ setCanceledOnTouchOutside(false);
+ }
+
+ @Override
+ public Bundle onSaveInstanceState () {
+ Bundle bundle = super.onSaveInstanceState();
+ bundle.putString(DIALOG_STATE, mDialogState.toString());
+ bundle.putString(DIALOG_MSG_STRING, mMsgString.toString());
+ return bundle;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Bundle savedInstanceState) {
+ if (savedInstanceState != null) {
+ super.onRestoreInstanceState(savedInstanceState);
+ DialogState dialogState = mDialogState.valueOf(savedInstanceState.getString(DIALOG_STATE));
+ String msg = savedInstanceState.getString(DIALOG_MSG_STRING);
+ updateDialog(dialogState, msg);
+ }
}
@Override
@@ -207,6 +230,7 @@ public class WpsDialog extends AlertDialog {
return;
}
mDialogState = state;
+ mMsgString = msg;
mHandler.post(new Runnable() {
@Override
diff --git a/src/com/android/settings/wifi/WriteWifiConfigToNfcDialog.java b/src/com/android/settings/wifi/WriteWifiConfigToNfcDialog.java
new file mode 100644
index 0000000..2667e0b
--- /dev/null
+++ b/src/com/android/settings/wifi/WriteWifiConfigToNfcDialog.java
@@ -0,0 +1,283 @@
+/*
+ * 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.wifi;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.net.wifi.WifiManager;
+import android.nfc.FormatException;
+import android.nfc.NdefMessage;
+import android.nfc.NdefRecord;
+import android.nfc.NfcAdapter;
+import android.nfc.Tag;
+import android.nfc.tech.Ndef;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.text.Editable;
+import android.text.InputType;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import com.android.settings.R;
+
+import java.io.IOException;
+
+class WriteWifiConfigToNfcDialog extends AlertDialog
+ implements TextWatcher, View.OnClickListener, CompoundButton.OnCheckedChangeListener {
+
+ private static final String NFC_TOKEN_MIME_TYPE = "application/vnd.wfa.wsc";
+
+ private static final String TAG = WriteWifiConfigToNfcDialog.class.getName().toString();
+ private static final String PASSWORD_FORMAT = "102700%s%s";
+ private static final int HEX_RADIX = 16;
+ private static final char[] hexArray = "0123456789ABCDEF".toCharArray();
+
+ private final PowerManager.WakeLock mWakeLock;
+
+ private AccessPoint mAccessPoint;
+ private View mView;
+ private Button mSubmitButton;
+ private Button mCancelButton;
+ private Handler mOnTextChangedHandler;
+ private TextView mPasswordView;
+ private TextView mLabelView;
+ private CheckBox mPasswordCheckBox;
+ private ProgressBar mProgressBar;
+ private WifiManager mWifiManager;
+ private String mWpsNfcConfigurationToken;
+ private Context mContext;
+
+ WriteWifiConfigToNfcDialog(Context context, AccessPoint accessPoint,
+ WifiManager wifiManager) {
+ super(context);
+
+ mContext = context;
+ mWakeLock = ((PowerManager) context.getSystemService(Context.POWER_SERVICE))
+ .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WriteWifiConfigToNfcDialog:wakeLock");
+ mAccessPoint = accessPoint;
+ mOnTextChangedHandler = new Handler();
+ mWifiManager = wifiManager;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ mView = getLayoutInflater().inflate(R.layout.write_wifi_config_to_nfc, null);
+
+ setView(mView);
+ setInverseBackgroundForced(true);
+ setTitle(R.string.setup_wifi_nfc_tag);
+ setCancelable(true);
+ setButton(DialogInterface.BUTTON_NEUTRAL,
+ mContext.getResources().getString(R.string.write_tag), (OnClickListener) null);
+ setButton(DialogInterface.BUTTON_NEGATIVE,
+ mContext.getResources().getString(com.android.internal.R.string.cancel),
+ (OnClickListener) null);
+
+ mPasswordView = (TextView) mView.findViewById(R.id.password);
+ mLabelView = (TextView) mView.findViewById(R.id.password_label);
+ mPasswordView.addTextChangedListener(this);
+ mPasswordCheckBox = (CheckBox) mView.findViewById(R.id.show_password);
+ mPasswordCheckBox.setOnCheckedChangeListener(this);
+ mProgressBar = (ProgressBar) mView.findViewById(R.id.progress_bar);
+
+ super.onCreate(savedInstanceState);
+
+ mSubmitButton = getButton(DialogInterface.BUTTON_NEUTRAL);
+ mSubmitButton.setOnClickListener(this);
+ mSubmitButton.setEnabled(false);
+
+ mCancelButton = getButton(DialogInterface.BUTTON_NEGATIVE);
+ }
+
+ @Override
+ public void onClick(View v) {
+ mWakeLock.acquire();
+
+ String password = mPasswordView.getText().toString();
+ String wpsNfcConfigurationToken
+ = mWifiManager.getWpsNfcConfigurationToken(mAccessPoint.networkId);
+ String passwordHex = byteArrayToHexString(password.getBytes());
+
+ String passwordLength = password.length() >= HEX_RADIX
+ ? Integer.toString(password.length(), HEX_RADIX)
+ : "0" + Character.forDigit(password.length(), HEX_RADIX);
+
+ passwordHex = String.format(PASSWORD_FORMAT, passwordLength, passwordHex).toUpperCase();
+
+ if (wpsNfcConfigurationToken.contains(passwordHex)) {
+ mWpsNfcConfigurationToken = wpsNfcConfigurationToken;
+
+ Activity activity = getOwnerActivity();
+ NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(activity);
+
+ nfcAdapter.enableReaderMode(activity, new NfcAdapter.ReaderCallback() {
+ @Override
+ public void onTagDiscovered(Tag tag) {
+ handleWriteNfcEvent(tag);
+ }
+ }, NfcAdapter.FLAG_READER_NFC_A |
+ NfcAdapter.FLAG_READER_NFC_B |
+ NfcAdapter.FLAG_READER_NFC_BARCODE |
+ NfcAdapter.FLAG_READER_NFC_F |
+ NfcAdapter.FLAG_READER_NFC_V,
+ null);
+
+ mPasswordView.setVisibility(View.GONE);
+ mPasswordCheckBox.setVisibility(View.GONE);
+ mSubmitButton.setVisibility(View.GONE);
+ InputMethodManager imm = (InputMethodManager)
+ getOwnerActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(mPasswordView.getWindowToken(), 0);
+
+ mLabelView.setText(R.string.status_awaiting_tap);
+
+ mView.findViewById(R.id.password_layout).setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
+ mProgressBar.setVisibility(View.VISIBLE);
+ } else {
+ mLabelView.setText(R.string.status_invalid_password);
+ }
+ }
+
+ private void handleWriteNfcEvent(Tag tag) {
+ Ndef ndef = Ndef.get(tag);
+
+ if (ndef != null) {
+ if (ndef.isWritable()) {
+ NdefRecord record = NdefRecord.createMime(
+ NFC_TOKEN_MIME_TYPE,
+ hexStringToByteArray(mWpsNfcConfigurationToken));
+ try {
+ ndef.connect();
+ ndef.writeNdefMessage(new NdefMessage(record));
+ getOwnerActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mProgressBar.setVisibility(View.GONE);
+ }
+ });
+ setViewText(mLabelView, R.string.status_write_success);
+ setViewText(mCancelButton, com.android.internal.R.string.done_label);
+ } catch (IOException e) {
+ setViewText(mLabelView, R.string.status_failed_to_write);
+ Log.e(TAG, "Unable to write Wi-Fi config to NFC tag.", e);
+ return;
+ } catch (FormatException e) {
+ setViewText(mLabelView, R.string.status_failed_to_write);
+ Log.e(TAG, "Unable to write Wi-Fi config to NFC tag.", e);
+ return;
+ }
+ } else {
+ setViewText(mLabelView, R.string.status_tag_not_writable);
+ Log.e(TAG, "Tag is not writable");
+ }
+ } else {
+ setViewText(mLabelView, R.string.status_tag_not_writable);
+ Log.e(TAG, "Tag does not support NDEF");
+ }
+ }
+
+ @Override
+ public void dismiss() {
+ if (mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
+
+ super.dismiss();
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ mOnTextChangedHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ enableSubmitIfAppropriate();
+ }
+ });
+ }
+
+ private void enableSubmitIfAppropriate() {
+
+ if (mPasswordView != null) {
+ if (mAccessPoint.security == AccessPoint.SECURITY_WEP) {
+ mSubmitButton.setEnabled(mPasswordView.length() > 0);
+ } else if (mAccessPoint.security == AccessPoint.SECURITY_PSK) {
+ mSubmitButton.setEnabled(mPasswordView.length() >= 8);
+ }
+ } else {
+ mSubmitButton.setEnabled(false);
+ }
+
+ }
+
+ private void setViewText(final TextView view, final int resid) {
+ getOwnerActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ view.setText(resid);
+ }
+ });
+ }
+
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ mPasswordView.setInputType(
+ InputType.TYPE_CLASS_TEXT |
+ (isChecked
+ ? InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
+ : InputType.TYPE_TEXT_VARIATION_PASSWORD));
+ }
+
+ private static byte[] hexStringToByteArray(String s) {
+ int len = s.length();
+ byte[] data = new byte[len / 2];
+
+ for (int i = 0; i < len; i += 2) {
+ data[i / 2] = (byte) ((Character.digit(s.charAt(i), HEX_RADIX) << 4)
+ + Character.digit(s.charAt(i + 1), HEX_RADIX));
+ }
+
+ return data;
+ }
+
+ private static String byteArrayToHexString(byte[] bytes) {
+ char[] hexChars = new char[bytes.length * 2];
+ for ( int j = 0; j < bytes.length; j++ ) {
+ int v = bytes[j] & 0xFF;
+ hexChars[j * 2] = hexArray[v >>> 4];
+ hexChars[j * 2 + 1] = hexArray[v & 0x0F];
+ }
+ return new String(hexChars);
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+
+ @Override
+ public void afterTextChanged(Editable s) {}
+}
diff --git a/src/com/android/settings/wifi/p2p/WifiP2pSettings.java b/src/com/android/settings/wifi/p2p/WifiP2pSettings.java
index 07d66b0..cd70796 100644
--- a/src/com/android/settings/wifi/p2p/WifiP2pSettings.java
+++ b/src/com/android/settings/wifi/p2p/WifiP2pSettings.java
@@ -16,7 +16,6 @@
package com.android.settings.wifi.p2p;
-import android.app.ActionBar;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
@@ -34,40 +33,32 @@ import android.net.wifi.p2p.WifiP2pDeviceList;
import android.net.wifi.p2p.WifiP2pGroup;
import android.net.wifi.p2p.WifiP2pGroupList;
import android.net.wifi.p2p.WifiP2pManager;
-import android.net.wifi.p2p.WifiP2pManager.GroupInfoListener;
+import android.net.wifi.p2p.WifiP2pManager.PeerListListener;
import android.net.wifi.p2p.WifiP2pManager.PersistentGroupInfoListener;
import android.net.wifi.WpsInfo;
import android.os.Bundle;
-import android.os.Handler;
import android.os.SystemProperties;
import android.preference.Preference;
-import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
import android.text.InputFilter;
import android.text.TextUtils;
import android.util.Log;
-import android.view.Gravity;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.EditText;
-import android.widget.Switch;
import android.widget.Toast;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Collection;
-
/*
* Displays Wi-fi p2p settings UI
*/
public class WifiP2pSettings extends SettingsPreferenceFragment
- implements PersistentGroupInfoListener, GroupInfoListener {
+ implements PersistentGroupInfoListener, PeerListListener {
private static final String TAG = "WifiP2pSettings";
private static final boolean DBG = false;
@@ -89,7 +80,6 @@ public class WifiP2pSettings extends SettingsPreferenceFragment
private boolean mWifiP2pEnabled;
private boolean mWifiP2pSearching;
private int mConnectedDevices;
- private WifiP2pGroup mConnectedGroup;
private boolean mLastGroupFormed = false;
private PreferenceGroup mPeersGroup;
@@ -129,9 +119,6 @@ public class WifiP2pSettings extends SettingsPreferenceFragment
WifiP2pManager.EXTRA_NETWORK_INFO);
WifiP2pInfo wifip2pinfo = (WifiP2pInfo) intent.getParcelableExtra(
WifiP2pManager.EXTRA_WIFI_P2P_INFO);
- if (mWifiP2pManager != null) {
- mWifiP2pManager.requestGroupInfo(mChannel, WifiP2pSettings.this);
- }
if (networkInfo.isConnected()) {
if (DBG) Log.d(TAG, "Connected");
} else if (mLastGroupFormed != true) {
@@ -311,16 +298,20 @@ public class WifiP2pSettings extends SettingsPreferenceFragment
final PreferenceScreen preferenceScreen = getPreferenceScreen();
preferenceScreen.removeAll();
-
preferenceScreen.setOrderingAsAdded(true);
+
mThisDevicePref = new Preference(getActivity());
+ mThisDevicePref.setPersistent(false);
+ mThisDevicePref.setSelectable(false);
preferenceScreen.addPreference(mThisDevicePref);
mPeersGroup = new PreferenceCategory(getActivity());
mPeersGroup.setTitle(R.string.wifi_p2p_peer_devices);
+ preferenceScreen.addPreference(mPeersGroup);
mPersistentGroup = new PreferenceCategory(getActivity());
mPersistentGroup.setTitle(R.string.wifi_p2p_remembered_groups);
+ preferenceScreen.addPreference(mPersistentGroup);
super.onActivityCreated(savedInstanceState);
}
@@ -329,12 +320,17 @@ public class WifiP2pSettings extends SettingsPreferenceFragment
public void onResume() {
super.onResume();
getActivity().registerReceiver(mReceiver, mIntentFilter);
+ if (mWifiP2pManager != null) {
+ mWifiP2pManager.requestPeers(mChannel, WifiP2pSettings.this);
+ }
}
@Override
public void onPause() {
super.onPause();
- mWifiP2pManager.stopPeerDiscovery(mChannel, null);
+ if (mWifiP2pManager != null) {
+ mWifiP2pManager.stopPeerDiscovery(mChannel, null);
+ }
getActivity().unregisterReceiver(mReceiver);
}
@@ -519,6 +515,7 @@ public class WifiP2pSettings extends SettingsPreferenceFragment
if (DBG) Log.d(TAG, " mConnectedDevices " + mConnectedDevices);
}
+ @Override
public void onPersistentGroupInfoAvailable(WifiP2pGroupList groups) {
mPersistentGroup.removeAll();
@@ -541,27 +538,18 @@ public class WifiP2pSettings extends SettingsPreferenceFragment
}
}
- public void onGroupInfoAvailable(WifiP2pGroup group) {
- if (DBG) Log.d(TAG, " group " + group);
- mConnectedGroup = group;
- updateDevicePref();
+ @Override
+ public void onPeersAvailable(WifiP2pDeviceList peers) {
+ if (DBG) Log.d(TAG, "Requested peers are available");
+ mPeers = peers;
+ handlePeersChanged();
}
private void handleP2pStateChanged() {
updateSearchMenu(false);
- if (mWifiP2pEnabled) {
- final PreferenceScreen preferenceScreen = getPreferenceScreen();
- preferenceScreen.removeAll();
-
- preferenceScreen.setOrderingAsAdded(true);
- preferenceScreen.addPreference(mThisDevicePref);
-
- mPeersGroup.setEnabled(true);
- preferenceScreen.addPreference(mPeersGroup);
-
- mPersistentGroup.setEnabled(true);
- preferenceScreen.addPreference(mPersistentGroup);
- }
+ mThisDevicePref.setEnabled(mWifiP2pEnabled);
+ mPeersGroup.setEnabled(mWifiP2pEnabled);
+ mPersistentGroup.setEnabled(mWifiP2pEnabled);
}
private void updateSearchMenu(boolean searching) {
@@ -589,10 +577,6 @@ public class WifiP2pSettings extends SettingsPreferenceFragment
} else {
mThisDevicePref.setTitle(mThisDevice.deviceName);
}
-
- mThisDevicePref.setPersistent(false);
- mThisDevicePref.setEnabled(true);
- mThisDevicePref.setSelectable(false);
}
}
}