summaryrefslogtreecommitdiff
path: root/packages/SettingsLib/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/SettingsLib/src')
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/CustomDialogPreference.java11
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/CustomDialogPreferenceCompat.java129
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/CustomEditTextPreference.java13
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/CustomEditTextPreferenceCompat.java136
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java5
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/HelpUtils.java246
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/RestrictedLockImageSpan.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java (renamed from packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java)202
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java9
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java13
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java9
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/TetherUtil.java17
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/TwoTargetPreference.java5
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/Utils.java86
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java18
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/animation/AppearAnimationUtils.java3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java13
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/applications/DefaultAppInfo.java22
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java5
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java1
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java11
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java17
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java123
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java382
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java (renamed from packages/SettingsLib/src/com/android/settingslib/bluetooth/Utils.java)25
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java354
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java322
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java11
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java223
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java23
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java19
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java14
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java10
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java5
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java62
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java341
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java13
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java11
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java8
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java13
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java13
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java1
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java62
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java28
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java81
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java5
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java17
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/core/lifecycle/Lifecycle.java13
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java8
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableDialogFragment.java14
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableFragment.java7
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java13
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnCreate.java4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnPrepareOptionsMenu.java1
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java9
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java8
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/development/AbstractLogdSizePreferenceController.java1
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/development/AbstractLogpersistPreferenceController.java3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/development/DeveloperOptionsPreferenceController.java1
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/development/DevelopmentSettingsEnabler.java5
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/development/SystemPropPoker.java3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractBluetoothAddressPreferenceController.java3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractImsStatusPreferenceController.java7
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractIpAddressPreferenceController.java1
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractUptimePreferenceController.java5
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java16
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java5
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java30
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java238
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java112
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/drawer/ProfileSelectDialog.java90
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java214
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/drawer/Tile.java334
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java360
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/drawer/UserAdapter.java213
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java15
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java12
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java12
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeEnablerManager.java11
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeEnablerManagerCompat.java265
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtil.java36
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java436
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java21
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java134
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSubtypePreference.java15
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/inputmethod/SwitchWithNoTextPreference.java1
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java5
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoader.java70
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java109
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/location/InjectedSetting.java191
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java576
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java73
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java261
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java65
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java92
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java234
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java89
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/MediaDeviceUtils.java46
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java157
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java80
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/net/ChartDataLoader.java5
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/net/ChartDataLoaderCompat.java152
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java172
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleChartData.java56
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleChartDataLoader.java108
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleData.java70
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataForUid.java65
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataForUidLoader.java112
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java186
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/net/NetworkStatsSummaryLoader.java112
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/net/SummaryForAllUidLoader.java6
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/net/SummaryForAllUidLoaderCompat.java87
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/net/UidDetailProvider.java4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/notification/ZenDurationDialog.java38
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionCategory.java25
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionController.java3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionControllerMixin.java12
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionControllerMixinCompat.java143
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionList.java85
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionLoader.java6
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionLoaderCompat.java54
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionParser.java498
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/users/AppRestrictionsHelper.java3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/utils/AsyncLoader.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/utils/AsyncLoaderCompat.java111
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/utils/IconCache.java1
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java51
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/widget/FooterPreference.java7
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixin.java7
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixinCompat.java72
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java58
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java13
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java1
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/WifiSavedConfigUtils.java6
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java6
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java22
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java1
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java1
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wrapper/LocationManagerWrapper.java64
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wrapper/PackageManagerWrapper.java253
145 files changed, 6389 insertions, 4168 deletions
diff --git a/packages/SettingsLib/src/com/android/settingslib/CustomDialogPreference.java b/packages/SettingsLib/src/com/android/settingslib/CustomDialogPreference.java
index cf1c2c348367..192a40c5e79a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/CustomDialogPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/CustomDialogPreference.java
@@ -20,11 +20,18 @@ import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
-import androidx.preference.PreferenceDialogFragment;
-import androidx.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.View;
+import androidx.preference.DialogPreference;
+import androidx.preference.PreferenceDialogFragment;
+
+/**
+ * Framework version is deprecated, use the compat version instead.
+ *
+ * @deprecated
+ */
+@Deprecated
public class CustomDialogPreference extends DialogPreference {
private CustomPreferenceDialogFragment mFragment;
diff --git a/packages/SettingsLib/src/com/android/settingslib/CustomDialogPreferenceCompat.java b/packages/SettingsLib/src/com/android/settingslib/CustomDialogPreferenceCompat.java
new file mode 100644
index 000000000000..6ac9d4e21f40
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/CustomDialogPreferenceCompat.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2018 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.settingslib;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.View;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.preference.DialogPreference;
+import androidx.preference.PreferenceDialogFragmentCompat;
+
+public class CustomDialogPreferenceCompat extends DialogPreference {
+
+ private CustomPreferenceDialogFragment mFragment;
+ private DialogInterface.OnShowListener mOnShowListener;
+
+ public CustomDialogPreferenceCompat(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ public CustomDialogPreferenceCompat(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public CustomDialogPreferenceCompat(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public CustomDialogPreferenceCompat(Context context) {
+ super(context);
+ }
+
+ public boolean isDialogOpen() {
+ return getDialog() != null && getDialog().isShowing();
+ }
+
+ public Dialog getDialog() {
+ return mFragment != null ? mFragment.getDialog() : null;
+ }
+
+ public void setOnShowListener(DialogInterface.OnShowListener listner) {
+ mOnShowListener = listner;
+ }
+
+ protected void onPrepareDialogBuilder(AlertDialog.Builder builder,
+ DialogInterface.OnClickListener listener) {
+ }
+
+ protected void onDialogClosed(boolean positiveResult) {
+ }
+
+ protected void onClick(DialogInterface dialog, int which) {
+ }
+
+ protected void onBindDialogView(View view) {
+ }
+
+ private void setFragment(CustomPreferenceDialogFragment fragment) {
+ mFragment = fragment;
+ }
+
+ private DialogInterface.OnShowListener getOnShowListener() {
+ return mOnShowListener;
+ }
+
+ public static class CustomPreferenceDialogFragment extends PreferenceDialogFragmentCompat {
+
+ public static CustomPreferenceDialogFragment newInstance(String key) {
+ final CustomPreferenceDialogFragment fragment = new CustomPreferenceDialogFragment();
+ final Bundle b = new Bundle(1);
+ b.putString(ARG_KEY, key);
+ fragment.setArguments(b);
+ return fragment;
+ }
+
+ private CustomDialogPreferenceCompat getCustomizablePreference() {
+ return (CustomDialogPreferenceCompat) getPreference();
+ }
+
+ @Override
+ protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
+ super.onPrepareDialogBuilder(builder);
+ getCustomizablePreference().setFragment(this);
+ getCustomizablePreference().onPrepareDialogBuilder(builder, this);
+ }
+
+ @Override
+ public void onDialogClosed(boolean positiveResult) {
+ getCustomizablePreference().onDialogClosed(positiveResult);
+ }
+
+ @Override
+ protected void onBindDialogView(View view) {
+ super.onBindDialogView(view);
+ getCustomizablePreference().onBindDialogView(view);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Dialog dialog = super.onCreateDialog(savedInstanceState);
+ dialog.setOnShowListener(getCustomizablePreference().getOnShowListener());
+ return dialog;
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ super.onClick(dialog, which);
+ getCustomizablePreference().onClick(dialog, which);
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/CustomEditTextPreference.java b/packages/SettingsLib/src/com/android/settingslib/CustomEditTextPreference.java
index 04c39540cf27..3caa0bb9cf4b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/CustomEditTextPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/CustomEditTextPreference.java
@@ -23,13 +23,20 @@ import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
-import androidx.annotation.CallSuper;
-import androidx.preference.EditTextPreferenceDialogFragment;
-import androidx.preference.EditTextPreference;
import android.util.AttributeSet;
import android.view.View;
import android.widget.EditText;
+import androidx.annotation.CallSuper;
+import androidx.preference.EditTextPreference;
+import androidx.preference.EditTextPreferenceDialogFragment;
+
+/**
+ * Framework version is deprecated, use the compat version instead.
+ *
+ * @deprecated
+ */
+@Deprecated
public class CustomEditTextPreference extends EditTextPreference {
private CustomPreferenceDialogFragment mFragment;
diff --git a/packages/SettingsLib/src/com/android/settingslib/CustomEditTextPreferenceCompat.java b/packages/SettingsLib/src/com/android/settingslib/CustomEditTextPreferenceCompat.java
new file mode 100644
index 000000000000..6ddc89af03ad
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/CustomEditTextPreferenceCompat.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2018 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.settingslib;
+
+import static android.text.InputType.TYPE_CLASS_TEXT;
+import static android.text.InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.EditText;
+
+import androidx.annotation.CallSuper;
+import androidx.appcompat.app.AlertDialog;
+import androidx.preference.EditTextPreference;
+import androidx.preference.EditTextPreferenceDialogFragmentCompat;
+
+public class CustomEditTextPreferenceCompat extends EditTextPreference {
+
+ private CustomPreferenceDialogFragment mFragment;
+
+ public CustomEditTextPreferenceCompat(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ public CustomEditTextPreferenceCompat(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public CustomEditTextPreferenceCompat(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public CustomEditTextPreferenceCompat(Context context) {
+ super(context);
+ }
+
+ public EditText getEditText() {
+ if (mFragment != null) {
+ final Dialog dialog = mFragment.getDialog();
+ if (dialog != null) {
+ return (EditText) dialog.findViewById(android.R.id.edit);
+ }
+ }
+ return null;
+ }
+
+ public boolean isDialogOpen() {
+ return getDialog() != null && getDialog().isShowing();
+ }
+
+ public Dialog getDialog() {
+ return mFragment != null ? mFragment.getDialog() : null;
+ }
+
+ protected void onPrepareDialogBuilder(AlertDialog.Builder builder,
+ DialogInterface.OnClickListener listener) {
+ }
+
+ protected void onDialogClosed(boolean positiveResult) {
+ }
+
+ protected void onClick(DialogInterface dialog, int which) {
+ }
+
+ @CallSuper
+ protected void onBindDialogView(View view) {
+ final EditText editText = view.findViewById(android.R.id.edit);
+ if (editText != null) {
+ editText.setInputType(TYPE_CLASS_TEXT | TYPE_TEXT_FLAG_CAP_SENTENCES);
+ editText.requestFocus();
+ }
+ }
+
+ private void setFragment(CustomPreferenceDialogFragment fragment) {
+ mFragment = fragment;
+ }
+
+ public static class CustomPreferenceDialogFragment extends
+ EditTextPreferenceDialogFragmentCompat {
+
+ public static CustomPreferenceDialogFragment newInstance(String key) {
+ final CustomPreferenceDialogFragment fragment = new CustomPreferenceDialogFragment();
+ final Bundle b = new Bundle(1);
+ b.putString(ARG_KEY, key);
+ fragment.setArguments(b);
+ return fragment;
+ }
+
+ private CustomEditTextPreferenceCompat getCustomizablePreference() {
+ return (CustomEditTextPreferenceCompat) getPreference();
+ }
+
+ @Override
+ protected void onBindDialogView(View view) {
+ super.onBindDialogView(view);
+ getCustomizablePreference().onBindDialogView(view);
+ }
+
+ @Override
+ protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
+ super.onPrepareDialogBuilder(builder);
+ getCustomizablePreference().setFragment(this);
+ getCustomizablePreference().onPrepareDialogBuilder(builder, this);
+ }
+
+ @Override
+ public void onDialogClosed(boolean positiveResult) {
+ super.onDialogClosed(positiveResult);
+ getCustomizablePreference().onDialogClosed(positiveResult);
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ super.onClick(dialog, which);
+ getCustomizablePreference().onClick(dialog, which);
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
index c98bc39fe574..bc5a2c05e379 100644
--- a/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
@@ -16,6 +16,8 @@
package com.android.settingslib;
+import static android.content.Context.TELEPHONY_SERVICE;
+
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -30,6 +32,7 @@ import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.Log;
+
import androidx.annotation.VisibleForTesting;
import java.io.BufferedReader;
@@ -43,8 +46,6 @@ import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import static android.content.Context.TELEPHONY_SERVICE;
-
public class DeviceInfoUtils {
private static final String TAG = "DeviceInfoUtils";
diff --git a/packages/SettingsLib/src/com/android/settingslib/HelpUtils.java b/packages/SettingsLib/src/com/android/settingslib/HelpUtils.java
deleted file mode 100644
index 8055caaad536..000000000000
--- a/packages/SettingsLib/src/com/android/settingslib/HelpUtils.java
+++ /dev/null
@@ -1,246 +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.settingslib;
-
-import android.app.Activity;
-import android.content.ActivityNotFoundException;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources;
-import android.content.res.Resources.Theme;
-import android.content.res.TypedArray;
-import android.net.Uri;
-import android.provider.Settings.Global;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.MenuItem.OnMenuItemClickListener;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-
-import java.net.URISyntaxException;
-import java.util.Locale;
-
-/**
- * Functions to easily prepare contextual help menu option items with an intent that opens up the
- * browser to a particular URL, while taking into account the preferred language and app version.
- */
-public class HelpUtils {
- private final static String TAG = HelpUtils.class.getSimpleName();
-
- private static final int MENU_HELP = Menu.FIRST + 100;
-
- /**
- * Help URL query parameter key for the preferred language.
- */
- private final static String PARAM_LANGUAGE_CODE = "hl";
-
- /**
- * Help URL query parameter key for the app version.
- */
- private final static String PARAM_VERSION = "version";
-
- // Constants for help intents.
- private static final String EXTRA_CONTEXT = "EXTRA_CONTEXT";
- private static final String EXTRA_THEME = "EXTRA_THEME";
- private static final String EXTRA_PRIMARY_COLOR = "EXTRA_PRIMARY_COLOR";
- private static final String EXTRA_BACKUP_URI = "EXTRA_BACKUP_URI";
-
- /**
- * Cached version code to prevent repeated calls to the package manager.
- */
- private static String sCachedVersionCode = null;
-
- /** Static helper that is not instantiable*/
- private HelpUtils() { }
-
- public static boolean prepareHelpMenuItem(Activity activity, Menu menu, String helpUri,
- String backupContext) {
- MenuItem helpItem = menu.add(0, MENU_HELP, 0, R.string.help_feedback_label);
- helpItem.setIcon(R.drawable.ic_help_actionbar);
- return prepareHelpMenuItem(activity, helpItem, helpUri, backupContext);
- }
-
- public static boolean prepareHelpMenuItem(Activity activity, Menu menu, int helpUriResource,
- String backupContext) {
- MenuItem helpItem = menu.add(0, MENU_HELP, 0, R.string.help_feedback_label);
- helpItem.setIcon(R.drawable.ic_help_actionbar);
- return prepareHelpMenuItem(activity, helpItem, activity.getString(helpUriResource),
- backupContext);
- }
-
- /**
- * Prepares the help menu item by doing the following.
- * - If the helpUrlString is empty or null, the help menu item is made invisible.
- * - Otherwise, this makes the help menu item visible and sets the intent for the help menu
- * item to view the URL.
- *
- * @return returns whether the help menu item has been made visible.
- */
- public static boolean prepareHelpMenuItem(final Activity activity, MenuItem helpMenuItem,
- String helpUriString, String backupContext) {
- if (Global.getInt(activity.getContentResolver(), Global.DEVICE_PROVISIONED, 0) == 0) {
- return false;
- }
- if (TextUtils.isEmpty(helpUriString)) {
- // The help url string is empty or null, so set the help menu item to be invisible.
- helpMenuItem.setVisible(false);
-
- // return that the help menu item is not visible (i.e. false)
- return false;
- } else {
- final Intent intent = getHelpIntent(activity, helpUriString, backupContext);
-
- // Set the intent to the help menu item, show the help menu item in the overflow
- // menu, and make it visible.
- if (intent != null) {
- helpMenuItem.setOnMenuItemClickListener(new OnMenuItemClickListener() {
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- MetricsLogger.action(activity,
- MetricsEvent.ACTION_SETTING_HELP_AND_FEEDBACK,
- intent.getStringExtra(EXTRA_CONTEXT));
- try {
- activity.startActivityForResult(intent, 0);
- } catch (ActivityNotFoundException exc) {
- Log.e(TAG, "No activity found for intent: " + intent);
- }
- return true;
- }
- });
- helpMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
- helpMenuItem.setVisible(true);
- } else {
- helpMenuItem.setVisible(false);
- return false;
- }
-
- // return that the help menu item is visible (i.e., true)
- return true;
- }
- }
-
- public static Intent getHelpIntent(Context context, String helpUriString,
- String backupContext) {
- if (Global.getInt(context.getContentResolver(), Global.DEVICE_PROVISIONED, 0) == 0) {
- return null;
- }
- // Try to handle as Intent Uri, otherwise just treat as Uri.
- try {
- Intent intent = Intent.parseUri(helpUriString,
- Intent.URI_ANDROID_APP_SCHEME | Intent.URI_INTENT_SCHEME);
- addIntentParameters(context, intent, backupContext, true /* sendPackageName */);
- ComponentName component = intent.resolveActivity(context.getPackageManager());
- if (component != null) {
- return intent;
- } else if (intent.hasExtra(EXTRA_BACKUP_URI)) {
- // This extra contains a backup URI for when the intent isn't available.
- return getHelpIntent(context, intent.getStringExtra(EXTRA_BACKUP_URI),
- backupContext);
- } else {
- return null;
- }
- } catch (URISyntaxException e) {
- }
- // The help url string exists, so first add in some extra query parameters.
- final Uri fullUri = uriWithAddedParameters(context, Uri.parse(helpUriString));
-
- // Then, create an intent that will be fired when the user
- // selects this help menu item.
- Intent intent = new Intent(Intent.ACTION_VIEW, fullUri);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
- return intent;
- }
-
- public static void addIntentParameters(Context context, Intent intent, String backupContext,
- boolean sendPackageName) {
- if (!intent.hasExtra(EXTRA_CONTEXT)) {
- // Insert some context if none exists.
- intent.putExtra(EXTRA_CONTEXT, backupContext);
- }
-
- Resources resources = context.getResources();
- boolean includePackageName =
- resources.getBoolean(com.android.internal.R.bool.config_sendPackageName);
-
- if (sendPackageName && includePackageName) {
- String[] packageNameKey =
- {resources.getString(com.android.internal.R.string.config_helpPackageNameKey)};
- String[] packageNameValue =
- {resources.getString(
- com.android.internal.R.string.config_helpPackageNameValue)};
- String helpIntentExtraKey =
- resources.getString(com.android.internal.R.string.config_helpIntentExtraKey);
- String helpIntentNameKey =
- resources.getString(com.android.internal.R.string.config_helpIntentNameKey);
- String feedbackIntentExtraKey =
- resources.getString(
- com.android.internal.R.string.config_feedbackIntentExtraKey);
- String feedbackIntentNameKey =
- resources.getString(com.android.internal.R.string.config_feedbackIntentNameKey);
- intent.putExtra(helpIntentExtraKey, packageNameKey);
- intent.putExtra(helpIntentNameKey, packageNameValue);
- intent.putExtra(feedbackIntentExtraKey, packageNameKey);
- intent.putExtra(feedbackIntentNameKey, packageNameValue);
- }
- intent.putExtra(EXTRA_THEME, 0 /* Light theme */);
- TypedArray array = context.obtainStyledAttributes(new int[]{android.R.attr.colorPrimary});
- intent.putExtra(EXTRA_PRIMARY_COLOR, array.getColor(0, 0));
- array.recycle();
- }
-
- /**
- * Adds two query parameters into the Uri, namely the language code and the version code
- * of the app's package as gotten via the context.
- * @return the uri with added query parameters
- */
- private static Uri uriWithAddedParameters(Context context, Uri baseUri) {
- Uri.Builder builder = baseUri.buildUpon();
-
- // Add in the preferred language
- builder.appendQueryParameter(PARAM_LANGUAGE_CODE, Locale.getDefault().toString());
-
- // Add in the package version code
- if (sCachedVersionCode == null) {
- // There is no cached version code, so try to get it from the package manager.
- try {
- // cache the version code
- PackageInfo info = context.getPackageManager().getPackageInfo(
- context.getPackageName(), 0);
- sCachedVersionCode = Long.toString(info.getLongVersionCode());
-
- // append the version code to the uri
- builder.appendQueryParameter(PARAM_VERSION, sCachedVersionCode);
- } catch (NameNotFoundException e) {
- // Cannot find the package name, so don't add in the version parameter
- // This shouldn't happen.
- Log.wtf(TAG, "Invalid package name for context", e);
- }
- } else {
- builder.appendQueryParameter(PARAM_VERSION, sCachedVersionCode);
- }
-
- // Build the full uri and return it
- return builder.build();
- }
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockImageSpan.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockImageSpan.java
index 360a34c959f2..787f27efbc30 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockImageSpan.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockImageSpan.java
@@ -37,7 +37,7 @@ public class RestrictedLockImageSpan extends ImageSpan {
mContext = context;
mExtraPadding = mContext.getResources().getDimensionPixelSize(
R.dimen.restricted_icon_padding);
- mRestrictedPadlock = RestrictedLockUtils.getRestrictedPadlock(mContext);
+ mRestrictedPadlock = RestrictedLockUtilsInternal.getRestrictedPadlock(mContext);
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
index 7f518c1d71d3..1457fcfadc89 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
@@ -26,16 +26,14 @@ import android.app.AppGlobals;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
+import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
-import android.provider.Settings;
-import androidx.annotation.VisibleForTesting;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.ForegroundColorSpan;
@@ -43,23 +41,30 @@ import android.text.style.ImageSpan;
import android.view.MenuItem;
import android.widget.TextView;
+import androidx.annotation.VisibleForTesting;
+
import com.android.internal.widget.LockPatternUtils;
import java.util.List;
-import java.util.Objects;
/**
* Utility class to host methods usable in adding a restricted padlock icon and showing admin
* support message dialog.
*/
-public class RestrictedLockUtils {
+public class RestrictedLockUtilsInternal extends RestrictedLockUtils {
/**
* @return drawables for displaying with settings that are locked by a device admin.
*/
public static Drawable getRestrictedPadlock(Context context) {
- Drawable restrictedPadlock = context.getDrawable(R.drawable.ic_info);
+ Drawable restrictedPadlock = context.getDrawable(android.R.drawable.ic_info);
final int iconSize = context.getResources().getDimensionPixelSize(
- R.dimen.restricted_icon_size);
+ android.R.dimen.config_restrictedIconSize);
+
+ TypedArray ta = context.obtainStyledAttributes(new int[]{android.R.attr.colorAccent});
+ int colorAccent = ta.getColor(0, 0);
+ ta.recycle();
+ restrictedPadlock.setTint(colorAccent);
+
restrictedPadlock.setBounds(0, 0, iconSize, iconSize);
return restrictedPadlock;
}
@@ -159,6 +164,17 @@ public class RestrictedLockUtils {
}
/**
+ * @return the UserHandle for a userId. Return null for USER_NULL
+ */
+ private static UserHandle getUserHandleOf(@UserIdInt int userId) {
+ if (userId == UserHandle.USER_NULL) {
+ return null;
+ } else {
+ return UserHandle.of(userId);
+ }
+ }
+
+ /**
* Filter a set of device admins based on a predicate {@code check}. This is equivalent to
* {@code admins.stream().filter(check).map(x → new EnforcedAdmin(admin, userId)} except it's
* returning a zero/one/many-type thing.
@@ -178,11 +194,13 @@ public class RestrictedLockUtils {
if (admins == null) {
return null;
}
+
+ final UserHandle user = getUserHandleOf(userId);
EnforcedAdmin enforcedAdmin = null;
for (ComponentName admin : admins) {
if (check.isEnforcing(dpm, admin, userId)) {
if (enforcedAdmin == null) {
- enforcedAdmin = new EnforcedAdmin(admin, userId);
+ enforcedAdmin = new EnforcedAdmin(admin, user);
} else {
return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
}
@@ -206,7 +224,7 @@ public class RestrictedLockUtils {
IPackageManager ipm = AppGlobals.getPackageManager();
try {
if (ipm.getBlockUninstallForUser(packageName, userId)) {
- return getProfileOrDeviceOwner(context, userId);
+ return getProfileOrDeviceOwner(context, getUserHandleOf(userId));
}
} catch (RemoteException e) {
// Nothing to do
@@ -225,7 +243,7 @@ public class RestrictedLockUtils {
IPackageManager ipm = AppGlobals.getPackageManager();
try {
if (ipm.isPackageSuspendedForUser(packageName, userId)) {
- return getProfileOrDeviceOwner(context, userId);
+ return getProfileOrDeviceOwner(context, getUserHandleOf(userId));
}
} catch (RemoteException | IllegalArgumentException e) {
// Nothing to do
@@ -240,14 +258,15 @@ public class RestrictedLockUtils {
if (dpm == null) {
return null;
}
- EnforcedAdmin admin = getProfileOrDeviceOwner(context, userId);
+ EnforcedAdmin admin = getProfileOrDeviceOwner(context, getUserHandleOf(userId));
boolean permitted = true;
if (admin != null) {
permitted = dpm.isInputMethodPermittedByAdmin(admin.component,
packageName, userId);
}
int managedProfileId = getManagedProfileId(context, userId);
- EnforcedAdmin profileAdmin = getProfileOrDeviceOwner(context, managedProfileId);
+ EnforcedAdmin profileAdmin = getProfileOrDeviceOwner(context,
+ getUserHandleOf(managedProfileId));
boolean permittedByProfileAdmin = true;
if (profileAdmin != null) {
permittedByProfileAdmin = dpm.isInputMethodPermittedByAdmin(profileAdmin.component,
@@ -293,14 +312,15 @@ public class RestrictedLockUtils {
if (dpm == null) {
return null;
}
- EnforcedAdmin admin = getProfileOrDeviceOwner(context, userId);
+ EnforcedAdmin admin = getProfileOrDeviceOwner(context, getUserHandleOf(userId));
boolean permitted = true;
if (admin != null) {
permitted = dpm.isAccessibilityServicePermittedByAdmin(admin.component,
packageName, userId);
}
int managedProfileId = getManagedProfileId(context, userId);
- EnforcedAdmin profileAdmin = getProfileOrDeviceOwner(context, managedProfileId);
+ EnforcedAdmin profileAdmin = getProfileOrDeviceOwner(context,
+ getUserHandleOf(managedProfileId));
boolean permittedByProfileAdmin = true;
if (profileAdmin != null) {
permittedByProfileAdmin = dpm.isAccessibilityServicePermittedByAdmin(
@@ -360,7 +380,7 @@ public class RestrictedLockUtils {
if (!isAccountTypeDisabled) {
return null;
}
- return getProfileOrDeviceOwner(context, userId);
+ return getProfileOrDeviceOwner(context, getUserHandleOf(userId));
}
/**
@@ -372,7 +392,8 @@ public class RestrictedLockUtils {
*/
public static EnforcedAdmin checkIfMeteredDataRestricted(Context context,
String packageName, int userId) {
- final EnforcedAdmin enforcedAdmin = getProfileOrDeviceOwner(context, userId);
+ final EnforcedAdmin enforcedAdmin = getProfileOrDeviceOwner(context,
+ getUserHandleOf(userId));
if (enforcedAdmin == null) {
return null;
}
@@ -397,7 +418,7 @@ public class RestrictedLockUtils {
return null;
}
ComponentName adminComponent = dpm.getDeviceOwnerComponentOnCallingUser();
- return new EnforcedAdmin(adminComponent, UserHandle.myUserId());
+ return new EnforcedAdmin(adminComponent, getUserHandleOf(UserHandle.myUserId()));
}
/**
@@ -429,10 +450,11 @@ public class RestrictedLockUtils {
return null;
}
EnforcedAdmin enforcedAdmin = null;
+ final UserHandle user = getUserHandleOf(userId);
for (ComponentName admin : admins) {
if (check.isEnforcing(dpm, admin, userId)) {
if (enforcedAdmin == null) {
- enforcedAdmin = new EnforcedAdmin(admin, userId);
+ enforcedAdmin = new EnforcedAdmin(admin, user);
} else {
return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
}
@@ -483,13 +505,14 @@ public class RestrictedLockUtils {
if (admins == null) {
continue;
}
+ final UserHandle user = getUserHandleOf(userInfo.id);
final boolean isSeparateProfileChallengeEnabled =
sProxy.isSeparateProfileChallengeEnabled(lockPatternUtils, userInfo.id);
for (ComponentName admin : admins) {
if (!isSeparateProfileChallengeEnabled) {
if (check.isEnforcing(dpm, admin, userInfo.id)) {
if (enforcedAdmin == null) {
- enforcedAdmin = new EnforcedAdmin(admin, userInfo.id);
+ enforcedAdmin = new EnforcedAdmin(admin, user);
} else {
return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
}
@@ -506,7 +529,7 @@ public class RestrictedLockUtils {
DevicePolicyManager parentDpm = sProxy.getParentProfileInstance(dpm, userInfo);
if (check.isEnforcing(parentDpm, admin, userInfo.id)) {
if (enforcedAdmin == null) {
- enforcedAdmin = new EnforcedAdmin(admin, userInfo.id);
+ enforcedAdmin = new EnforcedAdmin(admin, user);
} else {
return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
}
@@ -517,33 +540,6 @@ public class RestrictedLockUtils {
return enforcedAdmin;
}
- public static EnforcedAdmin getProfileOrDeviceOwner(Context context, int userId) {
- return getProfileOrDeviceOwner(context, null, userId);
- }
-
- public static EnforcedAdmin getProfileOrDeviceOwner(
- Context context, String enforcedRestriction, int userId) {
- if (userId == UserHandle.USER_NULL) {
- return null;
- }
- final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
- Context.DEVICE_POLICY_SERVICE);
- if (dpm == null) {
- return null;
- }
- ComponentName adminComponent = dpm.getProfileOwnerAsUser(userId);
- if (adminComponent != null) {
- return new EnforcedAdmin(adminComponent, enforcedRestriction, userId);
- }
- if (dpm.getDeviceOwnerUserId() == userId) {
- adminComponent = dpm.getDeviceOwnerComponentOnAnyUser();
- if (adminComponent != null) {
- return new EnforcedAdmin(adminComponent, enforcedRestriction, userId);
- }
- }
- return null;
- }
-
public static EnforcedAdmin getDeviceOwner(Context context) {
return getDeviceOwner(context, null);
}
@@ -557,7 +553,7 @@ public class RestrictedLockUtils {
ComponentName adminComponent = dpm.getDeviceOwnerComponentOnAnyUser();
if (adminComponent != null) {
return new EnforcedAdmin(
- adminComponent, enforcedRestriction, dpm.getDeviceOwnerUserId());
+ adminComponent, enforcedRestriction, dpm.getDeviceOwnerUser());
}
return null;
}
@@ -578,7 +574,7 @@ public class RestrictedLockUtils {
}
ComponentName adminComponent = dpm.getProfileOwnerAsUser(userId);
if (adminComponent != null) {
- return new EnforcedAdmin(adminComponent, enforcedRestriction, userId);
+ return new EnforcedAdmin(adminComponent, enforcedRestriction, getUserHandleOf(userId));
}
return null;
}
@@ -631,45 +627,6 @@ public class RestrictedLockUtils {
}
}
- /**
- * Send the intent to trigger the {@link android.settings.ShowAdminSupportDetailsDialog}.
- */
- public static void sendShowAdminSupportDetailsIntent(Context context, EnforcedAdmin admin) {
- final Intent intent = getShowAdminSupportDetailsIntent(context, admin);
- int targetUserId = UserHandle.myUserId();
- if (admin != null && admin.userId != UserHandle.USER_NULL
- && isCurrentUserOrProfile(context, admin.userId)) {
- targetUserId = admin.userId;
- }
- intent.putExtra(DevicePolicyManager.EXTRA_RESTRICTION, admin.enforcedRestriction);
- context.startActivityAsUser(intent, new UserHandle(targetUserId));
- }
-
- public static Intent getShowAdminSupportDetailsIntent(Context context, EnforcedAdmin admin) {
- final Intent intent = new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS);
- if (admin != null) {
- if (admin.component != null) {
- intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, admin.component);
- }
- int adminUserId = UserHandle.myUserId();
- if (admin.userId != UserHandle.USER_NULL) {
- adminUserId = admin.userId;
- }
- intent.putExtra(Intent.EXTRA_USER_ID, adminUserId);
- }
- return intent;
- }
-
- public static boolean isCurrentUserOrProfile(Context context, int userId) {
- UserManager um = UserManager.get(context);
- for (UserInfo userInfo : um.getProfiles(UserHandle.myUserId())) {
- if (userInfo.id == userId) {
- return true;
- }
- }
- return false;
- }
-
public static boolean isAdminInCurrentUserOrProfile(Context context, ComponentName admin) {
DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
Context.DEVICE_POLICY_SERVICE);
@@ -715,75 +672,6 @@ public class RestrictedLockUtils {
textView.setText(sb);
}
- public static class EnforcedAdmin {
- @Nullable
- public ComponentName component = null;
- /**
- * The restriction enforced by admin. It could be any user restriction or policy like
- * {@link DevicePolicyManager#POLICY_DISABLE_CAMERA}.
- */
- @Nullable
- public String enforcedRestriction = null;
- public int userId = UserHandle.USER_NULL;
-
- // We use this to represent the case where a policy is enforced by multiple admins.
- public final static EnforcedAdmin MULTIPLE_ENFORCED_ADMIN = new EnforcedAdmin();
-
- public static EnforcedAdmin createDefaultEnforcedAdminWithRestriction(
- String enforcedRestriction) {
- EnforcedAdmin enforcedAdmin = new EnforcedAdmin();
- enforcedAdmin.enforcedRestriction = enforcedRestriction;
- return enforcedAdmin;
- }
-
- public EnforcedAdmin(ComponentName component, int userId) {
- this.component = component;
- this.userId = userId;
- }
-
- public EnforcedAdmin(ComponentName component, String enforcedRestriction, int userId) {
- this.component = component;
- this.enforcedRestriction = enforcedRestriction;
- this.userId = userId;
- }
-
- public EnforcedAdmin(EnforcedAdmin other) {
- if (other == null) {
- throw new IllegalArgumentException();
- }
- this.component = other.component;
- this.enforcedRestriction = other.enforcedRestriction;
- this.userId = other.userId;
- }
-
- public EnforcedAdmin() {
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- EnforcedAdmin that = (EnforcedAdmin) o;
- return userId == that.userId &&
- Objects.equals(component, that.component) &&
- Objects.equals(enforcedRestriction, that.enforcedRestriction);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(component, enforcedRestriction, userId);
- }
-
- @Override
- public String toString() {
- return "EnforcedAdmin{" +
- "component=" + component +
- ", enforcedRestriction='" + enforcedRestriction +
- ", userId=" + userId +
- '}';
- }
- }
-
/**
* Static {@link LockPatternUtils} and {@link DevicePolicyManager} wrapper for testing purposes.
* {@link LockPatternUtils} is an internal API not supported by robolectric.
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
index 79e011c6e7ed..ad7e995412aa 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
@@ -16,15 +16,16 @@
package com.android.settingslib;
+import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+
import android.content.Context;
import android.os.UserHandle;
-import androidx.core.content.res.TypedArrayUtils;
-import androidx.preference.PreferenceManager;
-import androidx.preference.PreferenceViewHolder;
import android.util.AttributeSet;
import android.view.View;
-import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+import androidx.core.content.res.TypedArrayUtils;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceViewHolder;
/**
* Preference class that supports being disabled by a user restriction
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
index a930bb8373ba..1ba1f72cb2e1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
@@ -16,19 +16,18 @@
package com.android.settingslib;
+import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+
import android.content.Context;
import android.content.res.TypedArray;
-import android.graphics.drawable.Drawable;
import android.os.UserHandle;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceViewHolder;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.TypedValue;
-import android.view.View;
import android.widget.TextView;
-import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
/**
* Helper class for managing settings preferences that can be disabled
@@ -63,7 +62,7 @@ public class RestrictedPreferenceHelper {
}
mAttrUserRestriction = data == null ? null : data.toString();
// If the system has set the user restriction, then we shouldn't add the padlock.
- if (RestrictedLockUtils.hasBaseUserRestriction(mContext, mAttrUserRestriction,
+ if (RestrictedLockUtilsInternal.hasBaseUserRestriction(mContext, mAttrUserRestriction,
UserHandle.myUserId())) {
mAttrUserRestriction = null;
return;
@@ -134,7 +133,7 @@ public class RestrictedPreferenceHelper {
* @param userId user to check the restriction for.
*/
public void checkRestrictionAndSetDisabled(String userRestriction, int userId) {
- EnforcedAdmin admin = RestrictedLockUtils.checkIfRestrictionEnforced(mContext,
+ EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext,
userRestriction, userId);
setDisabledByAdmin(admin);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
index fe6d9fe77b73..0ed507c46372 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
@@ -21,15 +21,16 @@ import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.UserHandle;
-import androidx.preference.SwitchPreference;
-import androidx.core.content.res.TypedArrayUtils;
-import androidx.preference.PreferenceManager;
-import androidx.preference.PreferenceViewHolder;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.widget.TextView;
+import androidx.core.content.res.TypedArrayUtils;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceViewHolder;
+import androidx.preference.SwitchPreference;
+
/**
* Version of SwitchPreference that can be disabled by a device admin
* using a user restriction.
diff --git a/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java b/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java
index 535b2929563a..c3993e9063b2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java
+++ b/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java
@@ -15,11 +15,16 @@
*/
package com.android.settingslib;
+import static android.os.UserManager.DISALLOW_CONFIG_TETHERING;
+
import android.content.Context;
+import android.net.ConnectivityManager;
import android.os.SystemProperties;
-import androidx.annotation.VisibleForTesting;
+import android.os.UserHandle;
import android.telephony.CarrierConfigManager;
+import androidx.annotation.VisibleForTesting;
+
public class TetherUtil {
@VisibleForTesting
@@ -49,4 +54,14 @@ public class TetherUtil {
}
return (provisionApp.length == 2);
}
+
+ public static boolean isTetherAvailable(Context context) {
+ final ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
+ final boolean tetherConfigDisallowed = RestrictedLockUtilsInternal
+ .checkIfRestrictionEnforced(context, DISALLOW_CONFIG_TETHERING,
+ UserHandle.myUserId()) != null;
+ final boolean hasBaseUserRestriction = RestrictedLockUtilsInternal.hasBaseUserRestriction(
+ context, DISALLOW_CONFIG_TETHERING, UserHandle.myUserId());
+ return (cm.isTetheringSupported() || tetherConfigDisallowed) && !hasBaseUserRestriction;
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/TwoTargetPreference.java b/packages/SettingsLib/src/com/android/settingslib/TwoTargetPreference.java
index 3a26f4649b64..02895a479352 100644
--- a/packages/SettingsLib/src/com/android/settingslib/TwoTargetPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/TwoTargetPreference.java
@@ -18,13 +18,14 @@ package com.android.settingslib;
import android.annotation.IntDef;
import android.content.Context;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceViewHolder;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 505cfeac220c..6abe76a1e753 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -23,11 +23,12 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.print.PrintManager;
import android.provider.Settings;
+import android.telephony.ServiceState;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.UserIcons;
import com.android.settingslib.drawable.UserIconDrawable;
-import com.android.settingslib.wrapper.LocationManagerWrapper;
+
import java.text.NumberFormat;
public class Utils {
@@ -69,8 +70,7 @@ public class Utils {
intent, UserHandle.of(userId), android.Manifest.permission.WRITE_SECURE_SETTINGS);
LocationManager locationManager =
(LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
- LocationManagerWrapper wrapper = new LocationManagerWrapper(locationManager);
- wrapper.setLocationEnabledForUser(enabled, UserHandle.of(userId));
+ locationManager.setLocationEnabledForUser(enabled, UserHandle.of(userId));
}
public static boolean updateLocationMode(Context context, int oldMode, int newMode, int userId,
@@ -203,21 +203,28 @@ public class Utils {
return statusString;
}
- @ColorInt
- public static int getColorAccent(Context context) {
+ public static ColorStateList getColorAccent(Context context) {
return getColorAttr(context, android.R.attr.colorAccent);
}
- @ColorInt
- public static int getColorError(Context context) {
+ public static ColorStateList getColorError(Context context) {
return getColorAttr(context, android.R.attr.colorError);
}
@ColorInt
- public static int getDefaultColor(Context context, int resId) {
+ public static int getColorAccentDefaultColor(Context context) {
+ return getColorAttrDefaultColor(context, android.R.attr.colorAccent);
+ }
+
+ @ColorInt
+ public static int getColorErrorDefaultColor(Context context) {
+ return getColorAttrDefaultColor(context, android.R.attr.colorError);
+ }
+
+ @ColorInt
+ public static int getColorStateListDefaultColor(Context context, int resId) {
final ColorStateList list =
context.getResources().getColorStateList(resId, context.getTheme());
-
return list.getDefaultColor();
}
@@ -242,13 +249,24 @@ public class Utils {
}
@ColorInt
- public static int getColorAttr(Context context, int attr) {
+ public static int getColorAttrDefaultColor(Context context, int attr) {
TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
@ColorInt int colorAccent = ta.getColor(0, 0);
ta.recycle();
return colorAccent;
}
+ public static ColorStateList getColorAttr(Context context, int attr) {
+ TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
+ ColorStateList stateList = null;
+ try {
+ stateList = ta.getColorStateList(0);
+ } finally {
+ ta.recycle();
+ }
+ return stateList;
+ }
+
public static int getThemeAttr(Context context, int attr) {
TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
int theme = ta.getResourceId(0, 0);
@@ -374,4 +392,52 @@ public class Utils {
|| audioMode == AudioManager.MODE_IN_CALL
|| audioMode == AudioManager.MODE_IN_COMMUNICATION;
}
+
+ /**
+ * Return the service state is in-service or not.
+ * To make behavior consistent with SystemUI and Settings/AboutPhone/SIM status UI
+ *
+ * @param serviceState Service state. {@link ServiceState}
+ */
+ public static boolean isInService(ServiceState serviceState) {
+ if (serviceState == null) {
+ return false;
+ }
+ int state = getCombinedServiceState(serviceState);
+ if (state == ServiceState.STATE_POWER_OFF
+ || state == ServiceState.STATE_OUT_OF_SERVICE
+ || state == ServiceState.STATE_EMERGENCY_ONLY) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Return the combined service state.
+ * To make behavior consistent with SystemUI and Settings/AboutPhone/SIM status UI
+ *
+ * @param serviceState Service state. {@link ServiceState}
+ */
+ public static int getCombinedServiceState(ServiceState serviceState) {
+ if (serviceState == null) {
+ return ServiceState.STATE_OUT_OF_SERVICE;
+ }
+
+ // Consider the device to be in service if either voice or data
+ // service is available. Some SIM cards are marketed as data-only
+ // and do not support voice service, and on these SIM cards, we
+ // want to show signal bars for data service as well as the "no
+ // service" or "emergency calls only" text that indicates that voice
+ // is not available.
+ int state = serviceState.getState();
+ int dataState = serviceState.getDataRegState();
+ if (state == ServiceState.STATE_OUT_OF_SERVICE
+ || state == ServiceState.STATE_EMERGENCY_ONLY) {
+ if (dataState == ServiceState.STATE_IN_SERVICE) {
+ return ServiceState.STATE_IN_SERVICE;
+ }
+ }
+ return state;
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
index 8473c06c1ac7..a18600abf788 100644
--- a/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
@@ -40,9 +40,6 @@ import java.util.Set;
public class AccessibilityUtils {
public static final char ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR = ':';
- final static TextUtils.SimpleStringSplitter sStringColonSplitter =
- new TextUtils.SimpleStringSplitter(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR);
-
/**
* @return the set of enabled accessibility services. If there are no services,
* it returns the unmodifiable {@link Collections#emptySet()}.
@@ -72,16 +69,16 @@ public class AccessibilityUtils {
final String enabledServicesSetting = Settings.Secure.getStringForUser(
context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
userId);
- if (enabledServicesSetting == null) {
+ if (TextUtils.isEmpty(enabledServicesSetting)) {
return Collections.emptySet();
}
final Set<ComponentName> enabledServices = new HashSet<>();
- final TextUtils.SimpleStringSplitter colonSplitter = sStringColonSplitter;
+ final TextUtils.StringSplitter colonSplitter =
+ new TextUtils.SimpleStringSplitter(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR);
colonSplitter.setString(enabledServicesSetting);
- while (colonSplitter.hasNext()) {
- final String componentNameString = colonSplitter.next();
+ for (String componentNameString : colonSplitter) {
final ComponentName enabledService = ComponentName.unflattenFromString(
componentNameString);
if (enabledService != null) {
@@ -168,8 +165,7 @@ public class AccessibilityUtils {
* an OEM-configurable default if the setting has never been set.
*
* @param context A valid context
- * @param userId The user whose settings should be checked
- *
+ * @param userId The user whose settings should be checked
* @return The component name, flattened to a string, of the target service.
*/
public static String getShortcutTargetServiceComponentNameString(
@@ -187,9 +183,9 @@ public class AccessibilityUtils {
* Check if the accessibility shortcut is enabled for a user
*
* @param context A valid context
- * @param userId The user of interest
+ * @param userId The user of interest
* @return {@code true} if the shortcut is enabled for the user. {@code false} otherwise.
- * Note that the shortcut may be enabled, but no action associated with it.
+ * Note that the shortcut may be enabled, but no action associated with it.
*/
public static boolean isShortcutEnabled(Context context, int userId) {
return Settings.Secure.getIntForUser(context.getContentResolver(),
diff --git a/packages/SettingsLib/src/com/android/settingslib/animation/AppearAnimationUtils.java b/packages/SettingsLib/src/com/android/settingslib/animation/AppearAnimationUtils.java
index df76125a99f2..efac6bc3572d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/animation/AppearAnimationUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/animation/AppearAnimationUtils.java
@@ -19,15 +19,12 @@ package com.android.settingslib.animation;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
import android.content.Context;
import android.view.RenderNodeAnimator;
import android.view.View;
-import android.view.ViewPropertyAnimator;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
-import com.android.internal.widget.LockPatternView;
import com.android.settingslib.R;
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index fd80edf839bc..a936df2bf2eb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -22,9 +22,6 @@ import android.app.AppGlobals;
import android.app.Application;
import android.app.usage.StorageStats;
import android.app.usage.StorageStatsManager;
-import androidx.lifecycle.Lifecycle;
-import androidx.lifecycle.LifecycleObserver;
-import androidx.lifecycle.OnLifecycleEvent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -49,12 +46,16 @@ import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
-import androidx.annotation.VisibleForTesting;
import android.text.format.Formatter;
import android.util.IconDrawableFactory;
import android.util.Log;
import android.util.SparseArray;
+import androidx.annotation.VisibleForTesting;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.OnLifecycleEvent;
+
import com.android.internal.R;
import com.android.internal.util.ArrayUtils;
@@ -253,7 +254,8 @@ public class ApplicationsState {
user.isAdmin() ? mAdminRetrieveFlags : mRetrieveFlags,
user.id);
mApplications.addAll(list.getList());
- } catch (RemoteException e) {
+ } catch (Exception e) {
+ Log.e(TAG, "Error during doResumeIfNeededLocked", e);
}
}
@@ -674,6 +676,7 @@ public class ApplicationsState {
if (!mResumed) {
mResumed = true;
mSessionsChanged = true;
+ doPauseLocked();
doResumeIfNeededLocked();
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/DefaultAppInfo.java b/packages/SettingsLib/src/com/android/settingslib/applications/DefaultAppInfo.java
index 246ca474da32..3c451127508d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/DefaultAppInfo.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/DefaultAppInfo.java
@@ -25,11 +25,9 @@ import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.os.RemoteException;
-import android.os.UserHandle;
import android.util.IconDrawableFactory;
import com.android.settingslib.widget.CandidateInfo;
-import com.android.settingslib.wrapper.PackageManagerWrapper;
/**
* Data model representing an app in DefaultAppPicker UI.
@@ -40,18 +38,18 @@ public class DefaultAppInfo extends CandidateInfo {
public final ComponentName componentName;
public final PackageItemInfo packageItemInfo;
public final String summary;
- protected final PackageManagerWrapper mPm;
+ protected final PackageManager mPm;
private final Context mContext;
- public DefaultAppInfo(Context context, PackageManagerWrapper pm, int uid, ComponentName cn) {
+ public DefaultAppInfo(Context context, PackageManager pm, int uid, ComponentName cn) {
this(context, pm, uid, cn, null /* summary */, true /* enabled */);
}
- public DefaultAppInfo(Context context, PackageManagerWrapper pm, PackageItemInfo info) {
- this(context, pm, info, null /* summary */, true /* enabled */);
+ public DefaultAppInfo(Context context, PackageManager pm, int uid, PackageItemInfo info) {
+ this(context, pm, uid, info, null /* summary */, true /* enabled */);
}
- public DefaultAppInfo(Context context, PackageManagerWrapper pm, int uid, ComponentName cn,
+ public DefaultAppInfo(Context context, PackageManager pm, int uid, ComponentName cn,
String summary, boolean enabled) {
super(enabled);
mContext = context;
@@ -62,12 +60,12 @@ public class DefaultAppInfo extends CandidateInfo {
this.summary = summary;
}
- public DefaultAppInfo(Context context, PackageManagerWrapper pm, PackageItemInfo info,
+ public DefaultAppInfo(Context context, PackageManager pm, int uid, PackageItemInfo info,
String summary, boolean enabled) {
super(enabled);
mContext = context;
mPm = pm;
- userId = UserHandle.myUserId();
+ userId = uid;
packageItemInfo = info;
componentName = null;
this.summary = summary;
@@ -79,17 +77,17 @@ public class DefaultAppInfo extends CandidateInfo {
try {
final ComponentInfo componentInfo = getComponentInfo();
if (componentInfo != null) {
- return componentInfo.loadLabel(mPm.getPackageManager());
+ return componentInfo.loadLabel(mPm);
} else {
final ApplicationInfo appInfo = mPm.getApplicationInfoAsUser(
componentName.getPackageName(), 0, userId);
- return appInfo.loadLabel(mPm.getPackageManager());
+ return appInfo.loadLabel(mPm);
}
} catch (PackageManager.NameNotFoundException e) {
return null;
}
} else if (packageItemInfo != null) {
- return packageItemInfo.loadLabel(mPm.getPackageManager());
+ return packageItemInfo.loadLabel(mPm);
} else {
return null;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java b/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java
index 3c3c70ac364e..454d1dce0b2f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java
@@ -32,8 +32,6 @@ import android.os.Handler;
import android.provider.Settings;
import android.util.Slog;
-import com.android.settingslib.wrapper.PackageManagerWrapper;
-
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -127,8 +125,7 @@ public class ServiceListing {
mServices.clear();
final int user = ActivityManager.getCurrentUser();
- final PackageManagerWrapper pmWrapper =
- new PackageManagerWrapper(mContext.getPackageManager());
+ final PackageManager pmWrapper = mContext.getPackageManager();
List<ResolveInfo> installedServices = pmWrapper.queryIntentServicesAsUser(
new Intent(mIntentAction),
PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java b/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java
index b15f35ddc240..b457406dda03 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java
@@ -21,6 +21,7 @@ import android.app.usage.StorageStatsManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.UserHandle;
+
import androidx.annotation.VisibleForTesting;
import java.io.IOException;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
index 3c5816096f8c..58feef55bd29 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -42,7 +42,6 @@ public class A2dpProfile implements LocalBluetoothProfile {
private BluetoothA2dp mService;
private boolean mIsProfileReady;
- private final LocalBluetoothAdapter mLocalAdapter;
private final CachedBluetoothDeviceManager mDeviceManager;
static final ParcelUuid[] SINK_UUIDS = {
@@ -71,7 +70,7 @@ public class A2dpProfile implements LocalBluetoothProfile {
// we may add a new device here, but generally this should not happen
if (device == null) {
Log.w(TAG, "A2dpProfile found new device: " + nextDevice);
- device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
+ device = mDeviceManager.addDevice(nextDevice);
}
device.onProfileStateChanged(A2dpProfile.this, BluetoothProfile.STATE_CONNECTED);
device.refresh();
@@ -94,14 +93,12 @@ public class A2dpProfile implements LocalBluetoothProfile {
return BluetoothProfile.A2DP;
}
- A2dpProfile(Context context, LocalBluetoothAdapter adapter,
- CachedBluetoothDeviceManager deviceManager,
+ A2dpProfile(Context context, CachedBluetoothDeviceManager deviceManager,
LocalBluetoothProfileManager profileManager) {
mContext = context;
- mLocalAdapter = adapter;
mDeviceManager = deviceManager;
mProfileManager = profileManager;
- mLocalAdapter.getProfileProxy(context, new A2dpServiceListener(),
+ BluetoothAdapter.getDefaultAdapter().getProfileProxy(context, new A2dpServiceListener(),
BluetoothProfile.A2DP);
}
@@ -296,7 +293,7 @@ public class A2dpProfile implements LocalBluetoothProfile {
return R.string.bluetooth_a2dp_profile_summary_connected;
default:
- return Utils.getConnectionStateSummary(state);
+ return BluetoothUtils.getConnectionStateSummary(state);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
index 656f23ffb70c..988062de0a37 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
@@ -37,7 +37,6 @@ final class A2dpSinkProfile implements LocalBluetoothProfile {
private BluetoothA2dpSink mService;
private boolean mIsProfileReady;
- private final LocalBluetoothAdapter mLocalAdapter;
private final CachedBluetoothDeviceManager mDeviceManager;
static final ParcelUuid[] SRC_UUIDS = {
@@ -56,7 +55,7 @@ final class A2dpSinkProfile implements LocalBluetoothProfile {
implements BluetoothProfile.ServiceListener {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- Log.d(TAG, "Bluetooth service connected, profile:" + profile);
+ Log.d(TAG, "Bluetooth service connected");
mService = (BluetoothA2dpSink) proxy;
// We just bound to the service, so refresh the UI for any connected A2DP devices.
List<BluetoothDevice> deviceList = mService.getConnectedDevices();
@@ -66,7 +65,7 @@ final class A2dpSinkProfile implements LocalBluetoothProfile {
// we may add a new device here, but generally this should not happen
if (device == null) {
Log.w(TAG, "A2dpSinkProfile found new device: " + nextDevice);
- device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
+ device = mDeviceManager.addDevice(nextDevice);
}
device.onProfileStateChanged(A2dpSinkProfile.this, BluetoothProfile.STATE_CONNECTED);
device.refresh();
@@ -75,7 +74,7 @@ final class A2dpSinkProfile implements LocalBluetoothProfile {
}
public void onServiceDisconnected(int profile) {
- Log.d(TAG, "Bluetooth service disconnected, profile:" + profile);
+ Log.d(TAG, "Bluetooth service disconnected");
mIsProfileReady=false;
}
}
@@ -89,13 +88,11 @@ final class A2dpSinkProfile implements LocalBluetoothProfile {
return BluetoothProfile.A2DP_SINK;
}
- A2dpSinkProfile(Context context, LocalBluetoothAdapter adapter,
- CachedBluetoothDeviceManager deviceManager,
+ A2dpSinkProfile(Context context, CachedBluetoothDeviceManager deviceManager,
LocalBluetoothProfileManager profileManager) {
- mLocalAdapter = adapter;
mDeviceManager = deviceManager;
mProfileManager = profileManager;
- mLocalAdapter.getProfileProxy(context, new A2dpSinkServiceListener(),
+ BluetoothAdapter.getDefaultAdapter().getProfileProxy(context, new A2dpSinkServiceListener(),
BluetoothProfile.A2DP_SINK);
}
@@ -205,7 +202,7 @@ final class A2dpSinkProfile implements LocalBluetoothProfile {
return R.string.bluetooth_a2dp_profile_summary_connected;
default:
- return Utils.getConnectionStateSummary(state);
+ return BluetoothUtils.getConnectionStateSummary(state);
}
}
@@ -218,7 +215,7 @@ final class A2dpSinkProfile implements LocalBluetoothProfile {
if (mService != null) {
try {
BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.A2DP_SINK,
- mService);
+ mService);
mService = null;
}catch (Throwable t) {
Log.w(TAG, "Error cleaning up A2DP proxy", t);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
index c7f8c205ec6d..3152e65d5a36 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
@@ -22,15 +22,122 @@ package com.android.settingslib.bluetooth;
* UI to receive events from {@link BluetoothEventManager}.
*/
public interface BluetoothCallback {
- void onBluetoothStateChanged(int bluetoothState);
- void onScanningStateChanged(boolean started);
- void onDeviceAdded(CachedBluetoothDevice cachedDevice);
- void onDeviceDeleted(CachedBluetoothDevice cachedDevice);
- void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState);
- void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state);
- void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile);
- void onAudioModeChanged();
+ /**
+ * It will be called when the state of the local Bluetooth adapter has been changed.
+ * It is listening {@link android.bluetooth.BluetoothAdapter#ACTION_STATE_CHANGED}.
+ * For example, Bluetooth has been turned on or off.
+ *
+ * @param bluetoothState the current Bluetooth state, the possible values are:
+ * {@link android.bluetooth.BluetoothAdapter#STATE_OFF},
+ * {@link android.bluetooth.BluetoothAdapter#STATE_TURNING_ON},
+ * {@link android.bluetooth.BluetoothAdapter#STATE_ON},
+ * {@link android.bluetooth.BluetoothAdapter#STATE_TURNING_OFF}.
+ */
+ default void onBluetoothStateChanged(int bluetoothState) {}
+
+ /**
+ * It will be called when the local Bluetooth adapter has started
+ * or finished the remote device discovery process.
+ * It is listening {@link android.bluetooth.BluetoothAdapter#ACTION_DISCOVERY_STARTED} and
+ * {@link android.bluetooth.BluetoothAdapter#ACTION_DISCOVERY_FINISHED}.
+ *
+ * @param started indicate the current process is started or finished.
+ */
+ default void onScanningStateChanged(boolean started) {}
+
+ /**
+ * It will be called in following situations:
+ * 1. In scanning mode, when a new device has been found.
+ * 2. When a profile service is connected and existing connected devices has been found.
+ * This API only invoked once for each device and all devices will be cached in
+ * {@link CachedBluetoothDeviceManager}.
+ *
+ * @param cachedDevice the Bluetooth device.
+ */
+ default void onDeviceAdded(CachedBluetoothDevice cachedDevice) {}
+
+ /**
+ * It will be called when requiring to remove a remote device from CachedBluetoothDevice list
+ *
+ * @param cachedDevice the Bluetooth device.
+ */
+ default void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {}
+
+ /**
+ * It will be called when bond state of a remote device is changed.
+ * It is listening {@link android.bluetooth.BluetoothDevice#ACTION_BOND_STATE_CHANGED}
+ *
+ * @param cachedDevice the Bluetooth device.
+ * @param bondState the Bluetooth device bond state, the possible values are:
+ * {@link android.bluetooth.BluetoothDevice#BOND_NONE},
+ * {@link android.bluetooth.BluetoothDevice#BOND_BONDING},
+ * {@link android.bluetooth.BluetoothDevice#BOND_BONDED}.
+ */
+ default void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {}
+
+ /**
+ * It will be called in following situations:
+ * 1. When the adapter is not connected to any profiles of any remote devices
+ * and it attempts a connection to a profile.
+ * 2. When the adapter disconnects from the last profile of the last device.
+ * It is listening {@link android.bluetooth.BluetoothAdapter#ACTION_CONNECTION_STATE_CHANGED}
+ *
+ * @param cachedDevice the Bluetooth device.
+ * @param state the Bluetooth device connection state, the possible values are:
+ * {@link android.bluetooth.BluetoothAdapter#STATE_DISCONNECTED},
+ * {@link android.bluetooth.BluetoothAdapter#STATE_CONNECTING},
+ * {@link android.bluetooth.BluetoothAdapter#STATE_CONNECTED},
+ * {@link android.bluetooth.BluetoothAdapter#STATE_DISCONNECTING}.
+ */
+ default void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {}
+
+ /**
+ * It will be called when device been set as active for {@code bluetoothProfile}
+ * It is listening in following intent:
+ * {@link android.bluetooth.BluetoothA2dp#ACTION_ACTIVE_DEVICE_CHANGED}
+ * {@link android.bluetooth.BluetoothHeadset#ACTION_ACTIVE_DEVICE_CHANGED}
+ * {@link android.bluetooth.BluetoothHearingAid#ACTION_ACTIVE_DEVICE_CHANGED}
+ *
+ * @param activeDevice the active Bluetooth device.
+ * @param bluetoothProfile the profile of active Bluetooth device.
+ */
+ default void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) {}
+
+ /**
+ * It will be called in following situations:
+ * 1. When the call state on the device is changed.
+ * 2. When the audio connection state of the A2DP profile is changed.
+ * It is listening in following intent:
+ * {@link android.bluetooth.BluetoothHeadset#ACTION_AUDIO_STATE_CHANGED}
+ * {@link android.telephony.TelephonyManager#ACTION_PHONE_STATE_CHANGED}
+ */
+ default void onAudioModeChanged() {}
+
+ /**
+ * It will be called when one of the bluetooth device profile connection state is changed.
+ *
+ * @param cachedDevice the active Bluetooth device.
+ * @param state the BluetoothProfile connection state, the possible values are:
+ * {@link android.bluetooth.BluetoothProfile#STATE_CONNECTED},
+ * {@link android.bluetooth.BluetoothProfile#STATE_CONNECTING},
+ * {@link android.bluetooth.BluetoothProfile#STATE_DISCONNECTED},
+ * {@link android.bluetooth.BluetoothProfile#STATE_DISCONNECTING}.
+ * @param bluetoothProfile the BluetoothProfile id.
+ */
default void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice,
int state, int bluetoothProfile) {
}
+
+ /**
+ * Called when ACL connection state is changed. It listens to
+ * {@link android.bluetooth.BluetoothDevice#ACTION_ACL_CONNECTED} and {@link
+ * android.bluetooth.BluetoothDevice#ACTION_ACL_DISCONNECTED}
+ *
+ * @param cachedDevice Bluetooth device that changed
+ * @param state the Bluetooth device connection state, the possible values are:
+ * {@link android.bluetooth.BluetoothAdapter#STATE_DISCONNECTED},
+ * {@link android.bluetooth.BluetoothAdapter#STATE_CONNECTED}
+ */
+ default void onAclConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 9d8336f14f85..2b7babd06b47 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -18,7 +18,6 @@ package com.android.settingslib.bluetooth;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothHearingAid;
@@ -27,9 +26,13 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.os.UserHandle;
import android.telephony.TelephonyManager;
import android.util.Log;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
import com.android.settingslib.R;
import java.util.ArrayList;
@@ -49,43 +52,39 @@ public class BluetoothEventManager {
private final LocalBluetoothAdapter mLocalAdapter;
private final CachedBluetoothDeviceManager mDeviceManager;
- private LocalBluetoothProfileManager mProfileManager;
private final IntentFilter mAdapterIntentFilter, mProfileIntentFilter;
private final Map<String, Handler> mHandlerMap;
- private Context mContext;
-
- private final Collection<BluetoothCallback> mCallbacks =
- new ArrayList<BluetoothCallback>();
-
- private android.os.Handler mReceiverHandler;
+ private final BroadcastReceiver mBroadcastReceiver = new BluetoothBroadcastReceiver();
+ private final BroadcastReceiver mProfileBroadcastReceiver = new BluetoothBroadcastReceiver();
+ private final Collection<BluetoothCallback> mCallbacks = new ArrayList<>();
+ private final android.os.Handler mReceiverHandler;
+ private final UserHandle mUserHandle;
+ private final Context mContext;
interface Handler {
void onReceive(Context context, Intent intent, BluetoothDevice device);
}
- private void addHandler(String action, Handler handler) {
- mHandlerMap.put(action, handler);
- mAdapterIntentFilter.addAction(action);
- }
-
- void addProfileHandler(String action, Handler handler) {
- mHandlerMap.put(action, handler);
- mProfileIntentFilter.addAction(action);
- }
-
- // Set profile manager after construction due to circular dependency
- void setProfileManager(LocalBluetoothProfileManager manager) {
- mProfileManager = manager;
- }
-
+ /**
+ * Creates BluetoothEventManager with the ability to pass in {@link UserHandle} that tells it to
+ * listen for bluetooth events for that particular userHandle.
+ *
+ * <p> If passing in userHandle that's different from the user running the process,
+ * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission is required. If
+ * userHandle passed in is {@code null}, we register event receiver for the
+ * {@code context.getUser()} handle.
+ */
BluetoothEventManager(LocalBluetoothAdapter adapter,
- CachedBluetoothDeviceManager deviceManager, Context context) {
+ CachedBluetoothDeviceManager deviceManager, Context context,
+ android.os.Handler handler, @Nullable UserHandle userHandle) {
mLocalAdapter = adapter;
mDeviceManager = deviceManager;
mAdapterIntentFilter = new IntentFilter();
mProfileIntentFilter = new IntentFilter();
- mHandlerMap = new HashMap<String, Handler>();
+ mHandlerMap = new HashMap<>();
mContext = context;
+ mUserHandle = userHandle;
+ mReceiverHandler = handler;
// Bluetooth on/off broadcasts
addHandler(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedHandler());
@@ -108,16 +107,11 @@ public class BluetoothEventManager {
addHandler(BluetoothDevice.ACTION_UUID, new UuidChangedHandler());
addHandler(BluetoothDevice.ACTION_BATTERY_LEVEL_CHANGED, new BatteryLevelChangedHandler());
- // Dock event broadcasts
- addHandler(Intent.ACTION_DOCK_EVENT, new DockEventHandler());
-
// Active device broadcasts
- addHandler(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED,
- new ActiveDeviceChangedHandler());
- addHandler(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED,
- new ActiveDeviceChangedHandler());
+ addHandler(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED, new ActiveDeviceChangedHandler());
+ addHandler(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED, new ActiveDeviceChangedHandler());
addHandler(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED,
- new ActiveDeviceChangedHandler());
+ new ActiveDeviceChangedHandler());
// Headset state changed broadcasts
addHandler(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED,
@@ -125,20 +119,11 @@ public class BluetoothEventManager {
addHandler(TelephonyManager.ACTION_PHONE_STATE_CHANGED,
new AudioModeChangedHandler());
- mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter, null, mReceiverHandler);
- mContext.registerReceiver(mProfileBroadcastReceiver, mProfileIntentFilter, null, mReceiverHandler);
- }
+ // ACL connection changed broadcasts
+ addHandler(BluetoothDevice.ACTION_ACL_CONNECTED, new AclStateChangedHandler());
+ addHandler(BluetoothDevice.ACTION_ACL_DISCONNECTED, new AclStateChangedHandler());
- void registerProfileIntentReceiver() {
- mContext.registerReceiver(mProfileBroadcastReceiver, mProfileIntentFilter, null, mReceiverHandler);
- }
-
- public void setReceiverHandler(android.os.Handler handler) {
- mContext.unregisterReceiver(mBroadcastReceiver);
- mContext.unregisterReceiver(mProfileBroadcastReceiver);
- mReceiverHandler = handler;
- mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter, null, mReceiverHandler);
- registerProfileIntentReceiver();
+ registerAdapterIntentReceiver();
}
/** Register to start receiving callbacks for Bluetooth events. */
@@ -155,21 +140,122 @@ public class BluetoothEventManager {
}
}
- private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- BluetoothDevice device = intent
- .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ @VisibleForTesting
+ void registerProfileIntentReceiver() {
+ registerIntentReceiver(mProfileBroadcastReceiver, mProfileIntentFilter);
+ }
- Handler handler = mHandlerMap.get(action);
- if (handler != null) {
- handler.onReceive(context, intent, device);
+ @VisibleForTesting
+ void registerAdapterIntentReceiver() {
+ registerIntentReceiver(mBroadcastReceiver, mAdapterIntentFilter);
+ }
+
+ /**
+ * Registers the provided receiver to receive the broadcasts that correspond to the
+ * passed intent filter, in the context of the provided handler.
+ */
+ private void registerIntentReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+ if (mUserHandle == null) {
+ // If userHandle has not been provided, simply call registerReceiver.
+ mContext.registerReceiver(receiver, filter, null, mReceiverHandler);
+ } else {
+ // userHandle was explicitly specified, so need to call multi-user aware API.
+ mContext.registerReceiverAsUser(receiver, mUserHandle, filter, null, mReceiverHandler);
+ }
+ }
+
+ @VisibleForTesting
+ void addProfileHandler(String action, Handler handler) {
+ mHandlerMap.put(action, handler);
+ mProfileIntentFilter.addAction(action);
+ }
+
+ boolean readPairedDevices() {
+ Set<BluetoothDevice> bondedDevices = mLocalAdapter.getBondedDevices();
+ if (bondedDevices == null) {
+ return false;
+ }
+
+ boolean deviceAdded = false;
+ for (BluetoothDevice device : bondedDevices) {
+ CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
+ if (cachedDevice == null) {
+ mDeviceManager.addDevice(device);
+ deviceAdded = true;
+ }
+ }
+
+ return deviceAdded;
+ }
+
+ void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) {
+ synchronized (mCallbacks) {
+ for (BluetoothCallback callback : mCallbacks) {
+ callback.onDeviceAdded(cachedDevice);
+ }
+ }
+ }
+
+ void dispatchDeviceRemoved(CachedBluetoothDevice cachedDevice) {
+ synchronized (mCallbacks) {
+ for (BluetoothCallback callback : mCallbacks) {
+ callback.onDeviceDeleted(cachedDevice);
+ }
+ }
+ }
+
+ void dispatchProfileConnectionStateChanged(CachedBluetoothDevice device, int state,
+ int bluetoothProfile) {
+ synchronized (mCallbacks) {
+ for (BluetoothCallback callback : mCallbacks) {
+ callback.onProfileConnectionStateChanged(device, state, bluetoothProfile);
+ }
+ }
+ }
+
+ private void dispatchConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
+ synchronized (mCallbacks) {
+ for (BluetoothCallback callback : mCallbacks) {
+ callback.onConnectionStateChanged(cachedDevice, state);
+ }
+ }
+ }
+
+ private void dispatchAudioModeChanged() {
+ mDeviceManager.dispatchAudioModeChanged();
+ synchronized (mCallbacks) {
+ for (BluetoothCallback callback : mCallbacks) {
+ callback.onAudioModeChanged();
+ }
+ }
+ }
+
+ private void dispatchActiveDeviceChanged(CachedBluetoothDevice activeDevice,
+ int bluetoothProfile) {
+ mDeviceManager.onActiveDeviceChanged(activeDevice, bluetoothProfile);
+ synchronized (mCallbacks) {
+ for (BluetoothCallback callback : mCallbacks) {
+ callback.onActiveDeviceChanged(activeDevice, bluetoothProfile);
}
}
- };
+ }
+
+ private void dispatchAclStateChanged(CachedBluetoothDevice activeDevice,
+ int state) {
+ synchronized (mCallbacks) {
+ for (BluetoothCallback callback : mCallbacks) {
+ callback.onAclConnectionStateChanged(activeDevice, state);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void addHandler(String action, Handler handler) {
+ mHandlerMap.put(action, handler);
+ mAdapterIntentFilter.addAction(action);
+ }
- private final BroadcastReceiver mProfileBroadcastReceiver = new BroadcastReceiver() {
+ private class BluetoothBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
@@ -181,19 +267,13 @@ public class BluetoothEventManager {
handler.onReceive(context, intent, device);
}
}
- };
+ }
private class AdapterStateChangedHandler implements Handler {
public void onReceive(Context context, Intent intent,
BluetoothDevice device) {
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
- BluetoothAdapter.ERROR);
- // Reregister Profile Broadcast Receiver as part of TURN OFF
- if (state == BluetoothAdapter.STATE_OFF)
- {
- context.unregisterReceiver(mProfileBroadcastReceiver);
- registerProfileIntentReceiver();
- }
+ BluetoothAdapter.ERROR);
// update local profiles and get paired devices
mLocalAdapter.setBluetoothStateInt(state);
// send callback to update UI and possibly start scanning
@@ -228,19 +308,21 @@ public class BluetoothEventManager {
public void onReceive(Context context, Intent intent,
BluetoothDevice device) {
short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE);
- BluetoothClass btClass = intent.getParcelableExtra(BluetoothDevice.EXTRA_CLASS);
String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
// TODO Pick up UUID. They should be available for 2.1 devices.
// Skip for now, there's a bluez problem and we are not getting uuids even for 2.1.
CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
if (cachedDevice == null) {
- cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
+ cachedDevice = mDeviceManager.addDevice(device);
Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice: "
+ cachedDevice);
+ } else if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED
+ &&!cachedDevice.getDevice().isConnected()) {
+ // Dispatch device add callback to show bonded but
+ // not connected devices in discovery mode
+ dispatchDeviceAdded(cachedDevice);
}
cachedDevice.setRssi(rssi);
- cachedDevice.setBtClass(btClass);
- cachedDevice.setNewName(name);
cachedDevice.setJustDiscovered(true);
}
}
@@ -255,30 +337,6 @@ public class BluetoothEventManager {
}
}
- private void dispatchConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
- synchronized (mCallbacks) {
- for (BluetoothCallback callback : mCallbacks) {
- callback.onConnectionStateChanged(cachedDevice, state);
- }
- }
- }
-
- void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) {
- synchronized (mCallbacks) {
- for (BluetoothCallback callback : mCallbacks) {
- callback.onDeviceAdded(cachedDevice);
- }
- }
- }
-
- void dispatchDeviceRemoved(CachedBluetoothDevice cachedDevice) {
- synchronized (mCallbacks) {
- for (BluetoothCallback callback : mCallbacks) {
- callback.onDeviceDeleted(cachedDevice);
- }
- }
- }
-
private class NameChangedHandler implements Handler {
public void onReceive(Context context, Intent intent,
BluetoothDevice device) {
@@ -294,22 +352,12 @@ public class BluetoothEventManager {
return;
}
int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
- BluetoothDevice.ERROR);
+ BluetoothDevice.ERROR);
CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
if (cachedDevice == null) {
- Log.w(TAG, "CachedBluetoothDevice for device " + device +
- " not found, calling readPairedDevices().");
- if (readPairedDevices()) {
- cachedDevice = mDeviceManager.findDevice(device);
- }
-
- if (cachedDevice == null) {
- Log.w(TAG, "Got bonding state changed for " + device +
- ", but we have no record of that device.");
-
- cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
- dispatchDeviceAdded(cachedDevice);
- }
+ Log.w(TAG, "Got bonding state changed for " + device +
+ ", but we have no record of that device.");
+ cachedDevice = mDeviceManager.addDevice(device);
}
synchronized (mCallbacks) {
@@ -341,26 +389,26 @@ public class BluetoothEventManager {
int errorMsg;
switch(reason) {
- case BluetoothDevice.UNBOND_REASON_AUTH_FAILED:
- errorMsg = R.string.bluetooth_pairing_pin_error_message;
- break;
- case BluetoothDevice.UNBOND_REASON_AUTH_REJECTED:
- errorMsg = R.string.bluetooth_pairing_rejected_error_message;
- break;
- case BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN:
- errorMsg = R.string.bluetooth_pairing_device_down_error_message;
- break;
- case BluetoothDevice.UNBOND_REASON_DISCOVERY_IN_PROGRESS:
- case BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT:
- case BluetoothDevice.UNBOND_REASON_REPEATED_ATTEMPTS:
- case BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED:
- errorMsg = R.string.bluetooth_pairing_error_message;
- break;
- default:
- Log.w(TAG, "showUnbondMessage: Not displaying any message for reason: " + reason);
- return;
+ case BluetoothDevice.UNBOND_REASON_AUTH_FAILED:
+ errorMsg = R.string.bluetooth_pairing_pin_error_message;
+ break;
+ case BluetoothDevice.UNBOND_REASON_AUTH_REJECTED:
+ errorMsg = R.string.bluetooth_pairing_rejected_error_message;
+ break;
+ case BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN:
+ errorMsg = R.string.bluetooth_pairing_device_down_error_message;
+ break;
+ case BluetoothDevice.UNBOND_REASON_DISCOVERY_IN_PROGRESS:
+ case BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT:
+ case BluetoothDevice.UNBOND_REASON_REPEATED_ATTEMPTS:
+ case BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED:
+ errorMsg = R.string.bluetooth_pairing_error_message;
+ break;
+ default:
+ Log.w(TAG, "showUnbondMessage: Not displaying any message for reason: " + reason);
+ return;
}
- Utils.showError(context, name, errorMsg);
+ BluetoothUtils.showError(context, name, errorMsg);
}
}
@@ -378,22 +426,6 @@ public class BluetoothEventManager {
}
}
- private class DockEventHandler implements Handler {
- public void onReceive(Context context, Intent intent, BluetoothDevice device) {
- // Remove if unpair device upon undocking
- int anythingButUnDocked = Intent.EXTRA_DOCK_STATE_UNDOCKED + 1;
- int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, anythingButUnDocked);
- if (state == Intent.EXTRA_DOCK_STATE_UNDOCKED) {
- if (device != null && device.getBondState() == BluetoothDevice.BOND_NONE) {
- CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
- if (cachedDevice != null) {
- cachedDevice.setJustDiscovered(false);
- }
- }
- }
- }
- }
-
private class BatteryLevelChangedHandler implements Handler {
public void onReceive(Context context, Intent intent,
BluetoothDevice device) {
@@ -404,25 +436,6 @@ public class BluetoothEventManager {
}
}
- boolean readPairedDevices() {
- Set<BluetoothDevice> bondedDevices = mLocalAdapter.getBondedDevices();
- if (bondedDevices == null) {
- return false;
- }
-
- boolean deviceAdded = false;
- for (BluetoothDevice device : bondedDevices) {
- CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
- if (cachedDevice == null) {
- cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
- dispatchDeviceAdded(cachedDevice);
- deviceAdded = true;
- }
- }
-
- return deviceAdded;
- }
-
private class ActiveDeviceChangedHandler implements Handler {
@Override
public void onReceive(Context context, Intent intent, BluetoothDevice device) {
@@ -447,13 +460,29 @@ public class BluetoothEventManager {
}
}
- private void dispatchActiveDeviceChanged(CachedBluetoothDevice activeDevice,
- int bluetoothProfile) {
- mDeviceManager.onActiveDeviceChanged(activeDevice, bluetoothProfile);
- synchronized (mCallbacks) {
- for (BluetoothCallback callback : mCallbacks) {
- callback.onActiveDeviceChanged(activeDevice, bluetoothProfile);
+ private class AclStateChangedHandler implements Handler {
+ @Override
+ public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+ final String action = intent.getAction();
+ if (action == null) {
+ Log.w(TAG, "AclStateChangedHandler: action is null");
+ return;
+ }
+ final CachedBluetoothDevice activeDevice = mDeviceManager.findDevice(device);
+ final int state;
+ switch (action) {
+ case BluetoothDevice.ACTION_ACL_CONNECTED:
+ state = BluetoothAdapter.STATE_CONNECTED;
+ break;
+ case BluetoothDevice.ACTION_ACL_DISCONNECTED:
+ state = BluetoothAdapter.STATE_DISCONNECTED;
+ break;
+ default:
+ Log.w(TAG, "ActiveDeviceChangedHandler: unknown action " + action);
+ return;
+
}
+ dispatchAclStateChanged(activeDevice, state);
}
}
@@ -469,23 +498,4 @@ public class BluetoothEventManager {
dispatchAudioModeChanged();
}
}
-
- private void dispatchAudioModeChanged() {
- mDeviceManager.dispatchAudioModeChanged();
- synchronized (mCallbacks) {
- for (BluetoothCallback callback : mCallbacks) {
- callback.onAudioModeChanged();
- }
- }
- }
-
- void dispatchProfileConnectionStateChanged(CachedBluetoothDevice device, int state,
- int bluetoothProfile) {
- synchronized (mCallbacks) {
- for (BluetoothCallback callback : mCallbacks) {
- callback.onProfileConnectionStateChanged(device, state, bluetoothProfile);
- }
- }
- mDeviceManager.onProfileConnectionStateChanged(device, state, bluetoothProfile);
- }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/Utils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 26f3ab69ba37..ee80aa159ab1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -5,15 +5,16 @@ import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.graphics.drawable.Drawable;
-import androidx.annotation.DrawableRes;
import android.util.Pair;
+import androidx.annotation.DrawableRes;
+
import com.android.settingslib.R;
import com.android.settingslib.graph.BluetoothDeviceLayerDrawable;
import java.util.List;
-public class Utils {
+public class BluetoothUtils {
public static final boolean V = false; // verbose logging
public static final boolean D = true; // regular logging
@@ -21,16 +22,16 @@ public class Utils {
public static int getConnectionStateSummary(int connectionState) {
switch (connectionState) {
- case BluetoothProfile.STATE_CONNECTED:
- return R.string.bluetooth_connected;
- case BluetoothProfile.STATE_CONNECTING:
- return R.string.bluetooth_connecting;
- case BluetoothProfile.STATE_DISCONNECTED:
- return R.string.bluetooth_disconnected;
- case BluetoothProfile.STATE_DISCONNECTING:
- return R.string.bluetooth_disconnecting;
- default:
- return 0;
+ case BluetoothProfile.STATE_CONNECTED:
+ return R.string.bluetooth_connected;
+ case BluetoothProfile.STATE_CONNECTING:
+ return R.string.bluetooth_connecting;
+ case BluetoothProfile.STATE_DISCONNECTED:
+ return R.string.bluetooth_disconnected;
+ case BluetoothProfile.STATE_DISCONNECTING:
+ return R.string.bluetooth_disconnecting;
+ default:
+ return 0;
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 2aabb8a533e6..d6c64913f048 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -16,6 +16,7 @@
package com.android.settingslib.bluetooth;
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHearingAid;
@@ -23,12 +24,11 @@ import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
import android.content.Context;
import android.content.SharedPreferences;
-import android.media.AudioManager;
import android.os.ParcelUuid;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Log;
-import android.bluetooth.BluetoothAdapter;
+
import androidx.annotation.VisibleForTesting;
import com.android.settingslib.R;
@@ -36,7 +36,6 @@ import com.android.settingslib.R;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashMap;
import java.util.List;
/**
@@ -47,69 +46,42 @@ import java.util.List;
*/
public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
private static final String TAG = "CachedBluetoothDevice";
- private static final boolean DEBUG = Utils.V;
+
+ // See mConnectAttempted
+ private static final long MAX_UUID_DELAY_FOR_AUTO_CONNECT = 5000;
+ private static final long MAX_HOGP_DELAY_FOR_AUTO_CONNECT = 30000;
private final Context mContext;
- private final LocalBluetoothAdapter mLocalAdapter;
+ private final BluetoothAdapter mLocalAdapter;
private final LocalBluetoothProfileManager mProfileManager;
- private final AudioManager mAudioManager;
- private final BluetoothDevice mDevice;
- //TODO: consider remove, BluetoothDevice.getName() is already cached
- private String mName;
+ BluetoothDevice mDevice;
private long mHiSyncId;
// Need this since there is no method for getting RSSI
- private short mRssi;
- //TODO: consider remove, BluetoothDevice.getBluetoothClass() is already cached
- private BluetoothClass mBtClass;
- private HashMap<LocalBluetoothProfile, Integer> mProfileConnectionState;
-
+ short mRssi;
+ // mProfiles and mRemovedProfiles does not do swap() between main and sub device. It is
+ // because current sub device is only for HearingAid and its profile is the same.
private final List<LocalBluetoothProfile> mProfiles =
- new ArrayList<LocalBluetoothProfile>();
+ Collections.synchronizedList(new ArrayList<>());
// List of profiles that were previously in mProfiles, but have been removed
private final List<LocalBluetoothProfile> mRemovedProfiles =
- new ArrayList<LocalBluetoothProfile>();
+ Collections.synchronizedList(new ArrayList<>());
// Device supports PANU but not NAP: remove PanProfile after device disconnects from NAP
private boolean mLocalNapRoleConnected;
- private boolean mJustDiscovered;
+ boolean mJustDiscovered;
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
- public final static int ACCESS_UNKNOWN = 0;
- // User has accepted the connection and let Settings app remember the decision
- public final static int ACCESS_ALLOWED = 1;
- // User has rejected the connection and let Settings app remember the decision
- public final static int ACCESS_REJECTED = 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 MESSAGE_REJECTION_COUNT_PREFS_NAME = "bluetooth_message_reject";
/**
- * When we connect to multiple profiles, we only want to display a single
- * error even if they all fail. This tracks that state.
- */
- private boolean mIsConnectingErrorPossible;
-
- public long getHiSyncId() {
- return mHiSyncId;
- }
-
- public void setHiSyncId(long id) {
- if (Utils.D) {
- Log.d(TAG, "setHiSyncId: mDevice " + mDevice + ", id " + id);
- }
- mHiSyncId = id;
- }
-
- /**
* Last time a bt profile auto-connect was attempted.
* If an ACTION_UUID intent comes in within
* MAX_UUID_DELAY_FOR_AUTO_CONNECT milliseconds, we will try auto-connect
@@ -117,14 +89,23 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
*/
private long mConnectAttempted;
- // See mConnectAttempted
- private static final long MAX_UUID_DELAY_FOR_AUTO_CONNECT = 5000;
- private static final long MAX_HOGP_DELAY_FOR_AUTO_CONNECT = 30000;
-
// Active device state
private boolean mIsActiveDeviceA2dp = false;
private boolean mIsActiveDeviceHeadset = false;
private boolean mIsActiveDeviceHearingAid = false;
+ // Group second device for Hearing Aid
+ private CachedBluetoothDevice mSubDevice;
+
+ CachedBluetoothDevice(Context context, LocalBluetoothProfileManager profileManager,
+ BluetoothDevice device) {
+ mContext = context;
+ mLocalAdapter = BluetoothAdapter.getDefaultAdapter();
+ mProfileManager = profileManager;
+ mDevice = device;
+ fillData();
+ mHiSyncId = BluetoothHearingAid.HI_SYNC_ID_INVALID;
+ }
+
/**
* Describes the current device and profile for logging.
*
@@ -142,16 +123,17 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
}
void onProfileStateChanged(LocalBluetoothProfile profile, int newProfileState) {
- if (Utils.D) {
- Log.d(TAG, "onProfileStateChanged: profile " + profile + ", device=" + mDevice
- + ", newProfileState " + newProfileState);
+ if (BluetoothUtils.D) {
+ Log.d(TAG, "onProfileStateChanged: profile " + profile +
+ " newProfileState " + newProfileState);
}
- if (mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_TURNING_OFF)
+ if (mLocalAdapter.getState() == BluetoothAdapter.STATE_TURNING_OFF)
{
- if (Utils.D) Log.d(TAG, " BT Turninig Off...Profile conn state change ignored...");
+ if (BluetoothUtils.D) {
+ Log.d(TAG, " BT Turninig Off...Profile conn state change ignored...");
+ }
return;
}
- mProfileConnectionState.put(profile, newProfileState);
if (newProfileState == BluetoothProfile.STATE_CONNECTED) {
if (profile instanceof MapProfile) {
profile.setPreferred(mDevice, true);
@@ -179,20 +161,6 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
fetchActiveDevices();
}
- CachedBluetoothDevice(Context context,
- LocalBluetoothAdapter adapter,
- LocalBluetoothProfileManager profileManager,
- BluetoothDevice device) {
- mContext = context;
- mLocalAdapter = adapter;
- mProfileManager = profileManager;
- mAudioManager = context.getSystemService(AudioManager.class);
- mDevice = device;
- mProfileConnectionState = new HashMap<LocalBluetoothProfile, Integer>();
- fillData();
- mHiSyncId = BluetoothHearingAid.HI_SYNC_ID_INVALID;
- }
-
public void disconnect() {
for (LocalBluetoothProfile profile : mProfiles) {
disconnect(profile);
@@ -209,7 +177,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
public void disconnect(LocalBluetoothProfile profile) {
if (profile.disconnect(mDevice)) {
- if (Utils.D) {
+ if (BluetoothUtils.D) {
Log.d(TAG, "Command sent successfully:DISCONNECT " + describe(profile));
}
}
@@ -224,8 +192,15 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
connectWithoutResettingTimer(connectAllProfiles);
}
- public boolean isHearingAidDevice() {
- return mHiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID;
+ public long getHiSyncId() {
+ return mHiSyncId;
+ }
+
+ public void setHiSyncId(long id) {
+ if (BluetoothUtils.D) {
+ Log.d(TAG, "setHiSyncId: mDevice " + mDevice + ", id " + id);
+ }
+ mHiSyncId = id;
}
void onBondingDockConnect() {
@@ -248,9 +223,6 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
return;
}
- // Reset the only-show-one-error-dialog tracking variable
- mIsConnectingErrorPossible = true;
-
int preferredProfiles = 0;
for (LocalBluetoothProfile profile : mProfiles) {
if (connectAllProfiles ? profile.accessProfileEnabled() : profile.isAutoConnectable()) {
@@ -260,7 +232,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
}
}
}
- if (DEBUG) Log.d(TAG, "Preferred profiles = " + preferredProfiles);
+ if (BluetoothUtils.D) Log.d(TAG, "Preferred profiles = " + preferredProfiles);
if (preferredProfiles == 0) {
connectAutoConnectableProfiles();
@@ -271,8 +243,6 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
if (!ensurePaired()) {
return;
}
- // Reset the only-show-one-error-dialog tracking variable
- mIsConnectingErrorPossible = true;
for (LocalBluetoothProfile profile : mProfiles) {
if (profile.isAutoConnectable()) {
@@ -289,8 +259,6 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
*/
public void connectProfile(LocalBluetoothProfile profile) {
mConnectAttempted = SystemClock.elapsedRealtime();
- // Reset the only-show-one-error-dialog tracking variable
- mIsConnectingErrorPossible = true;
connectInt(profile);
// Refresh the UI based on profile.connect() call
refresh();
@@ -301,12 +269,12 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
return;
}
if (profile.connect(mDevice)) {
- if (Utils.D) {
+ if (BluetoothUtils.D) {
Log.d(TAG, "Command sent successfully:CONNECT " + describe(profile));
}
return;
}
- Log.i(TAG, "Failed to connect " + profile.toString() + " to " + mName);
+ Log.i(TAG, "Failed to connect " + profile.toString() + " to " + getName());
}
private boolean ensurePaired() {
@@ -331,14 +299,6 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
return true;
}
- /**
- * Return true if user initiated pairing on this device. The message text is
- * slightly different for local vs. remote initiated pairing dialogs.
- */
- boolean isUserInitiatedPairing() {
- return mDevice.isBondingInitiatedLocally();
- }
-
public void unpair() {
int state = getBondState();
@@ -351,10 +311,10 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
if (dev != null) {
final boolean successful = dev.removeBond();
if (successful) {
- if (Utils.D) {
+ if (BluetoothUtils.D) {
Log.d(TAG, "Command sent successfully:REMOVE_BOND " + describe(null));
}
- } else if (Utils.V) {
+ } else if (BluetoothUtils.V) {
Log.v(TAG, "Framework rejected command immediately:REMOVE_BOND " +
describe(null));
}
@@ -363,28 +323,13 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
}
public int getProfileConnectionState(LocalBluetoothProfile profile) {
- if (mProfileConnectionState.get(profile) == null) {
- // If cache is empty make the binder call to get the state
- int state = profile.getConnectionStatus(mDevice);
- mProfileConnectionState.put(profile, state);
- }
- return mProfileConnectionState.get(profile);
- }
-
- public void clearProfileConnectionState ()
- {
- if (Utils.D) {
- Log.d(TAG," Clearing all connection state for dev:" + mDevice.getName());
- }
- for (LocalBluetoothProfile profile :getProfiles()) {
- mProfileConnectionState.put(profile, BluetoothProfile.STATE_DISCONNECTED);
- }
+ return profile != null
+ ? profile.getConnectionStatus(mDevice)
+ : BluetoothProfile.STATE_DISCONNECTED;
}
// TODO: do any of these need to run async on a background thread?
private void fillData() {
- fetchName();
- fetchBtClass();
updateProfiles();
fetchActiveDevices();
migratePhonebookPermissionChoice();
@@ -407,21 +352,15 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
return mDevice.getAddress();
}
- public String getName() {
- return mName;
- }
-
/**
- * Populate name from BluetoothDevice.ACTION_FOUND intent
+ * Get name from remote device
+ * @return {@link BluetoothDevice#getAliasName()} if
+ * {@link BluetoothDevice#getAliasName()} is not null otherwise return
+ * {@link BluetoothDevice#getAddress()}
*/
- void setNewName(String name) {
- if (mName == null) {
- mName = name;
- if (mName == null || TextUtils.isEmpty(mName)) {
- mName = mDevice.getAddress();
- }
- dispatchAttributesChanged();
- }
+ public String getName() {
+ final String aliasName = mDevice.getAliasName();
+ return TextUtils.isEmpty(aliasName) ? getAddress() : aliasName;
}
/**
@@ -429,9 +368,8 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
* @param name new alias name to be set, should never be null
*/
public void setName(String name) {
- // Prevent mName to be set to null if setName(null) is called
- if (name != null && !TextUtils.equals(name, mName)) {
- mName = name;
+ // Prevent getName() to be set to null if setName(null) is called
+ if (name != null && !TextUtils.equals(name, getName())) {
mDevice.setAlias(name);
dispatchAttributesChanged();
}
@@ -468,17 +406,10 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
}
void refreshName() {
- fetchName();
- dispatchAttributesChanged();
- }
-
- private void fetchName() {
- mName = mDevice.getAliasName();
-
- if (TextUtils.isEmpty(mName)) {
- mName = mDevice.getAddress();
- if (DEBUG) Log.d(TAG, "Device has no name (yet), use address: " + mName);
+ if (BluetoothUtils.D) {
+ Log.d(TAG, "Device name: " + getName());
}
+ dispatchAttributesChanged();
}
/**
@@ -611,13 +542,6 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
return getBondState() == BluetoothDevice.BOND_BONDING;
}
- /**
- * Fetches a new value for the cached BT class.
- */
- private void fetchBtClass() {
- mBtClass = mDevice.getBluetoothClass();
- }
-
private boolean updateProfiles() {
ParcelUuid[] uuids = mDevice.getUuids();
if (uuids == null) return false;
@@ -633,7 +557,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
mProfileManager.updateProfiles(uuids, localUuids, mProfiles, mRemovedProfiles,
mLocalNapRoleConnected, mDevice);
- if (DEBUG) {
+ if (BluetoothUtils.D) {
Log.e(TAG, "updating profiles for " + mDevice.getAliasName());
BluetoothClass bluetoothClass = mDevice.getBluetoothClass();
@@ -662,15 +586,6 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
}
/**
- * Refreshes the UI for the BT class, including fetching the latest value
- * for the class.
- */
- void refreshBtClass() {
- fetchBtClass();
- dispatchAttributesChanged();
- }
-
- /**
* Refreshes the UI when framework alerts us of a UUID change.
*/
void onUuidChanged() {
@@ -682,7 +597,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
timeout = MAX_HOGP_DELAY_FOR_AUTO_CONNECT;
}
- if (DEBUG) {
+ if (BluetoothUtils.D) {
Log.d(TAG, "onUuidChanged: Time since last connect"
+ (SystemClock.elapsedRealtime() - mConnectAttempted));
}
@@ -702,9 +617,9 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
void onBondingStateChanged(int bondState) {
if (bondState == BluetoothDevice.BOND_NONE) {
mProfiles.clear();
- setPhonebookPermissionChoice(ACCESS_UNKNOWN);
- setMessagePermissionChoice(ACCESS_UNKNOWN);
- setSimPermissionChoice(ACCESS_UNKNOWN);
+ mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_UNKNOWN);
+ mDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_UNKNOWN);
+ mDevice.setSimAccessPermission(BluetoothDevice.ACCESS_UNKNOWN);
mMessageRejectionCount = 0;
saveMessageRejectionCount();
}
@@ -720,15 +635,8 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
}
}
- void setBtClass(BluetoothClass btClass) {
- if (btClass != null && mBtClass != btClass) {
- mBtClass = btClass;
- dispatchAttributesChanged();
- }
- }
-
public BluetoothClass getBtClass() {
- return mBtClass;
+ return mDevice.getBluetoothClass();
}
public List<LocalBluetoothProfile> getProfiles() {
@@ -762,7 +670,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
}
}
- private void dispatchAttributesChanged() {
+ void dispatchAttributesChanged() {
synchronized (mCallbacks) {
for (Callback callback : mCallbacks) {
callback.onDeviceAttributesChanged();
@@ -810,33 +718,13 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
if (comparison != 0) return comparison;
// Fallback on name
- return mName.compareTo(another.mName);
+ return getName().compareTo(another.getName());
}
public interface Callback {
void onDeviceAttributesChanged();
}
- public int getPhonebookPermissionChoice() {
- int permission = mDevice.getPhonebookAccessPermission();
- if (permission == BluetoothDevice.ACCESS_ALLOWED) {
- return ACCESS_ALLOWED;
- } else if (permission == BluetoothDevice.ACCESS_REJECTED) {
- return ACCESS_REJECTED;
- }
- return ACCESS_UNKNOWN;
- }
-
- public void setPhonebookPermissionChoice(int permissionChoice) {
- int permission = BluetoothDevice.ACCESS_UNKNOWN;
- if (permissionChoice == ACCESS_ALLOWED) {
- permission = BluetoothDevice.ACCESS_ALLOWED;
- } else if (permissionChoice == ACCESS_REJECTED) {
- permission = BluetoothDevice.ACCESS_REJECTED;
- }
- mDevice.setPhonebookAccessPermission(permission);
- }
-
// Migrates data from old data store (in Settings app's shared preferences) to new (in Bluetooth
// app's shared preferences).
private void migratePhonebookPermissionChoice() {
@@ -847,10 +735,11 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
}
if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_UNKNOWN) {
- int oldPermission = preferences.getInt(mDevice.getAddress(), ACCESS_UNKNOWN);
- if (oldPermission == ACCESS_ALLOWED) {
+ int oldPermission =
+ preferences.getInt(mDevice.getAddress(), BluetoothDevice.ACCESS_UNKNOWN);
+ if (oldPermission == BluetoothDevice.ACCESS_ALLOWED) {
mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
- } else if (oldPermission == ACCESS_REJECTED) {
+ } else if (oldPermission == BluetoothDevice.ACCESS_REJECTED) {
mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
}
}
@@ -860,46 +749,6 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
editor.commit();
}
- public int getMessagePermissionChoice() {
- int permission = mDevice.getMessageAccessPermission();
- if (permission == BluetoothDevice.ACCESS_ALLOWED) {
- return ACCESS_ALLOWED;
- } else if (permission == BluetoothDevice.ACCESS_REJECTED) {
- return ACCESS_REJECTED;
- }
- return ACCESS_UNKNOWN;
- }
-
- public void setMessagePermissionChoice(int permissionChoice) {
- 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);
- }
-
- public int getSimPermissionChoice() {
- int permission = mDevice.getSimAccessPermission();
- if (permission == BluetoothDevice.ACCESS_ALLOWED) {
- return ACCESS_ALLOWED;
- } else if (permission == BluetoothDevice.ACCESS_REJECTED) {
- return ACCESS_REJECTED;
- }
- return ACCESS_UNKNOWN;
- }
-
- void setSimPermissionChoice(int permissionChoice) {
- int permission = BluetoothDevice.ACCESS_UNKNOWN;
- if (permissionChoice == ACCESS_ALLOWED) {
- permission = BluetoothDevice.ACCESS_ALLOWED;
- } else if (permissionChoice == ACCESS_REJECTED) {
- permission = BluetoothDevice.ACCESS_REJECTED;
- }
- mDevice.setSimAccessPermission(permission);
- }
-
// Migrates data from old data store (in Settings app's shared preferences) to new (in Bluetooth
// app's shared preferences).
private void migrateMessagePermissionChoice() {
@@ -910,10 +759,11 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
}
if (mDevice.getMessageAccessPermission() == BluetoothDevice.ACCESS_UNKNOWN) {
- int oldPermission = preferences.getInt(mDevice.getAddress(), ACCESS_UNKNOWN);
- if (oldPermission == ACCESS_ALLOWED) {
+ int oldPermission =
+ preferences.getInt(mDevice.getAddress(), BluetoothDevice.ACCESS_UNKNOWN);
+ if (oldPermission == BluetoothDevice.ACCESS_ALLOWED) {
mDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
- } else if (oldPermission == ACCESS_REJECTED) {
+ } else if (oldPermission == BluetoothDevice.ACCESS_REJECTED) {
mDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_REJECTED);
}
}
@@ -958,14 +808,14 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
if (BluetoothUuid.containsAnyUuid(uuids, PbapServerProfile.PBAB_CLIENT_UUIDS)) {
// The pairing dialog now warns of phone-book access for paired devices.
// No separate prompt is displayed after pairing.
- if (getPhonebookPermissionChoice() == CachedBluetoothDevice.ACCESS_UNKNOWN) {
+ if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_UNKNOWN) {
if (mDevice.getBluetoothClass().getDeviceClass()
== BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE ||
mDevice.getBluetoothClass().getDeviceClass()
== BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET) {
- setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_ALLOWED);
+ mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
} else {
- setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_REJECTED);
+ mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
}
}
}
@@ -999,7 +849,8 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
switch (connectionStatus) {
case BluetoothProfile.STATE_CONNECTING:
case BluetoothProfile.STATE_DISCONNECTING:
- return mContext.getString(Utils.getConnectionStateSummary(connectionStatus));
+ return mContext.getString(
+ BluetoothUtils.getConnectionStateSummary(connectionStatus));
case BluetoothProfile.STATE_CONNECTED:
profileConnected = true;
@@ -1041,7 +892,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
if (batteryLevelPercentageString != null) {
//device is in phone call
if (com.android.settingslib.Utils.isAudioModeOngoingCall(mContext)) {
- if (mIsActiveDeviceHeadset) {
+ if (mIsActiveDeviceHearingAid || mIsActiveDeviceHeadset) {
stringRes = R.string.bluetooth_active_battery_level;
} else {
stringRes = R.string.bluetooth_battery_level;
@@ -1057,7 +908,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
} else {
//no battery information
if (com.android.settingslib.Utils.isAudioModeOngoingCall(mContext)) {
- if (mIsActiveDeviceHeadset) {
+ if (mIsActiveDeviceHearingAid || mIsActiveDeviceHeadset) {
stringRes = R.string.bluetooth_active_no_battery_level;
}
} else {
@@ -1094,7 +945,8 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
switch (connectionStatus) {
case BluetoothProfile.STATE_CONNECTING:
case BluetoothProfile.STATE_DISCONNECTING:
- return mContext.getString(Utils.getConnectionStateSummary(connectionStatus));
+ return mContext.getString(
+ BluetoothUtils.getConnectionStateSummary(connectionStatus));
case BluetoothProfile.STATE_CONNECTED:
profileConnected = true;
@@ -1192,7 +1044,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
/**
* @return {@code true} if {@code cachedBluetoothDevice} is a2dp device
*/
- public boolean isA2dpDevice() {
+ public boolean isConnectedA2dpDevice() {
A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile();
return a2dpProfile != null && a2dpProfile.getConnectionStatus(mDevice) ==
BluetoothProfile.STATE_CONNECTED;
@@ -1201,7 +1053,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
/**
* @return {@code true} if {@code cachedBluetoothDevice} is HFP device
*/
- public boolean isHfpDevice() {
+ public boolean isConnectedHfpDevice() {
HeadsetProfile headsetProfile = mProfileManager.getHeadsetProfile();
return headsetProfile != null && headsetProfile.getConnectionStatus(mDevice) ==
BluetoothProfile.STATE_CONNECTED;
@@ -1215,4 +1067,28 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
return hearingAidProfile != null && hearingAidProfile.getConnectionStatus(mDevice) ==
BluetoothProfile.STATE_CONNECTED;
}
+
+ public CachedBluetoothDevice getSubDevice() {
+ return mSubDevice;
+ }
+
+ public void setSubDevice(CachedBluetoothDevice subDevice) {
+ mSubDevice = subDevice;
+ }
+
+ public void switchSubDeviceContent() {
+ // Backup from main device
+ BluetoothDevice tmpDevice = mDevice;
+ short tmpRssi = mRssi;
+ boolean tmpJustDiscovered = mJustDiscovered;
+ // Set main device from sub device
+ mDevice = mSubDevice.mDevice;
+ mRssi = mSubDevice.mRssi;
+ mJustDiscovered = mSubDevice.mJustDiscovered;
+ // Set sub device from backup
+ mSubDevice.mDevice = tmpDevice;
+ mSubDevice.mRssi = tmpRssi;
+ mSubDevice.mJustDiscovered = tmpJustDiscovered;
+ fetchActiveDevices();
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 1bfcf9ade8a5..f7f65893efb5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -18,8 +18,6 @@ package com.android.settingslib.bluetooth;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHearingAid;
-import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.util.Log;
@@ -27,11 +25,7 @@ import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
-import java.util.Map;
-import java.util.Set;
import java.util.Objects;
/**
@@ -39,26 +33,20 @@ import java.util.Objects;
*/
public class CachedBluetoothDeviceManager {
private static final String TAG = "CachedBluetoothDeviceManager";
- private static final boolean DEBUG = Utils.D;
+ private static final boolean DEBUG = BluetoothUtils.D;
private Context mContext;
private final LocalBluetoothManager mBtManager;
@VisibleForTesting
- final List<CachedBluetoothDevice> mCachedDevices =
- new ArrayList<CachedBluetoothDevice>();
- // Contains the list of hearing aid devices that should not be shown in the UI.
+ final List<CachedBluetoothDevice> mCachedDevices = new ArrayList<CachedBluetoothDevice>();
@VisibleForTesting
- final List<CachedBluetoothDevice> mHearingAidDevicesNotAddedInCache
- = new ArrayList<CachedBluetoothDevice>();
- // Maintains a list of devices which are added in mCachedDevices and have hiSyncIds.
- @VisibleForTesting
- final Map<Long, CachedBluetoothDevice> mCachedDevicesMapForHearingAids
- = new HashMap<Long, CachedBluetoothDevice>();
+ HearingAidDeviceManager mHearingAidDeviceManager;
CachedBluetoothDeviceManager(Context context, LocalBluetoothManager localBtManager) {
mContext = context;
mBtManager = localBtManager;
+ mHearingAidDeviceManager = new HearingAidDeviceManager(localBtManager, mCachedDevices);
}
public synchronized Collection<CachedBluetoothDevice> getCachedDevicesCopy() {
@@ -92,12 +80,13 @@ public class CachedBluetoothDeviceManager {
if (cachedDevice.getDevice().equals(device)) {
return cachedDevice;
}
- }
- for (CachedBluetoothDevice notCachedDevice : mHearingAidDevicesNotAddedInCache) {
- if (notCachedDevice.getDevice().equals(device)) {
- return notCachedDevice;
+ // Check sub devices if it exists
+ CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
+ if (subDevice != null && subDevice.getDevice().equals(device)) {
+ return subDevice;
}
}
+
return null;
}
@@ -107,29 +96,14 @@ public class CachedBluetoothDeviceManager {
* @param device the address of the new Bluetooth device
* @return the newly created CachedBluetoothDevice object
*/
- public CachedBluetoothDevice addDevice(LocalBluetoothAdapter adapter,
- LocalBluetoothProfileManager profileManager,
- BluetoothDevice device) {
- CachedBluetoothDevice newDevice = new CachedBluetoothDevice(mContext, adapter,
- profileManager, device);
- if (profileManager.getHearingAidProfile() != null
- && profileManager.getHearingAidProfile().getHiSyncId(newDevice.getDevice())
- != BluetoothHearingAid.HI_SYNC_ID_INVALID) {
- newDevice.setHiSyncId(profileManager.getHearingAidProfile()
- .getHiSyncId(newDevice.getDevice()));
- }
- // Just add one of the hearing aids from a pair in the list that is shown in the UI.
- if (isPairAddedInCache(newDevice.getHiSyncId())) {
- synchronized (this) {
- mHearingAidDevicesNotAddedInCache.add(newDevice);
- }
- } else {
- synchronized (this) {
+ public CachedBluetoothDevice addDevice(BluetoothDevice device) {
+ LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
+ CachedBluetoothDevice newDevice = new CachedBluetoothDevice(mContext, profileManager,
+ device);
+ mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(newDevice);
+ synchronized (this) {
+ if (!mHearingAidDeviceManager.setSubDeviceIfNeeded(newDevice)) {
mCachedDevices.add(newDevice);
- if (newDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID
- && !mCachedDevicesMapForHearingAids.containsKey(newDevice.getHiSyncId())) {
- mCachedDevicesMapForHearingAids.put(newDevice.getHiSyncId(), newDevice);
- }
mBtManager.getEventManager().dispatchDeviceAdded(newDevice);
}
}
@@ -138,49 +112,18 @@ public class CachedBluetoothDeviceManager {
}
/**
- * Returns true if the one of the two hearing aid devices is already cached for UI.
- *
- * @param long hiSyncId
- * @return {@code True} if one of the two hearing aid devices is is already cached for UI.
- */
- private synchronized boolean isPairAddedInCache(long hiSyncId) {
- if (hiSyncId == BluetoothHearingAid.HI_SYNC_ID_INVALID) {
- return false;
- }
- if(mCachedDevicesMapForHearingAids.containsKey(hiSyncId)) {
- return true;
- }
- return false;
- }
-
- /**
* Returns device summary of the pair of the hearing aid passed as the parameter.
*
* @param CachedBluetoothDevice device
- * @return Device summary, or if the pair does not exist or if its not a hearing aid,
+ * @return Device summary, or if the pair does not exist or if it is not a hearing aid,
* then {@code null}.
*/
- public synchronized String getHearingAidPairDeviceSummary(CachedBluetoothDevice device) {
- String pairDeviceSummary = null;
- CachedBluetoothDevice otherHearingAidDevice =
- getHearingAidOtherDevice(device, device.getHiSyncId());
- if (otherHearingAidDevice != null) {
- pairDeviceSummary = otherHearingAidDevice.getConnectionSummary();
+ public synchronized String getSubDeviceSummary(CachedBluetoothDevice device) {
+ CachedBluetoothDevice subDevice = device.getSubDevice();
+ if (subDevice != null && subDevice.isConnected()) {
+ return subDevice.getConnectionSummary();
}
- log("getHearingAidPairDeviceSummary: pairDeviceSummary=" + pairDeviceSummary
- + ", otherHearingAidDevice=" + otherHearingAidDevice);
-
- return pairDeviceSummary;
- }
-
- /**
- * Adds the 2nd hearing aid in a pair in a list that maintains the hearing aids that are
- * not dispalyed in the UI.
- *
- * @param CachedBluetoothDevice device
- */
- public synchronized void addDeviceNotaddedInMap(CachedBluetoothDevice device) {
- mHearingAidDevicesNotAddedInCache.add(device);
+ return null;
}
/**
@@ -188,28 +131,8 @@ public class CachedBluetoothDeviceManager {
* Hearing Aid Service is connected and the HiSyncId's are now available.
* @param LocalBluetoothProfileManager profileManager
*/
- public synchronized void updateHearingAidsDevices(LocalBluetoothProfileManager profileManager) {
- HearingAidProfile profileProxy = profileManager.getHearingAidProfile();
- if (profileProxy == null) {
- log("updateHearingAidsDevices: getHearingAidProfile() is null");
- return;
- }
- final Set<Long> syncIdChangedSet = new HashSet<Long>();
- for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
- if (cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID) {
- continue;
- }
-
- long newHiSyncId = profileProxy.getHiSyncId(cachedDevice.getDevice());
-
- if (newHiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID) {
- cachedDevice.setHiSyncId(newHiSyncId);
- syncIdChangedSet.add(newHiSyncId);
- }
- }
- for (Long syncId : syncIdChangedSet) {
- onHiSyncIdChanged(syncId);
- }
+ public synchronized void updateHearingAidsDevices() {
+ mHearingAidDeviceManager.updateHearingAidsDevices();
}
/**
@@ -233,15 +156,21 @@ public class CachedBluetoothDeviceManager {
}
public synchronized void clearNonBondedDevices() {
-
- mCachedDevicesMapForHearingAids.entrySet().removeIf(entries
- -> entries.getValue().getBondState() == BluetoothDevice.BOND_NONE);
-
+ clearNonBondedSubDevices();
mCachedDevices.removeIf(cachedDevice
-> cachedDevice.getBondState() == BluetoothDevice.BOND_NONE);
+ }
- mHearingAidDevicesNotAddedInCache.removeIf(hearingAidDevice
- -> hearingAidDevice.getBondState() == BluetoothDevice.BOND_NONE);
+ private void clearNonBondedSubDevices() {
+ for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
+ CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
+ CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
+ if (subDevice != null
+ && subDevice.getDevice().getBondState() == BluetoothDevice.BOND_NONE) {
+ // Sub device exists and it is not bonded
+ cachedDevice.setSubDevice(null);
+ }
+ }
}
public synchronized void onScanningStateChanged(boolean started) {
@@ -251,17 +180,17 @@ public class CachedBluetoothDeviceManager {
for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
cachedDevice.setJustDiscovered(false);
- }
- for (int i = mHearingAidDevicesNotAddedInCache.size() - 1; i >= 0; i--) {
- CachedBluetoothDevice notCachedDevice = mHearingAidDevicesNotAddedInCache.get(i);
- notCachedDevice.setJustDiscovered(false);
+ final CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
+ if (subDevice != null) {
+ subDevice.setJustDiscovered(false);
+ }
}
}
public synchronized void onBtClassChanged(BluetoothDevice device) {
CachedBluetoothDevice cachedDevice = findDevice(device);
if (cachedDevice != null) {
- cachedDevice.refreshBtClass();
+ cachedDevice.dispatchAttributesChanged();
}
}
@@ -278,172 +207,45 @@ public class CachedBluetoothDeviceManager {
if (bluetoothState == BluetoothAdapter.STATE_TURNING_OFF) {
for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
+ CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
+ if (subDevice != null) {
+ if (subDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
+ cachedDevice.setSubDevice(null);
+ }
+ }
if (cachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
cachedDevice.setJustDiscovered(false);
mCachedDevices.remove(i);
- if (cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID
- && mCachedDevicesMapForHearingAids.containsKey(cachedDevice.getHiSyncId()))
- {
- mCachedDevicesMapForHearingAids.remove(cachedDevice.getHiSyncId());
- }
- } 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
- // by making a binder call.
- cachedDevice.clearProfileConnectionState();
- }
- }
- for (int i = mHearingAidDevicesNotAddedInCache.size() - 1; i >= 0; i--) {
- CachedBluetoothDevice notCachedDevice = mHearingAidDevicesNotAddedInCache.get(i);
- if (notCachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
- notCachedDevice.setJustDiscovered(false);
- mHearingAidDevicesNotAddedInCache.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
- // by making a binder call.
- notCachedDevice.clearProfileConnectionState();
}
}
}
}
public synchronized void onActiveDeviceChanged(CachedBluetoothDevice activeDevice,
- int bluetoothProfile) {
+ int bluetoothProfile) {
for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
boolean isActive = Objects.equals(cachedDevice, activeDevice);
cachedDevice.onActiveDeviceChanged(isActive, bluetoothProfile);
}
}
- public synchronized void onHiSyncIdChanged(long hiSyncId) {
- int firstMatchedIndex = -1;
-
- for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
- CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
- if (cachedDevice.getHiSyncId() == hiSyncId) {
- if (firstMatchedIndex != -1) {
- /* Found the second one */
- int indexToRemoveFromUi;
- CachedBluetoothDevice deviceToRemoveFromUi;
-
- // Since the hiSyncIds have been updated for a connected pair of hearing aids,
- // we remove the entry of one the hearing aids from the UI. Unless the
- // hiSyncId get updated, the system does not know it is a hearing aid, so we add
- // both the hearing aids as separate entries in the UI first, then remove one
- // of them after the hiSyncId is populated. We will choose the device that
- // is not connected to be removed.
- if (cachedDevice.isConnected()) {
- indexToRemoveFromUi = firstMatchedIndex;
- deviceToRemoveFromUi = mCachedDevices.get(firstMatchedIndex);
- mCachedDevicesMapForHearingAids.put(hiSyncId, cachedDevice);
- } else {
- indexToRemoveFromUi = i;
- deviceToRemoveFromUi = cachedDevice;
- mCachedDevicesMapForHearingAids.put(hiSyncId,
- mCachedDevices.get(firstMatchedIndex));
- }
-
- mCachedDevices.remove(indexToRemoveFromUi);
- mHearingAidDevicesNotAddedInCache.add(deviceToRemoveFromUi);
- log("onHiSyncIdChanged: removed from UI device=" + deviceToRemoveFromUi
- + ", with hiSyncId=" + hiSyncId);
- mBtManager.getEventManager().dispatchDeviceRemoved(deviceToRemoveFromUi);
- break;
- } else {
- mCachedDevicesMapForHearingAids.put(hiSyncId, cachedDevice);
- firstMatchedIndex = i;
- }
- }
- }
- }
-
- public CachedBluetoothDevice getHearingAidOtherDevice(CachedBluetoothDevice thisDevice,
- long hiSyncId) {
- if (hiSyncId == BluetoothHearingAid.HI_SYNC_ID_INVALID) {
- return null;
- }
-
- // Searched the lists for the other side device with the matching hiSyncId.
- for (CachedBluetoothDevice notCachedDevice : mHearingAidDevicesNotAddedInCache) {
- if ((hiSyncId == notCachedDevice.getHiSyncId()) &&
- (!Objects.equals(notCachedDevice, thisDevice))) {
- return notCachedDevice;
- }
- }
-
- CachedBluetoothDevice cachedDevice = mCachedDevicesMapForHearingAids.get(hiSyncId);
- if (!Objects.equals(cachedDevice, thisDevice)) {
- return cachedDevice;
- }
- return null;
- }
-
- private void hearingAidSwitchDisplayDevice(CachedBluetoothDevice toDisplayDevice,
- CachedBluetoothDevice toHideDevice, long hiSyncId)
- {
- log("hearingAidSwitchDisplayDevice: toDisplayDevice=" + toDisplayDevice
- + ", toHideDevice=" + toHideDevice);
-
- // Remove the "toHideDevice" device from the UI.
- mHearingAidDevicesNotAddedInCache.add(toHideDevice);
- mCachedDevices.remove(toHideDevice);
- mBtManager.getEventManager().dispatchDeviceRemoved(toHideDevice);
-
- // Add the "toDisplayDevice" device to the UI.
- mHearingAidDevicesNotAddedInCache.remove(toDisplayDevice);
- mCachedDevices.add(toDisplayDevice);
- mCachedDevicesMapForHearingAids.put(hiSyncId, toDisplayDevice);
- mBtManager.getEventManager().dispatchDeviceAdded(toDisplayDevice);
- }
-
- public synchronized void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice,
- int state, int bluetoothProfile) {
- if (bluetoothProfile == BluetoothProfile.HEARING_AID
- && cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID
- && cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED) {
-
- long hiSyncId = cachedDevice.getHiSyncId();
-
- CachedBluetoothDevice otherDevice = getHearingAidOtherDevice(cachedDevice, hiSyncId);
- if (otherDevice == null) {
- // no other side device. Nothing to do.
- return;
- }
-
- if (state == BluetoothProfile.STATE_CONNECTED &&
- mHearingAidDevicesNotAddedInCache.contains(cachedDevice)) {
- hearingAidSwitchDisplayDevice(cachedDevice, otherDevice, hiSyncId);
- } else if (state == BluetoothProfile.STATE_DISCONNECTED
- && otherDevice.isConnected()) {
- CachedBluetoothDevice mapDevice = mCachedDevicesMapForHearingAids.get(hiSyncId);
- if ((mapDevice != null) && (Objects.equals(cachedDevice, mapDevice))) {
- hearingAidSwitchDisplayDevice(otherDevice, cachedDevice, hiSyncId);
- }
- }
- }
+ public synchronized boolean onProfileConnectionStateChangedIfProcessed(CachedBluetoothDevice
+ cachedDevice, int state) {
+ return mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(cachedDevice,
+ state);
}
public synchronized void onDeviceUnpaired(CachedBluetoothDevice device) {
- final long hiSyncId = device.getHiSyncId();
-
- if (hiSyncId == BluetoothHearingAid.HI_SYNC_ID_INVALID) return;
-
- for (int i = mHearingAidDevicesNotAddedInCache.size() - 1; i >= 0; i--) {
- CachedBluetoothDevice cachedDevice = mHearingAidDevicesNotAddedInCache.get(i);
- if (cachedDevice.getHiSyncId() == hiSyncId) {
- // TODO: Look for more cleanups on unpairing the device.
- mHearingAidDevicesNotAddedInCache.remove(i);
- if (device == cachedDevice) continue;
- log("onDeviceUnpaired: Unpair device=" + cachedDevice);
- cachedDevice.unpair();
- }
- }
-
- CachedBluetoothDevice mappedDevice = mCachedDevicesMapForHearingAids.get(hiSyncId);
- if ((mappedDevice != null) && (!Objects.equals(device, mappedDevice))) {
- log("onDeviceUnpaired: Unpair mapped device=" + mappedDevice);
- mappedDevice.unpair();
+ CachedBluetoothDevice mainDevice = mHearingAidDeviceManager.findMainDevice(device);
+ CachedBluetoothDevice subDevice = device.getSubDevice();
+ if (subDevice != null) {
+ // Main device is unpaired, to unpair sub device
+ subDevice.unpair();
+ device.setSubDevice(null);
+ } else if (mainDevice != null) {
+ // Sub device unpaired, to unpair main device
+ mainDevice.unpair();
+ mainDevice.setSubDevice(null);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
index 3bb8450207ab..62507f58426f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
@@ -40,7 +40,6 @@ public class HeadsetProfile implements LocalBluetoothProfile {
private BluetoothHeadset mService;
private boolean mIsProfileReady;
- private final LocalBluetoothAdapter mLocalAdapter;
private final CachedBluetoothDeviceManager mDeviceManager;
private final LocalBluetoothProfileManager mProfileManager;
@@ -69,7 +68,7 @@ public class HeadsetProfile implements LocalBluetoothProfile {
// we may add a new device here, but generally this should not happen
if (device == null) {
Log.w(TAG, "HeadsetProfile found new device: " + nextDevice);
- device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
+ device = mDeviceManager.addDevice(nextDevice);
}
device.onProfileStateChanged(HeadsetProfile.this,
BluetoothProfile.STATE_CONNECTED);
@@ -96,13 +95,11 @@ public class HeadsetProfile implements LocalBluetoothProfile {
return BluetoothProfile.HEADSET;
}
- HeadsetProfile(Context context, LocalBluetoothAdapter adapter,
- CachedBluetoothDeviceManager deviceManager,
+ HeadsetProfile(Context context, CachedBluetoothDeviceManager deviceManager,
LocalBluetoothProfileManager profileManager) {
- mLocalAdapter = adapter;
mDeviceManager = deviceManager;
mProfileManager = profileManager;
- mLocalAdapter.getProfileProxy(context, new HeadsetServiceListener(),
+ BluetoothAdapter.getDefaultAdapter().getProfileProxy(context, new HeadsetServiceListener(),
BluetoothProfile.HEADSET);
}
@@ -226,7 +223,7 @@ public class HeadsetProfile implements LocalBluetoothProfile {
return R.string.bluetooth_headset_profile_summary_connected;
default:
- return Utils.getConnectionStateSummary(state);
+ return BluetoothUtils.getConnectionStateSummary(state);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
new file mode 100644
index 000000000000..20ece69d7281
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2018 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.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothProfile;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * HearingAidDeviceManager manages the set of remote HearingAid Bluetooth devices.
+ */
+public class HearingAidDeviceManager {
+ private static final String TAG = "HearingAidDeviceManager";
+ private static final boolean DEBUG = BluetoothUtils.D;
+
+ private final LocalBluetoothManager mBtManager;
+ private final List<CachedBluetoothDevice> mCachedDevices;
+ HearingAidDeviceManager(LocalBluetoothManager localBtManager,
+ List<CachedBluetoothDevice> CachedDevices) {
+ mBtManager = localBtManager;
+ mCachedDevices = CachedDevices;
+ }
+
+ void initHearingAidDeviceIfNeeded(CachedBluetoothDevice newDevice) {
+ long hiSyncId = getHiSyncId(newDevice.getDevice());
+ if (isValidHiSyncId(hiSyncId)) {
+ // Once hiSyncId is valid, assign hiSyncId
+ newDevice.setHiSyncId(hiSyncId);
+ }
+ }
+
+ private long getHiSyncId(BluetoothDevice device) {
+ LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
+ HearingAidProfile profileProxy = profileManager.getHearingAidProfile();
+ if (profileProxy != null) {
+ return profileProxy.getHiSyncId(device);
+ }
+ return BluetoothHearingAid.HI_SYNC_ID_INVALID;
+ }
+
+ boolean setSubDeviceIfNeeded(CachedBluetoothDevice newDevice) {
+ final long hiSyncId = newDevice.getHiSyncId();
+ if (isValidHiSyncId(hiSyncId)) {
+ final CachedBluetoothDevice hearingAidDevice = getCachedDevice(hiSyncId);
+ // Just add one of the hearing aids from a pair in the list that is shown in the UI.
+ // Once there is another device with the same hiSyncId, to add new device as sub
+ // device.
+ if (hearingAidDevice != null) {
+ hearingAidDevice.setSubDevice(newDevice);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isValidHiSyncId(long hiSyncId) {
+ return hiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID;
+ }
+
+ private CachedBluetoothDevice getCachedDevice(long hiSyncId) {
+ for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
+ CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
+ if (cachedDevice.getHiSyncId() == hiSyncId) {
+ return cachedDevice;
+ }
+ }
+ return null;
+ }
+
+ // To collect all HearingAid devices and call #onHiSyncIdChanged to group device by HiSyncId
+ void updateHearingAidsDevices() {
+ final Set<Long> newSyncIdSet = new HashSet<Long>();
+ for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
+ // Do nothing if HiSyncId has been assigned
+ if (!isValidHiSyncId(cachedDevice.getHiSyncId())) {
+ final long newHiSyncId = getHiSyncId(cachedDevice.getDevice());
+ // Do nothing if there is no HiSyncId on Bluetooth device
+ if (isValidHiSyncId(newHiSyncId)) {
+ cachedDevice.setHiSyncId(newHiSyncId);
+ newSyncIdSet.add(newHiSyncId);
+ }
+ }
+ }
+ for (Long syncId : newSyncIdSet) {
+ onHiSyncIdChanged(syncId);
+ }
+ }
+
+ // Group devices by hiSyncId
+ @VisibleForTesting
+ void onHiSyncIdChanged(long hiSyncId) {
+ int firstMatchedIndex = -1;
+
+ for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
+ CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
+ if (cachedDevice.getHiSyncId() != hiSyncId) {
+ continue;
+ }
+ if (firstMatchedIndex == -1) {
+ // Found the first one
+ firstMatchedIndex = i;
+ continue;
+ }
+ // Found the second one
+ int indexToRemoveFromUi;
+ CachedBluetoothDevice subDevice;
+ CachedBluetoothDevice mainDevice;
+ // Since the hiSyncIds have been updated for a connected pair of hearing aids,
+ // we remove the entry of one the hearing aids from the UI. Unless the
+ // hiSyncId get updated, the system does not know it is a hearing aid, so we add
+ // both the hearing aids as separate entries in the UI first, then remove one
+ // of them after the hiSyncId is populated. We will choose the device that
+ // is not connected to be removed.
+ if (cachedDevice.isConnected()) {
+ mainDevice = cachedDevice;
+ indexToRemoveFromUi = firstMatchedIndex;
+ subDevice = mCachedDevices.get(firstMatchedIndex);
+ } else {
+ mainDevice = mCachedDevices.get(firstMatchedIndex);
+ indexToRemoveFromUi = i;
+ subDevice = cachedDevice;
+ }
+
+ mainDevice.setSubDevice(subDevice);
+ mCachedDevices.remove(indexToRemoveFromUi);
+ log("onHiSyncIdChanged: removed from UI device =" + subDevice
+ + ", with hiSyncId=" + hiSyncId);
+ mBtManager.getEventManager().dispatchDeviceRemoved(subDevice);
+ break;
+ }
+ }
+
+ // @return {@code true}, the event is processed inside the method. It is for updating
+ // hearing aid device on main-sub relationship when receiving connected or disconnected.
+ // @return {@code false}, it is not hearing aid device or to process it same as other profiles
+ boolean onProfileConnectionStateChangedIfProcessed(CachedBluetoothDevice cachedDevice,
+ int state) {
+ switch (state) {
+ case BluetoothProfile.STATE_CONNECTED:
+ onHiSyncIdChanged(cachedDevice.getHiSyncId());
+ CachedBluetoothDevice mainDevice = findMainDevice(cachedDevice);
+ if (mainDevice != null){
+ if (mainDevice.isConnected()) {
+ // When main device exists and in connected state, receiving sub device
+ // connection. To refresh main device UI
+ mainDevice.refresh();
+ return true;
+ } else {
+ // When both Hearing Aid devices are disconnected, receiving sub device
+ // connection. To switch content and dispatch to notify UI change
+ mBtManager.getEventManager().dispatchDeviceRemoved(mainDevice);
+ mainDevice.switchSubDeviceContent();
+ mainDevice.refresh();
+ // It is necessary to do remove and add for updating the mapping on
+ // preference and device
+ mBtManager.getEventManager().dispatchDeviceAdded(mainDevice);
+ return true;
+ }
+ }
+ break;
+ case BluetoothProfile.STATE_DISCONNECTED:
+ mainDevice = findMainDevice(cachedDevice);
+ if (mainDevice != null) {
+ // When main device exists, receiving sub device disconnection
+ // To update main device UI
+ mainDevice.refresh();
+ return true;
+ }
+ CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
+ if (subDevice != null && subDevice.isConnected()) {
+ // Main device is disconnected and sub device is connected
+ // To copy data from sub device to main device
+ mBtManager.getEventManager().dispatchDeviceRemoved(cachedDevice);
+ cachedDevice.switchSubDeviceContent();
+ cachedDevice.refresh();
+ // It is necessary to do remove and add for updating the mapping on
+ // preference and device
+ mBtManager.getEventManager().dispatchDeviceAdded(cachedDevice);
+ return true;
+ }
+ break;
+ }
+ return false;
+ }
+
+ CachedBluetoothDevice findMainDevice(CachedBluetoothDevice device) {
+ for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
+ if (isValidHiSyncId(cachedDevice.getHiSyncId())) {
+ CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
+ if (subDevice != null && subDevice.equals(device)) {
+ return cachedDevice;
+ }
+ }
+ }
+ return null;
+ }
+
+ private void log(String msg) {
+ if (DEBUG) {
+ Log.d(TAG, msg);
+ }
+ }
+} \ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
index d4620de4bc6f..adb5ab34b5c1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
@@ -16,23 +16,17 @@
package com.android.settingslib.bluetooth;
-import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
-import android.bluetooth.BluetoothCodecConfig;
-import android.bluetooth.BluetoothCodecStatus;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothUuid;
import android.content.Context;
-import android.os.ParcelUuid;
import android.util.Log;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.R;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
public class HearingAidProfile implements LocalBluetoothProfile {
@@ -44,7 +38,6 @@ public class HearingAidProfile implements LocalBluetoothProfile {
private BluetoothHearingAid mService;
private boolean mIsProfileReady;
- private final LocalBluetoothAdapter mLocalAdapter;
private final CachedBluetoothDeviceManager mDeviceManager;
static final String NAME = "HearingAid";
@@ -70,7 +63,7 @@ public class HearingAidProfile implements LocalBluetoothProfile {
if (V) {
Log.d(TAG, "HearingAidProfile found new device: " + nextDevice);
}
- device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
+ device = mDeviceManager.addDevice(nextDevice);
}
device.onProfileStateChanged(HearingAidProfile.this,
BluetoothProfile.STATE_CONNECTED);
@@ -78,7 +71,7 @@ public class HearingAidProfile implements LocalBluetoothProfile {
}
// Check current list of CachedDevices to see if any are Hearing Aid devices.
- mDeviceManager.updateHearingAidsDevices(mProfileManager);
+ mDeviceManager.updateHearingAidsDevices();
mIsProfileReady=true;
}
@@ -98,15 +91,13 @@ public class HearingAidProfile implements LocalBluetoothProfile {
return BluetoothProfile.HEARING_AID;
}
- HearingAidProfile(Context context, LocalBluetoothAdapter adapter,
- CachedBluetoothDeviceManager deviceManager,
+ HearingAidProfile(Context context, CachedBluetoothDeviceManager deviceManager,
LocalBluetoothProfileManager profileManager) {
mContext = context;
- mLocalAdapter = adapter;
mDeviceManager = deviceManager;
mProfileManager = profileManager;
- mLocalAdapter.getProfileProxy(context, new HearingAidServiceListener(),
- BluetoothProfile.HEARING_AID);
+ BluetoothAdapter.getDefaultAdapter().getProfileProxy(context,
+ new HearingAidServiceListener(), BluetoothProfile.HEARING_AID);
}
public boolean accessProfileEnabled() {
@@ -234,7 +225,7 @@ public class HearingAidProfile implements LocalBluetoothProfile {
return R.string.bluetooth_hearing_aid_profile_summary_connected;
default:
- return Utils.getConnectionStateSummary(state);
+ return BluetoothUtils.getConnectionStateSummary(state);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
index e975dea61a69..4879144a5994 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
@@ -16,10 +16,10 @@
package com.android.settingslib.bluetooth;
-import android.bluetooth.BluetoothHeadsetClient;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadsetClient;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
import android.content.Context;
@@ -40,7 +40,6 @@ final class HfpClientProfile implements LocalBluetoothProfile {
private BluetoothHeadsetClient mService;
private boolean mIsProfileReady;
- private final LocalBluetoothAdapter mLocalAdapter;
private final CachedBluetoothDeviceManager mDeviceManager;
static final ParcelUuid[] SRC_UUIDS = {
@@ -60,7 +59,7 @@ final class HfpClientProfile implements LocalBluetoothProfile {
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- Log.d(TAG, "Bluetooth service connected, profile:" + profile);
+ Log.d(TAG, "Bluetooth service connected");
mService = (BluetoothHeadsetClient) proxy;
// We just bound to the service, so refresh the UI for any connected HFP devices.
List<BluetoothDevice> deviceList = mService.getConnectedDevices();
@@ -70,7 +69,7 @@ final class HfpClientProfile implements LocalBluetoothProfile {
// we may add a new device here, but generally this should not happen
if (device == null) {
Log.w(TAG, "HfpClient profile found new device: " + nextDevice);
- device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
+ device = mDeviceManager.addDevice(nextDevice);
}
device.onProfileStateChanged(
HfpClientProfile.this, BluetoothProfile.STATE_CONNECTED);
@@ -81,7 +80,7 @@ final class HfpClientProfile implements LocalBluetoothProfile {
@Override
public void onServiceDisconnected(int profile) {
- Log.d(TAG, "Bluetooth service disconnected, profile:" + profile);
+ Log.d(TAG, "Bluetooth service disconnected");
mIsProfileReady=false;
}
}
@@ -96,14 +95,12 @@ final class HfpClientProfile implements LocalBluetoothProfile {
return BluetoothProfile.HEADSET_CLIENT;
}
- HfpClientProfile(Context context, LocalBluetoothAdapter adapter,
- CachedBluetoothDeviceManager deviceManager,
+ HfpClientProfile(Context context, CachedBluetoothDeviceManager deviceManager,
LocalBluetoothProfileManager profileManager) {
- mLocalAdapter = adapter;
mDeviceManager = deviceManager;
mProfileManager = profileManager;
- mLocalAdapter.getProfileProxy(context, new HfpClientServiceListener(),
- BluetoothProfile.HEADSET_CLIENT);
+ BluetoothAdapter.getDefaultAdapter().getProfileProxy(context,
+ new HfpClientServiceListener(), BluetoothProfile.HEADSET_CLIENT);
}
@Override
@@ -210,7 +207,7 @@ final class HfpClientProfile implements LocalBluetoothProfile {
return R.string.bluetooth_headset_profile_summary_connected;
default:
- return Utils.getConnectionStateSummary(state);
+ return BluetoothUtils.getConnectionStateSummary(state);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java
index 0a94375f3aa4..61e5b6b3e125 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java
@@ -26,7 +26,6 @@ import android.util.Log;
import com.android.settingslib.R;
-import java.util.Collection;
import java.util.List;
/**
@@ -39,7 +38,6 @@ public class HidDeviceProfile implements LocalBluetoothProfile {
// HID Device Profile is always preferred.
private static final int PREFERRED_VALUE = -1;
- private final LocalBluetoothAdapter mLocalAdapter;
private final CachedBluetoothDeviceManager mDeviceManager;
private final LocalBluetoothProfileManager mProfileManager;
static final String NAME = "HID DEVICE";
@@ -47,14 +45,12 @@ public class HidDeviceProfile implements LocalBluetoothProfile {
private BluetoothHidDevice mService;
private boolean mIsProfileReady;
- HidDeviceProfile(Context context, LocalBluetoothAdapter adapter,
- CachedBluetoothDeviceManager deviceManager,
+ HidDeviceProfile(Context context,CachedBluetoothDeviceManager deviceManager,
LocalBluetoothProfileManager profileManager) {
- mLocalAdapter = adapter;
mDeviceManager = deviceManager;
mProfileManager = profileManager;
- adapter.getProfileProxy(context, new HidDeviceServiceListener(),
- BluetoothProfile.HID_DEVICE);
+ BluetoothAdapter.getDefaultAdapter().getProfileProxy(context,
+ new HidDeviceServiceListener(), BluetoothProfile.HID_DEVICE);
}
// These callbacks run on the main thread.
@@ -71,7 +67,7 @@ public class HidDeviceProfile implements LocalBluetoothProfile {
// we may add a new device here, but generally this should not happen
if (device == null) {
Log.w(TAG, "HidProfile found new device: " + nextDevice);
- device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
+ device = mDeviceManager.addDevice(nextDevice);
}
Log.d(TAG, "Connection status changed: " + device);
device.onProfileStateChanged(HidDeviceProfile.this,
@@ -171,7 +167,7 @@ public class HidDeviceProfile implements LocalBluetoothProfile {
case BluetoothProfile.STATE_CONNECTED:
return R.string.bluetooth_hid_profile_summary_connected;
default:
- return Utils.getConnectionStateSummary(state);
+ return BluetoothUtils.getConnectionStateSummary(state);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
index 1e064818cc72..75d16db13efe 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
@@ -38,7 +38,6 @@ public class HidProfile implements LocalBluetoothProfile {
private BluetoothHidHost mService;
private boolean mIsProfileReady;
- private final LocalBluetoothAdapter mLocalAdapter;
private final CachedBluetoothDeviceManager mDeviceManager;
private final LocalBluetoothProfileManager mProfileManager;
@@ -62,7 +61,7 @@ public class HidProfile implements LocalBluetoothProfile {
// we may add a new device here, but generally this should not happen
if (device == null) {
Log.w(TAG, "HidProfile found new device: " + nextDevice);
- device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
+ device = mDeviceManager.addDevice(nextDevice);
}
device.onProfileStateChanged(HidProfile.this, BluetoothProfile.STATE_CONNECTED);
device.refresh();
@@ -85,13 +84,12 @@ public class HidProfile implements LocalBluetoothProfile {
return BluetoothProfile.HID_HOST;
}
- HidProfile(Context context, LocalBluetoothAdapter adapter,
+ HidProfile(Context context,
CachedBluetoothDeviceManager deviceManager,
LocalBluetoothProfileManager profileManager) {
- mLocalAdapter = adapter;
mDeviceManager = deviceManager;
mProfileManager = profileManager;
- adapter.getProfileProxy(context, new HidHostServiceListener(),
+ BluetoothAdapter.getDefaultAdapter().getProfileProxy(context, new HidHostServiceListener(),
BluetoothProfile.HID_HOST);
}
@@ -164,7 +162,7 @@ public class HidProfile implements LocalBluetoothProfile {
return R.string.bluetooth_hid_profile_summary_connected;
default:
- return Utils.getConnectionStateSummary(state);
+ return BluetoothUtils.getConnectionStateSummary(state);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
index cbeeed8ea538..8f40ab47fe1b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
@@ -35,7 +35,10 @@ import java.util.Set;
* <p>Connection and bonding state changes affecting specific devices
* are handled by {@link CachedBluetoothDeviceManager},
* {@link BluetoothEventManager}, and {@link LocalBluetoothProfileManager}.
+ *
+ * @deprecated use {@link BluetoothAdapter} instead.
*/
+@Deprecated
public class LocalBluetoothAdapter {
private static final String TAG = "LocalBluetoothAdapter";
@@ -232,7 +235,7 @@ public class LocalBluetoothAdapter {
? BluetoothAdapter.STATE_TURNING_ON
: BluetoothAdapter.STATE_TURNING_OFF);
} else {
- if (Utils.V) {
+ if (BluetoothUtils.V) {
Log.v(TAG, "setBluetoothEnabled call, manager didn't return " +
"success for enabled: " + enabled);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java
index 1c50953bac12..53c6075ccff4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java
@@ -17,10 +17,15 @@
package com.android.settingslib.bluetooth;
import android.content.Context;
+import android.os.Handler;
+import android.os.UserHandle;
import android.util.Log;
import java.lang.ref.WeakReference;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresPermission;
+
/**
* LocalBluetoothManager provides a simplified interface on top of a subset of
* the Bluetooth API. Note that {@link #getInstance} will return null
@@ -48,6 +53,7 @@ public class LocalBluetoothManager {
/** The broadcast receiver event manager. */
private final BluetoothEventManager mEventManager;
+ @Nullable
public static synchronized LocalBluetoothManager getInstance(Context context,
BluetoothManagerCallback onInitCallback) {
if (sInstance == null) {
@@ -56,25 +62,61 @@ public class LocalBluetoothManager {
return null;
}
// This will be around as long as this process is
- Context appContext = context.getApplicationContext();
- sInstance = new LocalBluetoothManager(adapter, appContext);
+ sInstance = new LocalBluetoothManager(adapter, context, /* handler= */ null,
+ /* userHandle= */ null);
if (onInitCallback != null) {
- onInitCallback.onBluetoothManagerInitialized(appContext, sInstance);
+ onInitCallback.onBluetoothManagerInitialized(context.getApplicationContext(),
+ sInstance);
}
}
return sInstance;
}
- private LocalBluetoothManager(LocalBluetoothAdapter adapter, Context context) {
- mContext = context;
- mLocalAdapter = adapter;
+ /**
+ * Returns a new instance of {@link LocalBluetoothManager} or null if Bluetooth is not
+ * supported for this hardware. This instance should be globally cached by the caller.
+ */
+ @Nullable
+ public static LocalBluetoothManager create(Context context, Handler handler) {
+ LocalBluetoothAdapter adapter = LocalBluetoothAdapter.getInstance();
+ if (adapter == null) {
+ return null;
+ }
+ return new LocalBluetoothManager(adapter, context, handler, /* userHandle= */ null);
+ }
- mCachedDeviceManager = new CachedBluetoothDeviceManager(context, this);
- mEventManager = new BluetoothEventManager(mLocalAdapter,
- mCachedDeviceManager, context);
- mProfileManager = new LocalBluetoothProfileManager(context,
+ /**
+ * Returns a new instance of {@link LocalBluetoothManager} or null if Bluetooth is not
+ * supported for this hardware. This instance should be globally cached by the caller.
+ *
+ * <p> Allows to specify a {@link UserHandle} for which to receive bluetooth events.
+ *
+ * <p> Requires {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission.
+ */
+ @Nullable
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ public static LocalBluetoothManager create(Context context, Handler handler,
+ UserHandle userHandle) {
+ LocalBluetoothAdapter adapter = LocalBluetoothAdapter.getInstance();
+ if (adapter == null) {
+ return null;
+ }
+ return new LocalBluetoothManager(adapter, context, handler,
+ userHandle);
+ }
+
+ private LocalBluetoothManager(LocalBluetoothAdapter adapter, Context context, Handler handler,
+ UserHandle userHandle) {
+ mContext = context.getApplicationContext();
+ mLocalAdapter = adapter;
+ mCachedDeviceManager = new CachedBluetoothDeviceManager(mContext, this);
+ mEventManager = new BluetoothEventManager(mLocalAdapter, mCachedDeviceManager, mContext,
+ handler, userHandle);
+ mProfileManager = new LocalBluetoothProfileManager(mContext,
mLocalAdapter, mCachedDeviceManager, mEventManager);
+
+ mProfileManager.updateLocalProfiles();
mEventManager.readPairedDevices();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index d1ccc8fcc443..29c6d719641a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -18,6 +18,7 @@ package com.android.settingslib.bluetooth;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothA2dpSink;
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothHeadsetClient;
@@ -35,9 +36,12 @@ import android.bluetooth.BluetoothUuid;
import android.content.Context;
import android.content.Intent;
import android.os.ParcelUuid;
-import androidx.annotation.VisibleForTesting;
import android.util.Log;
-import com.android.internal.R;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.internal.util.CollectionUtils;
+
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@@ -51,9 +55,7 @@ import java.util.Map;
*/
public class LocalBluetoothProfileManager {
private static final String TAG = "LocalBluetoothProfileManager";
- private static final boolean DEBUG = Utils.D;
- /** Singleton instance. */
- private static LocalBluetoothProfileManager sInstance;
+ private static final boolean DEBUG = BluetoothUtils.D;
/**
* An interface for notifying BluetoothHeadset IPC clients when they have
@@ -80,7 +82,6 @@ public class LocalBluetoothProfileManager {
}
private final Context mContext;
- private final LocalBluetoothAdapter mLocalAdapter;
private final CachedBluetoothDeviceManager mDeviceManager;
private final BluetoothEventManager mEventManager;
@@ -90,14 +91,12 @@ public class LocalBluetoothProfileManager {
private HfpClientProfile mHfpClientProfile;
private MapProfile mMapProfile;
private MapClientProfile mMapClientProfile;
- private final HidProfile mHidProfile;
+ private HidProfile mHidProfile;
private HidDeviceProfile mHidDeviceProfile;
private OppProfile mOppProfile;
- private final PanProfile mPanProfile;
+ private PanProfile mPanProfile;
private PbapClientProfile mPbapClientProfile;
- private final PbapServerProfile mPbapProfile;
- private final boolean mUsePbapPce;
- private final boolean mUseMapClient;
+ private PbapServerProfile mPbapProfile;
private HearingAidProfile mHearingAidProfile;
private SapProfile mSapProfile;
@@ -113,201 +112,113 @@ public class LocalBluetoothProfileManager {
BluetoothEventManager eventManager) {
mContext = context;
- mLocalAdapter = adapter;
mDeviceManager = deviceManager;
mEventManager = eventManager;
- mUsePbapPce = mContext.getResources().getBoolean(R.bool.enable_pbap_pce_profile);
- // MAP Client is typically used in the same situations as PBAP Client
- mUseMapClient = mContext.getResources().getBoolean(R.bool.enable_pbap_pce_profile);
// pass this reference to adapter and event manager (circular dependency)
- mLocalAdapter.setProfileManager(this);
- mEventManager.setProfileManager(this);
-
- ParcelUuid[] uuids = adapter.getUuids();
-
- List<Integer> supportedList = mLocalAdapter.getSupportedProfiles();
- if (supportedList.contains(BluetoothProfile.HEARING_AID)) {
- mHearingAidProfile = new HearingAidProfile(mContext, mLocalAdapter, mDeviceManager,
- this);
- addProfile(mHearingAidProfile, HearingAidProfile.NAME,
- BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
- }
-
- // uuids may be null if Bluetooth is turned off
- if (uuids != null) {
- updateLocalProfiles(uuids);
- }
-
- // Always add HID host, HID device, and PAN profiles
- mHidProfile = new HidProfile(context, mLocalAdapter, mDeviceManager, this);
- addProfile(mHidProfile, HidProfile.NAME,
- BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED);
-
- mPanProfile = new PanProfile(context, mLocalAdapter);
- addPanProfile(mPanProfile, PanProfile.NAME,
- BluetoothPan.ACTION_CONNECTION_STATE_CHANGED);
-
- mHidDeviceProfile = new HidDeviceProfile(context, mLocalAdapter, mDeviceManager, this);
- addProfile(mHidDeviceProfile, HidDeviceProfile.NAME,
- BluetoothHidDevice.ACTION_CONNECTION_STATE_CHANGED);
-
- if(DEBUG) Log.d(TAG, "Adding local MAP profile");
- if (mUseMapClient) {
- mMapClientProfile = new MapClientProfile(mContext, mLocalAdapter, mDeviceManager, this);
- addProfile(mMapClientProfile, MapClientProfile.NAME,
- BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED);
- } else {
- mMapProfile = new MapProfile(mContext, mLocalAdapter, mDeviceManager, this);
- addProfile(mMapProfile, MapProfile.NAME,
- BluetoothMap.ACTION_CONNECTION_STATE_CHANGED);
- }
-
- //Create PBAP server profile
- if(DEBUG) Log.d(TAG, "Adding local PBAP profile");
-
- mPbapProfile = new PbapServerProfile(context);
- addProfile(mPbapProfile, PbapServerProfile.NAME,
- BluetoothPbap.ACTION_CONNECTION_STATE_CHANGED);
+ adapter.setProfileManager(this);
if (DEBUG) Log.d(TAG, "LocalBluetoothProfileManager construction complete");
}
/**
- * Initialize or update the local profile objects. If a UUID was previously
- * present but has been removed, we print a warning but don't remove the
- * profile object as it might be referenced elsewhere, or the UUID might
- * come back and we don't want multiple copies of the profile objects.
- * @param uuids
+ * create profile instance according to bluetooth supported profile list
*/
- void updateLocalProfiles(ParcelUuid[] uuids) {
- // A2DP SRC
- if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSource)) {
- if (mA2dpProfile == null) {
- if(DEBUG) Log.d(TAG, "Adding local A2DP SRC profile");
- mA2dpProfile = new A2dpProfile(mContext, mLocalAdapter, mDeviceManager, this);
- addProfile(mA2dpProfile, A2dpProfile.NAME,
- BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
- }
- } else if (mA2dpProfile != null) {
- Log.w(TAG, "Warning: A2DP profile was previously added but the UUID is now missing.");
- }
-
- // A2DP SINK
- if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSink)) {
- if (mA2dpSinkProfile == null) {
- if(DEBUG) Log.d(TAG, "Adding local A2DP Sink profile");
- mA2dpSinkProfile = new A2dpSinkProfile(mContext, mLocalAdapter,
- mDeviceManager, this);
- addProfile(mA2dpSinkProfile, A2dpSinkProfile.NAME,
- BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED);
- }
- } else if (mA2dpSinkProfile != null) {
- Log.w(TAG, "Warning: A2DP Sink profile was previously added but the "
- + "UUID is now missing.");
- }
-
- // Headset / Handsfree
- if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree_AG) ||
- BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP_AG)) {
- if (mHeadsetProfile == null) {
- if (DEBUG) Log.d(TAG, "Adding local HEADSET profile");
- mHeadsetProfile = new HeadsetProfile(mContext, mLocalAdapter,
- mDeviceManager, this);
- addHeadsetProfile(mHeadsetProfile, HeadsetProfile.NAME,
- BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED,
- BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED,
- BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
- }
- } else if (mHeadsetProfile != null) {
- Log.w(TAG, "Warning: HEADSET profile was previously added but the "
- + "UUID is now missing.");
- }
-
- // Headset HF
- if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree)) {
- if (mHfpClientProfile == null) {
- if(DEBUG) Log.d(TAG, "Adding local HfpClient profile");
- mHfpClientProfile =
- new HfpClientProfile(mContext, mLocalAdapter, mDeviceManager, this);
- addHeadsetProfile(mHfpClientProfile, HfpClientProfile.NAME,
- BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED,
- BluetoothHeadsetClient.ACTION_AUDIO_STATE_CHANGED,
- BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED);
- }
- } else if (mHfpClientProfile != null) {
- Log.w(TAG,
- "Warning: Hfp Client profile was previously added but the UUID is now missing.");
- } else {
- Log.d(TAG, "Handsfree Uuid not found.");
- }
-
- // Message Access Profile Client
- if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.MNS)) {
- if (mMapClientProfile == null) {
- if(DEBUG) Log.d(TAG, "Adding local Map Client profile");
- mMapClientProfile =
- new MapClientProfile(mContext, mLocalAdapter, mDeviceManager, this);
- addProfile(mMapClientProfile, MapClientProfile.NAME,
- BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED);
- }
- } else if (mMapClientProfile != null) {
- Log.w(TAG,
- "Warning: MAP Client profile was previously added but the "
- + "UUID is now missing.");
- } else {
- Log.d(TAG, "MAP Client Uuid not found.");
+ void updateLocalProfiles() {
+ List<Integer> supportedList = BluetoothAdapter.getDefaultAdapter().getSupportedProfiles();
+ if (CollectionUtils.isEmpty(supportedList)) {
+ if (DEBUG) Log.d(TAG, "supportedList is null");
+ return;
}
-
- // OPP
- if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush)) {
- if (mOppProfile == null) {
- if(DEBUG) Log.d(TAG, "Adding local OPP profile");
- mOppProfile = new OppProfile();
- // Note: no event handler for OPP, only name map.
- mProfileNameMap.put(OppProfile.NAME, mOppProfile);
- }
- } else if (mOppProfile != null) {
- Log.w(TAG, "Warning: OPP profile was previously added but the UUID is now missing.");
- }
-
- // PBAP Client
- if (mUsePbapPce) {
- if (mPbapClientProfile == null) {
- if(DEBUG) Log.d(TAG, "Adding local PBAP Client profile");
- mPbapClientProfile = new PbapClientProfile(mContext, mLocalAdapter, mDeviceManager,
- this);
- addProfile(mPbapClientProfile, PbapClientProfile.NAME,
- BluetoothPbapClient.ACTION_CONNECTION_STATE_CHANGED);
- }
- } else if (mPbapClientProfile != null) {
- Log.w(TAG,
- "Warning: PBAP Client profile was previously added but the UUID is now missing.");
- }
-
- // Hearing Aid Client
- if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HearingAid)) {
- if (mHearingAidProfile == null) {
- if(DEBUG) Log.d(TAG, "Adding local Hearing Aid profile");
- mHearingAidProfile = new HearingAidProfile(mContext, mLocalAdapter,
- mDeviceManager, this);
- addProfile(mHearingAidProfile, HearingAidProfile.NAME,
- BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
+ if (mA2dpProfile == null && supportedList.contains(BluetoothProfile.A2DP)) {
+ if (DEBUG) Log.d(TAG, "Adding local A2DP profile");
+ mA2dpProfile = new A2dpProfile(mContext, mDeviceManager, this);
+ addProfile(mA2dpProfile, A2dpProfile.NAME,
+ BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+ }
+ if (mA2dpSinkProfile == null && supportedList.contains(BluetoothProfile.A2DP_SINK)) {
+ if (DEBUG) Log.d(TAG, "Adding local A2DP SINK profile");
+ mA2dpSinkProfile = new A2dpSinkProfile(mContext, mDeviceManager, this);
+ addProfile(mA2dpSinkProfile, A2dpSinkProfile.NAME,
+ BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED);
+ }
+ if (mHeadsetProfile == null && supportedList.contains(BluetoothProfile.HEADSET)) {
+ if (DEBUG) Log.d(TAG, "Adding local HEADSET profile");
+ mHeadsetProfile = new HeadsetProfile(mContext, mDeviceManager, this);
+ addHeadsetProfile(mHeadsetProfile, HeadsetProfile.NAME,
+ BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED,
+ BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED,
+ BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+ }
+ if (mHfpClientProfile == null && supportedList.contains(BluetoothProfile.HEADSET_CLIENT)) {
+ if (DEBUG) Log.d(TAG, "Adding local HfpClient profile");
+ mHfpClientProfile = new HfpClientProfile(mContext, mDeviceManager, this);
+ addHeadsetProfile(mHfpClientProfile, HfpClientProfile.NAME,
+ BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED,
+ BluetoothHeadsetClient.ACTION_AUDIO_STATE_CHANGED,
+ BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED);
+ }
+ if (mMapClientProfile == null && supportedList.contains(BluetoothProfile.MAP_CLIENT)) {
+ if (DEBUG) Log.d(TAG, "Adding local MAP CLIENT profile");
+ mMapClientProfile = new MapClientProfile(mContext, mDeviceManager,this);
+ addProfile(mMapClientProfile, MapClientProfile.NAME,
+ BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED);
+ }
+ if (mMapProfile == null && supportedList.contains(BluetoothProfile.MAP)) {
+ if (DEBUG) Log.d(TAG, "Adding local MAP profile");
+ mMapProfile = new MapProfile(mContext, mDeviceManager, this);
+ addProfile(mMapProfile, MapProfile.NAME, BluetoothMap.ACTION_CONNECTION_STATE_CHANGED);
+ }
+ if (mOppProfile == null && supportedList.contains(BluetoothProfile.OPP)) {
+ if (DEBUG) Log.d(TAG, "Adding local OPP profile");
+ mOppProfile = new OppProfile();
+ // Note: no event handler for OPP, only name map.
+ mProfileNameMap.put(OppProfile.NAME, mOppProfile);
+ }
+ if (mHearingAidProfile == null && supportedList.contains(BluetoothProfile.HEARING_AID)) {
+ if (DEBUG) Log.d(TAG, "Adding local Hearing Aid profile");
+ mHearingAidProfile = new HearingAidProfile(mContext, mDeviceManager,
+ this);
+ addProfile(mHearingAidProfile, HearingAidProfile.NAME,
+ BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
+ }
+ if (mHidProfile == null && supportedList.contains(BluetoothProfile.HID_HOST)) {
+ if (DEBUG) Log.d(TAG, "Adding local HID_HOST profile");
+ mHidProfile = new HidProfile(mContext, mDeviceManager, this);
+ addProfile(mHidProfile, HidProfile.NAME,
+ BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED);
+ }
+ if (mHidDeviceProfile == null && supportedList.contains(BluetoothProfile.HID_DEVICE)) {
+ if (DEBUG) Log.d(TAG, "Adding local HID_DEVICE profile");
+ mHidDeviceProfile = new HidDeviceProfile(mContext, mDeviceManager, this);
+ addProfile(mHidDeviceProfile, HidDeviceProfile.NAME,
+ BluetoothHidDevice.ACTION_CONNECTION_STATE_CHANGED);
+ }
+ if (mPanProfile == null && supportedList.contains(BluetoothProfile.PAN)) {
+ if (DEBUG) Log.d(TAG, "Adding local PAN profile");
+ mPanProfile = new PanProfile(mContext);
+ addPanProfile(mPanProfile, PanProfile.NAME,
+ BluetoothPan.ACTION_CONNECTION_STATE_CHANGED);
+ }
+ if (mPbapProfile == null && supportedList.contains(BluetoothProfile.PBAP)) {
+ if (DEBUG) Log.d(TAG, "Adding local PBAP profile");
+ mPbapProfile = new PbapServerProfile(mContext);
+ addProfile(mPbapProfile, PbapServerProfile.NAME,
+ BluetoothPbap.ACTION_CONNECTION_STATE_CHANGED);
+ }
+ if (mPbapClientProfile == null && supportedList.contains(BluetoothProfile.PBAP_CLIENT)) {
+ if (DEBUG) Log.d(TAG, "Adding local PBAP Client profile");
+ mPbapClientProfile = new PbapClientProfile(mContext, mDeviceManager,this);
+ addProfile(mPbapClientProfile, PbapClientProfile.NAME,
+ BluetoothPbapClient.ACTION_CONNECTION_STATE_CHANGED);
+ }
+ if (mSapProfile == null && supportedList.contains(BluetoothProfile.SAP)) {
+ if (DEBUG) {
+ Log.d(TAG, "Adding local SAP profile");
}
- } else if (mHearingAidProfile != null) {
- Log.w(TAG, "Warning: Hearing Aid profile was previously added but the "
- + "UUID is now missing.");
- }
-
- // SAP
- if (mSapProfile == null && BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.SAP)) {
- Log.d(TAG, "Adding local SAP profile");
- mSapProfile = new SapProfile(mContext, mLocalAdapter, mDeviceManager, this);
- addProfile(mSapProfile, SapProfile.NAME,
- BluetoothSap.ACTION_CONNECTION_STATE_CHANGED);
+ mSapProfile = new SapProfile(mContext, mDeviceManager, this);
+ addProfile(mSapProfile, SapProfile.NAME, BluetoothSap.ACTION_CONNECTION_STATE_CHANGED);
}
mEventManager.registerProfileIntentReceiver();
-
- // There is no local SDP record for HID and Settings app doesn't control PBAP Server.
}
private void addHeadsetProfile(LocalBluetoothProfile profile, String profileName,
@@ -341,10 +252,7 @@ public class LocalBluetoothProfileManager {
// Called from LocalBluetoothAdapter when state changes to ON
void setBluetoothStateOn() {
- ParcelUuid[] uuids = mLocalAdapter.getUuids();
- if (uuids != null) {
- updateLocalProfiles(uuids);
- }
+ updateLocalProfiles();
mEventManager.readPairedDevices();
}
@@ -362,8 +270,7 @@ public class LocalBluetoothProfileManager {
CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
if (cachedDevice == null) {
Log.w(TAG, "StateChangedHandler found new device: " + device);
- cachedDevice = mDeviceManager.addDevice(mLocalAdapter,
- LocalBluetoothProfileManager.this, device);
+ cachedDevice = mDeviceManager.addDevice(device);
}
onReceiveInternal(intent, cachedDevice);
}
@@ -376,38 +283,26 @@ public class LocalBluetoothProfileManager {
Log.i(TAG, "Failed to connect " + mProfile + " device");
}
- boolean isHearingAidProfile = (getHearingAidProfile() != null) &&
- (mProfile instanceof HearingAidProfile);
-
- if (isHearingAidProfile && (newState == BluetoothProfile.STATE_CONNECTED)) {
+ if (getHearingAidProfile() != null &&
+ mProfile instanceof HearingAidProfile &&
+ (newState == BluetoothProfile.STATE_CONNECTED)) {
// Check if the HiSyncID has being initialized
if (cachedDevice.getHiSyncId() == BluetoothHearingAid.HI_SYNC_ID_INVALID) {
-
long newHiSyncId = getHearingAidProfile().getHiSyncId(cachedDevice.getDevice());
-
if (newHiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID) {
cachedDevice.setHiSyncId(newHiSyncId);
- mDeviceManager.onHiSyncIdChanged(newHiSyncId);
}
}
}
-
cachedDevice.onProfileStateChanged(mProfile, newState);
- cachedDevice.refresh();
-
- if (isHearingAidProfile) {
- CachedBluetoothDevice otherDevice =
- mDeviceManager.getHearingAidOtherDevice(cachedDevice, cachedDevice.getHiSyncId());
- if (otherDevice != null) {
- if (DEBUG) {
- Log.d(TAG, "Refreshing other hearing aid=" + otherDevice
- + ", newState=" + newState);
- }
- otherDevice.refresh();
- }
+ // Dispatch profile changed after device update
+ if (!(cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID
+ && mDeviceManager.onProfileConnectionStateChangedIfProcessed(cachedDevice,
+ newState))) {
+ cachedDevice.refresh();
+ mEventManager.dispatchProfileConnectionStateChanged(cachedDevice, newState,
+ mProfile.getProfileId());
}
- mEventManager.dispatchProfileConnectionStateChanged(cachedDevice, newState,
- mProfile.getProfileId());
}
}
@@ -650,7 +545,9 @@ public class LocalBluetoothProfileManager {
removedProfiles.remove(mMapClientProfile);
}
- if (mUsePbapPce) {
+ if ((mPbapClientProfile != null) &&
+ BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.PBAP_PCE) &&
+ BluetoothUuid.containsAnyUuid(uuids, PbapClientProfile.SRC_UUIDS)) {
profiles.add(mPbapClientProfile);
removedProfiles.remove(mPbapClientProfile);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
index 57712e3cbb28..1e22f440b5f8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
@@ -40,7 +40,6 @@ public final class MapClientProfile implements LocalBluetoothProfile {
private BluetoothMapClient mService;
private boolean mIsProfileReady;
- private final LocalBluetoothAdapter mLocalAdapter;
private final CachedBluetoothDeviceManager mDeviceManager;
private final LocalBluetoothProfileManager mProfileManager;
@@ -70,7 +69,7 @@ public final class MapClientProfile implements LocalBluetoothProfile {
// we may add a new device here, but generally this should not happen
if (device == null) {
Log.w(TAG, "MapProfile found new device: " + nextDevice);
- device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
+ device = mDeviceManager.addDevice(nextDevice);
}
device.onProfileStateChanged(MapClientProfile.this,
BluetoothProfile.STATE_CONNECTED);
@@ -98,14 +97,12 @@ public final class MapClientProfile implements LocalBluetoothProfile {
return BluetoothProfile.MAP_CLIENT;
}
- MapClientProfile(Context context, LocalBluetoothAdapter adapter,
- CachedBluetoothDeviceManager deviceManager,
+ MapClientProfile(Context context, CachedBluetoothDeviceManager deviceManager,
LocalBluetoothProfileManager profileManager) {
- mLocalAdapter = adapter;
mDeviceManager = deviceManager;
mProfileManager = profileManager;
- mLocalAdapter.getProfileProxy(context, new MapClientServiceListener(),
- BluetoothProfile.MAP_CLIENT);
+ BluetoothAdapter.getDefaultAdapter().getProfileProxy(context,
+ new MapClientServiceListener(), BluetoothProfile.MAP_CLIENT);
}
public boolean accessProfileEnabled() {
@@ -200,7 +197,7 @@ public final class MapClientProfile implements LocalBluetoothProfile {
return R.string.bluetooth_map_profile_summary_connected;
default:
- return Utils.getConnectionStateSummary(state);
+ return BluetoothUtils.getConnectionStateSummary(state);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
index e59a036731d9..758202412c93 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
@@ -40,7 +40,6 @@ public class MapProfile implements LocalBluetoothProfile {
private BluetoothMap mService;
private boolean mIsProfileReady;
- private final LocalBluetoothAdapter mLocalAdapter;
private final CachedBluetoothDeviceManager mDeviceManager;
private final LocalBluetoothProfileManager mProfileManager;
@@ -69,7 +68,7 @@ public class MapProfile implements LocalBluetoothProfile {
// we may add a new device here, but generally this should not happen
if (device == null) {
Log.w(TAG, "MapProfile found new device: " + nextDevice);
- device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
+ device = mDeviceManager.addDevice(nextDevice);
}
device.onProfileStateChanged(MapProfile.this,
BluetoothProfile.STATE_CONNECTED);
@@ -97,13 +96,11 @@ public class MapProfile implements LocalBluetoothProfile {
return BluetoothProfile.MAP;
}
- MapProfile(Context context, LocalBluetoothAdapter adapter,
- CachedBluetoothDeviceManager deviceManager,
+ MapProfile(Context context, CachedBluetoothDeviceManager deviceManager,
LocalBluetoothProfileManager profileManager) {
- mLocalAdapter = adapter;
mDeviceManager = deviceManager;
mProfileManager = profileManager;
- mLocalAdapter.getProfileProxy(context, new MapServiceListener(),
+ BluetoothAdapter.getDefaultAdapter().getProfileProxy(context, new MapServiceListener(),
BluetoothProfile.MAP);
}
@@ -196,7 +193,7 @@ public class MapProfile implements LocalBluetoothProfile {
return R.string.bluetooth_map_profile_summary_connected;
default:
- return Utils.getConnectionStateSummary(state);
+ return BluetoothUtils.getConnectionStateSummary(state);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java
index 129732ccba3e..e1e5dbe29a1a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java
@@ -16,12 +16,12 @@
package com.android.settingslib.bluetooth;
-import com.android.settingslib.R;
-
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
+import com.android.settingslib.R;
+
/**
* OppProfile handles Bluetooth OPP.
*/
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java
index 0d566c77eac2..7b811624a6f3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java
@@ -38,7 +38,6 @@ public class PanProfile implements LocalBluetoothProfile {
private BluetoothPan mService;
private boolean mIsProfileReady;
- private final LocalBluetoothAdapter mLocalAdapter;
// Tethering direction for each device
private final HashMap<BluetoothDevice, Integer> mDeviceRoleMap =
@@ -74,9 +73,8 @@ public class PanProfile implements LocalBluetoothProfile {
return BluetoothProfile.PAN;
}
- PanProfile(Context context, LocalBluetoothAdapter adapter) {
- mLocalAdapter = adapter;
- mLocalAdapter.getProfileProxy(context, new PanServiceListener(),
+ PanProfile(Context context) {
+ BluetoothAdapter.getDefaultAdapter().getProfileProxy(context, new PanServiceListener(),
BluetoothProfile.PAN);
}
@@ -153,7 +151,7 @@ public class PanProfile implements LocalBluetoothProfile {
}
default:
- return Utils.getConnectionStateSummary(state);
+ return BluetoothUtils.getConnectionStateSummary(state);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
index 07523bd8faab..1f15601f2756 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
@@ -16,10 +16,10 @@
package com.android.settingslib.bluetooth;
-import android.bluetooth.BluetoothPbapClient;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothPbapClient;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
import android.content.Context;
@@ -38,7 +38,6 @@ public final class PbapClientProfile implements LocalBluetoothProfile {
private BluetoothPbapClient mService;
private boolean mIsProfileReady;
- private final LocalBluetoothAdapter mLocalAdapter;
private final CachedBluetoothDeviceManager mDeviceManager;
static final ParcelUuid[] SRC_UUIDS = {
@@ -66,7 +65,7 @@ public final class PbapClientProfile implements LocalBluetoothProfile {
// we may add a new device here, but generally this should not happen
if (device == null) {
Log.w(TAG, "PbapClientProfile found new device: " + nextDevice);
- device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
+ device = mDeviceManager.addDevice(nextDevice);
}
device.onProfileStateChanged(PbapClientProfile.this, BluetoothProfile.STATE_CONNECTED);
device.refresh();
@@ -100,14 +99,12 @@ public final class PbapClientProfile implements LocalBluetoothProfile {
return BluetoothProfile.PBAP_CLIENT;
}
- PbapClientProfile(Context context, LocalBluetoothAdapter adapter,
- CachedBluetoothDeviceManager deviceManager,
+ PbapClientProfile(Context context, CachedBluetoothDeviceManager deviceManager,
LocalBluetoothProfileManager profileManager) {
- mLocalAdapter = adapter;
mDeviceManager = deviceManager;
mProfileManager = profileManager;
- mLocalAdapter.getProfileProxy(context, new PbapClientServiceListener(),
- BluetoothProfile.PBAP_CLIENT);
+ BluetoothAdapter.getDefaultAdapter().getProfileProxy(context,
+ new PbapClientServiceListener(), BluetoothProfile.PBAP_CLIENT);
}
public boolean accessProfileEnabled() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
index c8a56a199b15..b4acc4810faf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
@@ -19,8 +19,8 @@ package com.android.settingslib.bluetooth;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothSap;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothSap;
import android.bluetooth.BluetoothUuid;
import android.content.Context;
import android.os.ParcelUuid;
@@ -40,7 +40,6 @@ final class SapProfile implements LocalBluetoothProfile {
private BluetoothSap mService;
private boolean mIsProfileReady;
- private final LocalBluetoothAdapter mLocalAdapter;
private final CachedBluetoothDeviceManager mDeviceManager;
private final LocalBluetoothProfileManager mProfileManager;
@@ -68,7 +67,7 @@ final class SapProfile implements LocalBluetoothProfile {
// we may add a new device here, but generally this should not happen
if (device == null) {
Log.w(TAG, "SapProfile found new device: " + nextDevice);
- device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
+ device = mDeviceManager.addDevice(nextDevice);
}
device.onProfileStateChanged(SapProfile.this,
BluetoothProfile.STATE_CONNECTED);
@@ -95,13 +94,11 @@ final class SapProfile implements LocalBluetoothProfile {
return BluetoothProfile.SAP;
}
- SapProfile(Context context, LocalBluetoothAdapter adapter,
- CachedBluetoothDeviceManager deviceManager,
+ SapProfile(Context context, CachedBluetoothDeviceManager deviceManager,
LocalBluetoothProfileManager profileManager) {
- mLocalAdapter = adapter;
mDeviceManager = deviceManager;
mProfileManager = profileManager;
- mLocalAdapter.getProfileProxy(context, new SapServiceListener(),
+ BluetoothAdapter.getDefaultAdapter().getProfileProxy(context, new SapServiceListener(),
BluetoothProfile.SAP);
}
@@ -196,7 +193,7 @@ final class SapProfile implements LocalBluetoothProfile {
return R.string.bluetooth_sap_profile_summary_connected;
default:
- return Utils.getConnectionStateSummary(state);
+ return BluetoothUtils.getConnectionStateSummary(state);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
index f4d647d61005..9572fb3c629d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
@@ -1,6 +1,7 @@
package com.android.settingslib.core;
import android.content.Context;
+
import androidx.preference.Preference;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java
index 72273046ef29..1aeb075abf30 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java
@@ -18,7 +18,7 @@ package com.android.settingslib.core.instrumentation;
import android.content.Context;
import android.metrics.LogMaker;
-import android.util.Log;
+import android.text.TextUtils;
import android.util.Pair;
import com.android.internal.logging.MetricsLogger;
@@ -29,8 +29,7 @@ import com.android.internal.logging.nano.MetricsProto;
*/
public class EventLogWriter implements LogWriter {
- private final MetricsLogger mMetricsLogger = new MetricsLogger();
-
+ @Override
public void visible(Context context, int source, int category) {
final LogMaker logMaker = new LogMaker(category)
.setType(MetricsProto.MetricsEvent.TYPE_OPEN)
@@ -38,53 +37,27 @@ public class EventLogWriter implements LogWriter {
MetricsLogger.action(logMaker);
}
+ @Override
public void hidden(Context context, int category) {
MetricsLogger.hidden(context, category);
}
- public void action(int category, int value, Pair<Integer, Object>... taggedData) {
- if (taggedData == null || taggedData.length == 0) {
- mMetricsLogger.action(category, value);
- } else {
- final LogMaker logMaker = new LogMaker(category)
- .setType(MetricsProto.MetricsEvent.TYPE_ACTION)
- .setSubtype(value);
- for (Pair<Integer, Object> pair : taggedData) {
- logMaker.addTaggedData(pair.first, pair.second);
- }
- mMetricsLogger.write(logMaker);
- }
- }
-
- public void action(int category, boolean value, Pair<Integer, Object>... taggedData) {
- action(category, value ? 1 : 0, taggedData);
- }
-
+ @Override
public void action(Context context, int category, Pair<Integer, Object>... taggedData) {
action(context, category, "", taggedData);
}
- public void actionWithSource(Context context, int source, int category) {
- final LogMaker logMaker = new LogMaker(category)
- .setType(MetricsProto.MetricsEvent.TYPE_ACTION);
- if (source != MetricsProto.MetricsEvent.VIEW_UNKNOWN) {
- logMaker.addTaggedData(MetricsProto.MetricsEvent.FIELD_CONTEXT, source);
- }
- MetricsLogger.action(logMaker);
- }
-
- /** @deprecated use {@link #action(int, int, Pair[])} */
- @Deprecated
+ @Override
public void action(Context context, int category, int value) {
MetricsLogger.action(context, category, value);
}
- /** @deprecated use {@link #action(int, boolean, Pair[])} */
- @Deprecated
+ @Override
public void action(Context context, int category, boolean value) {
MetricsLogger.action(context, category, value);
}
+ @Override
public void action(Context context, int category, String pkg,
Pair<Integer, Object>... taggedData) {
if (taggedData == null || taggedData.length == 0) {
@@ -100,11 +73,20 @@ public class EventLogWriter implements LogWriter {
}
}
- public void count(Context context, String name, int value) {
- MetricsLogger.count(context, name, value);
- }
-
- public void histogram(Context context, String name, int bucket) {
- MetricsLogger.histogram(context, name, bucket);
+ @Override
+ public void action(int attribution, int action, int pageId, String key, int value) {
+ final LogMaker logMaker = new LogMaker(action)
+ .setType(MetricsProto.MetricsEvent.TYPE_ACTION);
+ if (attribution != MetricsProto.MetricsEvent.VIEW_UNKNOWN) {
+ logMaker.addTaggedData(MetricsProto.MetricsEvent.FIELD_CONTEXT, pageId);
+ }
+ if (!TextUtils.isEmpty(key)) {
+ logMaker.addTaggedData(MetricsProto.MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME,
+ key);
+ logMaker.addTaggedData(
+ MetricsProto.MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE,
+ value);
+ }
+ MetricsLogger.action(logMaker);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java
index 4b9f5727208d..b60364ea7271 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java
@@ -34,16 +34,6 @@ public interface LogWriter {
void hidden(Context context, int category);
/**
- * Logs a user action.
- */
- void action(int category, int value, Pair<Integer, Object>... taggedData);
-
- /**
- * Logs a user action.
- */
- void action(int category, boolean value, Pair<Integer, Object>... taggedData);
-
- /**
* Logs an user action.
*/
void action(Context context, int category, Pair<Integer, Object>... taggedData);
@@ -51,20 +41,11 @@ public interface LogWriter {
/**
* Logs an user action.
*/
- void actionWithSource(Context context, int source, int category);
-
- /**
- * Logs an user action.
- * @deprecated use {@link #action(int, int, Pair[])}
- */
- @Deprecated
void action(Context context, int category, int value);
/**
* Logs an user action.
- * @deprecated use {@link #action(int, boolean, Pair[])}
*/
- @Deprecated
void action(Context context, int category, boolean value);
/**
@@ -73,12 +54,7 @@ public interface LogWriter {
void action(Context context, int category, String pkg, Pair<Integer, Object>... taggedData);
/**
- * Logs a count.
- */
- void count(Context context, String name, int value);
-
- /**
- * Logs a histogram event.
+ * Generically log action.
*/
- void histogram(Context context, String name, int bucket);
+ void action(int attribution, int action, int pageId, String key, int value);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
index 1e5b378e931c..188204e82e70 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
@@ -15,6 +15,8 @@
*/
package com.android.settingslib.core.instrumentation;
+import android.app.Activity;
+import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -30,7 +32,12 @@ import java.util.List;
* FeatureProvider for metrics.
*/
public class MetricsFeatureProvider {
- private List<LogWriter> mLoggerWriters;
+ /**
+ * The metrics category constant for logging source when a setting fragment is opened.
+ */
+ public static final String EXTRA_SOURCE_METRICS_CATEGORY = ":settings:source_metrics";
+
+ protected List<LogWriter> mLoggerWriters;
public MetricsFeatureProvider() {
mLoggerWriters = new ArrayList<>();
@@ -41,6 +48,25 @@ public class MetricsFeatureProvider {
mLoggerWriters.add(new EventLogWriter());
}
+ /**
+ * Returns the attribution id for specified activity. If no attribution is set, returns {@link
+ * SettingsEnums#PAGE_UNKNOWN}.
+ *
+ * <p/> Attribution is a {@link SettingsEnums} page id that indicates where the specified
+ * activity is launched from.
+ */
+ public int getAttribution(Activity activity) {
+ if (activity == null) {
+ return SettingsEnums.PAGE_UNKNOWN;
+ }
+ final Intent intent = activity.getIntent();
+ if (intent == null) {
+ return SettingsEnums.PAGE_UNKNOWN;
+ }
+ return intent.getIntExtra(EXTRA_SOURCE_METRICS_CATEGORY,
+ SettingsEnums.PAGE_UNKNOWN);
+ }
+
public void visible(Context context, int source, int category) {
for (LogWriter writer : mLoggerWriters) {
writer.visible(context, source, category);
@@ -53,75 +79,43 @@ public class MetricsFeatureProvider {
}
}
- public void actionWithSource(Context context, int source, int category) {
+ public void action(Context context, int category, Pair<Integer, Object>... taggedData) {
for (LogWriter writer : mLoggerWriters) {
- writer.actionWithSource(context, source, category);
+ writer.action(context, category, taggedData);
}
}
/**
- * Logs a user action. Includes the elapsed time since the containing
- * fragment has been visible.
+ * Logs a generic Settings event.
*/
- public void action(VisibilityLoggerMixin visibilityLogger, int category, int value) {
+ public void action(Context context, int category, String pkg,
+ Pair<Integer, Object>... taggedData) {
for (LogWriter writer : mLoggerWriters) {
- writer.action(category, value,
- sinceVisibleTaggedData(visibilityLogger.elapsedTimeSinceVisible()));
+ writer.action(context, category, pkg, taggedData);
}
}
/**
- * Logs a user action. Includes the elapsed time since the containing
- * fragment has been visible.
+ * Logs a generic Settings event.
*/
- public void action(VisibilityLoggerMixin visibilityLogger, int category, boolean value) {
+ public void action(int attribution, int action, int pageId, String key, int value) {
for (LogWriter writer : mLoggerWriters) {
- writer.action(category, value,
- sinceVisibleTaggedData(visibilityLogger.elapsedTimeSinceVisible()));
+ writer.action(attribution, action, pageId, key, value);
}
}
- public void action(Context context, int category, Pair<Integer, Object>... taggedData) {
- for (LogWriter writer : mLoggerWriters) {
- writer.action(context, category, taggedData);
- }
- }
-
- /** @deprecated use {@link #action(VisibilityLoggerMixin, int, int)} */
- @Deprecated
public void action(Context context, int category, int value) {
for (LogWriter writer : mLoggerWriters) {
writer.action(context, category, value);
}
}
- /** @deprecated use {@link #action(VisibilityLoggerMixin, int, boolean)} */
- @Deprecated
public void action(Context context, int category, boolean value) {
for (LogWriter writer : mLoggerWriters) {
writer.action(context, category, value);
}
}
- public void action(Context context, int category, String pkg,
- Pair<Integer, Object>... taggedData) {
- for (LogWriter writer : mLoggerWriters) {
- writer.action(context, category, pkg, taggedData);
- }
- }
-
- public void count(Context context, String name, int value) {
- for (LogWriter writer : mLoggerWriters) {
- writer.count(context, name, value);
- }
- }
-
- public void histogram(Context context, String name, int bucket) {
- for (LogWriter writer : mLoggerWriters) {
- writer.histogram(context, name, bucket);
- }
- }
-
public int getMetricsCategory(Object object) {
if (object == null || !(object instanceof Instrumentable)) {
return MetricsEvent.VIEW_UNKNOWN;
@@ -153,7 +147,4 @@ public class MetricsFeatureProvider {
Pair.create(MetricsEvent.FIELD_CONTEXT, sourceMetricsCategory));
}
- private Pair<Integer, Object> sinceVisibleTaggedData(long timestamp) {
- return Pair.create(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS, timestamp);
- }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java
index a79f125d4c99..71f3789405c8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java
@@ -20,11 +20,12 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
-import androidx.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
+import androidx.annotation.VisibleForTesting;
+
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import java.util.Map;
@@ -115,8 +116,6 @@ public class SharedPreferencesLogger implements SharedPreferences {
mPreferenceKeySet.add(prefKey);
return;
}
- // TODO: Remove count logging to save some resource.
- mMetricsFeature.count(mContext, buildCountName(prefKey, value), 1);
final Pair<Integer, Object> valueData;
if (value instanceof Long) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
index cb1ca59d2f43..aed02a268aea 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
@@ -16,17 +16,18 @@
package com.android.settingslib.core.instrumentation;
+import static com.android.settingslib.core.instrumentation.Instrumentable.METRICS_CATEGORY_UNKNOWN;
+
import android.app.Activity;
+import android.content.Intent;
+import android.os.SystemClock;
+
import androidx.lifecycle.Lifecycle.Event;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
-import android.content.Intent;
-import android.os.SystemClock;
import com.android.internal.logging.nano.MetricsProto;
-import static com.android.settingslib.core.instrumentation.Instrumentable.METRICS_CATEGORY_UNKNOWN;
-
/**
* Logs visibility change of a fragment.
*/
@@ -40,11 +41,6 @@ public class VisibilityLoggerMixin implements LifecycleObserver {
private int mSourceMetricsCategory = MetricsProto.MetricsEvent.VIEW_UNKNOWN;
private long mVisibleTimestamp;
- /**
- * The metrics category constant for logging source when a setting fragment is opened.
- */
- public static final String EXTRA_SOURCE_METRICS_CATEGORY = ":settings:source_metrics";
-
private VisibilityLoggerMixin() {
mMetricsCategory = METRICS_CATEGORY_UNKNOWN;
}
@@ -81,7 +77,8 @@ public class VisibilityLoggerMixin implements LifecycleObserver {
if (intent == null) {
return;
}
- mSourceMetricsCategory = intent.getIntExtra(EXTRA_SOURCE_METRICS_CATEGORY,
+ mSourceMetricsCategory = intent.getIntExtra(
+ MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY,
MetricsProto.MetricsEvent.VIEW_UNKNOWN);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/Lifecycle.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/Lifecycle.java
index 7ec757a56217..56de280a0049 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/Lifecycle.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/Lifecycle.java
@@ -18,19 +18,20 @@ package com.android.settingslib.core.lifecycle;
import static androidx.lifecycle.Lifecycle.Event.ON_ANY;
import android.annotation.UiThread;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.lifecycle.LifecycleRegistry;
-import androidx.lifecycle.OnLifecycleEvent;
import android.content.Context;
import android.os.Bundle;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.preference.PreferenceScreen;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
+import androidx.lifecycle.OnLifecycleEvent;
+import androidx.preference.PreferenceScreen;
+
import com.android.settingslib.core.lifecycle.events.OnAttach;
import com.android.settingslib.core.lifecycle.events.OnCreate;
import com.android.settingslib.core.lifecycle.events.OnCreateOptionsMenu;
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java
index f9aa062b64da..2bd0b27063ea 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java
@@ -24,20 +24,22 @@ import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
import android.annotation.Nullable;
import android.app.Activity;
-import androidx.lifecycle.LifecycleOwner;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.view.Menu;
import android.view.MenuItem;
+import androidx.fragment.app.FragmentActivity;
+import androidx.lifecycle.LifecycleOwner;
+
/**
* {@link Activity} that has hooks to observe activity lifecycle events.
*/
-public class ObservableActivity extends Activity implements LifecycleOwner {
+public class ObservableActivity extends FragmentActivity implements LifecycleOwner {
private final Lifecycle mLifecycle = new Lifecycle(this);
- public Lifecycle getLifecycle() {
+ public Lifecycle getSettingsLifecycle() {
return mLifecycle;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableDialogFragment.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableDialogFragment.java
index 972e062bb396..869f54f7d13a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableDialogFragment.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableDialogFragment.java
@@ -22,14 +22,15 @@ import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
import static androidx.lifecycle.Lifecycle.Event.ON_START;
import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
-import android.app.DialogFragment;
-import androidx.lifecycle.LifecycleOwner;
import android.content.Context;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import androidx.fragment.app.DialogFragment;
+import androidx.lifecycle.LifecycleOwner;
+
/**
* {@link DialogFragment} that has hooks to observe fragment lifecycle events.
*/
@@ -37,6 +38,10 @@ public class ObservableDialogFragment extends DialogFragment implements Lifecycl
protected final Lifecycle mLifecycle = new Lifecycle(this);
+ public Lifecycle getSettingsLifecycle() {
+ return mLifecycle;
+ }
+
@Override
public void onAttach(Context context) {
super.onAttach(context);
@@ -100,9 +105,4 @@ public class ObservableDialogFragment extends DialogFragment implements Lifecycl
}
return lifecycleHandled;
}
-
- @Override
- public Lifecycle getLifecycle() {
- return mLifecycle;
- }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableFragment.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableFragment.java
index 55597cc1247a..6ba930dcac96 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableFragment.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableFragment.java
@@ -24,19 +24,20 @@ import static androidx.lifecycle.Lifecycle.Event.ON_START;
import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
import android.annotation.CallSuper;
-import android.app.Fragment;
-import androidx.lifecycle.LifecycleOwner;
import android.content.Context;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.LifecycleOwner;
+
public class ObservableFragment extends Fragment implements LifecycleOwner {
private final Lifecycle mLifecycle = new Lifecycle(this);
- public Lifecycle getLifecycle() {
+ public Lifecycle getSettingsLifecycle() {
return mLifecycle;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java
index 904681c4aa3c..bd1e5a588968 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java
@@ -24,24 +24,25 @@ import static androidx.lifecycle.Lifecycle.Event.ON_START;
import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
import android.annotation.CallSuper;
-import androidx.lifecycle.LifecycleOwner;
import android.content.Context;
import android.os.Bundle;
-import androidx.preference.PreferenceFragment;
-import androidx.preference.PreferenceScreen;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceScreen;
+
/**
- * {@link PreferenceFragment} that has hooks to observe fragment lifecycle events.
+ * {@link PreferenceFragmentCompat} that has hooks to observe fragment lifecycle events.
*/
-public abstract class ObservablePreferenceFragment extends PreferenceFragment
+public abstract class ObservablePreferenceFragment extends PreferenceFragmentCompat
implements LifecycleOwner {
private final Lifecycle mLifecycle = new Lifecycle(this);
- public Lifecycle getLifecycle() {
+ public Lifecycle getSettingsLifecycle() {
return mLifecycle;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnCreate.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnCreate.java
index ada1537250b6..ae6741194662 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnCreate.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnCreate.java
@@ -16,12 +16,10 @@
package com.android.settingslib.core.lifecycle.events;
-import androidx.lifecycle.Lifecycle;
-import androidx.lifecycle.OnLifecycleEvent;
import android.os.Bundle;
/**
- * @deprecated use {@link OnLifecycleEvent(Lifecycle.Event) }
+ * @deprecated use {@link androidx.lifecycle.OnLifecycleEvent}
*/
@Deprecated
public interface OnCreate {
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnPrepareOptionsMenu.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnPrepareOptionsMenu.java
index b9f13713c59f..92ae9fdd50a9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnPrepareOptionsMenu.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnPrepareOptionsMenu.java
@@ -17,7 +17,6 @@
package com.android.settingslib.core.lifecycle.events;
import android.view.Menu;
-import android.view.MenuInflater;
public interface OnPrepareOptionsMenu {
void onPrepareOptionsMenu(Menu menu);
diff --git a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
index 955f64a357dc..8fac3fd0570c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
@@ -20,9 +20,6 @@ import android.content.Context;
import android.content.res.XmlResourceParser;
import android.icu.text.TimeZoneFormat;
import android.icu.text.TimeZoneNames;
-import androidx.annotation.VisibleForTesting;
-import androidx.core.text.BidiFormatter;
-import androidx.core.text.TextDirectionHeuristicsCompat;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
@@ -31,6 +28,10 @@ import android.text.style.TtsSpan;
import android.util.Log;
import android.view.View;
+import androidx.annotation.VisibleForTesting;
+import androidx.core.text.BidiFormatter;
+import androidx.core.text.TextDirectionHeuristicsCompat;
+
import com.android.settingslib.R;
import libcore.util.TimeZoneFinder;
@@ -381,7 +382,7 @@ public class ZoneGetter {
// Create a lookup of local zone IDs.
final List<String> zoneIds = lookupTimeZoneIdsByCountry(locale.getCountry());
- localZoneIds = new HashSet<>(zoneIds);
+ localZoneIds = zoneIds != null ? new HashSet<>(zoneIds) : new HashSet<>();
}
@VisibleForTesting
diff --git a/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
index a2ae9031d7b3..caabf9af35f6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
@@ -22,13 +22,14 @@ import android.content.Context;
import android.content.Intent;
import android.os.UserManager;
import android.provider.Settings;
+import android.text.TextUtils;
+
import androidx.annotation.VisibleForTesting;
-import androidx.preference.SwitchPreference;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
+import androidx.preference.SwitchPreference;
import androidx.preference.TwoStatePreference;
-import android.text.TextUtils;
import com.android.settingslib.core.ConfirmationDialogController;
@@ -59,8 +60,7 @@ public abstract class AbstractEnableAdbPreferenceController extends
@Override
public boolean isAvailable() {
- final UserManager um = mContext.getSystemService(UserManager.class);
- return um != null && (um.isAdminUser() || um.isDemoUser());
+ return mContext.getSystemService(UserManager.class).isAdminUser();
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/development/AbstractLogdSizePreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/development/AbstractLogdSizePreferenceController.java
index e2f6b7b87560..5a823976cd0e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/development/AbstractLogdSizePreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/development/AbstractLogdSizePreferenceController.java
@@ -19,6 +19,7 @@ package com.android.settingslib.development;
import android.content.Context;
import android.content.Intent;
import android.os.SystemProperties;
+
import androidx.annotation.VisibleForTesting;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.preference.ListPreference;
diff --git a/packages/SettingsLib/src/com/android/settingslib/development/AbstractLogpersistPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/development/AbstractLogpersistPreferenceController.java
index f277c16a1e2c..87226746668e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/development/AbstractLogpersistPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/development/AbstractLogpersistPreferenceController.java
@@ -22,12 +22,13 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.SystemProperties;
+import android.text.TextUtils;
+
import androidx.annotation.VisibleForTesting;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
-import android.text.TextUtils;
import com.android.settingslib.R;
import com.android.settingslib.core.ConfirmationDialogController;
diff --git a/packages/SettingsLib/src/com/android/settingslib/development/DeveloperOptionsPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/development/DeveloperOptionsPreferenceController.java
index 15d5522bbd7b..f757aa4e4dab 100644
--- a/packages/SettingsLib/src/com/android/settingslib/development/DeveloperOptionsPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/development/DeveloperOptionsPreferenceController.java
@@ -17,6 +17,7 @@
package com.android.settingslib.development;
import android.content.Context;
+
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
diff --git a/packages/SettingsLib/src/com/android/settingslib/development/DevelopmentSettingsEnabler.java b/packages/SettingsLib/src/com/android/settingslib/development/DevelopmentSettingsEnabler.java
index 8d186032bfef..b191f888aa1a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/development/DevelopmentSettingsEnabler.java
+++ b/packages/SettingsLib/src/com/android/settingslib/development/DevelopmentSettingsEnabler.java
@@ -21,6 +21,7 @@ import android.content.Intent;
import android.os.Build;
import android.os.UserManager;
import android.provider.Settings;
+
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
public class DevelopmentSettingsEnabler {
@@ -45,7 +46,7 @@ public class DevelopmentSettingsEnabler {
Build.TYPE.equals("eng") ? 1 : 0) != 0;
final boolean hasRestriction = um.hasUserRestriction(
UserManager.DISALLOW_DEBUGGING_FEATURES);
- final boolean isAdminOrDemo = um.isAdminUser() || um.isDemoUser();
- return isAdminOrDemo && !hasRestriction && settingEnabled;
+ final boolean isAdmin = um.isAdminUser();
+ return isAdmin && !hasRestriction && settingEnabled;
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/development/SystemPropPoker.java b/packages/SettingsLib/src/com/android/settingslib/development/SystemPropPoker.java
index dba22d0c6af3..3f16be184c41 100644
--- a/packages/SettingsLib/src/com/android/settingslib/development/SystemPropPoker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/development/SystemPropPoker.java
@@ -21,9 +21,10 @@ import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.util.Log;
+
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
-import android.util.Log;
public class SystemPropPoker {
private static final String TAG = "SystemPropPoker";
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractBluetoothAddressPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractBluetoothAddressPreferenceController.java
index 821b4d5da049..745591235dbb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractBluetoothAddressPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractBluetoothAddressPreferenceController.java
@@ -19,10 +19,11 @@ package com.android.settingslib.deviceinfo;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
+import android.text.TextUtils;
+
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
-import android.text.TextUtils;
import com.android.settingslib.R;
import com.android.settingslib.core.lifecycle.Lifecycle;
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractImsStatusPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractImsStatusPreferenceController.java
index 10260de289d3..a5f403690dab 100644
--- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractImsStatusPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractImsStatusPreferenceController.java
@@ -21,13 +21,14 @@ import android.content.Context;
import android.net.ConnectivityManager;
import android.net.wifi.WifiManager;
import android.os.PersistableBundle;
-import androidx.annotation.VisibleForTesting;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceScreen;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
import com.android.settingslib.R;
import com.android.settingslib.core.lifecycle.Lifecycle;
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractIpAddressPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractIpAddressPreferenceController.java
index 45cd86667f1e..24da72ea611a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractIpAddressPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractIpAddressPreferenceController.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.net.ConnectivityManager;
import android.net.LinkProperties;
import android.net.wifi.WifiManager;
+
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java
index 60b29fb8682f..d28792ef30de 100644
--- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java
@@ -18,10 +18,11 @@ package com.android.settingslib.deviceinfo;
import android.content.Context;
import android.os.Build;
+import android.text.TextUtils;
+
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
-import android.text.TextUtils;
import com.android.settingslib.core.AbstractPreferenceController;
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractUptimePreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractUptimePreferenceController.java
index 332a2a463b74..5f7226969699 100644
--- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractUptimePreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractUptimePreferenceController.java
@@ -20,10 +20,11 @@ import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
+import android.text.format.DateUtils;
+
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
-import android.text.format.DateUtils;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
@@ -88,7 +89,7 @@ public abstract class AbstractUptimePreferenceController extends AbstractPrefere
}
private void updateTimes() {
- mUptime.setSummary(DateUtils.formatDuration(SystemClock.elapsedRealtime()));
+ mUptime.setSummary(DateUtils.formatElapsedTime(SystemClock.elapsedRealtime() / 1000));
}
private static class MyHandler extends Handler {
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java
index 89d259507d8a..9699294df587 100644
--- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java
@@ -22,10 +22,11 @@ import android.net.ConnectivityManager;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.provider.Settings;
+import android.text.TextUtils;
+
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
-import android.text.TextUtils;
import com.android.settingslib.R;
import com.android.settingslib.core.lifecycle.Lifecycle;
@@ -38,6 +39,10 @@ public abstract class AbstractWifiMacAddressPreferenceController
@VisibleForTesting
static final String KEY_WIFI_MAC_ADDRESS = "wifi_mac_address";
+ @VisibleForTesting
+ static final int OFF = 0;
+ @VisibleForTesting
+ static final int ON = 1;
private static final String[] CONNECTIVITY_INTENTS = {
ConnectivityManager.CONNECTIVITY_ACTION,
@@ -80,13 +85,14 @@ public abstract class AbstractWifiMacAddressPreferenceController
protected void updateConnectivity() {
WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
final int macRandomizationMode = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED, 0);
+ Settings.Global.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED, OFF);
final String macAddress = wifiInfo == null ? null : wifiInfo.getMacAddress();
- if (TextUtils.isEmpty(macAddress)) {
- mWifiMacAddress.setSummary(R.string.status_unavailable);
- } else if (macRandomizationMode == 1 && WifiInfo.DEFAULT_MAC_ADDRESS.equals(macAddress)) {
+ if (macRandomizationMode == ON && WifiInfo.DEFAULT_MAC_ADDRESS.equals(macAddress)) {
mWifiMacAddress.setSummary(R.string.wifi_status_mac_randomized);
+ } else if (TextUtils.isEmpty(macAddress)
+ || WifiInfo.DEFAULT_MAC_ADDRESS.equals(macAddress)) {
+ mWifiMacAddress.setSummary(R.string.status_unavailable);
} else {
mWifiMacAddress.setSummary(macAddress);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java b/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java
index af8fd4c46a64..e0ca1ab0c07c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java
@@ -16,11 +16,8 @@
package com.android.settingslib.display;
-import com.android.settingslib.R;
-
import android.content.Context;
import android.content.res.Resources;
-import android.hardware.display.DisplayManager;
import android.os.AsyncTask;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -31,6 +28,8 @@ import android.view.Display;
import android.view.IWindowManager;
import android.view.WindowManagerGlobal;
+import com.android.settingslib.R;
+
import java.util.Arrays;
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java b/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java
index 54d1aba09ae3..274696bfec0e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java
@@ -16,6 +16,7 @@
package com.android.settingslib.drawable;
+import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.app.admin.DevicePolicyManager;
@@ -171,11 +172,13 @@ public class UserIconDrawable extends Drawable implements Drawable.Callback {
public UserIconDrawable setBadgeIfManagedUser(Context context, int userId) {
Drawable badge = null;
- boolean isManaged = context.getSystemService(DevicePolicyManager.class)
- .getProfileOwnerAsUser(userId) != null;
- if (isManaged) {
- badge = getDrawableForDisplayDensity(
- context, com.android.internal.R.drawable.ic_corp_badge_case);
+ if (userId != UserHandle.USER_NULL) {
+ boolean isManaged = context.getSystemService(DevicePolicyManager.class)
+ .getProfileOwnerAsUser(userId) != null;
+ if (isManaged) {
+ badge = getDrawableForDisplayDensity(
+ context, com.android.internal.R.drawable.ic_corp_badge_case);
+ }
}
return setBadge(badge);
}
@@ -251,11 +254,8 @@ public class UserIconDrawable extends Drawable implements Drawable.Callback {
mPaint.setColorFilter(null);
} else {
int color = mTintColor.getColorForState(getState(), mTintColor.getDefaultColor());
- if (mPaint.getColorFilter() == null) {
+ if (shouldUpdateColorFilter(color, mTintMode)) {
mPaint.setColorFilter(new PorterDuffColorFilter(color, mTintMode));
- } else {
- ((PorterDuffColorFilter) mPaint.getColorFilter()).setMode(mTintMode);
- ((PorterDuffColorFilter) mPaint.getColorFilter()).setColor(color);
}
}
@@ -263,6 +263,18 @@ public class UserIconDrawable extends Drawable implements Drawable.Callback {
}
}
+ private boolean shouldUpdateColorFilter(@ColorInt int color, PorterDuff.Mode mode) {
+ ColorFilter colorFilter = mPaint.getColorFilter();
+ if (colorFilter instanceof PorterDuffColorFilter) {
+ PorterDuffColorFilter porterDuffColorFilter = (PorterDuffColorFilter) colorFilter;
+ int currentColor = porterDuffColorFilter.getColor();
+ PorterDuff.Mode currentMode = porterDuffColorFilter.getMode();
+ return currentColor != color || currentMode != mode;
+ } else {
+ return true;
+ }
+ }
+
@Override
public void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
index dd8bfda43ff9..92fd86894d56 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
@@ -51,6 +51,8 @@ public final class CategoryKey {
public static final String CATEGORY_GESTURES = "com.android.settings.category.ia.gestures";
public static final String CATEGORY_NIGHT_DISPLAY =
"com.android.settings.category.ia.night_display";
+ public static final String CATEGORY_PRIVACY =
+ "com.android.settings.category.ia.privacy";
public static final Map<String, String> KEY_COMPAT_MAP;
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java
deleted file mode 100644
index d9605367dc56..000000000000
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java
+++ /dev/null
@@ -1,238 +0,0 @@
-/**
- * Copyright (C) 2016 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.settingslib.drawer;
-
-import android.content.ComponentName;
-import android.content.Context;
-import androidx.annotation.VisibleForTesting;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Pair;
-
-import com.android.settingslib.applications.InterestingConfigChanges;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-
-import static java.lang.String.CASE_INSENSITIVE_ORDER;
-
-public class CategoryManager {
-
- private static final String TAG = "CategoryManager";
-
- private static CategoryManager sInstance;
- private final InterestingConfigChanges mInterestingConfigChanges;
-
- // Tile cache (key: <packageName, activityName>, value: tile)
- private final Map<Pair<String, String>, Tile> mTileByComponentCache;
-
- // Tile cache (key: category key, value: category)
- private final Map<String, DashboardCategory> mCategoryByKeyMap;
-
- private List<DashboardCategory> mCategories;
- private String mExtraAction;
-
- public static CategoryManager get(Context context) {
- return get(context, null);
- }
-
- public static CategoryManager get(Context context, String action) {
- if (sInstance == null) {
- sInstance = new CategoryManager(context, action);
- }
- return sInstance;
- }
-
- CategoryManager(Context context, String action) {
- mTileByComponentCache = new ArrayMap<>();
- mCategoryByKeyMap = new ArrayMap<>();
- mInterestingConfigChanges = new InterestingConfigChanges();
- mInterestingConfigChanges.applyNewConfig(context.getResources());
- mExtraAction = action;
- }
-
- public synchronized DashboardCategory getTilesByCategory(Context context, String categoryKey) {
- return getTilesByCategory(context, categoryKey, TileUtils.SETTING_PKG);
- }
-
- public synchronized DashboardCategory getTilesByCategory(Context context, String categoryKey,
- String settingPkg) {
- tryInitCategories(context, settingPkg);
-
- return mCategoryByKeyMap.get(categoryKey);
- }
-
- public synchronized List<DashboardCategory> getCategories(Context context) {
- return getCategories(context, TileUtils.SETTING_PKG);
- }
-
- public synchronized List<DashboardCategory> getCategories(Context context, String settingPkg) {
- tryInitCategories(context, settingPkg);
- return mCategories;
- }
-
- public synchronized void reloadAllCategories(Context context, String settingPkg) {
- final boolean forceClearCache = mInterestingConfigChanges.applyNewConfig(
- context.getResources());
- mCategories = null;
- tryInitCategories(context, forceClearCache, settingPkg);
- }
-
- public synchronized void updateCategoryFromBlacklist(Set<ComponentName> tileBlacklist) {
- if (mCategories == null) {
- Log.w(TAG, "Category is null, skipping blacklist update");
- }
- for (int i = 0; i < mCategories.size(); i++) {
- DashboardCategory category = mCategories.get(i);
- for (int j = 0; j < category.getTilesCount(); j++) {
- Tile tile = category.getTile(j);
- if (tileBlacklist.contains(tile.intent.getComponent())) {
- category.removeTile(j--);
- }
- }
- }
- }
-
- private synchronized void tryInitCategories(Context context, String settingPkg) {
- // Keep cached tiles by default. The cache is only invalidated when InterestingConfigChange
- // happens.
- tryInitCategories(context, false /* forceClearCache */, settingPkg);
- }
-
- private synchronized void tryInitCategories(Context context, boolean forceClearCache,
- String settingPkg) {
- if (mCategories == null) {
- if (forceClearCache) {
- mTileByComponentCache.clear();
- }
- mCategoryByKeyMap.clear();
- mCategories = TileUtils.getCategories(context, mTileByComponentCache,
- false /* categoryDefinedInManifest */, mExtraAction, settingPkg);
- for (DashboardCategory category : mCategories) {
- mCategoryByKeyMap.put(category.key, category);
- }
- backwardCompatCleanupForCategory(mTileByComponentCache, mCategoryByKeyMap);
- sortCategories(context, mCategoryByKeyMap);
- filterDuplicateTiles(mCategoryByKeyMap);
- }
- }
-
- @VisibleForTesting
- synchronized void backwardCompatCleanupForCategory(
- Map<Pair<String, String>, Tile> tileByComponentCache,
- Map<String, DashboardCategory> categoryByKeyMap) {
- // A package can use a) CategoryKey, b) old category keys, c) both.
- // Check if a package uses old category key only.
- // If yes, map them to new category key.
-
- // Build a package name -> tile map first.
- final Map<String, List<Tile>> packageToTileMap = new HashMap<>();
- for (Entry<Pair<String, String>, Tile> tileEntry : tileByComponentCache.entrySet()) {
- final String packageName = tileEntry.getKey().first;
- List<Tile> tiles = packageToTileMap.get(packageName);
- if (tiles == null) {
- tiles = new ArrayList<>();
- packageToTileMap.put(packageName, tiles);
- }
- tiles.add(tileEntry.getValue());
- }
-
- for (Entry<String, List<Tile>> entry : packageToTileMap.entrySet()) {
- final List<Tile> tiles = entry.getValue();
- // Loop map, find if all tiles from same package uses old key only.
- boolean useNewKey = false;
- boolean useOldKey = false;
- for (Tile tile : tiles) {
- if (CategoryKey.KEY_COMPAT_MAP.containsKey(tile.category)) {
- useOldKey = true;
- } else {
- useNewKey = true;
- break;
- }
- }
- // Uses only old key, map them to new keys one by one.
- if (useOldKey && !useNewKey) {
- for (Tile tile : tiles) {
- final String newCategoryKey = CategoryKey.KEY_COMPAT_MAP.get(tile.category);
- tile.category = newCategoryKey;
- // move tile to new category.
- DashboardCategory newCategory = categoryByKeyMap.get(newCategoryKey);
- if (newCategory == null) {
- newCategory = new DashboardCategory();
- categoryByKeyMap.put(newCategoryKey, newCategory);
- }
- newCategory.addTile(tile);
- }
- }
- }
- }
-
- /**
- * Sort the tiles injected from all apps such that if they have the same priority value,
- * they wil lbe sorted by package name.
- * <p/>
- * A list of tiles are considered sorted when their priority value decreases in a linear
- * scan.
- */
- @VisibleForTesting
- synchronized void sortCategories(Context context,
- Map<String, DashboardCategory> categoryByKeyMap) {
- for (Entry<String, DashboardCategory> categoryEntry : categoryByKeyMap.entrySet()) {
- categoryEntry.getValue().sortTiles(context.getPackageName());
- }
- }
-
- /**
- * Filter out duplicate tiles from category. Duplicate tiles are the ones pointing to the
- * same intent.
- */
- @VisibleForTesting
- synchronized void filterDuplicateTiles(Map<String, DashboardCategory> categoryByKeyMap) {
- for (Entry<String, DashboardCategory> categoryEntry : categoryByKeyMap.entrySet()) {
- final DashboardCategory category = categoryEntry.getValue();
- final int count = category.getTilesCount();
- final Set<ComponentName> components = new ArraySet<>();
- for (int i = count - 1; i >= 0; i--) {
- final Tile tile = category.getTile(i);
- if (tile.intent == null) {
- continue;
- }
- final ComponentName tileComponent = tile.intent.getComponent();
- if (components.contains(tileComponent)) {
- category.removeTile(i);
- } else {
- components.add(tileComponent);
- }
- }
- }
- }
-
- /**
- * Sort priority value for tiles within a single {@code DashboardCategory}.
- *
- * @see #sortCategories(Context, Map)
- */
- private synchronized void sortCategoriesForExternalTiles(Context context,
- DashboardCategory dashboardCategory) {
- dashboardCategory.sortTiles(context.getPackageName());
-
- }
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java b/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java
index 3a03644b6226..a3dda658bec7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -18,55 +18,39 @@ package com.android.settingslib.drawer;
import static java.lang.String.CASE_INSENSITIVE_ORDER;
-import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
-import android.util.Log;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Comparator;
import java.util.List;
public class DashboardCategory implements Parcelable {
- private static final String TAG = "DashboardCategory";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- /**
- * Title of the category that is shown to the user.
- */
- public CharSequence title;
-
/**
* Key used for placing external tiles.
*/
- public String key;
-
- /**
- * Used to control display order.
- */
- public int priority;
+ public final String key;
/**
* List of the category's children
*/
private List<Tile> mTiles = new ArrayList<>();
- DashboardCategory(DashboardCategory in) {
- if (in != null) {
- title = in.title;
- key = in.key;
- priority = in.priority;
- for (Tile tile : in.mTiles) {
- mTiles.add(tile);
- }
- }
+ public DashboardCategory(String key) {
+ this.key = key;
}
- public DashboardCategory() {
- // Empty
+ DashboardCategory(Parcel in) {
+ key = in.readString();
+
+ final int count = in.readInt();
+
+ for (int n = 0; n < count; n++) {
+ Tile tile = Tile.CREATOR.createFromParcel(in);
+ mTiles.add(tile);
+ }
}
/**
@@ -87,14 +71,6 @@ public class DashboardCategory implements Parcelable {
mTiles.add(tile);
}
- public synchronized void addTile(int n, Tile tile) {
- mTiles.add(n, tile);
- }
-
- public synchronized void removeTile(Tile tile) {
- mTiles.remove(tile);
- }
-
public synchronized void removeTile(int n) {
mTiles.remove(n);
}
@@ -107,44 +83,29 @@ public class DashboardCategory implements Parcelable {
return mTiles.get(n);
}
- public synchronized boolean containsComponent(ComponentName component) {
- for (Tile tile : mTiles) {
- if (TextUtils.equals(tile.intent.getComponent().getClassName(),
- component.getClassName())) {
- if (DEBUG) {
- Log.d(TAG, "category " + key + "contains component" + component);
- }
- return true;
- }
- }
- if (DEBUG) {
- Log.d(TAG, "category " + key + " does not contain component" + component);
- }
- return false;
- }
-
/**
* Sort priority value for tiles in this category.
*/
public void sortTiles() {
- Collections.sort(mTiles, TILE_COMPARATOR);
+ Collections.sort(mTiles, Tile.TILE_COMPARATOR);
}
/**
* Sort priority value and package name for tiles in this category.
*/
public synchronized void sortTiles(String skipPackageName) {
- // Sort mTiles based on [priority, package within priority]
+ // Sort mTiles based on [order, package within order]
Collections.sort(mTiles, (tile1, tile2) -> {
- final String package1 = tile1.intent.getComponent().getPackageName();
- final String package2 = tile2.intent.getComponent().getPackageName();
- final int packageCompare = CASE_INSENSITIVE_ORDER.compare(package1, package2);
- // First sort by priority
- final int priorityCompare = tile2.priority - tile1.priority;
- if (priorityCompare != 0) {
- return priorityCompare;
+ // First sort by order
+ final int orderCompare = tile2.getOrder() - tile1.getOrder();
+ if (orderCompare != 0) {
+ return orderCompare;
}
+
// Then sort by package name, skip package take precedence
+ final String package1 = tile1.getPackageName();
+ final String package2 = tile2.getPackageName();
+ final int packageCompare = CASE_INSENSITIVE_ORDER.compare(package1, package2);
if (packageCompare != 0) {
if (TextUtils.equals(package1, skipPackageName)) {
return -1;
@@ -164,9 +125,7 @@ public class DashboardCategory implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
- TextUtils.writeToParcel(title, dest, flags);
dest.writeString(key);
- dest.writeInt(priority);
final int count = mTiles.size();
dest.writeInt(count);
@@ -177,23 +136,6 @@ public class DashboardCategory implements Parcelable {
}
}
- public void readFromParcel(Parcel in) {
- title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
- key = in.readString();
- priority = in.readInt();
-
- final int count = in.readInt();
-
- for (int n = 0; n < count; n++) {
- Tile tile = Tile.CREATOR.createFromParcel(in);
- mTiles.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);
@@ -204,12 +146,4 @@ public class DashboardCategory implements Parcelable {
}
};
- public static final Comparator<Tile> TILE_COMPARATOR =
- new Comparator<Tile>() {
- @Override
- public int compare(Tile lhs, Tile rhs) {
- return rhs.priority - lhs.priority;
- }
- };
-
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/ProfileSelectDialog.java b/packages/SettingsLib/src/com/android/settingslib/drawer/ProfileSelectDialog.java
deleted file mode 100644
index c79b1466d606..000000000000
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/ProfileSelectDialog.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.settingslib.drawer;
-
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.DialogFragment;
-import android.app.FragmentManager;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.Log;
-
-import java.util.List;
-
-public class ProfileSelectDialog extends DialogFragment implements OnClickListener {
-
- private static final String TAG = "ProfileSelectDialog";
- private static final String ARG_SELECTED_TILE = "selectedTile";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- private Tile mSelectedTile;
-
- public static void show(FragmentManager manager, Tile tile) {
- ProfileSelectDialog dialog = new ProfileSelectDialog();
- Bundle args = new Bundle();
- args.putParcelable(ARG_SELECTED_TILE, tile);
- dialog.setArguments(args);
- dialog.show(manager, "select_profile");
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mSelectedTile = getArguments().getParcelable(ARG_SELECTED_TILE);
- }
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- Context context = getActivity();
- AlertDialog.Builder builder = new AlertDialog.Builder(context);
- UserAdapter adapter = UserAdapter.createUserAdapter(UserManager.get(context), context,
- mSelectedTile.userHandle);
- builder.setTitle(com.android.settingslib.R.string.choose_profile)
- .setAdapter(adapter, this);
-
- return builder.create();
- }
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- UserHandle user = mSelectedTile.userHandle.get(which);
- // Show menu on top level items.
- mSelectedTile.intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
- getActivity().startActivityAsUser(mSelectedTile.intent, user);
- }
-
- public static void updateUserHandlesIfNeeded(Context context, Tile tile) {
- List<UserHandle> userHandles = tile.userHandle;
- if (tile.userHandle == null || tile.userHandle.size() <= 1) {
- return;
- }
- final UserManager userManager = UserManager.get(context);
- for (int i = userHandles.size() - 1; i >= 0; i--) {
- if (userManager.getUserInfo(userHandles.get(i).getIdentifier()) == null) {
- if (DEBUG) {
- Log.d(TAG, "Delete the user: " + userHandles.get(i).getIdentifier());
- }
- userHandles.remove(i);
- }
- }
- }
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
deleted file mode 100644
index 68ead09d9d57..000000000000
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
+++ /dev/null
@@ -1,214 +0,0 @@
-/**
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.settingslib.drawer;
-
-import android.annotation.LayoutRes;
-import android.annotation.Nullable;
-import android.app.Activity;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.content.res.TypedArray;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.util.ArraySet;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.Window;
-import android.view.WindowManager.LayoutParams;
-import android.widget.FrameLayout;
-import android.widget.Toolbar;
-
-import com.android.settingslib.R;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class SettingsDrawerActivity extends Activity {
-
- protected static final boolean DEBUG_TIMING = false;
- private static final String TAG = "SettingsDrawerActivity";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- public static final String EXTRA_SHOW_MENU = "show_drawer_menu";
-
- // Serves as a temporary list of tiles to ignore until we heard back from the PM that they
- // are disabled.
- private static ArraySet<ComponentName> sTileBlacklist = new ArraySet<>();
-
- private final PackageReceiver mPackageReceiver = new PackageReceiver();
- private final List<CategoryListener> mCategoryListeners = new ArrayList<>();
-
- private FrameLayout mContentHeaderContainer;
-
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- long startTime = System.currentTimeMillis();
-
- TypedArray theme = getTheme().obtainStyledAttributes(android.R.styleable.Theme);
- if (!theme.getBoolean(android.R.styleable.Theme_windowNoTitle, false)) {
- getWindow().addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
- requestWindowFeature(Window.FEATURE_NO_TITLE);
- }
- super.setContentView(R.layout.settings_with_drawer);
- mContentHeaderContainer = findViewById(R.id.content_header_container);
-
- Toolbar toolbar = findViewById(R.id.action_bar);
- if (theme.getBoolean(android.R.styleable.Theme_windowNoTitle, false)) {
- toolbar.setVisibility(View.GONE);
- return;
- }
- setActionBar(toolbar);
-
- if (DEBUG_TIMING) {
- Log.d(TAG, "onCreate took " + (System.currentTimeMillis() - startTime)
- + " ms");
- }
- }
-
- @Override
- public boolean onNavigateUp() {
- if (!super.onNavigateUp()) {
- finish();
- }
- return true;
- }
-
- @Override
- protected 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");
- registerReceiver(mPackageReceiver, filter);
-
- new CategoriesUpdateTask().execute();
- }
-
- @Override
- protected void onPause() {
- unregisterReceiver(mPackageReceiver);
- super.onPause();
- }
-
- public void addCategoryListener(CategoryListener listener) {
- mCategoryListeners.add(listener);
- }
-
- public void remCategoryListener(CategoryListener listener) {
- mCategoryListeners.remove(listener);
- }
-
- @Override
- public void setContentView(@LayoutRes int layoutResID) {
- final ViewGroup parent = findViewById(R.id.content_frame);
- if (parent != null) {
- parent.removeAllViews();
- }
- LayoutInflater.from(this).inflate(layoutResID, parent);
- }
-
- @Override
- public void setContentView(View view) {
- ((ViewGroup) findViewById(R.id.content_frame)).addView(view);
- }
-
- @Override
- public void setContentView(View view, ViewGroup.LayoutParams params) {
- ((ViewGroup) findViewById(R.id.content_frame)).addView(view, params);
- }
-
- private void onCategoriesChanged() {
- final int N = mCategoryListeners.size();
- for (int i = 0; i < N; i++) {
- mCategoryListeners.get(i).onCategoriesChanged();
- }
- }
-
- /**
- * @return whether or not the enabled state actually changed.
- */
- public boolean setTileEnabled(ComponentName component, boolean enabled) {
- PackageManager pm = getPackageManager();
- int state = pm.getComponentEnabledSetting(component);
- boolean isEnabled = state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
- if (isEnabled != enabled || state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
- if (enabled) {
- sTileBlacklist.remove(component);
- } else {
- sTileBlacklist.add(component);
- }
- pm.setComponentEnabledSetting(component, enabled
- ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
- : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
- PackageManager.DONT_KILL_APP);
- return true;
- }
- return false;
- }
-
- /**
- * Updates dashboard categories. Only necessary to call this after setTileEnabled
- */
- public void updateCategories() {
- new CategoriesUpdateTask().execute();
- }
-
- public String getSettingPkg() {
- return TileUtils.SETTING_PKG;
- }
-
- public interface CategoryListener {
- void onCategoriesChanged();
- }
-
- private class CategoriesUpdateTask extends AsyncTask<Void, Void, Void> {
-
- private final CategoryManager mCategoryManager;
-
- public CategoriesUpdateTask() {
- mCategoryManager = CategoryManager.get(SettingsDrawerActivity.this);
- }
-
- @Override
- protected Void doInBackground(Void... params) {
- mCategoryManager.reloadAllCategories(SettingsDrawerActivity.this, getSettingPkg());
- return null;
- }
-
- @Override
- protected void onPostExecute(Void result) {
- mCategoryManager.updateCategoryFromBlacklist(sTileBlacklist);
- onCategoriesChanged();
- }
- }
-
- private class PackageReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- new CategoriesUpdateTask().execute();
- }
- }
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/Tile.java b/packages/SettingsLib/src/com/android/settingslib/drawer/Tile.java
index f1d43bfe4096..a6b24104fa90 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/Tile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/Tile.java
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -16,157 +16,321 @@
package com.android.settingslib.drawer;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_ORDER;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_PROFILE;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_URI;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY_URI;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
+import static com.android.settingslib.drawer.TileUtils.PROFILE_ALL;
+import static com.android.settingslib.drawer.TileUtils.PROFILE_PRIMARY;
+
+import android.content.Context;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
import android.text.TextUtils;
-import android.widget.RemoteViews;
+import android.util.Log;
import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
/**
* Description of a single dashboard tile that the user can select.
*/
public class Tile implements Parcelable {
- /**
- * Title of the tile that is shown to the user.
- * @attr ref android.R.styleable#PreferenceHeader_title
- */
- public CharSequence title;
+ private static final String TAG = "Tile";
/**
- * Optional summary describing what this tile controls.
- * @attr ref android.R.styleable#PreferenceHeader_summary
+ * Optional list of user handles which the intent should be launched on.
*/
- public CharSequence summary;
+ public ArrayList<UserHandle> userHandle = new ArrayList<>();
+
+ private final String mActivityPackage;
+ private final String mActivityName;
+ private final Intent mIntent;
+
+ private ActivityInfo mActivityInfo;
+ private CharSequence mSummaryOverride;
+ private Bundle mMetaData;
+ private String mCategory;
+
+ public Tile(ActivityInfo activityInfo, String category) {
+ mActivityInfo = activityInfo;
+ mActivityPackage = mActivityInfo.packageName;
+ mActivityName = mActivityInfo.name;
+ mMetaData = activityInfo.metaData;
+ mCategory = category;
+ mIntent = new Intent().setClassName(mActivityPackage, mActivityName);
+ }
+
+ Tile(Parcel in) {
+ mActivityPackage = in.readString();
+ mActivityName = in.readString();
+ mIntent = new Intent().setClassName(mActivityPackage, mActivityName);
+ final int N = in.readInt();
+ for (int i = 0; i < N; i++) {
+ userHandle.add(UserHandle.CREATOR.createFromParcel(in));
+ }
+ mCategory = in.readString();
+ mMetaData = in.readBundle();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mActivityPackage);
+ dest.writeString(mActivityName);
+ final int N = userHandle.size();
+ dest.writeInt(N);
+ for (int i = 0; i < N; i++) {
+ userHandle.get(i).writeToParcel(dest, flags);
+ }
+ dest.writeString(mCategory);
+ dest.writeBundle(mMetaData);
+ }
+
+ public int getId() {
+ return Objects.hash(mActivityPackage, mActivityName);
+ }
+
+ public String getDescription() {
+ return mActivityPackage + "/" + mActivityName;
+ }
+
+ public String getPackageName() {
+ return mActivityPackage;
+ }
/**
- * Optional icon to show for this tile.
- * @attr ref android.R.styleable#PreferenceHeader_icon
+ * Intent to launch when the preference is selected.
*/
- public Icon icon;
+ public Intent getIntent() {
+ return mIntent;
+ }
/**
- * Whether the icon can be tinted. This should be set to true for monochrome (single-color)
- * icons that can be tinted to match the design.
+ * Category in which the tile should be placed.
*/
- public boolean isIconTintable;
+ public String getCategory() {
+ return mCategory;
+ }
+
+ public void setCategory(String newCategoryKey) {
+ mCategory = newCategoryKey;
+ }
/**
- * Intent to launch when the preference is selected.
+ * Priority of this tile, used for display ordering.
*/
- public Intent intent;
+ public int getOrder() {
+ if (hasOrder()) {
+ return mMetaData.getInt(META_DATA_KEY_ORDER);
+ } else {
+ return 0;
+ }
+ }
+
+ public boolean hasOrder() {
+ return mMetaData.containsKey(META_DATA_KEY_ORDER)
+ && mMetaData.get(META_DATA_KEY_ORDER) instanceof Integer;
+ }
/**
- * Optional list of user handles which the intent should be launched on.
+ * Title of the tile that is shown to the user.
*/
- public ArrayList<UserHandle> userHandle = new ArrayList<>();
+ public CharSequence getTitle(Context context) {
+ CharSequence title = null;
+ final PackageManager packageManager = context.getPackageManager();
+ if (mMetaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
+ if (mMetaData.get(META_DATA_PREFERENCE_TITLE) instanceof Integer) {
+ try {
+ final Resources res =
+ packageManager.getResourcesForApplication(mActivityPackage);
+ title = res.getString(mMetaData.getInt(META_DATA_PREFERENCE_TITLE));
+ } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
+ Log.d(TAG, "Couldn't find info", e);
+ }
+ } else {
+ title = mMetaData.getString(META_DATA_PREFERENCE_TITLE);
+ }
+ }
+ // Set the preference title to the activity's label if no
+ // meta-data is found
+ if (title == null) {
+ title = getActivityInfo(context).loadLabel(packageManager);
+ }
+ return title;
+ }
/**
- * Optional additional data for use by subclasses of the activity
+ * Returns the raw metadata for summary, this is used for comparing 2 summary text without
+ * loading the real string.
*/
- public Bundle extras;
+ public String getSummaryReference() {
+ if (mSummaryOverride != null) {
+ return mSummaryOverride.toString();
+ }
+ if (mMetaData != null && mMetaData.containsKey(META_DATA_PREFERENCE_SUMMARY)) {
+ return mMetaData.get(META_DATA_PREFERENCE_SUMMARY).toString();
+ }
+ return null;
+ }
/**
- * Category in which the tile should be placed.
+ * Overrides the summary. This can happen when injected tile wants to provide dynamic summary.
*/
- public String category;
+ public void overrideSummary(CharSequence summaryOverride) {
+ mSummaryOverride = summaryOverride;
+ }
/**
- * Priority of the intent filter that created this tile, used for display ordering.
+ * Optional summary describing what this tile controls.
*/
- public int priority;
+ public CharSequence getSummary(Context context) {
+ if (mSummaryOverride != null) {
+ return mSummaryOverride;
+ }
+ CharSequence summary = null;
+ final PackageManager packageManager = context.getPackageManager();
+ if (mMetaData != null) {
+ if (mMetaData.containsKey(META_DATA_PREFERENCE_SUMMARY_URI)) {
+ return null;
+ }
+ if (mMetaData.containsKey(META_DATA_PREFERENCE_SUMMARY)) {
+ if (mMetaData.get(META_DATA_PREFERENCE_SUMMARY) instanceof Integer) {
+ try {
+ final Resources res =
+ packageManager.getResourcesForApplication(mActivityPackage);
+ summary = res.getString(mMetaData.getInt(META_DATA_PREFERENCE_SUMMARY));
+ } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
+ Log.d(TAG, "Couldn't find info", e);
+ }
+ } else {
+ summary = mMetaData.getString(META_DATA_PREFERENCE_SUMMARY);
+ }
+ }
+ }
+ return summary;
+ }
+
+ public void setMetaData(Bundle metaData) {
+ mMetaData = metaData;
+ }
/**
* The metaData from the activity that defines this tile.
*/
- public Bundle metaData;
+ public Bundle getMetaData() {
+ return mMetaData;
+ }
/**
* Optional key to use for this tile.
*/
- public String key;
-
- /**
- * Optional remote view which will be displayed instead of the regular title-summary item.
- */
- public RemoteViews remoteViews;
-
- public Tile() {
- // Empty
+ public String getKey(Context context) {
+ if (!hasKey()) {
+ return null;
+ }
+ if (mMetaData.get(META_DATA_PREFERENCE_KEYHINT) instanceof Integer) {
+ return context.getResources().getString(mMetaData.getInt(META_DATA_PREFERENCE_KEYHINT));
+ } else {
+ return mMetaData.getString(META_DATA_PREFERENCE_KEYHINT);
+ }
}
- @Override
- public int describeContents() {
- return 0;
+ public boolean hasKey() {
+ return mMetaData != null && mMetaData.containsKey(META_DATA_PREFERENCE_KEYHINT);
}
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- TextUtils.writeToParcel(title, dest, flags);
- TextUtils.writeToParcel(summary, dest, flags);
- if (icon != null) {
- dest.writeByte((byte) 1);
- icon.writeToParcel(dest, flags);
- } else {
- dest.writeByte((byte) 0);
+ /**
+ * Optional icon to show for this tile.
+ *
+ * @attr ref android.R.styleable#PreferenceHeader_icon
+ */
+ public Icon getIcon(Context context) {
+ if (context == null || mMetaData == null) {
+ return null;
}
- if (intent != null) {
- dest.writeByte((byte) 1);
- intent.writeToParcel(dest, flags);
- } else {
- dest.writeByte((byte) 0);
+
+ int iconResId = mMetaData.getInt(META_DATA_PREFERENCE_ICON);
+ // Set the icon
+ if (iconResId == 0) {
+ // Only fallback to activityinfo.icon if metadata does not contain ICON_URI.
+ // ICON_URI should be loaded in app UI when need the icon object. Handling IPC at this
+ // level is too complex because we don't have a strong threading contract for this class
+ if (!mMetaData.containsKey(META_DATA_PREFERENCE_ICON_URI)) {
+ iconResId = getActivityInfo(context).icon;
+ }
}
- final int N = userHandle.size();
- dest.writeInt(N);
- for (int i = 0; i < N; i++) {
- userHandle.get(i).writeToParcel(dest, flags);
+ if (iconResId != 0) {
+ return Icon.createWithResource(getActivityInfo(context).packageName, iconResId);
+ } else {
+ return null;
}
- dest.writeBundle(extras);
- dest.writeString(category);
- dest.writeInt(priority);
- dest.writeBundle(metaData);
- dest.writeString(key);
- dest.writeParcelable(remoteViews, flags);
- dest.writeBoolean(isIconTintable);
}
- public void readFromParcel(Parcel in) {
- title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
- summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
- if (in.readByte() != 0) {
- icon = Icon.CREATOR.createFromParcel(in);
- }
- if (in.readByte() != 0) {
- intent = Intent.CREATOR.createFromParcel(in);
- }
- final int N = in.readInt();
- for (int i = 0; i < N; i++) {
- userHandle.add(UserHandle.CREATOR.createFromParcel(in));
+ /**
+ * Whether the icon can be tinted. This is true when icon needs to be monochrome (single-color)
+ */
+ public boolean isIconTintable(Context context) {
+ if (mMetaData != null
+ && mMetaData.containsKey(TileUtils.META_DATA_PREFERENCE_ICON_TINTABLE)) {
+ return mMetaData.getBoolean(TileUtils.META_DATA_PREFERENCE_ICON_TINTABLE);
}
- extras = in.readBundle();
- category = in.readString();
- priority = in.readInt();
- metaData = in.readBundle();
- key = in.readString();
- remoteViews = in.readParcelable(RemoteViews.class.getClassLoader());
- isIconTintable = in.readBoolean();
+ final String pkgName = context.getPackageName();
+ // If this drawable is coming from outside Settings, tint it to match the color.
+ final ActivityInfo activityInfo = getActivityInfo(context);
+ return activityInfo != null
+ && !TextUtils.equals(pkgName, activityInfo.packageName);
}
- Tile(Parcel in) {
- readFromParcel(in);
+ private ActivityInfo getActivityInfo(Context context) {
+ if (mActivityInfo == null) {
+ final PackageManager pm = context.getApplicationContext().getPackageManager();
+ final Intent intent = new Intent().setClassName(mActivityPackage, mActivityName);
+ final List<ResolveInfo> infoList =
+ pm.queryIntentActivities(intent, PackageManager.GET_META_DATA);
+ if (infoList != null && !infoList.isEmpty()) {
+ mActivityInfo = infoList.get(0).activityInfo;
+ }
+ }
+ return mActivityInfo;
}
public static final Creator<Tile> CREATOR = new Creator<Tile>() {
public Tile createFromParcel(Parcel source) {
return new Tile(source);
}
+
public Tile[] newArray(int size) {
return new Tile[size];
}
};
+
+ public boolean isPrimaryProfileOnly() {
+ String profile = mMetaData != null ?
+ mMetaData.getString(META_DATA_KEY_PROFILE) : PROFILE_ALL;
+ profile = (profile != null ? profile : PROFILE_ALL);
+ return TextUtils.equals(profile, PROFILE_PRIMARY);
+ }
+
+ public static final Comparator<Tile> TILE_COMPARATOR =
+ (lhs, rhs) -> rhs.getOrder() - lhs.getOrder();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
index 0f0e4e57886c..67cfe6bfd18a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
@@ -20,11 +20,8 @@ import android.content.Context;
import android.content.IContentProvider;
import android.content.Intent;
import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Bundle;
import android.os.RemoteException;
@@ -34,21 +31,21 @@ import android.provider.Settings.Global;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
-import android.widget.RemoteViews;
+
+import androidx.annotation.VisibleForTesting;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TileUtils {
- private static final boolean DEBUG = false;
private static final boolean DEBUG_TIMING = false;
private static final String LOG_TAG = "TileUtils";
+ @VisibleForTesting
+ static final String SETTING_PKG = "com.android.settings";
/**
* Settings will search for system activities of this action and add them as a top level
@@ -65,21 +62,17 @@ public class TileUtils {
*
* <p>A summary my be defined by meta-data named {@link #META_DATA_PREFERENCE_SUMMARY}
*/
- public static final String EXTRA_SETTINGS_ACTION =
- "com.android.settings.action.EXTRA_SETTINGS";
+ public static final String EXTRA_SETTINGS_ACTION = "com.android.settings.action.EXTRA_SETTINGS";
/**
* @See {@link #EXTRA_SETTINGS_ACTION}.
*/
- private static final String IA_SETTINGS_ACTION =
- "com.android.settings.action.IA_SETTINGS";
-
+ public static final String IA_SETTINGS_ACTION = "com.android.settings.action.IA_SETTINGS";
/**
* Same as #EXTRA_SETTINGS_ACTION but used for the platform Settings activities.
*/
- private static final String SETTINGS_ACTION =
- "com.android.settings.action.SETTINGS";
+ private static final String SETTINGS_ACTION = "com.android.settings.action.SETTINGS";
private static final String OPERATOR_SETTINGS =
"com.android.settings.OPERATOR_APPLICATION_SETTING";
@@ -96,11 +89,7 @@ public class TileUtils {
/**
* The key used to get the category from metadata of activities of action
* {@link #EXTRA_SETTINGS_ACTION}
- * The value must be one of:
- * <li>com.android.settings.category.wireless</li>
- * <li>com.android.settings.category.device</li>
- * <li>com.android.settings.category.personal</li>
- * <li>com.android.settings.category.system</li>
+ * The value must be from {@link CategoryKey}.
*/
private static final String EXTRA_CATEGORY_KEY = "com.android.settings.category";
@@ -108,7 +97,7 @@ public class TileUtils {
* The key used to get the package name of the icon resource for the preference.
*/
private static final String EXTRA_PREFERENCE_ICON_PACKAGE =
- "com.android.settings.icon_package";
+ "com.android.settings.icon_package";
/**
* Name of the meta-data item that should be set in the AndroidManifest.xml
@@ -117,6 +106,12 @@ public class TileUtils {
public static final String META_DATA_PREFERENCE_KEYHINT = "com.android.settings.keyhint";
/**
+ * Order of the item that should be displayed on screen. Bigger value items displays closer on
+ * top.
+ */
+ public static final String META_DATA_KEY_ORDER = "com.android.settings.order";
+
+ /**
* 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.
*/
@@ -128,6 +123,12 @@ public class TileUtils {
*/
public static final String META_DATA_PREFERENCE_ICON_BACKGROUND_HINT =
"com.android.settings.bg.hint";
+ /**
+ * Name of the meta-data item that should be set in the AndroidManifest.xml
+ * to specify the icon background color as raw ARGB.
+ */
+ public static final String META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB =
+ "com.android.settings.bg.argb";
/**
* Name of the meta-data item that should be set in the AndroidManifest.xml
@@ -172,49 +173,38 @@ public class TileUtils {
"com.android.settings.summary_uri";
/**
- * Name of the meta-data item that should be set in the AndroidManifest.xml to specify the
- * custom view which should be displayed for the preference. The custom view will be inflated
- * as a remote view.
+ * Value for {@link #META_DATA_KEY_PROFILE}. When the device has a managed profile,
+ * the app will always be run in the primary profile.
*
- * This also can be used with {@link #META_DATA_PREFERENCE_SUMMARY_URI}, by setting the id
- * of the summary TextView to '@android:id/summary'.
+ * @see #META_DATA_KEY_PROFILE
*/
- public static final String META_DATA_PREFERENCE_CUSTOM_VIEW =
- "com.android.settings.custom_view";
-
- public static final String SETTING_PKG = "com.android.settings";
+ public static final String PROFILE_PRIMARY = "primary_profile_only";
/**
- * Build a list of DashboardCategory. Each category must be defined in manifest.
- * eg: .Settings$DeviceSettings
- * @deprecated
+ * Value for {@link #META_DATA_KEY_PROFILE}. When the device has a managed profile, the user
+ * will be presented with a dialog to choose the profile the app will be run in.
+ *
+ * @see #META_DATA_KEY_PROFILE
*/
- @Deprecated
- public static List<DashboardCategory> getCategories(Context context,
- Map<Pair<String, String>, Tile> cache) {
- return getCategories(context, cache, true /*categoryDefinedInManifest*/);
- }
+ public static final String PROFILE_ALL = "all_profiles";
/**
- * Build a list of DashboardCategory.
- * @param categoryDefinedInManifest If true, an dummy activity must exists in manifest to
- * represent this category (eg: .Settings$DeviceSettings)
+ * Name of the meta-data item that should be set in the AndroidManifest.xml
+ * to specify the profile in which the app should be run when the device has a managed profile.
+ * The default value is {@link #PROFILE_ALL} which means the user will be presented with a
+ * dialog to choose the profile. If set to {@link #PROFILE_PRIMARY} the app will always be
+ * run in the primary profile.
+ *
+ * @see #PROFILE_PRIMARY
+ * @see #PROFILE_ALL
*/
- public static List<DashboardCategory> getCategories(Context context,
- Map<Pair<String, String>, Tile> cache, boolean categoryDefinedInManifest) {
- return getCategories(context, cache, categoryDefinedInManifest, null, SETTING_PKG);
- }
+ public static final String META_DATA_KEY_PROFILE = "com.android.settings.profile";
/**
* Build a list of DashboardCategory.
- * @param categoryDefinedInManifest If true, an dummy activity must exists in manifest to
- * represent this category (eg: .Settings$DeviceSettings)
- * @param extraAction additional intent filter action to be usetileutild to build the dashboard
- * categories
*/
public static List<DashboardCategory> getCategories(Context context,
- Map<Pair<String, String>, Tile> cache, boolean categoryDefinedInManifest,
- String extraAction, String settingPkg) {
+ Map<Pair<String, String>, Tile> cache) {
final long startTime = System.currentTimeMillis();
boolean setup = Global.getInt(context.getContentResolver(), Global.DEVICE_PROVISIONED, 0)
!= 0;
@@ -224,37 +214,30 @@ public class TileUtils {
// TODO: Needs much optimization, too many PM queries going on here.
if (user.getIdentifier() == ActivityManager.getCurrentUser()) {
// Only add Settings for this user.
- getTilesForAction(context, user, SETTINGS_ACTION, cache, null, tiles, true,
- settingPkg);
+ getTilesForAction(context, user, SETTINGS_ACTION, cache, null, tiles, true);
getTilesForAction(context, user, OPERATOR_SETTINGS, cache,
- OPERATOR_DEFAULT_CATEGORY, tiles, false, true, settingPkg);
+ OPERATOR_DEFAULT_CATEGORY, tiles, false);
getTilesForAction(context, user, MANUFACTURER_SETTINGS, cache,
- MANUFACTURER_DEFAULT_CATEGORY, tiles, false, true, settingPkg);
+ MANUFACTURER_DEFAULT_CATEGORY, tiles, false);
}
if (setup) {
- getTilesForAction(context, user, EXTRA_SETTINGS_ACTION, cache, null, tiles, false,
- settingPkg);
- if (!categoryDefinedInManifest) {
- getTilesForAction(context, user, IA_SETTINGS_ACTION, cache, null, tiles, false,
- settingPkg);
- if (extraAction != null) {
- getTilesForAction(context, user, extraAction, cache, null, tiles, false,
- settingPkg);
- }
- }
+ getTilesForAction(context, user, EXTRA_SETTINGS_ACTION, cache, null, tiles, false);
+ getTilesForAction(context, user, IA_SETTINGS_ACTION, cache, null, tiles, false);
}
}
HashMap<String, DashboardCategory> categoryMap = new HashMap<>();
for (Tile tile : tiles) {
- DashboardCategory category = categoryMap.get(tile.category);
+ final String categoryKey = tile.getCategory();
+ DashboardCategory category = categoryMap.get(categoryKey);
if (category == null) {
- category = createCategory(context, tile.category, categoryDefinedInManifest);
+ category = new DashboardCategory(categoryKey);
+
if (category == null) {
- Log.w(LOG_TAG, "Couldn't find category " + tile.category);
+ Log.w(LOG_TAG, "Couldn't find category " + categoryKey);
continue;
}
- categoryMap.put(category.key, category);
+ categoryMap.put(categoryKey, category);
}
category.addTile(tile);
}
@@ -262,83 +245,25 @@ public class TileUtils {
for (DashboardCategory category : categories) {
category.sortTiles();
}
- Collections.sort(categories, CATEGORY_COMPARATOR);
- if (DEBUG_TIMING) Log.d(LOG_TAG, "getCategories took "
- + (System.currentTimeMillis() - startTime) + " ms");
- return categories;
- }
- /**
- * Create a new DashboardCategory from key.
- *
- * @param context Context to query intent
- * @param categoryKey The category key
- * @param categoryDefinedInManifest If true, an dummy activity must exists in manifest to
- * represent this category (eg: .Settings$DeviceSettings)
- */
- private static DashboardCategory createCategory(Context context, String categoryKey,
- boolean categoryDefinedInManifest) {
- DashboardCategory category = new DashboardCategory();
- category.key = categoryKey;
- if (!categoryDefinedInManifest) {
- return category;
- }
- PackageManager pm = context.getPackageManager();
- List<ResolveInfo> results = pm.queryIntentActivities(new Intent(categoryKey), 0);
- if (results.size() == 0) {
- return null;
+ if (DEBUG_TIMING) {
+ Log.d(LOG_TAG, "getCategories took "
+ + (System.currentTimeMillis() - startTime) + " ms");
}
- for (ResolveInfo resolved : results) {
- if (!resolved.system) {
- // Do not allow any app to add to settings, only system ones.
- continue;
- }
- category.title = resolved.activityInfo.loadLabel(pm);
- category.priority = SETTING_PKG.equals(
- resolved.activityInfo.applicationInfo.packageName) ? resolved.priority : 0;
- if (DEBUG) Log.d(LOG_TAG, "Adding category " + category.title);
- }
-
- return category;
- }
-
- private static void getTilesForAction(Context context,
- UserHandle user, String action, Map<Pair<String, String>, Tile> addedCache,
- String defaultCategory, ArrayList<Tile> outTiles, boolean requireSettings,
- String settingPkg) {
- getTilesForAction(context, user, action, addedCache, defaultCategory, outTiles,
- requireSettings, requireSettings, settingPkg);
+ return categories;
}
- private static void getTilesForAction(Context context,
+ @VisibleForTesting
+ static void getTilesForAction(Context context,
UserHandle user, String action, Map<Pair<String, String>, Tile> addedCache,
- String defaultCategory, ArrayList<Tile> outTiles, boolean requireSettings,
- boolean usePriority, String settingPkg) {
- Intent intent = new Intent(action);
+ String defaultCategory, List<Tile> outTiles, boolean requireSettings) {
+ final Intent intent = new Intent(action);
if (requireSettings) {
- intent.setPackage(settingPkg);
+ intent.setPackage(SETTING_PKG);
}
- getTilesForIntent(context, user, intent, addedCache, defaultCategory, outTiles,
- usePriority, true, true);
- }
-
- public static void getTilesForIntent(
- Context context, UserHandle user, Intent intent,
- Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles,
- boolean usePriority, boolean checkCategory, boolean forceTintExternalIcon) {
- getTilesForIntent(context, user, intent, addedCache, defaultCategory, outTiles,
- usePriority, checkCategory, forceTintExternalIcon, false /* shouldUpdateTiles */);
- }
-
- public static void getTilesForIntent(
- Context context, UserHandle user, Intent intent,
- Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles,
- boolean usePriority, boolean checkCategory, boolean forceTintExternalIcon,
- boolean shouldUpdateTiles) {
- PackageManager pm = context.getPackageManager();
+ final PackageManager pm = context.getPackageManager();
List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,
PackageManager.GET_META_DATA, user.getIdentifier());
- Map<String, IContentProvider> providerMap = new HashMap<>();
for (ResolveInfo resolved : results) {
if (!resolved.system) {
// Do not allow any app to add to settings, only system ones.
@@ -349,7 +274,7 @@ public class TileUtils {
String categoryKey = defaultCategory;
// Load category
- if (checkCategory && ((metaData == null) || !metaData.containsKey(EXTRA_CATEGORY_KEY))
+ if ((metaData == null || !metaData.containsKey(EXTRA_CATEGORY_KEY))
&& categoryKey == null) {
Log.w(LOG_TAG, "Found " + resolved.activityInfo.name + " for intent "
+ intent + " missing metadata "
@@ -359,22 +284,13 @@ public class TileUtils {
categoryKey = metaData.getString(EXTRA_CATEGORY_KEY);
}
- Pair<String, String> key = new Pair<String, String>(activityInfo.packageName,
- activityInfo.name);
+ Pair<String, String> key = new Pair<>(activityInfo.packageName, activityInfo.name);
Tile tile = addedCache.get(key);
if (tile == null) {
- tile = new Tile();
- tile.intent = new Intent().setClassName(
- activityInfo.packageName, activityInfo.name);
- tile.category = categoryKey;
- tile.priority = usePriority ? resolved.priority : 0;
- tile.metaData = activityInfo.metaData;
- updateTileData(context, tile, activityInfo, activityInfo.applicationInfo,
- pm, providerMap, forceTintExternalIcon);
- if (DEBUG) Log.d(LOG_TAG, "Adding tile " + tile.title);
+ tile = new Tile(activityInfo, categoryKey);
addedCache.put(key, tile);
- } else if (shouldUpdateTiles) {
- updateSummaryAndTitle(context, providerMap, tile);
+ } else {
+ tile.setMetaData(metaData);
}
if (!tile.userHandle.contains(user)) {
@@ -386,131 +302,12 @@ public class TileUtils {
}
}
- private static boolean updateTileData(Context context, Tile tile,
- ActivityInfo activityInfo, ApplicationInfo applicationInfo, PackageManager pm,
- Map<String, IContentProvider> providerMap, boolean forceTintExternalIcon) {
- if (applicationInfo.isSystemApp()) {
- boolean forceTintIcon = false;
- int icon = 0;
- Pair<String, Integer> iconFromUri = null;
- CharSequence title = null;
- String summary = null;
- String keyHint = null;
- boolean isIconTintable = false;
-
- // Get the activity's meta-data
- try {
- Resources res = pm.getResourcesForApplication(applicationInfo.packageName);
- Bundle metaData = activityInfo.metaData;
-
- if (forceTintExternalIcon
- && !context.getPackageName().equals(applicationInfo.packageName)) {
- isIconTintable = true;
- forceTintIcon = true;
- }
-
- if (res != null && metaData != null) {
- if (metaData.containsKey(META_DATA_PREFERENCE_ICON)) {
- icon = metaData.getInt(META_DATA_PREFERENCE_ICON);
- }
- if (metaData.containsKey(META_DATA_PREFERENCE_ICON_TINTABLE)) {
- if (forceTintIcon) {
- Log.w(LOG_TAG, "Ignoring icon tintable for " + activityInfo);
- } else {
- isIconTintable =
- metaData.getBoolean(META_DATA_PREFERENCE_ICON_TINTABLE);
- }
- }
- if (metaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
- if (metaData.get(META_DATA_PREFERENCE_TITLE) instanceof Integer) {
- title = res.getString(metaData.getInt(META_DATA_PREFERENCE_TITLE));
- } else {
- title = metaData.getString(META_DATA_PREFERENCE_TITLE);
- }
- }
- if (metaData.containsKey(META_DATA_PREFERENCE_SUMMARY)) {
- if (metaData.get(META_DATA_PREFERENCE_SUMMARY) instanceof Integer) {
- summary = res.getString(metaData.getInt(META_DATA_PREFERENCE_SUMMARY));
- } else {
- summary = metaData.getString(META_DATA_PREFERENCE_SUMMARY);
- }
- }
- if (metaData.containsKey(META_DATA_PREFERENCE_KEYHINT)) {
- if (metaData.get(META_DATA_PREFERENCE_KEYHINT) instanceof Integer) {
- keyHint = res.getString(metaData.getInt(META_DATA_PREFERENCE_KEYHINT));
- } else {
- keyHint = metaData.getString(META_DATA_PREFERENCE_KEYHINT);
- }
- }
- if (metaData.containsKey(META_DATA_PREFERENCE_CUSTOM_VIEW)) {
- int layoutId = metaData.getInt(META_DATA_PREFERENCE_CUSTOM_VIEW);
- tile.remoteViews = new RemoteViews(applicationInfo.packageName, layoutId);
- updateSummaryAndTitle(context, providerMap, tile);
- }
- }
- } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
- if (DEBUG) Log.d(LOG_TAG, "Couldn't find info", e);
- }
-
- // Set the preference title to the activity's label if no
- // meta-data is found
- if (TextUtils.isEmpty(title)) {
- title = activityInfo.loadLabel(pm).toString();
- }
-
- // Set the icon
- if (icon == 0) {
- // Only fallback to activityinfo.icon if metadata does not contain ICON_URI.
- // ICON_URI should be loaded in app UI when need the icon object.
- if (!tile.metaData.containsKey(META_DATA_PREFERENCE_ICON_URI)) {
- icon = activityInfo.icon;
- }
- }
- if (icon != 0) {
- tile.icon = Icon.createWithResource(activityInfo.packageName, icon);
- }
-
- // Set title and summary for the preference
- tile.title = title;
- tile.summary = summary;
- // Replace the intent with this specific activity
- tile.intent = new Intent().setClassName(activityInfo.packageName,
- activityInfo.name);
- // Suggest a key for this tile
- tile.key = keyHint;
- tile.isIconTintable = isIconTintable;
-
- return true;
- }
-
- return false;
- }
-
- private static void updateSummaryAndTitle(
- Context context, Map<String, IContentProvider> providerMap, Tile tile) {
- if (tile == null || tile.metaData == null
- || !tile.metaData.containsKey(META_DATA_PREFERENCE_SUMMARY_URI)) {
- return;
- }
-
- String uriString = tile.metaData.getString(META_DATA_PREFERENCE_SUMMARY_URI);
- Bundle bundle = getBundleFromUri(context, uriString, providerMap);
- String overrideSummary = getString(bundle, META_DATA_PREFERENCE_SUMMARY);
- String overrideTitle = getString(bundle, META_DATA_PREFERENCE_TITLE);
- if (overrideSummary != null) {
- tile.remoteViews.setTextViewText(android.R.id.summary, overrideSummary);
- }
-
- if (overrideTitle != null) {
- tile.remoteViews.setTextViewText(android.R.id.title, overrideTitle);
- }
- }
-
/**
* Gets the icon package name and resource id from content provider.
- * @param context context
+ *
+ * @param context context
* @param packageName package name of the target activity
- * @param uriString URI for the content provider
+ * @param uriString URI for the content provider
* @param providerMap Maps URI authorities to providers
* @return package name and resource id of the icon specified
*/
@@ -538,10 +335,11 @@ public class TileUtils {
/**
* Gets text associated with the input key from the content provider.
- * @param context context
- * @param uriString URI for the content provider
+ *
+ * @param context context
+ * @param uriString URI for the content provider
* @param providerMap Maps URI authorities to providers
- * @param key Key mapping to the text in bundle returned by the content provider
+ * @param key Key mapping to the text in bundle returned by the content provider
* @return Text associated with the key, if returned by the content provider
*/
public static String getTextFromUri(Context context, String uriString,
@@ -571,10 +369,6 @@ public class TileUtils {
}
}
- private static String getString(Bundle bundle, String key) {
- return bundle == null ? null : bundle.getString(key);
- }
-
private static IContentProvider getProviderFromUri(Context context, Uri uri,
Map<String, IContentProvider> providerMap) {
if (uri == null) {
@@ -601,12 +395,4 @@ public class TileUtils {
}
return pathSegments.get(0);
}
-
- private static final Comparator<DashboardCategory> CATEGORY_COMPARATOR =
- new Comparator<DashboardCategory>() {
- @Override
- public int compare(DashboardCategory lhs, DashboardCategory rhs) {
- return rhs.priority - lhs.priority;
- }
- };
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/UserAdapter.java b/packages/SettingsLib/src/com/android/settingslib/drawer/UserAdapter.java
deleted file mode 100644
index 8a09df2849c9..000000000000
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/UserAdapter.java
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.drawer;
-
-import android.app.ActivityManager;
-
-import android.content.Context;
-import android.content.pm.UserInfo;
-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.ListAdapter;
-import android.widget.SpinnerAdapter;
-import android.widget.TextView;
-import com.android.internal.util.UserIcons;
-import com.android.settingslib.drawable.UserIconDrawable;
-
-import com.android.settingslib.R;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Adapter for a spinner that shows a list of users.
- */
-public class UserAdapter implements SpinnerAdapter, ListAdapter {
- /** Holder for user details */
- public static class UserDetails {
- private final UserHandle mUserHandle;
- private final String mName;
- private final Drawable mIcon;
-
- public UserDetails(UserHandle userHandle, UserManager um, Context context) {
- mUserHandle = userHandle;
- UserInfo userInfo = um.getUserInfo(mUserHandle.getIdentifier());
- Drawable icon;
- if (userInfo.isManagedProfile()) {
- mName = context.getString(R.string.managed_user_title);
- icon = context.getDrawable(
- com.android.internal.R.drawable.ic_corp_badge);
- } else {
- mName = userInfo.name;
- final int userId = userInfo.id;
- if (um.getUserIcon(userId) != null) {
- icon = new BitmapDrawable(context.getResources(), um.getUserIcon(userId));
- } else {
- icon = UserIcons.getDefaultUserIcon(
- context.getResources(), userId, /* light= */ false);
- }
- }
- this.mIcon = encircle(context, icon);
- }
-
- private static Drawable encircle(Context context, Drawable icon) {
- return new UserIconDrawable(UserIconDrawable.getSizeForList(context))
- .setIconDrawable(icon).bake();
- }
- }
- private ArrayList<UserDetails> data;
- private final LayoutInflater mInflater;
-
- public UserAdapter(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.mIcon);
- ((TextView) row.findViewById(android.R.id.title)).setText(getTitle(user));
- return row;
- }
-
- private int getTitle(UserDetails user) {
- int userHandle = user.mUserHandle.getIdentifier();
- if (userHandle == UserHandle.USER_CURRENT
- || userHandle == ActivityManager.getCurrentUser()) {
- return R.string.category_personal;
- } else {
- return R.string.category_work;
- }
- }
-
- 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();
- }
-
- @Override
- public boolean areAllItemsEnabled() {
- return true;
- }
-
- @Override
- public boolean isEnabled(int position) {
- return true;
- }
-
- /**
- * Creates a {@link UserAdapter} 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 UserAdapter} or null if there is only one profile.
- */
- public static UserAdapter 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);
-
- return createUserAdapter(userManager, context, userProfiles);
- }
-
- public static UserAdapter createUserAdapter(UserManager userManager,
- Context context, List<UserHandle> userProfiles) {
- 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 UserAdapter(context, userDetails);
- }
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java
index 25e590b34e3b..9feacac60365 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java
@@ -23,12 +23,13 @@ import android.content.pm.PackageManager;
import android.os.IDeviceIdleController;
import android.os.RemoteException;
import android.os.ServiceManager;
-import androidx.annotation.VisibleForTesting;
import android.telecom.DefaultDialerManager;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
+import androidx.annotation.VisibleForTesting;
+
import com.android.internal.telephony.SmsApplication;
import com.android.internal.util.ArrayUtils;
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java b/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
index 2fc6db08c7f9..17c2b02ed576 100644
--- a/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
@@ -107,7 +107,8 @@ public class BatteryMeterDrawableBase extends Drawable {
for (int i = 0; i < N; i++) {
mColors[2 * i] = levels.getInt(i, 0);
if (colors.getType(i) == TypedValue.TYPE_ATTRIBUTE) {
- mColors[2 * i + 1] = Utils.getColorAttr(context, colors.getThemeAttributeId(i, 0));
+ mColors[2 * i + 1] = Utils.getColorAttrDefaultColor(context,
+ colors.getThemeAttributeId(i, 0));
} else {
mColors[2 * i + 1] = colors.getColor(i, 0);
}
@@ -149,14 +150,16 @@ public class BatteryMeterDrawableBase extends Drawable {
mWarningTextPaint.setColor(mColors[1]);
}
- mChargeColor = Utils.getDefaultColor(mContext, R.color.meter_consumed_color);
+ mChargeColor = Utils.getColorStateListDefaultColor(mContext, R.color.meter_consumed_color);
mBoltPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mBoltPaint.setColor(Utils.getDefaultColor(mContext, R.color.batterymeter_bolt_color));
+ mBoltPaint.setColor(Utils.getColorStateListDefaultColor(mContext,
+ R.color.batterymeter_bolt_color));
mBoltPoints = loadPoints(res, R.array.batterymeter_bolt_points);
mPlusPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mPlusPaint.setColor(Utils.getDefaultColor(mContext, R.color.batterymeter_plus_color));
+ mPlusPaint.setColor(Utils.getColorStateListDefaultColor(mContext,
+ R.color.batterymeter_plus_color));
mPlusPoints = loadPoints(res, R.array.batterymeter_plus_points);
mPowersavePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
@@ -207,6 +210,10 @@ public class BatteryMeterDrawableBase extends Drawable {
postInvalidate();
}
+ public boolean getPowerSave() {
+ return mPowerSaveEnabled;
+ }
+
protected void setPowerSaveAsColorError(boolean asError) {
mPowerSaveAsColorError = asError;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java b/packages/SettingsLib/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java
index 17523013a94f..f01eb2a5606e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java
@@ -19,18 +19,13 @@ package com.android.settingslib.graph;
import android.annotation.NonNull;
import android.content.Context;
import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
-import android.graphics.Rect;
-import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
-import androidx.annotation.VisibleForTesting;
import android.view.Gravity;
-import android.view.View;
+
+import androidx.annotation.VisibleForTesting;
import com.android.settingslib.R;
import com.android.settingslib.Utils;
@@ -117,7 +112,8 @@ public class BluetoothDeviceLayerDrawable extends LayerDrawable {
R.fraction.bt_battery_button_height_fraction, 1, 1);
mAspectRatio = resources.getFraction(R.fraction.bt_battery_ratio_fraction, 1, 1);
- final int tintColor = Utils.getColorAttr(context, android.R.attr.colorControlNormal);
+ final int tintColor = Utils.getColorAttrDefaultColor(context,
+ android.R.attr.colorControlNormal);
setColorFilter(new PorterDuffColorFilter(tintColor, PorterDuff.Mode.SRC_IN));
setBatteryLevel(batteryLevel);
mFrameColor = frameColor;
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java b/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
index 846e30d50063..a6b57550c358 100644
--- a/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
@@ -132,13 +132,17 @@ public class SignalDrawable extends Drawable {
public SignalDrawable(Context context) {
mDarkModeBackgroundColor =
- Utils.getDefaultColor(context, R.color.dark_mode_icon_color_dual_tone_background);
+ Utils.getColorStateListDefaultColor(context,
+ R.color.dark_mode_icon_color_dual_tone_background);
mDarkModeFillColor =
- Utils.getDefaultColor(context, R.color.dark_mode_icon_color_dual_tone_fill);
+ Utils.getColorStateListDefaultColor(context,
+ R.color.dark_mode_icon_color_dual_tone_fill);
mLightModeBackgroundColor =
- Utils.getDefaultColor(context, R.color.light_mode_icon_color_dual_tone_background);
+ Utils.getColorStateListDefaultColor(context,
+ R.color.light_mode_icon_color_dual_tone_background);
mLightModeFillColor =
- Utils.getDefaultColor(context, R.color.light_mode_icon_color_dual_tone_fill);
+ Utils.getColorStateListDefaultColor(context,
+ R.color.light_mode_icon_color_dual_tone_fill);
mIntrinsicSize = context.getResources().getDimensionPixelSize(R.dimen.signal_icon_size);
mHandler = new Handler();
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeEnablerManager.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeEnablerManager.java
index 628e70af8318..117b48ff852d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeEnablerManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeEnablerManager.java
@@ -19,16 +19,17 @@ package com.android.settingslib.inputmethod;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
-import androidx.preference.PreferenceFragment;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceCategory;
-import androidx.preference.PreferenceScreen;
-import androidx.preference.TwoStatePreference;
import android.text.TextUtils;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceFragment;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.TwoStatePreference;
+
import com.android.settingslib.R;
import java.text.Collator;
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeEnablerManagerCompat.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeEnablerManagerCompat.java
new file mode 100644
index 000000000000..6f8c7ac22077
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeEnablerManagerCompat.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2018 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.settingslib.inputmethod;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.text.TextUtils;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.TwoStatePreference;
+
+import com.android.settingslib.R;
+
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class InputMethodAndSubtypeEnablerManagerCompat implements
+ Preference.OnPreferenceChangeListener {
+
+ private final PreferenceFragmentCompat mFragment;
+
+ private boolean mHaveHardKeyboard;
+ private final HashMap<String, List<Preference>> mInputMethodAndSubtypePrefsMap =
+ new HashMap<>();
+ private final HashMap<String, TwoStatePreference> mAutoSelectionPrefsMap = new HashMap<>();
+ private InputMethodManager mImm;
+ // TODO: Change mInputMethodInfoList to Map
+ private List<InputMethodInfo> mInputMethodInfoList;
+ private final Collator mCollator = Collator.getInstance();
+
+ public InputMethodAndSubtypeEnablerManagerCompat(PreferenceFragmentCompat fragment) {
+ mFragment = fragment;
+ mImm = fragment.getContext().getSystemService(InputMethodManager.class);
+
+ mInputMethodInfoList = mImm.getInputMethodList();
+ }
+
+ public void init(PreferenceFragmentCompat fragment, String targetImi, PreferenceScreen root) {
+ final Configuration config = fragment.getResources().getConfiguration();
+ mHaveHardKeyboard = (config.keyboard == Configuration.KEYBOARD_QWERTY);
+
+ for (final InputMethodInfo imi : mInputMethodInfoList) {
+ // Add subtype preferences of this IME when it is specified or no IME is specified.
+ if (imi.getId().equals(targetImi) || TextUtils.isEmpty(targetImi)) {
+ addInputMethodSubtypePreferences(fragment, imi, root);
+ }
+ }
+ }
+
+ public void refresh(Context context, PreferenceFragmentCompat fragment) {
+ // Refresh internal states in mInputMethodSettingValues to keep the latest
+ // "InputMethodInfo"s and "InputMethodSubtype"s
+ InputMethodSettingValuesWrapper
+ .getInstance(context).refreshAllInputMethodAndSubtypes();
+ InputMethodAndSubtypeUtilCompat.loadInputMethodSubtypeList(fragment,
+ context.getContentResolver(), mInputMethodInfoList, mInputMethodAndSubtypePrefsMap);
+ updateAutoSelectionPreferences();
+ }
+
+ public void save(Context context, PreferenceFragmentCompat fragment) {
+ InputMethodAndSubtypeUtilCompat.saveInputMethodSubtypeList(fragment,
+ context.getContentResolver(), mInputMethodInfoList, mHaveHardKeyboard);
+ }
+
+ @Override
+ 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;
+ }
+ }
+ // 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 addInputMethodSubtypePreferences(PreferenceFragmentCompat fragment,
+ InputMethodInfo imi, final PreferenceScreen root) {
+ Context prefContext = fragment.getPreferenceManager().getContext();
+
+ final int subtypeCount = imi.getSubtypeCount();
+ if (subtypeCount <= 1) {
+ return;
+ }
+ final String imiId = imi.getId();
+ final PreferenceCategory keyboardSettingsCategory =
+ new PreferenceCategory(prefContext);
+ root.addPreference(keyboardSettingsCategory);
+ final PackageManager pm = prefContext.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(prefContext);
+ mAutoSelectionPrefsMap.put(imiId, autoSelectionPref);
+ keyboardSettingsCategory.addPreference(autoSelectionPref);
+ autoSelectionPref.setOnPreferenceChangeListener(this);
+
+ final PreferenceCategory activeInputMethodsCategory =
+ new PreferenceCategory(prefContext);
+ 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 = InputMethodAndSubtypeUtil.getSubtypeLocaleNameAsSentence(
+ subtype, prefContext, imi);
+ }
+ } else {
+ final Preference subtypePref = new InputMethodSubtypePreference(
+ prefContext, subtype, imi);
+ subtypePreferences.add(subtypePref);
+ }
+ }
+ subtypePreferences.sort((lhs, rhs) -> {
+ if (lhs instanceof InputMethodSubtypePreference) {
+ return ((InputMethodSubtypePreference) lhs).compareTo(rhs, mCollator);
+ }
+ return lhs.compareTo(rhs);
+ });
+ for (final Preference pref : subtypePreferences) {
+ 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);
+ }
+ }
+
+ private boolean isNoSubtypesExplicitlySelected(final String imiId) {
+ final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId);
+ for (final Preference pref : subtypePrefs) {
+ if (pref instanceof TwoStatePreference && ((TwoStatePreference) pref).isChecked()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ 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 (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.
+ pref.setEnabled(!autoSelectionEnabled);
+ if (autoSelectionEnabled) {
+ ((TwoStatePreference) pref).setChecked(false);
+ }
+ }
+ }
+ if (autoSelectionEnabled) {
+ InputMethodAndSubtypeUtilCompat.saveInputMethodSubtypeList(
+ mFragment, mFragment.getContext().getContentResolver(),
+ mInputMethodInfoList, mHaveHardKeyboard);
+ updateImplicitlyEnabledSubtypes(imiId);
+ }
+ }
+
+ private void updateImplicitlyEnabledSubtypes(final String targetImiId) {
+ // When targetImiId is null, apply to all subtypes of all IMEs
+ 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 (autoSelectionPref == null || !autoSelectionPref.isChecked()) {
+ continue;
+ }
+ if (imiId.equals(targetImiId) || targetImiId == null) {
+ updateImplicitlyEnabledSubtypesOf(imi);
+ }
+ }
+ }
+
+ private void updateImplicitlyEnabledSubtypesOf(final InputMethodInfo imi) {
+ 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;
+ }
+ for (final Preference pref : subtypePrefs) {
+ if (!(pref instanceof TwoStatePreference)) {
+ continue;
+ }
+ final TwoStatePreference subtypePref = (TwoStatePreference) pref;
+ subtypePref.setChecked(false);
+ for (final InputMethodSubtype subtype : implicitlyEnabledSubtypes) {
+ final String implicitlyEnabledSubtypePrefKey = imiId + subtype.hashCode();
+ if (subtypePref.getKey().equals(implicitlyEnabledSubtypePrefKey)) {
+ subtypePref.setChecked(true);
+ break;
+ }
+ }
+ }
+ }
+
+ private void updateAutoSelectionPreferences() {
+ for (final String imiId : mInputMethodAndSubtypePrefsMap.keySet()) {
+ setAutoSelectionSubtypesEnabled(imiId, isNoSubtypesExplicitlySelected(imiId));
+ }
+ updateImplicitlyEnabledSubtypes(null /* targetImiId */ /* check */);
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtil.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtil.java
index 0a5c28c7b127..057123bcd224 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtil.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtil.java
@@ -25,17 +25,17 @@ import android.content.res.Configuration;
import android.icu.text.ListFormatter;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
-import androidx.preference.PreferenceFragment;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceScreen;
-import androidx.preference.TwoStatePreference;
import android.text.TextUtils;
import android.util.Log;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragment;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.TwoStatePreference;
+
import com.android.internal.app.LocaleHelper;
-import com.android.internal.inputmethod.InputMethodUtils;
import java.util.HashMap;
import java.util.HashSet;
@@ -49,6 +49,7 @@ public class InputMethodAndSubtypeUtil {
private static final boolean DEBUG = false;
private static final String TAG = "InputMethdAndSubtypeUtl";
+ private static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
private static final char INPUT_METHOD_SEPARATER = ':';
private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';';
private static final int NOT_A_SUBTYPE_ID = -1;
@@ -61,7 +62,7 @@ public class InputMethodAndSubtypeUtil {
// InputMethods and subtypes are saved in the settings as follows:
// ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
- private static String buildInputMethodsAndSubtypesString(
+ public static String buildInputMethodsAndSubtypesString(
final HashMap<String, HashSet<String>> imeToSubtypesMap) {
final StringBuilder builder = new StringBuilder();
for (final String imi : imeToSubtypesMap.keySet()) {
@@ -106,7 +107,7 @@ public class InputMethodAndSubtypeUtil {
}
// Needs to modify InputMethodManageService if you want to change the format of saved string.
- private static HashMap<String, HashSet<String>> getEnabledInputMethodsAndSubtypeList(
+ static HashMap<String, HashSet<String>> getEnabledInputMethodsAndSubtypeList(
ContentResolver resolver) {
final String enabledInputMethodsStr = Settings.Secure.getString(
resolver, Settings.Secure.ENABLED_INPUT_METHODS);
@@ -116,7 +117,7 @@ public class InputMethodAndSubtypeUtil {
return parseInputMethodsAndSubtypesString(enabledInputMethodsStr);
}
- private static HashMap<String, HashSet<String>> parseInputMethodsAndSubtypesString(
+ public static HashMap<String, HashSet<String>> parseInputMethodsAndSubtypesString(
final String inputMethodsAndSubtypesString) {
final HashMap<String, HashSet<String>> subtypesMap = new HashMap<>();
if (TextUtils.isEmpty(inputMethodsAndSubtypesString)) {
@@ -176,9 +177,9 @@ public class InputMethodAndSubtypeUtil {
((TwoStatePreference) pref).isChecked()
: enabledIMEsAndSubtypesMap.containsKey(imiId);
final boolean isCurrentInputMethod = imiId.equals(currentInputMethodId);
- final boolean systemIme = InputMethodUtils.isSystemIme(imi);
+ final boolean systemIme = imi.isSystem();
if ((!hasHardKeyboard && InputMethodSettingValuesWrapper.getInstance(
- context.getActivity()).isAlwaysCheckedIme(imi, context.getActivity()))
+ context.getActivity()).isAlwaysCheckedIme(imi))
|| isImeChecked) {
if (!enabledIMEsAndSubtypesMap.containsKey(imiId)) {
// imiId has just been enabled
@@ -417,4 +418,19 @@ public class InputMethodAndSubtypeUtil {
}
return configurationLocale;
}
+
+ public static boolean isValidSystemNonAuxAsciiCapableIme(InputMethodInfo imi) {
+ if (imi.isAuxiliaryIme() || !imi.isSystem()) {
+ return false;
+ }
+ final int subtypeCount = imi.getSubtypeCount();
+ for (int i = 0; i < subtypeCount; ++i) {
+ final InputMethodSubtype subtype = imi.getSubtypeAt(i);
+ if (SUBTYPE_MODE_KEYBOARD.equalsIgnoreCase(subtype.getMode())
+ && subtype.isAsciiCapable()) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java
new file mode 100644
index 000000000000..692dfbff50f6
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java
@@ -0,0 +1,436 @@
+/*
+ * Copyright (C) 2018 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.settingslib.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.icu.text.ListFormatter;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.TwoStatePreference;
+
+import com.android.internal.app.LocaleHelper;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+// TODO: Consolidate this with {@link InputMethodSettingValuesWrapper}.
+public class InputMethodAndSubtypeUtilCompat {
+
+ private static final boolean DEBUG = false;
+ private static final String TAG = "InputMethdAndSubtypeUtlCompat";
+
+ private static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
+ private static final char INPUT_METHOD_SEPARATER = ':';
+ private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';';
+ private static final int NOT_A_SUBTYPE_ID = -1;
+
+ private static final TextUtils.SimpleStringSplitter sStringInputMethodSplitter
+ = new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER);
+
+ private static final TextUtils.SimpleStringSplitter sStringInputMethodSubtypeSplitter
+ = new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
+
+ // InputMethods and subtypes are saved in the settings as follows:
+ // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
+ public 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);
+ }
+ 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();
+ }
+
+ 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);
+ }
+ builder.append(imi);
+ }
+ return builder.toString();
+ }
+
+ private static int getInputMethodSubtypeSelected(ContentResolver resolver) {
+ try {
+ return Settings.Secure.getInt(resolver,
+ Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE);
+ } catch (SettingNotFoundException e) {
+ return NOT_A_SUBTYPE_ID;
+ }
+ }
+
+ private static boolean isInputMethodSubtypeSelected(ContentResolver resolver) {
+ return getInputMethodSubtypeSelected(resolver) != NOT_A_SUBTYPE_ID;
+ }
+
+ private static void putSelectedInputMethodSubtype(ContentResolver resolver, int hashCode) {
+ Settings.Secure.putInt(resolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, hashCode);
+ }
+
+ // Needs to modify InputMethodManageService if you want to change the format of saved string.
+ static HashMap<String, HashSet<String>> getEnabledInputMethodsAndSubtypeList(
+ ContentResolver resolver) {
+ final String enabledInputMethodsStr = Settings.Secure.getString(
+ resolver, Settings.Secure.ENABLED_INPUT_METHODS);
+ if (DEBUG) {
+ Log.d(TAG, "--- Load enabled input methods: " + enabledInputMethodsStr);
+ }
+ return parseInputMethodsAndSubtypesString(enabledInputMethodsStr);
+ }
+
+ public 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(inputMethodsAndSubtypesString);
+ while (sStringInputMethodSplitter.hasNext()) {
+ final String nextImsStr = sStringInputMethodSplitter.next();
+ sStringInputMethodSubtypeSplitter.setString(nextImsStr);
+ if (sStringInputMethodSubtypeSplitter.hasNext()) {
+ final HashSet<String> subtypeIdSet = new HashSet<>();
+ // The first element is {@link InputMethodInfoId}.
+ final String imiId = sStringInputMethodSubtypeSplitter.next();
+ while (sStringInputMethodSubtypeSplitter.hasNext()) {
+ subtypeIdSet.add(sStringInputMethodSubtypeSplitter.next());
+ }
+ subtypesMap.put(imiId, subtypeIdSet);
+ }
+ }
+ return subtypesMap;
+ }
+
+ private static HashSet<String> getDisabledSystemIMEs(ContentResolver resolver) {
+ HashSet<String> set = new HashSet<>();
+ String disabledIMEsStr = Settings.Secure.getString(
+ resolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS);
+ if (TextUtils.isEmpty(disabledIMEsStr)) {
+ return set;
+ }
+ sStringInputMethodSplitter.setString(disabledIMEsStr);
+ while(sStringInputMethodSplitter.hasNext()) {
+ set.add(sStringInputMethodSplitter.next());
+ }
+ return set;
+ }
+
+ public static void saveInputMethodSubtypeList(PreferenceFragmentCompat context,
+ ContentResolver resolver, List<InputMethodInfo> inputMethodInfos,
+ boolean hasHardKeyboard) {
+ String currentInputMethodId = Settings.Secure.getString(resolver,
+ Settings.Secure.DEFAULT_INPUT_METHOD);
+ final int selectedInputMethodSubtype = getInputMethodSubtypeSelected(resolver);
+ final HashMap<String, HashSet<String>> enabledIMEsAndSubtypesMap =
+ getEnabledInputMethodsAndSubtypeList(resolver);
+ final HashSet<String> disabledSystemIMEs = getDisabledSystemIMEs(resolver);
+
+ boolean needsToResetSelectedSubtype = false;
+ for (final InputMethodInfo imi : inputMethodInfos) {
+ final String imiId = imi.getId();
+ 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 = imi.isSystem();
+ if ((!hasHardKeyboard && InputMethodSettingValuesWrapper.getInstance(
+ context.getActivity()).isAlwaysCheckedIme(imi))
+ || isImeChecked) {
+ if (!enabledIMEsAndSubtypesMap.containsKey(imiId)) {
+ // imiId has just been enabled
+ enabledIMEsAndSubtypesMap.put(imiId, new HashSet<>());
+ }
+ final HashSet<String> subtypesSet = enabledIMEsAndSubtypesMap.get(imiId);
+
+ boolean subtypePrefFound = false;
+ final int subtypeCount = imi.getSubtypeCount();
+ for (int i = 0; i < subtypeCount; ++i) {
+ final InputMethodSubtype subtype = imi.getSubtypeAt(i);
+ final String subtypeHashCodeStr = String.valueOf(subtype.hashCode());
+ 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 (!subtypePrefFound) {
+ // 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.
+ needsToResetSelectedSubtype = true;
+ subtypePrefFound = true;
+ }
+ // Checking <code>subtypePref.isEnabled()</code> is insufficient to determine
+ // whether the user manually enabled this subtype or not. Implicitly-enabled
+ // subtypes are also checked just as an indicator to users. We also need to
+ // check <code>subtypePref.isEnabled()</code> so that only manually enabled
+ // subtypes can be saved here.
+ if (subtypePref.isEnabled() && subtypePref.isChecked()) {
+ subtypesSet.add(subtypeHashCodeStr);
+ if (isCurrentInputMethod) {
+ if (selectedInputMethodSubtype == subtype.hashCode()) {
+ // Selected subtype is still enabled, there is no need to reset
+ // selected subtype.
+ needsToResetSelectedSubtype = false;
+ }
+ }
+ } else {
+ subtypesSet.remove(subtypeHashCodeStr);
+ }
+ }
+ } else {
+ 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.
+ // If currentInputMethod is already uninstalled, InputMethodManagerService will
+ // find the applicable IME from the history and the system locale.
+ if (DEBUG) {
+ Log.d(TAG, "Current IME was uninstalled or disabled.");
+ }
+ currentInputMethodId = null;
+ }
+ }
+ // If it's a disabled system ime, add it to the disabled list so that it
+ // doesn't get enabled automatically on any changes to the package list
+ if (systemIme && hasHardKeyboard) {
+ if (disabledSystemIMEs.contains(imiId)) {
+ if (isImeChecked) {
+ disabledSystemIMEs.remove(imiId);
+ }
+ } else {
+ if (!isImeChecked) {
+ disabledSystemIMEs.add(imiId);
+ }
+ }
+ }
+ }
+
+ final String enabledIMEsAndSubtypesString = buildInputMethodsAndSubtypesString(
+ enabledIMEsAndSubtypesMap);
+ final String disabledSystemIMEsString = buildInputMethodsString(disabledSystemIMEs);
+ if (DEBUG) {
+ 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));
+ }
+
+ // Redefines SelectedSubtype when all subtypes are unchecked or there is no subtype
+ // selected. And if the selected subtype of the current input method was disabled,
+ // We should reset the selected input method's subtype.
+ if (needsToResetSelectedSubtype || !isInputMethodSubtypeSelected(resolver)) {
+ if (DEBUG) {
+ Log.d(TAG, "--- Reset inputmethod subtype because it's not defined.");
+ }
+ putSelectedInputMethodSubtype(resolver, NOT_A_SUBTYPE_ID);
+ }
+
+ Settings.Secure.putString(resolver,
+ Settings.Secure.ENABLED_INPUT_METHODS, enabledIMEsAndSubtypesString);
+ if (disabledSystemIMEsString.length() > 0) {
+ Settings.Secure.putString(resolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS,
+ disabledSystemIMEsString);
+ }
+ // If the current input method is unset, InputMethodManagerService will find the applicable
+ // IME from the history and the system locale.
+ Settings.Secure.putString(resolver, Settings.Secure.DEFAULT_INPUT_METHOD,
+ currentInputMethodId != null ? currentInputMethodId : "");
+ }
+
+ public static void loadInputMethodSubtypeList(final PreferenceFragmentCompat context,
+ final ContentResolver resolver, final List<InputMethodInfo> inputMethodInfos,
+ final Map<String, List<Preference>> inputMethodPrefsMap) {
+ final HashMap<String, HashSet<String>> enabledSubtypes =
+ getEnabledInputMethodsAndSubtypeList(resolver);
+
+ for (final InputMethodInfo imi : inputMethodInfos) {
+ final String imiId = imi.getId();
+ 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 (final Preference childPref: inputMethodPrefsMap.get(imiId)) {
+ childPref.setEnabled(isEnabled);
+ }
+ }
+ setSubtypesPreferenceEnabled(context, inputMethodInfos, imiId, isEnabled);
+ }
+ }
+ updateSubtypesPreferenceChecked(context, inputMethodInfos, enabledSubtypes);
+ }
+
+ private static void setSubtypesPreferenceEnabled(final PreferenceFragmentCompat 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) {
+ final InputMethodSubtype subtype = imi.getSubtypeAt(i);
+ final TwoStatePreference pref = (TwoStatePreference) preferenceScreen
+ .findPreference(id + subtype.hashCode());
+ if (pref != null) {
+ pref.setEnabled(enabled);
+ }
+ }
+ }
+ }
+ }
+
+ private static void updateSubtypesPreferenceChecked(final PreferenceFragmentCompat 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) {
+ 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));
+ }
+ final TwoStatePreference pref = (TwoStatePreference) preferenceScreen
+ .findPreference(id + hashCode);
+ if (pref != null) {
+ pref.setChecked(enabledSubtypesSet.contains(hashCode));
+ }
+ }
+ }
+ }
+
+ public 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();
+ }
+ }
+
+ @NonNull
+ public static String getSubtypeLocaleNameAsSentence(@Nullable InputMethodSubtype subtype,
+ @NonNull final Context context, @NonNull final InputMethodInfo inputMethodInfo) {
+ if (subtype == null) {
+ return "";
+ }
+ final Locale locale = getDisplayLocale(context);
+ final CharSequence subtypeName = subtype.getDisplayName(context,
+ inputMethodInfo.getPackageName(), inputMethodInfo.getServiceInfo()
+ .applicationInfo);
+ return LocaleHelper.toSentenceCase(subtypeName.toString(), locale);
+ }
+
+ @NonNull
+ public static String getSubtypeLocaleNameListAsSentence(
+ @NonNull final List<InputMethodSubtype> subtypes, @NonNull final Context context,
+ @NonNull final InputMethodInfo inputMethodInfo) {
+ if (subtypes.isEmpty()) {
+ return "";
+ }
+ final Locale locale = getDisplayLocale(context);
+ final int subtypeCount = subtypes.size();
+ final CharSequence[] subtypeNames = new CharSequence[subtypeCount];
+ for (int i = 0; i < subtypeCount; i++) {
+ subtypeNames[i] = subtypes.get(i).getDisplayName(context,
+ inputMethodInfo.getPackageName(), inputMethodInfo.getServiceInfo()
+ .applicationInfo);
+ }
+ return LocaleHelper.toSentenceCase(
+ ListFormatter.getInstance(locale).format((Object[]) subtypeNames), locale);
+ }
+
+ @NonNull
+ private static Locale getDisplayLocale(@Nullable final Context context) {
+ if (context == null) {
+ return Locale.getDefault();
+ }
+ if (context.getResources() == null) {
+ return Locale.getDefault();
+ }
+ final Configuration configuration = context.getResources().getConfiguration();
+ if (configuration == null) {
+ return Locale.getDefault();
+ }
+ final Locale configurationLocale = configuration.getLocales().get(0);
+ if (configurationLocale == null) {
+ return Locale.getDefault();
+ }
+ return configurationLocale;
+ }
+
+ public static boolean isValidSystemNonAuxAsciiCapableIme(InputMethodInfo imi) {
+ if (imi.isAuxiliaryIme() || !imi.isSystem()) {
+ return false;
+ }
+ final int subtypeCount = imi.getSubtypeCount();
+ for (int i = 0; i < subtypeCount; ++i) {
+ final InputMethodSubtype subtype = imi.getSubtypeAt(i);
+ if (SUBTYPE_MODE_KEYBOARD.equalsIgnoreCase(subtype.getMode())
+ && subtype.isAsciiCapable()) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
index 2e34dc5cac83..5c126b1d839b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
@@ -24,9 +24,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.UserHandle;
-import androidx.preference.Preference;
-import androidx.preference.Preference.OnPreferenceChangeListener;
-import androidx.preference.Preference.OnPreferenceClickListener;
import android.text.TextUtils;
import android.util.Log;
import android.view.inputmethod.InputMethodInfo;
@@ -34,10 +31,13 @@ import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
import android.widget.Toast;
+import androidx.preference.Preference;
+import androidx.preference.Preference.OnPreferenceChangeListener;
+import androidx.preference.Preference.OnPreferenceClickListener;
+
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.inputmethod.InputMethodUtils;
import com.android.settingslib.R;
-import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.RestrictedSwitchPreference;
import java.text.Collator;
@@ -124,8 +124,8 @@ public class InputMethodPreference extends RestrictedSwitchPreference implements
setIntent(intent);
}
mInputMethodSettingValues = InputMethodSettingValuesWrapper.getInstance(context);
- mHasPriorityInSorting = InputMethodUtils.isSystemIme(imi)
- && mInputMethodSettingValues.isValidSystemNonAuxAsciiCapableIme(imi, context);
+ mHasPriorityInSorting = imi.isSystem()
+ && InputMethodAndSubtypeUtil.isValidSystemNonAuxAsciiCapableIme(imi);
setOnPreferenceClickListener(this);
setOnPreferenceChangeListener(this);
}
@@ -153,7 +153,7 @@ public class InputMethodPreference extends RestrictedSwitchPreference implements
setCheckedInternal(false);
return false;
}
- if (InputMethodUtils.isSystemIme(mImi)) {
+ if (mImi.isSystem()) {
// Enable a system IME. No need to show a security warning dialog,
// but we might need to prompt if it's not Direct Boot aware.
// TV doesn't doesn't need to worry about this, but other platforms should show
@@ -198,8 +198,7 @@ public class InputMethodPreference extends RestrictedSwitchPreference implements
}
public void updatePreferenceViews() {
- final boolean isAlwaysChecked = mInputMethodSettingValues.isAlwaysCheckedIme(
- mImi, getContext());
+ final boolean isAlwaysChecked = mInputMethodSettingValues.isAlwaysCheckedIme(mImi);
// 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.
// This preference should also be disabled in case the admin does not allow this input
@@ -209,7 +208,7 @@ public class InputMethodPreference extends RestrictedSwitchPreference implements
setEnabled(false);
} else if (!mIsAllowedByOrganization) {
EnforcedAdmin admin =
- RestrictedLockUtils.checkIfInputMethodDisallowed(getContext(),
+ RestrictedLockUtilsInternal.checkIfInputMethodDisallowed(getContext(),
mImi.getPackageName(), UserHandle.myUserId());
setDisabledByAdmin(admin);
} else {
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java
index fac50bda3c69..b6786d424e6f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java
@@ -16,39 +16,33 @@
package com.android.settingslib.inputmethod;
-import android.app.ActivityManager;
+import android.annotation.UiThread;
+import android.content.ContentResolver;
import android.content.Context;
-import android.os.RemoteException;
import android.util.Log;
-import android.util.Slog;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
-import android.view.inputmethod.InputMethodSubtype;
-
-import com.android.internal.inputmethod.InputMethodUtils;
-import com.android.internal.inputmethod.InputMethodUtils.InputMethodSettings;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
-import java.util.Locale;
/**
- * This class is a wrapper for InputMethodSettings. You need to refresh internal states
- * manually on some events when "InputMethodInfo"s and "InputMethodSubtype"s can be
- * changed.
+ * This class is a wrapper for {@link InputMethodManager} and
+ * {@link android.provider.Settings.Secure#ENABLED_INPUT_METHODS}. You need to refresh internal
+ * states manually on some events when "InputMethodInfo"s and "InputMethodSubtype"s can be changed.
+ *
+ * <p>TODO: Consolidate this with {@link InputMethodAndSubtypeUtil}.</p>
*/
-// TODO: Consolidate this with {@link InputMethodAndSubtypeUtil}.
+@UiThread
public class InputMethodSettingValuesWrapper {
private static final String TAG = InputMethodSettingValuesWrapper.class.getSimpleName();
private static volatile InputMethodSettingValuesWrapper sInstance;
private final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>();
- private final HashMap<String, InputMethodInfo> mMethodMap = new HashMap<>();
- private final InputMethodSettings mSettings;
+ private final ContentResolver mContentResolver;
private final InputMethodManager mImm;
- private final HashSet<InputMethodInfo> mAsciiCapableEnabledImis = new HashSet<>();
public static InputMethodSettingValuesWrapper getInstance(Context context) {
if (sInstance == null) {
@@ -61,87 +55,42 @@ public class InputMethodSettingValuesWrapper {
return sInstance;
}
- private static int getDefaultCurrentUserId() {
- try {
- return ActivityManager.getService().getCurrentUser().id;
- } catch (RemoteException e) {
- Slog.w(TAG, "Couldn't get current user ID; guessing it's 0", e);
- }
- return 0;
- }
-
// Ensure singleton
private InputMethodSettingValuesWrapper(Context context) {
- mSettings = new InputMethodSettings(context.getResources(), context.getContentResolver(),
- mMethodMap, mMethodList, getDefaultCurrentUserId(), false /* copyOnWrite */);
- mImm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
+ mContentResolver = context.getContentResolver();
+ mImm = context.getSystemService(InputMethodManager.class);
refreshAllInputMethodAndSubtypes();
}
public void refreshAllInputMethodAndSubtypes() {
- synchronized (mMethodMap) {
- mMethodList.clear();
- mMethodMap.clear();
- final List<InputMethodInfo> imms = mImm.getInputMethodList();
- mMethodList.addAll(imms);
- for (InputMethodInfo imi : imms) {
- mMethodMap.put(imi.getId(), imi);
- }
- updateAsciiCapableEnabledImis();
- }
- }
-
- // TODO: Add a cts to ensure at least one AsciiCapableSubtypeEnabledImis exist
- private void updateAsciiCapableEnabledImis() {
- synchronized (mMethodMap) {
- mAsciiCapableEnabledImis.clear();
- final List<InputMethodInfo> enabledImis = mSettings.getEnabledInputMethodListLocked();
- for (final InputMethodInfo imi : enabledImis) {
- final int subtypeCount = imi.getSubtypeCount();
- for (int i = 0; i < subtypeCount; ++i) {
- final InputMethodSubtype subtype = imi.getSubtypeAt(i);
- if (InputMethodUtils.SUBTYPE_MODE_KEYBOARD.equalsIgnoreCase(subtype.getMode())
- && subtype.isAsciiCapable()) {
- mAsciiCapableEnabledImis.add(imi);
- break;
- }
- }
- }
- }
+ mMethodList.clear();
+ mMethodList.addAll(mImm.getInputMethodList());
}
public List<InputMethodInfo> getInputMethodList() {
- synchronized (mMethodMap) {
- return mMethodList;
- }
+ return new ArrayList<>(mMethodList);
}
- public boolean isAlwaysCheckedIme(InputMethodInfo imi, Context context) {
+ public boolean isAlwaysCheckedIme(InputMethodInfo imi) {
final boolean isEnabled = isEnabledImi(imi);
- synchronized (mMethodMap) {
- if (mSettings.getEnabledInputMethodListLocked().size() <= 1 && isEnabled) {
- return true;
- }
+ if (getEnabledInputMethodList().size() <= 1 && isEnabled) {
+ return true;
}
final int enabledValidSystemNonAuxAsciiCapableImeCount =
- getEnabledValidSystemNonAuxAsciiCapableImeCount(context);
+ getEnabledValidSystemNonAuxAsciiCapableImeCount();
return enabledValidSystemNonAuxAsciiCapableImeCount <= 1
&& !(enabledValidSystemNonAuxAsciiCapableImeCount == 1 && !isEnabled)
- && InputMethodUtils.isSystemIme(imi)
- && isValidSystemNonAuxAsciiCapableIme(imi, context);
-
+ && imi.isSystem()
+ && InputMethodAndSubtypeUtil.isValidSystemNonAuxAsciiCapableIme(imi);
}
- private int getEnabledValidSystemNonAuxAsciiCapableImeCount(Context context) {
+ private int getEnabledValidSystemNonAuxAsciiCapableImeCount() {
int count = 0;
- final List<InputMethodInfo> enabledImis;
- synchronized (mMethodMap) {
- enabledImis = mSettings.getEnabledInputMethodListLocked();
- }
+ final List<InputMethodInfo> enabledImis = getEnabledInputMethodList();
for (final InputMethodInfo imi : enabledImis) {
- if (isValidSystemNonAuxAsciiCapableIme(imi, context)) {
+ if (InputMethodAndSubtypeUtil.isValidSystemNonAuxAsciiCapableIme(imi)) {
++count;
}
}
@@ -152,10 +101,7 @@ public class InputMethodSettingValuesWrapper {
}
public boolean isEnabledImi(InputMethodInfo imi) {
- final List<InputMethodInfo> enabledImis;
- synchronized (mMethodMap) {
- enabledImis = mSettings.getEnabledInputMethodListLocked();
- }
+ final List<InputMethodInfo> enabledImis = getEnabledInputMethodList();
for (final InputMethodInfo tempImi : enabledImis) {
if (tempImi.getId().equals(imi.getId())) {
return true;
@@ -164,22 +110,22 @@ public class InputMethodSettingValuesWrapper {
return false;
}
- public boolean isValidSystemNonAuxAsciiCapableIme(InputMethodInfo imi, Context context) {
- if (imi.isAuxiliaryIme()) {
- return false;
- }
- final Locale systemLocale = context.getResources().getConfiguration().locale;
- if (InputMethodUtils.isSystemImeThatHasSubtypeOf(imi, context,
- true /* checkDefaultAttribute */, systemLocale, false /* checkCountry */,
- InputMethodUtils.SUBTYPE_MODE_ANY)) {
- return true;
- }
- if (mAsciiCapableEnabledImis.isEmpty()) {
- Log.w(TAG, "ascii capable subtype enabled imi not found. Fall back to English"
- + " Keyboard subtype.");
- return InputMethodUtils.containsSubtypeOf(imi, Locale.ENGLISH, false /* checkCountry */,
- InputMethodUtils.SUBTYPE_MODE_KEYBOARD);
+ /**
+ * Returns the list of the enabled {@link InputMethodInfo} determined by
+ * {@link android.provider.Settings.Secure#ENABLED_INPUT_METHODS} rather than just returning
+ * {@link InputMethodManager#getEnabledInputMethodList()}.
+ *
+ * @return the list of the enabled {@link InputMethodInfo}
+ */
+ private ArrayList<InputMethodInfo> getEnabledInputMethodList() {
+ final HashMap<String, HashSet<String>> enabledInputMethodsAndSubtypes =
+ InputMethodAndSubtypeUtil.getEnabledInputMethodsAndSubtypeList(mContentResolver);
+ final ArrayList<InputMethodInfo> result = new ArrayList<>();
+ for (InputMethodInfo imi : mMethodList) {
+ if (enabledInputMethodsAndSubtypes.keySet().contains(imi.getId())) {
+ result.add(imi);
+ }
}
- return mAsciiCapableEnabledImis.contains(imi);
+ return result;
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSubtypePreference.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSubtypePreference.java
index a0b1df25baf6..69bfffb55616 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSubtypePreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSubtypePreference.java
@@ -17,13 +17,13 @@
package com.android.settingslib.inputmethod;
import android.content.Context;
-import androidx.preference.Preference;
import android.text.TextUtils;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
+import androidx.preference.Preference;
+
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.inputmethod.InputMethodUtils;
import java.text.Collator;
import java.util.Locale;
@@ -42,7 +42,7 @@ public class InputMethodSubtypePreference extends SwitchWithNoTextPreference {
this(context,
imi.getId() + subtype.hashCode(),
InputMethodAndSubtypeUtil.getSubtypeLocaleNameAsSentence(subtype, context, imi),
- subtype.getLocale(),
+ subtype.getLocaleObject(),
context.getResources().getConfiguration().locale);
}
@@ -51,20 +51,19 @@ public class InputMethodSubtypePreference extends SwitchWithNoTextPreference {
final Context context,
final String prefKey,
final CharSequence title,
- final String subtypeLocaleString,
+ final Locale subtypeLocale,
final Locale systemLocale) {
super(context);
setPersistent(false);
setKey(prefKey);
setTitle(title);
- if (TextUtils.isEmpty(subtypeLocaleString)) {
+ if (subtypeLocale == null) {
mIsSystemLocale = false;
mIsSystemLanguage = false;
} else {
- mIsSystemLocale = subtypeLocaleString.equals(systemLocale.toString());
+ mIsSystemLocale = subtypeLocale.equals(systemLocale);
mIsSystemLanguage = mIsSystemLocale
- || InputMethodUtils.getLanguageFromLocaleString(subtypeLocaleString)
- .equals(systemLocale.getLanguage());
+ || TextUtils.equals(subtypeLocale.getLanguage(), systemLocale.getLanguage());
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/SwitchWithNoTextPreference.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/SwitchWithNoTextPreference.java
index 2b38697a65ce..2360298822ba 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/SwitchWithNoTextPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/SwitchWithNoTextPreference.java
@@ -17,6 +17,7 @@
package com.android.settingslib.inputmethod;
import android.content.Context;
+
import androidx.preference.SwitchPreference;
public class SwitchWithNoTextPreference extends SwitchPreference {
diff --git a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java
index b30950916136..9db4a35c1d78 100644
--- a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java
+++ b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java
@@ -16,11 +16,12 @@
package com.android.settingslib.license;
-import androidx.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.Log;
import android.util.Xml;
+import androidx.annotation.VisibleForTesting;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -45,7 +46,7 @@ import java.util.zip.GZIPInputStream;
* TODO: Remove duplicate codes once backward support ends.
*/
class LicenseHtmlGeneratorFromXml {
- private static final String TAG = "LicenseHtmlGeneratorFromXml";
+ private static final String TAG = "LicenseGeneratorFromXml";
private static final String TAG_ROOT = "licenses";
private static final String TAG_FILE_NAME = "file-name";
diff --git a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoader.java b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoader.java
index 03cbad72c9b0..8c03918ce266 100644
--- a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoader.java
+++ b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoader.java
@@ -17,15 +17,10 @@
package com.android.settingslib.license;
import android.content.Context;
-import androidx.annotation.VisibleForTesting;
-import android.util.Log;
-import com.android.settingslib.R;
import com.android.settingslib.utils.AsyncLoader;
import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
/**
* LicenseHtmlLoader is a loader which loads a license html file from default license xml files.
@@ -33,15 +28,6 @@ import java.util.List;
public class LicenseHtmlLoader extends AsyncLoader<File> {
private static final String TAG = "LicenseHtmlLoader";
- private static final String[] DEFAULT_LICENSE_XML_PATHS = {
- "/system/etc/NOTICE.xml.gz",
- "/vendor/etc/NOTICE.xml.gz",
- "/odm/etc/NOTICE.xml.gz",
- "/oem/etc/NOTICE.xml.gz",
- "/product/etc/NOTICE.xml.gz",
- "/product_services/etc/NOTICE.xml.gz"};
- private static final String NOTICE_HTML_FILE_NAME = "NOTICE.html";
-
private Context mContext;
public LicenseHtmlLoader(Context context) {
@@ -51,64 +37,10 @@ public class LicenseHtmlLoader extends AsyncLoader<File> {
@Override
public File loadInBackground() {
- return generateHtmlFromDefaultXmlFiles();
+ return new LicenseHtmlLoaderCompat(mContext).loadInBackground();
}
@Override
protected void onDiscardResult(File f) {
}
-
- private File generateHtmlFromDefaultXmlFiles() {
- final List<File> xmlFiles = getVaildXmlFiles();
- if (xmlFiles.isEmpty()) {
- Log.e(TAG, "No notice file exists.");
- return null;
- }
-
- File cachedHtmlFile = getCachedHtmlFile();
- if (!isCachedHtmlFileOutdated(xmlFiles, cachedHtmlFile)
- || generateHtmlFile(xmlFiles, cachedHtmlFile)) {
- return cachedHtmlFile;
- }
-
- return null;
- }
-
- @VisibleForTesting
- List<File> getVaildXmlFiles() {
- final List<File> xmlFiles = new ArrayList();
- for (final String xmlPath : DEFAULT_LICENSE_XML_PATHS) {
- File file = new File(xmlPath);
- if (file.exists() && file.length() != 0) {
- xmlFiles.add(file);
- }
- }
- return xmlFiles;
- }
-
- @VisibleForTesting
- File getCachedHtmlFile() {
- return new File(mContext.getCacheDir(), NOTICE_HTML_FILE_NAME);
- }
-
- @VisibleForTesting
- boolean isCachedHtmlFileOutdated(List<File> xmlFiles, File cachedHtmlFile) {
- boolean outdated = true;
- if (cachedHtmlFile.exists() && cachedHtmlFile.length() != 0) {
- outdated = false;
- for (File file : xmlFiles) {
- if (cachedHtmlFile.lastModified() < file.lastModified()) {
- outdated = true;
- break;
- }
- }
- }
- return outdated;
- }
-
- @VisibleForTesting
- boolean generateHtmlFile(List<File> xmlFiles, File htmlFile) {
- return LicenseHtmlGeneratorFromXml.generateHtml(xmlFiles, htmlFile,
- mContext.getString(R.string.notice_header));
- }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java
new file mode 100644
index 000000000000..0b6996365372
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2018 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.settingslib.license;
+
+import android.content.Context;
+import android.util.Log;
+
+import com.android.settingslib.R;
+import com.android.settingslib.utils.AsyncLoaderCompat;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * LicenseHtmlLoader is a loader which loads a license html file from default license xml files.
+ */
+public class LicenseHtmlLoaderCompat extends AsyncLoaderCompat<File> {
+ private static final String TAG = "LicenseHtmlLoaderCompat";
+
+ static final String[] DEFAULT_LICENSE_XML_PATHS = {
+ "/system/etc/NOTICE.xml.gz",
+ "/vendor/etc/NOTICE.xml.gz",
+ "/odm/etc/NOTICE.xml.gz",
+ "/oem/etc/NOTICE.xml.gz",
+ "/product/etc/NOTICE.xml.gz",
+ "/product_services/etc/NOTICE.xml.gz"};
+ static final String NOTICE_HTML_FILE_NAME = "NOTICE.html";
+
+ private final Context mContext;
+
+ public LicenseHtmlLoaderCompat(Context context) {
+ super(context);
+ mContext = context;
+ }
+
+ @Override
+ public File loadInBackground() {
+ return generateHtmlFromDefaultXmlFiles();
+ }
+
+ @Override
+ protected void onDiscardResult(File f) {
+ }
+
+ private File generateHtmlFromDefaultXmlFiles() {
+ final List<File> xmlFiles = getVaildXmlFiles();
+ if (xmlFiles.isEmpty()) {
+ Log.e(TAG, "No notice file exists.");
+ return null;
+ }
+
+ File cachedHtmlFile = getCachedHtmlFile(mContext);
+ if (!isCachedHtmlFileOutdated(xmlFiles, cachedHtmlFile)
+ || generateHtmlFile(mContext, xmlFiles, cachedHtmlFile)) {
+ return cachedHtmlFile;
+ }
+
+ return null;
+ }
+
+ private List<File> getVaildXmlFiles() {
+ final List<File> xmlFiles = new ArrayList();
+ for (final String xmlPath : DEFAULT_LICENSE_XML_PATHS) {
+ File file = new File(xmlPath);
+ if (file.exists() && file.length() != 0) {
+ xmlFiles.add(file);
+ }
+ }
+ return xmlFiles;
+ }
+
+ private File getCachedHtmlFile(Context context) {
+ return new File(context.getCacheDir(), NOTICE_HTML_FILE_NAME);
+ }
+
+ private boolean isCachedHtmlFileOutdated(List<File> xmlFiles, File cachedHtmlFile) {
+ boolean outdated = true;
+ if (cachedHtmlFile.exists() && cachedHtmlFile.length() != 0) {
+ outdated = false;
+ for (File file : xmlFiles) {
+ if (cachedHtmlFile.lastModified() < file.lastModified()) {
+ outdated = true;
+ break;
+ }
+ }
+ }
+ return outdated;
+ }
+
+ private boolean generateHtmlFile(Context context, List<File> xmlFiles, File htmlFile) {
+ return LicenseHtmlGeneratorFromXml.generateHtml(xmlFiles, htmlFile,
+ context.getString(R.string.notice_header));
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/location/InjectedSetting.java b/packages/SettingsLib/src/com/android/settingslib/location/InjectedSetting.java
new file mode 100644
index 000000000000..1805f1a90d10
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/location/InjectedSetting.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2018 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.settingslib.location;
+
+import android.content.Intent;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.annotations.Immutable;
+
+import java.util.Objects;
+
+/**
+ * Specifies a setting that is being injected into Settings &gt; Location &gt; Location services.
+ *
+ * @see android.location.SettingInjectorService
+ */
+@Immutable
+public class InjectedSetting {
+
+ /**
+ * Package for the subclass of {@link android.location.SettingInjectorService} and for the
+ * settings activity.
+ */
+ public final String packageName;
+
+ /**
+ * Class name for the subclass of {@link android.location.SettingInjectorService} that
+ * specifies dynamic values for the location setting.
+ */
+ public final String className;
+
+ /**
+ * The {@link android.support.v7.preference.Preference#getTitle()} value.
+ */
+ public final String title;
+
+ /**
+ * The {@link android.support.v7.preference.Preference#getIcon()} value.
+ */
+ public final int iconId;
+
+ /**
+ * The user/profile associated with this setting (e.g. managed profile)
+ */
+ public final UserHandle mUserHandle;
+
+ /**
+ * The activity to launch to allow the user to modify the settings value. Assumed to be in the
+ * {@link #packageName} package.
+ */
+ public final String settingsActivity;
+
+ /**
+ * The user restriction associated with this setting.
+ */
+ public final String userRestriction;
+
+ private InjectedSetting(Builder builder) {
+ this.packageName = builder.mPackageName;
+ this.className = builder.mClassName;
+ this.title = builder.mTitle;
+ this.iconId = builder.mIconId;
+ this.mUserHandle = builder.mUserHandle;
+ this.settingsActivity = builder.mSettingsActivity;
+ this.userRestriction = builder.mUserRestriction;
+ }
+
+ @Override
+ public String toString() {
+ return "InjectedSetting{" +
+ "mPackageName='" + packageName + '\'' +
+ ", mClassName='" + className + '\'' +
+ ", label=" + title +
+ ", iconId=" + iconId +
+ ", userId=" + mUserHandle.getIdentifier() +
+ ", settingsActivity='" + settingsActivity + '\'' +
+ ", userRestriction='" + userRestriction +
+ '}';
+ }
+
+ /**
+ * Returns the intent to start the {@link #className} service.
+ */
+ public Intent getServiceIntent() {
+ Intent intent = new Intent();
+ intent.setClassName(packageName, className);
+ return intent;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof InjectedSetting)) return false;
+
+ InjectedSetting that = (InjectedSetting) o;
+
+ return Objects.equals(packageName, that.packageName)
+ && Objects.equals(className, that.className)
+ && Objects.equals(title, that.title)
+ && Objects.equals(iconId, that.iconId)
+ && Objects.equals(mUserHandle, that.mUserHandle)
+ && Objects.equals(settingsActivity, that.settingsActivity)
+ && Objects.equals(userRestriction, that.userRestriction);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = packageName.hashCode();
+ result = 31 * result + className.hashCode();
+ result = 31 * result + title.hashCode();
+ result = 31 * result + iconId;
+ result = 31 * result + (mUserHandle == null ? 0 : mUserHandle.hashCode());
+ result = 31 * result + settingsActivity.hashCode();
+ result = 31 * result + (userRestriction == null ? 0 : userRestriction.hashCode());
+ return result;
+ }
+
+ public static class Builder {
+ private String mPackageName;
+ private String mClassName;
+ private String mTitle;
+ private int mIconId;
+ private UserHandle mUserHandle;
+ private String mSettingsActivity;
+ private String mUserRestriction;
+
+ public Builder setPackageName(String packageName) {
+ mPackageName = packageName;
+ return this;
+ }
+
+ public Builder setClassName(String className) {
+ mClassName = className;
+ return this;
+ }
+
+ public Builder setTitle(String title) {
+ mTitle = title;
+ return this;
+ }
+
+ public Builder setIconId(int iconId) {
+ mIconId = iconId;
+ return this;
+ }
+
+ public Builder setUserHandle(UserHandle userHandle) {
+ mUserHandle = userHandle;
+ return this;
+ }
+
+ public Builder setSettingsActivity(String settingsActivity) {
+ mSettingsActivity = settingsActivity;
+ return this;
+ }
+
+ public Builder setUserRestriction(String userRestriction) {
+ mUserRestriction = userRestriction;
+ return this;
+ }
+
+ public InjectedSetting build() {
+ if (mPackageName == null || mClassName == null || TextUtils.isEmpty(mTitle)
+ || TextUtils.isEmpty(mSettingsActivity)) {
+ if (Log.isLoggable(SettingsInjector.TAG, Log.WARN)) {
+ Log.w(SettingsInjector.TAG, "Illegal setting specification: package="
+ + mPackageName + ", class=" + mClassName
+ + ", title=" + mTitle + ", settingsActivity=" + mSettingsActivity);
+ }
+ return null;
+ }
+ return new InjectedSetting(this);
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
index 96bed93165fb..b8e1251dd79a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
+++ b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
@@ -25,10 +25,12 @@ import android.graphics.drawable.Drawable;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
-import androidx.annotation.VisibleForTesting;
import android.text.format.DateUtils;
import android.util.IconDrawableFactory;
import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
diff --git a/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java b/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java
new file mode 100644
index 000000000000..780fcbab9822
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java
@@ -0,0 +1,576 @@
+/*
+ * Copyright (C) 2018 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.settingslib.location;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.location.SettingInjectorService;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.AttributeSet;
+import android.util.IconDrawableFactory;
+import android.util.Log;
+import android.util.Xml;
+
+import androidx.preference.Preference;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Adds the preferences specified by the {@link InjectedSetting} objects to a preference group.
+ *
+ * Duplicates some code from {@link android.content.pm.RegisteredServicesCache}. We do not use that
+ * class directly because it is not a good match for our use case: we do not need the caching, and
+ * so do not want the additional resource hit at app install/upgrade time; and we would have to
+ * suppress the tie-breaking between multiple services reporting settings with the same name.
+ * Code-sharing would require extracting {@link
+ * android.content.pm.RegisteredServicesCache#parseServiceAttributes(android.content.res.Resources,
+ * String, android.util.AttributeSet)} into an interface, which didn't seem worth it.
+ */
+public class SettingsInjector {
+ static final String TAG = "SettingsInjector";
+
+ /**
+ * If reading the status of a setting takes longer than this, we go ahead and start reading
+ * the next setting.
+ */
+ private static final long INJECTED_STATUS_UPDATE_TIMEOUT_MILLIS = 1000;
+
+ /**
+ * {@link Message#what} value for starting to load status values
+ * in case we aren't already in the process of loading them.
+ */
+ private static final int WHAT_RELOAD = 1;
+
+ /**
+ * {@link Message#what} value sent after receiving a status message.
+ */
+ private static final int WHAT_RECEIVED_STATUS = 2;
+
+ /**
+ * {@link Message#what} value sent after the timeout waiting for a status message.
+ */
+ private static final int WHAT_TIMEOUT = 3;
+
+ private final Context mContext;
+
+ /**
+ * The settings that were injected
+ */
+ protected final Set<Setting> mSettings;
+
+ private final Handler mHandler;
+
+ public SettingsInjector(Context context) {
+ mContext = context;
+ mSettings = new HashSet<Setting>();
+ mHandler = new StatusLoadingHandler();
+ }
+
+ /**
+ * Returns a list for a profile with one {@link InjectedSetting} object for each
+ * {@link android.app.Service} that responds to
+ * {@link SettingInjectorService#ACTION_SERVICE_INTENT} and provides the expected setting
+ * metadata.
+ *
+ * Duplicates some code from {@link android.content.pm.RegisteredServicesCache}.
+ *
+ * TODO: unit test
+ */
+ protected List<InjectedSetting> getSettings(final UserHandle userHandle) {
+ PackageManager pm = mContext.getPackageManager();
+ Intent intent = new Intent(SettingInjectorService.ACTION_SERVICE_INTENT);
+
+ final int profileId = userHandle.getIdentifier();
+ List<ResolveInfo> resolveInfos =
+ pm.queryIntentServicesAsUser(intent, PackageManager.GET_META_DATA, profileId);
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Found services for profile id " + profileId + ": " + resolveInfos);
+ }
+ List<InjectedSetting> settings = new ArrayList<InjectedSetting>(resolveInfos.size());
+ for (ResolveInfo resolveInfo : resolveInfos) {
+ try {
+ InjectedSetting setting = parseServiceInfo(resolveInfo, userHandle, pm);
+ if (setting == null) {
+ Log.w(TAG, "Unable to load service info " + resolveInfo);
+ } else {
+ settings.add(setting);
+ }
+ } catch (XmlPullParserException e) {
+ Log.w(TAG, "Unable to load service info " + resolveInfo, e);
+ } catch (IOException e) {
+ Log.w(TAG, "Unable to load service info " + resolveInfo, e);
+ }
+ }
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Loaded settings for profile id " + profileId + ": " + settings);
+ }
+
+ return settings;
+ }
+
+ /**
+ * Adds the InjectedSetting information to a Preference object
+ */
+ private void populatePreference(Preference preference, InjectedSetting setting) {
+ final PackageManager pm = mContext.getPackageManager();
+ Drawable appIcon = null;
+ try {
+ final PackageItemInfo itemInfo = new PackageItemInfo();
+ itemInfo.icon = setting.iconId;
+ itemInfo.packageName = setting.packageName;
+ final ApplicationInfo appInfo = pm.getApplicationInfo(setting.packageName,
+ PackageManager.GET_META_DATA);
+ appIcon = IconDrawableFactory.newInstance(mContext)
+ .getBadgedIcon(itemInfo, appInfo, setting.mUserHandle.getIdentifier());
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Can't get ApplicationInfo for " + setting.packageName, e);
+ }
+ preference.setTitle(setting.title);
+ preference.setSummary(null);
+ preference.setIcon(appIcon);
+ preference.setOnPreferenceClickListener(new ServiceSettingClickedListener(setting));
+ }
+
+ /**
+ * Gets a list of preferences that other apps have injected.
+ *
+ * @param profileId Identifier of the user/profile to obtain the injected settings for or
+ * UserHandle.USER_CURRENT for all profiles associated with current user.
+ */
+ public List<Preference> getInjectedSettings(Context prefContext, final int profileId) {
+ final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ final List<UserHandle> profiles = um.getUserProfiles();
+ ArrayList<Preference> prefs = new ArrayList<>();
+ for (UserHandle userHandle : profiles) {
+ if (profileId == UserHandle.USER_CURRENT || profileId == userHandle.getIdentifier()) {
+ Iterable<InjectedSetting> settings = getSettings(userHandle);
+ for (InjectedSetting setting : settings) {
+ Preference preference = createPreference(prefContext, setting);
+ populatePreference(preference, setting);
+ prefs.add(preference);
+ mSettings.add(new Setting(setting, preference));
+ }
+ }
+ }
+
+ reloadStatusMessages();
+
+ return prefs;
+ }
+
+ /**
+ * Creates an injected Preference
+ *
+ * @return the created Preference
+ */
+ protected Preference createPreference(Context prefContext, InjectedSetting setting) {
+ return new Preference(prefContext);
+ }
+
+ /**
+ * Returns the settings parsed from the attributes of the
+ * {@link SettingInjectorService#META_DATA_NAME} tag, or null.
+ *
+ * Duplicates some code from {@link android.content.pm.RegisteredServicesCache}.
+ */
+ private static InjectedSetting parseServiceInfo(ResolveInfo service, UserHandle userHandle,
+ PackageManager pm) throws XmlPullParserException, IOException {
+
+ ServiceInfo si = service.serviceInfo;
+ ApplicationInfo ai = si.applicationInfo;
+
+ if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+ if (Log.isLoggable(TAG, Log.WARN)) {
+ Log.w(TAG, "Ignoring attempt to inject setting from app not in system image: "
+ + service);
+ return null;
+ }
+ }
+
+ XmlResourceParser parser = null;
+ try {
+ parser = si.loadXmlMetaData(pm, SettingInjectorService.META_DATA_NAME);
+ if (parser == null) {
+ throw new XmlPullParserException("No " + SettingInjectorService.META_DATA_NAME
+ + " meta-data for " + service + ": " + si);
+ }
+
+ AttributeSet attrs = Xml.asAttributeSet(parser);
+
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ }
+
+ String nodeName = parser.getName();
+ if (!SettingInjectorService.ATTRIBUTES_NAME.equals(nodeName)) {
+ throw new XmlPullParserException("Meta-data does not start with "
+ + SettingInjectorService.ATTRIBUTES_NAME + " tag");
+ }
+
+ Resources res = pm.getResourcesForApplicationAsUser(si.packageName,
+ userHandle.getIdentifier());
+ return parseAttributes(si.packageName, si.name, userHandle, res, attrs);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new XmlPullParserException(
+ "Unable to load resources for package " + si.packageName);
+ } finally {
+ if (parser != null) {
+ parser.close();
+ }
+ }
+ }
+
+ /**
+ * Returns an immutable representation of the static attributes for the setting, or null.
+ */
+ private static InjectedSetting parseAttributes(String packageName, String className,
+ UserHandle userHandle, Resources res, AttributeSet attrs) {
+
+ TypedArray sa = res.obtainAttributes(attrs, android.R.styleable.SettingInjectorService);
+ try {
+ // Note that to help guard against malicious string injection, we do not allow dynamic
+ // specification of the label (setting title)
+ final String title = sa.getString(android.R.styleable.SettingInjectorService_title);
+ final int iconId =
+ sa.getResourceId(android.R.styleable.SettingInjectorService_icon, 0);
+ final String settingsActivity =
+ sa.getString(android.R.styleable.SettingInjectorService_settingsActivity);
+ final String userRestriction = sa.getString(
+ android.R.styleable.SettingInjectorService_userRestriction);
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "parsed title: " + title + ", iconId: " + iconId
+ + ", settingsActivity: " + settingsActivity);
+ }
+ return new InjectedSetting.Builder()
+ .setPackageName(packageName)
+ .setClassName(className)
+ .setTitle(title)
+ .setIconId(iconId)
+ .setUserHandle(userHandle)
+ .setSettingsActivity(settingsActivity)
+ .setUserRestriction(userRestriction)
+ .build();
+ } finally {
+ sa.recycle();
+ }
+ }
+
+ /**
+ * Checks wheteher there is any preference that other apps have injected.
+ *
+ * @param profileId Identifier of the user/profile to obtain the injected settings for or
+ * UserHandle.USER_CURRENT for all profiles associated with current user.
+ */
+ public boolean hasInjectedSettings(final int profileId) {
+ final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ final List<UserHandle> profiles = um.getUserProfiles();
+ final int profileCount = profiles.size();
+ for (int i = 0; i < profileCount; ++i) {
+ final UserHandle userHandle = profiles.get(i);
+ if (profileId == UserHandle.USER_CURRENT || profileId == userHandle.getIdentifier()) {
+ Iterable<InjectedSetting> settings = getSettings(userHandle);
+ for (InjectedSetting setting : settings) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Reloads the status messages for all the preference items.
+ */
+ public void reloadStatusMessages() {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "reloadingStatusMessages: " + mSettings);
+ }
+ mHandler.sendMessage(mHandler.obtainMessage(WHAT_RELOAD));
+ }
+
+ protected class ServiceSettingClickedListener
+ implements Preference.OnPreferenceClickListener {
+ private InjectedSetting mInfo;
+
+ public ServiceSettingClickedListener(InjectedSetting info) {
+ mInfo = info;
+ }
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ // 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(mInfo.packageName, mInfo.settingsActivity);
+ // Sometimes the user may navigate back to "Settings" and launch another different
+ // injected setting after one injected setting has been launched.
+ //
+ // FLAG_ACTIVITY_CLEAR_TOP allows multiple Activities to stack on each other. When
+ // "back" button is clicked, the user will navigate through all the injected settings
+ // launched before. Such behavior could be quite confusing sometimes.
+ //
+ // In order to avoid such confusion, we use FLAG_ACTIVITY_CLEAR_TASK, which always clear
+ // up all existing injected settings and make sure that "back" button always brings the
+ // user back to "Settings" directly.
+ settingIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ mContext.startActivityAsUser(settingIntent, mInfo.mUserHandle);
+ return true;
+ }
+ }
+
+ /**
+ * Loads the setting status values one at a time. Each load starts a subclass of {@link
+ * SettingInjectorService}, so to reduce memory pressure we don't want to load too many at
+ * once.
+ */
+ private final class StatusLoadingHandler extends Handler {
+
+ /**
+ * Settings whose status values need to be loaded. A set is used to prevent redundant loads.
+ */
+ private Set<Setting> mSettingsToLoad = new HashSet<Setting>();
+
+ /**
+ * Settings that are being loaded now and haven't timed out. In practice this should have
+ * zero or one elements.
+ */
+ private Set<Setting> mSettingsBeingLoaded = new HashSet<Setting>();
+
+ /**
+ * Settings that are being loaded but have timed out. If only one setting has timed out, we
+ * will go ahead and start loading the next setting so that one slow load won't delay the
+ * load of the other settings.
+ */
+ private Set<Setting> mTimedOutSettings = new HashSet<Setting>();
+
+ private boolean mReloadRequested;
+
+ private StatusLoadingHandler() {
+ super(Looper.getMainLooper());
+ }
+ @Override
+ public void handleMessage(Message msg) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "handleMessage start: " + msg + ", " + this);
+ }
+
+ // Update state in response to message
+ switch (msg.what) {
+ case WHAT_RELOAD:
+ mReloadRequested = true;
+ break;
+ case WHAT_RECEIVED_STATUS:
+ final Setting receivedSetting = (Setting) msg.obj;
+ receivedSetting.maybeLogElapsedTime();
+ mSettingsBeingLoaded.remove(receivedSetting);
+ mTimedOutSettings.remove(receivedSetting);
+ removeMessages(WHAT_TIMEOUT, receivedSetting);
+ break;
+ case WHAT_TIMEOUT:
+ final Setting timedOutSetting = (Setting) msg.obj;
+ mSettingsBeingLoaded.remove(timedOutSetting);
+ mTimedOutSettings.add(timedOutSetting);
+ if (Log.isLoggable(TAG, Log.WARN)) {
+ Log.w(TAG, "Timed out after " + timedOutSetting.getElapsedTime()
+ + " millis trying to get status for: " + timedOutSetting);
+ }
+ break;
+ default:
+ Log.wtf(TAG, "Unexpected what: " + msg);
+ }
+
+ // Decide whether to load additional settings based on the new state. Start by seeing
+ // if we have headroom to load another setting.
+ if (mSettingsBeingLoaded.size() > 0 || mTimedOutSettings.size() > 1) {
+ // Don't load any more settings until one of the pending settings has completed.
+ // To reduce memory pressure, we want to be loading at most one setting (plus at
+ // most one timed-out setting) at a time. This means we'll be responsible for
+ // bringing in at most two services.
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "too many services already live for " + msg + ", " + this);
+ }
+ return;
+ }
+
+ if (mReloadRequested && mSettingsToLoad.isEmpty() && mSettingsBeingLoaded.isEmpty()
+ && mTimedOutSettings.isEmpty()) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "reloading because idle and reload requesteed " + msg + ", " + this);
+ }
+ // Reload requested, so must reload all settings
+ mSettingsToLoad.addAll(mSettings);
+ mReloadRequested = false;
+ }
+
+ // Remove the next setting to load from the queue, if any
+ Iterator<Setting> iter = mSettingsToLoad.iterator();
+ if (!iter.hasNext()) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "nothing left to do for " + msg + ", " + this);
+ }
+ return;
+ }
+ Setting setting = iter.next();
+ iter.remove();
+
+ // Request the status value
+ setting.startService();
+ mSettingsBeingLoaded.add(setting);
+
+ // Ensure that if receiving the status value takes too long, we start loading the
+ // next value anyway
+ Message timeoutMsg = obtainMessage(WHAT_TIMEOUT, setting);
+ sendMessageDelayed(timeoutMsg, INJECTED_STATUS_UPDATE_TIMEOUT_MILLIS);
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "handleMessage end " + msg + ", " + this
+ + ", started loading " + setting);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "StatusLoadingHandler{" +
+ "mSettingsToLoad=" + mSettingsToLoad +
+ ", mSettingsBeingLoaded=" + mSettingsBeingLoaded +
+ ", mTimedOutSettings=" + mTimedOutSettings +
+ ", mReloadRequested=" + mReloadRequested +
+ '}';
+ }
+ }
+
+ /**
+ * Represents an injected setting and the corresponding preference.
+ */
+ protected final class Setting {
+
+ public final InjectedSetting setting;
+ public final Preference preference;
+ public long startMillis;
+
+ public Setting(InjectedSetting setting, Preference preference) {
+ this.setting = setting;
+ this.preference = preference;
+ }
+
+ @Override
+ public String toString() {
+ return "Setting{" +
+ "setting=" + setting +
+ ", preference=" + preference +
+ '}';
+ }
+
+ /**
+ * Returns true if they both have the same {@link #setting} value. Ignores mutable
+ * {@link #preference} and {@link #startMillis} so that it's safe to use in sets.
+ */
+ @Override
+ public boolean equals(Object o) {
+ return this == o || o instanceof Setting && setting.equals(((Setting) o).setting);
+ }
+
+ @Override
+ public int hashCode() {
+ return setting.hashCode();
+ }
+
+ /**
+ * Starts the service to fetch for the current status for the setting, and updates the
+ * preference when the service replies.
+ */
+ public void startService() {
+ final ActivityManager am = (ActivityManager)
+ mContext.getSystemService(Context.ACTIVITY_SERVICE);
+ if (!am.isUserRunning(setting.mUserHandle.getIdentifier())) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Cannot start service as user "
+ + setting.mUserHandle.getIdentifier() + " is not running");
+ }
+ return;
+ }
+ Handler handler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ Bundle bundle = msg.getData();
+ boolean enabled = bundle.getBoolean(SettingInjectorService.ENABLED_KEY, true);
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, setting + ": received " + msg + ", bundle: " + bundle);
+ }
+ preference.setSummary(null);
+ preference.setEnabled(enabled);
+ mHandler.sendMessage(
+ mHandler.obtainMessage(WHAT_RECEIVED_STATUS, Setting.this));
+ }
+ };
+ Messenger messenger = new Messenger(handler);
+
+ Intent intent = setting.getServiceIntent();
+ intent.putExtra(SettingInjectorService.MESSENGER_KEY, messenger);
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, setting + ": sending update intent: " + intent
+ + ", handler: " + handler);
+ startMillis = SystemClock.elapsedRealtime();
+ } else {
+ startMillis = 0;
+ }
+
+ // Start the service, making sure that this is attributed to the user associated with
+ // the setting rather than the system user.
+ mContext.startServiceAsUser(intent, setting.mUserHandle);
+ }
+
+ public long getElapsedTime() {
+ long end = SystemClock.elapsedRealtime();
+ return end - startMillis;
+ }
+
+ public void maybeLogElapsedTime() {
+ if (Log.isLoggable(TAG, Log.DEBUG) && startMillis != 0) {
+ long elapsed = getElapsedTime();
+ Log.d(TAG, this + " update took " + elapsed + " millis");
+ }
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
new file mode 100644
index 000000000000..e5353486c23b
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2018 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.settingslib.media;
+
+import android.content.Context;
+import android.util.Log;
+
+import com.android.settingslib.R;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+
+/**
+ * BluetoothMediaDevice extends MediaDevice to represents Bluetooth device.
+ */
+public class BluetoothMediaDevice extends MediaDevice {
+
+ private static final String TAG = "BluetoothMediaDevice";
+
+ private CachedBluetoothDevice mCachedDevice;
+
+ BluetoothMediaDevice(Context context, CachedBluetoothDevice device) {
+ super(context, MediaDeviceType.TYPE_BLUETOOTH_DEVICE);
+ mCachedDevice = device;
+ }
+
+ @Override
+ public String getName() {
+ return mCachedDevice.getName();
+ }
+
+ @Override
+ public int getIcon() {
+ //TODO(b/117129183): This is not final icon for bluetooth device, just for demo.
+ return R.drawable.ic_bt_headphones_a2dp;
+ }
+
+ @Override
+ public String getId() {
+ return MediaDeviceUtils.getId(mCachedDevice);
+ }
+
+ @Override
+ public void connect() {
+ //TODO(b/117129183): add callback to notify LocalMediaManager connection state.
+ mIsConnected = mCachedDevice.setActive();
+ Log.d(TAG, "connect() device : " + getName() + ", is selected : " + mIsConnected);
+ }
+
+ @Override
+ public void disconnect() {
+ //TODO(b/117129183): disconnected last select device
+ mIsConnected = false;
+ }
+
+ /**
+ * Get current CachedBluetoothDevice
+ */
+ public CachedBluetoothDevice getCachedDevice() {
+ return mCachedDevice;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java
new file mode 100644
index 000000000000..04188e936cf6
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright 2018 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.settingslib.media;
+
+import android.app.Notification;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.settingslib.bluetooth.A2dpProfile;
+import com.android.settingslib.bluetooth.BluetoothCallback;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
+import com.android.settingslib.bluetooth.HearingAidProfile;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * BluetoothMediaManager provide interface to get Bluetooth device list.
+ */
+public class BluetoothMediaManager extends MediaManager implements BluetoothCallback {
+
+ private static final String TAG = "BluetoothMediaManager";
+
+ private final DeviceAttributeChangeCallback mCachedDeviceCallback =
+ new DeviceAttributeChangeCallback();
+
+ private LocalBluetoothManager mLocalBluetoothManager;
+ private LocalBluetoothProfileManager mProfileManager;
+
+ private MediaDevice mLastAddedDevice;
+ private MediaDevice mLastRemovedDevice;
+
+ BluetoothMediaManager(Context context, LocalBluetoothManager localBluetoothManager,
+ Notification notification) {
+ super(context, notification);
+
+ mLocalBluetoothManager = localBluetoothManager;
+ mProfileManager = mLocalBluetoothManager.getProfileManager();
+ }
+
+ @Override
+ public void startScan() {
+ mMediaDevices.clear();
+ mLocalBluetoothManager.getEventManager().registerCallback(this);
+ buildBluetoothDeviceList();
+ dispatchDeviceListAdded();
+ }
+
+ private void buildBluetoothDeviceList() {
+ addConnectedA2dpDevices();
+ addConnectedHearingAidDevices();
+ }
+
+ private void addConnectedA2dpDevices() {
+ final A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile();
+ if (a2dpProfile == null) {
+ Log.w(TAG, "addConnectedA2dpDevices() a2dp profile is null!");
+ return;
+ }
+ final List<BluetoothDevice> devices = a2dpProfile.getConnectedDevices();
+ final CachedBluetoothDeviceManager cachedBluetoothDeviceManager =
+ mLocalBluetoothManager.getCachedDeviceManager();
+
+ for (BluetoothDevice device : devices) {
+ final CachedBluetoothDevice cachedDevice =
+ cachedBluetoothDeviceManager.findDevice(device);
+
+ if (cachedDevice == null) {
+ Log.w(TAG, "Can't found CachedBluetoothDevice : " + device.getName());
+ continue;
+ }
+
+ Log.d(TAG, "addConnectedA2dpDevices() device : " + cachedDevice.getName()
+ + ", is connected : " + cachedDevice.isConnected());
+
+ if (cachedDevice.isConnected()) {
+ addMediaDevice(cachedDevice);
+ }
+ }
+ }
+
+ private void addConnectedHearingAidDevices() {
+ final HearingAidProfile hapProfile = mProfileManager.getHearingAidProfile();
+ if (hapProfile == null) {
+ Log.w(TAG, "addConnectedA2dpDevices() hap profile is null!");
+ return;
+ }
+ final List<Long> devicesHiSyncIds = new ArrayList<>();
+ final List<BluetoothDevice> devices = hapProfile.getConnectedDevices();
+ final CachedBluetoothDeviceManager cachedBluetoothDeviceManager =
+ mLocalBluetoothManager.getCachedDeviceManager();
+
+ for (BluetoothDevice device : devices) {
+ final CachedBluetoothDevice cachedDevice =
+ cachedBluetoothDeviceManager.findDevice(device);
+
+ if (cachedDevice == null) {
+ Log.w(TAG, "Can't found CachedBluetoothDevice : " + device.getName());
+ continue;
+ }
+
+ Log.d(TAG, "addConnectedHearingAidDevices() device : " + cachedDevice.getName()
+ + ", is connected : " + cachedDevice.isConnected());
+ final long hiSyncId = hapProfile.getHiSyncId(device);
+
+ // device with same hiSyncId should not be shown in the UI.
+ // So do not add it into connectedDevices.
+ if (!devicesHiSyncIds.contains(hiSyncId) && cachedDevice.isConnected()) {
+ devicesHiSyncIds.add(hiSyncId);
+ addMediaDevice(cachedDevice);
+ }
+ }
+ }
+
+ private void addMediaDevice(CachedBluetoothDevice cachedDevice) {
+ MediaDevice mediaDevice = findMediaDevice(MediaDeviceUtils.getId(cachedDevice));
+ if (mediaDevice == null) {
+ mediaDevice = new BluetoothMediaDevice(mContext, cachedDevice);
+ cachedDevice.registerCallback(mCachedDeviceCallback);
+ mLastAddedDevice = mediaDevice;
+ mMediaDevices.add(mediaDevice);
+ }
+ }
+
+ @Override
+ public void stopScan() {
+ mLocalBluetoothManager.getEventManager().unregisterCallback(this);
+ unregisterCachedDeviceCallback();
+ }
+
+ private void unregisterCachedDeviceCallback() {
+ for (MediaDevice device : mMediaDevices) {
+ if (device instanceof BluetoothMediaDevice) {
+ ((BluetoothMediaDevice) device).getCachedDevice()
+ .unregisterCallback(mCachedDeviceCallback);
+ }
+ }
+ }
+
+ @Override
+ public void onBluetoothStateChanged(int bluetoothState) {
+ if (BluetoothAdapter.STATE_ON == bluetoothState) {
+ buildBluetoothDeviceList();
+ dispatchDeviceListAdded();
+ } else if (BluetoothAdapter.STATE_OFF == bluetoothState) {
+ final List<MediaDevice> removeDevicesList = new ArrayList<>();
+ for (MediaDevice device : mMediaDevices) {
+ if (device instanceof BluetoothMediaDevice) {
+ ((BluetoothMediaDevice) device).getCachedDevice()
+ .unregisterCallback(mCachedDeviceCallback);
+ removeDevicesList.add(device);
+ }
+ }
+ mMediaDevices.removeAll(removeDevicesList);
+ dispatchDeviceListRemoved(removeDevicesList);
+ }
+ }
+
+ @Override
+ public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
+ if (isCachedDeviceConnected(cachedDevice)) {
+ addMediaDevice(cachedDevice);
+ dispatchDeviceAdded(cachedDevice);
+ }
+ }
+
+ private boolean isCachedDeviceConnected(CachedBluetoothDevice cachedDevice) {
+ final boolean isConnectedHearingAidDevice = cachedDevice.isConnectedHearingAidDevice();
+ final boolean isConnectedA2dpDevice = cachedDevice.isConnectedA2dpDevice();
+ Log.d(TAG, "isCachedDeviceConnected() cachedDevice : " + cachedDevice.getName()
+ + ", is hearing aid connected : " + isConnectedHearingAidDevice
+ + ", is a2dp connected : " + isConnectedA2dpDevice);
+
+ return isConnectedHearingAidDevice || isConnectedA2dpDevice;
+ }
+
+ private void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) {
+ if (mLastAddedDevice != null
+ && MediaDeviceUtils.getId(cachedDevice) == mLastAddedDevice.getId()) {
+ dispatchDeviceAdded(mLastAddedDevice);
+ }
+ }
+
+ @Override
+ public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
+ if (!isCachedDeviceConnected(cachedDevice)) {
+ removeMediaDevice(cachedDevice);
+ dispatchDeviceRemoved(cachedDevice);
+ }
+ }
+
+ private void removeMediaDevice(CachedBluetoothDevice cachedDevice) {
+ final MediaDevice mediaDevice = findMediaDevice(MediaDeviceUtils.getId(cachedDevice));
+ if (mediaDevice != null) {
+ cachedDevice.unregisterCallback(mCachedDeviceCallback);
+ mLastRemovedDevice = mediaDevice;
+ mMediaDevices.remove(mediaDevice);
+ }
+ }
+
+ void dispatchDeviceRemoved(CachedBluetoothDevice cachedDevice) {
+ if (mLastRemovedDevice != null
+ && MediaDeviceUtils.getId(cachedDevice) == mLastRemovedDevice.getId()) {
+ dispatchDeviceRemoved(mLastRemovedDevice);
+ }
+ }
+
+ @Override
+ public void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state,
+ int bluetoothProfile) {
+ Log.d(TAG, "onProfileConnectionStateChanged() device: " + cachedDevice.getName()
+ + ", state: " + state + ", bluetoothProfile: " + bluetoothProfile);
+
+ if (isCachedDeviceConnected(cachedDevice)) {
+ addMediaDevice(cachedDevice);
+ dispatchDeviceAdded(cachedDevice);
+ } else {
+ removeMediaDevice(cachedDevice);
+ dispatchDeviceRemoved(cachedDevice);
+ }
+ }
+
+ @Override
+ public void onAclConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
+ Log.d(TAG, "onAclConnectionStateChanged() device: " + cachedDevice.getName()
+ + ", state: " + state);
+
+ if (isCachedDeviceConnected(cachedDevice)) {
+ addMediaDevice(cachedDevice);
+ dispatchDeviceAdded(cachedDevice);
+ } else {
+ removeMediaDevice(cachedDevice);
+ dispatchDeviceRemoved(cachedDevice);
+ }
+ }
+
+ class DeviceAttributeChangeCallback implements CachedBluetoothDevice.Callback {
+ @Override
+ public void onDeviceAttributesChanged() {
+ dispatchDeviceAttributesChanged();
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
new file mode 100644
index 000000000000..498a0fc4d8b2
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2018 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.settingslib.media;
+
+import android.content.Context;
+
+import androidx.mediarouter.media.MediaRouter;
+
+import com.android.settingslib.R;
+
+/**
+ * InfoMediaDevice extends MediaDevice to represents wifi device.
+ */
+public class InfoMediaDevice extends MediaDevice {
+
+ private static final String TAG = "InfoMediaDevice";
+
+ private MediaRouter.RouteInfo mRouteInfo;
+
+ InfoMediaDevice(Context context, MediaRouter.RouteInfo info) {
+ super(context, MediaDeviceType.TYPE_CAST_DEVICE);
+ mRouteInfo = info;
+ }
+
+ @Override
+ public String getName() {
+ return mRouteInfo.getName();
+ }
+
+ @Override
+ public int getIcon() {
+ //TODO(b/117129183): This is not final icon for cast device, just for demo.
+ return R.drawable.ic_settings_print;
+ }
+
+ @Override
+ public String getId() {
+ return MediaDeviceUtils.getId(mRouteInfo);
+ }
+
+ @Override
+ public void connect() {
+ //TODO(b/117129183): use MediaController2 to transfer media
+ mIsConnected = true;
+ }
+
+ @Override
+ public void disconnect() {
+ //TODO(b/117129183): disconnected last select device
+ mIsConnected = false;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
new file mode 100644
index 000000000000..6907238082fc
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2018 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.settingslib.media;
+
+import android.app.Notification;
+import android.content.Context;
+import android.util.Log;
+
+import androidx.mediarouter.media.MediaRouteSelector;
+import androidx.mediarouter.media.MediaRouter;
+
+/**
+ * InfoMediaManager provide interface to get InfoMediaDevice list.
+ */
+public class InfoMediaManager extends MediaManager {
+
+ private static final String TAG = "InfoMediaManager";
+
+ private final MediaRouterCallback mMediaRouterCallback = new MediaRouterCallback();
+
+ private MediaRouter mMediaRouter;
+ private String mPackageName;
+
+ InfoMediaManager(Context context, String packageName, Notification notification) {
+ super(context, notification);
+
+ mMediaRouter = MediaRouter.getInstance(context);
+ mPackageName = packageName;
+ }
+
+ @Override
+ public void startScan() {
+ mMediaDevices.clear();
+ startScanCastDevice();
+ }
+
+ private void startScanCastDevice() {
+ final MediaRouteSelector selector = new MediaRouteSelector.Builder()
+ .addControlCategory(getControlCategoryByPackageName(mPackageName))
+ .build();
+
+ mMediaRouter.addCallback(selector, mMediaRouterCallback,
+ MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
+ }
+
+ private String getControlCategoryByPackageName(String packageName) {
+ //TODO(b/117129183): Use package name to get ControlCategory.
+ //Since api not ready, return fixed ControlCategory for prototype.
+ return "com.google.android.gms.cast.CATEGORY_CAST/4F8B3483";
+ }
+
+ @Override
+ public void stopScan() {
+ mMediaRouter.removeCallback(mMediaRouterCallback);
+ }
+
+ class MediaRouterCallback extends MediaRouter.Callback {
+ @Override
+ public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo route) {
+ MediaDevice mediaDevice = findMediaDevice(MediaDeviceUtils.getId(route));
+ if (mediaDevice == null) {
+ mediaDevice = new InfoMediaDevice(mContext, route);
+ Log.d(TAG, "onRouteAdded() route : " + route.getName());
+ mMediaDevices.add(mediaDevice);
+ dispatchDeviceAdded(mediaDevice);
+ }
+ }
+
+ @Override
+ public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo route) {
+ final MediaDevice mediaDevice = findMediaDevice(MediaDeviceUtils.getId(route));
+ if (mediaDevice != null) {
+ Log.d(TAG, "onRouteRemoved() route : " + route.getName());
+ mMediaDevices.remove(mediaDevice);
+ dispatchDeviceRemoved(mediaDevice);
+ }
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
new file mode 100644
index 000000000000..e375ea016ec2
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright 2018 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.settingslib.media;
+
+import android.app.Notification;
+import android.content.Context;
+import android.util.Log;
+
+import androidx.annotation.IntDef;
+
+import com.android.settingslib.bluetooth.BluetoothCallback;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * LocalMediaManager provide interface to get MediaDevice list and transfer media to MediaDevice.
+ */
+public class LocalMediaManager implements BluetoothCallback {
+
+ private static final String TAG = "LocalMediaManager";
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({MediaDeviceState.STATE_CONNECTED,
+ MediaDeviceState.STATE_CONNECTING,
+ MediaDeviceState.STATE_DISCONNECTED})
+ public @interface MediaDeviceState {
+ int STATE_CONNECTED = 1;
+ int STATE_CONNECTING = 2;
+ int STATE_DISCONNECTED = 3;
+ }
+
+ private final Collection<DeviceCallback> mCallbacks = new ArrayList<>();
+ private final MediaDeviceCallback mMediaDeviceCallback = new MediaDeviceCallback();
+
+ private Context mContext;
+ private List<MediaDevice> mMediaDevices = new ArrayList<>();
+ private BluetoothMediaManager mBluetoothMediaManager;
+ private InfoMediaManager mInfoMediaManager;
+
+ private LocalBluetoothManager mLocalBluetoothManager;
+ private MediaDevice mLastConnectedDevice;
+ private MediaDevice mPhoneDevice;
+
+ /**
+ * Register to start receiving callbacks for MediaDevice events.
+ */
+ public void registerCallback(DeviceCallback callback) {
+ synchronized (mCallbacks) {
+ mCallbacks.add(callback);
+ }
+ }
+
+ /**
+ * Unregister to stop receiving callbacks for MediaDevice events
+ */
+ public void unregisterCallback(DeviceCallback callback) {
+ synchronized (mCallbacks) {
+ mCallbacks.remove(callback);
+ }
+ }
+
+ public LocalMediaManager(Context context, String packageName, Notification notification) {
+ mContext = context;
+ mLocalBluetoothManager =
+ LocalBluetoothManager.getInstance(context, /* onInitCallback= */ null);
+ if (mLocalBluetoothManager == null) {
+ Log.e(TAG, "Bluetooth is not supported on this device");
+ return;
+ }
+
+ mBluetoothMediaManager =
+ new BluetoothMediaManager(context, mLocalBluetoothManager, notification);
+ mInfoMediaManager = new InfoMediaManager(context, packageName, notification);
+ }
+
+ /**
+ * Connect the MediaDevice to transfer media
+ * @param connectDevice the MediaDevice
+ */
+ public void connectDevice(MediaDevice connectDevice) {
+ if (connectDevice == mLastConnectedDevice) {
+ return;
+ }
+
+ if (mLastConnectedDevice != null) {
+ mLastConnectedDevice.disconnect();
+ }
+
+ connectDevice.connect();
+ if (connectDevice.isConnected()) {
+ mLastConnectedDevice = connectDevice;
+ }
+
+ final int state = connectDevice.isConnected()
+ ? MediaDeviceState.STATE_CONNECTED
+ : MediaDeviceState.STATE_DISCONNECTED;
+ dispatchSelectedDeviceStateChanged(connectDevice, state);
+ }
+
+ void dispatchSelectedDeviceStateChanged(MediaDevice device, @MediaDeviceState int state) {
+ synchronized (mCallbacks) {
+ for (DeviceCallback callback : mCallbacks) {
+ callback.onSelectedDeviceStateChanged(device, state);
+ }
+ }
+ }
+
+ /**
+ * Start scan connected MediaDevice
+ */
+ public void startScan() {
+ mMediaDevices.clear();
+ mBluetoothMediaManager.registerCallback(mMediaDeviceCallback);
+ mInfoMediaManager.registerCallback(mMediaDeviceCallback);
+ mBluetoothMediaManager.startScan();
+ mInfoMediaManager.startScan();
+ }
+
+ private void addPhoneDeviceIfNecessary() {
+ // add phone device to list if there have any Bluetooth device and cast device.
+ if (mMediaDevices.size() > 0 && !mMediaDevices.contains(mPhoneDevice)) {
+ if (mPhoneDevice == null) {
+ mPhoneDevice = new PhoneMediaDevice(mContext, mLocalBluetoothManager);
+ }
+ mMediaDevices.add(mPhoneDevice);
+ }
+ }
+
+ private void removePhoneMediaDeviceIfNecessary() {
+ // if PhoneMediaDevice is the last item in the list, remove it.
+ if (mMediaDevices.size() == 1 && mMediaDevices.contains(mPhoneDevice)) {
+ mMediaDevices.clear();
+ }
+ }
+
+ void dispatchDeviceListUpdate() {
+ synchronized (mCallbacks) {
+ for (DeviceCallback callback : mCallbacks) {
+ callback.onDeviceListUpdate(new ArrayList<>(mMediaDevices));
+ }
+ }
+ }
+
+ /**
+ * Stop scan MediaDevice
+ */
+ public void stopScan() {
+ mBluetoothMediaManager.unregisterCallback(mMediaDeviceCallback);
+ mInfoMediaManager.unregisterCallback(mMediaDeviceCallback);
+ mBluetoothMediaManager.stopScan();
+ mInfoMediaManager.stopScan();
+ }
+
+ class MediaDeviceCallback implements MediaManager.MediaDeviceCallback {
+ @Override
+ public void onDeviceAdded(MediaDevice device) {
+ if (!mMediaDevices.contains(device)) {
+ mMediaDevices.add(device);
+ addPhoneDeviceIfNecessary();
+ dispatchDeviceListUpdate();
+ }
+ }
+
+ @Override
+ public void onDeviceListAdded(List<MediaDevice> devices) {
+ mMediaDevices.addAll(devices);
+ addPhoneDeviceIfNecessary();
+ dispatchDeviceListUpdate();
+ }
+
+ @Override
+ public void onDeviceRemoved(MediaDevice device) {
+ if (mMediaDevices.contains(device)) {
+ mMediaDevices.remove(device);
+ removePhoneMediaDeviceIfNecessary();
+ dispatchDeviceListUpdate();
+ }
+ }
+
+ @Override
+ public void onDeviceListRemoved(List<MediaDevice> devices) {
+ mMediaDevices.removeAll(devices);
+ removePhoneMediaDeviceIfNecessary();
+ dispatchDeviceListUpdate();
+ }
+
+ @Override
+ public void onDeviceAttributesChanged() {
+ dispatchDeviceListUpdate();
+ }
+ }
+
+
+ /**
+ * Callback for notifying device information updating
+ */
+ public interface DeviceCallback {
+ /**
+ * Callback for notifying device list updated.
+ *
+ * @param devices MediaDevice list
+ */
+ void onDeviceListUpdate(List<MediaDevice> devices);
+
+ /**
+ * Callback for notifying the connected device is changed.
+ *
+ * @param device the changed connected MediaDevice
+ * @param state the current MediaDevice state, the possible values are:
+ * {@link MediaDeviceState#STATE_CONNECTED},
+ * {@link MediaDeviceState#STATE_CONNECTING},
+ * {@link MediaDeviceState#STATE_DISCONNECTED}
+ */
+ void onSelectedDeviceStateChanged(MediaDevice device, @MediaDeviceState int state);
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
new file mode 100644
index 000000000000..6c536f080207
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2018 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.settingslib.media;
+
+import android.content.Context;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * MediaDevice represents a media device(such like Bluetooth device, cast device and phone device).
+ */
+public abstract class MediaDevice {
+
+ private static final String TAG = "MediaDevice";
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({MediaDeviceType.TYPE_BLUETOOTH_DEVICE,
+ MediaDeviceType.TYPE_CAST_DEVICE,
+ MediaDeviceType.TYPE_PHONE_DEVICE})
+ public @interface MediaDeviceType {
+ int TYPE_BLUETOOTH_DEVICE = 1;
+ int TYPE_CAST_DEVICE = 2;
+ int TYPE_PHONE_DEVICE = 3;
+ }
+
+ protected boolean mIsConnected = false;
+ protected Context mContext;
+ protected int mType;
+
+ MediaDevice(Context context, @MediaDeviceType int type) {
+ mType = type;
+ mContext = context;
+ }
+
+ /**
+ * Check the MediaDevice is be connected to transfer.
+ *
+ * @return true if the MediaDevice is be connected to transfer, false otherwise.
+ */
+ protected boolean isConnected() {
+ return mIsConnected;
+ }
+
+ /**
+ * Get name from MediaDevice.
+ *
+ * @return name of MediaDevice.
+ */
+ public abstract String getName();
+
+ /**
+ * Get resource id of MediaDevice.
+ *
+ * @return resource id of MediaDevice.
+ */
+ public abstract int getIcon();
+
+ /**
+ * Get unique ID that represent MediaDevice
+ * @return unique id of MediaDevice
+ */
+ public abstract String getId();
+
+ /**
+ * Transfer MediaDevice for media
+ */
+ public abstract void connect();
+
+ /**
+ * Stop transfer MediaDevice
+ */
+ public abstract void disconnect();
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDeviceUtils.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDeviceUtils.java
new file mode 100644
index 000000000000..060e9ad3368f
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDeviceUtils.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2018 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.settingslib.media;
+
+import androidx.mediarouter.media.MediaRouter;
+
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+
+/**
+ * MediaDeviceUtils provides utility function for MediaDevice
+ */
+public class MediaDeviceUtils {
+
+ /**
+ * Use CachedBluetoothDevice address to represent unique id
+ *
+ * @param cachedDevice the CachedBluetoothDevice
+ * @return CachedBluetoothDevice address
+ */
+ public static String getId(CachedBluetoothDevice cachedDevice) {
+ return cachedDevice.getAddress();
+ }
+
+ /**
+ * Use RouteInfo id to represent unique id
+ *
+ * @param route the RouteInfo
+ * @return RouteInfo id
+ */
+ public static String getId(MediaRouter.RouteInfo route) {
+ return route.getId();
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
new file mode 100644
index 000000000000..72b6b09d637e
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2018 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.settingslib.media;
+
+import android.app.Notification;
+import android.content.Context;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * MediaManager provide interface to get MediaDevice list.
+ */
+public abstract class MediaManager {
+
+ private static final String TAG = "MediaManager";
+
+ protected final Collection<MediaDeviceCallback> mCallbacks = new ArrayList<>();
+ protected final List<MediaDevice> mMediaDevices = new ArrayList<>();
+
+ protected Context mContext;
+ protected Notification mNotification;
+
+ MediaManager(Context context, Notification notification) {
+ mContext = context;
+ mNotification = notification;
+ }
+
+ protected void registerCallback(MediaDeviceCallback callback) {
+ synchronized (mCallbacks) {
+ if (!mCallbacks.contains(callback)) {
+ mCallbacks.add(callback);
+ }
+ }
+ }
+
+ protected void unregisterCallback(MediaDeviceCallback callback) {
+ synchronized (mCallbacks) {
+ if (mCallbacks.contains(callback)) {
+ mCallbacks.remove(callback);
+ }
+ }
+ }
+
+ /**
+ * Start scan connected MediaDevice
+ */
+ public abstract void startScan();
+
+ /**
+ * Stop scan MediaDevice
+ */
+ public abstract void stopScan();
+
+ protected MediaDevice findMediaDevice(String id) {
+ for (MediaDevice mediaDevice : mMediaDevices) {
+ if (mediaDevice.getId().equals(id)) {
+ return mediaDevice;
+ }
+ }
+ Log.e(TAG, "findMediaDevice() can't found device");
+ return null;
+ }
+
+ protected void dispatchDeviceAdded(MediaDevice mediaDevice) {
+ synchronized (mCallbacks) {
+ for (MediaDeviceCallback callback : mCallbacks) {
+ callback.onDeviceAdded(mediaDevice);
+ }
+ }
+ }
+
+ protected void dispatchDeviceRemoved(MediaDevice mediaDevice) {
+ synchronized (mCallbacks) {
+ for (MediaDeviceCallback callback : mCallbacks) {
+ callback.onDeviceRemoved(mediaDevice);
+ }
+ }
+ }
+
+ protected void dispatchDeviceListAdded() {
+ synchronized (mCallbacks) {
+ for (MediaDeviceCallback callback : mCallbacks) {
+ callback.onDeviceListAdded(mMediaDevices);
+ }
+ }
+ }
+
+ protected void dispatchDeviceListRemoved(List<MediaDevice> devices) {
+ synchronized (mCallbacks) {
+ for (MediaDeviceCallback callback : mCallbacks) {
+ callback.onDeviceListRemoved(devices);
+ }
+ }
+ }
+
+ protected void dispatchDeviceAttributesChanged() {
+ synchronized (mCallbacks) {
+ for (MediaDeviceCallback callback : mCallbacks) {
+ callback.onDeviceAttributesChanged();
+ }
+ }
+ }
+
+ /**
+ * Callback for notifying device is added, removed and attributes changed.
+ */
+ public interface MediaDeviceCallback {
+ /**
+ * Callback for notifying MediaDevice is added.
+ *
+ * @param device the MediaDevice
+ */
+ void onDeviceAdded(MediaDevice device);
+
+ /**
+ * Callback for notifying MediaDevice list is added.
+ *
+ * @param devices the MediaDevice list
+ */
+ void onDeviceListAdded(List<MediaDevice> devices);
+
+ /**
+ * Callback for notifying MediaDevice is removed.
+ *
+ * @param device the MediaDevice
+ */
+ void onDeviceRemoved(MediaDevice device);
+
+ /**
+ * Callback for notifying MediaDevice list is removed.
+ *
+ * @param devices the MediaDevice list
+ */
+ void onDeviceListRemoved(List<MediaDevice> devices);
+
+ /**
+ * Callback for notifying MediaDevice attributes is changed.
+ */
+ void onDeviceAttributesChanged();
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
new file mode 100644
index 000000000000..5e49d6b49e11
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2018 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.settingslib.media;
+
+import android.content.Context;
+import android.util.Log;
+
+import com.android.settingslib.R;
+import com.android.settingslib.bluetooth.A2dpProfile;
+import com.android.settingslib.bluetooth.HearingAidProfile;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+
+/**
+ * PhoneMediaDevice extends MediaDevice to represents Phone device.
+ */
+public class PhoneMediaDevice extends MediaDevice {
+
+ private static final String TAG = "PhoneMediaDevice";
+
+ public static final String ID = "phone_media_device_id_1";
+
+ private LocalBluetoothProfileManager mProfileManager;
+ private LocalBluetoothManager mLocalBluetoothManager;
+
+ PhoneMediaDevice(Context context, LocalBluetoothManager localBluetoothManager) {
+ super(context, MediaDeviceType.TYPE_PHONE_DEVICE);
+
+ mLocalBluetoothManager = localBluetoothManager;
+ mProfileManager = mLocalBluetoothManager.getProfileManager();
+ }
+
+ @Override
+ public String getName() {
+ return mContext
+ .getString(com.android.settingslib.R.string.media_transfer_phone_device_name);
+ }
+
+ @Override
+ public int getIcon() {
+ //TODO(b/117129183): This is not final icon for phone device, just for demo.
+ return R.drawable.ic_bt_cellphone;
+ }
+
+ @Override
+ public String getId() {
+ return ID;
+ }
+
+ @Override
+ public void connect() {
+ final HearingAidProfile hapProfile = mProfileManager.getHearingAidProfile();
+ final A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile();
+
+ if (hapProfile != null && a2dpProfile != null) {
+ mIsConnected =
+ hapProfile.setActiveDevice(null) && a2dpProfile.setActiveDevice(null);
+ }
+ Log.d(TAG, "connect() device : " + getName() + ", is selected : " + mIsConnected);
+ }
+
+ @Override
+ public void disconnect() {
+ //TODO(b/117129183): disconnected last select device
+ mIsConnected = false;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/ChartDataLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/ChartDataLoader.java
index 223c05546b7f..60d22a0d5803 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/ChartDataLoader.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/ChartDataLoader.java
@@ -34,8 +34,11 @@ import android.os.RemoteException;
import com.android.settingslib.AppItem;
/**
- * Loader for historical chart data for both network and UID details.
+ * Framework loader is deprecated, use the compat version instead.
+ *
+ * @deprecated
*/
+@Deprecated
public class ChartDataLoader extends AsyncTaskLoader<ChartData> {
private static final String KEY_TEMPLATE = "template";
private static final String KEY_APP = "app";
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/ChartDataLoaderCompat.java b/packages/SettingsLib/src/com/android/settingslib/net/ChartDataLoaderCompat.java
new file mode 100644
index 000000000000..e9c523881373
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/net/ChartDataLoaderCompat.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2018 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.settingslib.net;
+
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.SET_FOREGROUND;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStatsHistory.FIELD_RX_BYTES;
+import static android.net.NetworkStatsHistory.FIELD_TX_BYTES;
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
+
+import android.content.Context;
+import android.net.INetworkStatsSession;
+import android.net.NetworkStatsHistory;
+import android.net.NetworkTemplate;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+import androidx.loader.content.AsyncTaskLoader;
+
+import com.android.settingslib.AppItem;
+
+/**
+ * Loader for historical chart data for both network and UID details.
+ *
+ * Deprecated in favor of {@link NetworkCycleChartDataLoader} and
+ * {@link NetworkCycleDataForUidLoader}
+ *
+ * @deprecated
+ */
+@Deprecated
+public class ChartDataLoaderCompat extends AsyncTaskLoader<ChartData> {
+ private static final String KEY_TEMPLATE = "template";
+ private static final String KEY_APP = "app";
+ private static final String KEY_FIELDS = "fields";
+
+ private final INetworkStatsSession mSession;
+ private final Bundle mArgs;
+
+ public static Bundle buildArgs(NetworkTemplate template, AppItem app) {
+ return buildArgs(template, app, FIELD_RX_BYTES | FIELD_TX_BYTES);
+ }
+
+ public static Bundle buildArgs(NetworkTemplate template, AppItem app, int fields) {
+ final Bundle args = new Bundle();
+ args.putParcelable(KEY_TEMPLATE, template);
+ args.putParcelable(KEY_APP, app);
+ args.putInt(KEY_FIELDS, fields);
+ return args;
+ }
+
+ public ChartDataLoaderCompat(Context context, INetworkStatsSession session, Bundle args) {
+ super(context);
+ mSession = session;
+ mArgs = args;
+ }
+
+ @Override
+ protected void onStartLoading() {
+ super.onStartLoading();
+ forceLoad();
+ }
+
+ @Override
+ public ChartData loadInBackground() {
+ final NetworkTemplate template = mArgs.getParcelable(KEY_TEMPLATE);
+ final AppItem app = mArgs.getParcelable(KEY_APP);
+ final int fields = mArgs.getInt(KEY_FIELDS);
+
+ try {
+ return loadInBackground(template, app, fields);
+ } catch (RemoteException e) {
+ // since we can't do much without history, and we don't want to
+ // leave with half-baked UI, we bail hard.
+ throw new RuntimeException("problem reading network stats", e);
+ }
+ }
+
+ private ChartData loadInBackground(NetworkTemplate template, AppItem app, int fields)
+ throws RemoteException {
+ final ChartData data = new ChartData();
+ data.network = mSession.getHistoryForNetwork(template, fields);
+
+ if (app != null) {
+ // load stats for current uid and template
+ final int size = app.uids.size();
+ for (int i = 0; i < size; i++) {
+ final int uid = app.uids.keyAt(i);
+ data.detailDefault = collectHistoryForUid(
+ template, uid, SET_DEFAULT, data.detailDefault);
+ data.detailForeground = collectHistoryForUid(
+ template, uid, SET_FOREGROUND, data.detailForeground);
+ }
+
+ if (size > 0) {
+ data.detail = new NetworkStatsHistory(data.detailForeground.getBucketDuration());
+ data.detail.recordEntireHistory(data.detailDefault);
+ data.detail.recordEntireHistory(data.detailForeground);
+ } else {
+ data.detailDefault = new NetworkStatsHistory(HOUR_IN_MILLIS);
+ data.detailForeground = new NetworkStatsHistory(HOUR_IN_MILLIS);
+ data.detail = new NetworkStatsHistory(HOUR_IN_MILLIS);
+ }
+ }
+
+ return data;
+ }
+
+ @Override
+ protected void onStopLoading() {
+ super.onStopLoading();
+ cancelLoad();
+ }
+
+ @Override
+ protected void onReset() {
+ super.onReset();
+ cancelLoad();
+ }
+
+ /**
+ * Collect {@link NetworkStatsHistory} for the requested UID, combining with
+ * an existing {@link NetworkStatsHistory} if provided.
+ */
+ private NetworkStatsHistory collectHistoryForUid(
+ NetworkTemplate template, int uid, int set, NetworkStatsHistory existing)
+ throws RemoteException {
+ final NetworkStatsHistory history = mSession.getHistoryForUid(
+ template, uid, set, TAG_NONE, FIELD_RX_BYTES | FIELD_TX_BYTES);
+
+ if (existing != null) {
+ existing.recordEntireHistory(history);
+ return existing;
+ } else {
+ return history;
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java
index f7aa29796ce8..183d4856ac6f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java
@@ -24,6 +24,8 @@ import static android.telephony.TelephonyManager.SIM_STATE_READY;
import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
+import android.app.usage.NetworkStats.Bucket;
+import android.app.usage.NetworkStatsManager;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.INetworkStatsService;
@@ -37,18 +39,23 @@ import android.os.ServiceManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.format.DateUtils;
+import android.util.FeatureFlagUtils;
import android.util.Log;
-import android.util.Pair;
+import android.util.Range;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import java.time.ZonedDateTime;
import java.util.Date;
+import java.util.Iterator;
import java.util.Locale;
public class DataUsageController {
private static final String TAG = "DataUsageController";
+ @VisibleForTesting
+ static final String DATA_USAGE_V2 = "settings_data_usage_v2";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final int FIELDS = FIELD_RX_BYTES | FIELD_TX_BYTES;
private static final StringBuilder PERIOD_BUILDER = new StringBuilder(50);
@@ -60,6 +67,7 @@ public class DataUsageController {
private final ConnectivityManager mConnectivityManager;
private final INetworkStatsService mStatsService;
private final NetworkPolicyManager mPolicyManager;
+ private final NetworkStatsManager mNetworkStatsManager;
private INetworkStatsSession mSession;
private Callback mCallback;
@@ -72,6 +80,7 @@ public class DataUsageController {
mStatsService = INetworkStatsService.Stub.asInterface(
ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
mPolicyManager = NetworkPolicyManager.from(mContext);
+ mNetworkStatsManager = context.getSystemService(NetworkStatsManager.class);
}
public void setNetworkController(NetworkNameProvider networkController) {
@@ -86,7 +95,9 @@ public class DataUsageController {
* mContext.getResources().getInteger(R.integer.default_data_warning_level_mb);
}
- private INetworkStatsSession getSession() {
+ @VisibleForTesting
+ @Deprecated
+ INetworkStatsSession getSession() {
if (mSession == null) {
try {
mSession = mStatsService.openSession();
@@ -125,55 +136,95 @@ public class DataUsageController {
}
public DataUsageInfo getDataUsageInfo(NetworkTemplate template) {
- final INetworkStatsSession session = getSession();
- if (session == null) {
- return warn("no stats session");
- }
final NetworkPolicy policy = findNetworkPolicy(template);
- try {
- final NetworkStatsHistory history = session.getHistoryForNetwork(template, FIELDS);
+ final long now = System.currentTimeMillis();
+ final long start, end;
+ final Iterator<Range<ZonedDateTime>> it = (policy != null) ? policy.cycleIterator() : null;
+ if (it != null && it.hasNext()) {
+ final Range<ZonedDateTime> cycle = it.next();
+ start = cycle.getLower().toInstant().toEpochMilli();
+ end = cycle.getUpper().toInstant().toEpochMilli();
+ } else {
+ // period = last 4 wks
+ end = now;
+ start = now - DateUtils.WEEK_IN_MILLIS * 4;
+ }
+ final long totalBytes;
+ final long callStart = System.currentTimeMillis();
+ if (FeatureFlagUtils.isEnabled(mContext, DATA_USAGE_V2)) {
+ totalBytes = getUsageLevel(template, start, end);
+ } else {
+ totalBytes = getUsageLevel(template, start, end, now);
+ }
+ if (totalBytes < 0L) {
+ return warn("no entry data");
+ }
+ final DataUsageInfo usage = new DataUsageInfo();
+ usage.startDate = start;
+ usage.usageLevel = totalBytes;
+ usage.period = formatDateRange(start, end);
+ usage.cycleStart = start;
+ usage.cycleEnd = end;
+
+ if (policy != null) {
+ usage.limitLevel = policy.limitBytes > 0 ? policy.limitBytes : 0;
+ usage.warningLevel = policy.warningBytes > 0 ? policy.warningBytes : 0;
+ } else {
+ usage.warningLevel = getDefaultWarningLevel();
+ }
+ if (usage != null && mNetworkController != null) {
+ usage.carrier = mNetworkController.getMobileDataNetworkName();
+ }
+ return usage;
+ }
+
+ /**
+ * Get the total usage level recorded in the network history
+ * @param template the network template to retrieve the network history
+ * @return the total usage level recorded in the network history or -1L if there is error
+ * retrieving the data.
+ */
+ public long getHistoricalUsageLevel(NetworkTemplate template) {
+ if (FeatureFlagUtils.isEnabled(mContext, DATA_USAGE_V2)) {
+ return getUsageLevel(template, 0L /* start */, System.currentTimeMillis() /* end */);
+ } else {
final long now = System.currentTimeMillis();
- final long start, end;
- if (policy != null) {
- final Pair<ZonedDateTime, ZonedDateTime> cycle = NetworkPolicyManager
- .cycleIterator(policy).next();
- start = cycle.first.toInstant().toEpochMilli();
- end = cycle.second.toInstant().toEpochMilli();
- } else {
- // period = last 4 wks
- end = now;
- start = now - DateUtils.WEEK_IN_MILLIS * 4;
- }
- final long callStart = System.currentTimeMillis();
- final NetworkStatsHistory.Entry entry = history.getValues(start, end, now, null);
- final long callEnd = System.currentTimeMillis();
- if (DEBUG) Log.d(TAG, String.format("history call from %s to %s now=%s took %sms: %s",
- new Date(start), new Date(end), new Date(now), callEnd - callStart,
- historyEntryToString(entry)));
- if (entry == null) {
- return warn("no entry data");
- }
- final long totalBytes = entry.rxBytes + entry.txBytes;
- final DataUsageInfo usage = new DataUsageInfo();
- usage.startDate = start;
- usage.usageLevel = totalBytes;
- usage.period = formatDateRange(start, end);
- usage.cycleStart = start;
- usage.cycleEnd = end;
-
- if (policy != null) {
- usage.limitLevel = policy.limitBytes > 0 ? policy.limitBytes : 0;
- usage.warningLevel = policy.warningBytes > 0 ? policy.warningBytes : 0;
- } else {
- usage.warningLevel = getDefaultWarningLevel();
+ return getUsageLevel(template, 0L /* start */, now /* end */, now);
+ }
+ }
+
+ @Deprecated
+ private long getUsageLevel(NetworkTemplate template, long start, long end, long now) {
+ final INetworkStatsSession session = getSession();
+ if (session != null) {
+ try {
+ final NetworkStatsHistory history =
+ session.getHistoryForNetwork(template, FIELDS);
+ final NetworkStatsHistory.Entry entry = history.getValues(
+ start, end, System.currentTimeMillis() /* now */, null /* recycle */);
+ if (entry != null) {
+ return entry.rxBytes + entry.txBytes;
+ }
+ Log.w(TAG, "Failed to get data usage, no entry data");
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to get data usage, remote call failed");
}
- if (usage != null && mNetworkController != null) {
- usage.carrier = mNetworkController.getMobileDataNetworkName();
+ }
+ return -1L;
+ }
+
+ private long getUsageLevel(NetworkTemplate template, long start, long end) {
+ try {
+ final Bucket bucket = mNetworkStatsManager.querySummaryForDevice(
+ getNetworkType(template), getActiveSubscriberId(mContext), start, end);
+ if (bucket != null) {
+ return bucket.getRxBytes() + bucket.getTxBytes();
}
- return usage;
+ Log.w(TAG, "Failed to get data usage, no entry data");
} catch (RemoteException e) {
- return warn("remote call failed");
+ Log.w(TAG, "Failed to get data usage, remote call failed");
}
+ return -1L;
}
private NetworkPolicy findNetworkPolicy(NetworkTemplate template) {
@@ -190,6 +241,7 @@ public class DataUsageController {
return null;
}
+ @Deprecated
private static String historyEntryToString(NetworkStatsHistory.Entry entry) {
return entry == null ? null : new StringBuilder("Entry[")
.append("bucketDuration=").append(entry.bucketDuration)
@@ -203,6 +255,17 @@ public class DataUsageController {
.append(']').toString();
}
+ private static String statsBucketToString(Bucket bucket) {
+ return bucket == null ? null : new StringBuilder("Entry[")
+ .append("bucketDuration=").append(bucket.getEndTimeStamp() - bucket.getStartTimeStamp())
+ .append(",bucketStart=").append(bucket.getStartTimeStamp())
+ .append(",rxBytes=").append(bucket.getRxBytes())
+ .append(",rxPackets=").append(bucket.getRxPackets())
+ .append(",txBytes=").append(bucket.getTxBytes())
+ .append(",txPackets=").append(bucket.getTxPackets())
+ .append(']').toString();
+ }
+
public void setMobileDataEnabled(boolean enabled) {
Log.d(TAG, "setMobileDataEnabled: enabled=" + enabled);
mTelephonyManager.setDataEnabled(enabled);
@@ -221,6 +284,25 @@ public class DataUsageController {
return mTelephonyManager.getDataEnabled();
}
+ static int getNetworkType(NetworkTemplate networkTemplate) {
+ if (networkTemplate == null) {
+ return ConnectivityManager.TYPE_NONE;
+ }
+ final int matchRule = networkTemplate.getMatchRule();
+ switch (matchRule) {
+ case NetworkTemplate.MATCH_MOBILE:
+ case NetworkTemplate.MATCH_MOBILE_WILDCARD:
+ return ConnectivityManager.TYPE_MOBILE;
+ case NetworkTemplate.MATCH_WIFI:
+ case NetworkTemplate.MATCH_WIFI_WILDCARD:
+ return ConnectivityManager.TYPE_WIFI;
+ case NetworkTemplate.MATCH_ETHERNET:
+ return ConnectivityManager.TYPE_ETHERNET;
+ default:
+ return ConnectivityManager.TYPE_MOBILE;
+ }
+ }
+
private static String getActiveSubscriberId(Context context) {
final TelephonyManager tele = TelephonyManager.from(context);
final String actualSubscriberId = tele.getSubscriberId(
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleChartData.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleChartData.java
new file mode 100644
index 000000000000..9b3ff8b2e165
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleChartData.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2018 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.settingslib.net;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Usage data in a billing cycle with bucketized data for plotting the usage chart.
+ */
+public class NetworkCycleChartData extends NetworkCycleData {
+ public static final long BUCKET_DURATION_MS = TimeUnit.DAYS.toMillis(1);
+
+ private List<NetworkCycleData> mUsageBuckets;
+
+ private NetworkCycleChartData() {
+ }
+
+ public List<NetworkCycleData> getUsageBuckets() {
+ return mUsageBuckets;
+ }
+
+ public static class Builder extends NetworkCycleData.Builder {
+ private NetworkCycleChartData mObject = new NetworkCycleChartData();
+
+ public Builder setUsageBuckets(List<NetworkCycleData> buckets) {
+ getObject().mUsageBuckets = buckets;
+ return this;
+ }
+
+ @Override
+ protected NetworkCycleChartData getObject() {
+ return mObject;
+ }
+
+ @Override
+ public NetworkCycleChartData build() {
+ return getObject();
+ }
+ }
+
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleChartDataLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleChartDataLoader.java
new file mode 100644
index 000000000000..ec5a0b5cc4cd
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleChartDataLoader.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2018 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.settingslib.net;
+
+import android.app.usage.NetworkStats;
+import android.content.Context;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Loader for network data usage history. It returns a list of usage data per billing cycle with
+ * bucketized usages.
+ */
+public class NetworkCycleChartDataLoader
+ extends NetworkCycleDataLoader<List<NetworkCycleChartData>> {
+
+ private static final String TAG = "NetworkCycleChartLoader";
+
+ private final List<NetworkCycleChartData> mData;
+
+ private NetworkCycleChartDataLoader(Builder builder) {
+ super(builder);
+ mData = new ArrayList<NetworkCycleChartData>();
+ }
+
+ @Override
+ void recordUsage(long start, long end) {
+ try {
+ final NetworkStats.Bucket bucket = mNetworkStatsManager.querySummaryForDevice(
+ mNetworkType, mSubId, start, end);
+ final long total = bucket == null ? 0L : bucket.getRxBytes() + bucket.getTxBytes();
+ if (total > 0L) {
+ final NetworkCycleChartData.Builder builder = new NetworkCycleChartData.Builder();
+ builder.setUsageBuckets(getUsageBuckets(start, end))
+ .setStartTime(start)
+ .setEndTime(end)
+ .setTotalUsage(total);
+ mData.add(builder.build());
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception querying network detail.", e);
+ }
+ }
+
+ @Override
+ List<NetworkCycleChartData> getCycleUsage() {
+ return mData;
+ }
+
+ public static Builder<?> builder(Context context) {
+ return new Builder<NetworkCycleChartDataLoader>(context) {
+ @Override
+ public NetworkCycleChartDataLoader build() {
+ return new NetworkCycleChartDataLoader(this);
+ }
+ };
+ }
+
+ private List<NetworkCycleData> getUsageBuckets(long start, long end) {
+ final List<NetworkCycleData> data = new ArrayList<>();
+ long bucketStart = start;
+ long bucketEnd = start + NetworkCycleChartData.BUCKET_DURATION_MS;
+ while (bucketEnd <= end) {
+ long usage = 0L;
+ try {
+ final NetworkStats.Bucket bucket = mNetworkStatsManager.querySummaryForDevice(
+ mNetworkType, mSubId, bucketStart, bucketEnd);
+ if (bucket != null) {
+ usage = bucket.getRxBytes() + bucket.getTxBytes();
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception querying network detail.", e);
+ }
+ data.add(new NetworkCycleData.Builder()
+ .setStartTime(bucketStart).setEndTime(bucketEnd).setTotalUsage(usage).build());
+ bucketStart = bucketEnd;
+ bucketEnd += NetworkCycleChartData.BUCKET_DURATION_MS;
+ }
+ return data;
+ }
+
+ public static abstract class Builder<T extends NetworkCycleChartDataLoader>
+ extends NetworkCycleDataLoader.Builder<T> {
+
+ public Builder(Context context) {
+ super(context);
+ }
+
+ }
+
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleData.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleData.java
new file mode 100644
index 000000000000..26c65a2c4a48
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleData.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2018 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.settingslib.net;
+
+/**
+ * Base data structure representing usage data in a billing cycle.
+ */
+public class NetworkCycleData {
+
+ private long mStartTime;
+ private long mEndTime;
+ private long mTotalUsage;
+
+ protected NetworkCycleData() {
+ }
+
+ public long getStartTime() {
+ return mStartTime;
+ }
+
+ public long getEndTime() {
+ return mEndTime;
+ }
+
+ public long getTotalUsage() {
+ return mTotalUsage;
+ }
+
+ public static class Builder {
+
+ private NetworkCycleData mObject = new NetworkCycleData();
+
+ public Builder setStartTime(long start) {
+ getObject().mStartTime = start;
+ return this;
+ }
+
+ public Builder setEndTime(long end) {
+ getObject().mEndTime = end;
+ return this;
+ }
+
+ public Builder setTotalUsage(long total) {
+ getObject().mTotalUsage = total;
+ return this;
+ }
+
+ protected NetworkCycleData getObject() {
+ return mObject;
+ }
+
+ public NetworkCycleData build() {
+ return getObject();
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataForUid.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataForUid.java
new file mode 100644
index 000000000000..9d13717bbbcc
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataForUid.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2018 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.settingslib.net;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Usage data in a billing cycle for a specific Uid.
+ */
+public class NetworkCycleDataForUid extends NetworkCycleData {
+
+ private long mBackgroudUsage;
+ private long mForegroudUsage;
+
+ private NetworkCycleDataForUid() {
+ }
+
+ public long getBackgroudUsage() {
+ return mBackgroudUsage;
+ }
+
+ public long getForegroudUsage() {
+ return mForegroudUsage;
+ }
+
+ public static class Builder extends NetworkCycleData.Builder {
+
+ private NetworkCycleDataForUid mObject = new NetworkCycleDataForUid();
+
+ public Builder setBackgroundUsage(long backgroundUsage) {
+ getObject().mBackgroudUsage = backgroundUsage;
+ return this;
+ }
+
+ public Builder setForegroundUsage(long foregroundUsage) {
+ getObject().mForegroudUsage = foregroundUsage;
+ return this;
+ }
+
+ @Override
+ public NetworkCycleDataForUid getObject() {
+ return mObject;
+ }
+
+ @Override
+ public NetworkCycleDataForUid build() {
+ return getObject();
+ }
+ }
+
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataForUidLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataForUidLoader.java
new file mode 100644
index 000000000000..cc970b93f601
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataForUidLoader.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2018 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.settingslib.net;
+
+import static android.app.usage.NetworkStats.Bucket.STATE_FOREGROUND;
+import static android.net.NetworkStats.TAG_NONE;
+
+import android.app.usage.NetworkStats;
+import android.content.Context;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Loader for network data usage history. It returns a list of usage data per billing cycle for a
+ * specific Uid.
+ */
+public class NetworkCycleDataForUidLoader extends
+ NetworkCycleDataLoader<List<NetworkCycleDataForUid>> {
+ private static final String TAG = "NetworkDataForUidLoader";
+
+ private final List<NetworkCycleDataForUid> mData;
+ private final int mUid;
+ private final boolean mRetrieveDetail;
+
+ private NetworkCycleDataForUidLoader(Builder builder) {
+ super(builder);
+ mUid = builder.mUid;
+ mRetrieveDetail = builder.mRetrieveDetail;
+ mData = new ArrayList<NetworkCycleDataForUid>();
+ }
+
+ @Override
+ void recordUsage(long start, long end) {
+ try {
+ final NetworkStats stats = mNetworkStatsManager.queryDetailsForUid(
+ mNetworkType, mSubId, start, end, mUid);
+ final long total = getTotalUsage(stats);
+ if (total > 0L) {
+ final NetworkCycleDataForUid.Builder builder = new NetworkCycleDataForUid.Builder();
+ builder.setStartTime(start)
+ .setEndTime(end)
+ .setTotalUsage(total);
+ if (mRetrieveDetail) {
+ final long foreground = getForegroundUsage(start, end);
+ builder.setBackgroundUsage(total - foreground)
+ .setForegroundUsage(foreground);
+ }
+ mData.add(builder.build());
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Exception querying network detail.", e);
+ }
+ }
+
+ @Override
+ List<NetworkCycleDataForUid> getCycleUsage() {
+ return mData;
+ }
+
+ public static Builder<?> builder(Context context) {
+ return new Builder<NetworkCycleDataForUidLoader>(context) {
+ @Override
+ public NetworkCycleDataForUidLoader build() {
+ return new NetworkCycleDataForUidLoader(this);
+ }
+ };
+ }
+
+ private long getForegroundUsage(long start, long end) {
+ final NetworkStats stats = mNetworkStatsManager.queryDetailsForUidTagState(
+ mNetworkType, mSubId, start, end, mUid, TAG_NONE, STATE_FOREGROUND);
+ return getTotalUsage(stats);
+ }
+
+ public static abstract class Builder<T extends NetworkCycleDataForUidLoader>
+ extends NetworkCycleDataLoader.Builder<T> {
+
+ private int mUid;
+ private boolean mRetrieveDetail = true;
+
+ public Builder(Context context) {
+ super(context);
+ }
+
+ public Builder<T> setUid(int uid) {
+ mUid = uid;
+ return this;
+ }
+
+ public Builder<T> setRetrieveDetail(boolean retrieveDetail) {
+ mRetrieveDetail = retrieveDetail;
+ return this;
+ }
+ }
+
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java
new file mode 100644
index 000000000000..d9578014e846
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2018 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.settingslib.net;
+
+import static android.net.NetworkStatsHistory.FIELD_RX_BYTES;
+import static android.net.NetworkStatsHistory.FIELD_TX_BYTES;
+
+import android.app.usage.NetworkStats;
+import android.app.usage.NetworkStatsManager;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.INetworkStatsService;
+import android.net.INetworkStatsSession;
+import android.net.NetworkPolicy;
+import android.net.NetworkPolicyManager;
+import android.net.NetworkStatsHistory;
+import android.net.NetworkTemplate;
+import android.net.TrafficStats;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.text.format.DateUtils;
+import android.util.Pair;
+
+import com.android.settingslib.NetworkPolicyEditor;
+
+import java.time.ZonedDateTime;
+import java.util.Iterator;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.loader.content.AsyncTaskLoader;
+
+/**
+ * Loader for network data usage history. It returns a list of usage data per billing cycle.
+ */
+public abstract class NetworkCycleDataLoader<D> extends AsyncTaskLoader<D> {
+ private static final String TAG = "NetworkCycleDataLoader";
+ protected final NetworkStatsManager mNetworkStatsManager;
+ protected final String mSubId;
+ protected final int mNetworkType;
+ private final NetworkPolicy mPolicy;
+ private final NetworkTemplate mNetworkTemplate;
+ @VisibleForTesting
+ final INetworkStatsService mNetworkStatsService;
+
+ protected NetworkCycleDataLoader(Builder<?> builder) {
+ super(builder.mContext);
+ mSubId = builder.mSubId;
+ mNetworkType = builder.mNetworkType;
+ mNetworkTemplate = builder.mNetworkTemplate;
+ mNetworkStatsManager = (NetworkStatsManager)
+ builder.mContext.getSystemService(Context.NETWORK_STATS_SERVICE);
+ mNetworkStatsService = INetworkStatsService.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
+ final NetworkPolicyEditor policyEditor =
+ new NetworkPolicyEditor(NetworkPolicyManager.from(builder.mContext));
+ policyEditor.read();
+ mPolicy = policyEditor.getPolicy(mNetworkTemplate);
+ }
+
+ @Override
+ protected void onStartLoading() {
+ super.onStartLoading();
+ forceLoad();
+ }
+
+ public D loadInBackground() {
+ if (mPolicy == null) {
+ loadFourWeeksData();
+ } else {
+ loadPolicyData();
+ }
+ return getCycleUsage();
+ }
+
+ @VisibleForTesting
+ void loadPolicyData() {
+ final Iterator<Pair<ZonedDateTime, ZonedDateTime>> iterator =
+ NetworkPolicyManager.cycleIterator(mPolicy);
+ while (iterator.hasNext()) {
+ final Pair<ZonedDateTime, ZonedDateTime> cycle = iterator.next();
+ final long cycleStart = cycle.first.toInstant().toEpochMilli();
+ final long cycleEnd = cycle.second.toInstant().toEpochMilli();
+ recordUsage(cycleStart, cycleEnd);
+ }
+ }
+
+ @Override
+ protected void onStopLoading() {
+ super.onStopLoading();
+ cancelLoad();
+ }
+
+ @Override
+ protected void onReset() {
+ super.onReset();
+ cancelLoad();
+ }
+
+ @VisibleForTesting
+ void loadFourWeeksData() {
+ try {
+ final INetworkStatsSession networkSession = mNetworkStatsService.openSession();
+ final NetworkStatsHistory networkHistory = networkSession.getHistoryForNetwork(
+ mNetworkTemplate, FIELD_RX_BYTES | FIELD_TX_BYTES);
+ final long historyStart = networkHistory.getStart();
+ final long historyEnd = networkHistory.getEnd();
+
+ long cycleEnd = historyEnd;
+ while (cycleEnd > historyStart) {
+ final long cycleStart = Math.max(
+ historyStart, cycleEnd - (DateUtils.WEEK_IN_MILLIS * 4));
+ recordUsage(cycleStart, cycleEnd);
+ cycleEnd = cycleStart;
+ }
+
+ TrafficStats.closeQuietly(networkSession);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @VisibleForTesting
+ abstract void recordUsage(long start, long end);
+
+ abstract D getCycleUsage();
+
+ public static Builder<?> builder(Context context) {
+ return new Builder<NetworkCycleDataLoader>(context) {
+ @Override
+ public NetworkCycleDataLoader build() {
+ return null;
+ }
+ };
+ }
+
+ protected long getTotalUsage(NetworkStats stats) {
+ long bytes = 0L;
+ if (stats != null) {
+ final NetworkStats.Bucket bucket = new NetworkStats.Bucket();
+ while (stats.hasNextBucket() && stats.getNextBucket(bucket)) {
+ bytes += bucket.getRxBytes() + bucket.getTxBytes();
+ }
+ stats.close();
+ }
+ return bytes;
+ }
+
+ public static abstract class Builder<T extends NetworkCycleDataLoader> {
+ private final Context mContext;
+ private String mSubId;
+ private int mNetworkType;
+ private NetworkTemplate mNetworkTemplate;
+
+ public Builder (Context context) {
+ mContext = context;
+ }
+
+ public Builder<T> setSubscriberId(String subId) {
+ mSubId = subId;
+ return this;
+ }
+
+ public Builder<T> setNetworkTemplate(NetworkTemplate template) {
+ mNetworkTemplate = template;
+ mNetworkType = DataUsageController.getNetworkType(template);
+ return this;
+ }
+
+ public abstract T build();
+ }
+
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkStatsSummaryLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkStatsSummaryLoader.java
new file mode 100644
index 000000000000..34e6097ea46e
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/net/NetworkStatsSummaryLoader.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2018 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.settingslib.net;
+
+import android.app.usage.NetworkStatsManager;
+import android.app.usage.NetworkStats;
+import android.content.Context;
+import android.os.RemoteException;
+import android.util.Log;
+
+import androidx.loader.content.AsyncTaskLoader;
+
+/**
+ * Loader for retrieving the network stats summary for all UIDs.
+ */
+public class NetworkStatsSummaryLoader extends AsyncTaskLoader<NetworkStats> {
+
+ private static final String TAG = "NetworkDetailLoader";
+ private final NetworkStatsManager mNetworkStatsManager;
+ private final long mStart;
+ private final long mEnd;
+ private final String mSubId;
+ private final int mNetworkType;
+
+ private NetworkStatsSummaryLoader(Builder builder) {
+ super(builder.mContext);
+ mStart = builder.mStart;
+ mEnd = builder.mEnd;
+ mSubId = builder.mSubId;
+ mNetworkType = builder.mNetworkType;
+ mNetworkStatsManager = (NetworkStatsManager)
+ builder.mContext.getSystemService(Context.NETWORK_STATS_SERVICE);
+ }
+
+ @Override
+ protected void onStartLoading() {
+ super.onStartLoading();
+ forceLoad();
+ }
+
+ @Override
+ public NetworkStats loadInBackground() {
+ try {
+ return mNetworkStatsManager.querySummary(mNetworkType, mSubId, mStart, mEnd);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception querying network detail.", e);
+ return null;
+ }
+ }
+
+ @Override
+ protected void onStopLoading() {
+ super.onStopLoading();
+ cancelLoad();
+ }
+
+ @Override
+ protected void onReset() {
+ super.onReset();
+ cancelLoad();
+ }
+
+ public static class Builder {
+ private final Context mContext;
+ private long mStart;
+ private long mEnd;
+ private String mSubId;
+ private int mNetworkType;
+
+ public Builder(Context context) {
+ mContext = context;
+ }
+
+ public Builder setStartTime(long start) {
+ mStart = start;
+ return this;
+ }
+
+ public Builder setEndTime(long end) {
+ mEnd = end;
+ return this;
+ }
+
+ public Builder setSubscriberId(String subId) {
+ mSubId = subId;
+ return this;
+ }
+
+ public Builder setNetworkType(int networkType) {
+ mNetworkType = networkType;
+ return this;
+ }
+
+ public NetworkStatsSummaryLoader build() {
+ return new NetworkStatsSummaryLoader(this);
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/SummaryForAllUidLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/SummaryForAllUidLoader.java
index 572bae1112da..649aeffd0d5f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/SummaryForAllUidLoader.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/SummaryForAllUidLoader.java
@@ -24,6 +24,12 @@ import android.net.NetworkTemplate;
import android.os.Bundle;
import android.os.RemoteException;
+/**
+ * Framework loader is deprecated, use the compat version instead.
+ *
+ * @deprecated
+ */
+@Deprecated
public class SummaryForAllUidLoader extends AsyncTaskLoader<NetworkStats> {
private static final String KEY_TEMPLATE = "template";
private static final String KEY_START = "start";
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/SummaryForAllUidLoaderCompat.java b/packages/SettingsLib/src/com/android/settingslib/net/SummaryForAllUidLoaderCompat.java
new file mode 100644
index 000000000000..82bb0115c66f
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/net/SummaryForAllUidLoaderCompat.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2018 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.settingslib.net;
+
+import android.content.Context;
+import android.net.INetworkStatsSession;
+import android.net.NetworkStats;
+import android.net.NetworkTemplate;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+import androidx.loader.content.AsyncTaskLoader;
+
+/**
+ * Deprecated in favor of {@link NetworkStatsDetailLoader}
+ *
+ * @deprecated
+ */
+@Deprecated
+public class SummaryForAllUidLoaderCompat extends AsyncTaskLoader<NetworkStats> {
+ private static final String KEY_TEMPLATE = "template";
+ private static final String KEY_START = "start";
+ private static final String KEY_END = "end";
+
+ private final INetworkStatsSession mSession;
+ private final Bundle mArgs;
+
+ public static Bundle buildArgs(NetworkTemplate template, long start, long end) {
+ final Bundle args = new Bundle();
+ args.putParcelable(KEY_TEMPLATE, template);
+ args.putLong(KEY_START, start);
+ args.putLong(KEY_END, end);
+ return args;
+ }
+
+ public SummaryForAllUidLoaderCompat(Context context, INetworkStatsSession session,
+ Bundle args) {
+ super(context);
+ mSession = session;
+ mArgs = args;
+ }
+
+ @Override
+ protected void onStartLoading() {
+ super.onStartLoading();
+ forceLoad();
+ }
+
+ @Override
+ public NetworkStats loadInBackground() {
+ final NetworkTemplate template = mArgs.getParcelable(KEY_TEMPLATE);
+ final long start = mArgs.getLong(KEY_START);
+ final long end = mArgs.getLong(KEY_END);
+
+ try {
+ return mSession.getSummaryForAllUid(template, start, end, false);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ @Override
+ protected void onStopLoading() {
+ super.onStopLoading();
+ cancelLoad();
+ }
+
+ @Override
+ protected void onReset() {
+ super.onReset();
+ cancelLoad();
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/UidDetailProvider.java b/packages/SettingsLib/src/com/android/settingslib/net/UidDetailProvider.java
index 224b9679a9a2..c14f5588cddf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/UidDetailProvider.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/UidDetailProvider.java
@@ -28,9 +28,9 @@ import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.TrafficStats;
-import android.os.UserManager;
-import android.os.UserHandle;
import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/ZenDurationDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/ZenDurationDialog.java
index 6da486ab3aa8..b9197fe1ebd5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/ZenDurationDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/ZenDurationDialog.java
@@ -17,14 +17,12 @@
package com.android.settingslib.notification;
import android.app.ActivityManager;
-import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.provider.Settings;
import android.service.notification.Condition;
import android.service.notification.ZenModeConfig;
-import androidx.annotation.VisibleForTesting;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CompoundButton;
@@ -35,6 +33,9 @@ import android.widget.RadioGroup;
import android.widget.ScrollView;
import android.widget.TextView;
+import androidx.annotation.VisibleForTesting;
+import androidx.appcompat.app.AlertDialog;
+
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.policy.PhoneWindow;
@@ -66,12 +67,17 @@ public class ZenDurationDialog {
}
public Dialog createDialog() {
- int zenDuration = Settings.Global.getInt(
- mContext.getContentResolver(), Settings.Global.ZEN_DURATION,
- Settings.Global.ZEN_DURATION_FOREVER);
+ final AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
+ setupDialog(builder);
+ return builder.create();
+ }
+
+ public void setupDialog(AlertDialog.Builder builder) {
+ int zenDuration = Settings.Secure.getInt(
+ mContext.getContentResolver(), Settings.Secure.ZEN_DURATION,
+ Settings.Secure.ZEN_DURATION_FOREVER);
- final AlertDialog.Builder builder = new AlertDialog.Builder(mContext)
- .setTitle(R.string.zen_mode_duration_settings_title)
+ builder.setTitle(R.string.zen_mode_duration_settings_title)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.okay,
new DialogInterface.OnClickListener() {
@@ -84,19 +90,18 @@ public class ZenDurationDialog {
View contentView = getContentView();
setupRadioButtons(zenDuration);
builder.setView(contentView);
- return builder.create();
}
@VisibleForTesting
protected void updateZenDuration(int currZenDuration) {
final int checkedRadioButtonId = mZenRadioGroup.getCheckedRadioButtonId();
- int newZenDuration = Settings.Global.getInt(
- mContext.getContentResolver(), Settings.Global.ZEN_DURATION,
- Settings.Global.ZEN_DURATION_FOREVER);
+ int newZenDuration = Settings.Secure.getInt(
+ mContext.getContentResolver(), Settings.Secure.ZEN_DURATION,
+ Settings.Secure.ZEN_DURATION_FOREVER);
switch (checkedRadioButtonId) {
case FOREVER_CONDITION_INDEX:
- newZenDuration = Settings.Global.ZEN_DURATION_FOREVER;
+ newZenDuration = Settings.Secure.ZEN_DURATION_FOREVER;
MetricsLogger.action(mContext,
MetricsProto.MetricsEvent.
NOTIFICATION_ZEN_MODE_DURATION_FOREVER);
@@ -110,7 +115,7 @@ public class ZenDurationDialog {
newZenDuration);
break;
case ALWAYS_ASK_CONDITION_INDEX:
- newZenDuration = Settings.Global.ZEN_DURATION_PROMPT;
+ newZenDuration = Settings.Secure.ZEN_DURATION_PROMPT;
MetricsLogger.action(mContext,
MetricsProto.MetricsEvent.
NOTIFICATION_ZEN_MODE_DURATION_PROMPT);
@@ -118,8 +123,8 @@ public class ZenDurationDialog {
}
if (currZenDuration != newZenDuration) {
- Settings.Global.putInt(mContext.getContentResolver(),
- Settings.Global.ZEN_DURATION, newZenDuration);
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.ZEN_DURATION, newZenDuration);
}
}
@@ -269,8 +274,7 @@ public class ZenDurationDialog {
String radioContentText = "";
switch (rowIndex) {
case FOREVER_CONDITION_INDEX:
- radioContentText = mContext.getString(
- com.android.internal.R.string.zen_mode_forever);
+ radioContentText = mContext.getString(R.string.zen_mode_forever);
break;
case COUNTDOWN_CONDITION_INDEX:
Condition condition = ZenModeConfig.toTimeCondition(mContext,
diff --git a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionCategory.java b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionCategory.java
deleted file mode 100644
index 19e556ad55bb..000000000000
--- a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionCategory.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2017 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.settingslib.suggestions;
-
-public class SuggestionCategory {
- public String category;
- public String pkg;
- public boolean multiple;
- public boolean exclusive;
- public long exclusiveExpireDaysInMillis;
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionController.java b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionController.java
index 6a8b01a23688..ec774872ce67 100644
--- a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionController.java
@@ -24,9 +24,10 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.service.settings.suggestions.ISuggestionService;
import android.service.settings.suggestions.Suggestion;
+import android.util.Log;
+
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
-import android.util.Log;
import java.util.List;
diff --git a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionControllerMixin.java b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionControllerMixin.java
index ee8b6fe60478..b0e987ee2779 100644
--- a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionControllerMixin.java
+++ b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionControllerMixin.java
@@ -17,22 +17,26 @@
package com.android.settingslib.suggestions;
import android.app.LoaderManager;
-import androidx.lifecycle.OnLifecycleEvent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Loader;
import android.os.Bundle;
import android.service.settings.suggestions.Suggestion;
-import androidx.annotation.Nullable;
import android.util.Log;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.OnLifecycleEvent;
+
import com.android.settingslib.core.lifecycle.Lifecycle;
import java.util.List;
/**
- * Manages IPC communication to SettingsIntelligence for suggestion related services.
+ * Framework mixin is deprecated, use the compat version instead.
+ *
+ * @deprecated
*/
+@Deprecated
public class SuggestionControllerMixin implements SuggestionController.ServiceConnectionListener,
androidx.lifecycle.LifecycleObserver, LoaderManager.LoaderCallbacks<List<Suggestion>> {
@@ -64,7 +68,7 @@ public class SuggestionControllerMixin implements SuggestionController.ServiceCo
mContext = context.getApplicationContext();
mHost = host;
mSuggestionController = new SuggestionController(mContext, componentName,
- this /* serviceConnectionListener */);
+ this /* serviceConnectionListener */);
if (lifecycle != null) {
lifecycle.addObserver(this);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionControllerMixinCompat.java b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionControllerMixinCompat.java
new file mode 100644
index 000000000000..eea49bcb2384
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionControllerMixinCompat.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2018 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.settingslib.suggestions;
+
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Bundle;
+import android.service.settings.suggestions.Suggestion;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+import androidx.lifecycle.OnLifecycleEvent;
+import androidx.loader.app.LoaderManager;
+import androidx.loader.content.Loader;
+
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+import java.util.List;
+
+/**
+ * Manages IPC communication to SettingsIntelligence for suggestion related services.
+ */
+public class SuggestionControllerMixinCompat implements
+ SuggestionController.ServiceConnectionListener, androidx.lifecycle.LifecycleObserver,
+ LoaderManager.LoaderCallbacks<List<Suggestion>> {
+
+ public interface SuggestionControllerHost {
+ /**
+ * Called when suggestion data fetching is ready.
+ */
+ void onSuggestionReady(List<Suggestion> data);
+
+ /**
+ * Returns {@link LoaderManager} associated with the host. If host is not attached to
+ * activity then return null.
+ */
+ @Nullable
+ LoaderManager getLoaderManager();
+ }
+
+ private static final String TAG = "SuggestionCtrlMixin";
+ private static final boolean DEBUG = false;
+
+ private final Context mContext;
+ private final SuggestionController mSuggestionController;
+ private final SuggestionControllerHost mHost;
+
+ private boolean mSuggestionLoaded;
+
+ public SuggestionControllerMixinCompat(Context context, SuggestionControllerHost host,
+ Lifecycle lifecycle, ComponentName componentName) {
+ mContext = context.getApplicationContext();
+ mHost = host;
+ mSuggestionController = new SuggestionController(mContext, componentName,
+ this /* serviceConnectionListener */);
+ if (lifecycle != null) {
+ lifecycle.addObserver(this);
+ }
+ }
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_START)
+ public void onStart() {
+ if (DEBUG) {
+ Log.d(TAG, "SuggestionController started");
+ }
+ mSuggestionController.start();
+ }
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
+ public void onStop() {
+ if (DEBUG) {
+ Log.d(TAG, "SuggestionController stopped.");
+ }
+ mSuggestionController.stop();
+ }
+
+ @Override
+ public void onServiceConnected() {
+ final LoaderManager loaderManager = mHost.getLoaderManager();
+ if (loaderManager != null) {
+ loaderManager.restartLoader(SuggestionLoader.LOADER_ID_SUGGESTIONS,
+ null /* args */, this /* callback */);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected() {
+ if (DEBUG) {
+ Log.d(TAG, "SuggestionService disconnected");
+ }
+ final LoaderManager loaderManager = mHost.getLoaderManager();
+ if (loaderManager != null) {
+ loaderManager.destroyLoader(SuggestionLoader.LOADER_ID_SUGGESTIONS);
+ }
+ }
+
+ @Override
+ public Loader<List<Suggestion>> onCreateLoader(int id, Bundle args) {
+ if (id == SuggestionLoader.LOADER_ID_SUGGESTIONS) {
+ mSuggestionLoaded = false;
+ return new SuggestionLoaderCompat(mContext, mSuggestionController);
+ }
+ throw new IllegalArgumentException("This loader id is not supported " + id);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<List<Suggestion>> loader, List<Suggestion> data) {
+ mSuggestionLoaded = true;
+ mHost.onSuggestionReady(data);
+ }
+
+ @Override
+ public void onLoaderReset(Loader<List<Suggestion>> loader) {
+ mSuggestionLoaded = false;
+ }
+
+ public boolean isSuggestionLoaded() {
+ return mSuggestionLoaded;
+ }
+
+ public void dismissSuggestion(Suggestion suggestion) {
+ mSuggestionController.dismissSuggestions(suggestion);
+ }
+
+ public void launchSuggestion(Suggestion suggestion) {
+ mSuggestionController.launchSuggestion(suggestion);
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionList.java b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionList.java
deleted file mode 100644
index a89092040e71..000000000000
--- a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionList.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2017 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.settingslib.suggestions;
-
-import android.content.Intent;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-
-import com.android.settingslib.drawer.Tile;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-public class SuggestionList {
- // Category -> list of suggestion map
- private final Map<SuggestionCategory, List<Tile>> mSuggestions;
-
- // A flatten list of all suggestions.
- private List<Tile> mSuggestionList;
-
- public SuggestionList() {
- mSuggestions = new ArrayMap<>();
- }
-
- public void addSuggestions(SuggestionCategory category, List<Tile> suggestions) {
- mSuggestions.put(category, suggestions);
- }
-
- public List<Tile> getSuggestions() {
- if (mSuggestionList != null) {
- return mSuggestionList;
- }
- mSuggestionList = new ArrayList<>();
- for (List<Tile> suggestions : mSuggestions.values()) {
- mSuggestionList.addAll(suggestions);
- }
- dedupeSuggestions(mSuggestionList);
- return mSuggestionList;
- }
-
- public boolean isExclusiveSuggestionCategory() {
- if (mSuggestions.size() != 1) {
- // If there is no category, or more than 1 category, it's not exclusive by definition.
- return false;
- }
- for (SuggestionCategory category : mSuggestions.keySet()) {
- if (category.exclusive) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Filter suggestions list so they are all unique.
- */
- private void dedupeSuggestions(List<Tile> suggestions) {
- final Set<String> intents = new ArraySet<>();
- for (int i = suggestions.size() - 1; i >= 0; i--) {
- final Tile suggestion = suggestions.get(i);
- final String intentUri = suggestion.intent.toUri(Intent.URI_INTENT_SCHEME);
- if (intents.contains(intentUri)) {
- suggestions.remove(i);
- } else {
- intents.add(intentUri);
- }
- }
- }
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionLoader.java b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionLoader.java
index 9c1af1edc778..8011424bb0d2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionLoader.java
+++ b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionLoader.java
@@ -24,6 +24,12 @@ import com.android.settingslib.utils.AsyncLoader;
import java.util.List;
+/**
+ * Framework loader is deprecated, use the compat version instead.
+ *
+ * @deprecated
+ */
+@Deprecated
public class SuggestionLoader extends AsyncLoader<List<Suggestion>> {
public static final int LOADER_ID_SUGGESTIONS = 42;
diff --git a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionLoaderCompat.java b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionLoaderCompat.java
new file mode 100644
index 000000000000..066de19172de
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionLoaderCompat.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 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.settingslib.suggestions;
+
+import android.content.Context;
+import android.service.settings.suggestions.Suggestion;
+import android.util.Log;
+
+import com.android.settingslib.utils.AsyncLoaderCompat;
+
+import java.util.List;
+
+public class SuggestionLoaderCompat extends AsyncLoaderCompat<List<Suggestion>> {
+
+ public static final int LOADER_ID_SUGGESTIONS = 42;
+ private static final String TAG = "SuggestionLoader";
+
+ private final SuggestionController mSuggestionController;
+
+ public SuggestionLoaderCompat(Context context, SuggestionController controller) {
+ super(context);
+ mSuggestionController = controller;
+ }
+
+ @Override
+ protected void onDiscardResult(List<Suggestion> result) {
+
+ }
+
+ @Override
+ public List<Suggestion> loadInBackground() {
+ final List<Suggestion> data = mSuggestionController.getSuggestions();
+ if (data == null) {
+ Log.d(TAG, "data is null");
+ } else {
+ Log.d(TAG, "data size " + data.size());
+ }
+ return data;
+ }
+} \ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionParser.java b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionParser.java
deleted file mode 100644
index 8705c9846a6f..000000000000
--- a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionParser.java
+++ /dev/null
@@ -1,498 +0,0 @@
-/*
- * Copyright (C) 2017 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.settingslib.suggestions;
-
-import android.Manifest;
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.annotation.RequiresPermission;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import androidx.annotation.VisibleForTesting;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.util.ArrayMap;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.Pair;
-import android.util.Xml;
-import android.view.InflateException;
-
-import com.android.settingslib.drawer.Tile;
-import com.android.settingslib.drawer.TileUtils;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-public class SuggestionParser {
-
- private static final String TAG = "SuggestionParser";
-
- // If defined, only returns this suggestion if the feature is supported.
- public static final String META_DATA_REQUIRE_FEATURE = "com.android.settings.require_feature";
-
- // If defined, only display this optional step if an account of that type exists.
- private static final String META_DATA_REQUIRE_ACCOUNT = "com.android.settings.require_account";
-
- // If defined and not true, do not should optional step.
- private static final String META_DATA_IS_SUPPORTED = "com.android.settings.is_supported";
-
- // If defined, only display this optional step if the current user is of that type.
- private static final String META_DATA_REQUIRE_USER_TYPE =
- "com.android.settings.require_user_type";
-
- // If defined, only display this optional step if a connection is available.
- private static final String META_DATA_IS_CONNECTION_REQUIRED =
- "com.android.settings.require_connection";
-
- // The valid values that setup wizard recognizes for differentiating user types.
- private static final String META_DATA_PRIMARY_USER_TYPE_VALUE = "primary";
- private static final String META_DATA_ADMIN_USER_TYPE_VALUE = "admin";
- private static final String META_DATA_GUEST_USER_TYPE_VALUE = "guest";
- private static final String META_DATA_RESTRICTED_USER_TYPE_VALUE = "restricted";
-
- /**
- * Allows suggestions to appear after a certain number of days, and to re-appear if dismissed.
- * For instance:
- * 0,10
- * Will appear immediately, but if the user removes it, it will come back after 10 days.
- *
- * Another example:
- * 10,30
- * Will only show up after 10 days, and then again after 30.
- */
- public static final String META_DATA_DISMISS_CONTROL = "com.android.settings.dismiss";
-
- // Shared prefs keys for storing dismissed state.
- // Index into current dismissed state.
- public static final String SETUP_TIME = "_setup_time";
- private static final String IS_DISMISSED = "_is_dismissed";
-
- // Default dismiss control for smart suggestions.
- private static final String DEFAULT_SMART_DISMISS_CONTROL = "0";
-
- private final Context mContext;
- private final List<SuggestionCategory> mSuggestionList;
- private final ArrayMap<Pair<String, String>, Tile> mAddCache = new ArrayMap<>();
- private final SharedPreferences mSharedPrefs;
- private final String mDefaultDismissControl;
-
- public SuggestionParser(Context context, SharedPreferences sharedPrefs, int orderXml,
- String defaultDismissControl) {
- this(
- context,
- sharedPrefs,
- (List<SuggestionCategory>) new SuggestionOrderInflater(context).parse(orderXml),
- defaultDismissControl);
- }
-
- public SuggestionParser(Context context, SharedPreferences sharedPrefs, int orderXml) {
- this(context, sharedPrefs, orderXml, DEFAULT_SMART_DISMISS_CONTROL);
- }
-
- @VisibleForTesting
- public SuggestionParser(
- Context context,
- SharedPreferences sharedPrefs,
- List<SuggestionCategory> suggestionList,
- String defaultDismissControl) {
- mContext = context;
- mSuggestionList = suggestionList;
- mSharedPrefs = sharedPrefs;
- mDefaultDismissControl = defaultDismissControl;
- }
-
- public SuggestionList getSuggestions(boolean isSmartSuggestionEnabled) {
- final SuggestionList suggestionList = new SuggestionList();
- final int N = mSuggestionList.size();
- for (int i = 0; i < N; i++) {
- final SuggestionCategory category = mSuggestionList.get(i);
- if (category.exclusive && !isExclusiveCategoryExpired(category)) {
- // If suggestions from an exclusive category are present, parsing is stopped
- // and only suggestions from that category are displayed. Note that subsequent
- // exclusive categories are also ignored.
- final List<Tile> exclusiveSuggestions = new ArrayList<>();
-
- // Read suggestion and force isSmartSuggestion to be false so the rule defined
- // from each suggestion itself is used.
- readSuggestions(category, exclusiveSuggestions, false /* isSmartSuggestion */);
- if (!exclusiveSuggestions.isEmpty()) {
- final SuggestionList exclusiveList = new SuggestionList();
- exclusiveList.addSuggestions(category, exclusiveSuggestions);
- return exclusiveList;
- }
- } else {
- // Either the category is not exclusive, or the exclusiveness expired so we should
- // treat it as a normal category.
- final List<Tile> suggestions = new ArrayList<>();
- readSuggestions(category, suggestions, isSmartSuggestionEnabled);
- suggestionList.addSuggestions(category, suggestions);
- }
- }
- return suggestionList;
- }
-
- /**
- * Dismisses a suggestion, returns true if the suggestion has no more dismisses left and should
- * be disabled.
- */
- public boolean dismissSuggestion(Tile suggestion) {
- final String keyBase = suggestion.intent.getComponent().flattenToShortString();
- mSharedPrefs.edit()
- .putBoolean(keyBase + IS_DISMISSED, true)
- .commit();
- return true;
- }
-
- @VisibleForTesting
- public void filterSuggestions(
- List<Tile> suggestions, int countBefore, boolean isSmartSuggestionEnabled) {
- for (int i = countBefore; i < suggestions.size(); i++) {
- if (!isAvailable(suggestions.get(i)) ||
- !isSupported(suggestions.get(i)) ||
- !satisifesRequiredUserType(suggestions.get(i)) ||
- !satisfiesRequiredAccount(suggestions.get(i)) ||
- !satisfiesConnectivity(suggestions.get(i)) ||
- isDismissed(suggestions.get(i), isSmartSuggestionEnabled)) {
- suggestions.remove(i--);
- }
- }
- }
-
- @VisibleForTesting
- void readSuggestions(
- SuggestionCategory category, List<Tile> suggestions, boolean isSmartSuggestionEnabled) {
- int countBefore = suggestions.size();
- Intent intent = new Intent(Intent.ACTION_MAIN);
- intent.addCategory(category.category);
- if (category.pkg != null) {
- intent.setPackage(category.pkg);
- }
- TileUtils.getTilesForIntent(mContext, new UserHandle(UserHandle.myUserId()), intent,
- mAddCache, null, suggestions, true, false, false, true /* shouldUpdateTiles */);
- filterSuggestions(suggestions, countBefore, isSmartSuggestionEnabled);
- if (!category.multiple && suggestions.size() > (countBefore + 1)) {
- // If there are too many, remove them all and only re-add the one with the highest
- // priority.
- Tile item = suggestions.remove(suggestions.size() - 1);
- while (suggestions.size() > countBefore) {
- Tile last = suggestions.remove(suggestions.size() - 1);
- if (last.priority > item.priority) {
- item = last;
- }
- }
- // If category is marked as done, do not add any item.
- if (!isCategoryDone(category.category)) {
- suggestions.add(item);
- }
- }
- }
-
- private boolean isAvailable(Tile suggestion) {
- final String featuresRequired = suggestion.metaData.getString(META_DATA_REQUIRE_FEATURE);
- if (featuresRequired != null) {
- for (String feature : featuresRequired.split(",")) {
- if (TextUtils.isEmpty(feature)) {
- Log.w(TAG, "Found empty substring when parsing required features: "
- + featuresRequired);
- } else if (!mContext.getPackageManager().hasSystemFeature(feature)) {
- Log.i(TAG, suggestion.title + " requires unavailable feature " + feature);
- return false;
- }
- }
- }
- return true;
- }
-
- @RequiresPermission(Manifest.permission.MANAGE_USERS)
- private boolean satisifesRequiredUserType(Tile suggestion) {
- final String requiredUser = suggestion.metaData.getString(META_DATA_REQUIRE_USER_TYPE);
- if (requiredUser != null) {
- final UserManager userManager = mContext.getSystemService(UserManager.class);
- UserInfo userInfo = userManager.getUserInfo(UserHandle.myUserId());
- for (String userType : requiredUser.split("\\|")) {
- final boolean primaryUserCondtionMet = userInfo.isPrimary()
- && META_DATA_PRIMARY_USER_TYPE_VALUE.equals(userType);
- final boolean adminUserConditionMet = userInfo.isAdmin()
- && META_DATA_ADMIN_USER_TYPE_VALUE.equals(userType);
- final boolean guestUserCondtionMet = userInfo.isGuest()
- && META_DATA_GUEST_USER_TYPE_VALUE.equals(userType);
- final boolean restrictedUserCondtionMet = userInfo.isRestricted()
- && META_DATA_RESTRICTED_USER_TYPE_VALUE.equals(userType);
- if (primaryUserCondtionMet || adminUserConditionMet || guestUserCondtionMet
- || restrictedUserCondtionMet) {
- return true;
- }
- }
- Log.i(TAG, suggestion.title + " requires user type " + requiredUser);
- return false;
- }
- return true;
- }
-
- public boolean satisfiesRequiredAccount(Tile suggestion) {
- final String requiredAccountType = suggestion.metaData.getString(META_DATA_REQUIRE_ACCOUNT);
- if (requiredAccountType == null) {
- return true;
- }
- AccountManager accountManager = mContext.getSystemService(AccountManager.class);
- Account[] accounts = accountManager.getAccountsByType(requiredAccountType);
- boolean satisfiesRequiredAccount = accounts.length > 0;
- if (!satisfiesRequiredAccount) {
- Log.i(TAG, suggestion.title + " requires unavailable account type "
- + requiredAccountType);
- }
- return satisfiesRequiredAccount;
- }
-
- public boolean isSupported(Tile suggestion) {
- final int isSupportedResource = suggestion.metaData.getInt(META_DATA_IS_SUPPORTED);
- try {
- if (suggestion.intent == null) {
- return false;
- }
- final Resources res = mContext.getPackageManager().getResourcesForActivity(
- suggestion.intent.getComponent());
- boolean isSupported =
- isSupportedResource != 0 ? res.getBoolean(isSupportedResource) : true;
- if (!isSupported) {
- Log.i(TAG, suggestion.title + " requires unsupported resource "
- + isSupportedResource);
- }
- return isSupported;
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "Cannot find resources for " + suggestion.intent.getComponent());
- return false;
- } catch (Resources.NotFoundException e) {
- Log.w(TAG, "Cannot find resources for " + suggestion.intent.getComponent(), e);
- return false;
- }
- }
-
- private boolean satisfiesConnectivity(Tile suggestion) {
- final boolean isConnectionRequired =
- suggestion.metaData.getBoolean(META_DATA_IS_CONNECTION_REQUIRED);
- if (!isConnectionRequired) {
- return true;
- }
- ConnectivityManager cm =
- (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
- NetworkInfo netInfo = cm.getActiveNetworkInfo();
- boolean satisfiesConnectivity = netInfo != null && netInfo.isConnectedOrConnecting();
- if (!satisfiesConnectivity) {
- Log.i(TAG, suggestion.title + " is missing required connection.");
- }
- return satisfiesConnectivity;
- }
-
- public boolean isCategoryDone(String category) {
- String name = Settings.Secure.COMPLETED_CATEGORY_PREFIX + category;
- return Settings.Secure.getInt(mContext.getContentResolver(), name, 0) != 0;
- }
-
- public void markCategoryDone(String category) {
- String name = Settings.Secure.COMPLETED_CATEGORY_PREFIX + category;
- Settings.Secure.putInt(mContext.getContentResolver(), name, 1);
- }
-
- /**
- * Whether or not the category's exclusiveness has expired.
- */
- private boolean isExclusiveCategoryExpired(SuggestionCategory category) {
- final String keySetupTime = category.category + SETUP_TIME;
- final long currentTime = System.currentTimeMillis();
- if (!mSharedPrefs.contains(keySetupTime)) {
- mSharedPrefs.edit()
- .putLong(keySetupTime, currentTime)
- .commit();
- }
- if (category.exclusiveExpireDaysInMillis < 0) {
- // negative means never expires
- return false;
- }
- final long setupTime = mSharedPrefs.getLong(keySetupTime, 0);
- final long elapsedTime = currentTime - setupTime;
- Log.d(TAG, "Day " + elapsedTime / DateUtils.DAY_IN_MILLIS + " for " + category.category);
- return elapsedTime > category.exclusiveExpireDaysInMillis;
- }
-
- @VisibleForTesting
- boolean isDismissed(Tile suggestion, boolean isSmartSuggestionEnabled) {
- String dismissControl = getDismissControl(suggestion, isSmartSuggestionEnabled);
- String keyBase = suggestion.intent.getComponent().flattenToShortString();
- if (!mSharedPrefs.contains(keyBase + SETUP_TIME)) {
- mSharedPrefs.edit()
- .putLong(keyBase + SETUP_TIME, System.currentTimeMillis())
- .commit();
- }
- // Check if it's already manually dismissed
- final boolean isDismissed = mSharedPrefs.getBoolean(keyBase + IS_DISMISSED, false);
- if (isDismissed) {
- return true;
- }
- if (dismissControl == null) {
- return false;
- }
- // Parse when suggestion should first appear. return true to artificially hide suggestion
- // before then.
- int firstAppearDay = parseDismissString(dismissControl);
- long firstAppearDayInMs = getEndTime(mSharedPrefs.getLong(keyBase + SETUP_TIME, 0),
- firstAppearDay);
- if (System.currentTimeMillis() >= firstAppearDayInMs) {
- // Dismiss timeout has passed, undismiss it.
- mSharedPrefs.edit()
- .putBoolean(keyBase + IS_DISMISSED, false)
- .commit();
- return false;
- }
- return true;
- }
-
- private long getEndTime(long startTime, int daysDelay) {
- long days = daysDelay * DateUtils.DAY_IN_MILLIS;
- return startTime + days;
- }
-
- /**
- * Parse the first int from a string formatted as "0,1,2..."
- * The value means suggestion should first appear on Day X.
- */
- private int parseDismissString(String dismissControl) {
- final String[] dismissStrs = dismissControl.split(",");
- return Integer.parseInt(dismissStrs[0]);
- }
-
- private String getDismissControl(Tile suggestion, boolean isSmartSuggestionEnabled) {
- if (isSmartSuggestionEnabled) {
- return mDefaultDismissControl;
- } else {
- return suggestion.metaData.getString(META_DATA_DISMISS_CONTROL);
- }
- }
-
- private static class SuggestionOrderInflater {
- private static final String TAG_LIST = "optional-steps";
- private static final String TAG_ITEM = "step";
-
- private static final String ATTR_CATEGORY = "category";
- private static final String ATTR_PACKAGE = "package";
- private static final String ATTR_MULTIPLE = "multiple";
- private static final String ATTR_EXCLUSIVE = "exclusive";
- private static final String ATTR_EXCLUSIVE_EXPIRE_DAYS = "exclusiveExpireDays";
-
- private final Context mContext;
-
- public SuggestionOrderInflater(Context context) {
- mContext = context;
- }
-
- public Object parse(int resource) {
- XmlPullParser parser = mContext.getResources().getXml(resource);
- final AttributeSet attrs = Xml.asAttributeSet(parser);
- try {
- // Look for the root node.
- int type;
- do {
- type = parser.next();
- } while (type != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT);
-
- if (type != XmlPullParser.START_TAG) {
- throw new InflateException(parser.getPositionDescription()
- + ": No start tag found!");
- }
-
- // Temp is the root that was found in the xml
- Object xmlRoot = onCreateItem(parser.getName(), attrs);
-
- // Inflate all children under temp
- rParse(parser, xmlRoot, attrs);
- return xmlRoot;
- } catch (XmlPullParserException | IOException e) {
- Log.w(TAG, "Problem parser resource " + resource, e);
- return null;
- }
- }
-
- /**
- * Recursive method used to descend down the xml hierarchy and instantiate
- * items, instantiate their children.
- */
- private void rParse(XmlPullParser parser, Object parent, final AttributeSet attrs)
- throws XmlPullParserException, IOException {
- final int depth = parser.getDepth();
-
- int type;
- while (((type = parser.next()) != XmlPullParser.END_TAG ||
- parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
- if (type != XmlPullParser.START_TAG) {
- continue;
- }
-
- final String name = parser.getName();
-
- Object item = onCreateItem(name, attrs);
- onAddChildItem(parent, item);
- rParse(parser, item, attrs);
- }
- }
-
- protected void onAddChildItem(Object parent, Object child) {
- if (parent instanceof List<?> && child instanceof SuggestionCategory) {
- ((List<SuggestionCategory>) parent).add((SuggestionCategory) child);
- } else {
- throw new IllegalArgumentException("Parent was not a list");
- }
- }
-
- protected Object onCreateItem(String name, AttributeSet attrs) {
- if (name.equals(TAG_LIST)) {
- return new ArrayList<SuggestionCategory>();
- } else if (name.equals(TAG_ITEM)) {
- SuggestionCategory category = new SuggestionCategory();
- category.category = attrs.getAttributeValue(null, ATTR_CATEGORY);
- category.pkg = attrs.getAttributeValue(null, ATTR_PACKAGE);
- String multiple = attrs.getAttributeValue(null, ATTR_MULTIPLE);
- category.multiple = !TextUtils.isEmpty(multiple) && Boolean.parseBoolean(multiple);
- String exclusive = attrs.getAttributeValue(null, ATTR_EXCLUSIVE);
- category.exclusive =
- !TextUtils.isEmpty(exclusive) && Boolean.parseBoolean(exclusive);
- String expireDaysAttr = attrs.getAttributeValue(null,
- ATTR_EXCLUSIVE_EXPIRE_DAYS);
- long expireDays = !TextUtils.isEmpty(expireDaysAttr)
- ? Integer.parseInt(expireDaysAttr)
- : -1;
- category.exclusiveExpireDaysInMillis = DateUtils.DAY_IN_MILLIS * expireDays;
- return category;
- } else {
- throw new IllegalArgumentException("Unknown item " + name);
- }
- }
- }
-}
-
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/AppRestrictionsHelper.java b/packages/SettingsLib/src/com/android/settingslib/users/AppRestrictionsHelper.java
index 7cdbe719df78..9451b3620418 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/AppRestrictionsHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/AppRestrictionsHelper.java
@@ -31,12 +31,13 @@ import android.graphics.drawable.Drawable;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
-import androidx.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.Log;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
+import androidx.annotation.VisibleForTesting;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/AsyncLoader.java b/packages/SettingsLib/src/com/android/settingslib/utils/AsyncLoader.java
index 06770ac09558..64b9ffe7a735 100644
--- a/packages/SettingsLib/src/com/android/settingslib/utils/AsyncLoader.java
+++ b/packages/SettingsLib/src/com/android/settingslib/utils/AsyncLoader.java
@@ -29,7 +29,9 @@ import android.content.Context;
* This loader is based on the MailAsyncTaskLoader from the AOSP EmailUnified repo.
*
* @param <T> the data type to be loaded.
+ * @deprecated Framework loader is deprecated, use the compat version instead.
*/
+@Deprecated
public abstract class AsyncLoader<T> extends AsyncTaskLoader<T> {
private T mResult;
diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/AsyncLoaderCompat.java b/packages/SettingsLib/src/com/android/settingslib/utils/AsyncLoaderCompat.java
new file mode 100644
index 000000000000..916d7e312631
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/utils/AsyncLoaderCompat.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2018 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.settingslib.utils;
+
+import android.content.Context;
+
+import androidx.loader.content.AsyncTaskLoader;
+
+/**
+ * This class fills in some boilerplate for AsyncTaskLoader to actually load things.
+ *
+ * Subclasses need to implement {@link AsyncLoaderCompat#loadInBackground()} to perform the actual
+ * background task, and {@link AsyncLoaderCompat#onDiscardResult(T)} to clean up previously loaded
+ * results.
+ *
+ * This loader is based on the MailAsyncTaskLoader from the AOSP EmailUnified repo.
+ *
+ * @param <T> the data type to be loaded.
+ */
+public abstract class AsyncLoaderCompat<T> extends AsyncTaskLoader<T> {
+ private T mResult;
+
+ public AsyncLoaderCompat(final Context context) {
+ super(context);
+ }
+
+ @Override
+ protected void onStartLoading() {
+ if (mResult != null) {
+ deliverResult(mResult);
+ }
+
+ if (takeContentChanged() || mResult == null) {
+ forceLoad();
+ }
+ }
+
+ @Override
+ protected void onStopLoading() {
+ cancelLoad();
+ }
+
+ @Override
+ public void deliverResult(final T data) {
+ if (isReset()) {
+ if (data != null) {
+ onDiscardResult(data);
+ }
+ return;
+ }
+
+ final T oldResult = mResult;
+ mResult = data;
+
+ if (isStarted()) {
+ super.deliverResult(data);
+ }
+
+ if (oldResult != null && oldResult != mResult) {
+ onDiscardResult(oldResult);
+ }
+ }
+
+ @Override
+ protected void onReset() {
+ super.onReset();
+
+ onStopLoading();
+
+ if (mResult != null) {
+ onDiscardResult(mResult);
+ }
+ mResult = null;
+ }
+
+ @Override
+ public void onCanceled(final T data) {
+ super.onCanceled(data);
+
+ if (data != null) {
+ onDiscardResult(data);
+ }
+ }
+
+ /**
+ * Called when discarding the load results so subclasses can take care of clean-up or
+ * recycling tasks. This is not called if the same result (by way of pointer equality) is
+ * returned again by a subsequent call to loadInBackground, or if result is null.
+ *
+ * Note that this may be called concurrently with loadInBackground(), and in some circumstances
+ * may be called more than once for a given object.
+ *
+ * @param result The value returned from {@link AsyncLoaderCompat#loadInBackground()} which
+ * is to be discarded.
+ */
+ protected abstract void onDiscardResult(T result);
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/IconCache.java b/packages/SettingsLib/src/com/android/settingslib/utils/IconCache.java
index f0548ff7a923..c8c8ac22089a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/utils/IconCache.java
+++ b/packages/SettingsLib/src/com/android/settingslib/utils/IconCache.java
@@ -19,6 +19,7 @@ package com.android.settingslib.utils;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
+
import androidx.annotation.VisibleForTesting;
import androidx.collection.ArrayMap;
diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java b/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java
index e16da84ce713..43c97df24a58 100644
--- a/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java
+++ b/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java
@@ -22,9 +22,10 @@ import android.icu.text.MeasureFormat;
import android.icu.text.MeasureFormat.FormatWidth;
import android.icu.util.Measure;
import android.icu.util.MeasureUnit;
-import androidx.annotation.Nullable;
import android.text.TextUtils;
+import androidx.annotation.Nullable;
+
import com.android.settingslib.R;
import java.time.Instant;
@@ -80,6 +81,30 @@ public class PowerUtil {
return null;
}
+ /**
+ * Method to produce a shortened string describing the remaining battery. Suitable for Quick
+ * Settings and other areas where space is constrained.
+ *
+ * @param context context to fetch descriptions from
+ * @param drainTimeMs The estimated time remaining before the phone dies in milliseconds.
+ *
+ * @return a properly formatted and localized short string describing how much time remains
+ * before the battery runs out.
+ */
+ @Nullable
+ public static String getBatteryRemainingShortStringFormatted(
+ Context context, long drainTimeMs) {
+ if (drainTimeMs <= 0) {
+ return null;
+ }
+
+ if (drainTimeMs <= ONE_DAY_MILLIS) {
+ return getRegularTimeRemainingShortString(context, drainTimeMs);
+ } else {
+ return getMoreThanOneDayShortString(context, drainTimeMs);
+ }
+ }
+
private static String getShutdownImminentString(Context context, String percentageString) {
return TextUtils.isEmpty(percentageString)
? context.getString(R.string.power_remaining_duration_only_shutdown_imminent)
@@ -119,6 +144,14 @@ public class PowerUtil {
}
}
+ private static String getMoreThanOneDayShortString(Context context, long drainTimeMs) {
+ final long roundedTimeMs = roundTimeToNearestThreshold(drainTimeMs, ONE_HOUR_MILLIS);
+ CharSequence timeString = StringUtil.formatElapsedTime(context, roundedTimeMs,
+ false /* withSeconds */);
+
+ return context.getString(R.string.power_remaining_duration_only_short, timeString);
+ }
+
private static String getMoreThanTwoDaysString(Context context, String percentageString) {
final Locale currentLocale = context.getResources().getConfiguration().getLocales().get(0);
final MeasureFormat frmt = MeasureFormat.getInstance(currentLocale, FormatWidth.SHORT);
@@ -161,6 +194,22 @@ public class PowerUtil {
}
}
+ private static String getRegularTimeRemainingShortString(Context context, long drainTimeMs) {
+ // Get the time of day we think device will die rounded to the nearest 15 min.
+ final long roundedTimeOfDayMs =
+ roundTimeToNearestThreshold(
+ System.currentTimeMillis() + drainTimeMs,
+ FIFTEEN_MINUTES_MILLIS);
+
+ // convert the time to a properly formatted string.
+ String skeleton = android.text.format.DateFormat.getTimeFormatString(context);
+ DateFormat fmt = DateFormat.getInstanceForSkeleton(skeleton);
+ Date date = Date.from(Instant.ofEpochMilli(roundedTimeOfDayMs));
+ CharSequence timeString = fmt.format(date);
+
+ return context.getString(R.string.power_discharge_by_only_short, timeString);
+ }
+
public static long convertUsToMs(long timeUs) {
return timeUs / 1000;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreference.java b/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreference.java
index 87a56c7548d2..f02044dc52e0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreference.java
@@ -17,13 +17,14 @@
package com.android.settingslib.widget;
import android.content.Context;
-import androidx.core.content.res.TypedArrayUtils;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceViewHolder;
import android.text.method.LinkMovementMethod;
import android.util.AttributeSet;
import android.widget.TextView;
+import androidx.core.content.res.TypedArrayUtils;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
import com.android.settingslib.R;
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixin.java b/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixin.java
index 5883754fa2ed..fcf236337703 100644
--- a/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixin.java
+++ b/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixin.java
@@ -17,6 +17,7 @@
package com.android.settingslib.widget;
import android.content.Context;
+
import androidx.preference.PreferenceFragment;
import androidx.preference.PreferenceScreen;
@@ -24,6 +25,12 @@ import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.SetPreferenceScreen;
+/**
+ * Framework mixin is deprecated, use the compat version instead.
+ *
+ * @deprecated
+ */
+@Deprecated
public class FooterPreferenceMixin implements LifecycleObserver, SetPreferenceScreen {
private final PreferenceFragment mFragment;
diff --git a/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixinCompat.java b/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixinCompat.java
new file mode 100644
index 000000000000..d45e56d486ae
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixinCompat.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2018 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.settingslib.widget;
+
+import android.content.Context;
+
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.SetPreferenceScreen;
+
+public class FooterPreferenceMixinCompat implements LifecycleObserver, SetPreferenceScreen {
+
+ private final PreferenceFragmentCompat mFragment;
+ private FooterPreference mFooterPreference;
+
+ public FooterPreferenceMixinCompat(PreferenceFragmentCompat fragment, Lifecycle lifecycle) {
+ mFragment = fragment;
+ lifecycle.addObserver(this);
+ }
+
+ @Override
+ public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
+ if (mFooterPreference != null) {
+ preferenceScreen.addPreference(mFooterPreference);
+ }
+ }
+
+ /**
+ * Creates a new {@link FooterPreference}.
+ */
+ public FooterPreference createFooterPreference() {
+ final PreferenceScreen screen = mFragment.getPreferenceScreen();
+ if (mFooterPreference != null && screen != null) {
+ screen.removePreference(mFooterPreference);
+ }
+ mFooterPreference = new FooterPreference(getPrefContext());
+
+ if (screen != null) {
+ screen.addPreference(mFooterPreference);
+ }
+ return mFooterPreference;
+ }
+
+ /**
+ * Returns an UI context with theme properly set for new Preference objects.
+ */
+ private Context getPrefContext() {
+ return mFragment.getPreferenceManager().getContext();
+ }
+
+ public boolean hasFooter() {
+ return mFooterPreference != null;
+ }
+}
+
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index b9c76013b0d7..22d5d83b702a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -48,14 +48,12 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
-import androidx.annotation.NonNull;
-import android.text.Spannable;
-import android.text.SpannableString;
import android.text.TextUtils;
-import android.text.style.TtsSpan;
import android.util.ArraySet;
import android.util.Log;
+import androidx.annotation.NonNull;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.R;
import com.android.settingslib.utils.ThreadUtils;
@@ -163,6 +161,9 @@ public class AccessPoint implements Comparable<AccessPoint> {
public static final int SECURITY_WEP = 1;
public static final int SECURITY_PSK = 2;
public static final int SECURITY_EAP = 3;
+ public static final int SECURITY_OWE = 4;
+ public static final int SECURITY_SAE = 5;
+ public static final int SECURITY_EAP_SUITE_B = 6;
private static final int PSK_UNKNOWN = 0;
private static final int PSK_WPA = 1;
@@ -435,7 +436,7 @@ public class AccessPoint implements Comparable<AccessPoint> {
if (isConnectable()) {
builder.append(',').append("connectable");
}
- if (security != SECURITY_NONE) {
+ if ((security != SECURITY_NONE) && (security != SECURITY_OWE)) {
builder.append(',').append(securityToString(security, pskType));
}
builder.append(",level=").append(getLevel());
@@ -722,6 +723,9 @@ public class AccessPoint implements Comparable<AccessPoint> {
case SECURITY_EAP:
return concise ? context.getString(R.string.wifi_security_short_eap) :
context.getString(R.string.wifi_security_eap);
+ case SECURITY_EAP_SUITE_B:
+ return concise ? context.getString(R.string.wifi_security_short_eap_suiteb) :
+ context.getString(R.string.wifi_security_eap_suiteb);
case SECURITY_PSK:
switch (pskType) {
case PSK_WPA:
@@ -741,6 +745,12 @@ public class AccessPoint implements Comparable<AccessPoint> {
case SECURITY_WEP:
return concise ? context.getString(R.string.wifi_security_short_wep) :
context.getString(R.string.wifi_security_wep);
+ case SECURITY_SAE:
+ return concise ? context.getString(R.string.wifi_security_short_sae) :
+ context.getString(R.string.wifi_security_sae);
+ case SECURITY_OWE:
+ return concise ? context.getString(R.string.wifi_security_short_owe) :
+ context.getString(R.string.wifi_security_owe);
case SECURITY_NONE:
default:
return concise ? "" : context.getString(R.string.wifi_security_none);
@@ -756,10 +766,7 @@ public class AccessPoint implements Comparable<AccessPoint> {
}
public CharSequence getSsid() {
- final SpannableString str = new SpannableString(ssid);
- str.setSpan(new TtsSpan.TelephoneBuilder(ssid).build(), 0, ssid.length(),
- Spannable.SPAN_INCLUSIVE_INCLUSIVE);
- return str;
+ return ssid;
}
public String getConfigName() {
@@ -985,13 +992,20 @@ public class AccessPoint implements Comparable<AccessPoint> {
* Can only be called for unsecured networks.
*/
public void generateOpenNetworkConfig() {
- if (security != SECURITY_NONE)
+ if ((security != SECURITY_NONE) && (security != SECURITY_OWE)) {
throw new IllegalStateException();
+ }
if (mConfig != null)
return;
mConfig = new WifiConfiguration();
mConfig.SSID = AccessPoint.convertToQuotedString(ssid);
- mConfig.allowedKeyManagement.set(KeyMgmt.NONE);
+
+ if (security == SECURITY_NONE) {
+ mConfig.allowedKeyManagement.set(KeyMgmt.NONE);
+ } else {
+ mConfig.allowedKeyManagement.set(KeyMgmt.OWE);
+ mConfig.requirePMF = true;
+ }
}
public void saveWifiState(Bundle savedState) {
@@ -1293,22 +1307,38 @@ public class AccessPoint implements Comparable<AccessPoint> {
private static int getSecurity(ScanResult result) {
if (result.capabilities.contains("WEP")) {
return SECURITY_WEP;
+ } else if (result.capabilities.contains("SAE")) {
+ return SECURITY_SAE;
} else if (result.capabilities.contains("PSK")) {
return SECURITY_PSK;
+ } else if (result.capabilities.contains("EAP_SUITE_B_192")) {
+ return SECURITY_EAP_SUITE_B;
} else if (result.capabilities.contains("EAP")) {
return SECURITY_EAP;
+ } else if (result.capabilities.contains("OWE")) {
+ return SECURITY_OWE;
}
+
return SECURITY_NONE;
}
static int getSecurity(WifiConfiguration config) {
+ if (config.allowedKeyManagement.get(KeyMgmt.SAE)) {
+ return SECURITY_SAE;
+ }
if (config.allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
return SECURITY_PSK;
}
+ if (config.allowedKeyManagement.get(KeyMgmt.SUITE_B_192)) {
+ return SECURITY_EAP_SUITE_B;
+ }
if (config.allowedKeyManagement.get(KeyMgmt.WPA_EAP) ||
config.allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
return SECURITY_EAP;
}
+ if (config.allowedKeyManagement.get(KeyMgmt.OWE)) {
+ return SECURITY_OWE;
+ }
return (config.wepKeys[0] != null) ? SECURITY_WEP : SECURITY_NONE;
}
@@ -1326,6 +1356,12 @@ public class AccessPoint implements Comparable<AccessPoint> {
return "PSK";
} else if (security == SECURITY_EAP) {
return "EAP";
+ } else if (security == SECURITY_SAE) {
+ return "SAE";
+ } else if (security == SECURITY_EAP_SUITE_B) {
+ return "SUITE_B";
+ } else if (security == SECURITY_OWE) {
+ return "OWE";
}
return "NONE";
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
index b07dd8f18354..db364a3b75e5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
@@ -25,9 +25,6 @@ import android.graphics.drawable.StateListDrawable;
import android.net.wifi.WifiConfiguration;
import android.os.Looper;
import android.os.UserHandle;
-import androidx.annotation.VisibleForTesting;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceViewHolder;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.SparseArray;
@@ -35,9 +32,12 @@ import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
import com.android.settingslib.R;
import com.android.settingslib.TronUtils;
-import com.android.settingslib.TwoTargetPreference;
import com.android.settingslib.Utils;
import com.android.settingslib.wifi.AccessPoint.Speed;
@@ -183,7 +183,7 @@ public class AccessPointPreference extends Preference {
Drawable drawable = mIconInjector.getIcon(level);
if (!mForSavedNetworks && drawable != null) {
- drawable.setTint(Utils.getColorAttr(context, android.R.attr.colorControlNormal));
+ drawable.setTintList(Utils.getColorAttr(context, android.R.attr.colorControlNormal));
setIcon(drawable);
} else {
safeSetDefaultIcon();
@@ -200,7 +200,8 @@ public class AccessPointPreference extends Preference {
if (frictionImageView == null || mFrictionSld == null) {
return;
}
- if (mAccessPoint.getSecurity() != AccessPoint.SECURITY_NONE) {
+ if ((mAccessPoint.getSecurity() != AccessPoint.SECURITY_NONE)
+ && (mAccessPoint.getSecurity() != AccessPoint.SECURITY_OWE)) {
mFrictionSld.setState(STATE_SECURED);
} else if (mAccessPoint.isMetered()) {
mFrictionSld.setState(STATE_METERED);
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
index 5862e6f19e16..afea5d2078b0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
@@ -24,6 +24,7 @@ import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.os.Bundle;
import android.os.Parcelable;
+
import androidx.annotation.Keep;
import com.android.settingslib.wifi.AccessPoint.Speed;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiSavedConfigUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiSavedConfigUtils.java
index 159b2a1047d9..19e38081fcad 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiSavedConfigUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiSavedConfigUtils.java
@@ -55,8 +55,10 @@ public class WifiSavedConfigUtils {
try {
List<PasspointConfiguration> savedPasspointConfigs =
wifiManager.getPasspointConfigurations();
- for (PasspointConfiguration config : savedPasspointConfigs) {
- savedConfigs.add(new AccessPoint(context, config));
+ if (savedPasspointConfigs != null) {
+ for (PasspointConfiguration config : savedPasspointConfigs) {
+ savedConfigs.add(new AccessPoint(context, config));
+ }
}
} catch (UnsupportedOperationException e) {
// Passpoint not supported.
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index fe0b35b4191c..089f773ec1e9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -163,6 +163,12 @@ public class WifiStatusTracker extends ConnectivityManager.NetworkCallback {
? null : AccessPoint.getSpeedLabel(mContext, scoredNetwork, rssi);
}
+ /** Refresh the status label on Locale changed. */
+ public void refreshLocale() {
+ updateStatusLabel();
+ mCallback.run();
+ }
+
private String getValidSsid(WifiInfo info) {
String ssid = info.getSSID();
if (ssid != null && !WifiSsid.NONE.equals(ssid)) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 6211d1214fcc..0dbc037f2298 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -41,15 +41,16 @@ import android.os.Message;
import android.os.Process;
import android.os.SystemClock;
import android.provider.Settings;
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.widget.Toast;
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
import com.android.settingslib.R;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
@@ -80,7 +81,7 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro
private static final long DEFAULT_MAX_CACHED_SCORE_AGE_MILLIS = 20 * DateUtils.MINUTE_IN_MILLIS;
/** Maximum age of scan results to hold onto while actively scanning. **/
- private static final long MAX_SCAN_RESULT_AGE_MILLIS = 25000;
+ private static final long MAX_SCAN_RESULT_AGE_MILLIS = 15000;
private static final String TAG = "WifiTracker";
private static final boolean DBG() {
@@ -133,8 +134,8 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro
/**
* Tracks whether fresh scan results have been received since scanning start.
*
- * <p>If this variable is false, we will not evict the scan result cache or invoke callbacks
- * so that we do not update the UI with stale data / clear out existing UI elements prematurely.
+ * <p>If this variable is false, we will not invoke callbacks so that we do not
+ * update the UI with stale data / clear out existing UI elements prematurely.
*/
private boolean mStaleScanResults = true;
@@ -326,7 +327,8 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro
* <p>Intended to only be invoked within {@link #onStart()}.
*/
@MainThread
- private void forceUpdate() {
+ @VisibleForTesting
+ void forceUpdate() {
mLastInfo = mWifiManager.getConnectionInfo();
mLastNetworkInfo = mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork());
@@ -442,10 +444,8 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro
mScanResultCache.put(newResult.BSSID, newResult);
}
- // Don't evict old results if no new scan results
- if (!mStaleScanResults) {
- evictOldScans();
- }
+ // Evict old results in all conditions
+ evictOldScans();
ArrayMap<String, List<ScanResult>> scanResultsByApKey = new ArrayMap<>();
for (ScanResult result : mScanResultCache.values()) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java
index e73d952cd645..93e4fce6e74b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java
@@ -16,6 +16,7 @@
package com.android.settingslib.wifi;
import android.content.Context;
+
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
index 4792317c9f1e..4e6c005457c0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
@@ -21,6 +21,7 @@ import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.os.SystemClock;
+
import androidx.annotation.VisibleForTesting;
import com.android.settingslib.R;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wrapper/LocationManagerWrapper.java b/packages/SettingsLib/src/com/android/settingslib/wrapper/LocationManagerWrapper.java
deleted file mode 100644
index 1a268a608e5d..000000000000
--- a/packages/SettingsLib/src/com/android/settingslib/wrapper/LocationManagerWrapper.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2017 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.settingslib.wrapper;
-
-import android.location.LocationManager;
-import android.os.UserHandle;
-
-/**
- * This class replicates some methods of android.location.LocationManager that are new and not
- * yet available in our current version of Robolectric. It provides a thin wrapper to call the real
- * methods in production and a mock in tests.
- */
-public class LocationManagerWrapper {
-
- private LocationManager mLocationManager;
-
- public LocationManagerWrapper(LocationManager locationManager) {
- mLocationManager = locationManager;
- }
-
- /** Returns the real {@code LocationManager} object */
- public LocationManager getLocationManager() {
- return mLocationManager;
- }
-
- /** Wraps {@code LocationManager.isProviderEnabled} method */
- public boolean isProviderEnabled(String provider) {
- return mLocationManager.isProviderEnabled(provider);
- }
-
- /** Wraps {@code LocationManager.setProviderEnabledForUser} method */
- public void setProviderEnabledForUser(String provider, boolean enabled, UserHandle userHandle) {
- mLocationManager.setProviderEnabledForUser(provider, enabled, userHandle);
- }
-
- /** Wraps {@code LocationManager.isLocationEnabled} method */
- public boolean isLocationEnabled() {
- return mLocationManager.isLocationEnabled();
- }
-
- /** Wraps {@code LocationManager.isLocationEnabledForUser} method */
- public boolean isLocationEnabledForUser(UserHandle userHandle) {
- return mLocationManager.isLocationEnabledForUser(userHandle);
- }
-
- /** Wraps {@code LocationManager.setLocationEnabledForUser} method */
- public void setLocationEnabledForUser(boolean enabled, UserHandle userHandle) {
- mLocationManager.setLocationEnabledForUser(enabled, userHandle);
- }
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wrapper/PackageManagerWrapper.java b/packages/SettingsLib/src/com/android/settingslib/wrapper/PackageManagerWrapper.java
deleted file mode 100644
index 235daf23a664..000000000000
--- a/packages/SettingsLib/src/com/android/settingslib/wrapper/PackageManagerWrapper.java
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- * Copyright (C) 2017 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.settingslib.wrapper;
-
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageDeleteObserver;
-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.UserHandle;
-import android.os.storage.VolumeInfo;
-
-import java.util.List;
-
-/**
- * A thin wrapper class that simplifies testing by putting a mockable layer between the application
- * and the PackageManager. This class only provides access to the minimum number of functions from
- * the PackageManager needed for DeletionHelper to work.
- */
-public class PackageManagerWrapper {
-
- private final PackageManager mPm;
-
- public PackageManagerWrapper(PackageManager pm) {
- mPm = pm;
- }
-
- /**
- * Returns the real {@code PackageManager} object.
- */
- public PackageManager getPackageManager() {
- return mPm;
- }
-
- /**
- * Calls {@code PackageManager.getInstalledApplicationsAsUser()}.
- *
- * @see android.content.pm.PackageManager#getInstalledApplicationsAsUser
- */
- public List<ApplicationInfo> getInstalledApplicationsAsUser(int flags, int userId) {
- return mPm.getInstalledApplicationsAsUser(flags, userId);
- }
-
- /**
- * Calls {@code PackageManager.getInstalledPackagesAsUser}
- */
- public List<PackageInfo> getInstalledPackagesAsUser(int flags, int userId) {
- return mPm.getInstalledPackagesAsUser(flags, userId);
- }
-
- /**
- * Calls {@code PackageManager.hasSystemFeature()}.
- *
- * @see android.content.pm.PackageManager#hasSystemFeature
- */
- public boolean hasSystemFeature(String name) {
- return mPm.hasSystemFeature(name);
- }
-
- /**
- * Calls {@code PackageManager.queryIntentActivitiesAsUser()}.
- *
- * @see android.content.pm.PackageManager#queryIntentActivitiesAsUser
- */
- public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, int flags, int userId) {
- return mPm.queryIntentActivitiesAsUser(intent, flags, userId);
- }
-
- /**
- * Calls {@code PackageManager.getInstallReason()}.
- *
- * @see android.content.pm.PackageManager#getInstallReason
- */
- public int getInstallReason(String packageName, UserHandle user) {
- return mPm.getInstallReason(packageName, user);
- }
-
- /**
- * Calls {@code PackageManager.getApplicationInfoAsUser}
- */
- public ApplicationInfo getApplicationInfoAsUser(String packageName, int i, int userId)
- throws PackageManager.NameNotFoundException {
- return mPm.getApplicationInfoAsUser(packageName, i, userId);
- }
-
- /**
- * Calls {@code PackageManager.setDefaultBrowserPackageNameAsUser}
- */
- public boolean setDefaultBrowserPackageNameAsUser(String packageName, int userId) {
- return mPm.setDefaultBrowserPackageNameAsUser(packageName, userId);
- }
-
- /**
- * Calls {@code PackageManager.getDefaultBrowserPackageNameAsUser}
- */
- public String getDefaultBrowserPackageNameAsUser(int userId) {
- return mPm.getDefaultBrowserPackageNameAsUser(userId);
- }
-
- /**
- * Calls {@code PackageManager.getHomeActivities}
- */
- public ComponentName getHomeActivities(List<ResolveInfo> homeActivities) {
- return mPm.getHomeActivities(homeActivities);
- }
-
- /**
- * Calls {@code PackageManager.queryIntentServicesAsUser}
- */
- public List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int i, int user) {
- return mPm.queryIntentServicesAsUser(intent, i, user);
- }
-
- /**
- * Calls {@code PackageManager.queryIntentServices}
- */
- public List<ResolveInfo> queryIntentServices(Intent intent, int i) {
- return mPm.queryIntentServices(intent, i);
- }
-
- /**
- * Calls {@code PackageManager.replacePreferredActivity}
- */
- public void replacePreferredActivity(IntentFilter homeFilter, int matchCategoryEmpty,
- ComponentName[] componentNames, ComponentName component) {
- mPm.replacePreferredActivity(homeFilter, matchCategoryEmpty, componentNames, component);
- }
-
- /**
- * Gets information about a particular package from the package manager.
- *
- * @param packageName The name of the package we would like information about.
- * @param i additional options flags. see javadoc for
- * {@link PackageManager#getPackageInfo(String, int)}
- * @return The PackageInfo for the requested package
- */
- public PackageInfo getPackageInfo(String packageName, int i) throws NameNotFoundException {
- return mPm.getPackageInfo(packageName, i);
- }
-
- /**
- * Retrieves the icon associated with this particular set of ApplicationInfo
- *
- * @param info The ApplicationInfo to retrieve the icon for
- * @return The icon as a drawable.
- */
- public Drawable getUserBadgedIcon(ApplicationInfo info) {
- return mPm.getUserBadgedIcon(mPm.loadUnbadgedItemIcon(info, info),
- new UserHandle(UserHandle.getUserId(info.uid)));
- }
-
- /**
- * Retrieves the label associated with the particular set of ApplicationInfo
- *
- * @param app The ApplicationInfo to retrieve the label for
- * @return the label as a CharSequence
- */
- public CharSequence loadLabel(ApplicationInfo app) {
- return app.loadLabel(mPm);
- }
-
- /**
- * Retrieve all activities that can be performed for the given intent.
- */
- public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) {
- return mPm.queryIntentActivities(intent, flags);
- }
-
- /**
- * Calls {@code PackageManager.getPrimaryStorageCurrentVolume}
- */
- public VolumeInfo getPrimaryStorageCurrentVolume() {
- return mPm.getPrimaryStorageCurrentVolume();
- }
-
- /**
- * Calls {@code PackageManager.deletePackageAsUser}
- */
- public void deletePackageAsUser(String packageName, IPackageDeleteObserver observer, int flags,
- int userId) {
- mPm.deletePackageAsUser(packageName, observer, flags, userId);
- }
-
- /**
- * Calls {@code PackageManager.getPackageUidAsUser}
- */
- public int getPackageUidAsUser(String pkg, int userId)
- throws PackageManager.NameNotFoundException {
- return mPm.getPackageUidAsUser(pkg, userId);
- }
-
- /**
- * Calls {@code PackageManager.setApplicationEnabledSetting}
- */
- public void setApplicationEnabledSetting(String packageName, int newState, int flags) {
- mPm.setApplicationEnabledSetting(packageName, newState, flags);
- }
-
- /**
- * Calls {@code PackageManager.getApplicationEnabledSetting}
- */
- public int getApplicationEnabledSetting(String packageName) {
- return mPm.getApplicationEnabledSetting(packageName);
- }
-
- /**
- * Calls {@code PackageManager.setComponentEnabledSetting}
- */
- public void setComponentEnabledSetting(ComponentName componentName, int newState, int flags) {
- mPm.setComponentEnabledSetting(componentName, newState, flags);
- }
-
- /**
- * Calls {@code PackageManager.getApplicationInfo}
- */
- public ApplicationInfo getApplicationInfo(String packageName, int flags)
- throws NameNotFoundException {
- return mPm.getApplicationInfo(packageName, flags);
- }
-
- /**
- * Calls {@code PackageManager.getApplicationLabel}
- */
- public CharSequence getApplicationLabel(ApplicationInfo info) {
- return mPm.getApplicationLabel(info);
- }
-
- /**
- * Calls {@code PackageManager.queryBroadcastReceivers}
- */
- public List<ResolveInfo> queryBroadcastReceivers(Intent intent, int flags) {
- return mPm.queryBroadcastReceivers(intent, flags);
- }
-}
-