createPreferenceControllers(
Context context) {
diff --git a/src/com/android/settings/RingtonePreference.java b/src/com/android/settings/RingtonePreference.java
index 6b9dad7ffbf..43600c28f6b 100644
--- a/src/com/android/settings/RingtonePreference.java
+++ b/src/com/android/settings/RingtonePreference.java
@@ -40,6 +40,7 @@
*
* If the user chooses the "Default" item, the saved string will be one of
* {@link System#DEFAULT_RINGTONE_URI},
+ * {@link System#DEFAULT_RINGTONE2_URI},
* {@link System#DEFAULT_NOTIFICATION_URI}, or
* {@link System#DEFAULT_ALARM_ALERT_URI}. If the user chooses the "Silent"
* item, the saved string will be an empty string.
@@ -55,6 +56,9 @@ public class RingtonePreference extends Preference {
private static final String TAG = "RingtonePreference";
+ // Default is slot0
+ private int mSlotId = 0;
+
private int mRingtoneType;
private boolean mShowDefault;
private boolean mShowSilent;
@@ -89,6 +93,25 @@ public int getUserId() {
return mUserId;
}
+ /**
+ * Sets the slot id that this preference belongs to.
+ *
+ * @param slotId The slot id that this preference belongs to.
+ */
+ public void setSlotId(int slotId) {
+ mSlotId = slotId;
+ }
+
+ /**
+ * Returns the slot id that this preference belongs to.
+ *
+ * @return The slot id that this preference belongs to.
+ * @see #setSlotId(int)
+ */
+ public int getSlotId() {
+ return mSlotId;
+ }
+
/**
* Returns the sound type(s) that are shown in the picker.
*
@@ -167,7 +190,7 @@ public void onPrepareRingtonePickerIntent(Intent ringtonePickerIntent) {
ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, mShowDefault);
if (mShowDefault) {
ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI,
- RingtoneManager.getDefaultUri(getRingtoneType()));
+ RingtoneManager.getDefaultUriBySlot(getRingtoneType(), getSlotId()));
}
ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, mShowSilent);
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index 3367bf18f9f..a128e22b86f 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -92,6 +92,7 @@ public static class DisplaySettingsActivity extends SettingsActivity { /* empty
public static class NightDisplaySettingsActivity extends SettingsActivity { /* empty */ }
public static class NightDisplaySuggestionActivity extends NightDisplaySettingsActivity { /* empty */ }
public static class SmartAutoRotateSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class RefreshRateSettingsActivity extends SettingsActivity { /* empty */ }
public static class MyDeviceInfoActivity extends SettingsActivity { /* empty */ }
public static class ModuleLicensesActivity extends SettingsActivity { /* empty */ }
public static class ApplicationSettingsActivity extends SettingsActivity { /* empty */ }
@@ -496,4 +497,6 @@ public static class ResetMobileNetworkSettingsActivity extends SettingsActivity
public static class HearingDevicesActivity extends SettingsActivity { /* empty */ }
public static class HearingDevicesPairingActivity extends SettingsActivity { /* empty */ }
+
+ public static class DevRunningServicesActivity extends SettingsActivity { /* empty */ }
}
diff --git a/src/com/android/settings/SettingsPreferenceFragment.java b/src/com/android/settings/SettingsPreferenceFragment.java
index 66397c0e66f..85514f18fce 100644
--- a/src/com/android/settings/SettingsPreferenceFragment.java
+++ b/src/com/android/settings/SettingsPreferenceFragment.java
@@ -53,6 +53,9 @@
import com.android.settingslib.core.instrumentation.Instrumentable;
import com.android.settingslib.search.Indexable;
import com.android.settingslib.widget.LayoutPreference;
+import com.android.settings.derp.notificationlight.ApplicationLightPreference;
+import com.android.settings.derp.notificationlight.BrightnessPreference;
+import com.android.settings.derp.buttons.preference.ButtonBacklightBrightness;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.setupcompat.util.WizardManagerHelper;
@@ -265,7 +268,7 @@ public void highlightPreferenceIfNeeded() {
* Only override this method if the initial expanded child must be determined at run time.
*/
public int getInitialExpandedChildCount() {
- return 0;
+ return Integer.MAX_VALUE;
}
/**
@@ -562,6 +565,15 @@ public void onDisplayPreferenceDialog(Preference preference) {
} else if (preference instanceof CustomEditTextPreferenceCompat) {
f = CustomEditTextPreferenceCompat.CustomPreferenceDialogFragment
.newInstance(preference.getKey());
+ } else if (preference instanceof ApplicationLightPreference) {
+ f = ApplicationLightPreference.CustomPreferenceDialogFragment
+ .newInstance(preference.getKey());
+ } else if (preference instanceof BrightnessPreference) {
+ f = BrightnessPreference.CustomPreferenceDialogFragment
+ .newInstance(preference.getKey());
+ } else if (preference instanceof ButtonBacklightBrightness) {
+ f = ButtonBacklightBrightness.CustomPreferenceDialogFragment
+ .newInstance(preference.getKey());
} else {
super.onDisplayPreferenceDialog(preference);
return;
diff --git a/src/com/android/settings/accessibility/InCallConnectVibrationTogglePreferenceController.java b/src/com/android/settings/accessibility/InCallConnectVibrationTogglePreferenceController.java
new file mode 100644
index 00000000000..7ed00ecc4ca
--- /dev/null
+++ b/src/com/android/settings/accessibility/InCallConnectVibrationTogglePreferenceController.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2022 The LibreMobileOS Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.accessibility;
+
+import android.content.Context;
+import android.os.VibrationAttributes;
+import android.os.Vibrator;
+import android.provider.Settings;
+
+/** Preference controller for incall vibration with only a toggle for on/off states. */
+public class InCallConnectVibrationTogglePreferenceController extends VibrationTogglePreferenceController {
+
+ /** General configuration for incall vibration intensity settings. */
+ public static final class InCallVibrationPreferenceConfig extends VibrationPreferenceConfig {
+
+ public InCallVibrationPreferenceConfig(Context context) {
+ super(context, Settings.System.VIBRATE_ON_CONNECT,
+ VibrationAttributes.USAGE_UNKNOWN);
+ }
+
+ /** Returns the default intensity to be displayed when the setting value is not set. */
+ public int getDefaultValue() {
+ return Vibrator.VIBRATION_INTENSITY_OFF;
+ }
+
+ /** Reads setting value for corresponding {@link VibrationPreferenceConfig} */
+ @Override
+ public int readIntensity() {
+ return Settings.System.getInt(mContentResolver, getSettingKey(), getDefaultValue());
+ }
+ }
+
+ public InCallConnectVibrationTogglePreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey, new InCallVibrationPreferenceConfig(context));
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+}
diff --git a/src/com/android/settings/accessibility/InCallDisconnectVibrationTogglePreferenceController.java b/src/com/android/settings/accessibility/InCallDisconnectVibrationTogglePreferenceController.java
new file mode 100644
index 00000000000..9536d68f5c2
--- /dev/null
+++ b/src/com/android/settings/accessibility/InCallDisconnectVibrationTogglePreferenceController.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2022 The LibreMobileOS Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.accessibility;
+
+import android.content.Context;
+import android.os.VibrationAttributes;
+import android.os.Vibrator;
+import android.provider.Settings;
+
+/** Preference controller for incall vibration with only a toggle for on/off states. */
+public class InCallDisconnectVibrationTogglePreferenceController extends VibrationTogglePreferenceController {
+
+ /** General configuration for incall vibration intensity settings. */
+ public static final class InCallVibrationPreferenceConfig extends VibrationPreferenceConfig {
+
+ public InCallVibrationPreferenceConfig(Context context) {
+ super(context, Settings.System.VIBRATE_ON_DISCONNECT,
+ VibrationAttributes.USAGE_UNKNOWN);
+ }
+
+ /** Returns the default intensity to be displayed when the setting value is not set. */
+ public int getDefaultValue() {
+ return Vibrator.VIBRATION_INTENSITY_OFF;
+ }
+
+ /** Reads setting value for corresponding {@link VibrationPreferenceConfig} */
+ @Override
+ public int readIntensity() {
+ return Settings.System.getInt(mContentResolver, getSettingKey(), getDefaultValue());
+ }
+ }
+
+ public InCallDisconnectVibrationTogglePreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey, new InCallVibrationPreferenceConfig(context));
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+}
diff --git a/src/com/android/settings/accessibility/InCallWaitingVibrationTogglePreferenceController.java b/src/com/android/settings/accessibility/InCallWaitingVibrationTogglePreferenceController.java
new file mode 100644
index 00000000000..353fdf42934
--- /dev/null
+++ b/src/com/android/settings/accessibility/InCallWaitingVibrationTogglePreferenceController.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2022 The LibreMobileOS Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.accessibility;
+
+import android.content.Context;
+import android.os.VibrationAttributes;
+import android.os.Vibrator;
+import android.provider.Settings;
+
+/** Preference controller for incall vibration with only a toggle for on/off states. */
+public class InCallWaitingVibrationTogglePreferenceController extends VibrationTogglePreferenceController {
+
+ /** General configuration for incall vibration intensity settings. */
+ public static final class InCallVibrationPreferenceConfig extends VibrationPreferenceConfig {
+
+ public InCallVibrationPreferenceConfig(Context context) {
+ super(context, Settings.System.VIBRATE_ON_CALLWAITING,
+ VibrationAttributes.USAGE_UNKNOWN);
+ }
+
+ /** Returns the default intensity to be displayed when the setting value is not set. */
+ public int getDefaultValue() {
+ return Vibrator.VIBRATION_INTENSITY_OFF;
+ }
+
+ /** Reads setting value for corresponding {@link VibrationPreferenceConfig} */
+ @Override
+ public int readIntensity() {
+ return Settings.System.getInt(mContentResolver, getSettingKey(), getDefaultValue());
+ }
+ }
+
+ public InCallWaitingVibrationTogglePreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey, new InCallVibrationPreferenceConfig(context));
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+}
diff --git a/src/com/android/settings/accounts/AccountFeatureProviderImpl.java b/src/com/android/settings/accounts/AccountFeatureProviderImpl.java
index 90b581ba80e..716cdcba6d6 100644
--- a/src/com/android/settings/accounts/AccountFeatureProviderImpl.java
+++ b/src/com/android/settings/accounts/AccountFeatureProviderImpl.java
@@ -1,16 +1,20 @@
package com.android.settings.accounts;
import android.accounts.Account;
+import android.accounts.AccountManager;
import android.content.Context;
+import com.android.settings.R;
+import com.android.settings.overlay.FeatureFactory;
+
public class AccountFeatureProviderImpl implements AccountFeatureProvider {
@Override
public String getAccountType() {
- return null;
+ return FeatureFactory.getAppContext().getString(R.string.account_type);
}
@Override
public Account[] getAccounts(Context context) {
- return new Account[0];
+ return AccountManager.get(context).getAccountsByType(getAccountType());
}
}
diff --git a/src/com/android/settings/applications/AppStorageSettings.java b/src/com/android/settings/applications/AppStorageSettings.java
index e45657fc067..15283cb8947 100644
--- a/src/com/android/settings/applications/AppStorageSettings.java
+++ b/src/com/android/settings/applications/AppStorageSettings.java
@@ -210,13 +210,6 @@ void handleClearDataClick() {
if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) {
RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
getActivity(), mAppsControlDisallowedAdmin);
- } else if (mAppEntry.info.manageSpaceActivityName != null) {
- if (!Utils.isMonkeyRunning()) {
- Intent intent = new Intent(Intent.ACTION_DEFAULT);
- intent.setClassName(mAppEntry.info.packageName,
- mAppEntry.info.manageSpaceActivityName);
- startActivityForResult(intent, REQUEST_MANAGE_SPACE);
- }
} else {
showDialogInner(DLG_CLEAR_DATA, 0);
}
@@ -300,11 +293,8 @@ private void initDataButtons() {
if (appHasSpaceManagementUI) {
intent.setClassName(mAppEntry.info.packageName, mAppEntry.info.manageSpaceActivityName);
}
- final boolean isManageSpaceActivityAvailable =
- getPackageManager().resolveActivity(intent, 0) != null;
- if ((!appHasSpaceManagementUI && appRestrictsClearingData)
- || !isManageSpaceActivityAvailable) {
+ if (!appHasSpaceManagementUI && appRestrictsClearingData) {
mButtonsPref
.setButton1Text(R.string.clear_user_data_text)
.setButton1Icon(R.drawable.ic_settings_delete)
diff --git a/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java b/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java
index 851d763ea94..4e1ec775bf6 100644
--- a/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java
+++ b/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java
@@ -27,10 +27,16 @@
import android.content.pm.ComponentInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
+import android.icu.text.MeasureFormat;
+import android.icu.text.MeasureFormat.FormatWidth;
+import android.icu.util.Measure;
+import android.icu.util.MeasureUnit;
import android.location.LocationManager;
+import android.os.Bundle;
import android.os.RemoteException;
import android.os.SystemConfigManager;
import android.os.UserManager;
@@ -48,10 +54,16 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Locale;
import java.util.Set;
public class ApplicationFeatureProviderImpl implements ApplicationFeatureProvider {
private static final String TAG = "AppFeatureProviderImpl";
+ private static final boolean DEBUG = false;
+
+ private static final String WELLBEING_APP_PACKAGE = "com.google.android.apps.wellbeing.api";
+ private static final String GET_APP_USAGE_MILLIS = "get_app_usage_millis";
+ private static final String TOTAL_TIME_MILLIS = "total_time_millis";
protected final Context mContext;
private final PackageManager mPm;
@@ -368,4 +380,85 @@ public boolean isLongBackgroundTaskPermissionToggleSupported() {
// converted to a special app-op permission, this should be updated.
return false;
}
+
+ public CharSequence getTimeSpentInApp(String packageName) {
+ try {
+ if (!isPrivilegedApp(WELLBEING_APP_PACKAGE)) {
+ if (DEBUG) {
+ Log.d("ApplicationFeatureProviderImpl", "Not a privileged app.");
+ }
+ return "";
+ }
+
+ Bundle bundle = new Bundle();
+ bundle.putString("packageName", packageName);
+
+ Bundle providerCall = mContext.getContentResolver().call(WELLBEING_APP_PACKAGE,
+ GET_APP_USAGE_MILLIS, null, bundle);
+ if (providerCall != null) {
+ if (providerCall.getBoolean("success")) {
+ Bundle dataBundle = providerCall.getBundle("data");
+ if (dataBundle == null) {
+ if (DEBUG) {
+ Log.d("ApplicationFeatureProviderImpl", "data bundle is null.");
+ }
+ return "";
+ }
+ String readableDuration = getReadableDuration(dataBundle.getLong(TOTAL_TIME_MILLIS),
+ R.string.duration_less_than_one_minute);
+ return mContext.getString(R.string.screen_time_summary_usage_today, readableDuration);
+ }
+ }
+ if (DEBUG) {
+ Log.d("ApplicationFeatureProviderImpl", "Provider call unsuccessful");
+ }
+ return "";
+ } catch (Exception e) {
+ Log.w("ApplicationFeatureProviderImpl",
+ "Error getting time spent for app " + packageName, e);
+ return "";
+ }
+ }
+
+ private String getReadableDuration(Long totalTime, int defaultString) {
+ long hours, minutes;
+
+ if (totalTime >= 3_600_000) {
+ hours = totalTime / 3_600_000;
+ totalTime -= 3_600_000 * hours;
+ } else {
+ hours = 0;
+ }
+
+ if (totalTime >= 60_000) {
+ minutes = totalTime / 60_000;
+ totalTime -= 60_000 * minutes;
+ } else {
+ minutes = 0;
+ }
+
+ Locale locale = Locale.getDefault();
+ MeasureFormat measureFormat = MeasureFormat.getInstance(locale, FormatWidth.NARROW);
+
+ if (hours > 0 && minutes > 0) {
+ return measureFormat.formatMeasures(new Measure(hours, MeasureUnit.HOUR),
+ new Measure(minutes, MeasureUnit.MINUTE));
+ } else if (hours > 0) {
+ return measureFormat.formatMeasures(new Measure(hours, MeasureUnit.HOUR));
+ } else if (minutes > 0) {
+ return measureFormat.formatMeasures(new Measure(minutes, MeasureUnit.MINUTE));
+ } else if (totalTime <= 0) {
+ return measureFormat.formatMeasures(new Measure(0, MeasureUnit.MINUTE));
+ }
+
+ return mContext.getResources().getString(defaultString);
+ }
+
+ private boolean isPrivilegedApp(String packageName) {
+ ProviderInfo providerInfo = mContext.getPackageManager().resolveContentProvider(packageName, 0);
+ if (providerInfo != null) {
+ return providerInfo.applicationInfo.isPrivilegedApp();
+ }
+ return false;
+ }
}
diff --git a/src/com/android/settings/applications/GameSpaceController.java b/src/com/android/settings/applications/GameSpaceController.java
new file mode 100644
index 00000000000..ca442f4757f
--- /dev/null
+++ b/src/com/android/settings/applications/GameSpaceController.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 Bianca Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.applications;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.Intent;
+import android.content.ComponentName;
+import android.os.UserHandle;
+import android.text.TextUtils;
+
+import androidx.preference.Preference;
+
+import com.android.settings.core.BasePreferenceController;
+
+public class GameSpaceController extends BasePreferenceController {
+
+ private static final String GAME_PACKAGE = "io.chaldeaprjkt.gamespace";
+ private static final String GAME_SETTINGS = "io.chaldeaprjkt.gamespace.settings.SettingsActivity";
+
+ private final PackageManager mPackageManager;
+
+ public GameSpaceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ mPackageManager = mContext.getPackageManager();
+ }
+
+ private Intent settingsIntent() {
+ Intent intent = new Intent();
+ ComponentName component = new ComponentName(GAME_PACKAGE, GAME_SETTINGS);
+ intent.setComponent(component);
+ return intent;
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return mContext.getPackageManager().resolveActivity(settingsIntent(), 0) != null
+ ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+ }
+
+ @Override
+ public boolean handlePreferenceTreeClick(Preference preference) {
+ if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) {
+ return false;
+ }
+
+ Intent intent = settingsIntent();
+ intent.putExtra("referer", this.getClass().getCanonicalName());
+ mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+ return true;
+ }
+}
diff --git a/src/com/android/settings/applications/ProcStatsData.java b/src/com/android/settings/applications/ProcStatsData.java
index aedb06640d0..fd178988d17 100644
--- a/src/com/android/settings/applications/ProcStatsData.java
+++ b/src/com/android/settings/applications/ProcStatsData.java
@@ -274,7 +274,7 @@ private ArrayList getProcs(ProcessDataCollection bgTotals,
final ProcessState proc = mStats.mProcesses.get(pkgProc.getName(),
pkgProc.getUid());
if (proc == null) {
- Log.w(TAG, "No process found for pkg " + st.mPackageName
+ if (DEBUG) Log.w(TAG, "No process found for pkg " + st.mPackageName
+ "/" + st.mUid + " proc name " + pkgProc.getName());
continue;
}
diff --git a/src/com/android/settings/applications/RunningServices.java b/src/com/android/settings/applications/RunningServices.java
index 4d1c166f904..1d1b80e21f3 100644
--- a/src/com/android/settings/applications/RunningServices.java
+++ b/src/com/android/settings/applications/RunningServices.java
@@ -112,6 +112,12 @@ private void updateOptionsMenu() {
boolean showingBackground = mRunningProcessesView.mAdapter.getShowBackground();
mOptionsMenu.findItem(SHOW_RUNNING_SERVICES).setVisible(showingBackground);
mOptionsMenu.findItem(SHOW_BACKGROUND_PROCESSES).setVisible(!showingBackground);
+
+ if (!showingBackground) {
+ getActivity().setTitle(com.android.settingslib.R.string.runningservices_settings_title);
+ } else {
+ getActivity().setTitle(R.string.background_processes_settings_title);
+ }
}
@Override
diff --git a/src/com/android/settings/applications/intentpicker/IntentPickerUtils.java b/src/com/android/settings/applications/intentpicker/IntentPickerUtils.java
index 5b14bc7ce3e..8feef9c9fe8 100644
--- a/src/com/android/settings/applications/intentpicker/IntentPickerUtils.java
+++ b/src/com/android/settings/applications/intentpicker/IntentPickerUtils.java
@@ -30,7 +30,7 @@
/** The common APIs for intent picker */
public class IntentPickerUtils {
private static final String TAG = "IntentPickerUtils";
- private static final boolean DEBUG = Build.IS_DEBUGGABLE;
+ private static final boolean DEBUG = Build.IS_ENG;
private IntentPickerUtils() {
}
diff --git a/src/com/android/settings/applications/manageapplications/ManageApplications.java b/src/com/android/settings/applications/manageapplications/ManageApplications.java
index 6c16d94a51d..0f9f49b0bd6 100644
--- a/src/com/android/settings/applications/manageapplications/ManageApplications.java
+++ b/src/com/android/settings/applications/manageapplications/ManageApplications.java
@@ -192,7 +192,7 @@ public class ManageApplications extends InstrumentedFragment
MenuItem.OnActionExpandListener {
static final String TAG = "ManageApplications";
- static final boolean DEBUG = Build.IS_DEBUGGABLE;
+ static final boolean DEBUG = Build.IS_ENG;
// Intent extras.
public static final String EXTRA_CLASSNAME = "classname";
diff --git a/src/com/android/settings/applications/sensors/SensorBlock.java b/src/com/android/settings/applications/sensors/SensorBlock.java
new file mode 100644
index 00000000000..ad5df6d05fd
--- /dev/null
+++ b/src/com/android/settings/applications/sensors/SensorBlock.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2019-2020 crDroid Android Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.applications.sensors;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ListView;
+
+import androidx.preference.SwitchPreference;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.PreferenceScreen;
+
+import com.android.internal.logging.nano.MetricsProto;
+
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settingslib.widget.FooterPreference;
+
+import org.derpfest.support.preferences.AppListPreference;
+import org.derpfest.support.preferences.PackageListAdapter;
+import org.derpfest.support.preferences.PackageListAdapter.PackageItem;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class SensorBlock extends SettingsPreferenceFragment
+ implements Preference.OnPreferenceClickListener {
+
+ private static final int DIALOG_BLOCKED_APPS = 1;
+ private static final String SENSOR_BLOCK = "sensor_block";
+
+ private PackageListAdapter mPackageAdapter;
+ private PackageManager mPackageManager;
+ private PreferenceGroup mSensorBlockPrefList;
+ private Preference mAddSensorBlockPref;
+
+ private String mBlockedPackageList;
+ private Map mBlockedPackages;
+ private Context mContext;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ // Get launch-able applications
+ addPreferencesFromResource(R.xml.sensor_block_settings);
+
+ getPreferenceScreen().addPreference(new FooterPreference.Builder(getActivity()).setTitle(
+ R.string.add_sensor_block_package_summary).build());
+
+ final PreferenceScreen prefScreen = getPreferenceScreen();
+
+ mPackageManager = getPackageManager();
+ mPackageAdapter = new PackageListAdapter(getActivity());
+
+ mSensorBlockPrefList = (PreferenceGroup) findPreference("sensor_block_applications");
+ mSensorBlockPrefList.setOrderingAsAdded(false);
+
+ mBlockedPackages = new HashMap();
+
+ mAddSensorBlockPref = findPreference("add_sensor_block_packages");
+
+ mAddSensorBlockPref.setOnPreferenceClickListener(this);
+
+ mContext = getActivity().getApplicationContext();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ refreshCustomApplicationPrefs();
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsProto.MetricsEvent.DERP;
+ }
+
+ @Override
+ public int getDialogMetricsCategory(int dialogId) {
+ if (dialogId == DIALOG_BLOCKED_APPS) {
+ return MetricsProto.MetricsEvent.DERP;
+ }
+ return 0;
+ }
+
+ /**
+ * Utility classes and supporting methods
+ */
+ @Override
+ public Dialog onCreateDialog(int id) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ final Dialog dialog;
+ final ListView list = new ListView(getActivity());
+ list.setAdapter(mPackageAdapter);
+ list.setDivider(null);
+
+ builder.setTitle(R.string.profile_choose_app);
+ builder.setView(list);
+ dialog = builder.create();
+
+ switch (id) {
+ case DIALOG_BLOCKED_APPS:
+ list.setOnItemClickListener(new OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ // Add empty application definition, the user will be able to edit it later
+ PackageItem info = (PackageItem) parent.getItemAtPosition(position);
+ addCustomApplicationPref(info.packageName, mBlockedPackages);
+ dialog.cancel();
+ }
+ });
+ }
+ return dialog;
+ }
+
+ public static void reset(Context mContext) {
+ ContentResolver resolver = mContext.getContentResolver();
+ Settings.System.putIntForUser(resolver,
+ Settings.System.SENSOR_BLOCK, 0, UserHandle.USER_CURRENT);
+ Settings.System.putString(resolver,
+ Settings.System.SENSOR_BLOCKED_APP, null);
+ }
+
+ /**
+ * Application class
+ */
+ private static class Package {
+ public String name;
+ /**
+ * Stores all the application values in one call
+ * @param name
+ */
+ public Package(String name) {
+ this.name = name;
+ }
+
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(name);
+ return builder.toString();
+ }
+
+ public static Package fromString(String value) {
+ if (TextUtils.isEmpty(value)) {
+ return null;
+ }
+
+ try {
+ Package item = new Package(value);
+ return item;
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ }
+
+ };
+
+ private void refreshCustomApplicationPrefs() {
+ if (!parsePackageList()) {
+ return;
+ }
+
+ // Add the Application Preferences
+ if (mSensorBlockPrefList != null) {
+ mSensorBlockPrefList.removeAll();
+
+ for (Package pkg : mBlockedPackages.values()) {
+ try {
+ Preference pref = createPreferenceFromInfo(pkg);
+ mSensorBlockPrefList.addPreference(pref);
+ } catch (PackageManager.NameNotFoundException e) {
+ // Do nothing
+ }
+ }
+
+ // Keep these at the top
+ mAddSensorBlockPref.setOrder(0);
+ // Add 'add' options
+ mSensorBlockPrefList.addPreference(mAddSensorBlockPref);
+ }
+ }
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ if (preference == mAddSensorBlockPref) {
+ showDialog(DIALOG_BLOCKED_APPS);
+ } else {
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.dialog_delete_title)
+ .setMessage(R.string.dialog_delete_message)
+ .setIconAttribute(android.R.attr.alertDialogIcon)
+ .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (preference == mSensorBlockPrefList.findPreference(preference.getKey())) {
+ removeApplicationPref(preference.getKey(), mBlockedPackages);
+ }
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, null);
+
+ builder.show();
+ }
+ return true;
+ }
+
+ private void addCustomApplicationPref(String packageName, Map map) {
+ Package pkg = map.get(packageName);
+ if (pkg == null) {
+ pkg = new Package(packageName);
+ map.put(packageName, pkg);
+ savePackageList(false, map);
+ refreshCustomApplicationPrefs();
+ }
+ }
+
+ private Preference createPreferenceFromInfo(Package pkg)
+ throws PackageManager.NameNotFoundException {
+ PackageInfo info = mPackageManager.getPackageInfo(pkg.name,
+ PackageManager.GET_META_DATA);
+ Preference pref =
+ new AppListPreference(getActivity());
+
+ pref.setKey(pkg.name);
+ pref.setTitle(info.applicationInfo.loadLabel(mPackageManager));
+ pref.setIcon(info.applicationInfo.loadIcon(mPackageManager));
+ pref.setPersistent(false);
+ pref.setOnPreferenceClickListener(this);
+ return pref;
+ }
+
+ private void removeApplicationPref(String packageName, Map map) {
+ if (map.remove(packageName) != null) {
+ savePackageList(false, map);
+ refreshCustomApplicationPrefs();
+ }
+ }
+
+ private boolean parsePackageList() {
+ boolean parsed = false;
+
+ String sensorBlockString = Settings.System.getString(getContentResolver(),
+ Settings.System.SENSOR_BLOCKED_APP);
+
+ if (sensorBlockString != null &&
+ !TextUtils.equals(mBlockedPackageList, sensorBlockString)) {
+ mBlockedPackageList = sensorBlockString;
+ mBlockedPackages.clear();
+ parseAndAddToMap(sensorBlockString, mBlockedPackages);
+ parsed = true;
+ }
+
+ return parsed;
+ }
+
+ private void parseAndAddToMap(String baseString, Map map) {
+ if (baseString == null) {
+ return;
+ }
+
+ final String[] array = TextUtils.split(baseString, "\\|");
+ for (String item : array) {
+ if (TextUtils.isEmpty(item)) {
+ continue;
+ }
+ Package pkg = Package.fromString(item);
+ map.put(pkg.name, pkg);
+ }
+ }
+
+
+ private void savePackageList(boolean preferencesUpdated, Map map) {
+ String setting = map == mBlockedPackages ? Settings.System.SENSOR_BLOCKED_APP : Settings.System.SENSOR_BLOCKED_APP_DUMMY;
+
+ List settings = new ArrayList();
+ for (Package app : map.values()) {
+ settings.add(app.toString());
+ }
+ final String value = TextUtils.join("|", settings);
+ if (preferencesUpdated) {
+ if (TextUtils.equals(setting, Settings.System.SENSOR_BLOCKED_APP)) {
+ mBlockedPackageList = value;
+ }
+ }
+ Settings.System.putString(getContentResolver(),
+ setting, value);
+ }
+}
diff --git a/src/com/android/settings/biometrics/face/FaceEnrollAnimationDrawable.java b/src/com/android/settings/biometrics/face/FaceEnrollAnimationDrawable.java
index 5be7c5331d3..88966782448 100644
--- a/src/com/android/settings/biometrics/face/FaceEnrollAnimationDrawable.java
+++ b/src/com/android/settings/biometrics/face/FaceEnrollAnimationDrawable.java
@@ -28,6 +28,7 @@
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import com.android.settingslib.Utils;
import com.android.settings.biometrics.BiometricEnrollSidecar;
/**
@@ -66,7 +67,7 @@ public FaceEnrollAnimationDrawable(Context context, ParticleCollection.Listener
mListener = listener;
mSquarePaint = new Paint();
- mSquarePaint.setColor(Color.WHITE);
+ mSquarePaint.setColor(Utils.getColorAttrDefaultColor(context, android.R.attr.colorBackground));
mSquarePaint.setAntiAlias(true);
mCircleCutoutPaint = new Paint();
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
index 795f999a030..e00f3c5f3d5 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
@@ -1189,10 +1189,19 @@ public static class IconTouchDialog extends InstrumentedDialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final boolean isFrontFacingFps = getResources().getBoolean(
+ R.bool.config_is_front_facing_fps);
+ final boolean isSideMountedFps = getResources().getBoolean(
+ R.bool.config_is_side_fps);
+ final String fpsLocation = getString(isSideMountedFps
+ ? R.string.fingerprint_enroll_touch_dialog_message_side : isFrontFacingFps
+ ? R.string.fingerprint_enroll_touch_dialog_message_front
+ : R.string.fingerprint_enroll_touch_dialog_message_rear);
+
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(),
R.style.Theme_AlertDialog);
builder.setTitle(R.string.security_settings_fingerprint_enroll_touch_dialog_title)
- .setMessage(R.string.security_settings_fingerprint_enroll_touch_dialog_message)
+ .setMessage(fpsLocation)
.setPositiveButton(R.string.security_settings_fingerprint_enroll_dialog_ok,
new DialogInterface.OnClickListener() {
@Override
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java
index aeb0dac97c4..533cfea2af6 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java
@@ -122,8 +122,23 @@ protected void onCreate(Bundle savedInstanceState) {
mIsReverseDefaultRotation = getApplicationContext().getResources().getBoolean(
com.android.internal.R.bool.config_reverseDefaultRotation);
} else {
+ // Remaining usecases can be either front facing fps or dedicated
+ // side mounted fps (not embedded in the power button)
+ final boolean isFrontFacingFps = getResources().getBoolean(
+ R.bool.config_is_front_facing_fps);
+ final boolean isSideMountedFps = getResources().getBoolean(
+ R.bool.config_is_side_fps);
+ final String fpsLocation = getString(isSideMountedFps
+ ? R.string.fingerprint_enroll_find_sensor_message_side : isFrontFacingFps
+ ? R.string.fingerprint_enroll_find_sensor_message_front
+ : R.string.fingerprint_enroll_find_sensor_message_rear);
+
setHeaderText(R.string.security_settings_fingerprint_enroll_find_sensor_title);
- setDescriptionText(R.string.security_settings_fingerprint_enroll_find_sensor_message);
+ setDescriptionText(fpsLocation);
+ if (isFrontFacingFps) {
+ findViewById(R.id.fingerprint_sensor_location_front_overlay)
+ .setVisibility(View.VISIBLE);
+ }
}
if (savedInstanceState != null) {
mNextClicked = savedInstanceState.getBoolean(SAVED_STATE_IS_NEXT_CLICKED, mNextClicked);
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java
index f6626b213bf..1d781a57ad4 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java
@@ -28,6 +28,7 @@
import android.os.Bundle;
import android.text.Html;
import android.text.method.LinkMovementMethod;
+import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
@@ -161,6 +162,10 @@ protected void onCreate(Bundle savedInstanceState) {
getNextButton().setEnabled(true);
}));
}
+
+ if (TextUtils.isEmpty(footerLink.getText())) {
+ findViewById(R.id.layout_footer_learn_more).setVisibility(View.GONE);
+ }
}
@Override
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintLocationAnimationView.java b/src/com/android/settings/biometrics/fingerprint/FingerprintLocationAnimationView.java
index 2916872e73f..08e86c3befa 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintLocationAnimationView.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintLocationAnimationView.java
@@ -27,6 +27,7 @@
import android.view.View;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
+import android.widget.ImageView;
import androidx.annotation.Nullable;
@@ -54,6 +55,7 @@ public class FingerprintLocationAnimationView extends View implements
private float mPulseRadius;
private ValueAnimator mRadiusAnimator;
private ValueAnimator mAlphaAnimator;
+ private ImageView mOverlayImage;
public FingerprintLocationAnimationView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
@@ -93,7 +95,11 @@ private float getCenterX() {
}
private float getCenterY() {
- return getHeight() * mFractionCenterY;
+ if (mOverlayImage == null) {
+ mOverlayImage = (ImageView) getRootView().findViewById(
+ R.id.fingerprint_sensor_location);
+ }
+ return mOverlayImage.getHeight() * mFractionCenterY;
}
@Override
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
index 46461340c10..98e27ce536c 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
@@ -175,7 +175,17 @@ private static List createThePreferenceControllers
if (manager == null || !manager.isHardwareDetected()) {
return null;
}
- if (manager.isPowerbuttonFps()) {
+ List sensorProperties =
+ manager.getSensorPropertiesInternal();
+ boolean isUdfps = false;
+ for (FingerprintSensorPropertiesInternal prop : sensorProperties) {
+ if (prop.isAnyUdfpsType()) {
+ isUdfps = true;
+ break;
+ }
+ }
+ if (!isUdfps && context.getResources().getBoolean(
+ com.android.internal.R.bool.config_fingerprintWakeAndUnlock)) {
controllers.add(
new FingerprintUnlockCategoryController(
context,
@@ -244,6 +254,8 @@ private static class FooterColumn {
private PreferenceCategory mFingerprintsEnrolledCategory;
private PreferenceCategory mFingerprintUnlockCategory;
private PreferenceCategory mFingerprintUnlockFooter;
+ private boolean mFingerprintWakeAndUnlock;
+ private boolean mProximityCheckOnFingerprintUnlock;
private FingerprintManager mFingerprintManager;
private FingerprintUpdater mFingerprintUpdater;
@@ -328,7 +340,7 @@ public void handleMessage(android.os.Message msg) {
case MSG_REFRESH_FINGERPRINT_TEMPLATES:
removeFingerprintPreference(msg.arg1);
updateAddPreference();
- if (isSfps()) {
+ if (!isUdfps() && mFingerprintWakeAndUnlock) {
updateFingerprintUnlockCategoryVisibility();
}
updatePreferences();
@@ -418,6 +430,10 @@ public void onCreate(Bundle savedInstanceState) {
mFingerprintManager = Utils.getFingerprintManagerOrNull(activity);
mFingerprintUpdater = new FingerprintUpdater(activity, mFingerprintManager);
mSensorProperties = mFingerprintManager.getSensorPropertiesInternal();
+ mFingerprintWakeAndUnlock = getContext().getResources().getBoolean(
+ com.android.internal.R.bool.config_fingerprintWakeAndUnlock);
+ mProximityCheckOnFingerprintUnlock = getContext().getResources().getBoolean(
+ com.android.internal.R.bool.config_proximityCheckOnFpsUnlock);
mToken = getIntent().getByteArrayExtra(
ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN);
@@ -486,10 +502,17 @@ public void onCreate(Bundle savedInstanceState) {
private void updateFooterColumns(@NonNull Activity activity) {
final EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled(
activity, DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT, mUserId);
- final Intent helpIntent = HelpUtils.getHelpIntent(
- activity, getString(getHelpResource()), activity.getClass().getName());
- final View.OnClickListener learnMoreClickListener = (v) ->
- activity.startActivityForResult(helpIntent, 0);
+ final Intent helpIntent;
+ final View.OnClickListener learnMoreClickListener;
+ if (getHelpResource() != 0) {
+ helpIntent = HelpUtils.getHelpIntent(
+ activity, getString(getHelpResource()), activity.getClass().getName());
+ learnMoreClickListener = (v) ->
+ activity.startActivityForResult(helpIntent, 0);
+ } else {
+ helpIntent = null;
+ learnMoreClickListener = null;
+ }
mFooterColumns.clear();
if (admin != null) {
@@ -511,11 +534,13 @@ private void updateFooterColumns(@NonNull Activity activity) {
column2.mTitle = getText(
R.string.security_fingerprint_disclaimer_lockscreen_disabled_2
);
- if (isSfps()) {
- column2.mLearnMoreOverrideText = getText(
- R.string.security_settings_fingerprint_settings_footer_learn_more);
+ if (helpIntent != null) {
+ if (!isUdfps() && mFingerprintWakeAndUnlock) {
+ column2.mLearnMoreOverrideText = getText(
+ R.string.security_settings_fingerprint_settings_footer_learn_more);
+ }
+ column2.mLearnMoreClickListener = learnMoreClickListener;
}
- column2.mLearnMoreClickListener = learnMoreClickListener;
mFooterColumns.add(column2);
} else {
final FooterColumn column = new FooterColumn();
@@ -523,17 +548,23 @@ private void updateFooterColumns(@NonNull Activity activity) {
? R.string.private_space_fingerprint_enroll_introduction_message
: R.string.security_settings_fingerprint_enroll_introduction_v3_message,
DeviceHelper.getDeviceName(getActivity()));
- column.mLearnMoreClickListener = learnMoreClickListener;
- column.mLearnMoreOverrideText = getText(
- R.string.security_settings_fingerprint_settings_footer_learn_more);
+ if (helpIntent != null) {
+ column.mLearnMoreClickListener = learnMoreClickListener;
+ column.mLearnMoreOverrideText = getText(
+ R.string.security_settings_fingerprint_settings_footer_learn_more);
+ }
mFooterColumns.add(column);
}
}
private boolean isUdfps() {
- for (FingerprintSensorPropertiesInternal prop : mSensorProperties) {
- if (prop.isAnyUdfpsType()) {
- return true;
+ mFingerprintManager = Utils.getFingerprintManagerOrNull(getActivity());
+ if (mFingerprintManager != null) {
+ mSensorProperties = mFingerprintManager.getSensorPropertiesInternal();
+ for (FingerprintSensorPropertiesInternal prop : mSensorProperties) {
+ if (prop.isAnyUdfpsType()) {
+ return true;
+ }
}
}
return false;
@@ -590,7 +621,7 @@ private void addFingerprintPreferences(PreferenceGroup root) {
// This needs to be after setting ids, otherwise
// |mRequireScreenOnToAuthPreferenceController.isChecked| is always checking the primary
// user instead of the user with |mUserId|.
- if (isSfps()) {
+ if (!isUdfps() && mFingerprintWakeAndUnlock) {
scrollToPreference(fpPrefKey);
addFingerprintUnlockCategory();
}
@@ -640,7 +671,9 @@ private void setupAddFingerprintPreference() {
private void addFingerprintUnlockCategory() {
mFingerprintUnlockCategory = findPreference(KEY_FINGERPRINT_UNLOCK_CATEGORY);
- setupFingerprintUnlockCategoryPreferences();
+ if (mRequireScreenOnToAuthPreferenceController != null) {
+ setupFingerprintUnlockCategoryPreferences();
+ }
final Preference restToUnlockPreference = FeatureFactory.getFeatureFactory()
.getFingerprintFeatureProvider()
.getSfpsRestToUnlockFeature(getContext())
@@ -654,7 +687,9 @@ private void addFingerprintUnlockCategory() {
mRequireScreenOnToAuthPreference.setOnPreferenceChangeListener(
restToUnlockPreference.getOnPreferenceChangeListener());
}
- updateFingerprintUnlockCategoryVisibility();
+ if (mFingerprintUnlockCategoryPreferenceController != null) {
+ updateFingerprintUnlockCategoryVisibility();
+ }
}
private void updateFingerprintUnlockCategoryVisibility() {
@@ -676,6 +711,10 @@ private void setupFingerprintUnlockCategoryPreferences() {
mRequireScreenOnToAuthPreferenceController.setChecked(!isChecked);
return true;
});
+ if (mProximityCheckOnFingerprintUnlock) {
+ mRequireScreenOnToAuthPreference.setSummary(R.string.
+ security_settings_require_screen_on_to_auth_with_proximity_description);
+ }
}
private void updateAddPreference() {
@@ -909,7 +948,8 @@ protected List createPreferenceControllers(Context
private List buildPreferenceControllers(Context context) {
final List controllers =
createThePreferenceControllers(context);
- if (isSfps()) {
+ if (!isUdfps() && context.getResources().getBoolean(
+ com.android.internal.R.bool.config_fingerprintWakeAndUnlock)) {
for (AbstractPreferenceController controller : controllers) {
if (controller.getPreferenceKey() == KEY_FINGERPRINT_UNLOCK_CATEGORY) {
mFingerprintUnlockCategoryPreferenceController =
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceController.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceController.java
index 58bc7e34202..887dfe58f56 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceController.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceController.java
@@ -61,7 +61,7 @@ public boolean isChecked() {
getUserHandle());
if (toReturn == -1) {
toReturn = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_performantAuthDefault) ? 1 : 0;
+ com.android.internal.R.bool.config_fingerprintWakeAndUnlock) ? 1 : 0;
Settings.Secure.putIntForUser(mContext.getContentResolver(),
Settings.Secure.SFPS_PERFORMANT_AUTH_ENABLED, toReturn, getUserHandle());
}
@@ -96,8 +96,7 @@ public void updateState(Preference preference) {
@Override
public int getAvailabilityStatus() {
if (mFingerprintManager != null
- && mFingerprintManager.isHardwareDetected()
- && mFingerprintManager.isPowerbuttonFps()) {
+ && mFingerprintManager.isHardwareDetected()) {
return mFingerprintManager.hasEnrolledTemplates(getUserId())
? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
} else {
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintUnlockCategoryController.java b/src/com/android/settings/biometrics/fingerprint/FingerprintUnlockCategoryController.java
index 674a0dfa758..acca88fc0af 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintUnlockCategoryController.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintUnlockCategoryController.java
@@ -41,8 +41,7 @@ public FingerprintUnlockCategoryController(Context context, String key) {
@Override
public int getAvailabilityStatus() {
if (mFingerprintManager != null
- && mFingerprintManager.isHardwareDetected()
- && mFingerprintManager.isPowerbuttonFps()) {
+ && mFingerprintManager.isHardwareDetected()) {
return mFingerprintManager.hasEnrolledTemplates(getUserId())
? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
} else {
diff --git a/src/com/android/settings/bluetooth/BluetoothTimeoutPreferenceController.java b/src/com/android/settings/bluetooth/BluetoothTimeoutPreferenceController.java
new file mode 100644
index 00000000000..47e79e9c890
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothTimeoutPreferenceController.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2020-2021 The Calyx Institute
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.util.Log;
+
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.core.PreferenceControllerMixin;
+
+public class BluetoothTimeoutPreferenceController extends BasePreferenceController implements
+ PreferenceControllerMixin, Preference.OnPreferenceChangeListener {
+ private static final String TAG = "BluetoothTimeoutPrefCtrl";
+
+ public static final int FALLBACK_BLUETOOTH_TIMEOUT_VALUE = 0;
+
+ private final String mBluetoothTimeoutKey;
+
+ protected BluetoothAdapter mBluetoothAdapter;
+
+ public BluetoothTimeoutPreferenceController(Context context, String key) {
+ super(context, key);
+ mBluetoothTimeoutKey = key;
+
+ mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ if (mBluetoothAdapter == null) {
+ Log.e(TAG, "Bluetooth is not supported on this device");
+ return;
+ }
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ if (mBluetoothAdapter != null) {
+ return UserManager.get(mContext).isAdminUser() ? AVAILABLE : DISABLED_FOR_USER;
+ }
+ return UNSUPPORTED_ON_DEVICE;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return mBluetoothTimeoutKey;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ final ListPreference timeoutListPreference = (ListPreference) preference;
+ final long currentTimeout = Settings.Global.getLong(mContext.getContentResolver(),
+ Settings.Global.BLUETOOTH_OFF_TIMEOUT, FALLBACK_BLUETOOTH_TIMEOUT_VALUE);
+ timeoutListPreference.setValue(String.valueOf(currentTimeout));
+ updateTimeoutPreferenceDescription(timeoutListPreference,
+ Long.parseLong(timeoutListPreference.getValue()));
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ try {
+ long value = Long.parseLong((String) newValue);
+ Settings.Global.putLong(mContext.getContentResolver(),
+ Settings.Global.BLUETOOTH_OFF_TIMEOUT, value);
+ updateTimeoutPreferenceDescription((ListPreference) preference, value);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "could not persist bluetooth timeout setting", e);
+ }
+ return true;
+ }
+
+ public static CharSequence getTimeoutDescription(
+ long currentTimeout, CharSequence[] entries, CharSequence[] values) {
+ if (currentTimeout < 0 || entries == null || values == null
+ || values.length != entries.length) {
+ return null;
+ }
+
+ for (int i = 0; i < values.length; i++) {
+ long timeout = Long.parseLong(values[i].toString());
+ if (currentTimeout == timeout) {
+ return entries[i];
+ }
+ }
+ return null;
+ }
+
+ private void updateTimeoutPreferenceDescription(ListPreference preference,
+ long currentTimeout) {
+ final CharSequence[] entries = preference.getEntries();
+ final CharSequence[] values = preference.getEntryValues();
+ final CharSequence timeoutDescription = getTimeoutDescription(
+ currentTimeout, entries, values);
+ String summary = "";
+ if (timeoutDescription != null) {
+ if (currentTimeout != 0)
+ summary = mContext.getString(R.string.bluetooth_timeout_summary,
+ timeoutDescription);
+ else
+ summary = mContext.getString(R.string.bluetooth_timeout_summary2);
+ }
+ preference.setSummary(summary);
+ }
+}
diff --git a/src/com/android/settings/bluetooth/DevicePickerActivity.java b/src/com/android/settings/bluetooth/DevicePickerActivity.java
index a4f025b267a..c58ddcff376 100644
--- a/src/com/android/settings/bluetooth/DevicePickerActivity.java
+++ b/src/com/android/settings/bluetooth/DevicePickerActivity.java
@@ -20,15 +20,14 @@
import android.os.Bundle;
-import androidx.fragment.app.FragmentActivity;
-
import com.android.settings.R;
+import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
/**
* Activity for Bluetooth device picker dialog. The device picker logic
* is implemented in the {@link BluetoothPairingDetail} fragment.
*/
-public final class DevicePickerActivity extends FragmentActivity {
+public final class DevicePickerActivity extends CollapsingToolbarBaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
diff --git a/src/com/android/settings/connecteddevice/NfcAndPaymentFragmentController.java b/src/com/android/settings/connecteddevice/NfcAndPaymentFragmentController.java
index ee0021ec951..f16dd378db5 100644
--- a/src/com/android/settings/connecteddevice/NfcAndPaymentFragmentController.java
+++ b/src/com/android/settings/connecteddevice/NfcAndPaymentFragmentController.java
@@ -16,21 +16,47 @@
package com.android.settings.connecteddevice;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.nfc.NfcAdapter;
import android.os.UserManager;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnResume;
+import com.android.settingslib.core.lifecycle.events.OnStop;
/**
* Controller that used to show NFC and payment features
*/
-public class NfcAndPaymentFragmentController extends BasePreferenceController {
+public class NfcAndPaymentFragmentController extends BasePreferenceController
+ implements LifecycleObserver, OnResume, OnStop {
private final NfcAdapter mNfcAdapter;
private final PackageManager mPackageManager;
private final UserManager mUserManager;
+ private final IntentFilter mIntentFilter;
+ private Preference mPreference;
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (mPreference == null) {
+ return;
+ }
+
+ final String action = intent.getAction();
+ if (NfcAdapter.ACTION_ADAPTER_STATE_CHANGED.equals(action)) {
+ refreshSummary(mPreference);
+ }
+ }
+ };
public NfcAndPaymentFragmentController(Context context, String preferenceKey) {
super(context, preferenceKey);
@@ -38,6 +64,15 @@ public NfcAndPaymentFragmentController(Context context, String preferenceKey) {
mPackageManager = context.getPackageManager();
mUserManager = context.getSystemService(UserManager.class);
mNfcAdapter = NfcAdapter.getDefaultAdapter(context);
+
+ mIntentFilter = isNfcAvailable()
+ ? new IntentFilter(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED) : null;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mPreference = screen.findPreference(getPreferenceKey());
}
@Override
@@ -61,4 +96,26 @@ public CharSequence getSummary() {
}
return null;
}
+
+ @Override
+ public void onStop() {
+ if (!isNfcAvailable()) {
+ return;
+ }
+
+ mContext.unregisterReceiver(mReceiver);
+ }
+
+ @Override
+ public void onResume() {
+ if (!isNfcAvailable()) {
+ return;
+ }
+
+ mContext.registerReceiver(mReceiver, mIntentFilter);
+ }
+
+ private boolean isNfcAvailable() {
+ return mNfcAdapter != null;
+ }
}
diff --git a/src/com/android/settings/connecteddevice/usb/UsbBackend.java b/src/com/android/settings/connecteddevice/usb/UsbBackend.java
index d194499c88c..6e643f42fea 100644
--- a/src/com/android/settings/connecteddevice/usb/UsbBackend.java
+++ b/src/com/android/settings/connecteddevice/usb/UsbBackend.java
@@ -165,6 +165,30 @@ public boolean areAllRolesSupported() {
&& mPortStatus.isRoleCombinationSupported(POWER_ROLE_SOURCE, DATA_ROLE_HOST);
}
+ public boolean isSingleDataRoleSupported() {
+ return mPort != null && mPortStatus != null
+ && ((!mPortStatus
+ .isRoleCombinationSupported(POWER_ROLE_SINK, DATA_ROLE_HOST)
+ && !mPortStatus
+ .isRoleCombinationSupported(POWER_ROLE_SOURCE, DATA_ROLE_HOST))
+ || (!mPortStatus
+ .isRoleCombinationSupported(POWER_ROLE_SINK, DATA_ROLE_DEVICE)
+ && !mPortStatus
+ .isRoleCombinationSupported(POWER_ROLE_SOURCE, DATA_ROLE_DEVICE)));
+ }
+
+ public boolean isSinglePowerRoleSupported() {
+ return mPort != null && mPortStatus != null
+ && ((!mPortStatus
+ .isRoleCombinationSupported(POWER_ROLE_SINK, DATA_ROLE_DEVICE)
+ && !mPortStatus
+ .isRoleCombinationSupported(POWER_ROLE_SINK, DATA_ROLE_HOST))
+ || (!mPortStatus
+ .isRoleCombinationSupported(POWER_ROLE_SOURCE, DATA_ROLE_DEVICE)
+ && !mPortStatus
+ .isRoleCombinationSupported(POWER_ROLE_SOURCE, DATA_ROLE_HOST)));
+ }
+
public static String usbFunctionsToString(long functions) {
// TODO replace with UsbManager.usbFunctionsToString once supported by Roboelectric
return Long.toBinaryString(functions);
diff --git a/src/com/android/settings/connecteddevice/usb/UsbDetailsDataRoleController.java b/src/com/android/settings/connecteddevice/usb/UsbDetailsDataRoleController.java
index 8782c796140..8c291abd205 100644
--- a/src/com/android/settings/connecteddevice/usb/UsbDetailsDataRoleController.java
+++ b/src/com/android/settings/connecteddevice/usb/UsbDetailsDataRoleController.java
@@ -115,7 +115,8 @@ public void onRadioButtonClicked(SelectorWithWidgetPreference preference) {
@Override
public boolean isAvailable() {
- return !Utils.isMonkeyRunning();
+ return !Utils.isMonkeyRunning()
+ && !mUsbBackend.isSingleDataRoleSupported();
}
@Override
diff --git a/src/com/android/settings/connecteddevice/usb/UsbDetailsFragment.java b/src/com/android/settings/connecteddevice/usb/UsbDetailsFragment.java
index f8cabbc08fb..31d5770d75f 100644
--- a/src/com/android/settings/connecteddevice/usb/UsbDetailsFragment.java
+++ b/src/com/android/settings/connecteddevice/usb/UsbDetailsFragment.java
@@ -106,7 +106,6 @@ protected List createPreferenceControllers(Context
private static List createControllerList(Context context,
UsbBackend usbBackend, UsbDetailsFragment fragment) {
List ret = new ArrayList<>();
- ret.add(new UsbDetailsHeaderController(context, fragment, usbBackend));
ret.add(new UsbDetailsDataRoleController(context, fragment, usbBackend));
ret.add(new UsbDetailsFunctionsController(context, fragment, usbBackend));
ret.add(new UsbDetailsPowerRoleController(context, fragment, usbBackend));
diff --git a/src/com/android/settings/connecteddevice/usb/UsbDetailsHeaderController.java b/src/com/android/settings/connecteddevice/usb/UsbDetailsHeaderController.java
deleted file mode 100644
index 39d7c751758..00000000000
--- a/src/com/android/settings/connecteddevice/usb/UsbDetailsHeaderController.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * 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.settings.connecteddevice.usb;
-
-import android.content.Context;
-
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.R;
-import com.android.settings.widget.EntityHeaderController;
-import com.android.settingslib.widget.LayoutPreference;
-
-/**
- * This class adds a header with device name.
- */
-public class UsbDetailsHeaderController extends UsbDetailsController {
- private static final String KEY_DEVICE_HEADER = "usb_device_header";
-
- private EntityHeaderController mHeaderController;
-
- public UsbDetailsHeaderController(Context context, UsbDetailsFragment fragment,
- UsbBackend backend) {
- super(context, fragment, backend);
- }
-
- @Override
- public void displayPreference(PreferenceScreen screen) {
- super.displayPreference(screen);
- final LayoutPreference headerPreference = screen.findPreference(KEY_DEVICE_HEADER);
- mHeaderController = EntityHeaderController.newInstance(mFragment.getActivity(), mFragment,
- headerPreference.findViewById(R.id.entity_header));
- }
-
-
- @Override
- protected void refresh(boolean connected, long functions, int powerRole, int dataRole) {
- mHeaderController.setLabel(mContext.getString(R.string.usb_pref));
- mHeaderController.setIcon(mContext.getDrawable(R.drawable.ic_usb));
- mHeaderController.done(true /* rebindActions */);
- }
-
- @Override
- public String getPreferenceKey() {
- return KEY_DEVICE_HEADER;
- }
-}
diff --git a/src/com/android/settings/connecteddevice/usb/UsbDetailsPowerRoleController.java b/src/com/android/settings/connecteddevice/usb/UsbDetailsPowerRoleController.java
index f00435a0cab..9a14f3cde86 100644
--- a/src/com/android/settings/connecteddevice/usb/UsbDetailsPowerRoleController.java
+++ b/src/com/android/settings/connecteddevice/usb/UsbDetailsPowerRoleController.java
@@ -126,7 +126,8 @@ public boolean onPreferenceClick(Preference preference) {
@Override
public boolean isAvailable() {
- return !Utils.isMonkeyRunning();
+ return !Utils.isMonkeyRunning()
+ && !mUsbBackend.isSinglePowerRoleSupported();
}
@Override
diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java
index 1c14712df30..883aa0f15dc 100644
--- a/src/com/android/settings/core/gateway/SettingsGateway.java
+++ b/src/com/android/settings/core/gateway/SettingsGateway.java
@@ -46,6 +46,7 @@
import com.android.settings.applications.AppDashboardFragment;
import com.android.settings.applications.ProcessStatsSummary;
import com.android.settings.applications.ProcessStatsUi;
+import com.android.settings.applications.RunningServices;
import com.android.settings.applications.UsageAccessDetails;
import com.android.settings.applications.appcompat.UserAspectRatioDetails;
import com.android.settings.applications.appinfo.AlarmsAndRemindersDetails;
@@ -109,6 +110,7 @@
import com.android.settings.display.ColorContrastFragment;
import com.android.settings.display.NightDisplaySettings;
import com.android.settings.display.ScreenTimeoutSettings;
+import com.android.settings.display.RefreshRateSettings;
import com.android.settings.display.SmartAutoRotatePreferenceFragment;
import com.android.settings.display.darkmode.DarkModeSettingsFragment;
import com.android.settings.dream.DreamSettings;
@@ -343,6 +345,7 @@ public class SettingsGateway {
MainClearConfirm.class.getName(),
ResetDashboardFragment.class.getName(),
NightDisplaySettings.class.getName(),
+ RefreshRateSettings.class.getName(),
ManageDomainUrls.class.getName(),
AutomaticStorageManagerSettings.class.getName(),
StorageDashboardFragment.class.getName(),
@@ -396,6 +399,7 @@ public class SettingsGateway {
CellularSecuritySettingsFragment.class.getName(),
AccessibilityHearingAidsFragment.class.getName(),
HearingDevicePairingFragment.class.getName(),
+ RunningServices.class.getName(),
};
public static final String[] SETTINGS_FOR_RESTRICTED = {
@@ -442,5 +446,6 @@ public class SettingsGateway {
UserBackupSettingsActivity.class.getName(),
Settings.MemtagPageActivity.class.getName(),
Settings.NavigationModeSettingsActivity.class.getName(),
+ Settings.DevRunningServicesActivity.class.getName(),
};
}
diff --git a/src/com/android/settings/custom/fragment/notifications/IslandSettings.java b/src/com/android/settings/custom/fragment/notifications/IslandSettings.java
new file mode 100644
index 00000000000..51cbccc8ee1
--- /dev/null
+++ b/src/com/android/settings/custom/fragment/notifications/IslandSettings.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 the risingOS Android Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.custom.fragment.notifications;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.search.SearchIndexable;
+
+import java.util.List;
+
+@SearchIndexable
+public class IslandSettings extends SettingsPreferenceFragment {
+
+ public static final String TAG = "IslandSettings";
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.island_settings);
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsProto.MetricsEvent.DERP;
+ }
+
+ /**
+ * For search
+ */
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider(R.xml.island_settings) {
+
+ @Override
+ public List getNonIndexableKeys(Context context) {
+ List keys = super.getNonIndexableKeys(context);
+
+ return keys;
+ }
+ };
+}
diff --git a/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java b/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java
index ffc97dc722c..3dddda24d5f 100644
--- a/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java
+++ b/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java
@@ -42,6 +42,7 @@
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
+import android.graphics.drawable.LayerDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.UserHandle;
@@ -90,6 +91,7 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider {
private static final String TAG = "DashboardFeatureImpl";
private static final String DASHBOARD_TILE_PREF_KEY_PREFIX = "dashboard_tile_pref_";
private static final String META_DATA_KEY_INTENT_ACTION = "com.android.settings.intent.action";
+ private static final String WELLBEING_PACKAGE = "com.google.android.apps.wellbeing";
protected final Context mContext;
@@ -443,6 +445,11 @@ private void setPreferenceIcon(Preference preference, Tile tile, boolean forceRo
return;
}
if (TextUtils.equals(tile.getCategory(), CategoryKey.CATEGORY_HOMEPAGE)) {
+ if (iconPackage.equals(WELLBEING_PACKAGE) && iconDrawable instanceof LayerDrawable
+ && ((LayerDrawable) iconDrawable).getDrawable(1) != null) {
+ iconDrawable = ((LayerDrawable) iconDrawable).getDrawable(1);
+ iconDrawable.mutate();
+ }
iconDrawable.setTint(Utils.getHomepageIconColor(preference.getContext()));
}
diff --git a/src/com/android/settings/dashboard/DashboardFragment.java b/src/com/android/settings/dashboard/DashboardFragment.java
index 9abc6c2a0fa..dca60f902be 100644
--- a/src/com/android/settings/dashboard/DashboardFragment.java
+++ b/src/com/android/settings/dashboard/DashboardFragment.java
@@ -556,12 +556,17 @@ private void refreshDashboardTiles(final String tag) {
screen.addPreference(pref);
}
} else {
+ Preference group = null;
if (tile.hasGroupKey()
&& mDashboardTilePrefKeys.containsKey(tile.getGroupKey())) {
- Preference group = screen.findPreference(tile.getGroupKey());
- if (group instanceof PreferenceCategory) {
- ((PreferenceCategory) group).addPreference(pref);
- }
+ group = screen.findPreference(tile.getGroupKey());
+ } else if ("top_level_google".equals(key)) {
+ group = screen.findPreference("top_level_account_category");
+ } else if ("top_level_wellbeing".equals(key)) {
+ group = screen.findPreference("top_level_security_privacy_category");
+ }
+ if (group instanceof PreferenceCategory) {
+ ((PreferenceCategory) group).addPreference(pref);
} else {
screen.addPreference(pref);
}
diff --git a/src/com/android/settings/deletionhelper/AutomaticStorageManagerSettings.java b/src/com/android/settings/deletionhelper/AutomaticStorageManagerSettings.java
index a36d9edca55..133b291ea31 100644
--- a/src/com/android/settings/deletionhelper/AutomaticStorageManagerSettings.java
+++ b/src/com/android/settings/deletionhelper/AutomaticStorageManagerSettings.java
@@ -25,7 +25,7 @@
import android.view.View;
import android.view.ViewGroup;
-import androidx.preference.DropDownPreference;
+import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceChangeListener;
@@ -51,7 +51,7 @@ public class AutomaticStorageManagerSettings extends DashboardFragment
private static final String KEY_DAYS = "days";
private AutomaticStorageManagerSwitchBarController mSwitchController;
- private DropDownPreference mDaysToRetain;
+ private ListPreference mDaysToRetain;
private SettingsMainSwitchBar mSwitchBar;
@Override
@@ -66,7 +66,7 @@ public View onCreateView(
}
private void initializeDaysToRetainPreference() {
- mDaysToRetain = (DropDownPreference) findPreference(KEY_DAYS);
+ mDaysToRetain = (ListPreference) findPreference(KEY_DAYS);
mDaysToRetain.setOnPreferenceChangeListener(this);
ContentResolver cr = getContentResolver();
diff --git a/src/com/android/settings/derp/ChangelogActivity.java b/src/com/android/settings/derp/ChangelogActivity.java
new file mode 100644
index 00000000000..9ad9227c461
--- /dev/null
+++ b/src/com/android/settings/derp/ChangelogActivity.java
@@ -0,0 +1,32 @@
+/*
+* Copyright (C) 2016 The NitrogenOS Project
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see .
+*
+*/
+
+package com.android.settings.derp;
+
+import android.os.Bundle;
+import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
+
+public class ChangelogActivity extends CollapsingToolbarBaseActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getFragmentManager().beginTransaction().replace(
+ com.android.settingslib.collapsingtoolbar.R.id.content_frame,
+ new ChangelogFragment()).commit();
+ }
+}
diff --git a/src/com/android/settings/derp/ChangelogFragment.java b/src/com/android/settings/derp/ChangelogFragment.java
new file mode 100644
index 00000000000..48b1986b153
--- /dev/null
+++ b/src/com/android/settings/derp/ChangelogFragment.java
@@ -0,0 +1,130 @@
+/*
+ Copyright (C) 2016 Nitrogen Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+package com.android.settings.derp;
+
+import android.annotation.Nullable;
+import android.app.Fragment;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.graphics.Color;
+import android.graphics.Typeface;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.StyleSpan;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.LinearLayout.LayoutParams;
+import android.widget.TextView;
+
+import androidx.preference.PreferenceFragment;
+
+import com.android.settings.R;
+
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+import com.android.internal.logging.nano.MetricsProto;
+
+public class ChangelogFragment extends PreferenceFragment {
+
+ TextView textView;
+
+ private static final String CHANGELOG_PATH = "/system/etc/Changelog.txt";
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.changelog, container, false);
+ }
+
+ @Override
+ public void onViewCreated(final View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ textView = view.findViewById(R.id.changelog_text);
+
+ InputStreamReader inputReader = null;
+ String text = null;
+ StringBuilder data = new StringBuilder();
+
+ Pattern date = Pattern.compile("(={20}|\\d{4}-\\d{2}-\\d{2})");
+ Pattern commit = Pattern.compile("([a-f0-9]{7})");
+ Pattern committer = Pattern.compile("\\[(\\D.*?)]");
+ Pattern title = Pattern.compile("(\\R\\s+[\\*]\\s.*)");
+
+ try {
+ char tmp[] = new char[2048];
+ int numRead;
+
+ inputReader = new FileReader(CHANGELOG_PATH);
+ while ((numRead = inputReader.read(tmp)) >= 0) {
+ data.append(tmp, 0, numRead);
+ }
+// text = data.toString();
+ } catch (IOException e) {
+// text = getString(R.string.changelog_error);
+ } finally {
+ try {
+ if (inputReader != null) {
+ inputReader.close();
+ }
+ } catch (IOException e) {
+ }
+ }
+
+ SpannableStringBuilder sb = new SpannableStringBuilder(data);
+ Resources.Theme theme = getContext().getTheme();
+ TypedValue typedValue = new TypedValue();
+ theme.resolveAttribute(android.R.attr.colorAccent, typedValue, true);
+ final int color = getContext().getColor(typedValue.resourceId);
+
+ Matcher m = date.matcher(data);
+ while (m.find()){
+ sb.setSpan(new ForegroundColorSpan(color), m.start(1), m.end(1), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+ sb.setSpan(new StyleSpan(Typeface.BOLD), m.start(1), m.end(1), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+ }
+ m = commit.matcher(data);
+ while (m.find()){
+ sb.setSpan(new StyleSpan(Typeface.NORMAL), m.start(1), m.end(1), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+ }
+ m = committer.matcher(data);
+ while (m.find()){
+ sb.setSpan(new ForegroundColorSpan(color), m.start(1), m.end(1), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+ sb.setSpan(new StyleSpan(Typeface.NORMAL), m.start(1), m.end(1), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+ }
+ m = title.matcher(data);
+ while (m.find()){
+ sb.setSpan(new ForegroundColorSpan(color), m.start(1), m.end(1), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+ sb.setSpan(new StyleSpan(Typeface.BOLD), m.start(1), m.end(1), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+ }
+
+ textView.setText(sb);
+ }
+
+ @Override
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+
+ }
+}
diff --git a/src/com/android/settings/derp/buttons/ButtonSettings.java b/src/com/android/settings/derp/buttons/ButtonSettings.java
new file mode 100644
index 00000000000..4b91abe9a89
--- /dev/null
+++ b/src/com/android/settings/derp/buttons/ButtonSettings.java
@@ -0,0 +1,1013 @@
+/*
+ * SPDX-FileCopyrightText: 2016 The CyanogenMod project
+ * SPDX-FileCopyrightText: 2017-2023 The LineageOS project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.android.settings.derp.buttons;
+
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.om.IOverlayManager;
+import android.content.res.Resources;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.Display;
+import android.view.IWindowManager;
+import android.view.WindowManagerGlobal;
+
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.SwitchPreferenceCompat;
+
+import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.derp.buttons.preference.*;
+import com.android.settings.derp.utils.DeviceUtils;
+import com.android.settings.derp.utils.TelephonyUtils;
+import com.android.settingslib.search.SearchIndexable;
+
+import static com.android.internal.util.derp.DeviceKeysConstants.*;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import com.android.internal.derp.hardware.LineageHardwareManager;
+
+@SearchIndexable
+public class ButtonSettings extends SettingsPreferenceFragment
+ implements Preference.OnPreferenceChangeListener {
+ private static final String TAG = "SystemSettings";
+
+ private static final String KEY_BUTTON_BACKLIGHT = "button_backlight";
+ private static final String KEY_BACK_WAKE_SCREEN = "back_wake_screen";
+ private static final String KEY_CAMERA_LAUNCH = "camera_launch";
+ private static final String KEY_CAMERA_SLEEP_ON_RELEASE = "camera_sleep_on_release";
+ private static final String KEY_CAMERA_WAKE_SCREEN = "camera_wake_screen";
+ private static final String KEY_BACK_LONG_PRESS = "hardware_keys_back_long_press";
+ private static final String KEY_HOME_LONG_PRESS = "hardware_keys_home_long_press";
+ private static final String KEY_HOME_DOUBLE_TAP = "hardware_keys_home_double_tap";
+ private static final String KEY_HOME_WAKE_SCREEN = "home_wake_screen";
+ private static final String KEY_MENU_PRESS = "hardware_keys_menu_press";
+ private static final String KEY_MENU_LONG_PRESS = "hardware_keys_menu_long_press";
+ private static final String KEY_MENU_WAKE_SCREEN = "menu_wake_screen";
+ private static final String KEY_ASSIST_PRESS = "hardware_keys_assist_press";
+ private static final String KEY_ASSIST_LONG_PRESS = "hardware_keys_assist_long_press";
+ private static final String KEY_ASSIST_WAKE_SCREEN = "assist_wake_screen";
+ private static final String KEY_APP_SWITCH_PRESS = "hardware_keys_app_switch_press";
+ private static final String KEY_APP_SWITCH_LONG_PRESS = "hardware_keys_app_switch_long_press";
+ private static final String KEY_APP_SWITCH_WAKE_SCREEN = "app_switch_wake_screen";
+ private static final String KEY_VOLUME_KEY_CURSOR_CONTROL = "volume_key_cursor_control";
+ private static final String KEY_SWAP_VOLUME_BUTTONS = "swap_volume_buttons";
+ private static final String KEY_VOLUME_PANEL_ON_LEFT = "volume_panel_on_left";
+ private static final String KEY_VOLUME_WAKE_SCREEN = "volume_wake_screen";
+ private static final String KEY_VOLUME_ANSWER_CALL = "volume_answer_call";
+ private static final String KEY_DISABLE_NAV_KEYS = "disable_nav_keys";
+ private static final String KEY_NAVIGATION_ARROW_KEYS = "navigation_bar_menu_arrow_keys";
+ private static final String KEY_NAVIGATION_BACK_LONG_PRESS = "navigation_back_long_press";
+ private static final String KEY_NAVIGATION_HOME_LONG_PRESS = "navigation_home_long_press";
+ private static final String KEY_NAVIGATION_HOME_DOUBLE_TAP = "navigation_home_double_tap";
+ private static final String KEY_NAVIGATION_APP_SWITCH_LONG_PRESS =
+ "navigation_app_switch_long_press";
+ private static final String KEY_EDGE_LONG_SWIPE = "navigation_bar_edge_long_swipe";
+ private static final String KEY_POWER_END_CALL = "power_end_call";
+ private static final String KEY_HOME_ANSWER_CALL = "home_answer_call";
+ private static final String KEY_VOLUME_MUSIC_CONTROLS = "volbtn_music_controls";
+ private static final String KEY_TORCH_LONG_PRESS_POWER_GESTURE =
+ "torch_long_press_power_gesture";
+ private static final String KEY_TORCH_LONG_PRESS_POWER_TIMEOUT =
+ "torch_long_press_power_timeout";
+ private static final String KEY_CLICK_PARTIAL_SCREENSHOT =
+ "click_partial_screenshot";
+ private static final String KEY_SWAP_CAPACITIVE_KEYS = "swap_capacitive_keys";
+ private static final String KEY_NAV_BAR_INVERSE = "sysui_nav_bar_inverse";
+ private static final String KEY_ENABLE_TASKBAR = "enable_taskbar";
+
+ private static final String CATEGORY_POWER = "power_key";
+ private static final String CATEGORY_HOME = "home_key";
+ private static final String CATEGORY_BACK = "back_key";
+ private static final String CATEGORY_MENU = "menu_key";
+ private static final String CATEGORY_ASSIST = "assist_key";
+ private static final String CATEGORY_APPSWITCH = "app_switch_key";
+ private static final String CATEGORY_CAMERA = "camera_key";
+ private static final String CATEGORY_VOLUME = "volume_keys";
+ private static final String CATEGORY_NAVBAR = "navigation_bar_category";
+ private static final String CATEGORY_EXTRAS = "extras_category";
+
+ private ListPreference mBackLongPressAction;
+ private ListPreference mHomeLongPressAction;
+ private ListPreference mHomeDoubleTapAction;
+ private ListPreference mMenuPressAction;
+ private ListPreference mMenuLongPressAction;
+ private ListPreference mAssistPressAction;
+ private ListPreference mAssistLongPressAction;
+ private ListPreference mAppSwitchPressAction;
+ private ListPreference mAppSwitchLongPressAction;
+ private SwitchPreferenceCompat mCameraWakeScreen;
+ private SwitchPreferenceCompat mCameraSleepOnRelease;
+ private ListPreference mVolumeKeyCursorControl;
+ private SwitchPreferenceCompat mSwapVolumeButtons;
+ private SwitchPreferenceCompat mVolumePanelOnLeft;
+ private SwitchPreferenceCompat mDisableNavigationKeys;
+ private SwitchPreferenceCompat mNavigationArrowKeys;
+ private ListPreference mNavigationBackLongPressAction;
+ private ListPreference mNavigationHomeLongPressAction;
+ private ListPreference mNavigationHomeDoubleTapAction;
+ private ListPreference mNavigationAppSwitchLongPressAction;
+ private ListPreference mEdgeLongSwipeAction;
+ private SwitchPreferenceCompat mPowerEndCall;
+ private SwitchPreferenceCompat mHomeAnswerCall;
+ private ListPreference mTorchLongPressPowerTimeout;
+ private SwitchPreferenceCompat mSwapCapacitiveKeys;
+ private SwitchPreferenceCompat mNavBarInverse;
+ private SwitchPreferenceCompat mEnableTaskbar;
+
+ private PreferenceCategory mNavigationPreferencesCat;
+
+ private Handler mHandler;
+
+ private LineageHardwareManager mHardware;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mHardware = LineageHardwareManager.getInstance(getActivity());
+
+ addPreferencesFromResource(R.xml.button_settings);
+
+ final Resources res = getResources();
+ final ContentResolver resolver = requireActivity().getContentResolver();
+ final PreferenceScreen prefScreen = getPreferenceScreen();
+
+ final boolean hasPowerKey = DeviceUtils.hasPowerKey();
+ final boolean hasHomeKey = DeviceUtils.hasHomeKey(getActivity());
+ final boolean hasBackKey = DeviceUtils.hasBackKey(getActivity());
+ final boolean hasMenuKey = DeviceUtils.hasMenuKey(getActivity());
+ final boolean hasAssistKey = DeviceUtils.hasAssistKey(getActivity());
+ final boolean hasAppSwitchKey = DeviceUtils.hasAppSwitchKey(getActivity());
+ final boolean hasCameraKey = DeviceUtils.hasCameraKey(getActivity());
+ final boolean hasVolumeKeys = DeviceUtils.hasVolumeKeys(getActivity());
+
+ final boolean showHomeWake = DeviceUtils.canWakeUsingHomeKey(getActivity());
+ final boolean showBackWake = DeviceUtils.canWakeUsingBackKey(getActivity());
+ final boolean showMenuWake = DeviceUtils.canWakeUsingMenuKey(getActivity());
+ final boolean showAssistWake = DeviceUtils.canWakeUsingAssistKey(getActivity());
+ final boolean showAppSwitchWake = DeviceUtils.canWakeUsingAppSwitchKey(getActivity());
+ final boolean showCameraWake = DeviceUtils.canWakeUsingCameraKey(getActivity());
+ final boolean showVolumeWake = DeviceUtils.canWakeUsingVolumeKeys(getActivity());
+
+ final PreferenceCategory powerCategory = prefScreen.findPreference(CATEGORY_POWER);
+ final PreferenceCategory homeCategory = prefScreen.findPreference(CATEGORY_HOME);
+ final PreferenceCategory backCategory = prefScreen.findPreference(CATEGORY_BACK);
+ final PreferenceCategory menuCategory = prefScreen.findPreference(CATEGORY_MENU);
+ final PreferenceCategory assistCategory = prefScreen.findPreference(CATEGORY_ASSIST);
+ final PreferenceCategory appSwitchCategory = prefScreen.findPreference(CATEGORY_APPSWITCH);
+ final PreferenceCategory volumeCategory = prefScreen.findPreference(CATEGORY_VOLUME);
+ final PreferenceCategory cameraCategory = prefScreen.findPreference(CATEGORY_CAMERA);
+ final PreferenceCategory extrasCategory = prefScreen.findPreference(CATEGORY_EXTRAS);
+
+ // Power button ends calls.
+ mPowerEndCall = findPreference(KEY_POWER_END_CALL);
+
+ // Long press power while display is off to activate torchlight
+ SwitchPreferenceCompat torchLongPressPowerGesture =
+ findPreference(KEY_TORCH_LONG_PRESS_POWER_GESTURE);
+ final int torchLongPressPowerTimeout = Settings.System.getInt(resolver,
+ Settings.System.TORCH_LONG_PRESS_POWER_TIMEOUT, 0);
+ mTorchLongPressPowerTimeout = initList(KEY_TORCH_LONG_PRESS_POWER_TIMEOUT,
+ torchLongPressPowerTimeout);
+
+ // Home button answers calls.
+ mHomeAnswerCall = findPreference(KEY_HOME_ANSWER_CALL);
+
+ mHandler = new Handler(Looper.getMainLooper());
+
+ // Force Navigation bar related options
+ mDisableNavigationKeys = findPreference(KEY_DISABLE_NAV_KEYS);
+
+ mNavigationPreferencesCat = findPreference(CATEGORY_NAVBAR);
+
+ Action defaultBackLongPressAction = Action.fromIntSafe(res.getInteger(
+ com.android.internal.R.integer.config_longPressOnBackBehavior));
+ Action defaultHomeLongPressAction = Action.fromIntSafe(res.getInteger(
+ com.android.internal.R.integer.config_longPressOnHomeBehavior));
+ Action defaultHomeDoubleTapAction = Action.fromIntSafe(res.getInteger(
+ com.android.internal.R.integer.config_doubleTapOnHomeBehavior));
+ Action defaultAppSwitchLongPressAction = Action.fromIntSafe(res.getInteger(
+ com.android.internal.R.integer.config_longPressOnAppSwitchBehavior));
+ Action backLongPressAction = Action.fromSettings(resolver,
+ Settings.System.KEY_BACK_LONG_PRESS_ACTION,
+ defaultBackLongPressAction);
+ Action homeLongPressAction = Action.fromSettings(resolver,
+ Settings.System.KEY_HOME_LONG_PRESS_ACTION,
+ defaultHomeLongPressAction);
+ Action homeDoubleTapAction = Action.fromSettings(resolver,
+ Settings.System.KEY_HOME_DOUBLE_TAP_ACTION,
+ defaultHomeDoubleTapAction);
+ Action appSwitchLongPressAction = Action.fromSettings(resolver,
+ Settings.System.KEY_APP_SWITCH_LONG_PRESS_ACTION,
+ defaultAppSwitchLongPressAction);
+ Action edgeLongSwipeAction = Action.fromSettings(resolver,
+ Settings.System.KEY_EDGE_LONG_SWIPE_ACTION,
+ Action.NOTHING);
+
+ // Navigation bar arrow keys while typing
+ mNavigationArrowKeys = findPreference(KEY_NAVIGATION_ARROW_KEYS);
+
+ // Navigation bar back long press
+ mNavigationBackLongPressAction = initList(KEY_NAVIGATION_BACK_LONG_PRESS,
+ backLongPressAction);
+
+ // Navigation bar home long press
+ mNavigationHomeLongPressAction = initList(KEY_NAVIGATION_HOME_LONG_PRESS,
+ homeLongPressAction);
+
+ // Navigation bar home double tap
+ mNavigationHomeDoubleTapAction = initList(KEY_NAVIGATION_HOME_DOUBLE_TAP,
+ homeDoubleTapAction);
+
+ // Navigation bar app switch long press
+ mNavigationAppSwitchLongPressAction = initList(KEY_NAVIGATION_APP_SWITCH_LONG_PRESS,
+ appSwitchLongPressAction);
+
+ // Edge long swipe gesture
+ mEdgeLongSwipeAction = initList(KEY_EDGE_LONG_SWIPE, edgeLongSwipeAction);
+
+ // Hardware key disabler
+ if (isKeyDisablerSupported(getActivity())) {
+ // Remove keys that can be provided by the navbar
+ updateDisableNavkeysOption();
+ mNavigationPreferencesCat.setEnabled(mDisableNavigationKeys.isChecked());
+ mDisableNavigationKeys.setDisableDependentsState(true);
+ } else {
+ prefScreen.removePreference(mDisableNavigationKeys);
+ }
+ updateDisableNavkeysCategories(mDisableNavigationKeys.isChecked(), /* force */ true);
+
+ if (hasPowerKey) {
+ if (!TelephonyUtils.isVoiceCapable(requireActivity())) {
+ powerCategory.removePreference(mPowerEndCall);
+ mPowerEndCall = null;
+ }
+ if (!DeviceUtils.deviceSupportsFlashLight(requireActivity())) {
+ powerCategory.removePreference(torchLongPressPowerGesture);
+ powerCategory.removePreference(mTorchLongPressPowerTimeout);
+ }
+ }
+ if (!hasPowerKey || powerCategory.getPreferenceCount() == 0) {
+ prefScreen.removePreference(powerCategory);
+ }
+
+ if (hasHomeKey) {
+ if (!showHomeWake) {
+ homeCategory.removePreference(findPreference(KEY_HOME_WAKE_SCREEN));
+ }
+
+ if (!TelephonyUtils.isVoiceCapable(requireActivity())) {
+ homeCategory.removePreference(mHomeAnswerCall);
+ mHomeAnswerCall = null;
+ }
+
+ mHomeLongPressAction = initList(KEY_HOME_LONG_PRESS, homeLongPressAction);
+ mHomeDoubleTapAction = initList(KEY_HOME_DOUBLE_TAP, homeDoubleTapAction);
+ if (mDisableNavigationKeys.isChecked()) {
+ mHomeLongPressAction.setEnabled(false);
+ mHomeDoubleTapAction.setEnabled(false);
+ }
+
+ }
+ if (!hasHomeKey || homeCategory.getPreferenceCount() == 0) {
+ prefScreen.removePreference(homeCategory);
+ }
+
+ if (hasBackKey) {
+ if (!showBackWake) {
+ backCategory.removePreference(findPreference(KEY_BACK_WAKE_SCREEN));
+ }
+
+ mBackLongPressAction = initList(KEY_BACK_LONG_PRESS, backLongPressAction);
+ if (mDisableNavigationKeys.isChecked()) {
+ mBackLongPressAction.setEnabled(false);
+ }
+
+ }
+ if (!hasBackKey || backCategory.getPreferenceCount() == 0) {
+ prefScreen.removePreference(backCategory);
+ }
+
+ if (hasMenuKey) {
+ if (!showMenuWake) {
+ menuCategory.removePreference(findPreference(KEY_MENU_WAKE_SCREEN));
+ }
+
+ Action pressAction = Action.fromSettings(resolver,
+ Settings.System.KEY_MENU_ACTION, Action.MENU);
+ mMenuPressAction = initList(KEY_MENU_PRESS, pressAction);
+
+ Action longPressAction = Action.fromSettings(resolver,
+ Settings.System.KEY_MENU_LONG_PRESS_ACTION,
+ hasAssistKey ? Action.NOTHING : Action.APP_SWITCH);
+ mMenuLongPressAction = initList(KEY_MENU_LONG_PRESS, longPressAction);
+
+ }
+ if (!hasMenuKey || menuCategory.getPreferenceCount() == 0) {
+ prefScreen.removePreference(menuCategory);
+ }
+
+ if (hasAssistKey) {
+ if (!showAssistWake) {
+ assistCategory.removePreference(findPreference(KEY_ASSIST_WAKE_SCREEN));
+ }
+
+ Action pressAction = Action.fromSettings(resolver,
+ Settings.System.KEY_ASSIST_ACTION, Action.SEARCH);
+ mAssistPressAction = initList(KEY_ASSIST_PRESS, pressAction);
+
+ Action longPressAction = Action.fromSettings(resolver,
+ Settings.System.KEY_ASSIST_LONG_PRESS_ACTION, Action.VOICE_SEARCH);
+ mAssistLongPressAction = initList(KEY_ASSIST_LONG_PRESS, longPressAction);
+
+ }
+ if (!hasAssistKey || assistCategory.getPreferenceCount() == 0) {
+ prefScreen.removePreference(assistCategory);
+ }
+
+ if (hasAppSwitchKey) {
+ if (!showAppSwitchWake) {
+ appSwitchCategory.removePreference(findPreference(KEY_APP_SWITCH_WAKE_SCREEN));
+ }
+
+ Action pressAction = Action.fromSettings(resolver,
+ Settings.System.KEY_APP_SWITCH_ACTION, Action.APP_SWITCH);
+ mAppSwitchPressAction = initList(KEY_APP_SWITCH_PRESS, pressAction);
+
+ mAppSwitchLongPressAction = initList(KEY_APP_SWITCH_LONG_PRESS,
+ appSwitchLongPressAction);
+
+ }
+ if (!hasAppSwitchKey || appSwitchCategory.getPreferenceCount() == 0) {
+ prefScreen.removePreference(appSwitchCategory);
+ }
+
+ if (hasCameraKey) {
+ mCameraWakeScreen = findPreference(KEY_CAMERA_WAKE_SCREEN);
+ mCameraSleepOnRelease = findPreference(KEY_CAMERA_SLEEP_ON_RELEASE);
+
+ if (!showCameraWake) {
+ prefScreen.removePreference(mCameraWakeScreen);
+ }
+ // Only show 'Camera sleep on release' if the device has a focus key
+ if (res.getBoolean(
+ com.android.internal.R.bool.config_singleStageCameraKey)) {
+ prefScreen.removePreference(mCameraSleepOnRelease);
+ }
+ }
+ if (!hasCameraKey || cameraCategory.getPreferenceCount() == 0) {
+ prefScreen.removePreference(cameraCategory);
+ }
+
+ if (hasVolumeKeys) {
+ if (!showVolumeWake) {
+ volumeCategory.removePreference(findPreference(KEY_VOLUME_WAKE_SCREEN));
+ }
+
+ if (!TelephonyUtils.isVoiceCapable(requireActivity())) {
+ volumeCategory.removePreference(findPreference(KEY_VOLUME_ANSWER_CALL));
+ }
+
+ int cursorControlAction = Settings.System.getInt(resolver,
+ Settings.System.VOLUME_KEY_CURSOR_CONTROL, 0);
+ mVolumeKeyCursorControl = initList(KEY_VOLUME_KEY_CURSOR_CONTROL,
+ cursorControlAction);
+
+ int swapVolumeKeys = Settings.System.getInt(resolver,
+ Settings.System.SWAP_VOLUME_KEYS_ON_ROTATION, 0);
+ mSwapVolumeButtons = prefScreen.findPreference(KEY_SWAP_VOLUME_BUTTONS);
+ if (mSwapVolumeButtons != null) {
+ mSwapVolumeButtons.setChecked(swapVolumeKeys > 0);
+ }
+
+ final boolean volumePanelOnLeft = Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.VOLUME_PANEL_ON_LEFT, 0, UserHandle.USER_CURRENT) != 0;
+ mVolumePanelOnLeft = prefScreen.findPreference(KEY_VOLUME_PANEL_ON_LEFT);
+ if (mVolumePanelOnLeft != null) {
+ mVolumePanelOnLeft.setChecked(volumePanelOnLeft);
+ }
+ } else {
+ extrasCategory.removePreference(findPreference(KEY_CLICK_PARTIAL_SCREENSHOT));
+ }
+ if (!hasVolumeKeys || volumeCategory.getPreferenceCount() == 0) {
+ prefScreen.removePreference(volumeCategory);
+ }
+
+ // Only show the navigation bar category on devices that have a navigation bar
+ // or support disabling the hardware keys
+ if (!hasNavigationBar() && !isKeyDisablerSupported(getActivity())) {
+ prefScreen.removePreference(mNavigationPreferencesCat);
+ }
+
+ final ButtonBacklightBrightness backlight = findPreference(KEY_BUTTON_BACKLIGHT);
+ if (!DeviceUtils.hasButtonBacklightSupport(requireActivity())
+ && !DeviceUtils.hasKeyboardBacklightSupport(getActivity())) {
+ prefScreen.removePreference(backlight);
+ }
+
+ if (mCameraWakeScreen != null) {
+ if (mCameraSleepOnRelease != null && !res.getBoolean(
+ com.android.internal.R.bool.config_singleStageCameraKey)) {
+ mCameraSleepOnRelease.setDependency(KEY_CAMERA_WAKE_SCREEN);
+ }
+ }
+
+ SwitchPreferenceCompat volumeWakeScreen = findPreference(KEY_VOLUME_WAKE_SCREEN);
+ SwitchPreferenceCompat volumeMusicControls = findPreference(KEY_VOLUME_MUSIC_CONTROLS);
+
+ if (volumeWakeScreen != null) {
+ if (volumeMusicControls != null) {
+ volumeMusicControls.setDependency(KEY_VOLUME_WAKE_SCREEN);
+ volumeWakeScreen.setDisableDependentsState(true);
+ }
+ }
+
+ mSwapCapacitiveKeys = findPreference(KEY_SWAP_CAPACITIVE_KEYS);
+ if (mSwapCapacitiveKeys != null && !isKeySwapperSupported(getActivity())) {
+ prefScreen.removePreference(mSwapCapacitiveKeys);
+ } else {
+ mSwapCapacitiveKeys.setOnPreferenceChangeListener(this);
+ mSwapCapacitiveKeys.setDependency(KEY_DISABLE_NAV_KEYS);
+ }
+
+ mNavBarInverse = findPreference(KEY_NAV_BAR_INVERSE);
+
+ mEnableTaskbar = findPreference(KEY_ENABLE_TASKBAR);
+ if (mEnableTaskbar != null) {
+ if (!hasNavigationBar()) {
+ mNavigationPreferencesCat.removePreference(mEnableTaskbar);
+ } else {
+ mEnableTaskbar.setOnPreferenceChangeListener(this);
+ mEnableTaskbar.setChecked(Settings.System.getInt(resolver,
+ Settings.System.ENABLE_TASKBAR,
+ isLargeScreen(requireContext()) ? 1 : 0) == 1);
+ toggleTaskBarDependencies(mEnableTaskbar.isChecked());
+ }
+ }
+
+ List unsupportedValues = new ArrayList<>();
+ List entries = new ArrayList<>(
+ Arrays.asList(res.getStringArray(R.array.hardware_keys_action_entries)));
+ List values = new ArrayList<>(
+ Arrays.asList(res.getStringArray(R.array.hardware_keys_action_values)));
+
+ // hide split screen option unconditionally - it doesn't work at the moment
+ // once someone gets it working again: hide it only for low-ram devices
+ // (check ActivityManager.isLowRamDeviceStatic())
+ unsupportedValues.add(Action.SPLIT_SCREEN.ordinal());
+
+ for (int unsupportedValue: unsupportedValues) {
+ entries.remove(unsupportedValue);
+ values.remove(unsupportedValue);
+ }
+
+ String[] actionEntries = entries.toArray(new String[0]);
+ String[] actionValues = values.toArray(new String[0]);
+
+ if (hasBackKey) {
+ mBackLongPressAction.setEntries(actionEntries);
+ mBackLongPressAction.setEntryValues(actionValues);
+ }
+
+ if (hasHomeKey) {
+ mHomeLongPressAction.setEntries(actionEntries);
+ mHomeLongPressAction.setEntryValues(actionValues);
+
+ mHomeDoubleTapAction.setEntries(actionEntries);
+ mHomeDoubleTapAction.setEntryValues(actionValues);
+ }
+
+ if (hasMenuKey) {
+ mMenuPressAction.setEntries(actionEntries);
+ mMenuPressAction.setEntryValues(actionValues);
+
+ mMenuLongPressAction.setEntries(actionEntries);
+ mMenuLongPressAction.setEntryValues(actionValues);
+ }
+
+ if (hasAssistKey) {
+ mAssistPressAction.setEntries(actionEntries);
+ mAssistPressAction.setEntryValues(actionValues);
+
+ mAssistLongPressAction.setEntries(actionEntries);
+ mAssistLongPressAction.setEntryValues(actionValues);
+ }
+
+ if (hasAppSwitchKey) {
+ mAppSwitchPressAction.setEntries(actionEntries);
+ mAppSwitchPressAction.setEntryValues(actionValues);
+
+ mAppSwitchLongPressAction.setEntries(actionEntries);
+ mAppSwitchLongPressAction.setEntryValues(actionValues);
+ }
+
+ mNavigationBackLongPressAction.setEntries(actionEntries);
+ mNavigationBackLongPressAction.setEntryValues(actionValues);
+
+ mNavigationHomeLongPressAction.setEntries(actionEntries);
+ mNavigationHomeLongPressAction.setEntryValues(actionValues);
+
+ mNavigationHomeDoubleTapAction.setEntries(actionEntries);
+ mNavigationHomeDoubleTapAction.setEntryValues(actionValues);
+
+ mNavigationAppSwitchLongPressAction.setEntries(actionEntries);
+ mNavigationAppSwitchLongPressAction.setEntryValues(actionValues);
+
+ mEdgeLongSwipeAction.setEntries(actionEntries);
+ mEdgeLongSwipeAction.setEntryValues(actionValues);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ // Power button ends calls.
+ if (mPowerEndCall != null) {
+ final int incallPowerBehavior = Settings.Secure.getInt(getContentResolver(),
+ Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR,
+ Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_DEFAULT);
+ final boolean powerButtonEndsCall =
+ (incallPowerBehavior == Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP);
+ mPowerEndCall.setChecked(powerButtonEndsCall);
+ }
+
+ // Home button answers calls.
+ if (mHomeAnswerCall != null) {
+ final int incallHomeBehavior = Settings.Secure.getInt(getContentResolver(),
+ Settings.Secure.RING_HOME_BUTTON_BEHAVIOR,
+ Settings.Secure.RING_HOME_BUTTON_BEHAVIOR_DEFAULT);
+ final boolean homeButtonAnswersCall =
+ (incallHomeBehavior == Settings.Secure.RING_HOME_BUTTON_BEHAVIOR_ANSWER);
+ mHomeAnswerCall.setChecked(homeButtonAnswersCall);
+ }
+ }
+
+ private ListPreference initList(String key, Action value) {
+ return initList(key, value.ordinal());
+ }
+
+ private ListPreference initList(String key, int value) {
+ ListPreference list = getPreferenceScreen().findPreference(key);
+ if (list == null) return null;
+ list.setValue(Integer.toString(value));
+ list.setSummary(list.getEntry());
+ list.setOnPreferenceChangeListener(this);
+ return list;
+ }
+
+ private void handleListChange(ListPreference pref, Object newValue, String setting) {
+ String value = (String) newValue;
+ int index = pref.findIndexOfValue(value);
+ pref.setSummary(pref.getEntries()[index]);
+ Settings.System.putInt(getContentResolver(), setting, Integer.parseInt(value));
+ }
+
+ private void handleSystemListChange(ListPreference pref, Object newValue, String setting) {
+ String value = (String) newValue;
+ int index = pref.findIndexOfValue(value);
+ pref.setSummary(pref.getEntries()[index]);
+ Settings.System.putInt(getContentResolver(), setting, Integer.parseInt(value));
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if (preference == mBackLongPressAction ||
+ preference == mNavigationBackLongPressAction) {
+ handleListChange((ListPreference) preference, newValue,
+ Settings.System.KEY_BACK_LONG_PRESS_ACTION);
+ return true;
+ } else if (preference == mHomeLongPressAction ||
+ preference == mNavigationHomeLongPressAction) {
+ handleListChange((ListPreference) preference, newValue,
+ Settings.System.KEY_HOME_LONG_PRESS_ACTION);
+ return true;
+ } else if (preference == mHomeDoubleTapAction ||
+ preference == mNavigationHomeDoubleTapAction) {
+ handleListChange((ListPreference) preference, newValue,
+ Settings.System.KEY_HOME_DOUBLE_TAP_ACTION);
+ return true;
+ } else if (preference == mMenuPressAction) {
+ handleListChange(mMenuPressAction, newValue,
+ Settings.System.KEY_MENU_ACTION);
+ return true;
+ } else if (preference == mMenuLongPressAction) {
+ handleListChange(mMenuLongPressAction, newValue,
+ Settings.System.KEY_MENU_LONG_PRESS_ACTION);
+ return true;
+ } else if (preference == mAssistPressAction) {
+ handleListChange(mAssistPressAction, newValue,
+ Settings.System.KEY_ASSIST_ACTION);
+ return true;
+ } else if (preference == mAssistLongPressAction) {
+ handleListChange(mAssistLongPressAction, newValue,
+ Settings.System.KEY_ASSIST_LONG_PRESS_ACTION);
+ return true;
+ } else if (preference == mAppSwitchPressAction) {
+ handleListChange(mAppSwitchPressAction, newValue,
+ Settings.System.KEY_APP_SWITCH_ACTION);
+ return true;
+ } else if (preference == mAppSwitchLongPressAction ||
+ preference == mNavigationAppSwitchLongPressAction) {
+ handleListChange((ListPreference) preference, newValue,
+ Settings.System.KEY_APP_SWITCH_LONG_PRESS_ACTION);
+ return true;
+ } else if (preference == mVolumeKeyCursorControl) {
+ handleSystemListChange(mVolumeKeyCursorControl, newValue,
+ Settings.System.VOLUME_KEY_CURSOR_CONTROL);
+ return true;
+ } else if (preference == mTorchLongPressPowerTimeout) {
+ handleListChange(mTorchLongPressPowerTimeout, newValue,
+ Settings.System.TORCH_LONG_PRESS_POWER_TIMEOUT);
+ return true;
+ } else if (preference == mEdgeLongSwipeAction) {
+ handleListChange(mEdgeLongSwipeAction, newValue,
+ Settings.System.KEY_EDGE_LONG_SWIPE_ACTION);
+ return true;
+ } else if (preference == mSwapCapacitiveKeys) {
+ mHardware.set(LineageHardwareManager.FEATURE_KEY_SWAP, (Boolean) newValue);
+ return true;
+ } else if (preference == mEnableTaskbar) {
+ toggleTaskBarDependencies((Boolean) newValue);
+ if ((Boolean) newValue && is2ButtonNavigationEnabled(requireContext())) {
+ // Let's switch to gestural mode if user previously had 2 buttons enabled.
+ setButtonNavigationMode(NAV_BAR_MODE_GESTURAL_OVERLAY);
+ }
+ Settings.System.putInt(getContentResolver(),
+ Settings.System.ENABLE_TASKBAR, ((Boolean) newValue) ? 1 : 0);
+ return true;
+ }
+ return false;
+ }
+
+ private static boolean is2ButtonNavigationEnabled(Context context) {
+ return NAV_BAR_MODE_2BUTTON == context.getResources().getInteger(
+ com.android.internal.R.integer.config_navBarInteractionMode);
+ }
+
+ private static void setButtonNavigationMode(String overlayPackage) {
+ IOverlayManager overlayManager = IOverlayManager.Stub.asInterface(
+ ServiceManager.getService(Context.OVERLAY_SERVICE));
+ try {
+ overlayManager.setEnabledExclusiveInCategory(overlayPackage, UserHandle.USER_CURRENT);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private void toggleTaskBarDependencies(boolean enabled) {
+ enablePreference(mNavigationArrowKeys, !enabled);
+ enablePreference(mNavBarInverse, !enabled);
+ enablePreference(mNavigationBackLongPressAction, !enabled);
+ enablePreference(mNavigationHomeLongPressAction, !enabled);
+ enablePreference(mNavigationHomeDoubleTapAction, !enabled);
+ enablePreference(mNavigationAppSwitchLongPressAction, !enabled);
+ }
+
+ private void enablePreference(Preference pref, boolean enabled) {
+ if (pref != null) {
+ pref.setEnabled(enabled);
+ }
+ }
+
+ private static void writeDisableNavkeysOption(Context context, boolean enabled) {
+ Settings.System.putIntForUser(context.getContentResolver(),
+ Settings.System.FORCE_SHOW_NAVBAR, enabled ? 1 : 0, UserHandle.USER_CURRENT);
+ }
+
+ private void updateDisableNavkeysOption() {
+ boolean enabled = Settings.System.getIntForUser(
+ requireActivity().getContentResolver(),
+ Settings.System.FORCE_SHOW_NAVBAR, 0, UserHandle.USER_CURRENT) != 0;
+
+ mDisableNavigationKeys.setChecked(enabled);
+ }
+
+ private void updateDisableNavkeysCategories(boolean navbarEnabled, boolean force) {
+ final PreferenceScreen prefScreen = getPreferenceScreen();
+
+ /* Disable hw-key options if they're disabled */
+ final PreferenceCategory homeCategory =
+ prefScreen.findPreference(CATEGORY_HOME);
+ final PreferenceCategory backCategory =
+ prefScreen.findPreference(CATEGORY_BACK);
+ final PreferenceCategory menuCategory =
+ prefScreen.findPreference(CATEGORY_MENU);
+ final PreferenceCategory assistCategory =
+ prefScreen.findPreference(CATEGORY_ASSIST);
+ final PreferenceCategory appSwitchCategory =
+ prefScreen.findPreference(CATEGORY_APPSWITCH);
+ final ButtonBacklightBrightness backlight =
+ (ButtonBacklightBrightness) prefScreen.findPreference(KEY_BUTTON_BACKLIGHT);
+
+ /* Toggle backlight control depending on navbar state, force it to
+ off if enabling */
+ if (backlight != null) {
+ backlight.setEnabled(!navbarEnabled);
+ backlight.updateSummary();
+ }
+
+ /* Toggle hardkey control availability depending on navbar state */
+ if (mNavigationPreferencesCat != null) {
+ if (force || navbarEnabled) {
+ if (DeviceUtils.isEdgeToEdgeEnabled(requireContext())) {
+ mNavigationPreferencesCat.addPreference(mEdgeLongSwipeAction);
+
+ mNavigationPreferencesCat.removePreference(mNavigationArrowKeys);
+ mNavigationPreferencesCat.removePreference(mNavigationBackLongPressAction);
+ mNavigationPreferencesCat.removePreference(mNavigationHomeLongPressAction);
+ mNavigationPreferencesCat.removePreference(mNavigationHomeDoubleTapAction);
+ mNavigationPreferencesCat.removePreference(mNavigationAppSwitchLongPressAction);
+ } else if (DeviceUtils.isSwipeUpEnabled(getContext())) {
+ mNavigationPreferencesCat.addPreference(mNavigationBackLongPressAction);
+ mNavigationPreferencesCat.addPreference(mNavigationHomeLongPressAction);
+ mNavigationPreferencesCat.addPreference(mNavigationHomeDoubleTapAction);
+
+ mNavigationPreferencesCat.removePreference(mNavigationAppSwitchLongPressAction);
+ mNavigationPreferencesCat.removePreference(mEdgeLongSwipeAction);
+ } else {
+ mNavigationPreferencesCat.addPreference(mNavigationBackLongPressAction);
+ mNavigationPreferencesCat.addPreference(mNavigationHomeLongPressAction);
+ mNavigationPreferencesCat.addPreference(mNavigationHomeDoubleTapAction);
+ mNavigationPreferencesCat.addPreference(mNavigationAppSwitchLongPressAction);
+
+ mNavigationPreferencesCat.removePreference(mEdgeLongSwipeAction);
+ }
+ }
+ }
+ if (backCategory != null) {
+ enablePreference(mBackLongPressAction, !navbarEnabled);
+ }
+ if (homeCategory != null) {
+ enablePreference(mHomeAnswerCall, !navbarEnabled);
+ enablePreference(mHomeLongPressAction, !navbarEnabled);
+ enablePreference(mHomeDoubleTapAction, !navbarEnabled);
+ }
+ if (menuCategory != null) {
+ enablePreference(mMenuPressAction, !navbarEnabled);
+ enablePreference(mMenuLongPressAction, !navbarEnabled);
+ }
+ if (assistCategory != null) {
+ enablePreference(mAssistPressAction, !navbarEnabled);
+ enablePreference(mAssistLongPressAction, !navbarEnabled);
+ }
+ if (appSwitchCategory != null) {
+ enablePreference(mAppSwitchPressAction, !navbarEnabled);
+ enablePreference(mAppSwitchLongPressAction, !navbarEnabled);
+ }
+ }
+
+ private static boolean hasNavigationBar() {
+ boolean hasNavigationBar = false;
+ try {
+ IWindowManager windowManager = WindowManagerGlobal.getWindowManagerService();
+ hasNavigationBar = windowManager.hasNavigationBar(Display.DEFAULT_DISPLAY);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error getting navigation bar status");
+ }
+ return hasNavigationBar;
+ }
+
+ private static boolean isKeyDisablerSupported(Context context) {
+ final LineageHardwareManager hardware = LineageHardwareManager.getInstance(context);
+ return hardware.isSupported(LineageHardwareManager.FEATURE_KEY_DISABLE);
+ }
+
+ private static boolean isKeySwapperSupported(Context context) {
+ final LineageHardwareManager hardware = LineageHardwareManager.getInstance(context);
+ return hardware.isSupported(LineageHardwareManager.FEATURE_KEY_SWAP);
+ }
+
+ public static void restoreKeyDisabler(Context context) {
+ if (!isKeyDisablerSupported(context)) {
+ return;
+ }
+
+ boolean enabled = Settings.System.getIntForUser(context.getContentResolver(),
+ Settings.System.FORCE_SHOW_NAVBAR, 0, UserHandle.USER_CURRENT) != 0;
+
+ writeDisableNavkeysOption(context, enabled);
+ }
+
+ public static void restoreKeySwapper(Context context) {
+ if (!isKeySwapperSupported(context)) {
+ return;
+ }
+
+ final SharedPreferences preferences =
+ PreferenceManager.getDefaultSharedPreferences(context);
+ final LineageHardwareManager hardware = LineageHardwareManager.getInstance(context);
+ hardware.set(LineageHardwareManager.FEATURE_KEY_SWAP,
+ preferences.getBoolean(KEY_SWAP_CAPACITIVE_KEYS, false));
+ }
+
+ @Override
+ public boolean onPreferenceTreeClick(Preference preference) {
+ if (preference == mSwapVolumeButtons) {
+ int value;
+
+ if (mSwapVolumeButtons.isChecked()) {
+ /* The native inputflinger service uses the same logic of:
+ * 1 - the volume rocker is on one the sides, relative to the natural
+ * orientation of the display (true for all phones and most tablets)
+ * 2 - the volume rocker is on the top or bottom, relative to the
+ * natural orientation of the display (true for some tablets)
+ */
+ value = getResources().getInteger(
+ R.integer.config_volumeRockerVsDisplayOrientation);
+ } else {
+ /* Disable the re-orient functionality */
+ value = 0;
+ }
+ Settings.System.putInt(requireActivity().getContentResolver(),
+ Settings.System.SWAP_VOLUME_KEYS_ON_ROTATION, value);
+ } else if (preference == mVolumePanelOnLeft) {
+ Settings.Secure.putIntForUser(requireActivity().getContentResolver(),
+ Settings.Secure.VOLUME_PANEL_ON_LEFT,
+ mVolumePanelOnLeft.isChecked() ? 1 : 0, UserHandle.USER_CURRENT);
+ return true;
+ } else if (preference == mDisableNavigationKeys) {
+ mDisableNavigationKeys.setEnabled(false);
+ mNavigationPreferencesCat.setEnabled(false);
+ if (!mDisableNavigationKeys.isChecked()) {
+ setButtonNavigationMode(NAV_BAR_MODE_3BUTTON_OVERLAY);
+ }
+ writeDisableNavkeysOption(requireActivity(), mDisableNavigationKeys.isChecked());
+ updateDisableNavkeysOption();
+ updateDisableNavkeysCategories(true, false);
+ mHandler.postDelayed(() -> {
+ mDisableNavigationKeys.setEnabled(true);
+ mNavigationPreferencesCat.setEnabled(mDisableNavigationKeys.isChecked());
+ updateDisableNavkeysCategories(mDisableNavigationKeys.isChecked(), false);
+ }, 1000);
+ } else if (preference == mPowerEndCall) {
+ handleTogglePowerButtonEndsCallPreferenceClick();
+ return true;
+ } else if (preference == mHomeAnswerCall) {
+ handleToggleHomeButtonAnswersCallPreferenceClick();
+ return true;
+ }
+
+ return super.onPreferenceTreeClick(preference);
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsEvent.DERP;
+ }
+
+ private void handleTogglePowerButtonEndsCallPreferenceClick() {
+ Settings.Secure.putInt(getContentResolver(),
+ Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR, (mPowerEndCall.isChecked()
+ ? Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP
+ : Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF));
+ }
+
+ private void handleToggleHomeButtonAnswersCallPreferenceClick() {
+ Settings.Secure.putInt(getContentResolver(),
+ Settings.Secure.RING_HOME_BUTTON_BEHAVIOR, (mHomeAnswerCall.isChecked()
+ ? Settings.Secure.RING_HOME_BUTTON_BEHAVIOR_ANSWER
+ : Settings.Secure.RING_HOME_BUTTON_BEHAVIOR_DO_NOTHING));
+ }
+
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider(R.xml.button_settings) {
+
+ @Override
+ public List getNonIndexableKeys(Context context) {
+ final List result = new ArrayList<>();
+
+ if (!TelephonyUtils.isVoiceCapable(context)) {
+ result.add(KEY_POWER_END_CALL);
+ result.add(KEY_HOME_ANSWER_CALL);
+ result.add(KEY_VOLUME_ANSWER_CALL);
+ }
+
+ if (!DeviceUtils.hasBackKey(context)) {
+ result.add(CATEGORY_BACK);
+ result.add(KEY_BACK_WAKE_SCREEN);
+ } else if (!DeviceUtils.canWakeUsingHomeKey(context)) {
+ result.add(KEY_BACK_WAKE_SCREEN);
+ }
+
+ if (!DeviceUtils.hasHomeKey(context)) {
+ result.add(CATEGORY_HOME);
+ result.add(KEY_HOME_LONG_PRESS);
+ result.add(KEY_HOME_DOUBLE_TAP);
+ result.add(KEY_HOME_ANSWER_CALL);
+ result.add(KEY_HOME_WAKE_SCREEN);
+ } else if (!DeviceUtils.canWakeUsingHomeKey(context)) {
+ result.add(KEY_HOME_WAKE_SCREEN);
+ }
+
+ if (!DeviceUtils.hasMenuKey(context)) {
+ result.add(CATEGORY_MENU);
+ result.add(KEY_MENU_PRESS);
+ result.add(KEY_MENU_LONG_PRESS);
+ result.add(KEY_MENU_WAKE_SCREEN);
+ } else if (!DeviceUtils.canWakeUsingMenuKey(context)) {
+ result.add(KEY_MENU_WAKE_SCREEN);
+ }
+
+ if (!DeviceUtils.hasAssistKey(context)) {
+ result.add(CATEGORY_ASSIST);
+ result.add(KEY_ASSIST_PRESS);
+ result.add(KEY_ASSIST_LONG_PRESS);
+ result.add(KEY_ASSIST_WAKE_SCREEN);
+ } else if (!DeviceUtils.canWakeUsingAssistKey(context)) {
+ result.add(KEY_ASSIST_WAKE_SCREEN);
+ }
+
+ if (!DeviceUtils.hasAppSwitchKey(context)) {
+ result.add(CATEGORY_APPSWITCH);
+ result.add(KEY_APP_SWITCH_PRESS);
+ result.add(KEY_APP_SWITCH_LONG_PRESS);
+ result.add(KEY_APP_SWITCH_WAKE_SCREEN);
+ } else if (!DeviceUtils.canWakeUsingAppSwitchKey(context)) {
+ result.add(KEY_APP_SWITCH_WAKE_SCREEN);
+ }
+
+ if (!DeviceUtils.hasCameraKey(context)) {
+ result.add(CATEGORY_CAMERA);
+ result.add(KEY_CAMERA_LAUNCH);
+ result.add(KEY_CAMERA_SLEEP_ON_RELEASE);
+ result.add(KEY_CAMERA_WAKE_SCREEN);
+ } else if (!DeviceUtils.canWakeUsingCameraKey(context)) {
+ result.add(KEY_CAMERA_WAKE_SCREEN);
+ }
+
+ if (!DeviceUtils.hasVolumeKeys(context)) {
+ result.add(CATEGORY_VOLUME);
+ result.add(KEY_SWAP_VOLUME_BUTTONS);
+ result.add(KEY_VOLUME_ANSWER_CALL);
+ result.add(KEY_VOLUME_KEY_CURSOR_CONTROL);
+ result.add(KEY_VOLUME_MUSIC_CONTROLS);
+ result.add(KEY_VOLUME_PANEL_ON_LEFT);
+ result.add(KEY_VOLUME_WAKE_SCREEN);
+ result.add(KEY_CLICK_PARTIAL_SCREENSHOT);
+ } else if (!DeviceUtils.canWakeUsingVolumeKeys(context)) {
+ result.add(KEY_VOLUME_WAKE_SCREEN);
+ }
+
+ if (!DeviceUtils.deviceSupportsFlashLight(context)) {
+ result.add(KEY_TORCH_LONG_PRESS_POWER_GESTURE);
+ result.add(KEY_TORCH_LONG_PRESS_POWER_TIMEOUT);
+ }
+
+ if (!isKeyDisablerSupported(context)) {
+ result.add(KEY_DISABLE_NAV_KEYS);
+ }
+
+ if (!isKeySwapperSupported(context)) {
+ result.add(KEY_SWAP_CAPACITIVE_KEYS);
+ }
+
+ if (!DeviceUtils.hasButtonBacklightSupport(context)
+ && !DeviceUtils.hasKeyboardBacklightSupport(context)) {
+ result.add(KEY_BUTTON_BACKLIGHT);
+ }
+
+ if (hasNavigationBar()) {
+ if (DeviceUtils.isEdgeToEdgeEnabled(context)) {
+ result.add(KEY_NAVIGATION_ARROW_KEYS);
+ result.add(KEY_NAVIGATION_HOME_LONG_PRESS);
+ result.add(KEY_NAVIGATION_HOME_DOUBLE_TAP);
+ result.add(KEY_NAVIGATION_APP_SWITCH_LONG_PRESS);
+ } else if (DeviceUtils.isSwipeUpEnabled(context)) {
+ result.add(KEY_NAVIGATION_APP_SWITCH_LONG_PRESS);
+ result.add(KEY_EDGE_LONG_SWIPE);
+ } else {
+ result.add(KEY_EDGE_LONG_SWIPE);
+ }
+ }
+ return result;
+ }
+ };
+}
diff --git a/src/com/android/settings/derp/buttons/PowerMenuActions.java b/src/com/android/settings/derp/buttons/PowerMenuActions.java
new file mode 100644
index 00000000000..39bec2fd6e3
--- /dev/null
+++ b/src/com/android/settings/derp/buttons/PowerMenuActions.java
@@ -0,0 +1,253 @@
+/*
+ * SPDX-FileCopyrightText: 2014-2015 The CyanogenMod Project
+ * SPDX-FileCopyrightText: 2017-2023 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.android.settings.derp.buttons;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.service.controls.ControlsProviderService;
+
+import androidx.preference.CheckBoxPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+
+import com.android.internal.util.EmergencyAffordanceManager;
+import com.android.settingslib.applications.ServiceListing;
+
+import com.android.internal.util.derp.PowerMenuConstants;
+import com.android.internal.util.derp.PowerMenuUtils;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.internal.derp.app.LineageGlobalActions;
+import com.android.settings.derp.utils.TelephonyUtils;
+
+import java.util.List;
+
+import static com.android.internal.util.derp.PowerMenuConstants.*;
+
+public class PowerMenuActions extends SettingsPreferenceFragment {
+ final static String TAG = "PowerMenuActions";
+
+ private static final String CATEGORY_POWER_MENU_ITEMS = "power_menu_items";
+
+ private PreferenceCategory mPowerMenuItemsCategory;
+
+ private CheckBoxPreference mScreenshotPref;
+ private CheckBoxPreference mAirplanePref;
+ private CheckBoxPreference mUsersPref;
+ private CheckBoxPreference mBugReportPref;
+ private CheckBoxPreference mEmergencyPref;
+ private CheckBoxPreference mDeviceControlsPref;
+ private CheckBoxPreference mPanicPref;
+ private CheckBoxPreference mRestartSystemUIPref;
+
+ private LineageGlobalActions mLineageGlobalActions;
+
+ private EmergencyAffordanceManager mEmergencyAffordanceManager;
+ private boolean mForceEmergCheck = false;
+
+ Context mContext;
+ private UserManager mUserManager;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ addPreferencesFromResource(R.xml.power_menu_actions_settings);
+ requireActivity().setTitle(R.string.power_menu_title);
+ mContext = requireActivity().getApplicationContext();
+ mUserManager = UserManager.get(mContext);
+ mLineageGlobalActions = mContext.getSystemService(LineageGlobalActions.class);
+ mEmergencyAffordanceManager = new EmergencyAffordanceManager(mContext);
+
+ mPowerMenuItemsCategory = findPreference(CATEGORY_POWER_MENU_ITEMS);
+
+ for (String action : PowerMenuConstants.getAllActions()) {
+ if (action.equals(GLOBAL_ACTION_KEY_SCREENSHOT)) {
+ mScreenshotPref = findPreference(GLOBAL_ACTION_KEY_SCREENSHOT);
+ } else if (action.equals(GLOBAL_ACTION_KEY_AIRPLANE)) {
+ mAirplanePref = findPreference(GLOBAL_ACTION_KEY_AIRPLANE);
+ } else if (action.equals(GLOBAL_ACTION_KEY_USERS)) {
+ mUsersPref = findPreference(GLOBAL_ACTION_KEY_USERS);
+ } else if (action.equals(GLOBAL_ACTION_KEY_BUGREPORT)) {
+ mBugReportPref = findPreference(GLOBAL_ACTION_KEY_BUGREPORT);
+ } else if (action.equals(GLOBAL_ACTION_KEY_EMERGENCY)) {
+ mEmergencyPref = findPreference(GLOBAL_ACTION_KEY_EMERGENCY);
+ } else if (action.equals(GLOBAL_ACTION_KEY_DEVICECONTROLS)) {
+ mDeviceControlsPref = findPreference(GLOBAL_ACTION_KEY_DEVICECONTROLS);
+ } else if (action.equals(GLOBAL_ACTION_KEY_PANIC)) {
+ mPanicPref = findPreference(GLOBAL_ACTION_KEY_PANIC);
+ } else if (action.equals(GLOBAL_ACTION_KEY_RESTART_SYSTEMUI)) {
+ mRestartSystemUIPref = findPreference(GLOBAL_ACTION_KEY_RESTART_SYSTEMUI);
+ }
+ }
+
+ if (!TelephonyUtils.isVoiceCapable(requireActivity())) {
+ mPowerMenuItemsCategory.removePreference(mEmergencyPref);
+ mEmergencyPref = null;
+ }
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ if (mScreenshotPref != null) {
+ mScreenshotPref.setChecked(mLineageGlobalActions.userConfigContains(
+ GLOBAL_ACTION_KEY_SCREENSHOT));
+ }
+
+ if (mAirplanePref != null) {
+ mAirplanePref.setChecked(mLineageGlobalActions.userConfigContains(
+ GLOBAL_ACTION_KEY_AIRPLANE));
+ }
+
+ if (mUsersPref != null) {
+ if (!UserHandle.MU_ENABLED || !UserManager.supportsMultipleUsers()) {
+ mPowerMenuItemsCategory.removePreference(mUsersPref);
+ mUsersPref = null;
+ } else {
+ List users = mUserManager.getUsers();
+ boolean enabled = (users.size() > 1);
+ mUsersPref.setChecked(mLineageGlobalActions.userConfigContains(
+ GLOBAL_ACTION_KEY_USERS) && enabled);
+ mUsersPref.setEnabled(enabled);
+ }
+ }
+
+ if (mBugReportPref != null) {
+ mBugReportPref.setChecked(mLineageGlobalActions.userConfigContains(
+ GLOBAL_ACTION_KEY_BUGREPORT));
+ }
+
+ if (mEmergencyPref != null) {
+ mForceEmergCheck = mEmergencyAffordanceManager.needsEmergencyAffordance();
+ mEmergencyPref.setChecked(mLineageGlobalActions.userConfigContains(
+ GLOBAL_ACTION_KEY_EMERGENCY) || mForceEmergCheck);
+ mEmergencyPref.setEnabled(!mForceEmergCheck);
+ }
+
+ if (mDeviceControlsPref != null) {
+ mDeviceControlsPref.setChecked(mLineageGlobalActions.userConfigContains(
+ GLOBAL_ACTION_KEY_DEVICECONTROLS));
+
+ // Enable preference if any device control app is installed
+ ServiceListing serviceListing = new ServiceListing.Builder(mContext)
+ .setIntentAction(ControlsProviderService.SERVICE_CONTROLS)
+ .setPermission(Manifest.permission.BIND_CONTROLS)
+ .setNoun("Controls Provider")
+ .setSetting("controls_providers")
+ .setTag("controls_providers")
+ .build();
+ serviceListing.addCallback(
+ services -> mDeviceControlsPref.setEnabled(!services.isEmpty()));
+ serviceListing.reload();
+ }
+
+ if (mPanicPref != null) {
+ mPanicPref.setChecked(mLineageGlobalActions.userConfigContains(
+ GLOBAL_ACTION_KEY_PANIC));
+ mPanicPref.setEnabled(PowerMenuUtils.isPanicAvailable(mContext));
+ }
+
+ if (mRestartSystemUIPref != null) {
+ mRestartSystemUIPref.setChecked(mLineageGlobalActions.userConfigContains(
+ GLOBAL_ACTION_KEY_RESTART_SYSTEMUI));
+ }
+
+ updatePreferences();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ updatePreferences();
+ }
+
+ @Override
+ public boolean onPreferenceTreeClick(Preference preference) {
+ boolean value;
+
+ if (preference == mScreenshotPref) {
+ value = mScreenshotPref.isChecked();
+ mLineageGlobalActions.updateUserConfig(value, GLOBAL_ACTION_KEY_SCREENSHOT);
+
+ } else if (preference == mAirplanePref) {
+ value = mAirplanePref.isChecked();
+ mLineageGlobalActions.updateUserConfig(value, GLOBAL_ACTION_KEY_AIRPLANE);
+
+ } else if (preference == mUsersPref) {
+ value = mUsersPref.isChecked();
+ mLineageGlobalActions.updateUserConfig(value, GLOBAL_ACTION_KEY_USERS);
+
+ } else if (preference == mBugReportPref) {
+ value = mBugReportPref.isChecked();
+ mLineageGlobalActions.updateUserConfig(value, GLOBAL_ACTION_KEY_BUGREPORT);
+ Settings.Global.putInt(getContentResolver(),
+ Settings.Global.BUGREPORT_IN_POWER_MENU, value ? 1 : 0);
+
+ } else if (preference == mEmergencyPref) {
+ value = mEmergencyPref.isChecked();
+ mLineageGlobalActions.updateUserConfig(value, GLOBAL_ACTION_KEY_EMERGENCY);
+
+ } else if (preference == mDeviceControlsPref) {
+ value = mDeviceControlsPref.isChecked();
+ mLineageGlobalActions.updateUserConfig(value, GLOBAL_ACTION_KEY_DEVICECONTROLS);
+
+ } else if (preference == mPanicPref) {
+ value = mPanicPref.isChecked();
+ mLineageGlobalActions.updateUserConfig(value, GLOBAL_ACTION_KEY_PANIC);
+
+ } else if (preference == mRestartSystemUIPref) {
+ value = mRestartSystemUIPref.isChecked();
+ mLineageGlobalActions.updateUserConfig(value, GLOBAL_ACTION_KEY_RESTART_SYSTEMUI);
+
+ } else {
+ return super.onPreferenceTreeClick(preference);
+ }
+ return true;
+ }
+
+ private void updatePreferences() {
+ UserInfo currentUser = mUserManager.getUserInfo(UserHandle.myUserId());
+ boolean developmentSettings = Settings.Global.getInt(
+ getContentResolver(), Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) == 1;
+ boolean bugReport = Settings.Global.getInt(
+ getContentResolver(), Settings.Global.BUGREPORT_IN_POWER_MENU, 0) == 1;
+ boolean isPrimaryUser = currentUser == null || currentUser.isPrimary();
+ if (mBugReportPref != null) {
+ mBugReportPref.setEnabled(developmentSettings && isPrimaryUser);
+ if (!developmentSettings) {
+ mBugReportPref.setChecked(false);
+ mBugReportPref.setSummary(R.string.power_menu_bug_report_devoptions_unavailable);
+ } else if (!isPrimaryUser) {
+ mBugReportPref.setChecked(false);
+ mBugReportPref.setSummary(R.string.power_menu_bug_report_unavailable_for_user);
+ } else {
+ mBugReportPref.setChecked(bugReport);
+ mBugReportPref.setSummary(null);
+ }
+ }
+ if (mEmergencyPref != null) {
+ if (mForceEmergCheck) {
+ mEmergencyPref.setSummary(R.string.power_menu_emergency_affordance_enabled);
+ } else {
+ mEmergencyPref.setSummary(null);
+ }
+ }
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsEvent.DERP;
+ }
+}
diff --git a/src/com/android/settings/derp/buttons/TorchLongPressController.java b/src/com/android/settings/derp/buttons/TorchLongPressController.java
new file mode 100644
index 00000000000..ae15d69f9c8
--- /dev/null
+++ b/src/com/android/settings/derp/buttons/TorchLongPressController.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2018 The PixelExperience Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.derp.buttons;
+
+import android.content.Context;
+import com.android.settings.core.BasePreferenceController;
+
+public class TorchLongPressController extends BasePreferenceController {
+
+ public static final String KEY = "torch_long_press_power_gesture";
+
+ private Context mContext;
+
+ public TorchLongPressController(Context context, String key) {
+ super(context, key);
+
+ mContext = context;
+ }
+
+ public TorchLongPressController(Context context) {
+ this(context, KEY);
+
+ mContext = context;
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ boolean exists = mContext.getResources().getBoolean(com.android.internal.R.bool.config_supportLongPressPowerWhenNonInteractive);
+ return (exists ? AVAILABLE : UNSUPPORTED_ON_DEVICE);
+ }
+
+}
diff --git a/src/com/android/settings/derp/buttons/preference/BacklightTimeoutSeekBar.java b/src/com/android/settings/derp/buttons/preference/BacklightTimeoutSeekBar.java
new file mode 100644
index 00000000000..c0ee7bb103a
--- /dev/null
+++ b/src/com/android/settings/derp/buttons/preference/BacklightTimeoutSeekBar.java
@@ -0,0 +1,57 @@
+/*
+ * SPDX-FileCopyrightText: 2013 The CyanogenMod Project
+ * SPDX-FileCopyrightText: 2022 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.android.settings.derp.buttons.preference;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.widget.SeekBar;
+
+public class BacklightTimeoutSeekBar extends SeekBar {
+ private int mMax;
+ private int mGap;
+
+ public BacklightTimeoutSeekBar(Context context) {
+ super(context);
+ }
+
+ public BacklightTimeoutSeekBar(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public BacklightTimeoutSeekBar(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ }
+
+ @Override
+ public void setThumb(Drawable thumb) {
+ super.setThumb(thumb);
+ }
+
+ @Override
+ public void setMax(int max) {
+ mMax = max;
+ mGap = max / 10;
+ super.setMax(max + 2 * mGap - 1);
+ }
+
+ @Override
+ protected int updateTouchProgress(int lastProgress, int newProgress) {
+ if (newProgress < mMax) {
+ return newProgress;
+ }
+ if (newProgress < mMax + mGap) {
+ return mMax - 1;
+ }
+ return getMax();
+ }
+}
diff --git a/src/com/android/settings/derp/buttons/preference/ButtonBacklightBrightness.java b/src/com/android/settings/derp/buttons/preference/ButtonBacklightBrightness.java
new file mode 100644
index 00000000000..c7241f37b23
--- /dev/null
+++ b/src/com/android/settings/derp/buttons/preference/ButtonBacklightBrightness.java
@@ -0,0 +1,467 @@
+/*
+ * SPDX-FileCopyrightText: 2013 The CyanogenMod Project
+ * SPDX-FileCopyrightText: 2017-2023 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.android.settings.derp.buttons.preference;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.Settings;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager.LayoutParams;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.preference.PreferenceManager;
+
+import com.android.settings.derp.utils.DeviceUtils;
+import com.android.settings.derp.preference.CustomDialogPreference;
+import com.android.settings.R;
+
+public class ButtonBacklightBrightness extends CustomDialogPreference implements
+ SeekBar.OnSeekBarChangeListener {
+ private static final int BUTTON_BRIGHTNESS_TOGGLE_MODE_ONLY = 1;
+ private static final int DEFAULT_BUTTON_TIMEOUT = 5;
+ private static final int KEYBOARD_BRIGHTNESS_TOGGLE_MODE_ONLY = 1;
+
+ public static final String KEY_BUTTON_BACKLIGHT = "pre_navbar_button_backlight";
+
+ private ButtonBrightnessControl mButtonBrightness;
+ private BrightnessControl mKeyboardBrightness;
+ private BrightnessControl mActiveControl;
+
+ private ViewGroup mTimeoutContainer;
+ private SeekBar mTimeoutBar;
+ private TextView mTimeoutValue;
+
+ private final ContentResolver mResolver;
+
+ private int mOriginalTimeout;
+
+ public ButtonBacklightBrightness(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ mResolver = context.getContentResolver();
+
+ setDialogLayoutResource(R.layout.button_backlight);
+
+ if (DeviceUtils.hasKeyboardBacklightSupport(context)) {
+ final boolean isSingleValue = KEYBOARD_BRIGHTNESS_TOGGLE_MODE_ONLY ==
+ context.getResources().getInteger(com.android.internal.R.integer
+ .config_deviceSupportsKeyboardBrightnessControl);
+ mKeyboardBrightness = new BrightnessControl(
+ Settings.Secure.KEYBOARD_BRIGHTNESS, isSingleValue);
+ mActiveControl = mKeyboardBrightness;
+ }
+ if (DeviceUtils.hasButtonBacklightSupport(context)) {
+ final boolean isSingleValue = BUTTON_BRIGHTNESS_TOGGLE_MODE_ONLY ==
+ context.getResources().getInteger(com.android.internal.R.integer
+ .config_deviceSupportsButtonBrightnessControl);
+
+ float defaultBrightness = context.getResources().getFloat(
+ com.android.internal.R.dimen
+ .config_buttonBrightnessSettingDefaultFloat);
+
+ mButtonBrightness = new ButtonBrightnessControl(
+ Settings.Secure.BUTTON_BRIGHTNESS,
+ Settings.System.BUTTON_BACKLIGHT_ONLY_WHEN_PRESSED,
+ isSingleValue, defaultBrightness);
+ mActiveControl = mButtonBrightness;
+ }
+
+ updateSummary();
+ }
+
+ @Override
+ protected void onClick(AlertDialog d, int which) {
+ super.onClick(d, which);
+
+ updateBrightnessPreview();
+ }
+
+ @Override
+ protected void onPrepareDialogBuilder(AlertDialog.Builder builder,
+ DialogInterface.OnClickListener listener) {
+ super.onPrepareDialogBuilder(builder, listener);
+ builder.setNeutralButton(R.string.reset, null);
+ builder.setNegativeButton(R.string.cancel, null);
+ builder.setPositiveButton(R.string.dlg_ok, null);
+ }
+
+ @Override
+ protected boolean onDismissDialog(AlertDialog dialog, int which) {
+ if (which == DialogInterface.BUTTON_NEUTRAL) {
+ mTimeoutBar.setProgress(DEFAULT_BUTTON_TIMEOUT);
+ applyTimeout(DEFAULT_BUTTON_TIMEOUT);
+ if (mButtonBrightness != null) {
+ mButtonBrightness.reset();
+ }
+ if (mKeyboardBrightness != null) {
+ mKeyboardBrightness.reset();
+ }
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected void onBindDialogView(View view) {
+ super.onBindDialogView(view);
+
+ mTimeoutContainer = view.findViewById(R.id.timeout_container);
+ mTimeoutBar = view.findViewById(R.id.timeout_seekbar);
+ mTimeoutValue = view.findViewById(R.id.timeout_value);
+ mTimeoutBar.setMax(30);
+ mTimeoutBar.setOnSeekBarChangeListener(this);
+ mOriginalTimeout = getTimeout();
+ mTimeoutBar.setProgress(mOriginalTimeout);
+ handleTimeoutUpdate(mTimeoutBar.getProgress());
+
+ ViewGroup buttonContainer = view.findViewById(R.id.button_container);
+ if (mButtonBrightness != null) {
+ mButtonBrightness.init(buttonContainer);
+ } else {
+ buttonContainer.setVisibility(View.GONE);
+ mTimeoutContainer.setVisibility(View.GONE);
+ }
+
+ ViewGroup keyboardContainer = view.findViewById(R.id.keyboard_container);
+ if (mKeyboardBrightness != null) {
+ mKeyboardBrightness.init(keyboardContainer);
+ } else {
+ keyboardContainer.setVisibility(View.GONE);
+ }
+
+ if (mButtonBrightness == null || mKeyboardBrightness == null) {
+ view.findViewById(R.id.button_keyboard_divider).setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ protected void onDialogClosed(boolean positiveResult) {
+ super.onDialogClosed(positiveResult);
+
+ if (!positiveResult) {
+ applyTimeout(mOriginalTimeout);
+ return;
+ }
+
+ if (mButtonBrightness != null) {
+ PreferenceManager.getDefaultSharedPreferences(getContext())
+ .edit()
+ .putFloat(KEY_BUTTON_BACKLIGHT, mButtonBrightness.getBrightness(false))
+ .apply();
+ }
+
+ applyTimeout(mTimeoutBar.getProgress());
+ if (mButtonBrightness != null) {
+ mButtonBrightness.applyBrightness();
+ }
+ if (mKeyboardBrightness != null) {
+ mKeyboardBrightness.applyBrightness();
+ }
+
+ updateSummary();
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ final Parcelable superState = super.onSaveInstanceState();
+ if (getDialog() == null || !getDialog().isShowing()) {
+ return superState;
+ }
+
+ // Save the dialog state
+ final SavedState myState = new SavedState(superState);
+ myState.timeout = mTimeoutBar.getProgress();
+ if (mButtonBrightness != null) {
+ myState.button = mButtonBrightness.getBrightness(false);
+ }
+ if (mKeyboardBrightness != null) {
+ myState.keyboard = mKeyboardBrightness.getBrightness(false);
+ }
+
+ return myState;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ if (state == null || !state.getClass().equals(SavedState.class)) {
+ // Didn't save state for us in onSaveInstanceState
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
+ SavedState myState = (SavedState) state;
+ super.onRestoreInstanceState(myState.getSuperState());
+
+ mTimeoutBar.setProgress(myState.timeout);
+ if (mButtonBrightness != null) {
+ mButtonBrightness.setBrightness(myState.button);
+ }
+ if (mKeyboardBrightness != null) {
+ mKeyboardBrightness.setBrightness(myState.keyboard);
+ }
+ }
+
+ public void updateSummary() {
+ if (mButtonBrightness != null) {
+ float buttonBrightness = mButtonBrightness.getBrightness(true);
+ int timeout = getTimeout();
+
+ if (buttonBrightness == 0.0f) {
+ setSummary(R.string.backlight_summary_disabled);
+ } else if (timeout == 0) {
+ setSummary(R.string.backlight_timeout_unlimited);
+ } else {
+ setSummary(getContext().getString(R.string.backlight_summary_enabled_with_timeout,
+ getTimeoutString(timeout)));
+ }
+ } else if (mKeyboardBrightness != null &&
+ mKeyboardBrightness.getBrightness(true) != 0.0f) {
+ setSummary(R.string.backlight_summary_enabled);
+ } else {
+ setSummary(R.string.backlight_summary_disabled);
+ }
+ }
+
+ private String getTimeoutString(int timeout) {
+ return getContext().getResources().getQuantityString(
+ R.plurals.backlight_timeout_time, timeout, timeout);
+ }
+
+ private int getTimeout() {
+ return Settings.Secure.getInt(mResolver,
+ Settings.Secure.BUTTON_BACKLIGHT_TIMEOUT, DEFAULT_BUTTON_TIMEOUT * 1000)
+ / 1000;
+ }
+
+ private void applyTimeout(int timeout) {
+ Settings.Secure.putInt(mResolver,
+ Settings.Secure.BUTTON_BACKLIGHT_TIMEOUT, timeout * 1000);
+ }
+
+ private void updateBrightnessPreview() {
+ if (getDialog() == null || getDialog().getWindow() == null) {
+ return;
+ }
+ Window window = getDialog().getWindow();
+ LayoutParams params = window.getAttributes();
+ if (mActiveControl != null) {
+ params.buttonBrightness = mActiveControl.getBrightness(false);
+ } else {
+ params.buttonBrightness = -1.0f;
+ }
+ window.setAttributes(params);
+ }
+
+ private void updateTimeoutEnabledState() {
+ float buttonBrightness = mButtonBrightness != null
+ ? mButtonBrightness.getBrightness(false) : 0.0f;
+ int count = mTimeoutContainer.getChildCount();
+ for (int i = 0; i < count; i++) {
+ mTimeoutContainer.getChildAt(i).setEnabled(buttonBrightness != 0.0f);
+ }
+ }
+
+ private void handleTimeoutUpdate(int timeout) {
+ if (timeout == 0) {
+ mTimeoutValue.setText(R.string.backlight_timeout_unlimited);
+ } else {
+ mTimeoutValue.setText(getTimeoutString(timeout));
+ }
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ handleTimeoutUpdate(progress);
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ // Do nothing here
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ applyTimeout(seekBar.getProgress());
+ }
+
+ private static class SavedState extends BaseSavedState {
+ int timeout;
+ float button;
+ float keyboard;
+
+ public SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ public SavedState(Parcel source) {
+ super(source);
+ timeout = source.readInt();
+ button = source.readFloat();
+ keyboard = source.readFloat();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(timeout);
+ dest.writeFloat(button);
+ dest.writeFloat(keyboard);
+ }
+
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator<>() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+ private class BrightnessControl implements
+ SeekBar.OnSeekBarChangeListener, CheckBox.OnCheckedChangeListener {
+ private final String mSetting;
+ private final boolean mIsSingleValue;
+ private final float mDefaultBrightness;
+ private CheckBox mCheckBox;
+ private SeekBar mSeekBar;
+ private TextView mValue;
+
+ public BrightnessControl(String setting, boolean singleValue, float defaultBrightness) {
+ mSetting = setting;
+ mIsSingleValue = singleValue;
+ mDefaultBrightness = defaultBrightness;
+ }
+
+ public BrightnessControl(String setting, boolean singleValue) {
+ this(setting, singleValue, 1.0f);
+ }
+
+ public void init(ViewGroup container) {
+ float brightness = getBrightness(true);
+
+ if (mIsSingleValue) {
+ container.findViewById(R.id.seekbar_container).setVisibility(View.GONE);
+ mCheckBox = container.findViewById(R.id.backlight_switch);
+ mCheckBox.setChecked(brightness != 0.0f);
+ mCheckBox.setOnCheckedChangeListener(this);
+ } else {
+ container.findViewById(R.id.checkbox_container).setVisibility(View.GONE);
+ mSeekBar = container.findViewById(R.id.seekbar);
+ mValue = container.findViewById(R.id.value);
+
+ mSeekBar.setMax(100);
+ mSeekBar.setProgress((int)(brightness * 100.0f));
+ mSeekBar.setOnSeekBarChangeListener(this);
+ }
+
+ handleBrightnessUpdate((int)(brightness * 100.0f));
+ }
+
+ public float getBrightness(boolean persisted) {
+ if (mCheckBox != null && !persisted) {
+ return mCheckBox.isChecked() ? mDefaultBrightness : 0.0f;
+ } else if (mSeekBar != null && !persisted) {
+ return mSeekBar.getProgress() / 100.0f;
+ }
+ return Settings.Secure.getFloat(mResolver, mSetting, mDefaultBrightness);
+ }
+
+ public void applyBrightness() {
+ Settings.Secure.putFloat(mResolver, mSetting, getBrightness(false));
+ }
+
+ /* Behaviors when it's a seekbar */
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ handleBrightnessUpdate(progress);
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ mActiveControl = this;
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ // Do nothing here
+ }
+
+ /* Behaviors when it's a plain checkbox */
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ mActiveControl = this;
+ updateBrightnessPreview();
+ updateTimeoutEnabledState();
+ }
+
+ public void setBrightness(float value) {
+ if (mIsSingleValue) {
+ mCheckBox.setChecked(value != 0.0f);
+ } else {
+ mSeekBar.setProgress((int)(value * 100.0f));
+ }
+ }
+
+ public void reset() {
+ setBrightness(mDefaultBrightness);
+ }
+
+ private void handleBrightnessUpdate(int brightness) {
+ updateBrightnessPreview();
+ if (mValue != null) {
+ mValue.setText(String.format("%d%%", brightness));
+ }
+ updateTimeoutEnabledState();
+ }
+ }
+
+ private class ButtonBrightnessControl extends BrightnessControl {
+ private final String mOnlyWhenPressedSetting;
+ private CheckBox mOnlyWhenPressedCheckBox;
+
+ public ButtonBrightnessControl(String brightnessSetting, String onlyWhenPressedSetting,
+ boolean singleValue, float defaultBrightness) {
+ super(brightnessSetting, singleValue, defaultBrightness);
+ mOnlyWhenPressedSetting = onlyWhenPressedSetting;
+ }
+
+ @Override
+ public void init(ViewGroup container) {
+ super.init(container);
+
+ mOnlyWhenPressedCheckBox =
+ container.findViewById(R.id.backlight_only_when_pressed_switch);
+ mOnlyWhenPressedCheckBox.setChecked(isOnlyWhenPressedEnabled());
+ mOnlyWhenPressedCheckBox.setOnCheckedChangeListener(this);
+ }
+
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ super.onCheckedChanged(buttonView, isChecked);
+ setOnlyWhenPressedEnabled(mOnlyWhenPressedCheckBox.isChecked());
+ }
+
+ public boolean isOnlyWhenPressedEnabled() {
+ return Settings.System.getInt(mResolver, mOnlyWhenPressedSetting, 0) == 1;
+ }
+
+ public void setOnlyWhenPressedEnabled(boolean enabled) {
+ Settings.System.putInt(mResolver, mOnlyWhenPressedSetting, enabled ? 1 : 0);
+ }
+ }
+}
diff --git a/src/com/android/settings/derp/health/ChargingControlPreferenceController.java b/src/com/android/settings/derp/health/ChargingControlPreferenceController.java
new file mode 100644
index 00000000000..e60474870e8
--- /dev/null
+++ b/src/com/android/settings/derp/health/ChargingControlPreferenceController.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 The PixelExperience Project
+ * Copyright (C) 2023 The LibreMobileOS Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.derp.health;
+
+import android.content.Context;
+import android.os.IBinder;
+import android.os.ServiceManager;
+
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.R;
+
+public class ChargingControlPreferenceController extends BasePreferenceController {
+
+ public static final String KEY = "charging_control";
+
+ private Context mContext;
+
+ public ChargingControlPreferenceController(Context context, String key) {
+ super(context, key);
+
+ mContext = context;
+ }
+
+ public ChargingControlPreferenceController(Context context) {
+ this(context, KEY);
+
+ mContext = context;
+ }
+
+ private boolean isNegated(String key) {
+ return key != null && key.startsWith("!");
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ String rService = "lineagehealth";
+ boolean negated = isNegated(rService);
+ if (negated) {
+ rService = rService.substring(1);
+ }
+ IBinder value = ServiceManager.getService(rService);
+ boolean available = value != null;
+ if (available == negated) {
+ return UNSUPPORTED_ON_DEVICE;
+ }
+ return AVAILABLE;
+ }
+
+}
diff --git a/src/com/android/settings/derp/health/ChargingControlSettings.java b/src/com/android/settings/derp/health/ChargingControlSettings.java
new file mode 100644
index 00000000000..55f0b5a91f1
--- /dev/null
+++ b/src/com/android/settings/derp/health/ChargingControlSettings.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2023 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.derp.health;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+
+import androidx.fragment.app.DialogFragment;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import java.lang.reflect.Array;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import java.util.stream.Stream;
+
+import com.android.settings.R;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settingslib.search.SearchIndexable;
+import com.android.settings.derp.preference.CustomDialogPreference;
+
+import com.android.internal.derp.health.HealthInterface;
+import org.derpfest.support.preferences.SystemSettingListPreference;
+import org.derpfest.support.preferences.SystemSettingMainSwitchPreference;
+import android.provider.Settings;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
+import static com.android.internal.derp.health.HealthInterface.MODE_AUTO;
+import static com.android.internal.derp.health.HealthInterface.MODE_MANUAL;
+import static com.android.internal.derp.health.HealthInterface.MODE_LIMIT;
+
+@SearchIndexable
+public class ChargingControlSettings extends SettingsPreferenceFragment implements
+ Preference.OnPreferenceChangeListener {
+ private static final String TAG = ChargingControlSettings.class.getSimpleName();
+
+ private static final String CHARGING_CONTROL_PREF = "charging_control";
+ private static final String CHARGING_CONTROL_ENABLED_PREF = "charging_control_enabled";
+ private static final String CHARGING_CONTROL_MODE_PREF = "charging_control_mode";
+ private static final String CHARGING_CONTROL_START_TIME_PREF = "charging_control_start_time";
+ private static final String CHARGING_CONTROL_TARGET_TIME_PREF = "charging_control_target_time";
+ private static final String CHARGING_CONTROL_LIMIT_PREF = "charging_control_charging_limit";
+
+ private SystemSettingMainSwitchPreference mChargingControlEnabledPref;
+ private SystemSettingListPreference mChargingControlModePref;
+ private StartTimePreference mChargingControlStartTimePref;
+ private TargetTimePreference mChargingControlTargetTimePref;
+ private ChargingLimitPreference mChargingControlLimitPref;
+
+ private HealthInterface mHealthInterface;
+
+ private static final int MENU_RESET = Menu.FIRST;
+
+ @Override
+ public void onActivityCreated(final Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ final Resources res = getResources();
+
+ addPreferencesFromResource(R.xml.charging_control_settings);
+ getActivity().getActionBar().setTitle(R.string.charging_control_title);
+
+ mHealthInterface = HealthInterface.getInstance(getActivity());
+
+ final PreferenceScreen prefSet = getPreferenceScreen();
+
+ mChargingControlEnabledPref = prefSet.findPreference(CHARGING_CONTROL_ENABLED_PREF);
+ mChargingControlEnabledPref.setOnPreferenceChangeListener(this);
+ mChargingControlModePref = prefSet.findPreference(CHARGING_CONTROL_MODE_PREF);
+ mChargingControlModePref.setOnPreferenceChangeListener(this);
+ mChargingControlStartTimePref = prefSet.findPreference(CHARGING_CONTROL_START_TIME_PREF);
+ mChargingControlTargetTimePref = prefSet.findPreference(CHARGING_CONTROL_TARGET_TIME_PREF);
+ mChargingControlLimitPref = prefSet.findPreference(CHARGING_CONTROL_LIMIT_PREF);
+
+ if (mChargingControlLimitPref != null) {
+ if (mHealthInterface.allowFineGrainedSettings()) {
+ mChargingControlModePref.setEntries(concatStringArrays(
+ mChargingControlModePref.getEntries(),
+ res.getStringArray(
+ R.array.charging_control_mode_entries_fine_grained_control)));
+ mChargingControlModePref.setEntryValues(concatStringArrays(
+ mChargingControlModePref.getEntryValues(),
+ res.getStringArray(
+ R.array.charging_control_mode_values_fine_grained_control)));
+ }
+ }
+
+ setHasOptionsMenu(true);
+
+ refreshValues();
+
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ refreshUi();
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsEvent.DERP;
+ }
+
+ @Override
+ public void onDisplayPreferenceDialog(Preference preference) {
+ if (preference.getKey() == null) {
+ // Auto-key preferences that don't have a key, so the dialog can find them.
+ preference.setKey(UUID.randomUUID().toString());
+ }
+ DialogFragment f = null;
+ if (preference instanceof CustomDialogPreference) {
+ f = CustomDialogPreference.CustomPreferenceDialogFragment
+ .newInstance(preference.getKey());
+ } else {
+ super.onDisplayPreferenceDialog(preference);
+ return;
+ }
+ f.setTargetFragment(this, 0);
+ f.show(getFragmentManager(), "dialog_preference");
+ onDialogShowing();
+ }
+
+ private void refreshValues() {
+ if (mChargingControlEnabledPref != null) {
+ mChargingControlEnabledPref.setChecked(mHealthInterface.getEnabled());
+ }
+
+ if (mChargingControlModePref != null) {
+ final int chargingControlMode = mHealthInterface.getMode();
+ mChargingControlModePref.setValue(Integer.toString(chargingControlMode));
+ refreshUi();
+ }
+
+ if (mChargingControlStartTimePref != null) {
+ mChargingControlStartTimePref.setValue(
+ mChargingControlStartTimePref.getTimeSetting());
+ }
+
+ if (mChargingControlTargetTimePref != null) {
+ mChargingControlTargetTimePref.setValue(
+ mChargingControlTargetTimePref.getTimeSetting());
+ }
+
+ if (mChargingControlLimitPref != null) {
+ mChargingControlLimitPref.setValue(
+ mChargingControlLimitPref.getSetting());
+ }
+ }
+
+ private void refreshUi() {
+ final int chargingControlMode = mHealthInterface.getMode();
+
+ refreshUi(chargingControlMode);
+ }
+
+ private void refreshUi(final int chargingControlMode) {
+ String summary = null;
+ boolean isChargingControlStartTimePrefVisible = false;
+ boolean isChargingControlTargetTimePrefVisible = false;
+ boolean isChargingControlLimitPrefVisible = false;
+
+ final Resources res = getResources();
+
+ switch (chargingControlMode) {
+ case MODE_AUTO:
+ summary = res.getString(R.string.charging_control_mode_auto_summary);
+ break;
+ case MODE_MANUAL:
+ summary = res.getString(R.string.charging_control_mode_custom_summary);
+ isChargingControlStartTimePrefVisible = true;
+ isChargingControlTargetTimePrefVisible = true;
+ break;
+ case MODE_LIMIT:
+ summary = res.getString(R.string.charging_control_mode_limit_summary);
+ isChargingControlLimitPrefVisible = true;
+ break;
+ default:
+ return;
+ }
+
+ mChargingControlModePref.setSummary(summary);
+
+ if (mChargingControlStartTimePref != null) {
+ mChargingControlStartTimePref.setVisible(isChargingControlStartTimePrefVisible);
+ }
+
+ if (mChargingControlTargetTimePref != null) {
+ mChargingControlTargetTimePref.setVisible(isChargingControlTargetTimePrefVisible);
+ }
+
+ if (mChargingControlLimitPref != null) {
+ mChargingControlLimitPref.setVisible(isChargingControlLimitPrefVisible);
+ }
+ }
+
+ @Override
+ public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
+ menu.add(0, MENU_RESET, 0, R.string.reset)
+ .setIcon(R.drawable.ic_settings_backup_restore)
+ .setAlphabeticShortcut('r')
+ .setShowAsActionFlags(
+ MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ if (item.getItemId() == MENU_RESET) {
+ resetToDefaults();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onPreferenceChange(final Preference preference, final Object objValue) {
+ if (preference == mChargingControlEnabledPref) {
+ mHealthInterface.setEnabled((Boolean) objValue);
+ } else if (preference == mChargingControlModePref) {
+ final int chargingControlMode = Integer.parseInt((String) objValue);
+ mHealthInterface.setMode(chargingControlMode);
+ refreshUi(chargingControlMode);
+ }
+ return true;
+ }
+
+ private void resetToDefaults() {
+ mHealthInterface.reset();
+
+ refreshValues();
+ }
+
+ private CharSequence[] concatStringArrays(CharSequence[] array1, CharSequence[] array2) {
+ return Stream.concat(Arrays.stream(array1), Arrays.stream(array2)).toArray(size ->
+ (CharSequence[]) Array.newInstance(CharSequence.class, size));
+ }
+
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider() {
+ @Override
+ public List getNonIndexableKeys(Context context) {
+ final List result = new ArrayList();
+ if (!HealthInterface.isChargingControlSupported(context)) {
+ result.add(CHARGING_CONTROL_PREF);
+ result.add(CHARGING_CONTROL_ENABLED_PREF);
+ result.add(CHARGING_CONTROL_MODE_PREF);
+ result.add(CHARGING_CONTROL_START_TIME_PREF);
+ result.add(CHARGING_CONTROL_TARGET_TIME_PREF);
+ result.add(CHARGING_CONTROL_LIMIT_PREF);
+ }
+ return result;
+ }
+ };
+
+}
diff --git a/src/com/android/settings/derp/health/ChargingLimitPreference.java b/src/com/android/settings/derp/health/ChargingLimitPreference.java
new file mode 100644
index 00000000000..1c299eb43cd
--- /dev/null
+++ b/src/com/android/settings/derp/health/ChargingLimitPreference.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2023 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.derp.health;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.internal.derp.health.HealthInterface;
+
+import com.android.settings.R;
+
+public class ChargingLimitPreference extends Preference
+ implements SeekBar.OnSeekBarChangeListener {
+ private static final String TAG = ChargingLimitPreference.class.getSimpleName();
+
+ private TextView mChargingLimitValue;
+ private SeekBar mChargingLimitBar;
+
+ private final HealthInterface mHealthInterface;
+
+ public ChargingLimitPreference(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+
+ setLayoutResource(R.layout.preference_charging_limit);
+
+ mHealthInterface = HealthInterface.getInstance(context);
+ }
+
+ @Override
+ public void onBindViewHolder(final PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+
+ mChargingLimitValue = (TextView) holder.findViewById(R.id.value);
+
+ mChargingLimitBar = (SeekBar) holder.findViewById(R.id.seekbar_widget);
+ mChargingLimitBar.setOnSeekBarChangeListener(this);
+
+ int currLimit = getSetting();
+ mChargingLimitBar.setProgress(currLimit);
+ updateValue(currLimit);
+ }
+
+ @Override
+ public void onStartTrackingTouch(final SeekBar seekBar) {
+ }
+
+ @Override
+ public void onStopTrackingTouch(final SeekBar seekBar) {
+ setSetting(seekBar.getProgress());
+ }
+
+ @Override
+ public void onProgressChanged(final SeekBar seekBar, final int progress,
+ final boolean fromUser) {
+ updateValue(progress);
+ }
+
+ public void setValue(final int value) {
+ if (mChargingLimitBar != null) {
+ mChargingLimitBar.setProgress(value);
+ }
+ updateValue(value);
+ }
+
+ protected int getSetting() {
+ return mHealthInterface.getLimit();
+ }
+
+ protected void setSetting(final int chargingLimit) {
+ mHealthInterface.setLimit(chargingLimit);
+ }
+
+ private void updateValue(final int value) {
+ if (mChargingLimitValue != null) {
+ mChargingLimitValue.setText(String.format("%d%%", value));
+ }
+ }
+}
diff --git a/src/com/android/settings/derp/health/StartTimePreference.java b/src/com/android/settings/derp/health/StartTimePreference.java
new file mode 100644
index 00000000000..5c928c1b85a
--- /dev/null
+++ b/src/com/android/settings/derp/health/StartTimePreference.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.derp.health;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.settings.R;
+
+public class StartTimePreference extends TimePreference {
+ private static final String TAG = StartTimePreference.class.getSimpleName();
+
+ public StartTimePreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected int getSummaryResourceId() {
+ return R.string.charging_control_start_time_summary;
+ }
+
+ @Override
+ protected int getTimeSetting() {
+ return mHealthInterface.getStartTime();
+ }
+
+ @Override
+ protected void setTimeSetting(int secondOfDay) {
+ mHealthInterface.setStartTime(secondOfDay);
+ }
+}
diff --git a/src/com/android/settings/derp/health/TargetTimePreference.java b/src/com/android/settings/derp/health/TargetTimePreference.java
new file mode 100644
index 00000000000..b536a208818
--- /dev/null
+++ b/src/com/android/settings/derp/health/TargetTimePreference.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.derp.health;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.settings.R;
+
+public class TargetTimePreference extends TimePreference {
+ private static final String TAG = TargetTimePreference.class.getSimpleName();
+
+ public TargetTimePreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected int getSummaryResourceId() {
+ return R.string.charging_control_target_time_summary;
+ }
+
+ @Override
+ protected int getTimeSetting() {
+ return mHealthInterface.getTargetTime();
+ }
+
+ @Override
+ protected void setTimeSetting(int secondOfDay) {
+ mHealthInterface.setTargetTime(secondOfDay);
+ }
+}
diff --git a/src/com/android/settings/derp/health/TimePreference.java b/src/com/android/settings/derp/health/TimePreference.java
new file mode 100644
index 00000000000..54ebe2c7d56
--- /dev/null
+++ b/src/com/android/settings/derp/health/TimePreference.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.derp.health;
+
+import static java.time.format.FormatStyle.SHORT;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.text.format.DateFormat;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.TimePicker;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.settings.derp.preference.CustomDialogPreference;
+import com.android.settings.R;
+
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+
+import com.android.internal.derp.health.HealthInterface;
+
+public abstract class TimePreference extends CustomDialogPreference {
+ private static final String TAG = TimePreference.class.getSimpleName();
+ private static final DateTimeFormatter mFormatter = DateTimeFormatter.ofLocalizedTime(SHORT);
+
+ private TimePicker mTimePicker;
+ private LocalTime mLocalTime;
+
+ protected HealthInterface mHealthInterface;
+
+ public TimePreference(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+
+ setDialogLayoutResource(R.layout.dialog_time);
+ mHealthInterface = HealthInterface.getInstance(context);
+ }
+
+ @Override
+ public void onBindViewHolder(final PreferenceViewHolder holder) {
+ mLocalTime = LocalTime.ofSecondOfDay(getTimeSetting());
+ super.onBindViewHolder(holder);
+ }
+
+ @Override
+ protected void onPrepareDialogBuilder(final AlertDialog.Builder builder,
+ final DialogInterface.OnClickListener listener) {
+ super.onPrepareDialogBuilder(builder, listener);
+
+ builder.setNegativeButton(R.string.cancel, null);
+ builder.setPositiveButton(R.string.dlg_ok, null);
+ }
+
+ @Override
+ protected void onDialogClosed(final boolean positiveResult) {
+ super.onDialogClosed(positiveResult);
+
+ if (positiveResult) {
+ mLocalTime = LocalTime.of(mTimePicker.getHour(),
+ mTimePicker.getMinute());
+ setTimeSetting(mLocalTime.toSecondOfDay());
+ setSummary(getSummary());
+ }
+ }
+
+ @Override
+ protected void onBindDialogView(View view) {
+ super.onBindDialogView(view);
+
+ mTimePicker = view.findViewById(R.id.time_picker);
+ mTimePicker.setIs24HourView(DateFormat.is24HourFormat(getContext()));
+ mTimePicker.setHour(mLocalTime.getHour());
+ mTimePicker.setMinute(mLocalTime.getMinute());
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ return String.format(getContext().getString(getSummaryResourceId()),
+ mLocalTime.format(mFormatter));
+ }
+
+ public void setValue(final int value) {
+ mLocalTime = LocalTime.ofSecondOfDay(value);
+ setSummary(getSummary());
+ }
+
+ protected abstract int getSummaryResourceId();
+
+ protected abstract int getTimeSetting();
+
+ protected abstract void setTimeSetting(int secondOfDay);
+}
diff --git a/src/com/android/settings/derp/livedisplay/DisplayColor.java b/src/com/android/settings/derp/livedisplay/DisplayColor.java
new file mode 100644
index 00000000000..f54e4d35970
--- /dev/null
+++ b/src/com/android/settings/derp/livedisplay/DisplayColor.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2013-2015 The CyanogenMod Project
+ * 2021-2022 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.derp.livedisplay;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import androidx.appcompat.app.AlertDialog;
+
+import com.android.settings.derp.preference.CustomDialogPreference;
+import com.android.settings.derp.widget.IntervalSeekBar;
+import com.android.settings.R;
+
+import com.android.internal.derp.hardware.LiveDisplayManager;
+
+/**
+ * Special preference type that allows configuration of Color settings
+ */
+public class DisplayColor extends CustomDialogPreference {
+ private static final String TAG = "ColorCalibration";
+
+ private final Context mContext;
+ private final LiveDisplayManager mLiveDisplay;
+
+ // These arrays must all match in length and order
+ private static final int[] SEEKBAR_ID = new int[] {
+ R.id.color_red_seekbar,
+ R.id.color_green_seekbar,
+ R.id.color_blue_seekbar
+ };
+
+ private static final int[] SEEKBAR_VALUE_ID = new int[] {
+ R.id.color_red_value,
+ R.id.color_green_value,
+ R.id.color_blue_value
+ };
+
+ private ColorSeekBar[] mSeekBars = new ColorSeekBar[SEEKBAR_ID.length];
+
+ private final float[] mCurrentColors = new float[3];
+ private final float[] mOriginalColors = new float[3];
+
+ public DisplayColor(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ mContext = context;
+ mLiveDisplay = mContext.getSystemService(LiveDisplayManager.class);
+
+ setDialogLayoutResource(R.layout.display_color_calibration);
+ }
+
+ @Override
+ protected void onPrepareDialogBuilder(AlertDialog.Builder builder,
+ DialogInterface.OnClickListener listener) {
+ super.onPrepareDialogBuilder(builder, listener);
+
+ builder.setNeutralButton(R.string.reset, null);
+ builder.setNegativeButton(R.string.cancel, null);
+ builder.setPositiveButton(R.string.dlg_ok, null);
+ }
+
+ @Override
+ protected void onBindDialogView(View view) {
+ super.onBindDialogView(view);
+
+ System.arraycopy(mLiveDisplay.getColorAdjustment(), 0, mOriginalColors, 0, 3);
+ System.arraycopy(mOriginalColors, 0, mCurrentColors, 0, 3);
+
+ for (int i = 0; i < SEEKBAR_ID.length; i++) {
+ IntervalSeekBar seekBar = (IntervalSeekBar) view.findViewById(SEEKBAR_ID[i]);
+ TextView value = (TextView) view.findViewById(SEEKBAR_VALUE_ID[i]);
+ mSeekBars[i] = new ColorSeekBar(seekBar, value, i);
+ mSeekBars[i].mSeekBar.setMinimum(0.1f);
+ mSeekBars[i].mSeekBar.setMaximum(1.0f);
+
+ mSeekBars[i].mSeekBar.setProgressFloat(mCurrentColors[i]);
+ int percent = Math.round(100F * mCurrentColors[i]);
+ value.setText(String.format("%d%%", percent));
+ }
+ }
+
+ @Override
+ protected boolean onDismissDialog(AlertDialog dialog, int which) {
+ // Can't use onPrepareDialogBuilder for this as we want the dialog
+ // to be kept open on click
+ if (which == DialogInterface.BUTTON_NEUTRAL) {
+ for (int i = 0; i < mSeekBars.length; i++) {
+ mSeekBars[i].mSeekBar.setProgressFloat(1.0f);
+ mCurrentColors[i] = 1.0f;
+ }
+ updateColors(mCurrentColors);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected void onDialogClosed(boolean positiveResult) {
+ super.onDialogClosed(positiveResult);
+ updateColors(positiveResult ? mCurrentColors : mOriginalColors);
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ final Parcelable superState = super.onSaveInstanceState();
+ if (getDialog() == null || !getDialog().isShowing()) {
+ return superState;
+ }
+
+ // Save the dialog state
+ final SavedState myState = new SavedState(superState);
+ myState.currentColors = mCurrentColors;
+ myState.originalColors = mOriginalColors;
+
+ // Restore the old state when the activity or dialog is being paused
+ updateColors(mOriginalColors);
+
+ return myState;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ if (state == null || !state.getClass().equals(SavedState.class)) {
+ // Didn't save state for us in onSaveInstanceState
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
+ SavedState myState = (SavedState) state;
+ super.onRestoreInstanceState(myState.getSuperState());
+
+ System.arraycopy(myState.originalColors, 0, mOriginalColors, 0, 3);
+ System.arraycopy(myState.currentColors, 0, mCurrentColors, 0, 3);
+ for (int i = 0; i < mSeekBars.length; i++) {
+ mSeekBars[i].mSeekBar.setProgressFloat(mCurrentColors[i]);
+ }
+ updateColors(mCurrentColors);
+ }
+
+ private static class SavedState extends BaseSavedState {
+ float[] originalColors;
+ float[] currentColors;
+
+ public SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ public SavedState(Parcel source) {
+ super(source);
+ originalColors = source.createFloatArray();
+ currentColors = source.createFloatArray();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeFloatArray(originalColors);
+ dest.writeFloatArray(currentColors);
+ }
+
+ public static final Parcelable.Creator CREATOR =
+ new Parcelable.Creator() {
+
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+ private void updateColors(float[] colors) {
+ mLiveDisplay.setColorAdjustment(colors);
+ }
+
+ private class ColorSeekBar implements SeekBar.OnSeekBarChangeListener {
+ private int mIndex;
+ private final IntervalSeekBar mSeekBar;
+ private TextView mValue;
+
+ public ColorSeekBar(IntervalSeekBar seekBar, TextView value, int index) {
+ mSeekBar = seekBar;
+ mValue = value;
+ mIndex = index;
+
+ mSeekBar.setOnSeekBarChangeListener(this);
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ IntervalSeekBar isb = (IntervalSeekBar)seekBar;
+ float fp = isb.getProgressFloat();
+ if (fromUser) {
+ mCurrentColors[mIndex] = fp > 1.0f ? 1.0f : fp;
+ updateColors(mCurrentColors);
+ }
+
+ int percent = Math.round(100F * fp);
+ mValue.setText(String.format("%d%%", percent));
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ // Do nothing here
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ // Do nothing here
+ }
+ }
+}
diff --git a/src/com/android/settings/derp/livedisplay/DisplayTemperature.java b/src/com/android/settings/derp/livedisplay/DisplayTemperature.java
new file mode 100644
index 00000000000..d0abb168b84
--- /dev/null
+++ b/src/com/android/settings/derp/livedisplay/DisplayTemperature.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod Project
+ * 2021-2022 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.derp.livedisplay;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import androidx.appcompat.app.AlertDialog;
+
+import com.android.settings.derp.preference.CustomDialogPreference;
+import com.android.settings.R;
+import com.android.internal.util.derp.MathUtils;
+
+import com.android.internal.derp.hardware.LiveDisplayConfig;
+import com.android.internal.derp.hardware.LiveDisplayManager;
+
+/**
+ * Preference for selection of color temperature range for LiveDisplay
+ */
+public class DisplayTemperature extends CustomDialogPreference {
+ private static final String TAG = "DisplayTemperature";
+
+ private final Context mContext;
+
+ private ColorTemperatureSeekBar mDayTemperature;
+ private ColorTemperatureSeekBar mNightTemperature;
+
+ private int mOriginalDayTemperature;
+ private int mOriginalNightTemperature;
+
+ private final LiveDisplayManager mLiveDisplay;
+ private final LiveDisplayConfig mConfig;
+
+ private static final int STEP = 100;
+
+ public DisplayTemperature(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mContext = context;
+ mLiveDisplay = mContext.getSystemService(LiveDisplayManager.class);
+ mConfig = mLiveDisplay.getConfig();
+
+ setDialogLayoutResource(R.layout.display_temperature);
+ }
+
+ @Override
+ protected void onPrepareDialogBuilder(AlertDialog.Builder builder,
+ DialogInterface.OnClickListener listener) {
+ super.onPrepareDialogBuilder(builder, listener);
+
+ builder.setNeutralButton(R.string.reset, null);
+ builder.setNegativeButton(R.string.cancel, null);
+ builder.setPositiveButton(R.string.dlg_ok, null);
+ }
+
+ @Override
+ protected void onBindDialogView(View view) {
+ super.onBindDialogView(view);
+
+ mOriginalDayTemperature = mLiveDisplay.getDayColorTemperature();
+ mOriginalNightTemperature = mLiveDisplay.getNightColorTemperature();
+
+ SeekBar day = (SeekBar) view.findViewById(R.id.day_temperature_seekbar);
+ TextView dayText = (TextView) view.findViewById(R.id.day_temperature_value);
+ mDayTemperature = new ColorTemperatureSeekBar(day, dayText);
+
+ SeekBar night = (SeekBar) view.findViewById(R.id.night_temperature_seekbar);
+ TextView nightText = (TextView) view.findViewById(R.id.night_temperature_value);
+ mNightTemperature = new ColorTemperatureSeekBar(night, nightText);
+
+ mDayTemperature.setTemperature(mOriginalDayTemperature);
+ mNightTemperature.setTemperature(mOriginalNightTemperature);
+ }
+
+
+ @Override
+ protected boolean onDismissDialog(AlertDialog dialog, int which) {
+ // Can't use onPrepareDialogBuilder for this as we want the dialog
+ // to be kept open on click
+ if (which == DialogInterface.BUTTON_NEUTRAL) {
+ mDayTemperature.setTemperature(mConfig.getDefaultDayTemperature());
+ mNightTemperature.setTemperature(mConfig.getDefaultNightTemperature());
+ updateTemperature(true);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected void onDialogClosed(boolean positiveResult) {
+ super.onDialogClosed(positiveResult);
+ updateTemperature(positiveResult);
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ final Parcelable superState = super.onSaveInstanceState();
+ if (getDialog() == null || !getDialog().isShowing()) {
+ return superState;
+ }
+
+ // Save the dialog state
+ final SavedState myState = new SavedState(superState);
+ myState.originalDayTemperature = mOriginalDayTemperature;
+ myState.originalNightTemperature = mOriginalNightTemperature;
+ myState.currentDayTemperature = mDayTemperature.getTemperature();
+ myState.currentNightTemperature = mNightTemperature.getTemperature();
+
+ // Restore the old state when the activity or dialog is being paused
+ updateTemperature(false);
+
+ return myState;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ if (state == null || !state.getClass().equals(SavedState.class)) {
+ // Didn't save state for us in onSaveInstanceState
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
+ SavedState myState = (SavedState) state;
+ super.onRestoreInstanceState(myState.getSuperState());
+
+ mOriginalDayTemperature = myState.originalDayTemperature;
+ mOriginalNightTemperature = myState.originalNightTemperature;
+ mDayTemperature.setTemperature(myState.currentDayTemperature);
+ mNightTemperature.setTemperature(myState.currentNightTemperature);;
+
+ updateTemperature(true);
+ }
+
+ private static class SavedState extends BaseSavedState {
+ int originalDayTemperature;
+ int originalNightTemperature;
+ int currentDayTemperature;
+ int currentNightTemperature;
+
+ public SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ public SavedState(Parcel source) {
+ super(source);
+ originalDayTemperature = source.readInt();
+ originalNightTemperature = source.readInt();
+ currentDayTemperature = source.readInt();
+ currentNightTemperature = source.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(originalDayTemperature);
+ dest.writeInt(originalNightTemperature);
+ dest.writeInt(currentDayTemperature);
+ dest.writeInt(currentNightTemperature);
+ }
+
+ public static final Parcelable.Creator CREATOR =
+ new Parcelable.Creator() {
+
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+ private void updateTemperature(boolean accept) {
+ int day = accept ? mDayTemperature.getTemperature() : mOriginalDayTemperature;
+ int night = accept ? mNightTemperature.getTemperature() : mOriginalNightTemperature;
+ callChangeListener(new Integer[] { day, night });
+
+ mLiveDisplay.setDayColorTemperature(day);
+ mLiveDisplay.setNightColorTemperature(night);
+ }
+
+ int roundUp(int value) {
+ return ((value + STEP / 2) / STEP) * STEP;
+ }
+
+ private class ColorTemperatureSeekBar implements SeekBar.OnSeekBarChangeListener {
+ private final SeekBar mSeekBar;
+ private final TextView mValue;
+
+ private final int mMin;
+ private final int mMax;
+
+ private final int mBalanceMin;
+ private final int mBalanceMax;
+
+ private final int mBarMax;
+
+ private final boolean mUseBalance;
+ private final double[] mBalanceCurve;
+
+ public ColorTemperatureSeekBar(SeekBar seekBar, TextView value) {
+ mSeekBar = seekBar;
+ mValue = value;
+ mMin = mConfig.getColorTemperatureRange().getLower();
+ mMax = mConfig.getColorTemperatureRange().getUpper();
+ mBalanceMin = mConfig.getColorBalanceRange().getLower();
+ mBalanceMax = mConfig.getColorBalanceRange().getUpper();
+ mUseBalance = mConfig.hasFeature(LiveDisplayManager.FEATURE_COLOR_BALANCE) &&
+ ((mBalanceMin != 0) || (mBalanceMax != 0));
+
+ if (mUseBalance) {
+ mBalanceCurve = MathUtils.powerCurve(mMin, mConfig.getDefaultDayTemperature(), mMax);
+ mBarMax = mBalanceMax - mBalanceMin;
+ } else {
+ mBalanceCurve = null;
+ mBarMax = (mMax - mMin) / STEP;
+ }
+ mSeekBar.setMax(mBarMax);
+ mSeekBar.setOnSeekBarChangeListener(this);
+
+ // init text value
+ int p = mSeekBar.getProgress();
+ onProgressChanged(mSeekBar, p, false);
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (fromUser) {
+ updateTemperature(true);
+ }
+
+ int displayValue;
+ if (mUseBalance) {
+ displayValue = roundUp(Math.round((float)MathUtils.linearToPowerCurve(
+ mBalanceCurve, (double)progress / (double)mBarMax)));
+ } else {
+ displayValue = progress * STEP + mMin;
+ }
+ Log.d(TAG, "onProgressChanged: progress=" + progress + " displayValue=" + displayValue);
+
+ mValue.setText(mContext.getResources().getString(
+ R.string.live_display_color_temperature_label, displayValue));
+ }
+
+ public void setTemperature(int temperature) {
+ if (mUseBalance) {
+ double z = MathUtils.powerCurveToLinear(mBalanceCurve, (double)temperature);
+ mSeekBar.setProgress(Math.round((float)(z * (double)mBarMax)));
+ return;
+ }
+ int p = Math.max(temperature, mMin) - mMin;
+ mSeekBar.setProgress(Math.round((float) p / STEP));
+ }
+
+ public int getTemperature() {
+ if (mUseBalance) {
+ return Math.round((float)MathUtils.linearToPowerCurve(
+ mBalanceCurve, (double)mSeekBar.getProgress() / (double)mBarMax));
+ }
+ return mSeekBar.getProgress() * STEP + mMin;
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ // Do nothing here
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ // Do nothing here
+ }
+ }
+}
diff --git a/src/com/android/settings/derp/livedisplay/LiveDisplayController.java b/src/com/android/settings/derp/livedisplay/LiveDisplayController.java
new file mode 100644
index 00000000000..091aaa0ed4d
--- /dev/null
+++ b/src/com/android/settings/derp/livedisplay/LiveDisplayController.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The PixelExperience Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.derp.livedisplay;
+
+import android.content.Context;
+import com.android.settings.core.BasePreferenceController;
+
+public class LiveDisplayController extends BasePreferenceController {
+
+ public static final String KEY = "livedisplay";
+
+ private Context mContext;
+
+ public LiveDisplayController(Context context, String key) {
+ super(context, key);
+
+ mContext = context;
+ }
+
+ public LiveDisplayController(Context context) {
+ this(context, KEY);
+
+ mContext = context;
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ boolean exists = mContext.getResources().getBoolean(com.android.internal.R.bool.config_enableLiveDisplay);
+ return (exists ? AVAILABLE : UNSUPPORTED_ON_DEVICE);
+ }
+
+}
diff --git a/src/com/android/settings/derp/livedisplay/LiveDisplaySettings.java b/src/com/android/settings/derp/livedisplay/LiveDisplaySettings.java
new file mode 100644
index 00000000000..59ccf0eef43
--- /dev/null
+++ b/src/com/android/settings/derp/livedisplay/LiveDisplaySettings.java
@@ -0,0 +1,650 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod Project
+ * 2017-2021 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.derp.livedisplay;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.display.ColorDisplayManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import androidx.fragment.app.DialogFragment;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.SwitchPreferenceCompat;
+import androidx.viewpager.widget.PagerAdapter;
+import androidx.viewpager.widget.ViewPager;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.ArrayUtils;
+
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.search.SearchIndexable;
+import com.android.settings.derp.utils.ResourceUtils;
+import com.android.settingslib.widget.LayoutPreference;
+
+import java.util.Collections;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+import com.android.internal.derp.hardware.LineageHardwareManager;
+import com.android.internal.derp.hardware.DisplayMode;
+import com.android.internal.derp.hardware.LiveDisplayConfig;
+import com.android.internal.derp.hardware.LiveDisplayManager;
+import com.android.internal.derp.preference.SettingsHelper;
+import com.android.settings.derp.preference.CustomDialogPreference;
+
+import static com.android.internal.derp.hardware.LiveDisplayManager.FEATURE_ANTI_FLICKER;
+import static com.android.internal.derp.hardware.LiveDisplayManager.FEATURE_CABC;
+import static com.android.internal.derp.hardware.LiveDisplayManager.FEATURE_COLOR_ADJUSTMENT;
+import static com.android.internal.derp.hardware.LiveDisplayManager.FEATURE_COLOR_ENHANCEMENT;
+import static com.android.internal.derp.hardware.LiveDisplayManager.FEATURE_DISPLAY_MODES;
+import static com.android.internal.derp.hardware.LiveDisplayManager.FEATURE_PICTURE_ADJUSTMENT;
+import static com.android.internal.derp.hardware.LiveDisplayManager.FEATURE_READING_ENHANCEMENT;
+import static com.android.internal.derp.hardware.LiveDisplayManager.MODE_AUTO;
+import static com.android.internal.derp.hardware.LiveDisplayManager.MODE_DAY;
+import static com.android.internal.derp.hardware.LiveDisplayManager.MODE_NIGHT;
+import static com.android.internal.derp.hardware.LiveDisplayManager.MODE_OFF;
+import static com.android.internal.derp.hardware.LiveDisplayManager.MODE_OUTDOOR;
+
+@SearchIndexable
+public class LiveDisplaySettings extends SettingsPreferenceFragment implements
+ Preference.OnPreferenceChangeListener, SettingsHelper.OnSettingsChangeListener {
+
+ private static final String TAG = "LiveDisplay";
+
+ private static final String KEY_SCREEN_LIVE_DISPLAY = "livedisplay";
+
+ private static final String KEY_CATEGORY_ADVANCED = "advanced";
+
+ private static final String KEY_LIVE_DISPLAY = "live_display";
+ private static final String KEY_LIVE_DISPLAY_ANTI_FLICKER = "display_anti_flicker";
+ private static final String KEY_LIVE_DISPLAY_AUTO_OUTDOOR_MODE =
+ "display_auto_outdoor_mode";
+ private static final String KEY_LIVE_DISPLAY_READING_ENHANCEMENT = "display_reading_mode";
+ private static final String KEY_LIVE_DISPLAY_LOW_POWER = "display_low_power";
+ private static final String KEY_LIVE_DISPLAY_COLOR_ENHANCE = "display_color_enhance";
+ private static final String KEY_LIVE_DISPLAY_TEMPERATURE = "live_display_color_temperature";
+
+ private static final String KEY_DISPLAY_COLOR = "color_calibration";
+ private static final String KEY_PICTURE_ADJUSTMENT = "picture_adjustment";
+
+ private static final String KEY_LIVE_DISPLAY_COLOR_PROFILE = "live_display_color_profile";
+
+ private static final String KEY_LIVE_DISPLAY_PREVIEW = "live_display_preview";
+
+ private static final String COLOR_PROFILE_TITLE =
+ KEY_LIVE_DISPLAY_COLOR_PROFILE + "_%s_title";
+
+ private static final String COLOR_PROFILE_SUMMARY =
+ KEY_LIVE_DISPLAY_COLOR_PROFILE + "_%s_summary";
+
+ private final Uri DISPLAY_TEMPERATURE_DAY_URI =
+ Settings.System.getUriFor(Settings.System.DISPLAY_TEMPERATURE_DAY);
+ private final Uri DISPLAY_TEMPERATURE_NIGHT_URI =
+ Settings.System.getUriFor(Settings.System.DISPLAY_TEMPERATURE_NIGHT);
+ private final Uri DISPLAY_TEMPERATURE_MODE_URI =
+ Settings.System.getUriFor(Settings.System.DISPLAY_TEMPERATURE_MODE);
+
+ private ListPreference mLiveDisplay;
+
+ private SwitchPreferenceCompat mAntiFlicker;
+ private SwitchPreferenceCompat mColorEnhancement;
+ private SwitchPreferenceCompat mLowPower;
+ private SwitchPreferenceCompat mOutdoorMode;
+ private SwitchPreferenceCompat mReadingMode;
+
+ private PictureAdjustment mPictureAdjustment;
+ private DisplayTemperature mDisplayTemperature;
+ private DisplayColor mDisplayColor;
+
+ private ListPreference mColorProfile;
+ private String[] mColorProfileSummaries;
+
+ private String[] mModeEntries;
+ private String[] mModeValues;
+ private String[] mModeSummaries;
+
+ private boolean mHasDisplayModes = false;
+
+ private LayoutPreference mPreview;
+ private View mViewArrowPrevious;
+ private View mViewArrowNext;
+ private ViewPager mViewPager;
+
+ private ArrayList mPageList;
+
+ private ImageView[] mDotIndicators;
+ private View[] mViewPagerImages;
+ private static final int DOT_INDICATOR_SIZE = 12;
+ private static final int DOT_INDICATOR_LEFT_PADDING = 6;
+ private static final int DOT_INDICATOR_RIGHT_PADDING = 6;
+
+ private LiveDisplayManager mLiveDisplayManager;
+ private LiveDisplayConfig mConfig;
+
+ private LineageHardwareManager mHardware;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ final Resources res = getResources();
+ final boolean isNightDisplayAvailable =
+ ColorDisplayManager.isNightDisplayAvailable(getContext());
+
+ mHardware = LineageHardwareManager.getInstance(getActivity());
+ mLiveDisplayManager = getActivity().getSystemService(LiveDisplayManager.class);
+ mConfig = mLiveDisplayManager.getConfig();
+
+ addPreferencesFromResource(R.xml.livedisplay);
+
+ PreferenceScreen liveDisplayPrefs = findPreference(KEY_SCREEN_LIVE_DISPLAY);
+
+ PreferenceCategory advancedPrefs = findPreference(KEY_CATEGORY_ADVANCED);
+
+ int adaptiveMode = mLiveDisplayManager.getMode();
+
+ mLiveDisplay = findPreference(KEY_LIVE_DISPLAY);
+ mLiveDisplay.setValue(String.valueOf(adaptiveMode));
+
+ mModeEntries = res.getStringArray(
+ com.android.internal.R.array.live_display_entries);
+ mModeValues = res.getStringArray(
+ com.android.internal.R.array.live_display_values);
+ mModeSummaries = res.getStringArray(
+ com.android.internal.R.array.live_display_summaries);
+
+ int[] removeIdx = null;
+ // Remove outdoor mode from lists if there is no support
+ if (!mConfig.hasFeature(MODE_OUTDOOR)) {
+ removeIdx = ArrayUtils.appendInt(removeIdx,
+ ArrayUtils.indexOf(mModeValues, String.valueOf(MODE_OUTDOOR)));
+ } else if (isNightDisplayAvailable) {
+ final int autoIdx = ArrayUtils.indexOf(mModeValues, String.valueOf(MODE_AUTO));
+ mModeSummaries[autoIdx] = res.getString(R.string.live_display_outdoor_mode_summary);
+ }
+
+ // Remove night display on HWC2
+ if (isNightDisplayAvailable) {
+ removeIdx = ArrayUtils.appendInt(removeIdx,
+ ArrayUtils.indexOf(mModeValues, String.valueOf(MODE_DAY)));
+ removeIdx = ArrayUtils.appendInt(removeIdx,
+ ArrayUtils.indexOf(mModeValues, String.valueOf(MODE_NIGHT)));
+ }
+
+ if (removeIdx != null) {
+ String[] entriesTemp = new String[mModeEntries.length - removeIdx.length];
+ String[] valuesTemp = new String[mModeValues.length - removeIdx.length];
+ String[] summariesTemp = new String[mModeSummaries.length - removeIdx.length];
+ int j = 0;
+ for (int i = 0; i < mModeEntries.length; i++) {
+ if (ArrayUtils.contains(removeIdx, i)) {
+ continue;
+ }
+ entriesTemp[j] = mModeEntries[i];
+ valuesTemp[j] = mModeValues[i];
+ summariesTemp[j] = mModeSummaries[i];
+ j++;
+ }
+ mModeEntries = entriesTemp;
+ mModeValues = valuesTemp;
+ mModeSummaries = summariesTemp;
+ }
+
+ mLiveDisplay.setEntries(mModeEntries);
+ mLiveDisplay.setEntryValues(mModeValues);
+ mLiveDisplay.setOnPreferenceChangeListener(this);
+
+ mPreview = findPreference(KEY_LIVE_DISPLAY_PREVIEW);
+ addViewPager(mPreview);
+
+ mDisplayTemperature = findPreference(KEY_LIVE_DISPLAY_TEMPERATURE);
+ if (isNightDisplayAvailable) {
+ if (!mConfig.hasFeature(MODE_OUTDOOR)) {
+ liveDisplayPrefs.removePreference(mLiveDisplay);
+ }
+ liveDisplayPrefs.removePreference(mDisplayTemperature);
+ }
+
+ mColorProfile = findPreference(KEY_LIVE_DISPLAY_COLOR_PROFILE);
+ if (liveDisplayPrefs != null && mColorProfile != null
+ && (!mConfig.hasFeature(FEATURE_DISPLAY_MODES) || !updateDisplayModes())) {
+ liveDisplayPrefs.removePreference(mColorProfile);
+ } else {
+ mHasDisplayModes = true;
+ mColorProfile.setOnPreferenceChangeListener(this);
+ }
+
+ mOutdoorMode = findPreference(KEY_LIVE_DISPLAY_AUTO_OUTDOOR_MODE);
+ if (liveDisplayPrefs != null && mOutdoorMode != null
+ // MODE_AUTO implies automatic outdoor mode on HWC2
+ && (isNightDisplayAvailable || !mConfig.hasFeature(MODE_OUTDOOR))) {
+ liveDisplayPrefs.removePreference(mOutdoorMode);
+ mOutdoorMode = null;
+ }
+
+ mReadingMode = findPreference(KEY_LIVE_DISPLAY_READING_ENHANCEMENT);
+ if (liveDisplayPrefs != null && mReadingMode != null &&
+ !mHardware.isSupported(LineageHardwareManager.FEATURE_READING_ENHANCEMENT)) {
+ liveDisplayPrefs.removePreference(mReadingMode);
+ mReadingMode = null;
+ } else {
+ mReadingMode.setOnPreferenceChangeListener(this);
+ }
+
+ mLowPower = findPreference(KEY_LIVE_DISPLAY_LOW_POWER);
+ if (advancedPrefs != null && mLowPower != null
+ && !mConfig.hasFeature(FEATURE_CABC)) {
+ advancedPrefs.removePreference(mLowPower);
+ mLowPower = null;
+ }
+
+ mColorEnhancement = findPreference(KEY_LIVE_DISPLAY_COLOR_ENHANCE);
+ if (advancedPrefs != null && mColorEnhancement != null
+ && !mConfig.hasFeature(FEATURE_COLOR_ENHANCEMENT)) {
+ advancedPrefs.removePreference(mColorEnhancement);
+ mColorEnhancement = null;
+ }
+
+ mPictureAdjustment = findPreference(KEY_PICTURE_ADJUSTMENT);
+ if (advancedPrefs != null && mPictureAdjustment != null &&
+ !mConfig.hasFeature(FEATURE_PICTURE_ADJUSTMENT)) {
+ advancedPrefs.removePreference(mPictureAdjustment);
+ mPictureAdjustment = null;
+ }
+
+ mDisplayColor = findPreference(KEY_DISPLAY_COLOR);
+ if (advancedPrefs != null && mDisplayColor != null &&
+ !mConfig.hasFeature(FEATURE_COLOR_ADJUSTMENT)) {
+ advancedPrefs.removePreference(mDisplayColor);
+ mDisplayColor = null;
+ }
+
+ mAntiFlicker = findPreference(KEY_LIVE_DISPLAY_ANTI_FLICKER);
+ if (liveDisplayPrefs != null && mAntiFlicker != null &&
+ !mHardware.isSupported(LineageHardwareManager.FEATURE_ANTI_FLICKER)) {
+ liveDisplayPrefs.removePreference(mAntiFlicker);
+ mAntiFlicker = null;
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ updateModeSummary();
+ updateTemperatureSummary();
+ updateColorProfileSummary(null);
+ updateReadingModeStatus();
+ SettingsHelper.get(getActivity()).startWatching(this, DISPLAY_TEMPERATURE_DAY_URI,
+ DISPLAY_TEMPERATURE_MODE_URI, DISPLAY_TEMPERATURE_NIGHT_URI);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ SettingsHelper.get(getActivity()).stopWatching(this);
+ }
+
+ private ArrayList getViewPagerResource() {
+ return new ArrayList(
+ Arrays.asList(
+ R.layout.color_mode_view1,
+ R.layout.color_mode_view2,
+ R.layout.color_mode_view3));
+ }
+
+ private void addViewPager(LayoutPreference preview) {
+ final ArrayList tmpviewPagerList = getViewPagerResource();
+ mViewPager = preview.findViewById(R.id.viewpager);
+
+ mViewPagerImages = new View[3];
+ for (int idx = 0; idx < tmpviewPagerList.size(); idx++) {
+ mViewPagerImages[idx] =
+ getLayoutInflater().inflate(tmpviewPagerList.get(idx), null /* root */);
+ }
+
+ mPageList = new ArrayList();
+ mPageList.add(mViewPagerImages[0]);
+ mPageList.add(mViewPagerImages[1]);
+ mPageList.add(mViewPagerImages[2]);
+
+ mViewPager.setAdapter(new ColorPagerAdapter(mPageList));
+
+ mViewArrowPrevious = preview.findViewById(R.id.arrow_previous);
+ mViewArrowPrevious.setOnClickListener(v -> {
+ final int previousPos = mViewPager.getCurrentItem() - 1;
+ mViewPager.setCurrentItem(previousPos, true);
+ });
+
+ mViewArrowNext = preview.findViewById(R.id.arrow_next);
+ mViewArrowNext.setOnClickListener(v -> {
+ final int nextPos = mViewPager.getCurrentItem() + 1;
+ mViewPager.setCurrentItem(nextPos, true);
+ });
+
+ mViewPager.addOnPageChangeListener(createPageListener());
+
+ final ViewGroup viewGroup = (ViewGroup) preview.findViewById(R.id.viewGroup);
+ mDotIndicators = new ImageView[mPageList.size()];
+ for (int i = 0; i < mPageList.size(); i++) {
+ final ImageView imageView = new ImageView(getContext());
+ final ViewGroup.MarginLayoutParams lp =
+ new ViewGroup.MarginLayoutParams(DOT_INDICATOR_SIZE, DOT_INDICATOR_SIZE);
+ lp.setMargins(DOT_INDICATOR_LEFT_PADDING, 0, DOT_INDICATOR_RIGHT_PADDING, 0);
+ imageView.setLayoutParams(lp);
+ mDotIndicators[i] = imageView;
+
+ viewGroup.addView(mDotIndicators[i]);
+ }
+
+ updateIndicator(mViewPager.getCurrentItem());
+ }
+
+ private ViewPager.OnPageChangeListener createPageListener() {
+ return new ViewPager.OnPageChangeListener() {
+ @Override
+ public void onPageScrolled(
+ int position, float positionOffset, int positionOffsetPixels) {
+ if (positionOffset != 0) {
+ for (int idx = 0; idx < mPageList.size(); idx++) {
+ mViewPagerImages[idx].setVisibility(View.VISIBLE);
+ }
+ } else {
+ mViewPagerImages[position].setContentDescription(
+ getContext().getString(R.string.colors_viewpager_content_description));
+ updateIndicator(position);
+ }
+ }
+
+ @Override
+ public void onPageSelected(int position) {}
+
+ @Override
+ public void onPageScrollStateChanged(int state) {}
+ };
+ }
+
+ private void updateIndicator(int position) {
+ for (int i = 0; i < mPageList.size(); i++) {
+ if (position == i) {
+ mDotIndicators[i].setBackgroundResource(
+ R.drawable.ic_color_page_indicator_focused);
+
+ mViewPagerImages[i].setVisibility(View.VISIBLE);
+ } else {
+ mDotIndicators[i].setBackgroundResource(
+ R.drawable.ic_color_page_indicator_unfocused);
+
+ mViewPagerImages[i].setVisibility(View.INVISIBLE);
+ }
+ }
+
+ if (position == 0) {
+ mViewArrowPrevious.setVisibility(View.INVISIBLE);
+ mViewArrowNext.setVisibility(View.VISIBLE);
+ } else if (position == (mPageList.size() - 1)) {
+ mViewArrowPrevious.setVisibility(View.VISIBLE);
+ mViewArrowNext.setVisibility(View.INVISIBLE);
+ } else {
+ mViewArrowPrevious.setVisibility(View.VISIBLE);
+ mViewArrowNext.setVisibility(View.VISIBLE);
+ }
+ }
+
+ static class ColorPagerAdapter extends PagerAdapter {
+ private final ArrayList mPageViewList;
+
+ ColorPagerAdapter(ArrayList pageViewList) {
+ mPageViewList = pageViewList;
+ }
+
+ @Override
+ public void destroyItem(ViewGroup container, int position, Object object) {
+ if (mPageViewList.get(position) != null) {
+ container.removeView(mPageViewList.get(position));
+ }
+ }
+
+ @Override
+ public Object instantiateItem(ViewGroup container, int position) {
+ container.addView(mPageViewList.get(position));
+ return mPageViewList.get(position);
+ }
+
+ @Override
+ public int getCount() {
+ return mPageViewList.size();
+ }
+
+ @Override
+ public boolean isViewFromObject(View view, Object object) {
+ return object == view;
+ }
+ }
+
+ private boolean updateDisplayModes() {
+ final DisplayMode[] modes = mHardware.getDisplayModes();
+ if (modes == null || modes.length == 0) {
+ return false;
+ }
+
+ final DisplayMode cur = mHardware.getCurrentDisplayMode() != null
+ ? mHardware.getCurrentDisplayMode() : mHardware.getDefaultDisplayMode();
+ int curId = -1;
+ String[] entries = new String[modes.length];
+ String[] values = new String[modes.length];
+ mColorProfileSummaries = new String[modes.length];
+ for (int i = 0; i < modes.length; i++) {
+ values[i] = String.valueOf(modes[i].id);
+ entries[i] = ResourceUtils.getLocalizedString(
+ getResources(), modes[i].name, COLOR_PROFILE_TITLE);
+
+ // Populate summary
+ String summary = ResourceUtils.getLocalizedString(
+ getResources(), modes[i].name, COLOR_PROFILE_SUMMARY);
+ if (summary != null) {
+ summary = String.format("%s - %s", entries[i], summary);
+ }
+ mColorProfileSummaries[i] = summary;
+
+ if (cur != null && modes[i].id == cur.id) {
+ curId = cur.id;
+ }
+ }
+ mColorProfile.setEntries(entries);
+ mColorProfile.setEntryValues(values);
+ if (curId >= 0) {
+ mColorProfile.setValue(String.valueOf(curId));
+ }
+
+ return true;
+ }
+
+ private void updateColorProfileSummary(String value) {
+ if (!mHasDisplayModes) {
+ return;
+ }
+
+ if (value == null) {
+ DisplayMode cur = mHardware.getCurrentDisplayMode() != null
+ ? mHardware.getCurrentDisplayMode() : mHardware.getDefaultDisplayMode();
+ if (cur != null && cur.id >= 0) {
+ value = String.valueOf(cur.id);
+ }
+ }
+
+ int idx = mColorProfile.findIndexOfValue(value);
+ if (idx < 0) {
+ Log.e(TAG, "No summary resource found for profile " + value);
+ mColorProfile.setSummary(null);
+ return;
+ }
+
+ mColorProfile.setValue(value);
+ mColorProfile.setSummary(mColorProfileSummaries[idx]);
+ }
+
+ private void updateModeSummary() {
+ int mode = mLiveDisplayManager.getMode();
+
+ int index = ArrayUtils.indexOf(mModeValues, String.valueOf(mode));
+ if (index < 0) {
+ index = ArrayUtils.indexOf(mModeValues, String.valueOf(MODE_OFF));
+ }
+
+ mLiveDisplay.setSummary(mModeSummaries[index]);
+ mLiveDisplay.setValue(String.valueOf(mode));
+
+ if (mDisplayTemperature != null) {
+ mDisplayTemperature.setEnabled(mode != MODE_OFF);
+ }
+ if (mOutdoorMode != null) {
+ mOutdoorMode.setEnabled(mode != MODE_OFF);
+ }
+ }
+
+ private void updateTemperatureSummary() {
+ int day = mLiveDisplayManager.getDayColorTemperature();
+ int night = mLiveDisplayManager.getNightColorTemperature();
+
+ mDisplayTemperature.setSummary(getResources().getString(
+ R.string.live_display_color_temperature_summary,
+ mDisplayTemperature.roundUp(day),
+ mDisplayTemperature.roundUp(night)));
+ }
+
+ private void updateReadingModeStatus() {
+ if (mReadingMode != null) {
+ mReadingMode.setChecked(
+ mHardware.get(LineageHardwareManager.FEATURE_READING_ENHANCEMENT));
+ }
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object objValue) {
+ if (preference == mLiveDisplay) {
+ mLiveDisplayManager.setMode(Integer.valueOf((String)objValue));
+ } else if (preference == mColorProfile) {
+ int id = Integer.valueOf((String)objValue);
+ Log.i("LiveDisplay", "Setting mode: " + id);
+ for (DisplayMode mode : mHardware.getDisplayModes()) {
+ if (mode.id == id) {
+ mHardware.setDisplayMode(mode, true);
+ updateColorProfileSummary((String)objValue);
+ break;
+ }
+ }
+ } else if (preference == mReadingMode) {
+ mHardware.set(LineageHardwareManager.FEATURE_READING_ENHANCEMENT, (Boolean) objValue);
+ }
+ return true;
+ }
+
+ @Override
+ public void onSettingsChanged(Uri uri) {
+ updateModeSummary();
+ updateTemperatureSummary();
+ updateReadingModeStatus();
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsEvent.DERP;
+ }
+
+ @Override
+ public void onDisplayPreferenceDialog(Preference preference) {
+ if (preference.getKey() == null) {
+ // Auto-key preferences that don't have a key, so the dialog can find them.
+ preference.setKey(UUID.randomUUID().toString());
+ }
+ DialogFragment f = null;
+ if (preference instanceof CustomDialogPreference) {
+ f = CustomDialogPreference.CustomPreferenceDialogFragment
+ .newInstance(preference.getKey());
+ } else {
+ super.onDisplayPreferenceDialog(preference);
+ return;
+ }
+ f.setTargetFragment(this, 0);
+ f.show(getFragmentManager(), "dialog_preference");
+ onDialogShowing();
+ }
+
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider(R.xml.livedisplay) {
+
+ @Override
+ public List getNonIndexableKeys(Context context) {
+ final LiveDisplayConfig config = context.getSystemService(LiveDisplayManager.class).getConfig();
+ final List result = super.getNonIndexableKeys(context);
+
+ if (!config.hasFeature(FEATURE_DISPLAY_MODES)) {
+ result.add(KEY_LIVE_DISPLAY_COLOR_PROFILE);
+ }
+ if (!config.hasFeature(MODE_OUTDOOR)) {
+ result.add(KEY_LIVE_DISPLAY_AUTO_OUTDOOR_MODE);
+ }
+ if (!config.hasFeature(FEATURE_COLOR_ENHANCEMENT)) {
+ result.add(KEY_LIVE_DISPLAY_COLOR_ENHANCE);
+ }
+ if (!config.hasFeature(FEATURE_CABC)) {
+ result.add(KEY_LIVE_DISPLAY_LOW_POWER);
+ }
+ if (!config.hasFeature(FEATURE_COLOR_ADJUSTMENT)) {
+ result.add(KEY_DISPLAY_COLOR);
+ }
+ if (!config.hasFeature(FEATURE_PICTURE_ADJUSTMENT)) {
+ result.add(KEY_PICTURE_ADJUSTMENT);
+ }
+ if (!config.hasFeature(FEATURE_READING_ENHANCEMENT)) {
+ result.add(KEY_LIVE_DISPLAY_READING_ENHANCEMENT);
+ }
+ if (ColorDisplayManager.isNightDisplayAvailable(context)) {
+ if (!config.hasFeature(MODE_OUTDOOR)) {
+ result.add(KEY_LIVE_DISPLAY);
+ }
+ result.add(KEY_LIVE_DISPLAY_TEMPERATURE);
+ }
+ if (!context.getResources().getBoolean(
+ com.android.internal.R.bool.config_enableLiveDisplay)) {
+ result.add(KEY_LIVE_DISPLAY_TEMPERATURE);
+ result.add(KEY_LIVE_DISPLAY);
+ }
+ if (!config.hasFeature(FEATURE_ANTI_FLICKER)) {
+ result.add(KEY_LIVE_DISPLAY_ANTI_FLICKER);
+ }
+
+ return result;
+ }
+ };
+}
diff --git a/src/com/android/settings/derp/livedisplay/PictureAdjustment.java b/src/com/android/settings/derp/livedisplay/PictureAdjustment.java
new file mode 100644
index 00000000000..6eac238c8e6
--- /dev/null
+++ b/src/com/android/settings/derp/livedisplay/PictureAdjustment.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ * 2021-2022 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.derp.livedisplay;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Range;
+import android.view.View;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import androidx.appcompat.app.AlertDialog;
+
+import com.android.settings.derp.preference.CustomDialogPreference;
+import com.android.settings.derp.widget.IntervalSeekBar;
+import com.android.settings.R;
+
+import java.util.List;
+
+import com.android.internal.derp.hardware.HSIC;
+import com.android.internal.derp.hardware.LiveDisplayManager;
+
+/**
+ * Special preference type that allows configuration of Color settings
+ */
+public class PictureAdjustment extends CustomDialogPreference {
+ private static final String TAG = "PictureAdjustment";
+
+ private final Context mContext;
+ private final LiveDisplayManager mLiveDisplay;
+ private final List> mRanges;
+
+ // These arrays must all match in length and order
+ private static final int[] SEEKBAR_ID = new int[] {
+ R.id.adj_hue_seekbar,
+ R.id.adj_saturation_seekbar,
+ R.id.adj_intensity_seekbar,
+ R.id.adj_contrast_seekbar
+ };
+
+ private static final int[] SEEKBAR_VALUE_ID = new int[] {
+ R.id.adj_hue_value,
+ R.id.adj_saturation_value,
+ R.id.adj_intensity_value,
+ R.id.adj_contrast_value
+ };
+
+ private ColorSeekBar[] mSeekBars = new ColorSeekBar[SEEKBAR_ID.length];
+
+ private final float[] mCurrentAdj = new float[5];
+ private final float[] mOriginalAdj = new float[5];
+
+ public PictureAdjustment(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ mContext = context;
+ mLiveDisplay = mContext.getSystemService(LiveDisplayManager.class);
+ mRanges = mLiveDisplay.getConfig().getPictureAdjustmentRanges();
+
+ setDialogLayoutResource(R.layout.display_picture_adjustment);
+ }
+
+ @Override
+ protected void onPrepareDialogBuilder(AlertDialog.Builder builder,
+ DialogInterface.OnClickListener listener) {
+ super.onPrepareDialogBuilder(builder, listener);
+
+ builder.setNeutralButton(R.string.reset, null);
+ builder.setNegativeButton(R.string.cancel, null);
+ builder.setPositiveButton(R.string.dlg_ok, null);
+ }
+
+ private void updateBars() {
+ for (int i = 0; i < SEEKBAR_ID.length; i++) {
+ mSeekBars[i].setValue(mCurrentAdj[i]);
+ }
+ }
+
+ @Override
+ protected void onBindDialogView(View view) {
+ super.onBindDialogView(view);
+
+ System.arraycopy(mLiveDisplay.getPictureAdjustment().toFloatArray(), 0, mOriginalAdj, 0, 5);
+ System.arraycopy(mOriginalAdj, 0, mCurrentAdj, 0, 5);
+
+ for (int i = 0; i < SEEKBAR_ID.length; i++) {
+ IntervalSeekBar seekBar = (IntervalSeekBar) view.findViewById(SEEKBAR_ID[i]);
+ TextView value = (TextView) view.findViewById(SEEKBAR_VALUE_ID[i]);
+ final Range range = mRanges.get(i);
+ mSeekBars[i] = new ColorSeekBar(seekBar, range, value, i);
+ }
+ updateBars();
+ }
+
+ @Override
+ protected boolean onDismissDialog(AlertDialog dialog, int which) {
+ // Can't use onPrepareDialogBuilder for this as we want the dialog
+ // to be kept open on click
+ if (which == DialogInterface.BUTTON_NEUTRAL) {
+ System.arraycopy(mLiveDisplay.getDefaultPictureAdjustment().toFloatArray(),
+ 0, mCurrentAdj, 0, 5);
+ updateBars();
+ updateAdjustment(mCurrentAdj);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected void onDialogClosed(boolean positiveResult) {
+ super.onDialogClosed(positiveResult);
+ updateAdjustment(positiveResult ? mCurrentAdj : mOriginalAdj);
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ final Parcelable superState = super.onSaveInstanceState();
+ if (getDialog() == null || !getDialog().isShowing()) {
+ return superState;
+ }
+
+ // Save the dialog state
+ final SavedState myState = new SavedState(superState);
+ myState.currentAdj = mCurrentAdj;
+ myState.originalAdj = mOriginalAdj;
+
+ // Restore the old state when the activity or dialog is being paused
+ updateAdjustment(mOriginalAdj);
+
+ return myState;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ if (state == null || !state.getClass().equals(SavedState.class)) {
+ // Didn't save state for us in onSaveInstanceState
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
+ SavedState myState = (SavedState) state;
+ super.onRestoreInstanceState(myState.getSuperState());
+
+ System.arraycopy(myState.originalAdj, 0, mOriginalAdj, 0, 5);
+ System.arraycopy(myState.currentAdj, 0, mCurrentAdj, 0, 5);
+
+ updateBars();
+ updateAdjustment(mCurrentAdj);
+ }
+
+ private static class SavedState extends BaseSavedState {
+ float[] originalAdj;
+ float[] currentAdj;
+
+ public SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ public SavedState(Parcel source) {
+ super(source);
+ originalAdj = source.createFloatArray();
+ currentAdj = source.createFloatArray();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeFloatArray(originalAdj);
+ dest.writeFloatArray(currentAdj);
+ }
+
+ public static final Creator CREATOR =
+ new Creator() {
+
+ public PictureAdjustment.SavedState createFromParcel(Parcel in) {
+ return new PictureAdjustment.SavedState(in);
+ }
+
+ public PictureAdjustment.SavedState[] newArray(int size) {
+ return new PictureAdjustment.SavedState[size];
+ }
+ };
+ }
+
+ private void updateAdjustment(final float[] adjustment) {
+ mLiveDisplay.setPictureAdjustment(HSIC.fromFloatArray(adjustment));
+ }
+
+ private class ColorSeekBar implements SeekBar.OnSeekBarChangeListener {
+ private int mIndex;
+ private final IntervalSeekBar mSeekBar;
+ private TextView mValue;
+ private Range mRange;
+
+ public ColorSeekBar(IntervalSeekBar seekBar, Range range, TextView value, int index) {
+ mSeekBar = seekBar;
+ mValue = value;
+ mIndex = index;
+ mRange = range;
+ mSeekBar.setMinimum(range.getLower());
+ mSeekBar.setMaximum(range.getUpper());
+
+ mSeekBar.setOnSeekBarChangeListener(this);
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ IntervalSeekBar isb = (IntervalSeekBar)seekBar;
+ float fp = isb.getProgressFloat();
+ if (fromUser) {
+ mCurrentAdj[mIndex] = mRanges.get(mIndex).clamp(fp);
+ updateAdjustment(mCurrentAdj);
+ }
+ mValue.setText(getLabel(mCurrentAdj[mIndex]));
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ // Do nothing here
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ // Do nothing here
+ }
+
+ private String getLabel(float value) {
+ if (mRange.getUpper() == 1.0f) {
+ return String.format("%d%%", Math.round(100F * value));
+ }
+ return String.format("%d", Math.round(value));
+ }
+
+ public void setValue(float value) {
+ mSeekBar.setProgressFloat(value);
+ mValue.setText(getLabel(value));
+ }
+ }
+}
diff --git a/src/com/android/settings/derp/notificationlight/AlphaPatternDrawable.java b/src/com/android/settings/derp/notificationlight/AlphaPatternDrawable.java
new file mode 100644
index 00000000000..39f358e7d51
--- /dev/null
+++ b/src/com/android/settings/derp/notificationlight/AlphaPatternDrawable.java
@@ -0,0 +1,115 @@
+/*
+ * SPDX-FileCopyrightText: 2010 Daniel Nilsson
+ * SPDX-FileCopyrightText: 2012 The CyanogenMod Project
+ * SPDX-FileCopyrightText: 2022 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.android.settings.derp.notificationlight;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Bitmap.Config;
+import android.graphics.drawable.Drawable;
+
+/**
+ * This drawable that draws a simple white and gray chess board pattern. It's
+ * pattern you will often see as a background behind a partly transparent image
+ * in many applications.
+ *
+ * @author Daniel Nilsson
+ */
+public class AlphaPatternDrawable extends Drawable {
+
+ private final int mRectangleSize;
+
+ private final Paint mPaint = new Paint();
+ private final Paint mPaintWhite = new Paint();
+ private final Paint mPaintGray = new Paint();
+
+ private int numRectanglesHorizontal;
+ private int numRectanglesVertical;
+
+ /**
+ * Bitmap in which the pattern will be cached.
+ */
+ private Bitmap mBitmap;
+
+ public AlphaPatternDrawable(int rectangleSize) {
+ mRectangleSize = rectangleSize;
+ mPaintWhite.setColor(0xffffffff);
+ mPaintGray.setColor(0xffcbcbcb);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ if (mBitmap != null) {
+ canvas.drawBitmap(mBitmap, null, getBounds(), mPaint);
+ }
+ }
+
+ @Override
+ public int getOpacity() {
+ return 0;
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ throw new UnsupportedOperationException("Alpha is not supported by this drawable.");
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter cf) {
+ throw new UnsupportedOperationException("ColorFilter is not supported by this drawable.");
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ super.onBoundsChange(bounds);
+
+ int height = bounds.height();
+ int width = bounds.width();
+
+ numRectanglesHorizontal = (int) Math.ceil((float) width / mRectangleSize);
+ numRectanglesVertical = (int) Math.ceil((float) height / mRectangleSize);
+
+ generatePatternBitmap();
+ }
+
+ /**
+ * This will generate a bitmap with the pattern as big as the rectangle we
+ * were allow to draw on. We do this to cache the bitmap so we don't need
+ * to recreate it each time draw() is called since it takes a few
+ * milliseconds.
+ */
+ private void generatePatternBitmap() {
+
+ if (getBounds().width() <= 0 || getBounds().height() <= 0) {
+ return;
+ }
+
+ mBitmap = Bitmap.createBitmap(getBounds().width(), getBounds().height(), Config.ARGB_8888);
+ Canvas canvas = new Canvas(mBitmap);
+
+ Rect r = new Rect();
+ boolean verticalStartWhite = true;
+ for (int i = 0; i <= numRectanglesVertical; i++) {
+ boolean isWhite = verticalStartWhite;
+ for (int j = 0; j <= numRectanglesHorizontal; j++) {
+ r.top = i * mRectangleSize;
+ r.left = j * mRectangleSize;
+ r.bottom = r.top + mRectangleSize;
+ r.right = r.left + mRectangleSize;
+
+ canvas.drawRect(r, isWhite ? mPaintWhite : mPaintGray);
+
+ isWhite = !isWhite;
+ }
+
+ verticalStartWhite = !verticalStartWhite;
+ }
+ }
+}
diff --git a/src/com/android/settings/derp/notificationlight/ApplicationLightPreference.java b/src/com/android/settings/derp/notificationlight/ApplicationLightPreference.java
new file mode 100644
index 00000000000..e56ac5d7a63
--- /dev/null
+++ b/src/com/android/settings/derp/notificationlight/ApplicationLightPreference.java
@@ -0,0 +1,304 @@
+/*
+ * SPDX-FileCopyrightText: 2012 The CyanogenMod Project
+ * SPDX-FileCopyrightText: 2017-2024 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.android.settings.derp.notificationlight;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.OvalShape;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.internal.derp.notification.LightsCapabilities;
+
+import com.android.settings.R;
+import com.android.settings.derp.preference.CustomDialogPreference;
+
+public class ApplicationLightPreference extends CustomDialogPreference
+ implements View.OnLongClickListener {
+
+ private static final String TAG = "AppLightPreference";
+ public static final int DEFAULT_TIME = 1000;
+ public static final int DEFAULT_COLOR = 0xffffff;
+
+ private ImageView mLightColorView;
+ private TextView mOnValueView;
+ private TextView mOffValueView;
+
+ private int mColorValue;
+ private int mOnValue;
+ private int mOffValue;
+ private boolean mOnOffChangeable;
+
+ private boolean mHasDefaults;
+ private int mDefaultColorValue;
+ private int mDefaultOnValue;
+ private int mDefaultOffValue;
+
+ private int mLedBrightness;
+
+ private LightSettingsDialog mDialog;
+
+ public interface ItemLongClickListener {
+ boolean onItemLongClick(String key);
+ }
+
+ private ItemLongClickListener mLongClickListener;
+
+ public ApplicationLightPreference(Context context, AttributeSet attrs) {
+ this(context, attrs, DEFAULT_COLOR, DEFAULT_TIME, DEFAULT_TIME);
+ }
+
+ public ApplicationLightPreference(Context context, AttributeSet attrs,
+ int color, int onValue, int offValue) {
+ this(context, attrs, color, onValue, offValue,
+ LightsCapabilities.supports(context, LightsCapabilities.LIGHTS_PULSATING_LED));
+ }
+
+ public ApplicationLightPreference(Context context, AttributeSet attrs,
+ int color, int onValue, int offValue,
+ boolean onOffChangeable) {
+ super(context, attrs);
+ mColorValue = color;
+ mOnValue = onValue;
+ mOffValue = offValue;
+ mOnOffChangeable = onOffChangeable;
+ mHasDefaults = false;
+ mLedBrightness = 0; // use system brightness
+
+ setWidgetLayoutResource(R.layout.preference_application_light);
+ }
+
+ @Override
+ public boolean onLongClick(View view) {
+ if (mLongClickListener != null) {
+ return mLongClickListener.onItemLongClick(getKey());
+ }
+ return false;
+ }
+
+ public void setOnLongClickListener(ItemLongClickListener l) {
+ mLongClickListener = l;
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+
+ mLightColorView = (ImageView) holder.findViewById(R.id.light_color);
+ mOnValueView = (TextView) holder.findViewById(R.id.textViewTimeOnValue);
+ mOffValueView = (TextView) holder.findViewById(R.id.textViewTimeOffValue);
+
+ // Hide the summary text - it takes up too much space on a low res device
+ // We use it for storing the package name for the longClickListener
+ TextView tView = (TextView) holder.findViewById(android.R.id.summary);
+ tView.setVisibility(View.GONE);
+
+ if (!LightsCapabilities.supports(
+ getContext(), LightsCapabilities.LIGHTS_RGB_NOTIFICATION_LED)) {
+ mLightColorView.setVisibility(View.GONE);
+ }
+
+ updatePreferenceViews();
+ holder.itemView.setOnLongClickListener(this);
+ }
+
+ @Override
+ public void onPause() {
+ if (getDialog() != null) {
+ getDialog().onPause();
+ }
+ }
+
+ @Override
+ public void onResume() {
+ if (getDialog() != null) {
+ getDialog().onResume();
+ }
+ }
+
+ private void updatePreferenceViews() {
+ final int size = (int) getContext().getResources().getDimension(
+ R.dimen.oval_notification_size);
+
+ if (mLightColorView != null) {
+ mLightColorView.setEnabled(true);
+ // adjust if necessary to prevent material whiteout
+ final int imageColor = ((mColorValue & 0xF0F0F0) == 0xF0F0F0) ?
+ (mColorValue - 0x101010) : mColorValue;
+ mLightColorView.setImageDrawable(createOvalShape(size,
+ 0xFF000000 + imageColor));
+ }
+ if (mOnValueView != null) {
+ mOnValueView.setText(mapLengthValue(mOnValue));
+ }
+ if (mOffValueView != null) {
+ if (mOnValue == 1 || !mOnOffChangeable) {
+ mOffValueView.setVisibility(View.GONE);
+ } else {
+ mOffValueView.setVisibility(View.VISIBLE);
+ }
+ mOffValueView.setText(mapSpeedValue(mOffValue));
+ }
+ }
+
+ @Override
+ protected boolean onDismissDialog(LightSettingsDialog dialog, int which) {
+ if (which == DialogInterface.BUTTON_NEUTRAL) {
+ // Reset to previously supplied defaults
+ mDialog.setColor(mDefaultColorValue);
+ if (mOnOffChangeable) {
+ mDialog.setPulseSpeedOn(mDefaultOnValue);
+ mDialog.setPulseSpeedOff(mDefaultOffValue);
+ }
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected void onDialogClosed(boolean positiveResult) {
+ if (positiveResult) {
+ mColorValue = mDialog.getColor() & 0x00FFFFFF; // strip alpha, led does not support it
+ mOnValue = mDialog.getPulseSpeedOn();
+ mOffValue = mDialog.getPulseSpeedOff();
+ updatePreferenceViews();
+ callChangeListener(null);
+ }
+ }
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ mDialog = new LightSettingsDialog(getContext(), 0xFF000000 | mColorValue,
+ mOnValue, mOffValue, mOnOffChangeable, mLedBrightness);
+ mDialog.setAlphaSliderVisible(false);
+
+ // Initialize the buttons with null handlers, as they will get remapped by
+ // CustomPreferenceDialogFragment
+ mDialog.setButton(AlertDialog.BUTTON_POSITIVE,
+ getContext().getResources().getString(R.string.dlg_ok),
+ (DialogInterface.OnClickListener) null);
+ mDialog.setButton(AlertDialog.BUTTON_NEGATIVE,
+ getContext().getResources().getString(R.string.cancel),
+ (DialogInterface.OnClickListener) null);
+ if (mHasDefaults) {
+ mDialog.setButton(AlertDialog.BUTTON_NEUTRAL,
+ getContext().getResources().getString(R.string.reset),
+ (DialogInterface.OnClickListener) null);
+ }
+
+ return mDialog;
+ }
+
+
+ /**
+ * Getters and Setters
+ */
+ public int getColor() {
+ return mColorValue;
+ }
+
+ public void setColor(int color) {
+ mColorValue = color;
+ updatePreferenceViews();
+ }
+
+ public int getOnValue() {
+ return mOnValue;
+ }
+
+ public int getOffValue() {
+ return mOffValue;
+ }
+
+ public void setAllValues(int color, int onValue, int offValue) {
+ mColorValue = color;
+ mOnValue = onValue;
+ mOffValue = offValue;
+ updatePreferenceViews();
+ }
+
+ public void setAllValues(int color, int onValue, int offValue, boolean onOffChangeable) {
+ mColorValue = color;
+ mOnValue = onValue;
+ mOffValue = offValue;
+ mOnOffChangeable = onOffChangeable;
+ updatePreferenceViews();
+ }
+
+ public void setDefaultValues(int color, int onValue, int offValue) {
+ mDefaultColorValue = color;
+ mDefaultOnValue = onValue;
+ mDefaultOffValue = offValue;
+ mHasDefaults = true;
+ }
+
+ public void setBrightness(int brightness) {
+ mLedBrightness = brightness;
+ }
+
+ /**
+ * Utility methods
+ */
+ private static ShapeDrawable createOvalShape(int size, int color) {
+ ShapeDrawable shape = new ShapeDrawable(new OvalShape());
+ shape.setIntrinsicHeight(size);
+ shape.setIntrinsicWidth(size);
+ shape.getPaint().setColor(color);
+ return shape;
+ }
+
+ private String mapLengthValue(Integer time) {
+ if (!mOnOffChangeable) {
+ return getContext().getResources().getString(R.string.pulse_length_always_on);
+ }
+ if (time == DEFAULT_TIME) {
+ return getContext().getResources().getString(R.string.default_time);
+ }
+
+ String[] timeNames = getContext().getResources().getStringArray(
+ R.array.notification_pulse_length_entries);
+ String[] timeValues = getContext().getResources().getStringArray(
+ R.array.notification_pulse_length_values);
+
+ for (int i = 0; i < timeValues.length; i++) {
+ if (Integer.decode(timeValues[i]).equals(time)) {
+ return timeNames[i];
+ }
+ }
+
+ return getContext().getResources().getString(R.string.custom_time);
+ }
+
+ private String mapSpeedValue(Integer time) {
+ if (time == DEFAULT_TIME) {
+ return getContext().getResources().getString(R.string.default_time);
+ }
+
+ String[] timeNames = getContext().getResources().getStringArray(
+ R.array.notification_pulse_speed_entries);
+ String[] timeValues = getContext().getResources().getStringArray(
+ R.array.notification_pulse_speed_values);
+
+ for (int i = 0; i < timeValues.length; i++) {
+ if (Integer.decode(timeValues[i]).equals(time)) {
+ return timeNames[i];
+ }
+ }
+
+ return getContext().getResources().getString(R.string.custom_time);
+ }
+}
diff --git a/src/com/android/settings/derp/notificationlight/BatteryBrightnessPreference.java b/src/com/android/settings/derp/notificationlight/BatteryBrightnessPreference.java
new file mode 100644
index 00000000000..f30ac96efc1
--- /dev/null
+++ b/src/com/android/settings/derp/notificationlight/BatteryBrightnessPreference.java
@@ -0,0 +1,37 @@
+/*
+ * SPDX-FileCopyrightText: 2017-2022 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.android.settings.derp.notificationlight;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.AttributeSet;
+
+public class BatteryBrightnessPreference extends BrightnessPreference {
+ private static final String TAG = "BatteryBrightnessPreference";
+
+ private final ContentResolver mResolver;
+
+ public BatteryBrightnessPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mResolver = context.getContentResolver();
+ }
+
+ @Override
+ protected int getBrightnessSetting() {
+ return Settings.System.getIntForUser(mResolver,
+ Settings.System.BATTERY_LIGHT_BRIGHTNESS_LEVEL,
+ LIGHT_BRIGHTNESS_MAXIMUM, UserHandle.USER_CURRENT);
+ }
+
+ @Override
+ protected void setBrightnessSetting(int brightness) {
+ Settings.System.putIntForUser(mResolver,
+ Settings.System.BATTERY_LIGHT_BRIGHTNESS_LEVEL,
+ brightness, UserHandle.USER_CURRENT);
+ }
+}
diff --git a/src/com/android/settings/derp/notificationlight/BatteryBrightnessZenPreference.java b/src/com/android/settings/derp/notificationlight/BatteryBrightnessZenPreference.java
new file mode 100644
index 00000000000..1721099d6aa
--- /dev/null
+++ b/src/com/android/settings/derp/notificationlight/BatteryBrightnessZenPreference.java
@@ -0,0 +1,37 @@
+/*
+ * SPDX-FileCopyrightText: 2017-2022 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.android.settings.derp.notificationlight;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.AttributeSet;
+
+public class BatteryBrightnessZenPreference extends BrightnessPreference {
+ private static final String TAG = "BatteryBrightnessZenPreference";
+
+ private final ContentResolver mResolver;
+
+ public BatteryBrightnessZenPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mResolver = context.getContentResolver();
+ }
+
+ @Override
+ protected int getBrightnessSetting() {
+ return Settings.System.getIntForUser(mResolver,
+ Settings.System.BATTERY_LIGHT_BRIGHTNESS_LEVEL_ZEN,
+ LIGHT_BRIGHTNESS_MAXIMUM, UserHandle.USER_CURRENT);
+ }
+
+ @Override
+ protected void setBrightnessSetting(int brightness) {
+ Settings.System.putIntForUser(mResolver,
+ Settings.System.BATTERY_LIGHT_BRIGHTNESS_LEVEL_ZEN,
+ brightness, UserHandle.USER_CURRENT);
+ }
+}
diff --git a/src/com/android/settings/derp/notificationlight/BatteryLightPreferenceController.java b/src/com/android/settings/derp/notificationlight/BatteryLightPreferenceController.java
new file mode 100644
index 00000000000..95855d40f37
--- /dev/null
+++ b/src/com/android/settings/derp/notificationlight/BatteryLightPreferenceController.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The PixelExperience Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.derp.notificationlight;
+
+import android.content.Context;
+import com.android.settings.core.BasePreferenceController;
+
+public class BatteryLightPreferenceController extends BasePreferenceController {
+
+ public static final String KEY = "battery_lights";
+
+ private Context mContext;
+
+ public BatteryLightPreferenceController(Context context, String key) {
+ super(context, key);
+
+ mContext = context;
+ }
+
+ public BatteryLightPreferenceController(Context context) {
+ this(context, KEY);
+
+ mContext = context;
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ int caps = mContext.getResources().getInteger(com.android.internal.R.integer.config_deviceLightCapabilities);
+ return (((caps & 64) == 64) ? AVAILABLE : UNSUPPORTED_ON_DEVICE);
+ }
+
+}
diff --git a/src/com/android/settings/derp/notificationlight/BatteryLightSettings.java b/src/com/android/settings/derp/notificationlight/BatteryLightSettings.java
new file mode 100644
index 00000000000..b4af9b25b23
--- /dev/null
+++ b/src/com/android/settings/derp/notificationlight/BatteryLightSettings.java
@@ -0,0 +1,319 @@
+/*
+ * SPDX-FileCopyrightText: 2012 The CyanogenMod Project
+ * SPDX-FileCopyrightText: 2017-2023 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.android.settings.derp.notificationlight;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.util.ArraySet;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.PreferenceScreen;
+
+import com.android.internal.derp.notification.LightsCapabilities;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.search.SearchIndexable;
+
+import org.derpfest.support.preferences.SystemSettingMainSwitchPreference;
+import org.derpfest.support.preferences.SystemSettingSwitchPreference;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+@SearchIndexable
+public class BatteryLightSettings extends SettingsPreferenceFragment implements
+ Preference.OnPreferenceChangeListener {
+ private static final String TAG = "BatteryLightSettings";
+
+ private static final String KEY_BATTERY_LIGHTS = "battery_lights";
+ private static final String GENERAL_SECTION = "general_section";
+ private static final String COLORS_SECTION = "colors_list";
+ private static final String BRIGHTNESS_SECTION = "brightness_section";
+
+ private static final String LOW_COLOR_PREF = "low_color";
+ private static final String MEDIUM_COLOR_PREF = "medium_color";
+ private static final String FULL_COLOR_PREF = "full_color";
+ private static final String LIGHT_ENABLED_PREF = "battery_light_enabled";
+ private static final String LIGHT_FULL_CHARGE_DISABLED_PREF =
+ "battery_light_full_charge_disabled";
+ private static final String PULSE_ENABLED_PREF = "battery_light_pulse";
+ private static final String BRIGHTNESS_PREFERENCE = "battery_light_brightness_level";
+ private static final String BRIGHTNESS_ZEN_PREFERENCE = "battery_light_brightness_level_zen";
+
+ private ApplicationLightPreference mLowColorPref;
+ private ApplicationLightPreference mMediumColorPref;
+ private ApplicationLightPreference mFullColorPref;
+ private SystemSettingMainSwitchPreference mLightEnabledPref;
+ private SystemSettingSwitchPreference mLightFullChargeDisabledPref;
+ private SystemSettingSwitchPreference mPulseEnabledPref;
+ private BatteryBrightnessPreference mBatteryBrightnessPref;
+ private BatteryBrightnessZenPreference mBatteryBrightnessZenPref;
+ private int mDefaultLowColor;
+ private int mDefaultMediumColor;
+ private int mDefaultFullColor;
+ private boolean mMultiColorLed;
+
+ private static final int MENU_RESET = Menu.FIRST;
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ final Context context = requireContext();
+ final Resources res = getResources();
+
+ // Collect battery led capabilities.
+ mMultiColorLed =
+ LightsCapabilities.supports(context, LightsCapabilities.LIGHTS_RGB_BATTERY_LED);
+ // liblights supports brightness control
+ final boolean halAdjustableBrightness = LightsCapabilities.supports(context,
+ LightsCapabilities.LIGHTS_ADJUSTABLE_BATTERY_LED_BRIGHTNESS);
+ final boolean pulsatingLed = LightsCapabilities.supports(context,
+ LightsCapabilities.LIGHTS_PULSATING_LED);
+ final boolean segmentedBatteryLed = LightsCapabilities.supports(context,
+ LightsCapabilities.LIGHTS_SEGMENTED_BATTERY_LED);
+
+ addPreferencesFromResource(R.xml.battery_light_settings);
+ requireActivity().getActionBar().setTitle(R.string.battery_light_title);
+
+ PreferenceScreen prefSet = getPreferenceScreen();
+
+ PreferenceGroup generalPrefs = prefSet.findPreference(GENERAL_SECTION);
+
+ mLightEnabledPref = prefSet.findPreference(LIGHT_ENABLED_PREF);
+ mLightFullChargeDisabledPref = prefSet.findPreference(LIGHT_FULL_CHARGE_DISABLED_PREF);
+ mPulseEnabledPref = prefSet.findPreference(PULSE_ENABLED_PREF);
+ mBatteryBrightnessPref = prefSet.findPreference(BRIGHTNESS_PREFERENCE);
+ mBatteryBrightnessZenPref = prefSet.findPreference(BRIGHTNESS_ZEN_PREFERENCE);
+
+ mDefaultLowColor = res.getInteger(
+ com.android.internal.R.integer.config_notificationsBatteryLowARGB);
+ mDefaultMediumColor = res.getInteger(
+ com.android.internal.R.integer.config_notificationsBatteryMediumARGB);
+ mDefaultFullColor = res.getInteger(
+ com.android.internal.R.integer.config_notificationsBatteryFullARGB);
+
+ int batteryBrightness = mBatteryBrightnessPref.getBrightnessSetting();
+
+ if (!pulsatingLed || segmentedBatteryLed) {
+ generalPrefs.removePreference(mPulseEnabledPref);
+ }
+
+ if (mMultiColorLed) {
+ generalPrefs.removePreference(mLightFullChargeDisabledPref);
+ setHasOptionsMenu(true);
+
+ // Low, Medium and full color preferences
+ mLowColorPref = prefSet.findPreference(LOW_COLOR_PREF);
+ mLowColorPref.setOnPreferenceChangeListener(this);
+ mLowColorPref.setDefaultValues(mDefaultLowColor, 0, 0);
+ mLowColorPref.setBrightness(batteryBrightness);
+
+ mMediumColorPref = prefSet.findPreference(MEDIUM_COLOR_PREF);
+ mMediumColorPref.setOnPreferenceChangeListener(this);
+ mMediumColorPref.setDefaultValues(mDefaultMediumColor, 0, 0);
+ mMediumColorPref.setBrightness(batteryBrightness);
+
+ mFullColorPref = prefSet.findPreference(FULL_COLOR_PREF);
+ mFullColorPref.setOnPreferenceChangeListener(this);
+ mFullColorPref.setDefaultValues(mDefaultFullColor, 0, 0);
+ mFullColorPref.setBrightness(batteryBrightness);
+
+ final BrightnessPreference.OnBrightnessChangedListener brightnessListener =
+ brightness -> {
+ mLowColorPref.setBrightness(brightness);
+ mMediumColorPref.setBrightness(brightness);
+ mFullColorPref.setBrightness(brightness);
+ };
+ mBatteryBrightnessPref.setOnBrightnessChangedListener(brightnessListener);
+ } else {
+ prefSet.removePreference(prefSet.findPreference(COLORS_SECTION));
+ resetColors();
+ }
+
+ // Remove battery LED brightness controls if we can't support them.
+ if (!mMultiColorLed && !halAdjustableBrightness) {
+ prefSet.removePreference(prefSet.findPreference(BRIGHTNESS_SECTION));
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ refreshColors();
+ }
+
+ private void refreshColors() {
+ ContentResolver resolver = requireActivity().getContentResolver();
+
+ if (mLowColorPref != null) {
+ int lowColor = Settings.System.getInt(resolver,
+ Settings.System.BATTERY_LIGHT_LOW_COLOR, mDefaultLowColor);
+ mLowColorPref.setAllValues(lowColor, 0, 0, false);
+ }
+
+ if (mMediumColorPref != null) {
+ int mediumColor = Settings.System.getInt(resolver,
+ Settings.System.BATTERY_LIGHT_MEDIUM_COLOR, mDefaultMediumColor);
+ mMediumColorPref.setAllValues(mediumColor, 0, 0, false);
+ }
+
+ if (mFullColorPref != null) {
+ int fullColor = Settings.System.getInt(resolver,
+ Settings.System.BATTERY_LIGHT_FULL_COLOR, mDefaultFullColor);
+ mFullColorPref.setAllValues(fullColor, 0, 0, false);
+ updateBrightnessPrefColor(fullColor);
+ }
+ }
+
+ private void updateBrightnessPrefColor(int color) {
+ // If the user has selected no light (ie black) for
+ // full charge, use white for the brightness preference.
+ if (color == 0) {
+ color = 0xFFFFFF;
+ }
+ mBatteryBrightnessPref.setLedColor(color);
+ mBatteryBrightnessZenPref.setLedColor(color);
+ }
+
+ /**
+ * Updates the default or application specific notification settings.
+ *
+ * @param key of the specific setting to update
+ */
+ protected void updateValues(String key, Integer color) {
+ ContentResolver resolver = requireActivity().getContentResolver();
+ switch (key) {
+ case LOW_COLOR_PREF:
+ Settings.System.putInt(resolver,
+ Settings.System.BATTERY_LIGHT_LOW_COLOR, color);
+ break;
+ case MEDIUM_COLOR_PREF:
+ Settings.System.putInt(resolver,
+ Settings.System.BATTERY_LIGHT_MEDIUM_COLOR, color);
+ break;
+ case FULL_COLOR_PREF:
+ Settings.System.putInt(resolver,
+ Settings.System.BATTERY_LIGHT_FULL_COLOR, color);
+ updateBrightnessPrefColor(color);
+ break;
+ }
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ if (mMultiColorLed) {
+ menu.add(0, MENU_RESET, 0, R.string.reset)
+ .setIcon(R.drawable.ic_settings_backup_restore)
+ .setAlphabeticShortcut('r')
+ .setShowAsActionFlags(
+ MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case MENU_RESET:
+ resetToDefaults();
+ return true;
+ }
+ return false;
+ }
+
+ protected void resetColors() {
+ ContentResolver resolver = requireActivity().getContentResolver();
+
+ // Reset to the framework default colors
+ Settings.System.putInt(resolver, Settings.System.BATTERY_LIGHT_LOW_COLOR,
+ mDefaultLowColor);
+ Settings.System.putInt(resolver, Settings.System.BATTERY_LIGHT_MEDIUM_COLOR,
+ mDefaultMediumColor);
+ Settings.System.putInt(resolver, Settings.System.BATTERY_LIGHT_FULL_COLOR,
+ mDefaultFullColor);
+ refreshColors();
+ }
+
+ protected void resetToDefaults() {
+ final Resources res = getResources();
+ final boolean batteryLightEnabled = res.getBoolean(R.bool.def_battery_light_enabled);
+ final boolean batteryLightFullChargeDisabled =
+ res.getBoolean(R.bool.def_battery_light_full_charge_disabled);
+ final boolean batteryLightPulseEnabled = res.getBoolean(R.bool.def_battery_light_pulse);
+
+ if (mLightEnabledPref != null) mLightEnabledPref.setChecked(batteryLightEnabled);
+ if (mLightFullChargeDisabledPref != null) {
+ mLightFullChargeDisabledPref.setChecked(batteryLightFullChargeDisabled);
+ }
+ if (mPulseEnabledPref != null) mPulseEnabledPref.setChecked(batteryLightPulseEnabled);
+
+ resetColors();
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object objValue) {
+ ApplicationLightPreference lightPref = (ApplicationLightPreference) preference;
+ updateValues(lightPref.getKey(), lightPref.getColor());
+ return true;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsEvent.DERP;
+ }
+
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider(R.xml.battery_light_settings) {
+
+ @Override
+ public List getNonIndexableKeys(Context context) {
+ final List result = new ArrayList<>();
+
+ if (!LightsCapabilities.supports(context, LightsCapabilities.LIGHTS_BATTERY_LED)) {
+ result.add(KEY_BATTERY_LIGHTS);
+ result.add(LIGHT_ENABLED_PREF);
+ result.add(GENERAL_SECTION);
+ result.add(LIGHT_FULL_CHARGE_DISABLED_PREF);
+ result.add(COLORS_SECTION);
+ result.add(LOW_COLOR_PREF);
+ result.add(MEDIUM_COLOR_PREF);
+ result.add(FULL_COLOR_PREF);
+ } else if (LightsCapabilities.supports(context,
+ LightsCapabilities.LIGHTS_RGB_BATTERY_LED)) {
+ result.add(LIGHT_FULL_CHARGE_DISABLED_PREF);
+ } else {
+ result.add(COLORS_SECTION);
+ result.add(LOW_COLOR_PREF);
+ result.add(MEDIUM_COLOR_PREF);
+ result.add(FULL_COLOR_PREF);
+ }
+ if (!LightsCapabilities.supports(context,
+ LightsCapabilities.LIGHTS_ADJUSTABLE_BATTERY_LED_BRIGHTNESS)) {
+ result.add(BRIGHTNESS_SECTION);
+ result.add(BRIGHTNESS_PREFERENCE);
+ result.add(BRIGHTNESS_ZEN_PREFERENCE);
+ }
+ if (!LightsCapabilities.supports(context, LightsCapabilities.LIGHTS_PULSATING_LED) ||
+ LightsCapabilities.supports(context,
+ LightsCapabilities.LIGHTS_SEGMENTED_BATTERY_LED)) {
+ result.add(PULSE_ENABLED_PREF);
+ }
+ return result;
+ }
+ };
+}
diff --git a/src/com/android/settings/derp/notificationlight/BrightnessPreference.java b/src/com/android/settings/derp/notificationlight/BrightnessPreference.java
new file mode 100644
index 00000000000..e3b3bb9eead
--- /dev/null
+++ b/src/com/android/settings/derp/notificationlight/BrightnessPreference.java
@@ -0,0 +1,290 @@
+/*
+ * SPDX-FileCopyrightText: 2017-2023 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.android.settings.derp.notificationlight;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.internal.derp.notification.LineageNotification;
+
+import com.android.settings.R;
+import com.android.settings.derp.preference.CustomDialogPreference;
+
+public class BrightnessPreference extends CustomDialogPreference
+ implements SeekBar.OnSeekBarChangeListener {
+
+ private static final String TAG = "BrightnessPreference";
+
+ public static final int LIGHT_BRIGHTNESS_MINIMUM = 1;
+ public static final int LIGHT_BRIGHTNESS_MAXIMUM = 255;
+
+ // Minimum delay between LED notification updates
+ private final static long LED_UPDATE_DELAY_MS = 250;
+
+ // Default led color used to illustrate brightness
+ private final static int DEFAULT_LED_COLOR = 0xFFFFFF;
+
+ private TextView mPreferencePercent;
+
+ private TextView mDialogPercent;
+ private SeekBar mBrightnessBar;
+
+ // The user selected brightness level (past or present if dialog is OKed).
+ private int mSelectedBrightness;
+ // Current position of brightness seekbar
+ private int mSeekBarBrightness;
+ // LED brightness currently on display (0 means notification is not showing)
+ private int mVisibleLedBrightness;
+ // LED color used to illustrate brightness
+ private int mLedColor = DEFAULT_LED_COLOR;
+
+ private final Context mContext;
+
+ private final Notification.Builder mNotificationBuilder;
+ private final NotificationManager mNotificationManager;
+
+ public interface OnBrightnessChangedListener {
+ void onBrightnessChanged(int brightness);
+ }
+
+ private OnBrightnessChangedListener mListener;
+
+ public BrightnessPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ setWidgetLayoutResource(R.layout.preference_brightness);
+ setDialogLayoutResource(R.layout.dialog_brightness);
+
+ mContext = context;
+
+ mNotificationManager = context.getSystemService(NotificationManager.class);
+
+ // Force lights on when screen is on and also force maximum brightness.
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(LineageNotification.EXTRA_FORCE_SHOW_LIGHTS, true);
+ bundle.putInt(LineageNotification.EXTRA_FORCE_LIGHT_BRIGHTNESS, LIGHT_BRIGHTNESS_MAXIMUM);
+
+ mNotificationBuilder = new Notification.Builder(mContext);
+ mNotificationBuilder.setExtras(bundle)
+ .setContentTitle(mContext.getString(R.string.led_notification_title))
+ .setContentText(mContext.getString(R.string.led_notification_text))
+ .setSmallIcon(R.drawable.ic_settings_24dp)
+ .setOngoing(true);
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+
+ mSelectedBrightness = getBrightnessSetting();
+ mSeekBarBrightness = mSelectedBrightness;
+
+ mPreferencePercent = (TextView) holder.findViewById(R.id.brightness_percent);
+ mPreferencePercent.setText(percentString(mSelectedBrightness, LIGHT_BRIGHTNESS_MINIMUM,
+ LIGHT_BRIGHTNESS_MAXIMUM));
+ }
+
+ @Override
+ protected void onPrepareDialogBuilder(AlertDialog.Builder builder,
+ DialogInterface.OnClickListener listener) {
+ super.onPrepareDialogBuilder(builder, listener);
+ builder.setNeutralButton(R.string.reset, null);
+ builder.setNegativeButton(R.string.cancel, null);
+ builder.setPositiveButton(R.string.dlg_ok, null);
+ }
+
+ @Override
+ protected boolean onDismissDialog(AlertDialog dialog, int which) {
+ if (which == DialogInterface.BUTTON_NEUTRAL) {
+ // Reset brightness to default (max).
+ mBrightnessBar.setProgress(LIGHT_BRIGHTNESS_MAXIMUM);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected void onDialogClosed(boolean positiveResult) {
+ super.onDialogClosed(positiveResult);
+
+ if (positiveResult) {
+ mSelectedBrightness = mSeekBarBrightness;
+ setBrightnessSetting(mSelectedBrightness);
+ if (mListener != null) {
+ mListener.onBrightnessChanged(mSelectedBrightness);
+ }
+ mPreferencePercent.setText(percentString(mSelectedBrightness,
+ LIGHT_BRIGHTNESS_MINIMUM, LIGHT_BRIGHTNESS_MAXIMUM));
+ } else {
+ mSeekBarBrightness = mSelectedBrightness;
+ }
+ }
+
+ @Override
+ protected void onBindDialogView(View view) {
+ super.onBindDialogView(view);
+
+ // Locate text view for percentage value
+ mDialogPercent = view.findViewById(R.id.brightness_percent);
+
+ mVisibleLedBrightness = 0; // LED notification is not showing.
+
+ mBrightnessBar = view.findViewById(R.id.brightness_seekbar);
+ mBrightnessBar.setMax(LIGHT_BRIGHTNESS_MAXIMUM);
+ mBrightnessBar.setMin(LIGHT_BRIGHTNESS_MINIMUM);
+ mBrightnessBar.setOnSeekBarChangeListener(this);
+ mBrightnessBar.setProgress(mSeekBarBrightness);
+ mBrightnessBar.setThumb(mContext.getResources().getDrawable(
+ R.drawable.ic_brightness_thumb, mContext.getTheme()));
+ }
+
+ @Override
+ protected void onResume() {
+ updateNotification();
+ }
+
+ @Override
+ protected void onPause() {
+ cancelNotification();
+ }
+
+ @Override
+ public void onStartTrackingTouch (SeekBar seekBar) {}
+
+ @Override
+ public void onStopTrackingTouch (SeekBar seekBar) {}
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ mSeekBarBrightness = progress;
+ updateNotification();
+ mDialogPercent.setText(percentString(progress, seekBar.getMin(), seekBar.getMax()));
+ }
+
+ public void setOnBrightnessChangedListener(OnBrightnessChangedListener listener) {
+ mListener = listener;
+ }
+
+ protected int getBrightnessSetting() {
+ // Null implementation
+ return LIGHT_BRIGHTNESS_MAXIMUM;
+ }
+
+ protected void setBrightnessSetting(int brightness) {
+ // Null implementation
+ }
+
+ public void setLedColor(int color) {
+ mLedColor = color & 0xFFFFFF;
+ }
+
+ private void updateNotification() {
+ // Exit if there's nothing to do or we're throttled.
+ if (mVisibleLedBrightness == mSeekBarBrightness || mLedHandler.hasMessages(0)) {
+ return;
+ }
+
+ mLedHandler.sendEmptyMessageDelayed(0, LED_UPDATE_DELAY_MS);
+
+ // Instead of canceling the notification, force it to update with the color.
+ // Use a white light for a better preview of the brightness.
+ int notificationColor = mLedColor | (mSeekBarBrightness << 24);
+ mNotificationBuilder.setLights(notificationColor, 1, 0);
+ mNotificationManager.notify(1, mNotificationBuilder.build());
+ mVisibleLedBrightness = mSeekBarBrightness;
+ }
+
+ private void cancelNotification() {
+ if (mVisibleLedBrightness > 0) {
+ mLedHandler.removeMessages(0);
+ mNotificationManager.cancel(1);
+ mVisibleLedBrightness = 0;
+ }
+ }
+
+ private final Handler mLedHandler = new Handler(Looper.getMainLooper()) {
+ public void handleMessage(Message msg) {
+ updateNotification();
+ }
+ };
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ final Parcelable superState = super.onSaveInstanceState();
+ if (getDialog() == null || !getDialog().isShowing()) {
+ return superState;
+ }
+
+ // Save the dialog state
+ final SavedState myState = new SavedState(superState);
+ myState.seekBarBrightness = mSeekBarBrightness;
+
+ return myState;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ if (state == null || !state.getClass().equals(SavedState.class)) {
+ // Didn't save state for us in onSaveInstanceState
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
+ SavedState myState = (SavedState) state;
+ super.onRestoreInstanceState(myState.getSuperState());
+
+ mSeekBarBrightness = myState.seekBarBrightness;
+ }
+
+ private String percentString(int progress, int min, int max) {
+ final float percentage = 100f * (progress - min ) / (max - min);
+ return String.format("%d%%", Math.round(percentage));
+ }
+
+ private static class SavedState extends BaseSavedState {
+ int seekBarBrightness;
+
+ public SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ public SavedState(Parcel source) {
+ super(source);
+ seekBarBrightness = source.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(seekBarBrightness);
+ }
+
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator<>() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+}
diff --git a/src/com/android/settings/derp/notificationlight/ColorPanelView.java b/src/com/android/settings/derp/notificationlight/ColorPanelView.java
new file mode 100644
index 00000000000..f4f2fc59b14
--- /dev/null
+++ b/src/com/android/settings/derp/notificationlight/ColorPanelView.java
@@ -0,0 +1,154 @@
+/*
+ * SPDX-FileCopyrightText: 2010 Daniel Nilsson
+ * SPDX-FileCopyrightText: 2012 The CyanogenMod Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.android.settings.derp.notificationlight;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * This class draws a panel which which will be filled with a color which can be
+ * set. It can be used to show the currently selected color which you will get
+ * from the {@link ColorPickerView}.
+ *
+ * @author Daniel Nilsson
+ */
+public class ColorPanelView extends View {
+
+ /**
+ * The width in pixels of the border surrounding the color panel.
+ */
+ private final static float BORDER_WIDTH_PX = 1;
+
+ private static float mDensity = 1f;
+
+ private int mBorderColor = 0xff6E6E6E;
+ private int mColor = 0xff000000;
+
+ private Paint mBorderPaint;
+ private Paint mColorPaint;
+
+ private RectF mDrawingRect;
+ private RectF mColorRect;
+
+ private AlphaPatternDrawable mAlphaPattern;
+
+ public ColorPanelView(Context context) {
+ this(context, null);
+ }
+
+ public ColorPanelView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ColorPanelView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ init();
+ }
+
+ private void init() {
+ mBorderPaint = new Paint();
+ mColorPaint = new Paint();
+ mDensity = getContext().getResources().getDisplayMetrics().density;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+
+ final RectF rect = mColorRect;
+
+ if (BORDER_WIDTH_PX > 0) {
+ mBorderPaint.setColor(mBorderColor);
+ canvas.drawRect(mDrawingRect, mBorderPaint);
+ }
+
+ if (mAlphaPattern != null) {
+ mAlphaPattern.draw(canvas);
+ }
+
+ mColorPaint.setColor(mColor);
+
+ canvas.drawRect(rect, mColorPaint);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+
+ setMeasuredDimension(width, height);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+
+ mDrawingRect = new RectF();
+ mDrawingRect.left = getPaddingLeft();
+ mDrawingRect.right = w - getPaddingRight();
+ mDrawingRect.top = getPaddingTop();
+ mDrawingRect.bottom = h - getPaddingBottom();
+
+ setUpColorRect();
+
+ }
+
+ private void setUpColorRect() {
+ final RectF dRect = mDrawingRect;
+
+ float left = dRect.left + BORDER_WIDTH_PX;
+ float top = dRect.top + BORDER_WIDTH_PX;
+ float bottom = dRect.bottom - BORDER_WIDTH_PX;
+ float right = dRect.right - BORDER_WIDTH_PX;
+
+ mColorRect = new RectF(left, top, right, bottom);
+
+ mAlphaPattern = new AlphaPatternDrawable((int) (5 * mDensity));
+
+ mAlphaPattern.setBounds(Math.round(mColorRect.left),
+ Math.round(mColorRect.top),
+ Math.round(mColorRect.right),
+ Math.round(mColorRect.bottom));
+
+ }
+
+ /**
+ * Set the color that should be shown by this view.
+ */
+ public void setColor(int color) {
+ mColor = color;
+ invalidate();
+ }
+
+ /**
+ * Get the color currently show by this view.
+ */
+ public int getColor() {
+ return mColor;
+ }
+
+ /**
+ * Set the color of the border surrounding the panel.
+ */
+ public void setBorderColor(int color) {
+ mBorderColor = color;
+ invalidate();
+ }
+
+ /**
+ * Get the color of the border surrounding the panel.
+ */
+ public int getBorderColor() {
+ return mBorderColor;
+ }
+
+}
diff --git a/src/com/android/settings/derp/notificationlight/ColorPickerView.java b/src/com/android/settings/derp/notificationlight/ColorPickerView.java
new file mode 100644
index 00000000000..6f7e5fe4b18
--- /dev/null
+++ b/src/com/android/settings/derp/notificationlight/ColorPickerView.java
@@ -0,0 +1,751 @@
+/*
+ * SPDX-FileCopyrightText: 2010 Daniel Nilsson
+ * SPDX-FileCopyrightText: 2012 The CyanogenMod Project
+ * SPDX-FileCopyrightText: 2022-2023 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.android.settings.derp.notificationlight;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ComposeShader;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.graphics.Paint.Align;
+import android.graphics.Paint.Style;
+import android.graphics.Shader.TileMode;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * Displays a color picker to the user and allow them to select a color. A
+ * slider for the alpha channel is also available. Enable it by setting
+ * setAlphaSliderVisible(boolean) to true.
+ *
+ * @author Daniel Nilsson
+ */
+public class ColorPickerView extends View {
+
+ public interface OnColorChangedListener {
+ void onColorChanged(int color);
+ }
+
+ private final static int PANEL_SAT_VAL = 0;
+ private final static int PANEL_HUE = 1;
+ private final static int PANEL_ALPHA = 2;
+
+ /**
+ * The width in pixels of the border surrounding all color panels.
+ */
+ private final static float BORDER_WIDTH_PX = 1;
+
+ /**
+ * The width in dp of the hue panel.
+ */
+ private float HUE_PANEL_WIDTH = 30f;
+ /**
+ * The height in dp of the alpha panel
+ */
+ private float ALPHA_PANEL_HEIGHT = 20f;
+ /**
+ * The distance in dp between the different color panels.
+ */
+ private float PANEL_SPACING = 10f;
+ /**
+ * The radius in dp of the color palette tracker circle.
+ */
+ private float PALETTE_CIRCLE_TRACKER_RADIUS = 5f;
+ /**
+ * The dp which the tracker of the hue or alpha panel will extend outside of
+ * its bounds.
+ */
+ private float RECTANGLE_TRACKER_OFFSET = 2f;
+
+ private static float mDensity = 1f;
+
+ private OnColorChangedListener mListener;
+
+ private Paint mSatValPaint;
+ private Paint mSatValTrackerPaint;
+
+ private Paint mHuePaint;
+ private Paint mHueTrackerPaint;
+
+ private Paint mAlphaPaint;
+ private Paint mAlphaTextPaint;
+
+ private Paint mBorderPaint;
+
+ private Shader mValShader;
+ private Shader mSatShader;
+ private Shader mHueShader;
+ private Shader mAlphaShader;
+
+ private int mAlpha = 0xff;
+ private float mHue = 360f;
+ private float mSat = 0f;
+ private float mVal = 0f;
+
+ private final static String ALPHA_SLIDER_TEXT = "Alpha";
+ private final static int SLIDER_TRACKER_COLOR = 0xff1c1c1c;
+ private final static int BORDER_COLOR = 0xff6E6E6E;
+ private boolean mShowAlphaPanel = false;
+
+ /*
+ * To remember which panel that has the "focus" when processing hardware
+ * button data.
+ */
+ private int mLastTouchedPanel = PANEL_SAT_VAL;
+
+ /**
+ * Offset from the edge we must have or else the finger tracker will get
+ * clipped when it is drawn outside of the view.
+ */
+ private float mDrawingOffset;
+
+ /*
+ * Distance form the edges of the view of where we are allowed to draw.
+ */
+ private RectF mDrawingRect;
+
+ private RectF mSatValRect;
+ private RectF mHueRect;
+ private RectF mAlphaRect;
+
+ private AlphaPatternDrawable mAlphaPattern;
+
+ private Point mStartTouchPoint = null;
+
+ public ColorPickerView(Context context) {
+ this(context, null);
+ }
+
+ public ColorPickerView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ColorPickerView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init();
+ }
+
+ private void init() {
+ mDensity = getContext().getResources().getDisplayMetrics().density;
+ PALETTE_CIRCLE_TRACKER_RADIUS *= mDensity;
+ RECTANGLE_TRACKER_OFFSET *= mDensity;
+ HUE_PANEL_WIDTH *= mDensity;
+ ALPHA_PANEL_HEIGHT *= mDensity;
+ PANEL_SPACING = PANEL_SPACING * mDensity;
+
+ mDrawingOffset = calculateRequiredOffset();
+ initPaintTools();
+
+ // Needed for receiving track ball motion events.
+ setFocusableInTouchMode(true);
+ setFocusable(true);
+ setClickable(true);
+ }
+
+ private void initPaintTools() {
+ mSatValPaint = new Paint();
+ mSatValTrackerPaint = new Paint();
+ mHuePaint = new Paint();
+ mHueTrackerPaint = new Paint();
+ mAlphaPaint = new Paint();
+ mAlphaTextPaint = new Paint();
+ mBorderPaint = new Paint();
+
+ mSatValTrackerPaint.setStyle(Style.STROKE);
+ mSatValTrackerPaint.setStrokeWidth(2f * mDensity);
+ mSatValTrackerPaint.setAntiAlias(true);
+
+ mHueTrackerPaint.setColor(SLIDER_TRACKER_COLOR);
+ mHueTrackerPaint.setStyle(Style.STROKE);
+ mHueTrackerPaint.setStrokeWidth(2f * mDensity);
+ mHueTrackerPaint.setAntiAlias(true);
+
+ mAlphaTextPaint.setColor(0xff1c1c1c);
+ mAlphaTextPaint.setTextSize(14f * mDensity);
+ mAlphaTextPaint.setAntiAlias(true);
+ mAlphaTextPaint.setTextAlign(Align.CENTER);
+ mAlphaTextPaint.setFakeBoldText(true);
+ }
+
+ private float calculateRequiredOffset() {
+ float offset = Math.max(PALETTE_CIRCLE_TRACKER_RADIUS, RECTANGLE_TRACKER_OFFSET);
+ offset = Math.max(offset, BORDER_WIDTH_PX * mDensity);
+
+ return offset * 1.5f;
+ }
+
+ private int[] buildHueColorArray() {
+ int[] hue = new int[361];
+
+ int count = 0;
+ for (int i = hue.length - 1; i >= 0; i--, count++) {
+ hue[count] = Color.HSVToColor(new float[] {
+ i, 1f, 1f
+ });
+ }
+ return hue;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (mDrawingRect.width() <= 0 || mDrawingRect.height() <= 0) {
+ return;
+ }
+ drawSatValPanel(canvas);
+ drawHuePanel(canvas);
+ drawAlphaPanel(canvas);
+ }
+
+ private void drawSatValPanel(Canvas canvas) {
+ final RectF rect = mSatValRect;
+ int rgb = Color.HSVToColor(new float[] {
+ mHue, 1f, 1f
+ });
+
+ if (BORDER_WIDTH_PX > 0) {
+ mBorderPaint.setColor(BORDER_COLOR);
+ canvas.drawRect(mDrawingRect.left, mDrawingRect.top, rect.right + BORDER_WIDTH_PX,
+ rect.bottom + BORDER_WIDTH_PX, mBorderPaint);
+ }
+
+ // On Honeycomb+ we need to use software rendering to create the shader properly
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+ }
+
+ // Get the overlaying gradients ready and create the ComposeShader
+ if (mValShader == null) {
+ mValShader = new LinearGradient(rect.left, rect.top, rect.left, rect.bottom,
+ 0xffffffff, 0xff000000, TileMode.CLAMP);
+ }
+ mSatShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top,
+ 0xffffffff, rgb, TileMode.CLAMP);
+ ComposeShader mShader = new ComposeShader(mValShader, mSatShader, Mode.MULTIPLY);
+ mSatValPaint.setShader(mShader);
+ canvas.drawRect(rect, mSatValPaint);
+
+ Point p = satValToPoint(mSat, mVal);
+ mSatValTrackerPaint.setColor(0xff000000);
+ canvas.drawCircle(p.x, p.y, PALETTE_CIRCLE_TRACKER_RADIUS - mDensity, mSatValTrackerPaint);
+
+ mSatValTrackerPaint.setColor(0xffdddddd);
+ canvas.drawCircle(p.x, p.y, PALETTE_CIRCLE_TRACKER_RADIUS, mSatValTrackerPaint);
+ }
+
+ private void drawHuePanel(Canvas canvas) {
+ final RectF rect = mHueRect;
+
+ if (BORDER_WIDTH_PX > 0) {
+ mBorderPaint.setColor(BORDER_COLOR);
+ canvas.drawRect(rect.left - BORDER_WIDTH_PX,
+ rect.top - BORDER_WIDTH_PX,
+ rect.right + BORDER_WIDTH_PX,
+ rect.bottom + BORDER_WIDTH_PX,
+ mBorderPaint);
+ }
+
+ if (mHueShader == null) {
+ mHueShader = new LinearGradient(rect.left, rect.top, rect.left, rect.bottom,
+ buildHueColorArray(), null, TileMode.CLAMP);
+ mHuePaint.setShader(mHueShader);
+ }
+
+ canvas.drawRect(rect, mHuePaint);
+
+ float rectHeight = 4 * mDensity / 2;
+
+ Point p = hueToPoint(mHue);
+
+ RectF r = new RectF();
+ r.left = rect.left - RECTANGLE_TRACKER_OFFSET;
+ r.right = rect.right + RECTANGLE_TRACKER_OFFSET;
+ r.top = p.y - rectHeight;
+ r.bottom = p.y + rectHeight;
+
+ canvas.drawRoundRect(r, 2, 2, mHueTrackerPaint);
+
+ }
+
+ private void drawAlphaPanel(Canvas canvas) {
+ if (!mShowAlphaPanel || mAlphaRect == null || mAlphaPattern == null) {
+ return;
+ }
+
+ final RectF rect = mAlphaRect;
+
+ if (BORDER_WIDTH_PX > 0) {
+ mBorderPaint.setColor(BORDER_COLOR);
+ canvas.drawRect(rect.left - BORDER_WIDTH_PX,
+ rect.top - BORDER_WIDTH_PX,
+ rect.right + BORDER_WIDTH_PX,
+ rect.bottom + BORDER_WIDTH_PX,
+ mBorderPaint);
+ }
+
+ mAlphaPattern.draw(canvas);
+
+ float[] hsv = new float[] {
+ mHue, mSat, mVal
+ };
+ int color = Color.HSVToColor(hsv);
+ int acolor = Color.HSVToColor(0, hsv);
+
+ mAlphaShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top,
+ color, acolor, TileMode.CLAMP);
+
+ mAlphaPaint.setShader(mAlphaShader);
+
+ canvas.drawRect(rect, mAlphaPaint);
+
+ canvas.drawText(ALPHA_SLIDER_TEXT, rect.centerX(), rect.centerY() + 4 * mDensity,
+ mAlphaTextPaint);
+
+ float rectWidth = 4 * mDensity / 2;
+ Point p = alphaToPoint(mAlpha);
+
+ RectF r = new RectF();
+ r.left = p.x - rectWidth;
+ r.right = p.x + rectWidth;
+ r.top = rect.top - RECTANGLE_TRACKER_OFFSET;
+ r.bottom = rect.bottom + RECTANGLE_TRACKER_OFFSET;
+
+ canvas.drawRoundRect(r, 2, 2, mHueTrackerPaint);
+ }
+
+ private Point hueToPoint(float hue) {
+ final RectF rect = mHueRect;
+ final float height = rect.height();
+
+ Point p = new Point();
+ p.y = (int) (height - (hue * height / 360f) + rect.top);
+ p.x = (int) rect.left;
+ return p;
+ }
+
+ private Point satValToPoint(float sat, float val) {
+
+ final RectF rect = mSatValRect;
+ final float height = rect.height();
+ final float width = rect.width();
+
+ Point p = new Point();
+
+ p.x = (int) (sat * width + rect.left);
+ p.y = (int) ((1f - val) * height + rect.top);
+
+ return p;
+ }
+
+ private Point alphaToPoint(int alpha) {
+ final RectF rect = mAlphaRect;
+ final float width = rect.width();
+
+ Point p = new Point();
+ p.x = (int) (width - (alpha * width / 0xff) + rect.left);
+ p.y = (int) rect.top;
+ return p;
+ }
+
+ private float[] pointToSatVal(float x, float y) {
+ final RectF rect = mSatValRect;
+ float[] result = new float[2];
+ float width = rect.width();
+ float height = rect.height();
+
+ if (x < rect.left) {
+ x = 0f;
+ } else if (x > rect.right) {
+ x = width;
+ } else {
+ x = x - rect.left;
+ }
+
+ if (y < rect.top) {
+ y = 0f;
+ } else if (y > rect.bottom) {
+ y = height;
+ } else {
+ y = y - rect.top;
+ }
+
+ result[0] = 1.f / width * x;
+ result[1] = 1.f - (1.f / height * y);
+ return result;
+ }
+
+ private float pointToHue(float y) {
+ final RectF rect = mHueRect;
+ float height = rect.height();
+
+ if (y < rect.top) {
+ y = 0f;
+ } else if (y > rect.bottom) {
+ y = height;
+ } else {
+ y = y - rect.top;
+ }
+ return 360f - (y * 360f / height);
+ }
+
+ private int pointToAlpha(int x) {
+ final RectF rect = mAlphaRect;
+ final int width = (int) rect.width();
+
+ if (x < rect.left) {
+ x = 0;
+ } else if (x > rect.right) {
+ x = width;
+ } else {
+ x = x - (int) rect.left;
+ }
+ return 0xff - (x * 0xff / width);
+ }
+
+ @Override
+ public boolean onTrackballEvent(MotionEvent event) {
+ float x = event.getX();
+ float y = event.getY();
+ boolean update = false;
+
+ if (event.getAction() == MotionEvent.ACTION_MOVE) {
+ switch (mLastTouchedPanel) {
+ case PANEL_SAT_VAL:
+ float sat,
+ val;
+ sat = mSat + x / 50f;
+ val = mVal - y / 50f;
+ if (sat < 0f) {
+ sat = 0f;
+ } else if (sat > 1f) {
+ sat = 1f;
+ }
+
+ if (val < 0f) {
+ val = 0f;
+ } else if (val > 1f) {
+ val = 1f;
+ }
+ mSat = sat;
+ mVal = val;
+ update = true;
+ break;
+ case PANEL_HUE:
+ float hue = mHue - y * 10f;
+ if (hue < 0f) {
+ hue = 0f;
+ } else if (hue > 360f) {
+ hue = 360f;
+ }
+ mHue = hue;
+ update = true;
+ break;
+ case PANEL_ALPHA:
+ if (mShowAlphaPanel && mAlphaRect != null) {
+ int alpha = (int) (mAlpha - x * 10);
+ if (alpha < 0) {
+ alpha = 0;
+ } else if (alpha > 0xff) {
+ alpha = 0xff;
+ }
+ mAlpha = alpha;
+ update = true;
+ }
+ break;
+ }
+ }
+
+ if (update) {
+ if (mListener != null) {
+ mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[] {
+ mHue, mSat, mVal
+ }));
+ }
+ invalidate();
+ return true;
+ }
+ return super.onTrackballEvent(event);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ boolean update = false;
+
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ mStartTouchPoint = new Point((int) event.getX(), (int) event.getY());
+ update = moveTrackersIfNeeded(event);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ update = moveTrackersIfNeeded(event);
+ break;
+ case MotionEvent.ACTION_UP:
+ mStartTouchPoint = null;
+ update = moveTrackersIfNeeded(event);
+ break;
+ }
+
+ if (update) {
+ requestFocus();
+ if (mListener != null) {
+ mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[] {
+ mHue, mSat, mVal
+ }));
+ }
+ invalidate();
+ return true;
+ }
+
+ return super.onTouchEvent(event);
+ }
+
+ private boolean moveTrackersIfNeeded(MotionEvent event) {
+
+ if (mStartTouchPoint == null)
+ return false;
+
+ boolean update = false;
+ int startX = mStartTouchPoint.x;
+ int startY = mStartTouchPoint.y;
+
+ if (mHueRect.contains(startX, startY)) {
+ mLastTouchedPanel = PANEL_HUE;
+ mHue = pointToHue(event.getY());
+ update = true;
+ } else if (mSatValRect.contains(startX, startY)) {
+ mLastTouchedPanel = PANEL_SAT_VAL;
+ float[] result = pointToSatVal(event.getX(), event.getY());
+ mSat = result[0];
+ mVal = result[1];
+ update = true;
+ } else if (mAlphaRect != null && mAlphaRect.contains(startX, startY)) {
+ mLastTouchedPanel = PANEL_ALPHA;
+ mAlpha = pointToAlpha((int) event.getX());
+ update = true;
+ }
+
+ return update;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int width = 0;
+ int height = 0;
+
+ int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+
+ int widthAllowed = MeasureSpec.getSize(widthMeasureSpec);
+ int heightAllowed = MeasureSpec.getSize(heightMeasureSpec);
+
+ widthAllowed = chooseWidth(widthMode, widthAllowed);
+ heightAllowed = chooseHeight(heightMode, heightAllowed);
+
+ if (!mShowAlphaPanel) {
+ height = (int) (widthAllowed - PANEL_SPACING - HUE_PANEL_WIDTH);
+
+ // If calculated height (based on the width) is more than the
+ // allowed height.
+ if (height > heightAllowed && heightMode != MeasureSpec.UNSPECIFIED) {
+ height = heightAllowed;
+ width = (int) (height + PANEL_SPACING + HUE_PANEL_WIDTH);
+ } else {
+ width = widthAllowed;
+ }
+ } else {
+ width = (int) (heightAllowed - ALPHA_PANEL_HEIGHT + HUE_PANEL_WIDTH);
+
+ if (width > widthAllowed && widthMode != MeasureSpec.UNSPECIFIED) {
+ width = widthAllowed;
+ height = (int) (widthAllowed - HUE_PANEL_WIDTH + ALPHA_PANEL_HEIGHT);
+ } else {
+ height = heightAllowed;
+ }
+ }
+ setMeasuredDimension(width, height);
+ }
+
+ private int chooseWidth(int mode, int size) {
+ if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) {
+ return size;
+ } else { // (mode == MeasureSpec.UNSPECIFIED)
+ return getPrefferedWidth();
+ }
+ }
+
+ private int chooseHeight(int mode, int size) {
+ if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) {
+ return size;
+ } else { // (mode == MeasureSpec.UNSPECIFIED)
+ return getPrefferedHeight();
+ }
+ }
+
+ private int getPrefferedWidth() {
+ int width = getPrefferedHeight();
+ if (mShowAlphaPanel) {
+ width -= (PANEL_SPACING + ALPHA_PANEL_HEIGHT);
+ }
+ return (int) (width + HUE_PANEL_WIDTH + PANEL_SPACING);
+ }
+
+ private int getPrefferedHeight() {
+ int height = (int) (200 * mDensity);
+ if (mShowAlphaPanel) {
+ height += PANEL_SPACING + ALPHA_PANEL_HEIGHT;
+ }
+ return height;
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+
+ mDrawingRect = new RectF();
+ mDrawingRect.left = mDrawingOffset + getPaddingLeft();
+ mDrawingRect.right = w - mDrawingOffset - getPaddingRight();
+ mDrawingRect.top = mDrawingOffset + getPaddingTop();
+ mDrawingRect.bottom = h - mDrawingOffset - getPaddingBottom();
+
+ setUpSatValRect();
+ setUpHueRect();
+ setUpAlphaRect();
+ }
+
+ private void setUpSatValRect() {
+ final RectF dRect = mDrawingRect;
+ float panelSide = dRect.height() - BORDER_WIDTH_PX * 2;
+
+ if (mShowAlphaPanel) {
+ panelSide -= PANEL_SPACING + ALPHA_PANEL_HEIGHT;
+ }
+
+ float left = dRect.left + BORDER_WIDTH_PX;
+ float top = dRect.top + BORDER_WIDTH_PX;
+ float bottom = top + panelSide;
+ float right = left + panelSide;
+ mSatValRect = new RectF(left, top, right, bottom);
+ }
+
+ private void setUpHueRect() {
+ final RectF dRect = mDrawingRect;
+
+ float left = dRect.right - HUE_PANEL_WIDTH + BORDER_WIDTH_PX;
+ float top = dRect.top + BORDER_WIDTH_PX;
+ float bottom = dRect.bottom - BORDER_WIDTH_PX
+ - (mShowAlphaPanel ? (PANEL_SPACING + ALPHA_PANEL_HEIGHT) : 0);
+ float right = dRect.right - BORDER_WIDTH_PX;
+
+ mHueRect = new RectF(left, top, right, bottom);
+ }
+
+ private void setUpAlphaRect() {
+ if (!mShowAlphaPanel) {
+ return;
+ }
+
+ final RectF dRect = mDrawingRect;
+ float left = dRect.left + BORDER_WIDTH_PX;
+ float top = dRect.bottom - ALPHA_PANEL_HEIGHT + BORDER_WIDTH_PX;
+ float bottom = dRect.bottom - BORDER_WIDTH_PX;
+ float right = dRect.right - BORDER_WIDTH_PX;
+
+ mAlphaRect = new RectF(left, top, right, bottom);
+ mAlphaPattern = new AlphaPatternDrawable((int) (5 * mDensity));
+ mAlphaPattern.setBounds(Math.round(mAlphaRect.left), Math
+ .round(mAlphaRect.top), Math.round(mAlphaRect.right), Math
+ .round(mAlphaRect.bottom));
+ }
+
+ /**
+ * Set a OnColorChangedListener to get notified when the color selected by
+ * the user has changed.
+ */
+ public void setOnColorChangedListener(OnColorChangedListener listener) {
+ mListener = listener;
+ }
+
+ /**
+ * Get the current color this view is showing.
+ *
+ * @return the current color.
+ */
+ public int getColor() {
+ return Color.HSVToColor(mAlpha, new float[] {
+ mHue, mSat, mVal
+ });
+ }
+
+ /**
+ * Set the color the view should show.
+ *
+ * @param color The color that should be selected.
+ */
+ public void setColor(int color) {
+ setColor(color, false);
+ }
+
+ /**
+ * Set the color this view should show.
+ *
+ * @param color The color that should be selected.
+ * @param callback If you want to get a callback to your
+ * OnColorChangedListener.
+ */
+ public void setColor(int color, boolean callback) {
+ int alpha = Color.alpha(color);
+ int red = Color.red(color);
+ int blue = Color.blue(color);
+ int green = Color.green(color);
+ float[] hsv = new float[3];
+
+ Color.RGBToHSV(red, green, blue, hsv);
+ mAlpha = alpha;
+ mHue = hsv[0];
+ mSat = hsv[1];
+ mVal = hsv[2];
+
+ if (callback && mListener != null) {
+ mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[] {
+ mHue, mSat, mVal
+ }));
+ }
+ invalidate();
+ }
+
+ /**
+ * Set if the user is allowed to adjust the alpha panel. Default is false.
+ * If it is set to false no alpha will be set.
+ */
+ public void setAlphaSliderVisible(boolean visible) {
+ if (mShowAlphaPanel != visible) {
+ mShowAlphaPanel = visible;
+
+ /*
+ * Reset all shader to force a recreation. Otherwise they will not
+ * look right after the size of the view has changed.
+ */
+ mValShader = null;
+ mSatShader = null;
+ mHueShader = null;
+ mAlphaShader = null;
+ requestLayout();
+ }
+
+ }
+
+ public boolean isAlphaSliderVisible() {
+ return mShowAlphaPanel;
+ }
+}
diff --git a/src/com/android/settings/derp/notificationlight/LightSettingsDialog.java b/src/com/android/settings/derp/notificationlight/LightSettingsDialog.java
new file mode 100644
index 00000000000..1a0eaf5f0d3
--- /dev/null
+++ b/src/com/android/settings/derp/notificationlight/LightSettingsDialog.java
@@ -0,0 +1,457 @@
+/*
+ * SPDX-FileCopyrightText: 2010 Daniel Nilsson
+ * SPDX-FileCopyrightText: 2012 The CyanogenMod Project
+ * SPDX-FileCopyrightText: 2017-2024 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.android.settings.derp.notificationlight;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.text.InputFilter;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnFocusChangeListener;
+import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
+import android.widget.SpinnerAdapter;
+import android.widget.TextView;
+
+import androidx.appcompat.app.AlertDialog;
+
+import com.android.internal.derp.notification.LedValues;
+import com.android.internal.derp.notification.LightsCapabilities;
+import com.android.internal.derp.notification.LineageNotification;
+
+import com.android.settings.R;
+
+import java.util.ArrayList;
+import java.util.Locale;
+
+public class LightSettingsDialog extends AlertDialog implements
+ ColorPickerView.OnColorChangedListener, TextWatcher, OnFocusChangeListener {
+
+ private final static String STATE_KEY_COLOR = "LightSettingsDialog:color";
+ // Minimum delay between LED notification updates
+ private final static long LED_UPDATE_DELAY_MS = 250;
+
+ private ColorPickerView mColorPicker;
+
+ private EditText mHexColorInput;
+ private ColorPanelView mNewColor;
+ private PulseSpeedAdapter mPulseSpeedAdapterOn;
+ private PulseSpeedAdapter mPulseSpeedAdapterOff;
+ private Spinner mPulseSpeedOn;
+ private Spinner mPulseSpeedOff;
+ private LayoutInflater mInflater;
+
+ private NotificationManager mNotificationManager;
+
+ private boolean mReadyForLed;
+ private int mLedLastColor;
+ private int mLedLastSpeedOn;
+ private int mLedLastSpeedOff;
+
+ private int mLedBrightness;
+ private int mLedLastBrightness;
+
+ private Context mContext;
+
+ protected LightSettingsDialog(Context context, int initialColor, int initialSpeedOn,
+ int initialSpeedOff) {
+ super(context);
+
+ init(context, initialColor, initialSpeedOn, initialSpeedOff, true, 0);
+ }
+
+ protected LightSettingsDialog(Context context, int initialColor, int initialSpeedOn,
+ int initialSpeedOff, boolean onOffChangeable, int brightness) {
+ super(context);
+
+ init(context, initialColor, initialSpeedOn, initialSpeedOff, onOffChangeable, brightness);
+ }
+
+ private void init(Context context, int color, int speedOn, int speedOff,
+ boolean onOffChangeable, int brightness) {
+ mContext = context;
+ mNotificationManager = mContext.getSystemService(NotificationManager.class);
+
+ mReadyForLed = false;
+ mLedLastColor = 0;
+
+ // To fight color banding.
+ getWindow().setFormat(PixelFormat.RGBA_8888);
+ setUp(color, speedOn, speedOff, onOffChangeable, brightness);
+ }
+
+ /**
+ * This function sets up the dialog with the proper values. If the speedOff parameters
+ * has a -1 value disable both spinners
+ *
+ * @param color - the color to set
+ * @param speedOn - the flash time in ms
+ * @param speedOff - the flash length in ms
+ */
+ private void setUp(int color, int speedOn, int speedOff, boolean onOffChangeable,
+ int brightness) {
+ mInflater = mContext.getSystemService(LayoutInflater.class);
+ View layout = mInflater.inflate(R.layout.dialog_light_settings, null);
+
+ mColorPicker = layout.findViewById(R.id.color_picker_view);
+ mHexColorInput = layout.findViewById(R.id.hex_color_input);
+ mNewColor = layout.findViewById(R.id.color_panel);
+ mPulseSpeedOn = layout.findViewById(R.id.on_spinner);
+ mPulseSpeedOff = layout.findViewById(R.id.off_spinner);
+ mColorPicker.setOnColorChangedListener(this);
+ mColorPicker.setColor(color, true);
+
+ mHexColorInput.setOnFocusChangeListener(this);
+
+ if (onOffChangeable) {
+ mPulseSpeedAdapterOn = new PulseSpeedAdapter(
+ R.array.notification_pulse_length_entries,
+ R.array.notification_pulse_length_values,
+ speedOn);
+ mPulseSpeedOn.setAdapter(mPulseSpeedAdapterOn);
+ mPulseSpeedOn.setSelection(mPulseSpeedAdapterOn.getTimePosition(speedOn));
+ mPulseSpeedOn.setOnItemSelectedListener(mPulseSelectionListener);
+
+ mPulseSpeedAdapterOff = new PulseSpeedAdapter(R.array.notification_pulse_speed_entries,
+ R.array.notification_pulse_speed_values,
+ speedOff);
+ mPulseSpeedOff.setAdapter(mPulseSpeedAdapterOff);
+ mPulseSpeedOff.setSelection(mPulseSpeedAdapterOff.getTimePosition(speedOff));
+ mPulseSpeedOff.setOnItemSelectedListener(mPulseSelectionListener);
+ } else {
+ View speedSettingsGroup = layout.findViewById(R.id.speed_title_view);
+ speedSettingsGroup.setVisibility(View.GONE);
+ }
+
+ mPulseSpeedOn.setEnabled(onOffChangeable);
+ mPulseSpeedOff.setEnabled((speedOn != 1) && onOffChangeable);
+
+ setView(layout);
+ setTitle(R.string.edit_light_settings);
+
+ if (!LightsCapabilities.supports(
+ mContext, LightsCapabilities.LIGHTS_RGB_NOTIFICATION_LED)) {
+ mColorPicker.setVisibility(View.GONE);
+ LinearLayout colorPanel = layout.findViewById(R.id.color_panel_view);
+ colorPanel.setVisibility(View.GONE);
+ View lightsDialogDivider = layout.findViewById(R.id.lights_dialog_divider);
+ lightsDialogDivider.setVisibility(View.GONE);
+ }
+
+ mLedBrightness = brightness;
+ mLedLastBrightness = -1; // out of range
+
+ mReadyForLed = true;
+ updateLed();
+ }
+
+ private final AdapterView.OnItemSelectedListener mPulseSelectionListener =
+ new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView> parent, View view, int position, long id) {
+ if (parent == mPulseSpeedOn) {
+ mPulseSpeedOff.setEnabled(mPulseSpeedOn.isEnabled() && getPulseSpeedOn() != 1);
+ }
+ updateLed();
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView> parent) {
+ }
+ };
+
+ @Override
+ public Bundle onSaveInstanceState() {
+ Bundle state = super.onSaveInstanceState();
+ state.putInt(STATE_KEY_COLOR, getColor());
+ return state;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Bundle state) {
+ super.onRestoreInstanceState(state);
+ mColorPicker.setColor(state.getInt(STATE_KEY_COLOR), true);
+ }
+
+ public void onPause() {
+ dismissLed();
+ }
+
+ public void onResume() {
+ updateLed();
+ }
+
+ @Override
+ public void onColorChanged(int color) {
+ final boolean hasAlpha = mColorPicker.isAlphaSliderVisible();
+ final String format = hasAlpha ? "%08x" : "%06x";
+ final int mask = hasAlpha ? 0xFFFFFFFF : 0x00FFFFFF;
+
+ mNewColor.setColor(color);
+ mHexColorInput.setText(String.format(Locale.US, format, color & mask));
+
+ updateLed();
+ }
+
+ public void setAlphaSliderVisible(boolean visible) {
+ mHexColorInput.setFilters(new InputFilter[] {
+ new InputFilter.LengthFilter(visible ? 8 : 6) } );
+ mColorPicker.setAlphaSliderVisible(visible);
+ }
+
+ public int getColor() {
+ return mColorPicker.getColor();
+ }
+
+ public void setColor(int color) {
+ mColorPicker.setColor(color, true);
+ }
+
+ @SuppressWarnings("unchecked")
+ public int getPulseSpeedOn() {
+ if (mPulseSpeedOn.isEnabled()) {
+ return ((Pair) mPulseSpeedOn.getSelectedItem()).second;
+ } else {
+ return 1;
+ }
+ }
+
+ public void setPulseSpeedOn(int speedOn) {
+ mPulseSpeedOn.setSelection(mPulseSpeedAdapterOn.getTimePosition(speedOn));
+ }
+
+ @SuppressWarnings("unchecked")
+ public int getPulseSpeedOff() {
+ // return 0 if 'Always on' is selected
+ return getPulseSpeedOn() == 1
+ ? 0
+ : ((Pair) mPulseSpeedOff.getSelectedItem()).second;
+ }
+
+ public void setPulseSpeedOff(int speedOff) {
+ mPulseSpeedOff.setSelection(mPulseSpeedAdapterOff.getTimePosition(speedOff));
+ }
+
+ private final Handler mLedHandler = new Handler(Looper.getMainLooper()) {
+ public void handleMessage(Message msg) {
+ updateLed();
+ }
+ };
+
+ private void updateLed() {
+ if (!mReadyForLed) {
+ return;
+ }
+
+ final int color = getColor() & 0xFFFFFF;
+ final int speedOn, speedOff;
+ if (mPulseSpeedOn.isEnabled()) {
+ speedOn = getPulseSpeedOn();
+ speedOff = getPulseSpeedOff();
+ } else {
+ speedOn = 1;
+ speedOff = 0;
+ }
+
+ if (mLedLastColor == color && mLedLastSpeedOn == speedOn && mLedLastSpeedOff == speedOff
+ && mLedLastBrightness == mLedBrightness) {
+ return;
+ }
+
+ // Dampen rate of consecutive LED changes
+ if (mLedHandler.hasMessages(0)) {
+ return;
+ }
+ mLedHandler.sendEmptyMessageDelayed(0, LED_UPDATE_DELAY_MS);
+
+ // Set a notification to display the LED color
+ final Bundle b = new Bundle();
+ b.putBoolean(LineageNotification.EXTRA_FORCE_SHOW_LIGHTS, true);
+ if (mLedBrightness > 0 && mLedBrightness < LedValues.LIGHT_BRIGHTNESS_MAXIMUM) {
+ b.putInt(LineageNotification.EXTRA_FORCE_LIGHT_BRIGHTNESS, mLedBrightness);
+ }
+ b.putInt(LineageNotification.EXTRA_FORCE_COLOR, color);
+ b.putInt(LineageNotification.EXTRA_FORCE_LIGHT_ON_MS, speedOn);
+ b.putInt(LineageNotification.EXTRA_FORCE_LIGHT_OFF_MS, speedOff);
+
+ createNotificationChannel();
+
+ final String channelId = mContext.getString(R.string.channel_light_settings_id);
+ final Notification.Builder builder = new Notification.Builder(mContext, channelId);
+ builder.setLights(color, speedOn, speedOff);
+ builder.setExtras(b);
+ builder.setSmallIcon(R.drawable.ic_settings_24dp);
+ builder.setContentTitle(mContext.getString(R.string.led_notification_title));
+ builder.setContentText(mContext.getString(R.string.led_notification_text));
+ builder.setOngoing(true);
+
+ final Notification notification = builder.build();
+ mNotificationManager.notify(channelId, 1, notification);
+
+ mLedLastColor = color;
+ mLedLastSpeedOn = speedOn;
+ mLedLastSpeedOff = speedOff;
+ mLedLastBrightness = mLedBrightness;
+ }
+
+ public void dismissLed() {
+ final String channelId = mContext.getString(R.string.channel_light_settings_id);
+ mNotificationManager.cancel(channelId, 1);
+ // ensure we later reset LED if dialog is
+ // hidden and then made visible
+ mLedLastColor = 0;
+ }
+
+ private void createNotificationChannel() {
+ final String channelId = mContext.getString(R.string.channel_light_settings_id);
+ final String channelName = mContext.getString(R.string.channel_light_settings_name);
+ final NotificationChannel notificationChannel = new NotificationChannel(
+ channelId, channelName, NotificationManager.IMPORTANCE_LOW);
+ notificationChannel.enableLights(true);
+ notificationChannel.enableVibration(false);
+ notificationChannel.setShowBadge(false);
+
+ mNotificationManager.createNotificationChannel(notificationChannel);
+ }
+
+ class PulseSpeedAdapter extends BaseAdapter implements SpinnerAdapter {
+ private final ArrayList> times;
+
+ public PulseSpeedAdapter(int timeNamesResource, int timeValuesResource) {
+ times = new ArrayList<>();
+
+ String[] time_names = mContext.getResources().getStringArray(timeNamesResource);
+ String[] time_values = mContext.getResources().getStringArray(timeValuesResource);
+
+ for(int i = 0; i < time_values.length; ++i) {
+ times.add(new Pair<>(time_names[i], Integer.decode(time_values[i])));
+ }
+
+ }
+
+ /**
+ * This constructor apart from taking a usual time entry array takes the
+ * currently configured time value which might cause the addition of a
+ * "Custom" time entry in the spinner in case this time value does not
+ * match any of the predefined ones in the array.
+ *
+ * @param timeNamesResource The time entry names array
+ * @param timeValuesResource The time entry values array
+ * @param customTime Current time value that might be one of the
+ * predefined values or a totally custom value
+ */
+ public PulseSpeedAdapter(int timeNamesResource, int timeValuesResource,
+ Integer customTime) {
+ this(timeNamesResource, timeValuesResource);
+
+ // Check if we also need to add the custom value entry
+ if (getTimePosition(customTime) == -1) {
+ times.add(new Pair<>(mContext.getResources()
+ .getString(R.string.custom_time), customTime));
+ }
+ }
+
+ /**
+ * Will return the position of the spinner entry with the specified
+ * time. Returns -1 if there is no such entry.
+ *
+ * @param time Time in ms
+ * @return Position of entry with given time or -1 if not found.
+ */
+ public int getTimePosition(Integer time) {
+ for (int position = 0; position < getCount(); ++position) {
+ if (getItem(position).second.equals(time)) {
+ return position;
+ }
+ }
+
+ return -1;
+ }
+
+ @Override
+ public int getCount() {
+ return times.size();
+ }
+
+ @Override
+ public Pair getItem(int position) {
+ return times.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View view, ViewGroup parent) {
+ if (view == null) {
+ view = mInflater.inflate(R.layout.pulse_time_item, parent, false);
+ }
+
+ Pair entry = getItem(position);
+ ((TextView) view.findViewById(R.id.textViewName)).setText(entry.first);
+
+ return view;
+ }
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ String hexColor = mHexColorInput.getText().toString();
+ if (!hexColor.isEmpty()) {
+ try {
+ int color = Color.parseColor('#' + hexColor);
+ if (!mColorPicker.isAlphaSliderVisible()) {
+ color |= 0xFF000000; // set opaque
+ }
+ mColorPicker.setColor(color);
+ mNewColor.setColor(color);
+ updateLed();
+ } catch (IllegalArgumentException ex) {
+ // Number format is incorrect, ignore
+ }
+ }
+ }
+
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (!hasFocus) {
+ mHexColorInput.removeTextChangedListener(this);
+ InputMethodManager inputMethodManager =
+ mContext.getSystemService(InputMethodManager.class);
+ inputMethodManager.hideSoftInputFromWindow(v.getWindowToken(), 0);
+ } else {
+ mHexColorInput.addTextChangedListener(this);
+ }
+ }
+}
diff --git a/src/com/android/settings/derp/notificationlight/NotificationBrightnessPreference.java b/src/com/android/settings/derp/notificationlight/NotificationBrightnessPreference.java
new file mode 100644
index 00000000000..3bbb35b575f
--- /dev/null
+++ b/src/com/android/settings/derp/notificationlight/NotificationBrightnessPreference.java
@@ -0,0 +1,37 @@
+/*
+ * SPDX-FileCopyrightText: 2017-2022 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.android.settings.derp.notificationlight;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.AttributeSet;
+
+public class NotificationBrightnessPreference extends BrightnessPreference {
+ private static final String TAG = "NotificationBrightnessPreference";
+
+ private final ContentResolver mResolver;
+
+ public NotificationBrightnessPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mResolver = context.getContentResolver();
+ }
+
+ @Override
+ protected int getBrightnessSetting() {
+ return Settings.System.getIntForUser(mResolver,
+ Settings.System.NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL,
+ LIGHT_BRIGHTNESS_MAXIMUM, UserHandle.USER_CURRENT);
+ }
+
+ @Override
+ protected void setBrightnessSetting(int brightness) {
+ Settings.System.putIntForUser(mResolver,
+ Settings.System.NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL,
+ brightness, UserHandle.USER_CURRENT);
+ }
+}
diff --git a/src/com/android/settings/derp/notificationlight/NotificationBrightnessZenPreference.java b/src/com/android/settings/derp/notificationlight/NotificationBrightnessZenPreference.java
new file mode 100644
index 00000000000..bf6140b59bf
--- /dev/null
+++ b/src/com/android/settings/derp/notificationlight/NotificationBrightnessZenPreference.java
@@ -0,0 +1,37 @@
+/*
+ * SPDX-FileCopyrightText: 2017-2022 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.android.settings.derp.notificationlight;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.AttributeSet;
+
+public class NotificationBrightnessZenPreference extends BrightnessPreference {
+ private static final String TAG = "NotificationBrightnessZenPreference";
+
+ private final ContentResolver mResolver;
+
+ public NotificationBrightnessZenPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mResolver = context.getContentResolver();
+ }
+
+ @Override
+ protected int getBrightnessSetting() {
+ return Settings.System.getIntForUser(mResolver,
+ Settings.System.NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL_ZEN,
+ LIGHT_BRIGHTNESS_MAXIMUM, UserHandle.USER_CURRENT);
+ }
+
+ @Override
+ protected void setBrightnessSetting(int brightness) {
+ Settings.System.putIntForUser(mResolver,
+ Settings.System.NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL_ZEN,
+ brightness, UserHandle.USER_CURRENT);
+ }
+}
diff --git a/src/com/android/settings/derp/notificationlight/NotificationLightPreferenceController.java b/src/com/android/settings/derp/notificationlight/NotificationLightPreferenceController.java
new file mode 100644
index 00000000000..eac27700293
--- /dev/null
+++ b/src/com/android/settings/derp/notificationlight/NotificationLightPreferenceController.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The PixelExperience Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.derp.notificationlight;
+
+import android.content.Context;
+import com.android.settings.core.BasePreferenceController;
+
+public class NotificationLightPreferenceController extends BasePreferenceController {
+
+ public static final String KEY = "notification_lights";
+
+ private Context mContext;
+
+ public NotificationLightPreferenceController(Context context, String key) {
+ super(context, key);
+
+ mContext = context;
+ }
+
+ public NotificationLightPreferenceController(Context context) {
+ this(context, KEY);
+
+ mContext = context;
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ boolean exists = mContext.getResources().getBoolean(com.android.internal.R.bool.config_intrusiveNotificationLed);
+ return (exists ? AVAILABLE : UNSUPPORTED_ON_DEVICE);
+ }
+
+}
diff --git a/src/com/android/settings/derp/notificationlight/NotificationLightSettings.java b/src/com/android/settings/derp/notificationlight/NotificationLightSettings.java
new file mode 100644
index 00000000000..304c47068eb
--- /dev/null
+++ b/src/com/android/settings/derp/notificationlight/NotificationLightSettings.java
@@ -0,0 +1,611 @@
+/*
+ * SPDX-FileCopyrightText: 2012 The CyanogenMod Project
+ * SPDX-FileCopyrightText: 2017-2024 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.android.settings.derp.notificationlight;
+
+import android.app.Dialog;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.widget.ListView;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.PreferenceScreen;
+
+import com.android.internal.derp.notification.LightsCapabilities;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.derp.ColorUtils;
+
+import com.android.settings.R;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.derp.widget.PackageListAdapter;
+import com.android.settings.derp.widget.PackageListAdapter.PackageItem;
+import com.android.settings.SettingsPreferenceFragment;
+
+import com.android.settingslib.search.SearchIndexable;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.derpfest.support.preferences.SystemSettingSwitchPreference;
+import org.derpfest.support.preferences.SystemSettingMainSwitchPreference;
+
+@SearchIndexable
+public class NotificationLightSettings extends SettingsPreferenceFragment implements
+ ApplicationLightPreference.ItemLongClickListener, Preference.OnPreferenceChangeListener {
+ private static final String TAG = "NotificationLightSettings";
+
+ private static final String KEY_NOTIFICATION_LIGHTS = "notification_lights";
+ private static final String NOTIFICATION_LIGHT_PULSE =
+ Settings.System.NOTIFICATION_LIGHT_PULSE;
+ private static final String NOTIFICATION_LIGHT_COLOR_AUTO =
+ Settings.System.NOTIFICATION_LIGHT_COLOR_AUTO;
+ private static final String NOTIFICATION_LIGHT_SCREEN_ON =
+ Settings.System.NOTIFICATION_LIGHT_SCREEN_ON;
+ private static final String NOTIFICATION_LIGHT_PULSE_CUSTOM_ENABLE =
+ Settings.System.NOTIFICATION_LIGHT_PULSE_CUSTOM_ENABLE;
+ private static final String NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL =
+ Settings.System.NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL;
+ private static final String NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL_ZEN =
+ Settings.System.NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL_ZEN;
+
+ private static final String ADVANCED_SECTION = "advanced_section";
+ private static final String APPLICATION_SECTION = "applications_list";
+ private static final String BRIGHTNESS_SECTION = "brightness_section";
+ private static final String GENERAL_SECTION = "general_section";
+ private static final String PHONE_SECTION = "phone_list";
+
+ private static final String DEFAULT_PREF = "default";
+ private static final String MISSED_CALL_PREF = "missed_call";
+ private static final String VOICEMAIL_PREF = "voicemail";
+ private static final String ADD_APPS = "custom_apps_add";
+ private static final int DIALOG_APPS = 0;
+
+ private int mDefaultColor;
+ private int mDefaultLedOn;
+ private int mDefaultLedOff;
+ private PackageManager mPackageManager;
+ private PreferenceGroup mApplicationPrefList;
+ private SystemSettingMainSwitchPreference mEnabledPref;
+ private SystemSettingSwitchPreference mCustomEnabledPref;
+ private SystemSettingSwitchPreference mScreenOnLightsPref;
+ private SystemSettingSwitchPreference mAutoGenerateColors;
+ private ApplicationLightPreference mDefaultPref;
+ private ApplicationLightPreference mCallPref;
+ private ApplicationLightPreference mVoicemailPref;
+ private PackageListAdapter mPackageAdapter;
+ private String mPackageList;
+ private Map mPackages;
+ // Supports rgb color control
+ private boolean mMultiColorLed;
+ // Supports adjustable pulse
+ private boolean mLedCanPulse;
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ final Context context = requireContext();
+
+ addPreferencesFromResource(R.xml.notification_light_settings);
+ requireActivity().getActionBar().setTitle(R.string.notification_light_title);
+
+ PreferenceScreen prefSet = getPreferenceScreen();
+ Resources resources = getResources();
+
+ PreferenceGroup mAdvancedPrefs = prefSet.findPreference(ADVANCED_SECTION);
+ PreferenceGroup mGeneralPrefs = prefSet.findPreference(GENERAL_SECTION);
+
+ // Get the system defined default notification color
+ mDefaultColor = resources.getColor(
+ com.android.internal.R.color.config_defaultNotificationColor, null);
+
+ mDefaultLedOn = resources.getInteger(
+ com.android.internal.R.integer.config_defaultNotificationLedOn);
+ mDefaultLedOff = resources.getInteger(
+ com.android.internal.R.integer.config_defaultNotificationLedOff);
+
+ // liblights supports brightness control
+ final boolean halAdjustableBrightness = LightsCapabilities.supports(
+ context, LightsCapabilities.LIGHTS_ADJUSTABLE_NOTIFICATION_LED_BRIGHTNESS);
+ mLedCanPulse = LightsCapabilities.supports(
+ context, LightsCapabilities.LIGHTS_PULSATING_LED);
+ mMultiColorLed = LightsCapabilities.supports(
+ context, LightsCapabilities.LIGHTS_RGB_NOTIFICATION_LED);
+
+ mEnabledPref = findPreference(NOTIFICATION_LIGHT_PULSE);
+ mEnabledPref.setOnPreferenceChangeListener(this);
+
+ mDefaultPref = findPreference(DEFAULT_PREF);
+
+ mAutoGenerateColors = findPreference(NOTIFICATION_LIGHT_COLOR_AUTO);
+
+ // Advanced light settings
+ mScreenOnLightsPref = findPreference(NOTIFICATION_LIGHT_SCREEN_ON);
+ mScreenOnLightsPref.setOnPreferenceChangeListener(this);
+ mCustomEnabledPref = findPreference(NOTIFICATION_LIGHT_PULSE_CUSTOM_ENABLE);
+ if (!mMultiColorLed && !halAdjustableBrightness) {
+ removePreference(BRIGHTNESS_SECTION);
+ }
+ if (!mLedCanPulse && !mMultiColorLed) {
+ mGeneralPrefs.removePreference(mDefaultPref);
+ mAdvancedPrefs.removePreference(mCustomEnabledPref);
+ } else {
+ mCustomEnabledPref.setOnPreferenceChangeListener(this);
+ mDefaultPref.setOnPreferenceChangeListener(this);
+ mDefaultPref.setDefaultValues(mDefaultColor, mDefaultLedOn, mDefaultLedOff);
+ }
+
+ // Missed call and Voicemail preferences should only show on devices with voice capabilities
+ TelephonyManager tm = getActivity().getSystemService(TelephonyManager.class);
+ if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_NONE
+ || (!mLedCanPulse && !mMultiColorLed)) {
+ removePreference(PHONE_SECTION);
+ } else {
+ mCallPref = findPreference(MISSED_CALL_PREF);
+ mCallPref.setOnPreferenceChangeListener(this);
+ mCallPref.setDefaultValues(mDefaultColor, mDefaultLedOn, mDefaultLedOff);
+
+ mVoicemailPref = findPreference(VOICEMAIL_PREF);
+ mVoicemailPref.setOnPreferenceChangeListener(this);
+ mVoicemailPref.setDefaultValues(mDefaultColor, mDefaultLedOn, mDefaultLedOff);
+ }
+
+ if (!mLedCanPulse && !mMultiColorLed) {
+ removePreference(APPLICATION_SECTION);
+ } else {
+ mApplicationPrefList = findPreference(APPLICATION_SECTION);
+ mApplicationPrefList.setOrderingAsAdded(false);
+
+ // Get launch-able applications
+ mPackageManager = getActivity().getPackageManager();
+ mPackageAdapter = new PackageListAdapter(getActivity());
+
+ mPackages = new HashMap<>();
+
+ Preference addPreference = prefSet.findPreference(ADD_APPS);
+ addPreference.setOnPreferenceClickListener(preference -> {
+ showDialog(DIALOG_APPS);
+ return true;
+ });
+ }
+
+ if (!mMultiColorLed) {
+ resetColors();
+ mGeneralPrefs.removePreference(mAutoGenerateColors);
+ } else {
+ mAutoGenerateColors.setOnPreferenceChangeListener(this);
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ refreshDefault();
+ refreshCustomApplicationPrefs();
+ requireActivity().invalidateOptionsMenu();
+ }
+
+ private void refreshDefault() {
+ ContentResolver resolver = requireActivity().getContentResolver();
+ int color = Settings.System.getInt(resolver,
+ Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR, mDefaultColor);
+ int timeOn = Settings.System.getInt(resolver,
+ Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_ON, mDefaultLedOn);
+ int timeOff = Settings.System.getInt(resolver,
+ Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_OFF, mDefaultLedOff);
+
+ mDefaultPref.setAllValues(color, timeOn, timeOff);
+
+ // Get Missed call and Voicemail values
+ if (mCallPref != null) {
+ int callColor = Settings.System.getInt(resolver,
+ Settings.System.NOTIFICATION_LIGHT_PULSE_CALL_COLOR, mDefaultColor);
+ int callTimeOn = Settings.System.getInt(resolver,
+ Settings.System.NOTIFICATION_LIGHT_PULSE_CALL_LED_ON, mDefaultLedOn);
+ int callTimeOff = Settings.System.getInt(resolver,
+ Settings.System.NOTIFICATION_LIGHT_PULSE_CALL_LED_OFF, mDefaultLedOff);
+
+ mCallPref.setAllValues(callColor, callTimeOn, callTimeOff);
+ }
+
+ if (mVoicemailPref != null) {
+ int vmailColor = Settings.System.getInt(resolver,
+ Settings.System.NOTIFICATION_LIGHT_PULSE_VMAIL_COLOR, mDefaultColor);
+ int vmailTimeOn = Settings.System.getInt(resolver,
+ Settings.System.NOTIFICATION_LIGHT_PULSE_VMAIL_LED_ON, mDefaultLedOn);
+ int vmailTimeOff = Settings.System.getInt(resolver,
+ Settings.System.NOTIFICATION_LIGHT_PULSE_VMAIL_LED_OFF, mDefaultLedOff);
+
+ mVoicemailPref.setAllValues(vmailColor, vmailTimeOn, vmailTimeOff);
+ }
+
+ if (mLedCanPulse || mMultiColorLed) {
+ mApplicationPrefList = findPreference(APPLICATION_SECTION);
+ mApplicationPrefList.setOrderingAsAdded(false);
+ }
+ }
+
+ private void refreshCustomApplicationPrefs() {
+ Context context = getActivity();
+
+ if (!parsePackageList()) {
+ maybeDisplayApplicationHint(context);
+ return;
+ }
+
+ // Add the Application Preferences
+ if (mApplicationPrefList != null) {
+ for (int i = 0; i < mApplicationPrefList.getPreferenceCount();) {
+ Preference pref = mApplicationPrefList.getPreference(i);
+ if (ADD_APPS.equals(pref.getKey())) {
+ i++;
+ continue;
+ }
+
+ mApplicationPrefList.removePreference(pref);
+ }
+
+ for (Package pkg : mPackages.values()) {
+ try {
+ PackageInfo info = mPackageManager.getPackageInfo(pkg.name,
+ PackageManager.PackageInfoFlags.of(PackageManager.GET_META_DATA));
+ ApplicationLightPreference pref =
+ new ApplicationLightPreference(context, null,
+ pkg.color, pkg.timeon, pkg.timeoff);
+
+ pref.setKey(pkg.name);
+ pref.setTitle(info.applicationInfo.loadLabel(mPackageManager));
+ pref.setIcon(info.applicationInfo.loadIcon(mPackageManager));
+ pref.setPersistent(false);
+ pref.setOnPreferenceChangeListener(this);
+ pref.setOnLongClickListener(this);
+ mApplicationPrefList.addPreference(pref);
+ } catch (NameNotFoundException e) {
+ // Do nothing
+ }
+ }
+
+ maybeDisplayApplicationHint(context);
+ mPackageAdapter.setExcludedPackages(new HashSet<>(mPackages.keySet()));
+ }
+ }
+
+ private void maybeDisplayApplicationHint(Context context) {
+ /* Display a pref explaining how to add apps */
+ if (mApplicationPrefList != null && mApplicationPrefList.getPreferenceCount() == 1) {
+ String summary = getResources().getString(
+ R.string.notification_light_add_apps_empty_summary);
+ String useCustom = getResources().getString(
+ R.string.notification_light_use_custom);
+ Preference pref = new Preference(context);
+ pref.setSummary(String.format(summary, useCustom));
+ pref.setEnabled(false);
+ mApplicationPrefList.addPreference(pref);
+ }
+ }
+
+ private int getInitialColorForPackage(String packageName) {
+ boolean autoColor = Settings.System.getInt(getActivity().getContentResolver(),
+ NOTIFICATION_LIGHT_COLOR_AUTO, mMultiColorLed ? 1 : 0) == 1;
+ int color = mDefaultColor;
+ if (autoColor) {
+ try {
+ Drawable icon = mPackageManager.getApplicationIcon(packageName);
+ color = ColorUtils.generateAlertColorFromDrawable(icon);
+ } catch (NameNotFoundException e) {
+ // shouldn't happen, but just return default
+ }
+ }
+ return color;
+ }
+
+ private void addCustomApplicationPref(String packageName) {
+ Package pkg = mPackages.get(packageName);
+ if (pkg == null) {
+ int color = getInitialColorForPackage(packageName);
+ pkg = new Package(packageName, color, mDefaultLedOn, mDefaultLedOff);
+ mPackages.put(packageName, pkg);
+ savePackageList(false);
+ refreshCustomApplicationPrefs();
+ }
+ }
+
+ private void removeCustomApplicationPref(String packageName) {
+ if (mPackages.remove(packageName) != null) {
+ savePackageList(false);
+ refreshCustomApplicationPrefs();
+ }
+ }
+
+ private boolean parsePackageList() {
+ final String baseString = Settings.System.getString(
+ getActivity().getContentResolver(),
+ Settings.System.NOTIFICATION_LIGHT_PULSE_CUSTOM_VALUES);
+
+ if (TextUtils.equals(mPackageList, baseString)) {
+ return false;
+ }
+
+ mPackageList = baseString;
+ mPackages.clear();
+
+ if (baseString != null) {
+ final String[] array = TextUtils.split(baseString, "\\|");
+ for (String item : array) {
+ if (TextUtils.isEmpty(item)) {
+ continue;
+ }
+ Package pkg = Package.fromString(item);
+ if (pkg != null) {
+ mPackages.put(pkg.name, pkg);
+ }
+ }
+ }
+
+ mPackageAdapter.setExcludedPackages(new HashSet<>(mPackages.keySet()));
+
+ return true;
+ }
+
+ private void savePackageList(boolean preferencesUpdated) {
+ List settings = new ArrayList<>();
+ for (Package app : mPackages.values()) {
+ settings.add(app.toString());
+ }
+ final String value = TextUtils.join("|", settings);
+ if (preferencesUpdated) {
+ mPackageList = value;
+ }
+ Settings.System.putString(getActivity().getContentResolver(),
+ Settings.System.NOTIFICATION_LIGHT_PULSE_CUSTOM_VALUES, value);
+ }
+
+ /**
+ * Updates the default or package specific notification settings.
+ *
+ * @param packageName Package name of application specific settings to update
+ */
+ protected void updateValues(String packageName, Integer color, Integer timeOn,
+ Integer timeOff) {
+ ContentResolver resolver = requireActivity().getContentResolver();
+
+ switch (packageName) {
+ case DEFAULT_PREF:
+ Settings.System.putInt(resolver,
+ Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR, color);
+ Settings.System.putInt(resolver,
+ Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_ON, timeOn);
+ Settings.System.putInt(resolver,
+ Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_OFF, timeOff);
+ refreshDefault();
+ return;
+ case MISSED_CALL_PREF:
+ Settings.System.putInt(resolver,
+ Settings.System.NOTIFICATION_LIGHT_PULSE_CALL_COLOR, color);
+ Settings.System.putInt(resolver,
+ Settings.System.NOTIFICATION_LIGHT_PULSE_CALL_LED_ON, timeOn);
+ Settings.System.putInt(resolver,
+ Settings.System.NOTIFICATION_LIGHT_PULSE_CALL_LED_OFF, timeOff);
+ refreshDefault();
+ return;
+ case VOICEMAIL_PREF:
+ Settings.System.putInt(resolver,
+ Settings.System.NOTIFICATION_LIGHT_PULSE_VMAIL_COLOR, color);
+ Settings.System.putInt(resolver,
+ Settings.System.NOTIFICATION_LIGHT_PULSE_VMAIL_LED_ON, timeOn);
+ Settings.System.putInt(resolver,
+ Settings.System.NOTIFICATION_LIGHT_PULSE_VMAIL_LED_OFF, timeOff);
+ refreshDefault();
+ return;
+ }
+
+ // Find the custom package and sets its new values
+ Package app = mPackages.get(packageName);
+ if (app != null) {
+ app.color = color;
+ app.timeon = timeOn;
+ app.timeoff = timeOff;
+ savePackageList(true);
+ }
+ }
+
+ protected void resetColors() {
+ ContentResolver resolver = getActivity().getContentResolver();
+
+ // Reset to the framework default colors
+ Settings.System.putInt(resolver,
+ Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR, mDefaultColor);
+ Settings.System.putInt(resolver,
+ Settings.System.NOTIFICATION_LIGHT_PULSE_CALL_COLOR, mDefaultColor);
+ Settings.System.putInt(resolver,
+ Settings.System.NOTIFICATION_LIGHT_PULSE_VMAIL_COLOR, mDefaultColor);
+
+ refreshDefault();
+ }
+
+ public boolean onItemLongClick(final String key) {
+ if (mApplicationPrefList.findPreference(key) == null) {
+ return false;
+ }
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity())
+ .setTitle(R.string.dialog_delete_title)
+ .setMessage(R.string.dialog_delete_message)
+ .setIconAttribute(android.R.attr.alertDialogIcon)
+ .setPositiveButton(android.R.string.ok, (dialog, which) ->
+ removeCustomApplicationPref(key)
+ )
+ .setNegativeButton(android.R.string.cancel, null);
+
+ builder.show();
+ return true;
+ }
+
+ public boolean onPreferenceChange(Preference preference, Object objValue) {
+ if (preference == mEnabledPref || preference == mCustomEnabledPref ||
+ preference == mScreenOnLightsPref ||
+ preference == mAutoGenerateColors) {
+ getActivity().invalidateOptionsMenu();
+ } else {
+ ApplicationLightPreference lightPref = (ApplicationLightPreference) preference;
+ updateValues(lightPref.getKey(), lightPref.getColor(),
+ lightPref.getOnValue(), lightPref.getOffValue());
+ }
+
+ return true;
+ }
+
+ /**
+ * Utility classes and supporting methods
+ */
+ @Override
+ public Dialog onCreateDialog(int id) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity());
+ final Dialog dialog;
+ switch (id) {
+ case DIALOG_APPS:
+ Resources res = getResources();
+ int paddingTop = res.getDimensionPixelOffset(R.dimen.package_list_padding_top);
+
+ final ListView list = new ListView(requireActivity());
+ list.setAdapter(mPackageAdapter);
+ list.setDivider(null);
+ list.setPadding(0, paddingTop, 0, 0);
+
+ builder.setTitle(R.string.choose_app);
+ builder.setView(list);
+ dialog = builder.create();
+
+ list.setOnItemClickListener((parent, view, position, id1) -> {
+ // Add empty application definition, the user will be able to edit it later
+ PackageItem info = (PackageItem) parent.getItemAtPosition(position);
+ addCustomApplicationPref(info.packageName);
+ dialog.cancel();
+ });
+ break;
+ default:
+ dialog = null;
+ }
+ return dialog;
+ }
+
+ @Override
+ public int getDialogMetricsCategory(int dialogId) {
+ if (dialogId == DIALOG_APPS) {
+ return MetricsEvent.DERP;
+ }
+ return 0;
+ }
+
+ /**
+ * Application class
+ */
+ private static class Package {
+ public String name;
+ public Integer color;
+ public Integer timeon;
+ public Integer timeoff;
+
+ /**
+ * Stores all the application values in one call
+ */
+ public Package(String name, Integer color, Integer timeon, Integer timeoff) {
+ this.name = name;
+ this.color = color;
+ this.timeon = timeon;
+ this.timeoff = timeoff;
+ }
+
+ public String toString() {
+ return name + "=" + color + ";" + timeon + ";" + timeoff;
+ }
+
+ public static Package fromString(String value) {
+ if (TextUtils.isEmpty(value)) {
+ return null;
+ }
+ String[] app = value.split("=", -1);
+ if (app.length != 2)
+ return null;
+
+ String[] values = app[1].split(";", -1);
+ if (values.length != 3)
+ return null;
+
+ try {
+ return new Package(app[0], Integer.parseInt(values[0]), Integer
+ .parseInt(values[1]), Integer.parseInt(values[2]));
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ }
+
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsEvent.DERP;
+ }
+
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider(R.xml.notification_light_settings) {
+
+ @Override
+ public List getNonIndexableKeys(Context context) {
+ final List result = new ArrayList<>();
+
+ TelephonyManager tm = context.getSystemService(TelephonyManager.class);
+
+ if (!context.getResources().getBoolean(com.android.internal.R.bool
+ .config_intrusiveNotificationLed)) {
+ result.add(KEY_NOTIFICATION_LIGHTS);
+ result.add(NOTIFICATION_LIGHT_PULSE);
+ }
+ if (!LightsCapabilities.supports(context, LightsCapabilities.LIGHTS_PULSATING_LED) &&
+ !LightsCapabilities.supports(context,
+ LightsCapabilities.LIGHTS_RGB_NOTIFICATION_LED)) {
+ result.add(GENERAL_SECTION);
+ result.add(NOTIFICATION_LIGHT_COLOR_AUTO);
+ result.add(DEFAULT_PREF);
+ result.add(ADVANCED_SECTION);
+ result.add(NOTIFICATION_LIGHT_SCREEN_ON);
+ result.add(NOTIFICATION_LIGHT_PULSE_CUSTOM_ENABLE);
+ result.add(PHONE_SECTION);
+ result.add(MISSED_CALL_PREF);
+ result.add(VOICEMAIL_PREF);
+ result.add(APPLICATION_SECTION);
+ result.add(ADD_APPS);
+ } else if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_NONE) {
+ result.add(PHONE_SECTION);
+ result.add(MISSED_CALL_PREF);
+ result.add(VOICEMAIL_PREF);
+ }
+ if (!LightsCapabilities.supports(context,
+ LightsCapabilities.LIGHTS_ADJUSTABLE_BATTERY_LED_BRIGHTNESS)) {
+ result.add(BRIGHTNESS_SECTION);
+ result.add(NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL);
+ result.add(NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL_ZEN);
+ }
+ return result;
+ }
+ };
+}
diff --git a/src/com/android/settings/derp/preference/CustomDialogPreference.java b/src/com/android/settings/derp/preference/CustomDialogPreference.java
new file mode 100644
index 00000000000..135f12c5ef8
--- /dev/null
+++ b/src/com/android/settings/derp/preference/CustomDialogPreference.java
@@ -0,0 +1,208 @@
+/*
+ * SPDX-FileCopyrightText: 2015 The Android Open Source Project
+ * SPDX-FileCopyrightText: 2016 The CyanogenMod Project
+ * SPDX-FileCopyrightText: 2017,2019,2021-2022 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.android.settings.derp.preference;
+
+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.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
+import androidx.preference.DialogPreference;
+import androidx.preference.PreferenceDialogFragmentCompat;
+
+public class CustomDialogPreference extends DialogPreference {
+
+ private CustomPreferenceDialogFragment mFragment;
+
+ public CustomDialogPreference(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ public CustomDialogPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public CustomDialogPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public CustomDialogPreference(Context context) {
+ super(context);
+ }
+
+ public boolean isDialogOpen() {
+ return getDialog() != null && getDialog() instanceof Dialog &&
+ ((Dialog)getDialog()).isShowing();
+ }
+
+ public T getDialog() {
+ return (T) (mFragment != null ? mFragment.getDialog() : null);
+ }
+
+ protected void onPrepareDialogBuilder(AlertDialog.Builder builder,
+ DialogInterface.OnClickListener listener) {
+ }
+
+ protected void onDialogClosed(boolean positiveResult) {
+ }
+
+ protected void onClick(T dialog, int which) {
+ }
+
+ protected void onBindDialogView(View view) {
+ }
+
+ protected void onStart() {
+ }
+
+ protected void onStop() {
+ }
+
+ protected void onPause() {
+ }
+
+ protected void onResume() {
+ }
+
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ return null;
+ }
+
+ protected View onCreateDialogView(Context context) {
+ return null;
+ }
+
+ private void setFragment(CustomPreferenceDialogFragment fragment) {
+ mFragment = fragment;
+ }
+
+ protected boolean onDismissDialog(T dialog, int which) {
+ return true;
+ }
+
+ 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 CustomDialogPreference getCustomizablePreference() {
+ return (CustomDialogPreference) getPreference();
+ }
+
+ private class OnDismissListener implements View.OnClickListener {
+ private final int mWhich;
+ private final DialogInterface mDialog;
+
+ public OnDismissListener(DialogInterface dialog, int which) {
+ mWhich = which;
+ mDialog = dialog;
+ }
+
+ @Override
+ public void onClick(View view) {
+ CustomPreferenceDialogFragment.this.onClick(mDialog, mWhich);
+ if (getCustomizablePreference().onDismissDialog(mDialog, mWhich)) {
+ mDialog.dismiss();
+ }
+ }
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ if (getDialog() instanceof AlertDialog) {
+ AlertDialog a = (AlertDialog)getDialog();
+ if (a.getButton(Dialog.BUTTON_NEUTRAL) != null) {
+ a.getButton(Dialog.BUTTON_NEUTRAL).setOnClickListener(
+ new OnDismissListener(a, Dialog.BUTTON_NEUTRAL));
+ }
+ if (a.getButton(Dialog.BUTTON_POSITIVE) != null) {
+ a.getButton(Dialog.BUTTON_POSITIVE).setOnClickListener(
+ new OnDismissListener(a, Dialog.BUTTON_POSITIVE));
+ }
+ if (a.getButton(Dialog.BUTTON_NEGATIVE) != null) {
+ a.getButton(Dialog.BUTTON_NEGATIVE).setOnClickListener(
+ new OnDismissListener(a, Dialog.BUTTON_NEGATIVE));
+ }
+ }
+ getCustomizablePreference().onStart();
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ getCustomizablePreference().onStop();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ getCustomizablePreference().onPause();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ getCustomizablePreference().onResume();
+ }
+
+ @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 void onClick(DialogInterface dialog, int which) {
+ super.onClick(dialog, which);
+ getCustomizablePreference().onClick(dialog, which);
+ }
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ getCustomizablePreference().setFragment(this);
+ final Dialog sub = getCustomizablePreference().onCreateDialog(savedInstanceState);
+ if (sub == null) {
+ return super.onCreateDialog(savedInstanceState);
+ }
+ return sub;
+ }
+
+ @Override
+ protected View onCreateDialogView(Context context) {
+ final View v = getCustomizablePreference().onCreateDialogView(context);
+ if (v == null) {
+ return super.onCreateDialogView(context);
+ }
+ return v;
+ }
+ }
+}
diff --git a/src/com/android/settings/derp/statusbar/BatterySettings.java b/src/com/android/settings/derp/statusbar/BatterySettings.java
new file mode 100644
index 00000000000..df7781fc729
--- /dev/null
+++ b/src/com/android/settings/derp/statusbar/BatterySettings.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2024 Yet Another AOSP Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.derp.statusbar;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
+import android.text.format.DateFormat;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.Preference.OnPreferenceChangeListener;
+import androidx.preference.PreferenceFragment;
+import androidx.preference.SwitchPreferenceCompat;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.R;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.search.SearchIndexable;
+
+import org.derpfest.support.preferences.SystemSettingListPreference;
+import org.derpfest.support.preferences.SystemSettingSwitchPreference;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@SearchIndexable
+public class BatterySettings extends DashboardFragment implements
+ OnPreferenceChangeListener {
+
+ private static final String TAG = "BatterySettings";
+
+ private static final String BATTERY_STYLE = "status_bar_battery_style";
+ private static final String SHOW_BATTERY_PERCENT = "status_bar_show_battery_percent";
+ private static final String SHOW_BATTERY_PERCENT_CHARGING = "status_bar_show_battery_percent_charging";
+ private static final String SHOW_BATTERY_PERCENT_INSIDE = "status_bar_show_battery_percent_inside";
+
+ private SystemSettingListPreference mBatteryStyle;
+ private SystemSettingSwitchPreference mBatteryPercent;
+ private SystemSettingSwitchPreference mBatteryPercentCharging;
+ private SystemSettingSwitchPreference mBatteryPercentInside;
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.battery_settings;
+ }
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ PreferenceScreen prefSet = getPreferenceScreen();
+ final ContentResolver resolver = getActivity().getContentResolver();
+
+ mBatteryPercent = findPreference(SHOW_BATTERY_PERCENT);
+ final boolean percentEnabled = Settings.System.getIntForUser(resolver,
+ SHOW_BATTERY_PERCENT, 0, UserHandle.USER_CURRENT) == 1;
+ mBatteryPercent.setChecked(percentEnabled);
+ mBatteryPercent.setOnPreferenceChangeListener(this);
+
+ mBatteryPercentInside = findPreference(SHOW_BATTERY_PERCENT_INSIDE);
+ mBatteryPercentInside.setEnabled(percentEnabled);
+ final boolean percentInside = Settings.System.getIntForUser(resolver,
+ SHOW_BATTERY_PERCENT_INSIDE, 0, UserHandle.USER_CURRENT) == 1;
+ mBatteryPercentInside.setChecked(percentInside);
+ mBatteryPercentInside.setOnPreferenceChangeListener(this);
+
+ mBatteryStyle = findPreference(BATTERY_STYLE);
+ final int value = Settings.System.getIntForUser(resolver,
+ BATTERY_STYLE, 0, UserHandle.USER_CURRENT);
+ mBatteryStyle.setValue(Integer.toString(value));
+ mBatteryStyle.setSummary(mBatteryStyle.getEntry());
+ mBatteryStyle.setOnPreferenceChangeListener(this);
+ updatePercentEnablement(value != 2);
+
+ mBatteryPercentCharging = findPreference(SHOW_BATTERY_PERCENT_CHARGING);
+ updatePercentChargingEnablement(value, percentEnabled, percentInside);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object objValue) {
+ final ContentResolver resolver = getActivity().getContentResolver();
+ if (preference == mBatteryStyle) {
+ int value = Integer.valueOf((String) objValue);
+ int index = mBatteryStyle.findIndexOfValue((String) objValue);
+ mBatteryStyle.setSummary(mBatteryStyle.getEntries()[index]);
+ Settings.System.putIntForUser(resolver,
+ BATTERY_STYLE, value, UserHandle.USER_CURRENT);
+ updatePercentEnablement(value != 2);
+ updatePercentChargingEnablement(value, null, null);
+ return true;
+ } else if (preference == mBatteryPercent) {
+ boolean enabled = (boolean) objValue;
+ Settings.System.putInt(resolver,
+ SHOW_BATTERY_PERCENT, enabled ? 1 : 0);
+ mBatteryPercentInside.setEnabled(enabled);
+ updatePercentChargingEnablement(null, enabled, null);
+ return true;
+ } else if (preference == mBatteryPercentInside) {
+ boolean enabled = (boolean) objValue;
+ Settings.System.putInt(resolver,
+ SHOW_BATTERY_PERCENT_INSIDE, enabled ? 1 : 0);
+ // we already know style isn't text and percent is enabled
+ mBatteryPercentCharging.setEnabled(enabled);
+ return true;
+ }
+ return false;
+ }
+
+ private void updatePercentEnablement(boolean enabled) {
+ mBatteryPercent.setEnabled(enabled);
+ mBatteryPercentInside.setEnabled(enabled && mBatteryPercent.isChecked());
+ }
+
+ private void updatePercentChargingEnablement(Integer style, Boolean percent, Boolean inside) {
+ if (style == null) style = Integer.valueOf(mBatteryStyle.getValue());
+ if (percent == null) percent = mBatteryPercent.isChecked();
+ if (inside == null) inside = mBatteryPercentInside.isChecked();
+ mBatteryPercentCharging.setEnabled(style != 2 && (!percent || inside));
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsProto.MetricsEvent.DERP;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider(R.xml.battery_settings);
+}
diff --git a/src/com/android/settings/derp/statusbar/NetworkTrafficSettings.java b/src/com/android/settings/derp/statusbar/NetworkTrafficSettings.java
new file mode 100644
index 00000000000..2f0ce945b76
--- /dev/null
+++ b/src/com/android/settings/derp/statusbar/NetworkTrafficSettings.java
@@ -0,0 +1,91 @@
+/*
+ * SPDX-FileCopyrightText: 2017-2023 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.android.settings.derp.statusbar;
+
+import android.content.ContentResolver;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.widget.Toast;
+
+import androidx.preference.DropDownPreference;
+import androidx.preference.Preference;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+
+import org.derpfest.support.preferences.SecureSettingMainSwitchPreference;
+import org.derpfest.support.preferences.SecureSettingSwitchPreference;
+
+public class NetworkTrafficSettings extends SettingsPreferenceFragment
+ implements Preference.OnPreferenceChangeListener {
+
+ private static final String TAG = "NetworkTrafficSettings";
+ private static final String STATUS_BAR_CLOCK_STYLE = "status_bar_clock";
+
+ private SecureSettingMainSwitchPreference mNetTraffic;
+ private SecureSettingSwitchPreference mNetTrafficAutohide;
+ private DropDownPreference mNetTrafficUnits;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.network_traffic_settings);
+ getActivity().setTitle(R.string.network_traffic_settings_title);
+
+ final ContentResolver resolver = getActivity().getContentResolver();
+
+ mNetTraffic = findPreference(Settings.Secure.NETWORK_TRAFFIC_MODE);
+
+ mNetTrafficAutohide = findPreference(Settings.Secure.NETWORK_TRAFFIC_AUTOHIDE);
+
+ mNetTrafficUnits = findPreference(Settings.Secure.NETWORK_TRAFFIC_UNITS);
+ mNetTrafficUnits.setOnPreferenceChangeListener(this);
+ int units = Settings.Secure.getInt(resolver,
+ Settings.Secure.NETWORK_TRAFFIC_UNITS, /* Mbps */ 1);
+ mNetTrafficUnits.setValue(String.valueOf(units));
+
+ updateForClockConflicts();
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ int value = Integer.parseInt((String) newValue);
+ String key = preference.getKey();
+ switch (key) {
+ case Settings.Secure.NETWORK_TRAFFIC_UNITS:
+ Settings.Secure.putInt(getActivity().getContentResolver(),
+ Settings.Secure.NETWORK_TRAFFIC_UNITS, value);
+ break;
+ }
+ return true;
+ }
+
+ private void updateEnabledStates(boolean enabled) {
+ mNetTrafficAutohide.setEnabled(enabled);
+ mNetTrafficUnits.setEnabled(enabled);
+ }
+
+ private void updateForClockConflicts() {
+ int clockPosition = Settings.System.getInt(getActivity().getContentResolver(),
+ STATUS_BAR_CLOCK_STYLE, 2);
+
+ if (clockPosition != 1) {
+ return;
+ }
+
+ mNetTraffic.setEnabled(false);
+ Toast.makeText(getActivity(),
+ R.string.network_traffic_disabled_clock,
+ Toast.LENGTH_LONG).show();
+ updateEnabledStates(false);
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsEvent.DERP;
+ }
+}
diff --git a/src/com/android/settings/derp/statusbar/QSPanelSettings.java b/src/com/android/settings/derp/statusbar/QSPanelSettings.java
new file mode 100644
index 00000000000..816ae108063
--- /dev/null
+++ b/src/com/android/settings/derp/statusbar/QSPanelSettings.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The LeafOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.derp.statusbar;
+
+import android.os.Bundle;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.search.SearchIndexable;
+
+@SearchIndexable
+public class QSPanelSettings extends DashboardFragment {
+
+ private static final String KEY_QS_SHOW_AUTO_BRIGHTNESS = "qs_show_auto_brightness";
+ private static final String TAG = "QSPanelSettings";
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ PreferenceScreen preferenceScreen = getPreferenceScreen();
+ Preference qsShowAutoBrightnessPreference = preferenceScreen.findPreference(KEY_QS_SHOW_AUTO_BRIGHTNESS);
+
+ if (qsShowAutoBrightnessPreference != null) {
+ boolean automaticBrightnessAvailable = getContext().getResources().getBoolean(
+ com.android.internal.R.bool.config_automatic_brightness_available);
+ if (!automaticBrightnessAvailable) {
+ qsShowAutoBrightnessPreference.setVisible(false);
+ }
+ }
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return METRICS_CATEGORY_UNKNOWN;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.qs_panel_settings;
+ }
+
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider(R.xml.qs_panel_settings);
+}
diff --git a/src/com/android/settings/derp/statusbar/StatusBarSettings.java b/src/com/android/settings/derp/statusbar/StatusBarSettings.java
new file mode 100644
index 00000000000..82920759f06
--- /dev/null
+++ b/src/com/android/settings/derp/statusbar/StatusBarSettings.java
@@ -0,0 +1,184 @@
+/*
+ * SPDX-FileCopyrightText: 2014-2015 The CyanogenMod Project
+ * SPDX-FileCopyrightText: 2017-2023 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package com.android.settings.derp.statusbar;
+
+import android.os.Bundle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.text.format.DateFormat;
+import android.view.View;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settings.derp.utils.DeviceUtils;
+import com.android.settings.R;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settingslib.search.SearchIndexable;
+
+import org.derpfest.support.preferences.SystemSettingListPreference;
+
+import java.util.Set;
+
+@SearchIndexable
+public class StatusBarSettings extends SettingsPreferenceFragment
+ implements Preference.OnPreferenceChangeListener {
+
+ private static final String CATEGORY_CLOCK = "status_bar_clock_key";
+
+ private static final String ICON_BLACKLIST = "icon_blacklist";
+
+ private static final String STATUS_BAR_CLOCK_STYLE = "status_bar_clock";
+ private static final String STATUS_BAR_AM_PM = "status_bar_am_pm";
+ private static final String STATUS_BAR_QUICK_QS_PULLDOWN = "qs_quick_pulldown";
+
+ private static final int PULLDOWN_DIR_NONE = 0;
+ private static final int PULLDOWN_DIR_RIGHT = 1;
+ private static final int PULLDOWN_DIR_LEFT = 2;
+
+ private static final String NETWORK_TRAFFIC_SETTINGS = "network_traffic_settings";
+
+ private SystemSettingListPreference mQuickPulldown;
+ private SystemSettingListPreference mStatusBarClock;
+ private SystemSettingListPreference mStatusBarAmPm;
+
+ private PreferenceCategory mStatusBarClockCategory;
+ private Preference mNetworkTrafficPref;
+
+ private boolean mHasCenteredCutout;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.status_bar_settings);
+
+ mNetworkTrafficPref = findPreference(NETWORK_TRAFFIC_SETTINGS);
+
+ mHasCenteredCutout = DeviceUtils.hasCenteredCutout(getActivity());
+
+ mStatusBarAmPm = findPreference(STATUS_BAR_AM_PM);
+ mStatusBarClock = findPreference(STATUS_BAR_CLOCK_STYLE);
+ mStatusBarClock.setOnPreferenceChangeListener(this);
+
+ mStatusBarClockCategory = getPreferenceScreen().findPreference(CATEGORY_CLOCK);
+
+ mQuickPulldown = findPreference(STATUS_BAR_QUICK_QS_PULLDOWN);
+ mQuickPulldown.setOnPreferenceChangeListener(this);
+ updateQuickPulldownSummary(mQuickPulldown.getIntValue(0));
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ final String curIconBlacklist = Settings.Secure.getString(getContext().getContentResolver(),
+ ICON_BLACKLIST);
+
+ if (TextUtils.delimitedStringContains(curIconBlacklist, ',', "clock")) {
+ getPreferenceScreen().removePreference(mStatusBarClockCategory);
+ } else {
+ getPreferenceScreen().addPreference(mStatusBarClockCategory);
+ }
+
+ if (DateFormat.is24HourFormat(getActivity())) {
+ mStatusBarAmPm.setEnabled(false);
+ mStatusBarAmPm.setSummary(R.string.status_bar_am_pm_info);
+ }
+
+ final boolean disallowCenteredClock = mHasCenteredCutout || getNetworkTrafficStatus() != 0;
+
+ // Adjust status bar preferences for RTL
+ if (getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
+ if (disallowCenteredClock) {
+ mStatusBarClock.setEntries(R.array.status_bar_clock_position_entries_notch_rtl);
+ mStatusBarClock.setEntryValues(R.array.status_bar_clock_position_values_notch);
+ } else {
+ mStatusBarClock.setEntries(R.array.status_bar_clock_position_entries_rtl);
+ mStatusBarClock.setEntryValues(R.array.status_bar_clock_position_values);
+ }
+ mQuickPulldown.setEntries(R.array.status_bar_quick_qs_pulldown_entries_rtl);
+ } else {
+ if (disallowCenteredClock) {
+ mStatusBarClock.setEntries(R.array.status_bar_clock_position_entries_notch);
+ mStatusBarClock.setEntryValues(R.array.status_bar_clock_position_values_notch);
+ } else {
+ mStatusBarClock.setEntries(R.array.status_bar_clock_position_entries);
+ mStatusBarClock.setEntryValues(R.array.status_bar_clock_position_values);
+ }
+ mQuickPulldown.setEntries(R.array.status_bar_quick_qs_pulldown_entries);
+ }
+
+ // Disable network traffic preferences if clock is centered in the status bar
+ updateNetworkTrafficStatus(getClockPosition());
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ int value = Integer.parseInt((String) newValue);
+ String key = preference.getKey();
+ switch (key) {
+ case STATUS_BAR_QUICK_QS_PULLDOWN:
+ updateQuickPulldownSummary(value);
+ break;
+ case STATUS_BAR_CLOCK_STYLE:
+ updateNetworkTrafficStatus(value);
+ break;
+ }
+ return true;
+ }
+
+ private void updateQuickPulldownSummary(int value) {
+ String summary="";
+ switch (value) {
+ case PULLDOWN_DIR_NONE:
+ summary = getResources().getString(
+ R.string.status_bar_quick_qs_pulldown_off);
+ break;
+
+ case PULLDOWN_DIR_LEFT:
+ case PULLDOWN_DIR_RIGHT:
+ summary = getResources().getString(
+ R.string.status_bar_quick_qs_pulldown_summary,
+ getResources().getString(
+ (value == PULLDOWN_DIR_LEFT) ^
+ (getResources().getConfiguration().getLayoutDirection()
+ == View.LAYOUT_DIRECTION_RTL)
+ ? R.string.status_bar_quick_qs_pulldown_summary_left
+ : R.string.status_bar_quick_qs_pulldown_summary_right));
+ break;
+ }
+ mQuickPulldown.setSummary(summary);
+ }
+
+ private void updateNetworkTrafficStatus(int clockPosition) {
+ boolean isClockCentered = clockPosition == 1;
+ mNetworkTrafficPref.setEnabled(!isClockCentered);
+ mNetworkTrafficPref.setSummary(getResources().getString(isClockCentered ?
+ R.string.network_traffic_disabled_clock :
+ R.string.network_traffic_settings_summary
+ ));
+ }
+
+ private int getNetworkTrafficStatus() {
+ return Settings.Secure.getInt(getActivity().getContentResolver(),
+ Settings.Secure.NETWORK_TRAFFIC_MODE, 0);
+ }
+
+ private int getClockPosition() {
+ return Settings.System.getInt(getActivity().getContentResolver(),
+ STATUS_BAR_CLOCK_STYLE, 2);
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsEvent.DERP;
+ }
+
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider(R.xml.status_bar_settings);
+}
diff --git a/src/com/android/settings/derp/statusbar/StatusbarLyricSettings.java b/src/com/android/settings/derp/statusbar/StatusbarLyricSettings.java
new file mode 100644
index 00000000000..855d225299f
--- /dev/null
+++ b/src/com/android/settings/derp/statusbar/StatusbarLyricSettings.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 Project Kaleidoscope
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.derp.statusbar;
+
+import android.os.Bundle;
+
+import androidx.preference.Preference;
+
+import com.android.internal.logging.nano.MetricsProto;
+
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+
+public class StatusbarLyricSettings extends SettingsPreferenceFragment {
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ addPreferencesFromResource(R.xml.status_bar_lyric_settings);
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsProto.MetricsEvent.DERP;
+ }
+}
diff --git a/src/com/android/settings/derp/touch/HighTouchPollingRateSettingsPreferenceController.java b/src/com/android/settings/derp/touch/HighTouchPollingRateSettingsPreferenceController.java
new file mode 100644
index 00000000000..3c512dfb745
--- /dev/null
+++ b/src/com/android/settings/derp/touch/HighTouchPollingRateSettingsPreferenceController.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.settings.derp.touch;
+
+import android.content.Context;
+import com.android.internal.derp.hardware.LineageHardwareManager;
+import com.android.settings.core.BasePreferenceController;
+
+public class HighTouchPollingRateSettingsPreferenceController extends BasePreferenceController {
+
+ public static final String KEY = "high_touch_polling_rate_enable";
+
+ private final LineageHardwareManager mHardware;
+
+ public HighTouchPollingRateSettingsPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+
+ mHardware = LineageHardwareManager.getInstance(context);
+ }
+
+ public HighTouchPollingRateSettingsPreferenceController(Context context) {
+ this(context, KEY);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ if (!mHardware.isSupported(LineageHardwareManager.FEATURE_HIGH_TOUCH_POLLING_RATE)){
+ return UNSUPPORTED_ON_DEVICE;
+ }
+ return AVAILABLE;
+ }
+}
diff --git a/src/com/android/settings/derp/touch/HighTouchSensitivitySettingsPreferenceController.java b/src/com/android/settings/derp/touch/HighTouchSensitivitySettingsPreferenceController.java
new file mode 100644
index 00000000000..ee2256d66bf
--- /dev/null
+++ b/src/com/android/settings/derp/touch/HighTouchSensitivitySettingsPreferenceController.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2020 PixelExperience
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.settings.derp.touch;
+
+import android.content.Context;
+import com.android.internal.derp.hardware.LineageHardwareManager;
+import com.android.settings.core.BasePreferenceController;
+
+public class HighTouchSensitivitySettingsPreferenceController extends BasePreferenceController {
+
+ public static final String KEY = "high_touch_sensitivity_enable";
+
+ private final LineageHardwareManager mHardware;
+
+ public HighTouchSensitivitySettingsPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+
+ mHardware = LineageHardwareManager.getInstance(context);
+ }
+
+ public HighTouchSensitivitySettingsPreferenceController(Context context) {
+ this(context, KEY);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ if (!mHardware.isSupported(LineageHardwareManager.FEATURE_HIGH_TOUCH_SENSITIVITY)){
+ return UNSUPPORTED_ON_DEVICE;
+ }
+ return AVAILABLE;
+ }
+}
diff --git a/src/com/android/settings/derp/touch/TouchHoveringSettingsPreferenceController.java b/src/com/android/settings/derp/touch/TouchHoveringSettingsPreferenceController.java
new file mode 100644
index 00000000000..4bde32cbe02
--- /dev/null
+++ b/src/com/android/settings/derp/touch/TouchHoveringSettingsPreferenceController.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2020 PixelExperience
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.settings.derp.touch;
+
+import android.content.Context;
+import com.android.internal.derp.hardware.LineageHardwareManager;
+import com.android.settings.core.BasePreferenceController;
+
+public class TouchHoveringSettingsPreferenceController extends BasePreferenceController {
+
+ public static final String KEY = "feature_touch_hovering";
+
+ private final LineageHardwareManager mHardware;
+
+ public TouchHoveringSettingsPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+
+ mHardware = LineageHardwareManager.getInstance(context);
+ }
+
+ public TouchHoveringSettingsPreferenceController(Context context) {
+ this(context, KEY);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ if (!mHardware.isSupported(LineageHardwareManager.FEATURE_TOUCH_HOVERING)){
+ return UNSUPPORTED_ON_DEVICE;
+ }
+ return AVAILABLE;
+ }
+}
diff --git a/src/com/android/settings/derp/utils/DeviceUtils.java b/src/com/android/settings/derp/utils/DeviceUtils.java
new file mode 100644
index 00000000000..2ef05fcbe0c
--- /dev/null
+++ b/src/com/android/settings/derp/utils/DeviceUtils.java
@@ -0,0 +1,285 @@
+/*
+ * SPDX-FileCopyrightText: 2016 The CyanogenMod project
+ * SPDX-FileCopyrightText: 2017-2023 The LineageOS project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package com.android.settings.derp.utils;
+
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
+
+import android.app.Activity;
+import android.bluetooth.BluetoothManager;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraManager;
+import android.nfc.NfcAdapter;
+import android.os.Build;
+import android.os.SystemProperties;
+import android.telephony.TelephonyManager;
+import android.telephony.SubscriptionManager;
+import android.text.TextUtils;
+import android.view.Display;
+import android.view.DisplayCutout;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.Surface;
+
+import static com.android.internal.util.derp.DeviceKeysConstants.*;
+
+import androidx.annotation.NonNull;
+
+public class DeviceUtils {
+
+ /* returns whether the device has a centered display cutout or not. */
+ public static boolean hasCenteredCutout(Context context) {
+ Display display = context.getDisplay();
+ DisplayCutout cutout = display.getCutout();
+ if (cutout != null) {
+ Point realSize = new Point();
+ display.getRealSize(realSize);
+
+ switch (display.getRotation()) {
+ case Surface.ROTATION_0: {
+ Rect rect = cutout.getBoundingRectTop();
+ return !(rect.left <= 0 || rect.right >= realSize.x);
+ }
+ case Surface.ROTATION_90: {
+ Rect rect = cutout.getBoundingRectLeft();
+ return !(rect.top <= 0 || rect.bottom >= realSize.y);
+ }
+ case Surface.ROTATION_180: {
+ Rect rect = cutout.getBoundingRectBottom();
+ return !(rect.left <= 0 || rect.right >= realSize.x);
+ }
+ case Surface.ROTATION_270: {
+ Rect rect = cutout.getBoundingRectRight();
+ return !(rect.top <= 0 || rect.bottom >= realSize.y);
+ }
+ }
+ }
+ return false;
+ }
+
+ public static int getDeviceKeys(Context context) {
+ return context.getResources().getInteger(
+ com.android.internal.R.integer.config_deviceHardwareKeys);
+ }
+
+ public static int getDeviceWakeKeys(Context context) {
+ return context.getResources().getInteger(
+ com.android.internal.R.integer.config_deviceHardwareWakeKeys);
+ }
+
+ /* returns whether the device has power key or not. */
+ public static boolean hasPowerKey() {
+ return KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_POWER);
+ }
+
+ /* returns whether the device has home key or not. */
+ public static boolean hasHomeKey(Context context) {
+ return (getDeviceKeys(context) & KEY_MASK_HOME) != 0;
+ }
+
+ /* returns whether the device has back key or not. */
+ public static boolean hasBackKey(Context context) {
+ return (getDeviceKeys(context) & KEY_MASK_BACK) != 0;
+ }
+
+ /* returns whether the device has menu key or not. */
+ public static boolean hasMenuKey(Context context) {
+ return (getDeviceKeys(context) & KEY_MASK_MENU) != 0;
+ }
+
+ /* returns whether the device has assist key or not. */
+ public static boolean hasAssistKey(Context context) {
+ return (getDeviceKeys(context) & KEY_MASK_ASSIST) != 0;
+ }
+
+ /* returns whether the device has app switch key or not. */
+ public static boolean hasAppSwitchKey(Context context) {
+ return (getDeviceKeys(context) & KEY_MASK_APP_SWITCH) != 0;
+ }
+
+ /* returns whether the device has camera key or not. */
+ public static boolean hasCameraKey(Context context) {
+ return (getDeviceKeys(context) & KEY_MASK_CAMERA) != 0;
+ }
+
+ /* returns whether the device has volume rocker or not. */
+ public static boolean hasVolumeKeys(Context context) {
+ return (getDeviceKeys(context) & KEY_MASK_VOLUME) != 0;
+ }
+
+ /* returns whether the device can be waken using the home key or not. */
+ public static boolean canWakeUsingHomeKey(Context context) {
+ return (getDeviceWakeKeys(context) & KEY_MASK_HOME) != 0;
+ }
+
+ /* returns whether the device can be waken using the back key or not. */
+ public static boolean canWakeUsingBackKey(Context context) {
+ return (getDeviceWakeKeys(context) & KEY_MASK_BACK) != 0;
+ }
+
+ /* returns whether the device can be waken using the menu key or not. */
+ public static boolean canWakeUsingMenuKey(Context context) {
+ return (getDeviceWakeKeys(context) & KEY_MASK_MENU) != 0;
+ }
+
+ /* returns whether the device can be waken using the assist key or not. */
+ public static boolean canWakeUsingAssistKey(Context context) {
+ return (getDeviceWakeKeys(context) & KEY_MASK_ASSIST) != 0;
+ }
+
+ /* returns whether the device can be waken using the app switch key or not. */
+ public static boolean canWakeUsingAppSwitchKey(Context context) {
+ return (getDeviceWakeKeys(context) & KEY_MASK_APP_SWITCH) != 0;
+ }
+
+ /* returns whether the device can be waken using the camera key or not. */
+ public static boolean canWakeUsingCameraKey(Context context) {
+ return (getDeviceWakeKeys(context) & KEY_MASK_CAMERA) != 0;
+ }
+
+ /* returns whether the device can be waken using the volume rocker or not. */
+ public static boolean canWakeUsingVolumeKeys(Context context) {
+ return (getDeviceWakeKeys(context) & KEY_MASK_VOLUME) != 0;
+ }
+
+ /* returns whether the device supports button backlight adjusment or not. */
+ public static boolean hasButtonBacklightSupport(Context context) {
+ final boolean buttonBrightnessControlSupported = context.getResources().getInteger(
+ com.android.internal.R.integer
+ .config_deviceSupportsButtonBrightnessControl) != 0;
+
+ // All hardware keys besides volume and camera can possibly have a backlight
+ return buttonBrightnessControlSupported
+ && (hasHomeKey(context) || hasBackKey(context) || hasMenuKey(context)
+ || hasAssistKey(context) || hasAppSwitchKey(context));
+ }
+
+ /* returns whether the device supports keyboard backlight adjustment or not. */
+ public static boolean hasKeyboardBacklightSupport(Context context) {
+ return context.getResources().getInteger(com.android.internal.R.integer
+ .config_deviceSupportsKeyboardBrightnessControl) != 0;
+ }
+
+ public static boolean isPackageInstalled(Context context, String pkg, boolean ignoreState) {
+ if (pkg != null) {
+ try {
+ PackageInfo pi = context.getPackageManager().getPackageInfo(pkg,
+ PackageManager.PackageInfoFlags.of(0));
+ if (!pi.applicationInfo.enabled && !ignoreState) {
+ return false;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Locks the activity orientation to the current device orientation
+ */
+ public static void lockCurrentOrientation(Activity activity) {
+ int currentRotation = activity.getDisplay().getRotation();
+ int orientation = activity.getResources().getConfiguration().orientation;
+ int frozenRotation = 0;
+ switch (currentRotation) {
+ case Surface.ROTATION_0:
+ frozenRotation = orientation == Configuration.ORIENTATION_LANDSCAPE
+ ? ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
+ : ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+ break;
+ case Surface.ROTATION_90:
+ frozenRotation = orientation == Configuration.ORIENTATION_PORTRAIT
+ ? ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT
+ : ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+ break;
+ case Surface.ROTATION_180:
+ frozenRotation = orientation == Configuration.ORIENTATION_LANDSCAPE
+ ? ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
+ : ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
+ break;
+ case Surface.ROTATION_270:
+ frozenRotation = orientation == Configuration.ORIENTATION_PORTRAIT
+ ? ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
+ : ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
+ break;
+ }
+ activity.setRequestedOrientation(frozenRotation);
+ }
+
+ public static boolean isDozeAvailable(Context context) {
+ String name = Build.IS_DEBUGGABLE ? SystemProperties.get("debug.doze.component") : null;
+ if (TextUtils.isEmpty(name)) {
+ name = context.getResources().getString(
+ com.android.internal.R.string.config_dozeComponent);
+ }
+ return !TextUtils.isEmpty(name);
+ }
+
+ public static boolean deviceSupportsMobileData(Context ctx) {
+ TelephonyManager telephonyManager = ctx.getSystemService(TelephonyManager.class);
+ return telephonyManager.isDataCapable();
+ }
+
+ public static boolean deviceSupportsBluetooth(Context ctx) {
+ BluetoothManager bluetoothManager = (BluetoothManager)
+ ctx.getSystemService(Context.BLUETOOTH_SERVICE);
+ return (bluetoothManager.getAdapter() != null);
+ }
+
+ public static boolean deviceSupportsNfc(Context ctx) {
+ return NfcAdapter.getDefaultAdapter(ctx) != null;
+ }
+
+ public static boolean deviceSupportsFlashLight(@NonNull Context context) {
+ CameraManager cameraManager = context.getSystemService(CameraManager.class);
+ try {
+ String[] ids = cameraManager.getCameraIdList();
+ for (String id : ids) {
+ CameraCharacteristics c = cameraManager.getCameraCharacteristics(id);
+ Boolean flashAvailable = c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
+ Integer lensFacing = c.get(CameraCharacteristics.LENS_FACING);
+ if (flashAvailable != null
+ && flashAvailable
+ && lensFacing != null
+ && lensFacing == CameraCharacteristics.LENS_FACING_BACK) {
+ return true;
+ }
+ }
+ } catch (CameraAccessException | AssertionError e) {
+ // Ignore
+ }
+ return false;
+ }
+
+ public static boolean isMobileDataEnabled(Context context) {
+ TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class);
+ int subId = SubscriptionManager.getDefaultDataSubscriptionId();
+ return telephonyManager.createForSubscriptionId(subId).isDataEnabled();
+ }
+
+ public static boolean isSwipeUpEnabled(Context context) {
+ if (isEdgeToEdgeEnabled(context)) {
+ return false;
+ }
+ return NAV_BAR_MODE_2BUTTON == context.getResources().getInteger(
+ com.android.internal.R.integer.config_navBarInteractionMode);
+ }
+
+ public static boolean isEdgeToEdgeEnabled(Context context) {
+ return NAV_BAR_MODE_GESTURAL == context.getResources().getInteger(
+ com.android.internal.R.integer.config_navBarInteractionMode);
+ }
+}
diff --git a/src/com/android/settings/derp/utils/ResourceUtils.java b/src/com/android/settings/derp/utils/ResourceUtils.java
new file mode 100644
index 00000000000..4fbc81616c6
--- /dev/null
+++ b/src/com/android/settings/derp/utils/ResourceUtils.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright (C) 2016 The CyanogenMod project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.derp.utils;
+
+import android.content.res.Resources;
+import android.util.Log;
+
+public class ResourceUtils {
+
+ private static final String TAG = ResourceUtils.class.getSimpleName();
+
+ public static String getLocalizedString(final Resources res,
+ final String stringName,
+ final String stringFormat) {
+ final String name = stringName.toLowerCase().replace(" ", "_");
+ final String nameRes = String.format(stringFormat, name);
+ return getStringForResourceName(res, nameRes, stringName);
+ }
+
+ public static String getStringForResourceName(final Resources res,
+ final String resourceName,
+ final String defaultValue) {
+ final int resId = res.getIdentifier(resourceName, "string", "com.android.settings");
+ if (resId <= 0) {
+ Log.e(TAG, "No resource found for " + resourceName);
+ return defaultValue;
+ } else {
+ return res.getString(resId);
+ }
+ }
+}
diff --git a/src/com/android/settings/derp/utils/TelephonyUtils.java b/src/com/android/settings/derp/utils/TelephonyUtils.java
new file mode 100644
index 00000000000..6ff430433ef
--- /dev/null
+++ b/src/com/android/settings/derp/utils/TelephonyUtils.java
@@ -0,0 +1,24 @@
+/*
+ * SPDX-FileCopyrightText: 2016 The CyanogenMod Project
+ * SPDX-FileCopyrightText: 2023 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package com.android.settings.derp.utils;
+
+import android.content.Context;
+import android.telephony.TelephonyManager;
+
+import androidx.annotation.NonNull;
+
+public class TelephonyUtils {
+
+ private static final String TAG = TelephonyUtils.class.getSimpleName();
+
+ /**
+ * Returns whether the device is voice-capable (meaning, it is also a phone).
+ */
+ public static boolean isVoiceCapable(@NonNull Context context) {
+ TelephonyManager telephony = context.getSystemService(TelephonyManager.class);
+ return telephony != null && telephony.isVoiceCapable();
+ }
+}
diff --git a/src/com/android/settings/derp/widget/IntervalSeekBar.java b/src/com/android/settings/derp/widget/IntervalSeekBar.java
new file mode 100644
index 00000000000..35c986f2bb3
--- /dev/null
+++ b/src/com/android/settings/derp/widget/IntervalSeekBar.java
@@ -0,0 +1,93 @@
+/*
+ * SPDX-FileCopyrightText: 2016 The CyanogenMod Project
+ * SPDX-FileCopyrightText: 2017-2022 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package com.android.settings.derp.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.widget.SeekBar;
+
+import com.android.settings.R;
+
+/**
+ * Custom SeekBar that allows setting both a minimum and maximum value.
+ * This also handles floating point values (to 2 decimal places) through
+ * integer conversions.
+ */
+public class IntervalSeekBar extends SeekBar {
+ private float mMin;
+ private float mMax;
+ private final float mDefault;
+ private final float mMultiplier;
+
+ public IntervalSeekBar(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ TypedArray seekBarType = context.obtainStyledAttributes(attrs,
+ R.styleable.IntervalSeekBar, 0, 0);
+
+ mMax = seekBarType.getFloat(R.styleable.IntervalSeekBar_maxValue, 1.5f);
+ mMin = seekBarType.getFloat(R.styleable.IntervalSeekBar_minValue, 0.5f);
+ mDefault = seekBarType.getFloat(R.styleable.IntervalSeekBar_defaultValue, 1.0f);
+
+ int digits = seekBarType.getInt(R.styleable.IntervalSeekBar_digits, 0);
+ mMultiplier = (float) Math.pow(10, digits);
+
+ if (mMin > mMax) {
+ float temp = mMax;
+ mMax = mMin;
+ mMin = temp;
+ }
+
+ setMax(convertFloatToProgress(mMax));
+ setProgressFloat(mDefault);
+
+ seekBarType.recycle();
+ }
+
+ /*
+ * Converts from SeekBar units (which the SeekBar uses), to scale units
+ * (which are saved).
+ * This operation is the inverse of setFontScaling.
+ */
+ public float getProgressFloat() {
+ return (getProgress() / mMultiplier) + mMin;
+ }
+
+ /*
+ * Converts from scale units (which are saved), to SeekBar units
+ * (which the SeekBar uses). This also sets the SeekBar progress.
+ * This operation is the inverse of getProgressFloat.
+ */
+ public void setProgressFloat(float progress) {
+ setProgress(convertFloatToProgress(progress));
+ }
+
+ private int convertFloatToProgress(float value) {
+ return Math.round((value - mMin) * mMultiplier);
+ }
+
+ public float getMinimum() {
+ return mMin;
+ }
+
+ public float getMaximum() {
+ return mMax;
+ }
+
+ public float getDefault() {
+ return mDefault;
+ }
+
+ public void setMaximum(float max) {
+ mMax = max;
+ setMax(convertFloatToProgress(mMax));
+ }
+
+ public void setMinimum(float min) {
+ mMin = min;
+ }
+}
diff --git a/src/com/android/settings/derp/widget/PackageListAdapter.java b/src/com/android/settings/derp/widget/PackageListAdapter.java
new file mode 100644
index 00000000000..b6ddf23427d
--- /dev/null
+++ b/src/com/android/settings/derp/widget/PackageListAdapter.java
@@ -0,0 +1,194 @@
+/*
+ * SPDX-FileCopyrightText: 2012-2014 The CyanogenMod Project
+ * SPDX-FileCopyrightText: 2022-2023 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.android.settings.derp.widget;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.settings.R;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+public class PackageListAdapter extends BaseAdapter implements Runnable {
+ private final PackageManager mPm;
+ private final LayoutInflater mInflater;
+ private final List mInstalledPackages = new LinkedList<>();
+ private Set mExcludedPackages = new HashSet<>();
+
+ // Packages which don't have launcher icons, but which we want to show nevertheless
+ private static final String[] PACKAGE_WHITELIST = new String[] {
+ "android", /* system server */
+ "com.android.systemui", /* system UI */
+ "com.android.providers.downloads" /* download provider */
+ };
+
+ private final Handler mHandler = new Handler(Looper.getMainLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ PackageItem item = (PackageItem) msg.obj;
+ int index = Collections.binarySearch(mInstalledPackages, item);
+ if (index < 0) {
+ mInstalledPackages.add(-index - 1, item);
+ } else {
+ mInstalledPackages.get(index).activityTitles.addAll(item.activityTitles);
+ }
+ notifyDataSetChanged();
+ }
+ };
+
+ public static class PackageItem implements Comparable {
+ public final String packageName;
+ public final CharSequence title;
+ private final TreeSet activityTitles = new TreeSet<>();
+ public final Drawable icon;
+
+ PackageItem(String packageName, CharSequence title, Drawable icon) {
+ this.packageName = packageName;
+ this.title = title;
+ this.icon = icon;
+ }
+
+ @Override
+ public int compareTo(PackageItem another) {
+ int result = title.toString().compareToIgnoreCase(another.title.toString());
+ return result != 0 ? result : packageName.compareTo(another.packageName);
+ }
+ }
+
+ public PackageListAdapter(Context context) {
+ mPm = context.getPackageManager();
+ mInflater = LayoutInflater.from(context);
+ reloadList();
+ }
+
+ @Override
+ public int getCount() {
+ synchronized (mInstalledPackages) {
+ return mInstalledPackages.size();
+ }
+ }
+
+ @Override
+ public PackageItem getItem(int position) {
+ synchronized (mInstalledPackages) {
+ return mInstalledPackages.get(position);
+ }
+ }
+
+ @Override
+ public long getItemId(int position) {
+ synchronized (mInstalledPackages) {
+ // packageName is guaranteed to be unique in mInstalledPackages
+ return mInstalledPackages.get(position).packageName.hashCode();
+ }
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ ViewHolder holder;
+ if (convertView != null) {
+ holder = (ViewHolder) convertView.getTag();
+ } else {
+ convertView = mInflater.inflate(R.layout.preference_icon, null, false);
+ holder = new ViewHolder();
+ convertView.setTag(holder);
+ holder.title = convertView.findViewById(com.android.internal.R.id.title);
+ holder.summary = convertView.findViewById(com.android.internal.R.id.summary);
+ holder.icon = convertView.findViewById(com.android.internal.R.id.icon);
+ }
+
+ PackageItem applicationInfo = getItem(position);
+ holder.title.setText(applicationInfo.title);
+ holder.icon.setImageDrawable(applicationInfo.icon);
+
+ boolean needSummary = applicationInfo.activityTitles.size() > 0;
+ if (applicationInfo.activityTitles.size() == 1) {
+ if (TextUtils.equals(applicationInfo.title, applicationInfo.activityTitles.first())) {
+ needSummary = false;
+ }
+ }
+
+ if (needSummary) {
+ holder.summary.setText(TextUtils.join(", ", applicationInfo.activityTitles));
+ holder.summary.setVisibility(View.VISIBLE);
+ } else {
+ holder.summary.setVisibility(View.GONE);
+ }
+
+ return convertView;
+ }
+
+ private void reloadList() {
+ mInstalledPackages.clear();
+ new Thread(this).start();
+ }
+
+ @Override
+ public void run() {
+ final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
+ mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+ List installedAppsInfo = mPm.queryIntentActivities(mainIntent,
+ PackageManager.ResolveInfoFlags.of(0));
+
+ for (ResolveInfo info : installedAppsInfo) {
+ ApplicationInfo appInfo = info.activityInfo.applicationInfo;
+ if (mExcludedPackages.contains(appInfo.packageName)) {
+ continue;
+ }
+
+ final PackageItem item = new PackageItem(appInfo.packageName,
+ appInfo.loadLabel(mPm), appInfo.loadIcon(mPm));
+ item.activityTitles.add(info.loadLabel(mPm));
+ mHandler.obtainMessage(0, item).sendToTarget();
+ }
+
+ for (String packageName : PACKAGE_WHITELIST) {
+ if (mExcludedPackages.contains(packageName)) {
+ continue;
+ }
+ try {
+ ApplicationInfo appInfo = mPm.getApplicationInfo(packageName,
+ PackageManager.ApplicationInfoFlags.of(0));
+ final PackageItem item = new PackageItem(appInfo.packageName,
+ appInfo.loadLabel(mPm), appInfo.loadIcon(mPm));
+ mHandler.obtainMessage(0, item).sendToTarget();
+ } catch (PackageManager.NameNotFoundException ignored) {
+ // package not present, so nothing to add -> ignore it
+ }
+ }
+ }
+
+ public void setExcludedPackages(HashSet packages) {
+ mExcludedPackages = packages;
+ reloadList();
+ }
+
+ private static class ViewHolder {
+ TextView title;
+ TextView summary;
+ ImageView icon;
+ }
+}
diff --git a/src/com/android/settings/development/AdbRootPreferenceController.java b/src/com/android/settings/development/AdbRootPreferenceController.java
new file mode 100644
index 00000000000..1c2ff6b87b3
--- /dev/null
+++ b/src/com/android/settings/development/AdbRootPreferenceController.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2018 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.development;
+
+import android.adb.ADBRootService;
+import android.content.Context;
+import android.os.UserManager;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.SwitchPreferenceCompat;
+
+import com.android.settings.R;
+import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settingslib.development.DeveloperOptionsPreferenceController;
+
+public class AdbRootPreferenceController extends DeveloperOptionsPreferenceController
+ implements Preference.OnPreferenceChangeListener, PreferenceControllerMixin {
+
+ private static final String TAG = "AdbRootPreferenceController";
+ private static final String PREF_KEY = "enable_adb_root";
+
+ private final ADBRootService mADBRootService;
+
+ public AdbRootPreferenceController(Context context,
+ DevelopmentSettingsDashboardFragment fragment) {
+ super(context);
+
+ mADBRootService = new ADBRootService();
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return PREF_KEY;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return mADBRootService.isSupported();
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+
+ ((SwitchPreferenceCompat) mPreference).setChecked(mADBRootService.getEnabled());
+
+ if (!isAdminUser()) {
+ mPreference.setEnabled(false);
+ }
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ final boolean rootEnabled = (Boolean) newValue;
+ mADBRootService.setEnabled(rootEnabled);
+ return true;
+ }
+
+ @Override
+ protected void onDeveloperOptionsSwitchDisabled() {
+ super.onDeveloperOptionsSwitchDisabled();
+
+ mADBRootService.setEnabled(false);
+ ((SwitchPreferenceCompat) mPreference).setChecked(false);
+ }
+
+ @Override
+ protected void onDeveloperOptionsSwitchEnabled() {
+ if (isAdminUser()) {
+ mPreference.setEnabled(true);
+ }
+ }
+
+ boolean isAdminUser() {
+ return ((UserManager) mContext.getSystemService(Context.USER_SERVICE)).isAdminUser();
+ }
+}
diff --git a/src/com/android/settings/development/AutomaticSystemServerHeapDumpPreferenceController.java b/src/com/android/settings/development/AutomaticSystemServerHeapDumpPreferenceController.java
index 05a5dc90b4c..877b7fe7abf 100644
--- a/src/com/android/settings/development/AutomaticSystemServerHeapDumpPreferenceController.java
+++ b/src/com/android/settings/development/AutomaticSystemServerHeapDumpPreferenceController.java
@@ -49,7 +49,7 @@ public AutomaticSystemServerHeapDumpPreferenceController(Context context) {
@Override
public boolean isAvailable() {
- return Build.IS_DEBUGGABLE && mIsConfigEnabled
+ return Build.IS_ENG && mIsConfigEnabled
&& !mUserManager.hasUserRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES);
}
diff --git a/src/com/android/settings/development/BluetoothA2dpHwOffloadPreferenceController.java b/src/com/android/settings/development/BluetoothA2dpHwOffloadPreferenceController.java
index f460b9e33fe..7d4b7a6fdda 100644
--- a/src/com/android/settings/development/BluetoothA2dpHwOffloadPreferenceController.java
+++ b/src/com/android/settings/development/BluetoothA2dpHwOffloadPreferenceController.java
@@ -74,23 +74,10 @@ public void updateState(Preference preference) {
}
}
- @Override
- protected void onDeveloperOptionsSwitchDisabled() {
- super.onDeveloperOptionsSwitchDisabled();
- final boolean offloadSupported =
- SystemProperties.getBoolean(A2DP_OFFLOAD_SUPPORTED_PROPERTY, false);
- if (offloadSupported) {
- ((TwoStatePreference) mPreference).setChecked(false);
- SystemProperties.set(A2DP_OFFLOAD_DISABLED_PROPERTY, "false");
- }
- }
-
public boolean isDefaultValue() {
- final boolean offloadSupported =
- SystemProperties.getBoolean(A2DP_OFFLOAD_SUPPORTED_PROPERTY, false);
- final boolean offloadDisabled =
- SystemProperties.getBoolean(A2DP_OFFLOAD_DISABLED_PROPERTY, false);
- return offloadSupported ? !offloadDisabled : true;
+ // Always return true here to avoid needing to reboot when disabling
+ // developer options, since we aren't turning this off when doing so anymore.
+ return true;
}
/**
diff --git a/src/com/android/settings/development/BluetoothSnoopLogPreferenceController.java b/src/com/android/settings/development/BluetoothSnoopLogPreferenceController.java
index 9e058913de3..f79b0b83dcd 100644
--- a/src/com/android/settings/development/BluetoothSnoopLogPreferenceController.java
+++ b/src/com/android/settings/development/BluetoothSnoopLogPreferenceController.java
@@ -57,7 +57,7 @@ public BluetoothSnoopLogPreferenceController(
// Default mode is DISABLED. It can also be changed by modifying the global setting.
public int getDefaultModeIndex() {
- if (!Build.IS_DEBUGGABLE) {
+ if (!Build.IS_ENG) {
return BTSNOOP_LOG_MODE_DISABLED_INDEX;
}
diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
index 38cb6c72e96..7ed53fbfa38 100644
--- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
+++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
@@ -32,6 +32,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Bundle;
@@ -670,11 +671,12 @@ private static List buildPreferenceControllers(Con
controllers.add(new PictureColorModePreferenceController(context, lifecycle));
controllers.add(new WebViewAppPreferenceController(context));
controllers.add(new CoolColorTemperaturePreferenceController(context));
- controllers.add(new DisableAutomaticUpdatesPreferenceController(context));
+ // controllers.add(new DisableAutomaticUpdatesPreferenceController(context));
controllers.add(new SelectDSUPreferenceController(context));
controllers.add(new AdbPreferenceController(context, fragment));
controllers.add(new ClearAdbKeysPreferenceController(context, fragment));
controllers.add(new WirelessDebuggingPreferenceController(context, lifecycle));
+ controllers.add(new AdbRootPreferenceController(context, fragment));
controllers.add(new AdbAuthorizationTimeoutPreferenceController(context));
controllers.add(new LocalTerminalPreferenceController(context));
controllers.add(new BugReportInPowerPreferenceController(context));
@@ -693,10 +695,12 @@ private static List buildPreferenceControllers(Con
controllers.add(new LogdSizePreferenceController(context));
controllers.add(new LogPersistPreferenceController(context, fragment, lifecycle));
controllers.add(new CameraLaserSensorPreferenceController(context));
- controllers.add(new WifiDisplayCertificationPreferenceController(context));
- controllers.add(new WifiVerboseLoggingPreferenceController(context));
- controllers.add(new WifiScanThrottlingPreferenceController(context));
- controllers.add(new WifiNonPersistentMacRandomizationPreferenceController(context));
+ if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+ controllers.add(new WifiDisplayCertificationPreferenceController(context));
+ controllers.add(new WifiVerboseLoggingPreferenceController(context));
+ controllers.add(new WifiScanThrottlingPreferenceController(context));
+ controllers.add(new WifiNonPersistentMacRandomizationPreferenceController(context));
+ }
controllers.add(new MobileDataAlwaysOnPreferenceController(context));
controllers.add(new TetheringHardwareAccelPreferenceController(context));
controllers.add(new BluetoothDeviceNoNamePreferenceController(context));
@@ -731,8 +735,6 @@ private static List buildPreferenceControllers(Con
controllers.add(new DebugGpuOverdrawPreferenceController(context));
controllers.add(new DebugNonRectClipOperationsPreferenceController(context));
controllers.add(new GameDefaultFrameRatePreferenceController(context));
- controllers.add(new ForceDarkPreferenceController(context));
- controllers.add(new EnableBlursPreferenceController(context));
controllers.add(new ForceMSAAPreferenceController(context));
controllers.add(new HardwareOverlaysPreferenceController(context));
controllers.add(new SimulateColorSpacePreferenceController(context));
diff --git a/src/com/android/settings/development/DisableAutomaticUpdatesPreferenceController.java b/src/com/android/settings/development/DisableAutomaticUpdatesPreferenceController.java
index 4f7fb4d48fd..006a1e6338e 100644
--- a/src/com/android/settings/development/DisableAutomaticUpdatesPreferenceController.java
+++ b/src/com/android/settings/development/DisableAutomaticUpdatesPreferenceController.java
@@ -73,4 +73,9 @@ protected void onDeveloperOptionsSwitchDisabled() {
Settings.Global.OTA_DISABLE_AUTOMATIC_UPDATE, DISABLE_UPDATES_SETTING);
((TwoStatePreference) mPreference).setChecked(false);
}
+
+ @Override
+ public boolean isAvailable() {
+ return false;
+ }
}
diff --git a/src/com/android/settings/development/EnableBlursPreferenceController.java b/src/com/android/settings/development/EnableBlursPreferenceController.java
deleted file mode 100644
index d3a521a7798..00000000000
--- a/src/com/android/settings/development/EnableBlursPreferenceController.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.development;
-
-import static android.view.CrossWindowBlurListeners.CROSS_WINDOW_BLUR_SUPPORTED;
-
-import android.content.Context;
-import android.provider.Settings;
-
-import androidx.annotation.VisibleForTesting;
-import androidx.preference.Preference;
-import androidx.preference.TwoStatePreference;
-
-import com.android.settings.core.PreferenceControllerMixin;
-import com.android.settingslib.development.DeveloperOptionsPreferenceController;
-
-/**
- * Controller that toggles window blurs on devices that support it.
- */
-public final class EnableBlursPreferenceController extends DeveloperOptionsPreferenceController
- implements Preference.OnPreferenceChangeListener, PreferenceControllerMixin {
-
- private static final String ENABLE_BLURS_ON_WINDOWS = "enable_blurs_on_windows";
- private final boolean mBlurSupported;
-
- public EnableBlursPreferenceController(Context context) {
- this(context, CROSS_WINDOW_BLUR_SUPPORTED);
- }
-
- @VisibleForTesting
- public EnableBlursPreferenceController(Context context, boolean blurSupported) {
- super(context);
- mBlurSupported = blurSupported;
- }
-
- @Override
- public String getPreferenceKey() {
- return ENABLE_BLURS_ON_WINDOWS;
- }
-
- @Override
- public boolean onPreferenceChange(Preference preference, Object newValue) {
- boolean enabled = (Boolean) newValue;
- Settings.Global.putInt(mContext.getContentResolver(),
- Settings.Global.DISABLE_WINDOW_BLURS, enabled ? 0 : 1);
- return true;
- }
-
- @Override
- public boolean isAvailable() {
- return mBlurSupported;
- }
-
- @Override
- public void updateState(Preference preference) {
- boolean isEnabled = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.DISABLE_WINDOW_BLURS, 0) == 0;
- ((TwoStatePreference) mPreference).setChecked(isEnabled);
- }
-
- @Override
- protected void onDeveloperOptionsSwitchDisabled() {
- super.onDeveloperOptionsSwitchDisabled();
- Settings.Global.putInt(mContext.getContentResolver(),
- Settings.Global.DISABLE_WINDOW_BLURS, 0);
- updateState(null);
- }
-}
diff --git a/src/com/android/settings/development/OverlayCategoryPreferenceController.java b/src/com/android/settings/development/OverlayCategoryPreferenceController.java
index ce51b54cf8c..0c20c0329a0 100644
--- a/src/com/android/settings/development/OverlayCategoryPreferenceController.java
+++ b/src/com/android/settings/development/OverlayCategoryPreferenceController.java
@@ -54,7 +54,7 @@ public class OverlayCategoryPreferenceController extends DeveloperOptionsPrefere
static final String PACKAGE_DEVICE_DEFAULT = "package_device_default";
private static final String OVERLAY_TARGET_PACKAGE = "android";
private static final Comparator OVERLAY_INFO_COMPARATOR =
- Comparator.comparingInt(a -> a.priority);
+ Comparator.comparing(OverlayInfo::getPackageName);
private final IOverlayManager mOverlayManager;
private final boolean mAvailable;
private final String mCategory;
diff --git a/src/com/android/settings/development/SystemServerHeapDumpPreferenceController.java b/src/com/android/settings/development/SystemServerHeapDumpPreferenceController.java
index e6701ce248c..769439c819e 100644
--- a/src/com/android/settings/development/SystemServerHeapDumpPreferenceController.java
+++ b/src/com/android/settings/development/SystemServerHeapDumpPreferenceController.java
@@ -53,7 +53,7 @@ public SystemServerHeapDumpPreferenceController(Context context) {
@Override
public boolean isAvailable() {
- return Build.IS_DEBUGGABLE
+ return Build.IS_ENG
&& !mUserManager.hasUserRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES);
}
diff --git a/src/com/android/settings/development/featureflags/FeatureFlagsPreferenceController.java b/src/com/android/settings/development/featureflags/FeatureFlagsPreferenceController.java
index f0b7961bb65..f7361f8f0b8 100644
--- a/src/com/android/settings/development/featureflags/FeatureFlagsPreferenceController.java
+++ b/src/com/android/settings/development/featureflags/FeatureFlagsPreferenceController.java
@@ -40,7 +40,7 @@ public FeatureFlagsPreferenceController(Context context, String key) {
@Override
public int getAvailabilityStatus() {
- return Build.IS_DEBUGGABLE ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+ return Build.IS_ENG ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
}
@Override
diff --git a/src/com/android/settings/development/qstile/DevelopmentTilePreferenceController.java b/src/com/android/settings/development/qstile/DevelopmentTilePreferenceController.java
index 7fc9d9e5d9f..113e164a278 100644
--- a/src/com/android/settings/development/qstile/DevelopmentTilePreferenceController.java
+++ b/src/com/android/settings/development/qstile/DevelopmentTilePreferenceController.java
@@ -127,15 +127,17 @@ public OnChangeHandler(Context context) {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
- boolean enabled = ((Boolean) newValue).booleanValue();
- ComponentName componentName = new ComponentName(
+ ComponentName cn = new ComponentName(
+ //boolean enabled = ((Boolean) newValue).booleanValue();
+ //ComponentName componentName = new ComponentName(
mContext.getPackageName(), preference.getKey());
- mPackageManager.setComponentEnabledSetting(componentName, enabled
+ mPackageManager.setComponentEnabledSetting(cn, (Boolean) newValue
+ //mPackageManager.setComponentEnabledSetting(componentName, enabled
? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
: PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
- try {
+ /*try {
if (mStatusBarService != null) {
if (enabled) {
mStatusBarService.addTile(componentName);
@@ -146,7 +148,7 @@ public boolean onPreferenceChange(Preference preference, Object newValue) {
} catch (RemoteException e) {
Log.e(TAG, "Failed to modify QS tile for component " +
componentName.toString(), e);
- }
+ }*/
return true;
}
}
diff --git a/src/com/android/settings/development/qstile/DevelopmentTiles.java b/src/com/android/settings/development/qstile/DevelopmentTiles.java
index cf0d4d1847f..dfb0bf47587 100644
--- a/src/com/android/settings/development/qstile/DevelopmentTiles.java
+++ b/src/com/android/settings/development/qstile/DevelopmentTiles.java
@@ -29,7 +29,9 @@
import android.hardware.SensorPrivacyManager;
import android.net.Uri;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
+import android.os.Parcel;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
@@ -339,4 +341,64 @@ protected void setIsEnabled(boolean isEnabled) {
Settings.System.SHOW_TOUCHES, isEnabled ? SETTING_VALUE_ON : SETTING_VALUE_OFF);
}
}
+
+ /**
+ * Tile for refresh rate indicator
+ */
+ public static class ShowRefreshRate extends DevelopmentTiles {
+
+ private static final int SETTING_VALUE_QUERY = 2;
+ private static final int SETTING_VALUE_ON = 1;
+ private static final int SETTING_VALUE_OFF = 0;
+
+ private static final String SURFACE_FLINGER_SERVICE_KEY = "SurfaceFlinger";
+ private static final String SURFACE_COMPOSER_INTERFACE_KEY = "android.ui.ISurfaceComposer";
+ private static final int SURFACE_FLINGER_CODE = 1034;
+
+ private IBinder mSurfaceFlinger;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mSurfaceFlinger = ServiceManager.getService(SURFACE_FLINGER_SERVICE_KEY);
+ }
+
+ @Override
+ protected boolean isEnabled() {
+ boolean enabled = false;
+ // magic communication with surface flinger.
+ try {
+ if (mSurfaceFlinger != null) {
+ final Parcel data = Parcel.obtain();
+ final Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(SURFACE_COMPOSER_INTERFACE_KEY);
+ data.writeInt(SETTING_VALUE_QUERY);
+ mSurfaceFlinger.transact(SURFACE_FLINGER_CODE, data, reply, 0 /* flags */);
+ enabled = reply.readBoolean();
+ reply.recycle();
+ data.recycle();
+ }
+ } catch (RemoteException ex) {
+ // intentional no-op
+ }
+ return enabled;
+ }
+
+ @Override
+ protected void setIsEnabled(boolean isEnabled) {
+ try {
+ if (mSurfaceFlinger != null) {
+ final Parcel data = Parcel.obtain();
+ data.writeInterfaceToken(SURFACE_COMPOSER_INTERFACE_KEY);
+ final int showRefreshRate = isEnabled ? SETTING_VALUE_ON : SETTING_VALUE_OFF;
+ data.writeInt(showRefreshRate);
+ mSurfaceFlinger.transact(SURFACE_FLINGER_CODE, data,
+ null /* reply */, 0 /* flags */);
+ data.recycle();
+ }
+ } catch (RemoteException ex) {
+ // intentional no-op
+ }
+ }
+ }
}
diff --git a/src/com/android/settings/deviceinfo/BuildNumberPreferenceController.java b/src/com/android/settings/deviceinfo/BuildNumberPreferenceController.java
index 6fe3ca4521b..29d000302b1 100644
--- a/src/com/android/settings/deviceinfo/BuildNumberPreferenceController.java
+++ b/src/com/android/settings/deviceinfo/BuildNumberPreferenceController.java
@@ -50,6 +50,8 @@
import com.google.android.setupcompat.util.WizardManagerHelper;
+import java.util.Random;
+
public class BuildNumberPreferenceController extends BasePreferenceController implements
LifecycleObserver, OnStart {
@@ -67,6 +69,29 @@ public class BuildNumberPreferenceController extends BasePreferenceController im
private int mDevHitCountdown;
private boolean mProcessingLastDevHit;
+ public final static java.lang.String[] insults = {
+ "Hahaha, n00b!",
+ "What are you doing??",
+ "n00b alert!",
+ "What is this...? Amateur hour!?",
+ "This is not Windows",
+ "Please step away from the device!",
+ "error code: 1D10T",
+ "Go outside",
+ "¯\\_(ツ)_/¯",
+ "Pro tip: Stop doing this!",
+ "Y u no speak computer???",
+ "Why are you so stupid?!",
+ "Perhaps this Android thing is not for you...",
+ "Don't you have anything better to do?!",
+ "This is why nobody likes you...",
+ "Are you even trying?!",
+ "Looks like you're derping... BUT THATS OUR BUSINESS!!!",
+ "This won't make you look cooler to your friends",
+ "Go back to your stock ROM",
+ "You look like a person who plays PUBG on his phone",
+ };
+
public BuildNumberPreferenceController(Context context, String key) {
super(context, key);
mUm = (UserManager) context.getSystemService(Context.USER_SERVICE);
@@ -198,7 +223,9 @@ public boolean handlePreferenceTreeClick(Preference preference) {
if (mDevHitToast != null) {
mDevHitToast.cancel();
}
- mDevHitToast = Toast.makeText(mContext, R.string.show_dev_already,
+ Random randomInsult = new Random();
+ final int toasts = randomInsult.nextInt(insults.length);
+ mDevHitToast = Toast.makeText(mContext, insults[toasts],
Toast.LENGTH_LONG);
mDevHitToast.show();
mMetricsFeatureProvider.action(
diff --git a/src/com/android/settings/deviceinfo/FeedbackPreferenceController.java b/src/com/android/settings/deviceinfo/FeedbackPreferenceController.java
index d94586be094..51151c9de32 100644
--- a/src/com/android/settings/deviceinfo/FeedbackPreferenceController.java
+++ b/src/com/android/settings/deviceinfo/FeedbackPreferenceController.java
@@ -41,7 +41,7 @@ public FeedbackPreferenceController(Fragment host, Context context) {
@Override
public boolean isAvailable() {
- return !TextUtils.isEmpty(DeviceInfoUtils.getFeedbackReporterPackage(mContext));
+ return false;
}
@Override
diff --git a/src/com/android/settings/deviceinfo/PhoneNumberPreferenceController.java b/src/com/android/settings/deviceinfo/PhoneNumberPreferenceController.java
index 6df100ca4ec..a3288db1df2 100644
--- a/src/com/android/settings/deviceinfo/PhoneNumberPreferenceController.java
+++ b/src/com/android/settings/deviceinfo/PhoneNumberPreferenceController.java
@@ -55,6 +55,28 @@ public int getAvailabilityStatus() {
AVAILABLE : UNSUPPORTED_ON_DEVICE;
}
+ @Override
+ public CharSequence getSummary() {
+ return mContext.getString(R.string.device_info_protected_single_press);
+ }
+
+ @Override
+ public boolean handlePreferenceTreeClick(Preference preference) {
+ String prefKey = preference.getKey();
+ if (prefKey.startsWith(KEY_PHONE_NUMBER)) {
+ int simSlotNumber = 0;
+ if (!TextUtils.equals(prefKey, KEY_PHONE_NUMBER)) {
+ // Get multisim slot number from preference key.
+ // Multisim preference key is KEY_PHONE_NUMBER + simSlotNumber
+ simSlotNumber = Integer.parseInt(
+ prefKey.replaceAll("[^0-9]", ""));
+ }
+ final Preference simStatusPreference = mPreferenceList.get(simSlotNumber);
+ simStatusPreference.setSummary(getPhoneNumber(simSlotNumber));
+ }
+ return super.handlePreferenceTreeClick(preference);
+ }
+
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
@@ -84,7 +106,7 @@ public void updateState(Preference preference) {
for (int simSlotNumber = 0; simSlotNumber < mPreferenceList.size(); simSlotNumber++) {
final Preference simStatusPreference = mPreferenceList.get(simSlotNumber);
simStatusPreference.setTitle(getPreferenceTitle(simSlotNumber));
- simStatusPreference.setSummary(getPhoneNumber(simSlotNumber));
+ simStatusPreference.setSummary(getSummary());
}
}
@@ -134,7 +156,7 @@ protected SubscriptionInfo getSubscriptionInfo(int simSlot) {
}
@VisibleForTesting
- protected String getFormattedPhoneNumber(SubscriptionInfo subscriptionInfo) {
+ protected CharSequence getFormattedPhoneNumber(SubscriptionInfo subscriptionInfo) {
final String phoneNumber = SubscriptionUtil.getBidiFormattedPhoneNumber(mContext,
subscriptionInfo);
return TextUtils.isEmpty(phoneNumber) ? mContext.getString(R.string.device_info_default)
@@ -143,6 +165,6 @@ protected String getFormattedPhoneNumber(SubscriptionInfo subscriptionInfo) {
@VisibleForTesting
protected Preference createNewPreference(Context context) {
- return new Preference(context);
+ return new PhoneNumberSummaryPreference(context);
}
}
diff --git a/src/com/android/settings/deviceinfo/RadioInfoPreferenceController.java b/src/com/android/settings/deviceinfo/RadioInfoPreferenceController.java
new file mode 100644
index 00000000000..35523025cf3
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/RadioInfoPreferenceController.java
@@ -0,0 +1,47 @@
+/*
+ * 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.settings.deviceinfo;
+
+import android.content.Context;
+import android.telephony.TelephonyManager;
+
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+
+public class RadioInfoPreferenceController extends BasePreferenceController {
+
+ private TelephonyManager mTelephonyManager;
+
+ public RadioInfoPreferenceController(Context context, String key) {
+ super(context, key);
+ mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return mTelephonyManager.isVoiceCapable()
+ ? AVAILABLE
+ : UNSUPPORTED_ON_DEVICE;
+ }
+}
diff --git a/src/com/android/settings/deviceinfo/UpdatePreferenceController.java b/src/com/android/settings/deviceinfo/UpdatePreferenceController.java
new file mode 100644
index 00000000000..8a9132d82d3
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/UpdatePreferenceController.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2017 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.deviceinfo;
+
+import android.content.Context;
+import androidx.preference.Preference;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settings.core.PreferenceControllerMixin;
+
+
+public class UpdatePreferenceController extends AbstractPreferenceController implements
+ PreferenceControllerMixin {
+
+ private static final String KEY_UPDATE_SETTING = "update_settings";
+
+ public UpdatePreferenceController(Context context) {
+ super(context);
+ }
+
+ @Override
+ public boolean isAvailable() {
+ String packagename = mContext.getResources().getString(
+ com.android.settings.R.string.update_package);
+ try {
+ ApplicationInfo ai = mContext.getPackageManager().getApplicationInfo(packagename, 0);
+ return true;
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY_UPDATE_SETTING;
+ }
+}
diff --git a/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java b/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java
index 50b60973d5b..500235f1a92 100644
--- a/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java
+++ b/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java
@@ -44,6 +44,7 @@
import com.android.settings.deviceinfo.simstatus.SimEidPreferenceController;
import com.android.settings.deviceinfo.simstatus.SimStatusPreferenceController;
import com.android.settings.deviceinfo.simstatus.SlotSimStatus;
+import com.android.settings.deviceinfo.UpdatePreferenceController;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.core.AbstractPreferenceController;
@@ -88,7 +89,6 @@ public void onAttach(Context context) {
@Override
public void onStart() {
super.onStart();
- initHeader();
}
@Override
@@ -125,6 +125,7 @@ private static List buildPreferenceControllers(
controllers.add(new FeedbackPreferenceController(fragment, context));
controllers.add(new FccEquipmentIdPreferenceController(context));
controllers.add(new UptimePreferenceController(context, lifecycle));
+ controllers.add(new UpdatePreferenceController(context));
Consumer imeiInfoList = imeiKey -> {
ImeiInfoPreferenceController imeiRecord =
diff --git a/src/com/android/settings/deviceinfo/batteryinfo/BatteryCycleCountPreferenceController.java b/src/com/android/settings/deviceinfo/batteryinfo/BatteryCycleCountPreferenceController.java
index b022fcf7df2..f7dff9d3d8f 100644
--- a/src/com/android/settings/deviceinfo/batteryinfo/BatteryCycleCountPreferenceController.java
+++ b/src/com/android/settings/deviceinfo/batteryinfo/BatteryCycleCountPreferenceController.java
@@ -36,7 +36,8 @@ public BatteryCycleCountPreferenceController(Context context,
@Override
public int getAvailabilityStatus() {
- return AVAILABLE;
+ return mContext.getResources().getBoolean(R.bool.config_show_battery_cycle_count)
+ ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
}
@Override
@@ -44,7 +45,7 @@ public CharSequence getSummary() {
final Intent batteryIntent = BatteryUtils.getBatteryIntent(mContext);
final int cycleCount = batteryIntent.getIntExtra(BatteryManager.EXTRA_CYCLE_COUNT, -1);
- return cycleCount == -1
+ return cycleCount <= 0
? mContext.getText(R.string.battery_cycle_count_not_available)
: Integer.toString(cycleCount);
}
diff --git a/src/com/android/settings/deviceinfo/batteryinfo/BatteryDesignCapacityPreferenceController.java b/src/com/android/settings/deviceinfo/batteryinfo/BatteryDesignCapacityPreferenceController.java
new file mode 100644
index 00000000000..ab40d30607c
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/batteryinfo/BatteryDesignCapacityPreferenceController.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 Paranoid Android
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.deviceinfo.batteryinfo;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.BatteryManager;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.fuelgauge.BatteryUtils;
+
+/**
+ * A controller that manages the information about battery design capacity.
+ */
+public class BatteryDesignCapacityPreferenceController extends BasePreferenceController {
+
+ public BatteryDesignCapacityPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ Intent batteryIntent = BatteryUtils.getBatteryIntent(mContext);
+ final int designCapacityUah =
+ batteryIntent.getIntExtra(BatteryManager.EXTRA_DESIGN_CAPACITY, -1);
+
+ if (designCapacityUah > 0) {
+ int designCapacity = designCapacityUah / 1_000;
+ return mContext.getString(R.string.battery_design_capacity_summary, designCapacity);
+ }
+
+ return mContext.getString(R.string.battery_design_capacity_not_available);
+ }
+}
diff --git a/src/com/android/settings/deviceinfo/batteryinfo/BatteryHealthPreferenceController.java b/src/com/android/settings/deviceinfo/batteryinfo/BatteryHealthPreferenceController.java
new file mode 100644
index 00000000000..71825f884a8
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/batteryinfo/BatteryHealthPreferenceController.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 Paranoid Android
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.deviceinfo.batteryinfo;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.BatteryManager;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.fuelgauge.BatteryUtils;
+
+/**
+ * A controller that manages the information about battery health.
+ */
+public class BatteryHealthPreferenceController extends BasePreferenceController {
+
+ public BatteryHealthPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ final Intent batteryIntent = BatteryUtils.getBatteryIntent(mContext);
+ final int health =
+ batteryIntent.getIntExtra(
+ BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_UNKNOWN);
+
+ switch (health) {
+ case BatteryManager.BATTERY_HEALTH_GOOD:
+ return mContext.getString(R.string.battery_health_good);
+ case BatteryManager.BATTERY_HEALTH_OVERHEAT:
+ return mContext.getString(R.string.battery_health_overheat);
+ case BatteryManager.BATTERY_HEALTH_DEAD:
+ return mContext.getString(R.string.battery_health_dead);
+ case BatteryManager.BATTERY_HEALTH_OVER_VOLTAGE:
+ return mContext.getString(R.string.battery_health_over_voltage);
+ case BatteryManager.BATTERY_HEALTH_UNSPECIFIED_FAILURE:
+ return mContext.getString(R.string.battery_health_unspecified_failure);
+ case BatteryManager.BATTERY_HEALTH_COLD:
+ return mContext.getString(R.string.battery_health_cold);
+ default:
+ return mContext.getString(R.string.battery_health_unknown);
+ }
+ }
+}
diff --git a/src/com/android/settings/deviceinfo/batteryinfo/BatteryInfoFragment.java b/src/com/android/settings/deviceinfo/batteryinfo/BatteryInfoFragment.java
index c0170227bae..882901d9b7b 100644
--- a/src/com/android/settings/deviceinfo/batteryinfo/BatteryInfoFragment.java
+++ b/src/com/android/settings/deviceinfo/batteryinfo/BatteryInfoFragment.java
@@ -18,18 +18,23 @@
import android.app.settings.SettingsEnums;
import android.content.Context;
+import android.os.Bundle;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;
+import com.android.settingslib.widget.FooterPreference;
/** A fragment that shows battery hardware information. */
@SearchIndexable
public class BatteryInfoFragment extends DashboardFragment {
public static final String TAG = "BatteryInfo";
+ private static final String KEY_BATTERY_INFO_FOOTER = "battery_info_footer";
+
+ private FooterPreference mFooterPreference;
@Override
public int getMetricsCategory() {
@@ -41,6 +46,19 @@ protected int getPreferenceScreenResId() {
return R.xml.battery_info;
}
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ mFooterPreference = findPreference(KEY_BATTERY_INFO_FOOTER);
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ mFooterPreference.setVisible(
+ getContext().getResources().getBoolean(R.bool.config_show_battery_cycle_count));
+ }
+
@Override
protected String getLogTag() {
return TAG;
diff --git a/src/com/android/settings/deviceinfo/batteryinfo/BatteryMaximumCapacityPreferenceController.java b/src/com/android/settings/deviceinfo/batteryinfo/BatteryMaximumCapacityPreferenceController.java
new file mode 100644
index 00000000000..186945efde8
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/batteryinfo/BatteryMaximumCapacityPreferenceController.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 Paranoid Android
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.deviceinfo.batteryinfo;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.BatteryManager;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.fuelgauge.BatteryUtils;
+
+/**
+ * A controller that manages the information about battery maximum capacity.
+ */
+public class BatteryMaximumCapacityPreferenceController extends BasePreferenceController {
+
+ public BatteryMaximumCapacityPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ Intent batteryIntent = BatteryUtils.getBatteryIntent(mContext);
+ final int maxCapacityUah =
+ batteryIntent.getIntExtra(BatteryManager.EXTRA_MAXIMUM_CAPACITY, -1);
+ final int designCapacityUah =
+ batteryIntent.getIntExtra(BatteryManager.EXTRA_DESIGN_CAPACITY, -1);
+
+ if (maxCapacityUah > 0 && designCapacityUah > 0) {
+ int maxCapacity = maxCapacityUah / 1_000;
+ int designCapacity = designCapacityUah / 1_000;
+ int percentage = (maxCapacity * 100) / designCapacity;
+
+ return mContext.getString(
+ R.string.battery_maximum_capacity_summary, maxCapacity, percentage);
+ }
+
+ return mContext.getString(R.string.battery_maximum_capacity_not_available);
+ }
+}
diff --git a/src/com/android/settings/deviceinfo/batteryinfo/BatteryTechnologyPreferenceController.java b/src/com/android/settings/deviceinfo/batteryinfo/BatteryTechnologyPreferenceController.java
new file mode 100644
index 00000000000..d9c42511cdf
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/batteryinfo/BatteryTechnologyPreferenceController.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 Paranoid Android
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.deviceinfo.batteryinfo;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.BatteryManager;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.fuelgauge.BatteryUtils;
+
+/**
+ * A controller that manages the information about battery technology.
+ */
+public class BatteryTechnologyPreferenceController extends BasePreferenceController {
+
+ public BatteryTechnologyPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ final Intent batteryIntent = BatteryUtils.getBatteryIntent(mContext);
+ final String technology = batteryIntent.getStringExtra(BatteryManager.EXTRA_TECHNOLOGY);
+
+ return technology != null
+ ? technology
+ : mContext.getText(R.string.battery_technology_not_available);
+ }
+}
diff --git a/src/com/android/settings/deviceinfo/batteryinfo/BatteryTemperaturePreferenceController.java b/src/com/android/settings/deviceinfo/batteryinfo/BatteryTemperaturePreferenceController.java
new file mode 100644
index 00000000000..8049f325114
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/batteryinfo/BatteryTemperaturePreferenceController.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 Paranoid Android
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.deviceinfo.batteryinfo;
+
+import android.content.Context;
+import android.content.Intent;
+import android.icu.text.MeasureFormat;
+import android.icu.util.Measure;
+import android.icu.util.MeasureUnit;
+import android.os.BatteryManager;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.fuelgauge.BatteryUtils;
+
+import java.util.Locale;
+
+/**
+ * A controller that manages the information about battery temperature.
+ */
+public class BatteryTemperaturePreferenceController extends BasePreferenceController {
+
+ public BatteryTemperaturePreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ final Intent batteryIntent = BatteryUtils.getBatteryIntent(mContext);
+ final int temperatureTenths =
+ batteryIntent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, -1);
+
+ if (temperatureTenths > 0) {
+ float temperature = temperatureTenths / 10f;
+
+ return MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.SHORT)
+ .format(new Measure(temperature, MeasureUnit.CELSIUS));
+ }
+
+ return mContext.getText(R.string.battery_temperature_not_available);
+ }
+}
diff --git a/src/com/android/settings/deviceinfo/batteryinfo/BatteryVoltagePreferenceController.java b/src/com/android/settings/deviceinfo/batteryinfo/BatteryVoltagePreferenceController.java
new file mode 100644
index 00000000000..0fc408f4ec2
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/batteryinfo/BatteryVoltagePreferenceController.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 Paranoid Android
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.deviceinfo.batteryinfo;
+
+import android.content.Context;
+import android.content.Intent;
+import android.icu.text.MeasureFormat;
+import android.icu.util.Measure;
+import android.icu.util.MeasureUnit;
+import android.os.BatteryManager;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.fuelgauge.BatteryUtils;
+
+import java.util.Locale;
+
+/**
+ * A controller that manages the information about battery voltage.
+ */
+public class BatteryVoltagePreferenceController extends BasePreferenceController {
+
+ public BatteryVoltagePreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ final Intent batteryIntent = BatteryUtils.getBatteryIntent(mContext);
+ final int voltageMillivolts = batteryIntent.getIntExtra(BatteryManager.EXTRA_VOLTAGE, -1);
+
+ if (voltageMillivolts > 0) {
+ float voltage = voltageMillivolts / 1_000f;
+
+ return MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.SHORT)
+ .format(new Measure(voltage, MeasureUnit.VOLT));
+ }
+
+ return mContext.getText(R.string.battery_voltage_not_available);
+ }
+}
diff --git a/src/com/android/settings/deviceinfo/firmwareversion/BasebandVersionPreferenceController.java b/src/com/android/settings/deviceinfo/firmwareversion/BasebandVersionPreferenceController.java
index dd3d560282a..a81993b2e64 100644
--- a/src/com/android/settings/deviceinfo/firmwareversion/BasebandVersionPreferenceController.java
+++ b/src/com/android/settings/deviceinfo/firmwareversion/BasebandVersionPreferenceController.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.os.SystemProperties;
+import android.text.TextUtils;
import androidx.annotation.VisibleForTesting;
@@ -41,7 +42,13 @@ public int getAvailabilityStatus() {
@Override
public CharSequence getSummary() {
- return SystemProperties.get(BASEBAND_PROPERTY,
+ String baseband = SystemProperties.get(BASEBAND_PROPERTY,
mContext.getString(R.string.device_info_default));
+ for (String str : baseband.split(",")) {
+ if (!TextUtils.isEmpty(str)) {
+ return str;
+ }
+ }
+ return baseband;
}
}
diff --git a/src/com/android/settings/deviceinfo/firmwareversion/DerpFestLogoPreferenceController.java b/src/com/android/settings/deviceinfo/firmwareversion/DerpFestLogoPreferenceController.java
new file mode 100644
index 00000000000..2f941e37260
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/firmwareversion/DerpFestLogoPreferenceController.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.deviceinfo.firmwareversion;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.SystemProperties;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+
+import androidx.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+
+public class DerpFestLogoPreferenceController extends BasePreferenceController {
+
+ private static final Uri INTENT_URI_DATA = Uri.parse("https://derpfest.org/");
+ private static final String TAG = "DerpFestLogoPreferenceCtrl";
+
+ private final PackageManager mPackageManager;
+
+ public DerpFestLogoPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ mPackageManager = mContext.getPackageManager();
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+
+ @Override
+ public boolean handlePreferenceTreeClick(Preference preference) {
+ if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) {
+ return false;
+ }
+
+ final Intent intent = new Intent();
+ intent.setAction(Intent.ACTION_VIEW);
+ intent.setData(INTENT_URI_DATA);
+ if (mPackageManager.queryIntentActivities(intent, 0).isEmpty()) {
+ // Don't send out the intent to stop crash
+ Log.w(TAG, "queryIntentActivities() returns empty");
+ return true;
+ }
+
+ mContext.startActivity(intent);
+ return true;
+ }
+}
diff --git a/src/com/android/settings/deviceinfo/firmwareversion/DerpFestVersionPreferenceController.java b/src/com/android/settings/deviceinfo/firmwareversion/DerpFestVersionPreferenceController.java
new file mode 100644
index 00000000000..6f0d5fbd240
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/firmwareversion/DerpFestVersionPreferenceController.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.deviceinfo.firmwareversion;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settings.Utils;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.slices.Sliceable;
+import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.RestrictedLockUtilsInternal;
+
+public class DerpFestVersionPreferenceController extends BasePreferenceController {
+
+ private static final String TAG = "derpfestVersionDialogCtrl";
+ private static final int DELAY_TIMER_MILLIS = 500;
+ private static final int ACTIVITY_TRIGGER_COUNT = 3;
+
+ private static final String KEY_DERP_VERSION_PROP = "ro.derp.version";
+
+ private final UserManager mUserManager;
+ private final long[] mHits = new long[ACTIVITY_TRIGGER_COUNT];
+
+ private RestrictedLockUtils.EnforcedAdmin mFunDisallowedAdmin;
+ private boolean mFunDisallowedBySystem;
+
+ public DerpFestVersionPreferenceController(Context context, String key) {
+ super(context, key);
+ mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ initializeAdminPermissions();
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+
+ @Override
+ public boolean useDynamicSliceSummary() {
+ return true;
+ }
+
+ @Override
+ public boolean isSliceable() {
+ return true;
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ return SystemProperties.get(KEY_DERP_VERSION_PROP,
+ mContext.getString(R.string.unknown));
+ }
+
+ @Override
+ public boolean handlePreferenceTreeClick(Preference preference) {
+ if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) {
+ return false;
+ }
+ if (Utils.isMonkeyRunning()) {
+ return false;
+ }
+ arrayCopy();
+ mHits[mHits.length - 1] = SystemClock.uptimeMillis();
+ if (mHits[0] >= (SystemClock.uptimeMillis() - DELAY_TIMER_MILLIS)) {
+ if (mUserManager.hasUserRestriction(UserManager.DISALLOW_FUN)) {
+ if (mFunDisallowedAdmin != null && !mFunDisallowedBySystem) {
+ RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mContext,
+ mFunDisallowedAdmin);
+ }
+ Log.d(TAG, "Sorry, no fun for you!");
+ return true;
+ }
+
+ final Intent intent = new Intent(Intent.ACTION_MAIN)
+ .setClassName(
+ "android", com.android.internal.app.PlatLogoActivity.class.getName());
+ try {
+ mContext.startActivity(intent);
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to start activity " + intent.toString());
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Copies the array onto itself to remove the oldest hit.
+ */
+ @VisibleForTesting
+ void arrayCopy() {
+ System.arraycopy(mHits, 1, mHits, 0, mHits.length - 1);
+ }
+
+ @VisibleForTesting
+ void initializeAdminPermissions() {
+ mFunDisallowedAdmin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
+ mContext, UserManager.DISALLOW_FUN, UserHandle.myUserId());
+ mFunDisallowedBySystem = RestrictedLockUtilsInternal.hasBaseUserRestriction(
+ mContext, UserManager.DISALLOW_FUN, UserHandle.myUserId());
+ }
+}
diff --git a/src/com/android/settings/deviceinfo/firmwareversion/KernelVersionPreferenceController.java b/src/com/android/settings/deviceinfo/firmwareversion/KernelVersionPreferenceController.java
index 0500c89371a..cc7cbf24721 100644
--- a/src/com/android/settings/deviceinfo/firmwareversion/KernelVersionPreferenceController.java
+++ b/src/com/android/settings/deviceinfo/firmwareversion/KernelVersionPreferenceController.java
@@ -20,9 +20,21 @@
import com.android.settings.core.BasePreferenceController;
import com.android.settingslib.DeviceInfoUtils;
+import androidx.preference.Preference;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
public class KernelVersionPreferenceController extends BasePreferenceController {
+ private static final String KEY_KERNEL_VERSION = "kernel_version";
+ private static final String FILENAME_PROC_VERSION = "/proc/version";
+ private static final String LOG_TAG = "KernelVersionPreferenceController";
+ private boolean fullKernelVersion = false;
+
public KernelVersionPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@@ -36,4 +48,51 @@ public int getAvailabilityStatus() {
public CharSequence getSummary() {
return DeviceInfoUtils.getFormattedKernelVersion(mContext);
}
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY_KERNEL_VERSION;
+ }
+
+ @Override
+ public boolean handlePreferenceTreeClick(Preference preference) {
+ if (!TextUtils.equals(preference.getKey(), KEY_KERNEL_VERSION)) {
+ return false;
+ }
+ if(fullKernelVersion) {
+ preference.setSummary(DeviceInfoUtils.getFormattedKernelVersion(mContext));
+ fullKernelVersion = false;
+ } else {
+ preference.setSummary(getFullKernelVersion());
+ fullKernelVersion = true;
+ }
+ return false;
+ }
+
+ private String getFullKernelVersion() {
+ String procVersionStr;
+ try {
+ procVersionStr = readLine(FILENAME_PROC_VERSION);
+ return procVersionStr;
+ } catch (IOException e) {
+ Log.e(LOG_TAG,
+ "IO Exception when getting kernel version for Device Info screen", e);
+ return "Unavailable";
+ }
+ }
+
+ /**
+ * Reads a line from the specified file.
+ * @param filename the file to read from
+ * @return the first line, if any.
+ * @throws IOException if the file couldn't be read
+ */
+ private static String readLine(String filename) throws IOException {
+ BufferedReader reader = new BufferedReader(new FileReader(filename), 256);
+ try {
+ return reader.readLine();
+ } finally {
+ reader.close();
+ }
+ }
}
diff --git a/src/com/android/settings/deviceinfo/firmwareversion/MainlineModuleVersionPreferenceController.java b/src/com/android/settings/deviceinfo/firmwareversion/MainlineModuleVersionPreferenceController.java
index 4c02feb044f..4171a9c4fea 100644
--- a/src/com/android/settings/deviceinfo/firmwareversion/MainlineModuleVersionPreferenceController.java
+++ b/src/com/android/settings/deviceinfo/firmwareversion/MainlineModuleVersionPreferenceController.java
@@ -74,8 +74,7 @@ public int getAvailabilityStatus() {
}
private void initModules() {
- final String moduleProvider = mContext.getString(
- com.android.internal.R.string.config_defaultModuleMetadataProvider);
+ final String moduleProvider = "com.google.android.modulemetadata";
if (!TextUtils.isEmpty(moduleProvider)) {
try {
mModuleVersion =
diff --git a/src/com/android/settings/deviceinfo/firmwareversion/SelinuxStatusPreferenceController.java b/src/com/android/settings/deviceinfo/firmwareversion/SelinuxStatusPreferenceController.java
new file mode 100644
index 00000000000..38b1976aa07
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/firmwareversion/SelinuxStatusPreferenceController.java
@@ -0,0 +1,48 @@
+/*
+ * 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.settings.deviceinfo.firmwareversion;
+
+import android.content.Context;
+import android.os.SELinux;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+
+public class SelinuxStatusPreferenceController extends BasePreferenceController {
+
+ private static final String TAG = "SelinuxStatusCtrl";
+
+ public SelinuxStatusPreferenceController(Context context, String key) {
+ super(context, key);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ if (!SELinux.isSELinuxEnabled()) {
+ return (CharSequence) mContext.getString(R.string.selinux_status_disabled);
+ } else if (!SELinux.isSELinuxEnforced()) {
+ return (CharSequence) mContext.getString(R.string.selinux_status_permissive);
+ } else {
+ return (CharSequence) mContext.getString(R.string.selinux_status_enforcing);
+ }
+ }
+}
diff --git a/src/com/android/settings/deviceinfo/hardwareinfo/SoCModelPreferenceController.java b/src/com/android/settings/deviceinfo/hardwareinfo/SoCModelPreferenceController.java
new file mode 100644
index 00000000000..04bbba2c6e6
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/hardwareinfo/SoCModelPreferenceController.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.deviceinfo.hardwareinfo;
+
+import android.content.Context;
+import android.os.Build;
+import android.os.SystemProperties;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.slices.Sliceable;
+
+public class SoCModelPreferenceController extends BasePreferenceController {
+
+ public SoCModelPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return mContext.getResources().getBoolean(R.bool.config_show_device_model)
+ ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+ }
+
+ @Override
+ public boolean useDynamicSliceSummary() {
+ return true;
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ if (!Build.SOC_MODEL.equals(Build.UNKNOWN)) {
+ if (!Build.SOC_MANUFACTURER.equals(Build.UNKNOWN)) {
+ return Build.SOC_MANUFACTURER + " " + Build.SOC_MODEL;
+ }
+ return Build.SOC_MODEL;
+ }
+ return SystemProperties.get("ro.board.platform");
+ }
+}
diff --git a/src/com/android/settings/deviceinfo/hardwareinfo/TotalRAMPreferenceController.java b/src/com/android/settings/deviceinfo/hardwareinfo/TotalRAMPreferenceController.java
new file mode 100644
index 00000000000..9009610af14
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/hardwareinfo/TotalRAMPreferenceController.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2023 The LeafOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.deviceinfo.hardwareinfo;
+
+import android.content.Context;
+import android.text.format.Formatter;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.applications.ProcStatsData;
+import com.android.settings.applications.ProcessStatsBase;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settingslib.utils.ThreadUtils;
+
+public class TotalRAMPreferenceController extends BasePreferenceController implements
+ PreferenceControllerMixin {
+
+ private ProcStatsData mProcStatsData;
+ private PreferenceScreen mPreferenceScreen;
+
+ public TotalRAMPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return mContext.getResources().getBoolean(R.bool.config_show_device_model)
+ ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+
+ mProcStatsData = getProcStatsData();
+ mPreferenceScreen = screen;
+ setDuration();
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ // This is posted on the background thread to speed up fragment launch time for dev options
+ // mProcStasData.refreshStats(true) takes ~20ms to run.
+ ThreadUtils.postOnBackgroundThread(() -> {
+ mProcStatsData.refreshStats(true);
+ final ProcStatsData.MemInfo memInfo = mProcStatsData.getMemInfo();
+ final String totalResult = Formatter.formatShortFileSize(mContext,
+ (long) memInfo.realTotalRam);
+ ThreadUtils.postOnMainThread(
+ () -> mPreferenceScreen.findPreference(mPreferenceKey).setSummary(totalResult));
+ });
+ }
+
+ @VisibleForTesting
+ void setDuration() {
+ mProcStatsData.setDuration(ProcessStatsBase.sDurations[0] /* 3 hours */);
+ }
+
+ @VisibleForTesting
+ ProcStatsData getProcStatsData() {
+ return new ProcStatsData(mContext, false);
+ }
+}
diff --git a/src/com/android/settings/deviceinfo/imei/ImeiInfoPreferenceController.java b/src/com/android/settings/deviceinfo/imei/ImeiInfoPreferenceController.java
index ff55184741a..2045b9661c0 100644
--- a/src/com/android/settings/deviceinfo/imei/ImeiInfoPreferenceController.java
+++ b/src/com/android/settings/deviceinfo/imei/ImeiInfoPreferenceController.java
@@ -33,6 +33,7 @@
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
+import com.android.settings.deviceinfo.PhoneNumberSummaryPreference;
import com.android.settings.deviceinfo.simstatus.SlotSimStatus;
import com.android.settings.network.SubscriptionUtil;
import com.android.settingslib.Utils;
@@ -102,7 +103,6 @@ public void displayPreference(PreferenceScreen screen) {
multiImeiPreference.setKey(DEFAULT_KEY + (1 + simSlotNumber));
multiImeiPreference.setEnabled(true);
multiImeiPreference.setCopyingEnabled(true);
-
category.addPreference(multiImeiPreference);
}
}
@@ -112,6 +112,11 @@ public void updateState(Preference preference) {
updatePreference(preference, keyToSlotIndex(preference.getKey()));
}
+ @Override
+ public CharSequence getSummary() {
+ return mContext.getString(R.string.device_info_protected_single_press);
+ }
+
private CharSequence getSummary(int simSlot) {
final int phoneType = getPhoneType(simSlot);
return phoneType == PHONE_TYPE_CDMA ? mTelephonyManager.getMeid(simSlot)
@@ -145,12 +150,8 @@ public boolean useDynamicSliceSummary() {
@VisibleForTesting
protected void updatePreference(Preference preference, int simSlot) {
- if (simSlot < 0) {
- preference.setVisible(false);
- return;
- }
preference.setTitle(getTitle(simSlot));
- preference.setSummary(getSummary(simSlot));
+ preference.setSummary(getSummary());
}
private CharSequence getTitleForGsmPhone(int simSlot, boolean isPrimaryImei) {
@@ -194,6 +195,6 @@ public int getPhoneType(int slotIndex) {
@VisibleForTesting
Preference createNewPreference(Context context) {
- return new Preference(context);
+ return new PhoneNumberSummaryPreference(context);
}
}
diff --git a/src/com/android/settings/deviceinfo/simstatus/SimEidPreferenceController.kt b/src/com/android/settings/deviceinfo/simstatus/SimEidPreferenceController.kt
index f765d8c6cca..e0376dc1944 100644
--- a/src/com/android/settings/deviceinfo/simstatus/SimEidPreferenceController.kt
+++ b/src/com/android/settings/deviceinfo/simstatus/SimEidPreferenceController.kt
@@ -92,7 +92,6 @@ class SimEidPreferenceController(context: Context, preferenceKey: String) :
}
preference.title = title
preference.dialogTitle = title
- preference.summary = eid
updateDialog()
}
}
@@ -131,6 +130,9 @@ class SimEidPreferenceController(context: Context, preferenceKey: String) :
val qrCodeView = dialog.requireViewById(R.id.esim_id_qrcode)
qrCodeView.setImageBitmap(getEidQrCode(eid))
+
+ // After "Tap to show", eid is displayed on preference.
+ preference.summary = textView.text
}
override fun handlePreferenceTreeClick(preference: Preference): Boolean {
diff --git a/src/com/android/settings/display/AODSchedule.java b/src/com/android/settings/display/AODSchedule.java
new file mode 100644
index 00000000000..dffab166245
--- /dev/null
+++ b/src/com/android/settings/display/AODSchedule.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2021 Yet Another AOSP Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.display;
+
+import static com.android.settings.display.AODSchedulePreferenceController.MODE_DISABLED;
+import static com.android.settings.display.AODSchedulePreferenceController.MODE_NIGHT;
+import static com.android.settings.display.AODSchedulePreferenceController.MODE_TIME;
+import static com.android.settings.display.AODSchedulePreferenceController.MODE_MIXED_SUNSET;
+import static com.android.settings.display.AODSchedulePreferenceController.MODE_MIXED_SUNRISE;
+
+import android.app.TimePickerDialog;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.format.DateFormat;
+
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.search.SearchIndexable;
+
+import java.time.format.DateTimeFormatter;
+import java.time.LocalTime;
+
+@SearchIndexable
+public class AODSchedule extends SettingsPreferenceFragment implements
+ Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener {
+
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider(R.xml.always_on_display_schedule);
+
+ private static final String MODE_KEY = "doze_always_on_auto_mode";
+ private static final String SINCE_PREF_KEY = "doze_always_on_auto_since";
+ private static final String TILL_PREF_KEY = "doze_always_on_auto_till";
+
+ private ListPreference mModePref;
+ private Preference mSincePref;
+ private Preference mTillPref;
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.always_on_display_schedule;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsProto.MetricsEvent.DERP;
+ }
+
+ @Override
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ super.onCreatePreferences(savedInstanceState, rootKey);
+
+ mSincePref = findPreference(SINCE_PREF_KEY);
+ mSincePref.setOnPreferenceClickListener(this);
+ mTillPref = findPreference(TILL_PREF_KEY);
+ mTillPref.setOnPreferenceClickListener(this);
+
+ final int mode = Settings.Secure.getIntForUser(getActivity().getContentResolver(),
+ MODE_KEY, MODE_DISABLED, UserHandle.USER_CURRENT);
+ mModePref = findPreference(MODE_KEY);
+ mModePref.setValue(String.valueOf(mode));
+ mModePref.setSummary(mModePref.getEntry());
+ mModePref.setOnPreferenceChangeListener(this);
+
+ updateTimeEnablement(mode);
+ updateTimeSummary(mode);
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object objValue) {
+ final int value = Integer.parseInt((String) objValue);
+ final int index = mModePref.findIndexOfValue((String) objValue);
+ mModePref.setSummary(mModePref.getEntries()[index]);
+ Settings.Secure.putIntForUser(getActivity().getContentResolver(),
+ MODE_KEY, value, UserHandle.USER_CURRENT);
+ updateTimeEnablement(value);
+ updateTimeSummary(value);
+ return true;
+ }
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ final String[] times = getCustomTimeSetting();
+ final boolean isSince = preference == mSincePref;
+ final int hour, minute;
+ if (isSince) {
+ final String[] sinceValues = times[0].split(":", 0);
+ hour = Integer.parseInt(sinceValues[0]);
+ minute = Integer.parseInt(sinceValues[1]);
+ } else {
+ final String[] tillValues = times[1].split(":", 0);
+ hour = Integer.parseInt(tillValues[0]);
+ minute = Integer.parseInt(tillValues[1]);
+ }
+ final TimePickerDialog.OnTimeSetListener listener = (view, hourOfDay, minute1) ->
+ updateTimeSetting(isSince, hourOfDay, minute1);
+ final TimePickerDialog dialog = new TimePickerDialog(getContext(), listener,
+ hour, minute, DateFormat.is24HourFormat(getContext()));
+ dialog.show();
+ return true;
+ }
+
+ private String[] getCustomTimeSetting() {
+ final String value = Settings.Secure.getStringForUser(getActivity().getContentResolver(),
+ Settings.Secure.DOZE_ALWAYS_ON_AUTO_TIME, UserHandle.USER_CURRENT);
+ if (value == null || value.isEmpty()) return new String[] { "20:00", "07:00" };
+ return value.split(",", 0);
+ }
+
+ private void updateTimeEnablement(int mode) {
+ mSincePref.setEnabled(mode == MODE_TIME || mode == MODE_MIXED_SUNRISE);
+ mTillPref.setEnabled(mode == MODE_TIME || mode == MODE_MIXED_SUNSET);
+ }
+
+ private void updateTimeSummary(int mode) {
+ updateTimeSummary(getCustomTimeSetting(), mode);
+ }
+
+ private void updateTimeSummary(String[] times, int mode) {
+ if (mode == MODE_DISABLED) {
+ mSincePref.setSummary("-");
+ mTillPref.setSummary("-");
+ return;
+ }
+
+ if (mode == MODE_NIGHT) {
+ mSincePref.setSummary(R.string.always_on_display_schedule_sunset);
+ mTillPref.setSummary(R.string.always_on_display_schedule_sunrise);
+ return;
+ }
+
+ final String outputFormat = DateFormat.is24HourFormat(getContext()) ? "HH:mm" : "hh:mm a";
+ final DateTimeFormatter outputFormatter = DateTimeFormatter.ofPattern(outputFormat);
+ final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm");
+ final LocalTime sinceDT = LocalTime.parse(times[0], formatter);
+ final LocalTime tillDT = LocalTime.parse(times[1], formatter);
+
+ if (mode == MODE_MIXED_SUNSET) {
+ mSincePref.setSummary(R.string.always_on_display_schedule_sunset);
+ mTillPref.setSummary(tillDT.format(outputFormatter));
+ } else if (mode == MODE_MIXED_SUNRISE) {
+ mTillPref.setSummary(R.string.always_on_display_schedule_sunrise);
+ mSincePref.setSummary(sinceDT.format(outputFormatter));
+ } else {
+ mSincePref.setSummary(sinceDT.format(outputFormatter));
+ mTillPref.setSummary(tillDT.format(outputFormatter));
+ }
+ }
+
+ private void updateTimeSetting(boolean since, int hour, int minute) {
+ final String[] times = getCustomTimeSetting();
+ String nHour = "";
+ String nMinute = "";
+ if (hour < 10) nHour += "0";
+ if (minute < 10) nMinute += "0";
+ nHour += String.valueOf(hour);
+ nMinute += String.valueOf(minute);
+ times[since ? 0 : 1] = nHour + ":" + nMinute;
+ Settings.Secure.putStringForUser(getActivity().getContentResolver(),
+ Settings.Secure.DOZE_ALWAYS_ON_AUTO_TIME,
+ times[0] + "," + times[1], UserHandle.USER_CURRENT);
+ updateTimeSummary(times, Integer.parseInt(mModePref.getValue()));
+ }
+}
diff --git a/src/com/android/settings/display/AODSchedulePreferenceController.kt b/src/com/android/settings/display/AODSchedulePreferenceController.kt
new file mode 100644
index 00000000000..0b62f22518a
--- /dev/null
+++ b/src/com/android/settings/display/AODSchedulePreferenceController.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2021 Yet Another AOSP Project
+ * Copyright (C) 2022 FlamingoOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.settings.display
+
+import android.content.Context
+import android.hardware.display.AmbientDisplayConfiguration
+import android.os.SystemProperties
+import android.os.UserHandle
+import android.provider.Settings
+
+import com.android.settings.R
+import com.android.settings.core.BasePreferenceController
+
+class AODSchedulePreferenceController(
+ context: Context,
+ key: String,
+) : BasePreferenceController(context, key) {
+
+ private var ambientDisplayConfig: AmbientDisplayConfiguration? = null
+
+ override fun getAvailabilityStatus() =
+ if (isAODAvailable()) AVAILABLE else UNSUPPORTED_ON_DEVICE
+
+ override fun getSummary(): CharSequence {
+ val mode = Settings.Secure.getIntForUser(mContext.contentResolver,
+ Settings.Secure.DOZE_ALWAYS_ON_AUTO_MODE, 0, UserHandle.USER_CURRENT)
+ return when (mode) {
+ MODE_NIGHT -> mContext.getString(R.string.night_display_auto_mode_twilight)
+ MODE_TIME -> mContext.getString(R.string.night_display_auto_mode_custom)
+ MODE_MIXED_SUNSET -> mContext.getString(R.string.always_on_display_schedule_mixed_sunset)
+ MODE_MIXED_SUNRISE -> mContext.getString(R.string.always_on_display_schedule_mixed_sunrise)
+ else -> mContext.getString(R.string.disabled)
+ }
+ }
+
+ fun setConfig(config: AmbientDisplayConfiguration): AODSchedulePreferenceController {
+ ambientDisplayConfig = config
+ return this
+ }
+
+ private fun getConfig(): AmbientDisplayConfiguration {
+ if (ambientDisplayConfig == null) {
+ ambientDisplayConfig = AmbientDisplayConfiguration(mContext)
+ }
+ return ambientDisplayConfig!!
+ }
+
+ private fun isAODAvailable(): Boolean {
+ return getConfig().alwaysOnAvailableForUser(UserHandle.myUserId()) &&
+ !SystemProperties.getBoolean(PROP_AWARE_AVAILABLE, false)
+ }
+
+ companion object {
+ const val MODE_DISABLED = 0
+ const val MODE_NIGHT = 1
+ const val MODE_TIME = 2
+ const val MODE_MIXED_SUNSET = 3
+ const val MODE_MIXED_SUNRISE = 4
+
+ private const val PROP_AWARE_AVAILABLE = "ro.vendor.aware_available"
+ }
+}
diff --git a/src/com/android/settings/display/AmbientDisplayAlwaysOnPreferenceController.java b/src/com/android/settings/display/AmbientDisplayAlwaysOnPreferenceController.java
index 245803493e2..bc186ba136c 100644
--- a/src/com/android/settings/display/AmbientDisplayAlwaysOnPreferenceController.java
+++ b/src/com/android/settings/display/AmbientDisplayAlwaysOnPreferenceController.java
@@ -74,7 +74,7 @@ public int getSliceHighlightMenuRes() {
@Override
public boolean isChecked() {
- return getConfig().alwaysOnEnabled(MY_USER);
+ return getConfig().alwaysOnEnabledSetting(MY_USER);
}
@Override
diff --git a/src/com/android/settings/display/AutoBrightnessObserver.java b/src/com/android/settings/display/AutoBrightnessObserver.java
new file mode 100644
index 00000000000..ba27b683527
--- /dev/null
+++ b/src/com/android/settings/display/AutoBrightnessObserver.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 Yet Another AOSP Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.settings.display;
+
+import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+public class AutoBrightnessObserver {
+ private final ContentObserver mContentObserver;
+ private final Context mContext;
+ private Runnable mCallback;
+
+ public AutoBrightnessObserver(Context context) {
+ mContext = context;
+ mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
+ @Override
+ public void onChange(boolean selfChange) {
+ super.onChange(selfChange);
+ mCallback.run();
+ }
+ };
+ }
+
+ public void subscribe(Runnable callback) {
+ mCallback = callback;
+ mContext.getContentResolver().registerContentObserver(
+ Settings.System.getUriFor(SCREEN_BRIGHTNESS_MODE),
+ false, mContentObserver);
+ }
+
+ public void unsubscribe() {
+ mContext.getContentResolver().unregisterContentObserver(mContentObserver);
+ }
+}
diff --git a/src/com/android/settings/display/AutoBrightnessOneShotPreferenceController.java b/src/com/android/settings/display/AutoBrightnessOneShotPreferenceController.java
new file mode 100644
index 00000000000..0decc50aced
--- /dev/null
+++ b/src/com/android/settings/display/AutoBrightnessOneShotPreferenceController.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2021 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.display;
+
+import android.content.Context;
+
+import android.provider.Settings;
+
+import com.android.settings.R;
+import com.android.settings.core.TogglePreferenceController;
+
+public class AutoBrightnessOneShotPreferenceController extends TogglePreferenceController {
+
+ public AutoBrightnessOneShotPreferenceController(Context context, String key) {
+ super(context, key);
+ }
+
+ @Override
+ public boolean isChecked() {
+ return Settings.System.getInt(mContext.getContentResolver(),
+ Settings.System.AUTO_BRIGHTNESS_ONE_SHOT, 0) == 1;
+ }
+
+ @Override
+ public boolean setChecked(boolean isChecked) {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.AUTO_BRIGHTNESS_ONE_SHOT, isChecked ? 1 : 0);
+ return true;
+ }
+
+ @Override
+ @AvailabilityStatus
+ public int getAvailabilityStatus() {
+ return mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_automatic_brightness_available)
+ ? AVAILABLE_UNSEARCHABLE
+ : UNSUPPORTED_ON_DEVICE;
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ return mContext.getText(isChecked()
+ ? R.string.auto_brightness_summary_on
+ : R.string.auto_brightness_summary_off);
+ }
+
+ @Override
+ public int getSliceHighlightMenuRes() {
+ return R.string.menu_key_display;
+ }
+}
diff --git a/src/com/android/settings/display/AutoBrightnessPreference.java b/src/com/android/settings/display/AutoBrightnessPreference.java
new file mode 100644
index 00000000000..7a8344391b5
--- /dev/null
+++ b/src/com/android/settings/display/AutoBrightnessPreference.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 Yet Another AOSP Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.settings.display;
+
+import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE;
+import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.util.AttributeSet;
+
+import com.android.settings.display.AutoBrightnessObserver;
+
+import com.android.settingslib.PrimarySwitchPreference;
+
+/**
+ * component for the display auto brightness
+ */
+public class AutoBrightnessPreference extends PrimarySwitchPreference {
+
+ private final AutoBrightnessObserver mAutoBrightnessObserver;
+
+ private final Runnable mCallback = () -> {
+ final int value = Settings.System.getInt(
+ getContext().getContentResolver(),
+ SCREEN_BRIGHTNESS_MODE, SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ setChecked(value == SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ };
+
+ public AutoBrightnessPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mAutoBrightnessObserver = new AutoBrightnessObserver(context);
+ }
+
+ @Override
+ public void onAttached() {
+ super.onAttached();
+ mAutoBrightnessObserver.subscribe(mCallback);
+ }
+
+ @Override
+ public void onDetached() {
+ super.onDetached();
+ mAutoBrightnessObserver.unsubscribe();
+ }
+}
diff --git a/src/com/android/settings/display/AutoBrightnessSettings.java b/src/com/android/settings/display/AutoBrightnessSettings.java
index 0c594730d5c..43ad7fa7415 100644
--- a/src/com/android/settings/display/AutoBrightnessSettings.java
+++ b/src/com/android/settings/display/AutoBrightnessSettings.java
@@ -16,12 +16,18 @@
package com.android.settings.display;
+import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE;
+import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
+
import android.app.settings.SettingsEnums;
import android.os.Bundle;
+import android.provider.Settings;
+import com.android.settings.display.AutoBrightnessObserver;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.widget.SettingsMainSwitchPreference;
import com.android.settingslib.search.SearchIndexable;
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
@@ -29,9 +35,33 @@ public class AutoBrightnessSettings extends DashboardFragment {
private static final String TAG = "AutoBrightnessSettings";
+ private AutoBrightnessObserver mAutoBrightnessObserver;
+
+ private final Runnable mCallback = () -> {
+ final int value = Settings.System.getInt(
+ getContext().getContentResolver(),
+ SCREEN_BRIGHTNESS_MODE, SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ SettingsMainSwitchPreference pref = findPreference("auto_brightness");
+ if (pref == null) return;
+ pref.setChecked(value == SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ };
+
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
+ mAutoBrightnessObserver = new AutoBrightnessObserver(getContext());
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ mAutoBrightnessObserver.subscribe(mCallback);
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ mAutoBrightnessObserver.unsubscribe();
}
@Override
diff --git a/src/com/android/settings/display/AutoRotatePreferenceController.java b/src/com/android/settings/display/AutoRotatePreferenceController.java
index ec2592d38b5..707579d0bcd 100644
--- a/src/com/android/settings/display/AutoRotatePreferenceController.java
+++ b/src/com/android/settings/display/AutoRotatePreferenceController.java
@@ -18,6 +18,7 @@
import android.text.TextUtils;
import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
import com.android.internal.view.RotationPolicy;
import com.android.settings.R;
@@ -29,12 +30,17 @@
import com.android.settingslib.core.lifecycle.events.OnPause;
import com.android.settingslib.core.lifecycle.events.OnResume;
+import org.derpfest.support.preferences.SystemSettingSwitchPreference;
+
public class AutoRotatePreferenceController extends TogglePreferenceController implements
PreferenceControllerMixin, Preference.OnPreferenceChangeListener, LifecycleObserver,
OnResume, OnPause {
+ private final String FLOATING_BUTTON_PREF_KEY = "enable_floating_rotation_button";
+
private final MetricsFeatureProvider mMetricsFeatureProvider;
private Preference mPreference;
+ private SystemSettingSwitchPreference mFloatingPref;
private RotationPolicy.RotationPolicyListener mRotationPolicyListener;
public AutoRotatePreferenceController(Context context, String key) {
@@ -42,9 +48,16 @@ public AutoRotatePreferenceController(Context context, String key) {
mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
}
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mFloatingPref = screen.findPreference(FLOATING_BUTTON_PREF_KEY);
+ }
+
@Override
public void updateState(Preference preference) {
mPreference = preference;
+ if (mFloatingPref != null) mFloatingPref.setEnabled(!isChecked());
super.updateState(preference);
}
@@ -105,6 +118,7 @@ public boolean setChecked(boolean isChecked) {
isLocked);
RotationPolicy.setRotationLock(mContext, isLocked,
/* caller= */ "AutoRotatePreferenceController#setChecked");
+ if (mFloatingPref != null) mFloatingPref.setEnabled(!isChecked);
return true;
}
}
diff --git a/src/com/android/settings/display/BatteryPercentagePreferenceController.java b/src/com/android/settings/display/BatteryPercentagePreferenceController.java
index a7113b3d490..398c8279912 100644
--- a/src/com/android/settings/display/BatteryPercentagePreferenceController.java
+++ b/src/com/android/settings/display/BatteryPercentagePreferenceController.java
@@ -56,12 +56,7 @@ public void displayPreference(PreferenceScreen screen) {
@Override
public int getAvailabilityStatus() {
- if (!Utils.isBatteryPresent(mContext)) {
- return CONDITIONALLY_UNAVAILABLE;
- }
- return mContext.getResources().getBoolean(
- R.bool.config_battery_percentage_setting_available) ? AVAILABLE
- : UNSUPPORTED_ON_DEVICE;
+ return UNSUPPORTED_ON_DEVICE;
}
@Override
diff --git a/src/com/android/settings/display/ColorModePreferenceController.java b/src/com/android/settings/display/ColorModePreferenceController.java
index 6cd4867b588..14a2241481c 100644
--- a/src/com/android/settings/display/ColorModePreferenceController.java
+++ b/src/com/android/settings/display/ColorModePreferenceController.java
@@ -28,8 +28,11 @@ public ColorModePreferenceController(Context context, String key) {
@Override
public int getAvailabilityStatus() {
+ final int[] availableColorModes = mContext.getResources().getIntArray(
+ com.android.internal.R.array.config_availableColorModes);
return mContext.getSystemService(ColorDisplayManager.class)
.isDeviceColorManaged()
+ && availableColorModes.length > 0
&& !ColorDisplayManager.areAccessibilityTransformsEnabled(mContext) ?
AVAILABLE : DISABLED_FOR_USER;
}
diff --git a/src/com/android/settings/display/ControlsPrivacyPreferenceController.java b/src/com/android/settings/display/ControlsPrivacyPreferenceController.java
index b9a2bfa84f6..1345bf9267f 100644
--- a/src/com/android/settings/display/ControlsPrivacyPreferenceController.java
+++ b/src/com/android/settings/display/ControlsPrivacyPreferenceController.java
@@ -92,7 +92,7 @@ private boolean isSecure() {
.getSecurityFeatureProvider()
.getLockPatternUtils(mContext);
final int userId = UserHandle.myUserId();
- return utils.isSecure(userId);
+ return !utils.isLockScreenDisabled(userId);
}
private boolean isControlsAvailable() {
diff --git a/src/com/android/settings/display/DisplayRotationPreferenceController.java b/src/com/android/settings/display/DisplayRotationPreferenceController.java
new file mode 100644
index 00000000000..31713b7cb43
--- /dev/null
+++ b/src/com/android/settings/display/DisplayRotationPreferenceController.java
@@ -0,0 +1,48 @@
+/*
+ * 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.settings.display;
+
+import android.content.Context;
+
+import androidx.preference.Preference;
+
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.core.PreferenceControllerMixin;
+
+import com.android.internal.view.RotationPolicy;
+
+import com.android.settings.R;
+
+public class DisplayRotationPreferenceController extends BasePreferenceController implements
+ PreferenceControllerMixin {
+
+ public DisplayRotationPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ String summary = RotationPolicy.isRotationLocked(mContext) ?
+ mContext.getString(R.string.display_rotation_disabled) :
+ mContext.getString(R.string.display_rotation_enabled);
+ preference.setSummary(summary);
+ }
+}
diff --git a/src/com/android/settings/display/DozeOnChargePreferenceController.kt b/src/com/android/settings/display/DozeOnChargePreferenceController.kt
new file mode 100644
index 00000000000..bcecad88f06
--- /dev/null
+++ b/src/com/android/settings/display/DozeOnChargePreferenceController.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2022 FlamingoOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.display
+
+import android.content.Context
+import android.database.ContentObserver
+import android.hardware.display.AmbientDisplayConfiguration
+import android.os.Handler
+import android.os.Looper
+import android.os.UserHandle
+import android.provider.Settings
+
+import androidx.lifecycle.Lifecycle.Event
+import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.LifecycleOwner
+import androidx.preference.Preference
+import androidx.preference.PreferenceScreen
+import androidx.preference.SwitchPreferenceCompat
+
+import com.android.settings.R
+import com.android.settings.core.TogglePreferenceController
+import com.android.settingslib.core.lifecycle.Lifecycle
+
+class DozeOnChargePreferenceController(
+ context: Context,
+ lifecycle: Lifecycle?,
+) : TogglePreferenceController(context, KEY),
+ LifecycleEventObserver {
+
+ private var config: AmbientDisplayConfiguration = AmbientDisplayConfiguration(context)
+ private var preference: Preference? = null
+
+ private val settingsObserver = object : ContentObserver(
+ Handler(Looper.getMainLooper())
+ ) {
+ override fun onChange(selfChange: Boolean) {
+ preference?.let{ updateState(it) }
+ }
+ }
+
+ init {
+ lifecycle?.addObserver(this)
+ }
+
+ override fun getAvailabilityStatus(): Int {
+ return if (config.alwaysOnAvailableForUser(UserHandle.USER_CURRENT)) {
+ if (config.alwaysOnEnabledSetting(UserHandle.USER_CURRENT))
+ DISABLED_DEPENDENT_SETTING
+ else
+ AVAILABLE
+ } else {
+ UNSUPPORTED_ON_DEVICE
+ }
+ }
+
+ override fun displayPreference(screen: PreferenceScreen) {
+ super.displayPreference(screen)
+ preference = screen.findPreference(preferenceKey)
+ }
+
+ override fun isChecked(): Boolean =
+ Settings.Secure.getIntForUser(mContext.contentResolver,
+ Settings.Secure.DOZE_ON_CHARGE, 0, UserHandle.USER_CURRENT) == 1
+
+ override fun setChecked(isChecked: Boolean): Boolean =
+ Settings.Secure.putIntForUser(
+ mContext.contentResolver,
+ Settings.Secure.DOZE_ON_CHARGE,
+ if (isChecked) 1 else 0,
+ UserHandle.USER_CURRENT
+ )
+
+ override fun getSliceHighlightMenuRes() = R.string.menu_key_display
+
+ override fun updateState(preference: Preference) {
+ preference.setEnabled(getAvailabilityStatus() == AVAILABLE)
+ super.updateState(preference)
+ }
+
+ override fun onStateChanged(owner: LifecycleOwner, event: Event) {
+ if (event == Event.ON_START) {
+ mContext.contentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.DOZE_ALWAYS_ON),
+ false,
+ settingsObserver
+ )
+ } else if (event == Event.ON_STOP) {
+ mContext.contentResolver.unregisterContentObserver(settingsObserver)
+ }
+ }
+
+ fun setConfig(
+ config: AmbientDisplayConfiguration
+ ): DozeOnChargePreferenceController {
+ this.config = config
+ return this
+ }
+
+ companion object {
+ private const val KEY = "doze_on_charge"
+ }
+}
diff --git a/src/com/android/settings/display/EnableBlursPreferenceController.java b/src/com/android/settings/display/EnableBlursPreferenceController.java
new file mode 100644
index 00000000000..2776f0d6121
--- /dev/null
+++ b/src/com/android/settings/display/EnableBlursPreferenceController.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.display;
+
+import static android.view.CrossWindowBlurListeners.CROSS_WINDOW_BLUR_SUPPORTED;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.PowerManager;
+import android.provider.Settings;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.SwitchPreferenceCompat;
+
+import com.android.settings.R;
+import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settings.core.TogglePreferenceController;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
+
+/**
+ * Controller that toggles window blurs on devices that support it.
+ */
+public final class EnableBlursPreferenceController extends TogglePreferenceController
+ implements Preference.OnPreferenceChangeListener, PreferenceControllerMixin,
+ LifecycleObserver, OnStart, OnStop {
+
+ private Preference mPreference;
+ private final PowerManager mPowerManager;
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (mPreference != null)
+ updateState(mPreference);
+ }
+ };
+
+ public EnableBlursPreferenceController(Context context, String key) {
+ super(context, key);
+ mPowerManager = context.getSystemService(PowerManager.class);
+ }
+
+ @Override
+ public void onStart() {
+ mContext.registerReceiver(mReceiver,
+ new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
+ }
+
+ @Override
+ public void onStop() {
+ mContext.unregisterReceiver(mReceiver);
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mPreference = screen.findPreference(getPreferenceKey());
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ return mContext.getString(mPowerManager.isPowerSaveMode()
+ ? R.string.dark_ui_mode_disabled_summary_dark_theme_on
+ : R.string.enable_blurs_on_windows_summary);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return CROSS_WINDOW_BLUR_SUPPORTED ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+ }
+
+ @Override
+ public boolean isChecked() {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.DISABLE_WINDOW_BLURS, 0) == 0;
+ }
+
+ @Override
+ public boolean setChecked(boolean isChecked) {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.DISABLE_WINDOW_BLURS, isChecked ? 0 : 1);
+ return true;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ super.updateState(preference);
+ refreshSummary(preference);
+ preference.setEnabled(!mPowerManager.isPowerSaveMode());
+ }
+
+ @Override
+ public int getSliceHighlightMenuRes() {
+ return R.string.menu_key_display;
+ }
+
+}
diff --git a/src/com/android/settings/development/ForceDarkPreferenceController.java b/src/com/android/settings/display/ForceDarkPreferenceController.java
similarity index 98%
rename from src/com/android/settings/development/ForceDarkPreferenceController.java
rename to src/com/android/settings/display/ForceDarkPreferenceController.java
index c81c16f3263..cbd02b00ac1 100644
--- a/src/com/android/settings/development/ForceDarkPreferenceController.java
+++ b/src/com/android/settings/display/ForceDarkPreferenceController.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settings.development;
+package com.android.settings.display;
import android.content.Context;
import android.os.SystemProperties;
diff --git a/src/com/android/settings/display/LidBehaviorPreferenceController.java b/src/com/android/settings/display/LidBehaviorPreferenceController.java
new file mode 100644
index 00000000000..71f3a93dd93
--- /dev/null
+++ b/src/com/android/settings/display/LidBehaviorPreferenceController.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2024 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.display;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.provider.Settings;
+
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.core.BasePreferenceController;
+
+public class LidBehaviorPreferenceController extends BasePreferenceController
+ implements Preference.OnPreferenceChangeListener {
+
+ public LidBehaviorPreferenceController(Context context, String key) {
+ super(context, key);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ Resources res = mContext.getResources();
+ if (res.getBoolean(com.android.internal.R.bool.config_lidControlsScreenLock) ||
+ res.getBoolean(com.android.internal.R.bool.config_lidControlsSleep)) {
+ return AVAILABLE;
+ }
+ return UNSUPPORTED_ON_DEVICE;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ screen.findPreference(getPreferenceKey()).setOnPreferenceChangeListener(this);
+ super.displayPreference(screen);
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ final ListPreference listPreference = (ListPreference) preference;
+ listPreference.setValue(String.valueOf(Settings.Global.getInt(
+ mContext.getContentResolver(),
+ Settings.Global.LID_BEHAVIOR,
+ 0 /* LID_BEHAVIOR_NONE */)));
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.LID_BEHAVIOR,
+ Integer.valueOf((String) newValue));
+ return true;
+ }
+}
diff --git a/src/com/android/settings/display/MinRefreshRatePreferenceController.java b/src/com/android/settings/display/MinRefreshRatePreferenceController.java
new file mode 100644
index 00000000000..b9c63040f26
--- /dev/null
+++ b/src/com/android/settings/display/MinRefreshRatePreferenceController.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2020 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.display;
+
+import static android.provider.Settings.System.MIN_REFRESH_RATE;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.view.Display;
+
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+public class MinRefreshRatePreferenceController extends BasePreferenceController
+ implements Preference.OnPreferenceChangeListener {
+
+ private static final String KEY_MIN_REFRESH_RATE = "min_refresh_rate";
+
+ private ListPreference mListPreference;
+
+ private List mEntries = new ArrayList<>();
+ private List mValues = new ArrayList<>();
+
+ public MinRefreshRatePreferenceController(Context context) {
+ super(context, KEY_MIN_REFRESH_RATE);
+
+ if (mContext.getResources().getBoolean(R.bool.config_show_min_refresh_rate_switch)) {
+ Display.Mode mode = mContext.getDisplay().getMode();
+ Display.Mode[] modes = mContext.getDisplay().getSupportedModes();
+ Arrays.sort(modes, (mode1, mode2) ->
+ Float.compare(mode2.getRefreshRate(), mode1.getRefreshRate()));
+ for (Display.Mode m : modes) {
+ if (m.getPhysicalWidth() == mode.getPhysicalWidth() &&
+ m.getPhysicalHeight() == mode.getPhysicalHeight()) {
+ mEntries.add(String.format("%.02fHz", m.getRefreshRate())
+ .replaceAll("[\\.,]00", ""));
+ mValues.add(String.format(Locale.US, "%.02f", m.getRefreshRate()));
+ }
+ }
+ }
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return mEntries.size() > 1 ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY_MIN_REFRESH_RATE;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ mListPreference = screen.findPreference(getPreferenceKey());
+ mListPreference.setEntries(mEntries.toArray(new String[mEntries.size()]));
+ mListPreference.setEntryValues(mValues.toArray(new String[mValues.size()]));
+
+ super.displayPreference(screen);
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ final float currentValue = Settings.System.getFloat(mContext.getContentResolver(),
+ MIN_REFRESH_RATE, 60.00f);
+ int index = mListPreference.findIndexOfValue(
+ String.format(Locale.US, "%.02f", currentValue));
+ if (index < 0) index = 0;
+ mListPreference.setValueIndex(index);
+ mListPreference.setSummary(mListPreference.getEntries()[index]);
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ Settings.System.putFloat(mContext.getContentResolver(), MIN_REFRESH_RATE,
+ Float.valueOf((String) newValue));
+ updateState(preference);
+ return true;
+ }
+
+}
diff --git a/src/com/android/settings/display/MonetSettings.java b/src/com/android/settings/display/MonetSettings.java
new file mode 100644
index 00000000000..5db8fc9b600
--- /dev/null
+++ b/src/com/android/settings/display/MonetSettings.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2022 Yet Another AOSP Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.display;
+
+import android.content.ContentResolver;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.Preference.OnPreferenceChangeListener;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.R;
+import com.android.settingslib.search.SearchIndexable;
+
+import org.derpfest.support.colorpicker.ColorPickerPreference;
+import org.derpfest.support.preferences.ProperSeekBarPreference;
+import org.derpfest.support.preferences.SwitchPreferenceCompat;
+
+import java.lang.CharSequence;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+@SearchIndexable
+public class MonetSettings extends SettingsPreferenceFragment implements
+ OnPreferenceChangeListener {
+
+ private static final String OVERLAY_CATEGORY_ACCENT_COLOR =
+ "android.theme.customization.accent_color";
+ private static final String OVERLAY_CATEGORY_SYSTEM_PALETTE =
+ "android.theme.customization.system_palette";
+ private static final String OVERLAY_CATEGORY_THEME_STYLE =
+ "android.theme.customization.theme_style";
+ private static final String OVERLAY_CATEGORY_BG_COLOR =
+ "android.theme.customization.bg_color";
+ private static final String OVERLAY_COLOR_SOURCE =
+ "android.theme.customization.color_source";
+ private static final String OVERLAY_COLOR_BOTH =
+ "android.theme.customization.color_both";
+ private static final String OVERLAY_LUMINANCE_FACTOR =
+ "android.theme.customization.luminance_factor";
+ private static final String OVERLAY_CHROMA_FACTOR =
+ "android.theme.customization.chroma_factor";
+ private static final String OVERLAY_WHOLE_PALETTE =
+ "android.theme.customization.whole_palette";
+ private static final String OVERLAY_TINT_BACKGROUND =
+ "android.theme.customization.tint_background";
+ private static final String COLOR_SOURCE_PRESET = "preset";
+ private static final String COLOR_SOURCE_HOME = "home_wallpaper";
+ private static final String COLOR_SOURCE_LOCK = "lock_wallpaper";
+
+ private static final String PREF_THEME_STYLE = "theme_style";
+ private static final String PREF_COLOR_SOURCE = "color_source";
+ private static final String PREF_ACCENT_COLOR = "accent_color";
+ private static final String PREF_ACCENT_BACKGROUND = "accent_background";
+ private static final String PREF_BG_COLOR = "bg_color";
+ private static final String PREF_LUMINANCE_FACTOR = "luminance_factor";
+ private static final String PREF_CHROMA_FACTOR = "chroma_factor";
+ private static final String PREF_WHOLE_PALETTE = "whole_palette";
+ private static final String PREF_TINT_BACKGROUND = "tint_background";
+
+ private ListPreference mThemeStylePref;
+ private ListPreference mColorSourcePref;
+ private ColorPickerPreference mAccentColorPref;
+ private ColorPickerPreference mBgColorPref;
+ private ProperSeekBarPreference mLuminancePref;
+ private ProperSeekBarPreference mChromaPref;
+ private SwitchPreferenceCompat mAccentBackgroundPref;
+ private SwitchPreferenceCompat mWholePalettePref;
+ private SwitchPreferenceCompat mTintBackgroundPref;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ addPreferencesFromResource(R.xml.monet_settings);
+
+ mThemeStylePref = findPreference(PREF_THEME_STYLE);
+ mColorSourcePref = findPreference(PREF_COLOR_SOURCE);
+ mAccentColorPref = findPreference(PREF_ACCENT_COLOR);
+ mAccentBackgroundPref = findPreference(PREF_ACCENT_BACKGROUND);
+ mBgColorPref = findPreference(PREF_BG_COLOR);
+ mLuminancePref = findPreference(PREF_LUMINANCE_FACTOR);
+ mChromaPref = findPreference(PREF_CHROMA_FACTOR);
+ mWholePalettePref = findPreference(PREF_WHOLE_PALETTE);
+ mTintBackgroundPref = findPreference(PREF_TINT_BACKGROUND);
+
+ updatePreferences();
+
+ mThemeStylePref.setOnPreferenceChangeListener(this);
+ mColorSourcePref.setOnPreferenceChangeListener(this);
+ mAccentColorPref.setOnPreferenceChangeListener(this);
+ mAccentBackgroundPref.setOnPreferenceChangeListener(this);
+ mBgColorPref.setOnPreferenceChangeListener(this);
+ mLuminancePref.setOnPreferenceChangeListener(this);
+ mChromaPref.setOnPreferenceChangeListener(this);
+ mWholePalettePref.setOnPreferenceChangeListener(this);
+ mTintBackgroundPref.setOnPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ updatePreferences();
+ }
+
+ private void updatePreferences() {
+ final String overlayPackageJson = Settings.Secure.getStringForUser(
+ getActivity().getContentResolver(),
+ Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
+ UserHandle.USER_CURRENT);
+ if (overlayPackageJson != null && !overlayPackageJson.isEmpty()) {
+ try {
+ final JSONObject object = new JSONObject(overlayPackageJson);
+ final String style = object.optString(OVERLAY_CATEGORY_THEME_STYLE, null);
+ final String source = object.optString(OVERLAY_COLOR_SOURCE, null);
+ final String color = object.optString(OVERLAY_CATEGORY_SYSTEM_PALETTE, null);
+ final int bgColor = object.optInt(OVERLAY_CATEGORY_BG_COLOR);
+ final boolean both = object.optInt(OVERLAY_COLOR_BOTH, 0) == 1;
+ final boolean wholePalette = object.optInt(OVERLAY_WHOLE_PALETTE, 0) == 1;
+ final boolean tintBG = object.optInt(OVERLAY_TINT_BACKGROUND, 0) == 1;
+ final float lumin = (float) object.optDouble(OVERLAY_LUMINANCE_FACTOR, 1d);
+ final float chroma = (float) object.optDouble(OVERLAY_CHROMA_FACTOR, 1d);
+ // style handling
+ boolean styleUpdated = false;
+ if (style != null && !style.isEmpty()) {
+ for (CharSequence value : mThemeStylePref.getEntryValues()) {
+ if (value.toString().equals(style)) {
+ styleUpdated = true;
+ break;
+ }
+ }
+ if (styleUpdated) {
+ updateListByValue(mThemeStylePref, style);
+ }
+ }
+ if (!styleUpdated) {
+ updateListByValue(mThemeStylePref,
+ mThemeStylePref.getEntryValues()[0].toString());
+ }
+ // color handling
+ final String sourceVal = (source == null || source.isEmpty() ||
+ (source.equals(COLOR_SOURCE_HOME) && both)) ? "both" : source;
+ updateListByValue(mColorSourcePref, sourceVal);
+ final boolean enabled = updateAccentEnablement(sourceVal);
+ if (enabled && color != null && !color.isEmpty()) {
+ mAccentColorPref.setNewPreviewColor(
+ ColorPickerPreference.convertToColorInt(color));
+ }
+ final boolean bgEnabled = enabled && bgColor != 0;
+ if (bgEnabled) {
+ mBgColorPref.setNewPreviewColor(bgColor);
+ } else if (!enabled) {
+ mAccentBackgroundPref.setEnabled(false);
+ }
+ mAccentBackgroundPref.setChecked(bgEnabled);
+ mBgColorPref.setEnabled(bgEnabled);
+ // etc
+ int luminV = 0;
+ if (lumin > 1d) luminV = Math.round((lumin - 1f) * 100f);
+ else if (lumin < 1d) luminV = -1 * Math.round((1f - lumin) * 100f);
+ mLuminancePref.setValue(luminV);
+ int chromaV = 0;
+ if (chroma > 1d) chromaV = Math.round((chroma - 1f) * 100f);
+ else if (chroma < 1d) chromaV = -1 * Math.round((1f - chroma) * 100f);
+ mChromaPref.setValue(chromaV);
+ mWholePalettePref.setChecked(wholePalette);
+ mTintBackgroundPref.setChecked(tintBG);
+ } catch (JSONException | IllegalArgumentException ignored) {}
+ }
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ final ContentResolver resolver = getActivity().getContentResolver();
+ if (preference == mThemeStylePref) {
+ String value = (String) newValue;
+ setStyleValue(value);
+ updateListByValue(mThemeStylePref, value, false);
+ return true;
+ } else if (preference == mColorSourcePref) {
+ String value = (String) newValue;
+ setSourceValue(value);
+ updateListByValue(mColorSourcePref, value, false);
+ updateAccentEnablement(value);
+ return true;
+ } else if (preference == mAccentColorPref) {
+ int value = (Integer) newValue;
+ setColorValue(value);
+ return true;
+ } else if (preference == mAccentBackgroundPref) {
+ boolean value = (Boolean) newValue;
+ if (!value) setBgColorValue(0);
+ mBgColorPref.setEnabled(value);
+ return true;
+ } else if (preference == mBgColorPref) {
+ int value = (Integer) newValue;
+ setBgColorValue(value);
+ return true;
+ } else if (preference == mLuminancePref) {
+ int value = (Integer) newValue;
+ setLuminanceValue(value);
+ return true;
+ } else if (preference == mChromaPref) {
+ int value = (Integer) newValue;
+ setChromaValue(value);
+ return true;
+ } else if (preference == mWholePalettePref) {
+ boolean value = (Boolean) newValue;
+ setWholePaletteValue(value);
+ return true;
+ } else if (preference == mTintBackgroundPref) {
+ boolean value = (Boolean) newValue;
+ setTintBackgroundValue(value);
+ return true;
+ }
+ return false;
+ }
+
+ private void updateListByValue(ListPreference pref, String value) {
+ updateListByValue(pref, value, true);
+ }
+
+ private void updateListByValue(ListPreference pref, String value, boolean set) {
+ if (set) pref.setValue(value);
+ final int index = pref.findIndexOfValue(value);
+ pref.setSummary(pref.getEntries()[index]);
+ }
+
+ private boolean updateAccentEnablement(String source) {
+ final boolean shouldEnable = source != null && source.equals(COLOR_SOURCE_PRESET);
+ mAccentColorPref.setEnabled(shouldEnable);
+ mAccentBackgroundPref.setEnabled(shouldEnable);
+ if (!shouldEnable) {
+ mBgColorPref.setEnabled(false);
+ mAccentBackgroundPref.setEnabled(false);
+ mAccentBackgroundPref.setChecked(false);
+ }
+ return shouldEnable;
+ }
+
+ private JSONObject getSettingsJson() throws JSONException {
+ final String overlayPackageJson = Settings.Secure.getStringForUser(
+ getActivity().getContentResolver(),
+ Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
+ UserHandle.USER_CURRENT);
+ JSONObject object;
+ if (overlayPackageJson == null || overlayPackageJson.isEmpty())
+ return new JSONObject();
+ return new JSONObject(overlayPackageJson);
+ }
+
+ private void putSettingsJson(JSONObject object) {
+ Settings.Secure.putStringForUser(
+ getActivity().getContentResolver(),
+ Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
+ object.toString(), UserHandle.USER_CURRENT);
+ }
+
+ private void setStyleValue(String style) {
+ try {
+ JSONObject object = getSettingsJson();
+ object.putOpt(OVERLAY_CATEGORY_THEME_STYLE, style);
+ putSettingsJson(object);
+ } catch (JSONException | IllegalArgumentException ignored) {}
+ }
+
+ private void setSourceValue(String source) {
+ try {
+ JSONObject object = getSettingsJson();
+ if (source.equals("both")) {
+ object.putOpt(OVERLAY_COLOR_BOTH, 1);
+ object.putOpt(OVERLAY_COLOR_SOURCE, COLOR_SOURCE_HOME);
+ } else {
+ object.remove(OVERLAY_COLOR_BOTH);
+ object.putOpt(OVERLAY_COLOR_SOURCE, source);
+ }
+ if (!source.equals(COLOR_SOURCE_PRESET)) {
+ object.remove(OVERLAY_CATEGORY_ACCENT_COLOR);
+ object.remove(OVERLAY_CATEGORY_SYSTEM_PALETTE);
+ }
+ putSettingsJson(object);
+ } catch (JSONException | IllegalArgumentException ignored) {}
+ }
+
+ private void setColorValue(int color) {
+ try {
+ JSONObject object = getSettingsJson();
+ final String rgbColor = ColorPickerPreference.convertToRGB(color).replace("#", "");
+ object.putOpt(OVERLAY_CATEGORY_ACCENT_COLOR, rgbColor);
+ object.putOpt(OVERLAY_CATEGORY_SYSTEM_PALETTE, rgbColor);
+ object.putOpt(OVERLAY_COLOR_SOURCE, COLOR_SOURCE_PRESET);
+ putSettingsJson(object);
+ } catch (JSONException | IllegalArgumentException ignored) {}
+ }
+
+ private void setBgColorValue(int color) {
+ try {
+ JSONObject object = getSettingsJson();
+ if (color != 0) object.putOpt(OVERLAY_CATEGORY_BG_COLOR, color);
+ else object.remove(OVERLAY_CATEGORY_BG_COLOR);
+ putSettingsJson(object);
+ } catch (JSONException | IllegalArgumentException ignored) {}
+ }
+
+ private void setLuminanceValue(int lumin) {
+ try {
+ JSONObject object = getSettingsJson();
+ if (lumin == 0)
+ object.remove(OVERLAY_LUMINANCE_FACTOR);
+ else
+ object.putOpt(OVERLAY_LUMINANCE_FACTOR, 1d + ((double) lumin / 100d));
+ putSettingsJson(object);
+ } catch (JSONException | IllegalArgumentException ignored) {}
+ }
+
+ private void setChromaValue(int chroma) {
+ try {
+ JSONObject object = getSettingsJson();
+ if (chroma == 0)
+ object.remove(OVERLAY_CHROMA_FACTOR);
+ else
+ object.putOpt(OVERLAY_CHROMA_FACTOR, 1d + ((double) chroma / 100d));
+ putSettingsJson(object);
+ } catch (JSONException | IllegalArgumentException ignored) {}
+ }
+
+ private void setWholePaletteValue(boolean whole) {
+ try {
+ JSONObject object = getSettingsJson();
+ if (!whole) object.remove(OVERLAY_WHOLE_PALETTE);
+ else object.putOpt(OVERLAY_WHOLE_PALETTE, 1);
+ putSettingsJson(object);
+ } catch (JSONException | IllegalArgumentException ignored) {}
+ }
+
+ private void setTintBackgroundValue(boolean tint) {
+ try {
+ JSONObject object = getSettingsJson();
+ if (!tint) object.remove(OVERLAY_TINT_BACKGROUND);
+ else object.putOpt(OVERLAY_TINT_BACKGROUND, 1);
+ putSettingsJson(object);
+ } catch (JSONException | IllegalArgumentException ignored) {}
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsProto.MetricsEvent.DERP;
+ }
+
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider(R.xml.monet_settings);
+}
diff --git a/src/com/android/settings/display/NightDisplayAutoModePreferenceController.java b/src/com/android/settings/display/NightDisplayAutoModePreferenceController.java
index ef11e00cebb..68c4b98b908 100644
--- a/src/com/android/settings/display/NightDisplayAutoModePreferenceController.java
+++ b/src/com/android/settings/display/NightDisplayAutoModePreferenceController.java
@@ -20,7 +20,7 @@
import android.hardware.display.ColorDisplayManager;
import android.location.LocationManager;
-import androidx.preference.DropDownPreference;
+import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
@@ -31,7 +31,7 @@ public class NightDisplayAutoModePreferenceController extends BasePreferenceCont
implements Preference.OnPreferenceChangeListener {
private final LocationManager mLocationManager;
- private DropDownPreference mPreference;
+ private ListPreference mPreference;
private ColorDisplayManager mColorDisplayManager;
public NightDisplayAutoModePreferenceController(Context context, String key) {
diff --git a/src/com/android/settings/display/PeakRefreshRateListPreferenceController.java b/src/com/android/settings/display/PeakRefreshRateListPreferenceController.java
new file mode 100644
index 00000000000..e51f5d45110
--- /dev/null
+++ b/src/com/android/settings/display/PeakRefreshRateListPreferenceController.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.display;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.Display;
+
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.Executor;
+import java.util.List;
+import java.util.Locale;
+
+public class PeakRefreshRateListPreferenceController extends BasePreferenceController
+ implements LifecycleObserver, OnStart, OnStop, Preference.OnPreferenceChangeListener {
+
+ private static float DEFAULT_REFRESH_RATE = 60f;
+
+ private static final String TAG = "PeakRefreshRatePrefCtr";
+ private static final float INVALIDATE_REFRESH_RATE = -1f;
+
+ private final Handler mHandler;
+ private final IDeviceConfigChange mOnDeviceConfigChange;
+ private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings;
+ private ListPreference mListPreference;
+
+ private List mEntries = new ArrayList<>();
+ private List mValues = new ArrayList<>();
+
+ private interface IDeviceConfigChange {
+ void onDefaultRefreshRateChanged();
+ }
+
+ public PeakRefreshRateListPreferenceController(Context context, String key) {
+ super(context, key);
+ mHandler = new Handler(context.getMainLooper());
+ mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings();
+ mOnDeviceConfigChange =
+ new IDeviceConfigChange() {
+ public void onDefaultRefreshRateChanged() {
+ updateState(mListPreference);
+ }
+ };
+
+ final DisplayManager dm = mContext.getSystemService(DisplayManager.class);
+ final Display display = dm.getDisplay(Display.DEFAULT_DISPLAY);
+
+ if (display == null) {
+ Log.w(TAG, "No valid default display device");
+ } else {
+ Display.Mode mode = display.getMode();
+ Display.Mode[] modes = display.getSupportedModes();
+ Arrays.sort(modes, (mode1, mode2) ->
+ Float.compare(mode2.getRefreshRate(), mode1.getRefreshRate()));
+ for (Display.Mode m : modes) {
+ if (m.getPhysicalWidth() == mode.getPhysicalWidth() &&
+ m.getPhysicalHeight() == mode.getPhysicalHeight()) {
+ mEntries.add(String.format("%.02fHz", m.getRefreshRate())
+ .replaceAll("[\\.,]00", ""));
+ mValues.add(String.format(Locale.US, "%.02f", m.getRefreshRate()));
+ }
+ }
+ }
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+
+ mListPreference = screen.findPreference(getPreferenceKey());
+ mListPreference.setEntries(mEntries.toArray(new String[mEntries.size()]));
+ mListPreference.setEntryValues(mValues.toArray(new String[mValues.size()]));
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ if (mContext.getResources().getBoolean(R.bool.config_show_peak_refresh_rate_switch)) {
+ return AVAILABLE;
+ } else {
+ return UNSUPPORTED_ON_DEVICE;
+ }
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ final float currentValue = Settings.System.getFloat(mContext.getContentResolver(),
+ Settings.System.PEAK_REFRESH_RATE, getDefaultPeakRefreshRate());
+ int index = mListPreference.findIndexOfValue(
+ String.format(Locale.US, "%.02f", currentValue));
+ if (index < 0) index = 0;
+ mListPreference.setValueIndex(index);
+ mListPreference.setSummary(mListPreference.getEntries()[index]);
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ Settings.System.putFloat(mContext.getContentResolver(), Settings.System.PEAK_REFRESH_RATE,
+ Float.valueOf((String) newValue));
+ updateState(preference);
+ return true;
+ }
+
+ @Override
+ public void onStart() {
+ mDeviceConfigDisplaySettings.startListening();
+ }
+
+ @Override
+ public void onStop() {
+ mDeviceConfigDisplaySettings.stopListening();
+ }
+
+ private float findPeakRefreshRate(Display.Mode[] modes) {
+ float peakRefreshRate = DEFAULT_REFRESH_RATE;
+ for (Display.Mode mode : modes) {
+ if (Math.round(mode.getRefreshRate()) > DEFAULT_REFRESH_RATE) {
+ peakRefreshRate = mode.getRefreshRate();
+ }
+ }
+ return peakRefreshRate;
+ }
+
+ private class DeviceConfigDisplaySettings
+ implements DeviceConfig.OnPropertiesChangedListener, Executor {
+ public void startListening() {
+ DeviceConfig.addOnPropertiesChangedListener(
+ DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ this /* Executor */,
+ this /* Listener */);
+ }
+
+ public void stopListening() {
+ DeviceConfig.removeOnPropertiesChangedListener(this);
+ }
+
+ public float getDefaultPeakRefreshRate() {
+ float defaultPeakRefreshRate =
+ DeviceConfig.getFloat(
+ DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_DEFAULT,
+ INVALIDATE_REFRESH_RATE);
+ Log.d(TAG, "DeviceConfig getDefaultPeakRefreshRate : " + defaultPeakRefreshRate);
+
+ return defaultPeakRefreshRate;
+ }
+
+ @Override
+ public void onPropertiesChanged(DeviceConfig.Properties properties) {
+ // Got notified if any property has been changed in NAMESPACE_DISPLAY_MANAGER. The
+ // KEY_PEAK_REFRESH_RATE_DEFAULT value could be added, changed, removed or unchanged.
+ // Just force a UI update for any case.
+ if (mOnDeviceConfigChange != null) {
+ mOnDeviceConfigChange.onDefaultRefreshRateChanged();
+ updateState(mListPreference);
+ }
+ }
+
+ @Override
+ public void execute(Runnable runnable) {
+ if (mHandler != null) {
+ mHandler.post(runnable);
+ }
+ }
+ }
+
+ private float getDefaultPeakRefreshRate() {
+ float defaultPeakRefreshRate = mDeviceConfigDisplaySettings.getDefaultPeakRefreshRate();
+ if (defaultPeakRefreshRate == INVALIDATE_REFRESH_RATE) {
+ defaultPeakRefreshRate = (float) mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_defaultPeakRefreshRate);
+ }
+
+ return defaultPeakRefreshRate;
+ }
+}
diff --git a/src/com/android/settings/display/PocketJudgePreferenceController.java b/src/com/android/settings/display/PocketJudgePreferenceController.java
new file mode 100644
index 00000000000..01d5e3f0810
--- /dev/null
+++ b/src/com/android/settings/display/PocketJudgePreferenceController.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2018 Paranoid Android
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.settings.display;
+
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Settings;
+import androidx.preference.SwitchPreferenceCompat;
+import androidx.preference.Preference;
+
+import com.android.settings.DisplaySettings;
+import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settings.R;
+import com.android.settingslib.core.AbstractPreferenceController;
+
+import static android.provider.Settings.System.POCKET_JUDGE;
+
+public class PocketJudgePreferenceController extends AbstractPreferenceController implements
+ PreferenceControllerMixin, Preference.OnPreferenceChangeListener {
+
+ private static final String KEY_POCKET_JUDGE = "pocket_judge";
+
+ public PocketJudgePreferenceController(Context context) {
+ super(context);
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY_POCKET_JUDGE;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ int pocketJudgeValue = Settings.System.getInt(mContext.getContentResolver(),
+ POCKET_JUDGE, 0);
+ ((SwitchPreferenceCompat) preference).setChecked(pocketJudgeValue != 0);
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_pocketModeSupported);
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ boolean pocketJudgeValue = (Boolean) newValue;
+ Settings.System.putInt(mContext.getContentResolver(), POCKET_JUDGE, pocketJudgeValue ? 1 : 0);
+ return true;
+ }
+}
diff --git a/src/com/android/settings/display/QRCodeScannerPreferenceController.java b/src/com/android/settings/display/QRCodeScannerPreferenceController.java
index cb022a74fa8..e15860ec026 100644
--- a/src/com/android/settings/display/QRCodeScannerPreferenceController.java
+++ b/src/com/android/settings/display/QRCodeScannerPreferenceController.java
@@ -64,7 +64,7 @@ public void displayPreference(PreferenceScreen screen) {
@OnLifecycleEvent(ON_START)
public void onStart() {
mContentResolver.registerContentObserver(
- Settings.Global.getUriFor(SHOW_QR_CODE_SCANNER_SETTING), false,
+ Settings.Secure.getUriFor(SHOW_QR_CODE_SCANNER_SETTING), false,
mSettingsObserver);
}
diff --git a/src/com/android/settings/display/RefreshRatePreferenceController.java b/src/com/android/settings/display/RefreshRatePreferenceController.java
new file mode 100644
index 00000000000..b63f12fbc5f
--- /dev/null
+++ b/src/com/android/settings/display/RefreshRatePreferenceController.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2023 Paranoid Android
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.display;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.PowerManager;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
+
+public class RefreshRatePreferenceController extends BasePreferenceController
+ implements LifecycleObserver, OnStart, OnStop {
+
+ private static final String TAG = "RefreshRatePreferenceController";
+
+ private RefreshRateUtils mUtils;
+ private Preference mPreference;
+ private final PowerManager mPowerManager;
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (mPreference != null)
+ updateState(mPreference);
+ }
+ };
+
+ public RefreshRatePreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ mUtils = new RefreshRateUtils(context);
+ mPowerManager = context.getSystemService(PowerManager.class);
+ }
+
+ @Override
+ public void onStart() {
+ mContext.registerReceiver(mReceiver,
+ new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
+ }
+
+ @Override
+ public void onStop() {
+ mContext.unregisterReceiver(mReceiver);
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mPreference = screen.findPreference(getPreferenceKey());
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ if (mPowerManager.isPowerSaveMode()) {
+ return mContext.getString(R.string.dark_ui_mode_disabled_summary_dark_theme_on);
+ }
+ return mContext.getString(mUtils.isVrrEnabled() ? R.string.refresh_rate_summary_vrr_on
+ : R.string.refresh_rate_summary_vrr_off, mUtils.getCurrentRefreshRate());
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return mUtils.isHighRefreshRateAvailable() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ super.updateState(preference);
+ preference.setEnabled(!mPowerManager.isPowerSaveMode());
+ }
+}
diff --git a/src/com/android/settings/display/RefreshRateSettings.java b/src/com/android/settings/display/RefreshRateSettings.java
new file mode 100644
index 00000000000..3fced007a1a
--- /dev/null
+++ b/src/com/android/settings/display/RefreshRateSettings.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2023 Paranoid Android
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.display;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+
+import androidx.preference.PreferenceScreen;
+import androidx.preference.SwitchPreferenceCompat;
+
+import com.android.settings.R;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.widget.RadioButtonPickerFragment;
+import com.android.settingslib.search.SearchIndexable;
+import com.android.settingslib.widget.CandidateInfo;
+import com.android.settingslib.widget.FooterPreference;
+import com.android.settingslib.widget.MainSwitchPreference;
+import com.android.settingslib.widget.SelectorWithWidgetPreference;
+import com.android.settingslib.widget.TopIntroPreference;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/** Preference fragment used for switching refresh rate */
+@SearchIndexable
+public class RefreshRateSettings extends RadioButtonPickerFragment {
+
+ private static final String TAG = "RefreshRateSettings";
+ private static final String KEY_VRR_PREF = "refresh_rate_vrr";
+
+ private Context mContext;
+ private RefreshRateUtils mUtils;
+ private SwitchPreferenceCompat mVrrSwitchPref;
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ mContext = context;
+ mUtils = new RefreshRateUtils(context);
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.refresh_rate_settings;
+ }
+
+ @Override
+ protected void addStaticPreferences(PreferenceScreen screen) {
+ mVrrSwitchPref = new SwitchPreferenceCompat(screen.getContext());
+ mVrrSwitchPref.setKey(KEY_VRR_PREF);
+ mVrrSwitchPref.setTitle(R.string.refresh_rate_vrr_title);
+ mVrrSwitchPref.setSummary(R.string.refresh_rate_vrr_summary);
+ mVrrSwitchPref.setOnPreferenceChangeListener((pref, newValue) -> {
+ mUtils.setVrrEnabled((Boolean) newValue);
+ return true;
+ });
+ screen.addPreference(mVrrSwitchPref);
+ updateVrrPref();
+
+ final FooterPreference footerPreference = new FooterPreference(screen.getContext());
+ footerPreference.setTitle(R.string.refresh_rate_footer);
+ footerPreference.setSelectable(false);
+ footerPreference.setLayoutResource(com.android.settingslib.widget.preference.footer.R.layout.preference_footer);
+ screen.addPreference(footerPreference);
+ }
+
+ @Override
+ protected List extends CandidateInfo> getCandidates() {
+ return mUtils.getRefreshRates().stream()
+ .filter(r -> r >= RefreshRateUtils.DEFAULT_REFRESH_RATE)
+ .map(RefreshRateCandidateInfo::new)
+ .collect(Collectors.toList());
+ }
+
+ private void updateVrrPref() {
+ if (mVrrSwitchPref == null) return;
+ mVrrSwitchPref.setEnabled(mUtils.isVrrPossible());
+ mVrrSwitchPref.setChecked(mUtils.isVrrEnabled());
+ }
+
+ @Override
+ protected String getDefaultKey() {
+ return String.valueOf(mUtils.getCurrentRefreshRate());
+ }
+
+ @Override
+ protected boolean setDefaultKey(final String key) {
+ final int refreshRate = Integer.parseInt(key);
+ mUtils.setCurrentRefreshRate(refreshRate);
+ updateVrrPref();
+ return true;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return -1;
+ }
+
+ private class RefreshRateCandidateInfo extends CandidateInfo {
+ private final CharSequence mLabel;
+ private final String mKey;
+
+ RefreshRateCandidateInfo(Integer refreshRate) {
+ super(true);
+ mLabel = String.format("%d Hz", refreshRate.intValue());
+ mKey = refreshRate.toString();
+ }
+
+ @Override
+ public CharSequence loadLabel() {
+ return mLabel;
+ }
+
+ @Override
+ public Drawable loadIcon() {
+ return null;
+ }
+
+ @Override
+ public String getKey() {
+ return mKey;
+ }
+ }
+
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider(R.xml.refresh_rate_settings) {
+ @Override
+ protected boolean isPageSearchEnabled(Context context) {
+ return new RefreshRateUtils(context).isHighRefreshRateAvailable();
+ }
+ };
+}
diff --git a/src/com/android/settings/display/RefreshRateUtils.java b/src/com/android/settings/display/RefreshRateUtils.java
new file mode 100644
index 00000000000..eeb17d013cd
--- /dev/null
+++ b/src/com/android/settings/display/RefreshRateUtils.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2024 Paranoid Android
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.display;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.util.Log;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import com.android.settings.R;
+
+public class RefreshRateUtils {
+
+ private static final String TAG = "RefreshRateUtils";
+
+ static final int DEFAULT_REFRESH_RATE = 60;
+ static final int DEFAULT_MIN_REFRESH_RATE = 60;
+
+ private Context mContext;
+ private List mRefreshRates;
+ private int mMinRefreshRate, mMaxRefreshRate;
+
+ RefreshRateUtils(Context context) {
+ mContext = context;
+ mRefreshRates = getRefreshRates();
+ mMinRefreshRate = getMinRefreshRateFromConfig();
+ mMaxRefreshRate = Collections.max(mRefreshRates);
+ }
+
+ List getRefreshRates() {
+ return Arrays.stream(mContext.getDisplay().getSupportedModes())
+ .map(m -> Math.round(m.getRefreshRate()))
+ .sorted().distinct().collect(Collectors.toList());
+ }
+
+ boolean isHighRefreshRateAvailable() {
+ return mRefreshRates.stream()
+ .filter(r -> r > DEFAULT_REFRESH_RATE)
+ .count() > 0;
+ }
+
+ private int roundToNearestRefreshRate(int refreshRate, boolean floor) {
+ if (mRefreshRates.contains(refreshRate)) return refreshRate;
+ int findRefreshRate = mMinRefreshRate;
+ for (Integer knownRefreshRate : mRefreshRates) {
+ if (!floor) findRefreshRate = knownRefreshRate;
+ if (knownRefreshRate > refreshRate) break;
+ if (floor) findRefreshRate = knownRefreshRate;
+ }
+ return findRefreshRate;
+ }
+
+ private float getDefaultPeakRefreshRate() {
+ return (float) mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_defaultPeakRefreshRate);
+ }
+
+ private int getPeakRefreshRate() {
+ final int peakRefreshRate = Math.round(Settings.System.getFloat(
+ mContext.getContentResolver(),
+ Settings.System.PEAK_REFRESH_RATE, getDefaultPeakRefreshRate()));
+ return peakRefreshRate < mMinRefreshRate ? mMaxRefreshRate
+ : roundToNearestRefreshRate(peakRefreshRate, true);
+ }
+
+ private void setPeakRefreshRate(int refreshRate) {
+ Settings.System.putFloat(mContext.getContentResolver(),
+ Settings.System.PEAK_REFRESH_RATE, (float) refreshRate);
+ }
+
+ private int getMinRefreshRateFromConfig() {
+ int minRefreshRate;
+ try {
+ minRefreshRate = mContext.getResources().getInteger(R.integer.default_min_refresh_rate);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to retrieve default_min_refresh_rate from config, using default", e);
+ minRefreshRate = getMinRefreshRate();
+ }
+ return minRefreshRate;
+ }
+
+ private int getMinRefreshRate() {
+ final int minRefreshRate = Math.round(Settings.System.getFloat(
+ mContext.getContentResolver(), Settings.System.MIN_REFRESH_RATE,
+ (float) DEFAULT_MIN_REFRESH_RATE));
+ return minRefreshRate == mMinRefreshRate ? mMinRefreshRate
+ : roundToNearestRefreshRate(minRefreshRate, false);
+ }
+
+ private void setMinRefreshRate(int refreshRate) {
+ Settings.System.putFloat(mContext.getContentResolver(),
+ Settings.System.MIN_REFRESH_RATE, (float) refreshRate);
+ }
+
+ int getCurrentRefreshRate() {
+ return Math.max(getMinRefreshRate(), getPeakRefreshRate());
+ }
+
+ void setCurrentRefreshRate(int refreshRate) {
+ setPeakRefreshRate(refreshRate);
+ setMinRefreshRate(isVrrEnabled() ? mMinRefreshRate : refreshRate);
+ }
+
+ boolean isVrrPossible() {
+ return getCurrentRefreshRate() > DEFAULT_REFRESH_RATE;
+ }
+
+ boolean isVrrEnabled() {
+ return getMinRefreshRate() <= mMinRefreshRate;
+ }
+
+ void setVrrEnabled(boolean enable) {
+ setMinRefreshRate(enable ? mMinRefreshRate : getCurrentRefreshRate());
+ }
+}
diff --git a/src/com/android/settings/display/ResetAutoBrightnessAdjustmentPreferenceController.java b/src/com/android/settings/display/ResetAutoBrightnessAdjustmentPreferenceController.java
new file mode 100644
index 00000000000..57c3eb164c4
--- /dev/null
+++ b/src/com/android/settings/display/ResetAutoBrightnessAdjustmentPreferenceController.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 Project Kaleidoscope
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.settings.display;
+
+import static android.provider.Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.widget.Toast;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.R;
+
+public class ResetAutoBrightnessAdjustmentPreferenceController extends
+ BasePreferenceController implements Preference.OnPreferenceClickListener {
+
+ public ResetAutoBrightnessAdjustmentPreferenceController(Context context, String key) {
+ super(context, key);
+ }
+
+ @Override
+ @AvailabilityStatus
+ public int getAvailabilityStatus() {
+ return mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_automatic_brightness_available)
+ ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ screen.findPreference(getPreferenceKey()).setOnPreferenceClickListener(this);
+ }
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ Settings.System.putFloat(mContext.getContentResolver(), SCREEN_AUTO_BRIGHTNESS_ADJ, 0f);
+ Toast.makeText(mContext, mContext.getString(
+ R.string.reset_auto_brightness_adjustment_done),
+ Toast.LENGTH_SHORT).show();
+ return true;
+ }
+}
diff --git a/src/com/android/settings/display/SmartAutoRotatePreferenceFragment.java b/src/com/android/settings/display/SmartAutoRotatePreferenceFragment.java
index 8fd996ea575..551d95b3b53 100644
--- a/src/com/android/settings/display/SmartAutoRotatePreferenceFragment.java
+++ b/src/com/android/settings/display/SmartAutoRotatePreferenceFragment.java
@@ -20,6 +20,7 @@
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
@@ -27,6 +28,8 @@
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
+import androidx.preference.SwitchPreference;
+import android.provider.Settings;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
@@ -56,6 +59,23 @@ public class SmartAutoRotatePreferenceFragment extends DashboardFragment {
static final String AUTO_ROTATE_SWITCH_PREFERENCE_KEY = "auto_rotate_switch";
private static final String KEY_FOOTER_PREFERENCE = "auto_rotate_footer_preference";
+ private static final String LOCKSCREEN_ROTATION = "lockscreen_rotation";
+ private static final String ROTATION_0_PREF = "display_rotation_0";
+ private static final String ROTATION_90_PREF = "display_rotation_90";
+ private static final String ROTATION_180_PREF = "display_rotation_180";
+ private static final String ROTATION_270_PREF = "display_rotation_270";
+
+ private SwitchPreference mLockScreenRotationPref;
+ private SwitchPreference mRotation0Pref;
+ private SwitchPreference mRotation90Pref;
+ private SwitchPreference mRotation180Pref;
+ private SwitchPreference mRotation270Pref;
+
+ public static final int ROTATION_0_MODE = 1;
+ public static final int ROTATION_90_MODE = 2;
+ public static final int ROTATION_180_MODE = 4;
+ public static final int ROTATION_270_MODE = 8;
+
@Override
protected int getPreferenceScreenResId() {
return R.xml.auto_rotate_settings;
@@ -86,6 +106,27 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container,
footerPreference.setVisible(isRotationResolverServiceAvailable(activity));
setupFooter();
}
+
+ mLockScreenRotationPref = findPreference(LOCKSCREEN_ROTATION);
+ mRotation0Pref = findPreference(ROTATION_0_PREF);
+ mRotation90Pref = findPreference(ROTATION_90_PREF);
+ mRotation180Pref = findPreference(ROTATION_180_PREF);
+ mRotation270Pref = findPreference(ROTATION_270_PREF);
+
+ int mode = Settings.System.getIntForUser(getContentResolver(),
+ Settings.System.ACCELEROMETER_ROTATION_ANGLES,
+ ROTATION_0_MODE|ROTATION_90_MODE|ROTATION_270_MODE, UserHandle.USER_CURRENT);
+
+ boolean configEnableLockRotation = getResources().
+ getBoolean(com.android.internal.R.bool.config_enableLockScreenRotation);
+ boolean lockScreenRotationEnabled = Settings.System.getInt(getContentResolver(),
+ Settings.System.LOCKSCREEN_ROTATION, configEnableLockRotation ? 1 : 0) != 0;
+
+ mLockScreenRotationPref.setChecked(lockScreenRotationEnabled);
+ mRotation0Pref.setChecked((mode & ROTATION_0_MODE) != 0);
+ mRotation90Pref.setChecked((mode & ROTATION_90_MODE) != 0);
+ mRotation180Pref.setChecked((mode & ROTATION_180_MODE) != 0);
+ mRotation270Pref.setChecked((mode & ROTATION_270_MODE) != 0);
return view;
}
@@ -138,6 +179,37 @@ void addHelpLink() {
}
}
+ @Override
+ public boolean onPreferenceTreeClick(Preference preference) {
+ if (preference == mRotation0Pref ||
+ preference == mRotation90Pref ||
+ preference == mRotation180Pref ||
+ preference == mRotation270Pref) {
+ int mode = 0;
+ if (mRotation0Pref.isChecked())
+ mode |= ROTATION_0_MODE;
+ if (mRotation90Pref.isChecked())
+ mode |= ROTATION_90_MODE;
+ if (mRotation180Pref.isChecked())
+ mode |= ROTATION_180_MODE;
+ if (mRotation270Pref.isChecked())
+ mode |= ROTATION_270_MODE;
+ if (mode == 0) {
+ mode |= ROTATION_0_MODE;
+ mRotation0Pref.setChecked(true);
+ }
+ Settings.System.putIntForUser(getActivity().getApplicationContext().getContentResolver(),
+ Settings.System.ACCELEROMETER_ROTATION_ANGLES, mode, UserHandle.USER_CURRENT);
+ return true;
+ } else if (preference == mLockScreenRotationPref) {
+ Settings.System.putInt(getContentResolver(),
+ Settings.System.LOCKSCREEN_ROTATION,
+ mLockScreenRotationPref.isChecked() ? 1 : 0);
+ return true;
+ }
+ return super.onPreferenceTreeClick(preference);
+ }
+
public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.auto_rotate_settings) {
diff --git a/src/com/android/settings/display/darkmode/DarkModeScheduleSelectorController.java b/src/com/android/settings/display/darkmode/DarkModeScheduleSelectorController.java
index e122ad06864..980b8063e95 100644
--- a/src/com/android/settings/display/darkmode/DarkModeScheduleSelectorController.java
+++ b/src/com/android/settings/display/darkmode/DarkModeScheduleSelectorController.java
@@ -20,7 +20,7 @@
import android.location.LocationManager;
import android.os.PowerManager;
-import androidx.preference.DropDownPreference;
+import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
@@ -29,7 +29,7 @@
import com.android.settings.display.TwilightLocationDialog;
/**
- * Controller for the dark ui option dropdown
+ * Controller for the dark ui option list
*/
public class DarkModeScheduleSelectorController extends BasePreferenceController
implements Preference.OnPreferenceChangeListener {
@@ -40,7 +40,7 @@ public class DarkModeScheduleSelectorController extends BasePreferenceController
private final LocationManager mLocationManager;
private final BedtimeSettings mBedtimeSettings;
- private DropDownPreference mPreference;
+ private ListPreference mPreference;
private int mCurrentMode;
public DarkModeScheduleSelectorController(Context context, String key) {
diff --git a/src/com/android/settings/flashlight/FlashlightHandleActivity.java b/src/com/android/settings/flashlight/FlashlightHandleActivity.java
index 6ae07cdcea4..bb3ff2d0477 100644
--- a/src/com/android/settings/flashlight/FlashlightHandleActivity.java
+++ b/src/com/android/settings/flashlight/FlashlightHandleActivity.java
@@ -87,10 +87,7 @@ public List getRawDataToIndex(Context context,
@Override
public List getNonIndexableKeys(Context context) {
List keys = super.getNonIndexableKeys(context);
- if (!FlashlightSlice.isFlashlightAvailable(context)) {
- Log.i(TAG, "Flashlight is unavailable");
- keys.add(DATA_KEY);
- }
+ keys.add(DATA_KEY);
return keys;
}
};
diff --git a/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java b/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java
index 7d15858f8b5..8480b16594f 100644
--- a/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java
+++ b/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java
@@ -77,6 +77,7 @@ public int getAvailabilityStatus() {
}
private CharSequence generateLabel(BatteryInfo info) {
+ String temperature = info.batteryTemp + "\u2103";
if (Utils.containsIncompatibleChargers(mContext, TAG)) {
return mContext.getString(
com.android.settingslib.R.string.battery_info_status_not_charging);
@@ -91,7 +92,7 @@ private CharSequence generateLabel(BatteryInfo info) {
}
if (info.remainingLabel == null
|| info.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING) {
- return info.statusLabel;
+ return info.statusLabel + " \u2022 " + temperature;
}
if (info.pluggedStatus == BatteryManager.BATTERY_PLUGGED_WIRELESS) {
final CharSequence wirelessChargingLabel =
@@ -110,25 +111,25 @@ private CharSequence generateLabel(BatteryInfo info) {
? mContext.getString(
R.string.battery_state_and_duration,
info.statusLabel,
- info.remainingLabel)
+ info.remainingLabel, temperature)
: info.remainingLabel;
}
return mContext.getString(
- R.string.battery_state_and_duration, info.statusLabel, info.remainingLabel);
+ R.string.battery_state_and_duration, info.statusLabel, info.remainingLabel, temperature);
} else if (mPowerManager.isPowerSaveMode()) {
// Power save mode is on
final String powerSaverOn =
mContext.getString(R.string.battery_tip_early_heads_up_done_title);
return mContext.getString(
- R.string.battery_state_and_duration, powerSaverOn, info.remainingLabel);
+ R.string.battery_state_and_duration, powerSaverOn, info.remainingLabel, temperature);
} else if (mBatteryTip != null && mBatteryTip.getType() == BatteryTip.TipType.LOW_BATTERY) {
// Low battery state
final String lowBattery = mContext.getString(R.string.low_battery_summary);
return mContext.getString(
- R.string.battery_state_and_duration, lowBattery, info.remainingLabel);
+ R.string.battery_state_and_duration, lowBattery, info.remainingLabel, temperature);
} else {
// Discharging state
- return info.remainingLabel;
+ return info.remainingLabel + " \u2022 " + temperature;
}
}
@@ -155,9 +156,17 @@ public void quickUpdateHeaderPreference() {
final int batteryLevel = Utils.getBatteryLevel(batteryBroadcast);
final boolean discharging =
batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) == 0;
+ final int chargeCounterUah =
+ batteryBroadcast.getIntExtra(BatteryManager.EXTRA_CHARGE_COUNTER, -1);
mBatteryUsageProgressBarPref.setUsageSummary(formatBatteryPercentageText(batteryLevel));
mBatteryUsageProgressBarPref.setPercent(batteryLevel, BATTERY_MAX_LEVEL);
+
+ if (chargeCounterUah > 0) {
+ int chargeCounter = chargeCounterUah / 1_000;
+ mBatteryUsageProgressBarPref.setTotalSummary(
+ formatBatteryChargeCounterText(chargeCounter));
+ }
}
/** Update summary when battery tips changed. */
@@ -172,4 +181,8 @@ public void updateHeaderByBatteryTips(BatteryTip batteryTip, BatteryInfo battery
private CharSequence formatBatteryPercentageText(int batteryLevel) {
return com.android.settings.Utils.formatPercentage(batteryLevel);
}
+
+ private CharSequence formatBatteryChargeCounterText(int chargeCounter) {
+ return mContext.getString(R.string.battery_charge_counter_summary, chargeCounter);
+ }
}
diff --git a/src/com/android/settings/fuelgauge/BatteryInfo.java b/src/com/android/settings/fuelgauge/BatteryInfo.java
index b54801a677a..ea5935c11df 100644
--- a/src/com/android/settings/fuelgauge/BatteryInfo.java
+++ b/src/com/android/settings/fuelgauge/BatteryInfo.java
@@ -55,6 +55,7 @@ public class BatteryInfo {
public boolean discharging = true;
public boolean isBatteryDefender;
public boolean isFastCharging;
+ public String batteryTemp;
public long remainingTimeUs = 0;
public long averageTimeToDischarge = EstimateKt.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN;
public String batteryPercentString;
@@ -311,6 +312,7 @@ public static BatteryInfo getBatteryInfo(
BatteryManager.EXTRA_CHARGING_STATUS,
BatteryManager.CHARGING_POLICY_DEFAULT)
== BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE;
+ info.batteryTemp = tenthsToFixedString(batteryBroadcast.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0));
info.statusLabel = Utils.getBatteryStatus(context, batteryBroadcast, isCompactStatus);
info.batteryStatus =
@@ -340,6 +342,16 @@ public static BatteryInfo getBatteryInfo(
return info;
}
+ /**
+ * Format a number of tenths-units as a decimal string without using a
+ * conversion to float. E.g. 347 -> "34.7", -99 -> "-9.9"
+ */
+ private static final String tenthsToFixedString(int x) {
+ int tens = x / 10;
+ // use Math.abs to avoid "-9.-9" about -99
+ return Integer.toString(tens) + "." + Math.abs(x - 10 * tens);
+ }
+
/** Returns a {@code BatteryInfo} with battery and charging relative information. */
@WorkerThread
public static BatteryInfo getBatteryInfo(
diff --git a/src/com/android/settings/fuelgauge/BatterySettingsFeatureProviderImpl.java b/src/com/android/settings/fuelgauge/BatterySettingsFeatureProviderImpl.java
index 4b5d9526f16..476cc8b081e 100644
--- a/src/com/android/settings/fuelgauge/BatterySettingsFeatureProviderImpl.java
+++ b/src/com/android/settings/fuelgauge/BatterySettingsFeatureProviderImpl.java
@@ -21,6 +21,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.settings.R;
import com.android.settings.fuelgauge.batterytip.BatteryTipPolicy;
import com.android.settings.fuelgauge.batterytip.detectors.LowBatteryDetector;
import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
@@ -42,7 +43,7 @@ public boolean isFirstUseDateAvailable(Context context, long firstUseDateMs) {
@Override
public boolean isBatteryInfoEnabled(Context context) {
- return false;
+ return context.getResources().getBoolean(R.bool.config_show_battery_info);
}
@Override
diff --git a/src/com/android/settings/fuelgauge/BatteryUtils.java b/src/com/android/settings/fuelgauge/BatteryUtils.java
index 9e08664c901..93423d6d782 100644
--- a/src/com/android/settings/fuelgauge/BatteryUtils.java
+++ b/src/com/android/settings/fuelgauge/BatteryUtils.java
@@ -566,26 +566,9 @@ public long getAppLongVersionCode(String packageName) {
return -1L;
}
- /** Whether the package is installed from Google Play Store or not */
- public static boolean isAppInstalledFromGooglePlayStore(Context context, String packageName) {
- if (TextUtils.isEmpty(packageName)) {
- return false;
- }
- InstallSourceInfo installSourceInfo;
- try {
- installSourceInfo = context.getPackageManager().getInstallSourceInfo(packageName);
- } catch (PackageManager.NameNotFoundException e) {
- return false;
- }
- return installSourceInfo != null
- && GOOGLE_PLAY_STORE_PACKAGE.equals(installSourceInfo.getInitiatingPackageName());
- }
-
/** Gets the logging package name. */
public static String getLoggingPackageName(Context context, String originalPackingName) {
- return BatteryUtils.isAppInstalledFromGooglePlayStore(context, originalPackingName)
- ? originalPackingName
- : PACKAGE_NAME_NONE;
+ return PACKAGE_NAME_NONE;
}
/** Gets the latest sticky battery intent from the Android system. */
diff --git a/src/com/android/settings/fuelgauge/FastChargingPreferenceController.java b/src/com/android/settings/fuelgauge/FastChargingPreferenceController.java
new file mode 100644
index 00000000000..a4eea6e11b4
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/FastChargingPreferenceController.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2021 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge;
+
+import android.content.Context;
+import android.os.RemoteException;
+import android.util.Log;
+
+import androidx.preference.Preference;
+import androidx.preference.SwitchPreferenceCompat;
+
+import com.android.settings.core.BasePreferenceController;
+
+import vendor.lineage.fastcharge.V1_0.IFastCharge;
+
+import java.util.NoSuchElementException;
+
+/**
+ * Controller to change and update the fast charging toggle
+ */
+public class FastChargingPreferenceController extends BasePreferenceController
+ implements Preference.OnPreferenceChangeListener {
+
+ private static final String KEY_FAST_CHARGING = "fast_charging";
+ private static final String TAG = "FastChargingPreferenceController";
+
+ private IFastCharge mFastCharge = null;
+
+ public FastChargingPreferenceController(Context context) {
+ super(context, KEY_FAST_CHARGING);
+ try {
+ mFastCharge = IFastCharge.getService();
+ } catch (NoSuchElementException | RemoteException e) {
+ Log.e(TAG, "Failed to get IFastCharge interface", e);
+ }
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return mFastCharge != null ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ super.updateState(preference);
+ boolean fastChargingEnabled = false;
+
+ try {
+ fastChargingEnabled = mFastCharge.isEnabled();
+ } catch (RemoteException e) {
+ Log.e(TAG, "isEnabled failed", e);
+ }
+
+ ((SwitchPreferenceCompat) preference).setChecked(fastChargingEnabled);
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ final boolean shouldEnableFastCharging = (Boolean) newValue;
+
+ try {
+ mFastCharge.setEnabled(shouldEnableFastCharging);
+ updateState(preference);
+ } catch (RemoteException e) {
+ Log.e(TAG, "setEnabled failed", e);
+ }
+
+ return false;
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/batterydata/BatteryDataBroadcastReceiver.java b/src/com/android/settings/fuelgauge/batterydata/BatteryDataBroadcastReceiver.java
new file mode 100644
index 00000000000..3b8b12fdbf8
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batterydata/BatteryDataBroadcastReceiver.java
@@ -0,0 +1,41 @@
+//
+// Copyright (C) 2022 The Project Mia
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+
+package com.android.settings.fuelgauge.batterydata;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public final class BatteryDataBroadcastReceiver extends BroadcastReceiver {
+
+ private static final String TAG = "BatteryDataBroadcastReceiver";
+
+ public boolean mFetchBatteryUsageData;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String batteryData = intent.getAction();
+ switch (batteryData) {
+ // Fetch device usage data
+ case "settings.intelligence.battery.action.FETCH_BATTERY_USAGE_DATA":
+ mFetchBatteryUsageData = true;
+ BatteryDataFetchService.enqueueWork(context);
+ break;
+ // Fetch bluetooth device usage data
+ case "settings.intelligence.battery.action.FETCH_BLUETOOTH_BATTERY_DATA":
+ try {
+ BluetoothBatteryDataFetch.returnBluetoothDevices(context, intent);
+ } catch (Exception e) {
+ Log.e(TAG, "returnBluetoothDevices() error: ", e);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/batterydata/BatteryDataFetchService.java b/src/com/android/settings/fuelgauge/batterydata/BatteryDataFetchService.java
new file mode 100644
index 00000000000..19360981e78
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batterydata/BatteryDataFetchService.java
@@ -0,0 +1,53 @@
+//
+// Copyright (C) 2022 The Project Mia
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+
+package com.android.settings.fuelgauge.batterydata;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.BatteryStatsManager;
+import android.os.BatteryUsageStats;
+import android.os.BatteryUsageStatsQuery;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.core.app.JobIntentService;
+
+import java.util.List;
+
+public class BatteryDataFetchService extends JobIntentService {
+
+ private static final String TAG = "BatteryDataFetchService";
+ private static final Intent JOB_INTENT = new Intent("action.LOAD_BATTERY_USAGE_DATA");
+
+ public static void enqueueWork(final Context context) {
+ AsyncTask.execute(() -> {
+ loadUsageDataSafely(context);
+ });
+ }
+
+ @Override
+ protected void onHandleWork(@NonNull Intent intent) {
+ loadUsageDataSafely(this);
+ }
+
+ private static void loadUsageDataSafely(Context context) {
+ try {
+ loadUsageData(context);
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Fail load usage data:" + e);
+ }
+ }
+
+ private static void loadUsageData(Context context) {
+ BatteryUsageStats batteryUsageStats = context
+ .getSystemService(BatteryStatsManager.class)
+ .getBatteryUsageStats(new BatteryUsageStatsQuery.Builder()
+ .includeBatteryHistory()
+ .build());
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/batterydata/BluetoothBatteryDataFetch.java b/src/com/android/settings/fuelgauge/batterydata/BluetoothBatteryDataFetch.java
new file mode 100644
index 00000000000..d437cf5fd80
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batterydata/BluetoothBatteryDataFetch.java
@@ -0,0 +1,171 @@
+//
+// Copyright (C) 2022 The Project Mia
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+
+package com.android.settings.fuelgauge.batterydata;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.ResultReceiver;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+public final class BluetoothBatteryDataFetch {
+
+ private static final String TAG = "BluetoothBatteryDataFetch";
+
+ @VisibleForTesting
+ public static LocalBluetoothManager mLocalBluetoothManager;
+ private static Context context;
+ private static Intent intent;
+
+ private static String emptyIfNull(String value) {
+ return value == null ? "" : value;
+ }
+
+ public static ContentValues wrapBluetoothData(
+ Context context, CachedBluetoothDevice cachedBluetoothDevice,
+ boolean nonbuds) {
+ BluetoothDevice device = cachedBluetoothDevice.getDevice();
+
+ ContentValues contentValues = new ContentValues();
+ contentValues.put("type", device.getType());
+ contentValues.put("name", emptyIfNull(device.getName()));
+ contentValues.put("alias", emptyIfNull(device.getAlias()));
+ contentValues.put("address", emptyIfNull(device.getAddress()));
+ contentValues.put("batteryLevel", device.getBatteryLevel());
+
+ putStringMetadata(contentValues, "hardwareVersion", device.getMetadata(
+ BluetoothDevice.METADATA_HARDWARE_VERSION));
+ putStringMetadata(contentValues, "batteryLevelRight", device.getMetadata(
+ BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY));
+ putStringMetadata(contentValues, "batteryLevelLeft", device.getMetadata(
+ BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY));
+ putStringMetadata(contentValues, "batteryLevelCase", device.getMetadata(
+ BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY));
+ putStringMetadata(contentValues, "batteryChargingRight", device.getMetadata(
+ BluetoothDevice.METADATA_UNTETHERED_RIGHT_CHARGING));
+ putStringMetadata(contentValues, "batteryChargingLeft", device.getMetadata(
+ BluetoothDevice.METADATA_UNTETHERED_LEFT_CHARGING));
+ putStringMetadata(contentValues, "batteryChargingCase", device.getMetadata(
+ BluetoothDevice.METADATA_UNTETHERED_CASE_CHARGING));
+ putStringMetadata(contentValues, "batteryChargingMain", device.getMetadata(
+ BluetoothDevice.METADATA_MAIN_CHARGING));
+ if (nonbuds) {
+ putStringMetadata(contentValues, "deviceIconMain", device.getMetadata(
+ BluetoothDevice.METADATA_MAIN_ICON));
+ putStringMetadata(contentValues, "deviceIconCase", device.getMetadata(
+ BluetoothDevice.METADATA_UNTETHERED_CASE_ICON));
+ putStringMetadata(contentValues, "deviceIconLeft", device.getMetadata(
+ BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON));
+ putStringMetadata(contentValues, "deviceIconRight", device.getMetadata(
+ BluetoothDevice.METADATA_UNTETHERED_RIGHT_ICON));
+ }
+ BluetoothClass bluetoothClass = device.getBluetoothClass();
+ if (bluetoothClass != null) {
+ contentValues.put("bluetoothClass", marshall(bluetoothClass));
+ }
+ return contentValues;
+ }
+
+ private static byte[] marshall(Parcelable parcelable) {
+ Parcel obtain = Parcel.obtain();
+ parcelable.writeToParcel(obtain, 0);
+ byte[] marshall = obtain.marshall();
+ obtain.recycle();
+ return marshall;
+ }
+
+ private static void putStringMetadata(
+ ContentValues contentValues, String key, byte[] value) {
+ if (value == null || value.length == 0) {
+ return;
+ }
+ contentValues.put(key, new String(value));
+ }
+
+ public static void returnBluetoothDevices(Context context, Intent intent) {
+ AsyncTask.execute(() -> returnBluetoothDevicesInner(context, intent));
+ }
+
+ public static void returnBluetoothDevicesInner(Context context, Intent intent) {
+ ResultReceiver resultReceiver = intent.getParcelableExtra(Intent.EXTRA_RESULT_RECEIVER);
+ if (resultReceiver == null) {
+ Log.w(TAG, "No result receiver found from intent");
+ return;
+ }
+ if (mLocalBluetoothManager == null) {
+ mLocalBluetoothManager = LocalBluetoothManager.getInstance(context, null);
+ }
+ BluetoothAdapter adapter = context.getSystemService(BluetoothManager.class).getAdapter();
+ if (adapter == null || !adapter.isEnabled() || mLocalBluetoothManager == null) {
+ Log.w(TAG, "BluetoothAdapter not present or not enabled");
+ resultReceiver.send(1, null);
+ return;
+ }
+ sendAndFilterBluetoothData(context, resultReceiver, mLocalBluetoothManager,
+ intent.getBooleanExtra("extra_fetch_icon", false));
+ }
+
+ public static void sendAndFilterBluetoothData(Context context,
+ ResultReceiver resultReceiver,
+ LocalBluetoothManager localBluetoothManager,
+ boolean cache) {
+ long start = System.currentTimeMillis();
+ Collection cachedDevicesCopy =
+ localBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy();
+ Log.d(TAG, "cachedDevices:" + cachedDevicesCopy);
+ if (cachedDevicesCopy == null || cachedDevicesCopy.isEmpty()) {
+ resultReceiver.send(0, Bundle.EMPTY);
+ return;
+ }
+ List connectedDevices = cachedDevicesCopy.stream()
+ .filter(CachedBluetoothDevice::isConnected)
+ .collect(Collectors.toList());
+ Log.d(TAG, "Connected devices:" + connectedDevices);
+ if (connectedDevices.isEmpty()) {
+ resultReceiver.send(0, Bundle.EMPTY);
+ return;
+ }
+ ArrayList bluetoothWrapDataListKey = new ArrayList<>();
+ ArrayList bluetoothParcelableList = new ArrayList<>();
+ connectedDevices.forEach(cachedBluetoothDevice -> {
+ BluetoothDevice device = cachedBluetoothDevice.getDevice();
+ bluetoothParcelableList.add(device);
+ try {
+ bluetoothWrapDataListKey.add(
+ wrapBluetoothData(context, cachedBluetoothDevice, cache));
+ } catch (Exception e) {
+ Log.e(TAG, "Wrap bluetooth data failed: " + device, e);
+ }
+ });
+ Bundle bundle = new Bundle();
+ bundle.putParcelableArrayList("bluetoothParcelableListKey", bluetoothParcelableList);
+ if (!bluetoothWrapDataListKey.isEmpty()) {
+ bundle.putParcelableArrayList("bluetoothWrapDataListKey", bluetoothWrapDataListKey);
+ }
+ resultReceiver.send(0, bundle);
+ Log.d(TAG, String.format("Send and filter bluetooth data size=%d in %d/ms",
+ bluetoothWrapDataListKey.size(), (System.currentTimeMillis() - start)));
+ }
+}
diff --git a/src/com/android/settings/gestures/BackGestureIndicatorView.java b/src/com/android/settings/gestures/BackGestureIndicatorView.java
index c60afd003d3..06b455470c4 100644
--- a/src/com/android/settings/gestures/BackGestureIndicatorView.java
+++ b/src/com/android/settings/gestures/BackGestureIndicatorView.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.PixelFormat;
+import android.graphics.Point;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -37,6 +38,7 @@ public class BackGestureIndicatorView extends LinearLayout {
private ImageView mRightIndicator;
private BackGestureIndicatorDrawable mLeftDrawable;
private BackGestureIndicatorDrawable mRightDrawable;
+ private int mHeightScale;
public BackGestureIndicatorView(Context context) {
super(context);
@@ -87,6 +89,10 @@ public void setIndicatorWidth(int width, boolean leftIndicator) {
indicator.setWidth(width);
}
+ public void setIndicatorHeightScale(int heightScale) {
+ mHeightScale = heightScale;
+ }
+
public WindowManager.LayoutParams getLayoutParams(
WindowManager.LayoutParams parentWindowAttributes) {
int copiedFlags = (parentWindowAttributes.flags
@@ -99,8 +105,18 @@ public WindowManager.LayoutParams getLayoutParams(
| copiedFlags,
PixelFormat.TRANSLUCENT);
+ setCurrentGestureHeight(lp);
lp.setTitle("BackGestureIndicatorView");
lp.token = getContext().getActivityToken();
return lp;
}
+
+ private void setCurrentGestureHeight(WindowManager.LayoutParams lp) {
+ Point displaySize = new Point();
+ getContext().getDisplay().getRealSize(displaySize);
+ lp.height = Math.round((float) displaySize.y -
+ ((float) displaySize.y) * ((float) mHeightScale / 6f));
+ if (mHeightScale == 0) lp.y = 0;
+ else lp.y = displaySize.y - lp.height;
+ }
}
diff --git a/src/com/android/settings/gestures/DoubleTapAmbientSettings.java b/src/com/android/settings/gestures/DoubleTapAmbientSettings.java
new file mode 100644
index 00000000000..1289fc3cd9e
--- /dev/null
+++ b/src/com/android/settings/gestures/DoubleTapAmbientSettings.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2019-2020 The Evolution X Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.gestures;
+
+import android.os.Bundle;
+
+import androidx.preference.Preference;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+
+public class DoubleTapAmbientSettings extends SettingsPreferenceFragment implements
+ Preference.OnPreferenceChangeListener {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.double_tap_ambient_screen_settings);
+
+ getActivity().getActionBar().setTitle(R.string.double_tap_title);
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ return false;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsEvent.DERP;
+ }
+}
diff --git a/src/com/android/settings/gestures/GestureNavigationLongPressController.java b/src/com/android/settings/gestures/GestureNavigationLongPressController.java
new file mode 100644
index 00000000000..e0bf2bde7cc
--- /dev/null
+++ b/src/com/android/settings/gestures/GestureNavigationLongPressController.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2024 Yet Another AOSP Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.gestures;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.provider.Settings;
+
+import com.android.settings.R;
+import com.android.settings.core.TogglePreferenceController;
+
+public class GestureNavigationLongPressController extends TogglePreferenceController {
+
+ private static final String GSA_PACKAGE = "com.google.android.googlequicksearchbox";
+
+ public GestureNavigationLongPressController(Context context, String key) {
+ super(context, key);
+ }
+
+ @Override
+ public boolean isChecked() {
+ return Settings.System.getInt(mContext.getContentResolver(),
+ Settings.System.NAVBAR_LONG_PRESS_GESTURE, 1) == 1;
+ }
+
+ @Override
+ public boolean setChecked(boolean isChecked) {
+ return Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.NAVBAR_LONG_PRESS_GESTURE, isChecked ? 1 : 0);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ PackageManager pm = mContext.getPackageManager();
+ if (pm == null) {
+ return UNSUPPORTED_ON_DEVICE;
+ }
+ try {
+ ApplicationInfo ai = pm.getApplicationInfo(GSA_PACKAGE, 0);
+ if (ai.enabled && ai.isProduct()) {
+ return AVAILABLE;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ return UNSUPPORTED_ON_DEVICE;
+ }
+ return UNSUPPORTED_ON_DEVICE;
+ }
+
+ @Override
+ public int getSliceHighlightMenuRes() {
+ return R.string.menu_key_system;
+ }
+}
diff --git a/src/com/android/settings/gestures/GestureNavigationSettingsFragment.java b/src/com/android/settings/gestures/GestureNavigationSettingsFragment.java
index 546581bd128..1350f2eadd3 100644
--- a/src/com/android/settings/gestures/GestureNavigationSettingsFragment.java
+++ b/src/com/android/settings/gestures/GestureNavigationSettingsFragment.java
@@ -31,6 +31,8 @@
import com.android.settings.widget.SeekBarPreference;
import com.android.settingslib.search.SearchIndexable;
+import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen;
+
/**
* A fragment to include all the settings related to Gesture Navigation mode.
*/
@@ -44,12 +46,18 @@ public class GestureNavigationSettingsFragment extends DashboardFragment {
private static final String LEFT_EDGE_SEEKBAR_KEY = "gesture_left_back_sensitivity";
private static final String RIGHT_EDGE_SEEKBAR_KEY = "gesture_right_back_sensitivity";
+ private static final String KEY_BACK_HEIGHT = "gesture_back_height";
+
+ private static final String NAVIGATION_BAR_MISC = "navigation_bar_misc";
private WindowManager mWindowManager;
private BackGestureIndicatorView mIndicatorView;
private float[] mBackGestureInsetScales;
private float mDefaultBackGestureInset;
+ private float[] mBackGestureHeightScales = { 0f, 1f, 2f, 3f, 4f, 5f };
+ private int mCurrentRightWidth;
+ private int mCurrentLeftWidth;
public GestureNavigationSettingsFragment() {
super();
@@ -75,6 +83,14 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
initSeekBarPreference(LEFT_EDGE_SEEKBAR_KEY);
initSeekBarPreference(RIGHT_EDGE_SEEKBAR_KEY);
+ initSeekBarPreference(KEY_BACK_HEIGHT);
+
+ boolean isTaskbarEnabled = Settings.System.getInt(getContext().getContentResolver(),
+ Settings.System.ENABLE_TASKBAR, isLargeScreen(getContext()) ? 1 : 0) == 1;
+ if (isTaskbarEnabled) {
+ getPreferenceScreen().removePreference(
+ getPreferenceScreen().findPreference(NAVIGATION_BAR_MISC));
+ }
}
@Override
@@ -118,12 +134,30 @@ private void initSeekBarPreference(final String key) {
pref.setContinuousUpdates(true);
pref.setHapticFeedbackMode(SeekBarPreference.HAPTIC_FEEDBACK_MODE_ON_TICKS);
- final String settingsKey = key == LEFT_EDGE_SEEKBAR_KEY
- ? Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT
- : Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT;
- final float initScale = Settings.Secure.getFloat(
+ final String settingsKey = key == LEFT_EDGE_SEEKBAR_KEY ?
+ Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT
+ : key == RIGHT_EDGE_SEEKBAR_KEY ?
+ Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT
+ : Settings.System.BACK_GESTURE_HEIGHT;
+
+
+ float initScale = Settings.Secure.getFloat(
getContext().getContentResolver(), settingsKey, 1.0f);
+ // needed if we just change the height
+ float currentWidthScale = Settings.Secure.getFloat(
+ getContext().getContentResolver(), Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT, 1.0f);
+ mCurrentRightWidth = (int) (mDefaultBackGestureInset * currentWidthScale);
+ currentWidthScale = Settings.Secure.getFloat(
+ getContext().getContentResolver(), Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT, 1.0f);
+ mCurrentLeftWidth = (int) (mDefaultBackGestureInset * currentWidthScale);
+
+ if (key == KEY_BACK_HEIGHT) {
+ mBackGestureInsetScales = mBackGestureHeightScales;
+ initScale = Settings.System.getInt(
+ getContext().getContentResolver(), settingsKey, 0);
+ }
+
// Find the closest value to initScale
float minDistance = Float.MAX_VALUE;
int minDistanceIndex = -1;
@@ -137,15 +171,38 @@ private void initSeekBarPreference(final String key) {
pref.setProgress(minDistanceIndex);
pref.setOnPreferenceChangeListener((p, v) -> {
- final int width = (int) (mDefaultBackGestureInset * mBackGestureInsetScales[(int) v]);
- mIndicatorView.setIndicatorWidth(width, key == LEFT_EDGE_SEEKBAR_KEY);
+ if (key != KEY_BACK_HEIGHT) {
+ final int width = (int) (mDefaultBackGestureInset * mBackGestureInsetScales[(int) v]);
+ mIndicatorView.setIndicatorWidth(width, key == LEFT_EDGE_SEEKBAR_KEY);
+ if (key == LEFT_EDGE_SEEKBAR_KEY) {
+ mCurrentLeftWidth = width;
+ } else {
+ mCurrentRightWidth = width;
+ }
+ } else {
+ final int heightScale = (int) (mBackGestureInsetScales[(int) v]);
+ mIndicatorView.setIndicatorHeightScale(heightScale);
+ // dont use updateViewLayout else it will animate
+ mWindowManager.removeView(mIndicatorView);
+ mWindowManager.addView(mIndicatorView, mIndicatorView.getLayoutParams(
+ getActivity().getWindow().getAttributes()));
+ // peek the indicators
+ mIndicatorView.setIndicatorWidth(mCurrentRightWidth, false);
+ mIndicatorView.setIndicatorWidth(mCurrentLeftWidth, true);
+ }
return true;
});
pref.setOnPreferenceChangeStopListener((p, v) -> {
- mIndicatorView.setIndicatorWidth(0, key == LEFT_EDGE_SEEKBAR_KEY);
final float scale = mBackGestureInsetScales[(int) v];
- Settings.Secure.putFloat(getContext().getContentResolver(), settingsKey, scale);
+ if (key == KEY_BACK_HEIGHT) {
+ mIndicatorView.setIndicatorWidth(0, false);
+ mIndicatorView.setIndicatorWidth(0, true);
+ Settings.System.putInt(getContext().getContentResolver(), settingsKey, (int) scale);
+ } else {
+ mIndicatorView.setIndicatorWidth(0, key == LEFT_EDGE_SEEKBAR_KEY);
+ Settings.Secure.putFloat(getContext().getContentResolver(), settingsKey, scale);
+ }
return true;
});
}
diff --git a/src/com/android/settings/gestures/GestureSettings.java b/src/com/android/settings/gestures/GestureSettings.java
index 8532b162224..5d5d038bb65 100644
--- a/src/com/android/settings/gestures/GestureSettings.java
+++ b/src/com/android/settings/gestures/GestureSettings.java
@@ -55,6 +55,7 @@ public void onAttach(Context context) {
super.onAttach(context);
use(PickupGesturePreferenceController.class).setConfig(getConfig(context));
use(DoubleTapScreenPreferenceController.class).setConfig(getConfig(context));
+ use(ScreenOffUdfpsPreferenceController.class).setConfig(getConfig(context));
}
private AmbientDisplayConfiguration getConfig(Context context) {
diff --git a/src/com/android/settings/gestures/GesturesSettingPreferenceController.java b/src/com/android/settings/gestures/GesturesSettingPreferenceController.java
index e8c4b934663..d6e0077b0e9 100644
--- a/src/com/android/settings/gestures/GesturesSettingPreferenceController.java
+++ b/src/com/android/settings/gestures/GesturesSettingPreferenceController.java
@@ -68,6 +68,9 @@ private static List buildAllPreferenceControllers(
controllers.add(new DoubleTapScreenPreferenceController(context, FAKE_PREF_KEY)
.setConfig(ambientDisplayConfiguration));
controllers.add(new PreventRingingParentPreferenceController(context, FAKE_PREF_KEY));
+ controllers.add(new SwipeToScreenshotPreferenceController(context, FAKE_PREF_KEY));
+ controllers.add(new ScreenOffUdfpsPreferenceController(context, FAKE_PREF_KEY)
+ .setConfig(ambientDisplayConfiguration));
return controllers;
}
}
diff --git a/src/com/android/settings/gestures/PowerMenuSettingsUtils.java b/src/com/android/settings/gestures/PowerMenuSettingsUtils.java
index b1103811749..4262c0d8cd3 100644
--- a/src/com/android/settings/gestures/PowerMenuSettingsUtils.java
+++ b/src/com/android/settings/gestures/PowerMenuSettingsUtils.java
@@ -55,6 +55,7 @@ final class PowerMenuSettingsUtils {
private static final int LONG_PRESS_POWER_GLOBAL_ACTIONS = 1; // a.k.a., Power Menu
private static final int LONG_PRESS_POWER_ASSISTANT_VALUE = 5; // Settings.Secure.ASSISTANT
+ private static final int KEY_CHORD_POWER_VOLUME_UP_MUTE_TOGGLE = 1;
private static final int KEY_CHORD_POWER_VOLUME_UP_GLOBAL_ACTIONS = 2;
private static final Uri POWER_BUTTON_LONG_PRESS_URI =
@@ -113,14 +114,11 @@ public static boolean setLongPressPowerForPowerMenu(Context context) {
context.getContentResolver(),
POWER_BUTTON_LONG_PRESS_SETTING,
LONG_PRESS_POWER_GLOBAL_ACTIONS)) {
- // We restore power + volume up buttons to the default action.
- int keyChordDefaultValue =
- context.getResources()
- .getInteger(KEY_CHORD_POWER_VOLUME_UP_DEFAULT_VALUE_RESOURCE);
+ // We restore power + volume up buttons to the mute action.
Settings.Global.putInt(
context.getContentResolver(),
KEY_CHORD_POWER_VOLUME_UP_SETTING,
- keyChordDefaultValue);
+ KEY_CHORD_POWER_VOLUME_UP_MUTE_TOGGLE);
return true;
}
return false;
diff --git a/src/com/android/settings/gestures/PreventRingingGesturePreferenceController.java b/src/com/android/settings/gestures/PreventRingingGesturePreferenceController.java
index d171677d72a..0f9d6f321a5 100644
--- a/src/com/android/settings/gestures/PreventRingingGesturePreferenceController.java
+++ b/src/com/android/settings/gestures/PreventRingingGesturePreferenceController.java
@@ -16,6 +16,11 @@
package com.android.settings.gestures;
+import static android.provider.Settings.Secure.DERP_VOLUME_HUSH_OFF;
+import static android.provider.Settings.Secure.DERP_VOLUME_HUSH_MUTE;
+import static android.provider.Settings.Secure.DERP_VOLUME_HUSH_NORMAL;
+import static android.provider.Settings.Secure.DERP_VOLUME_HUSH_VIBRATE;
+
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
@@ -26,8 +31,8 @@
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;
+import androidx.preference.SwitchPreference;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settingslib.core.AbstractPreferenceController;
@@ -36,27 +41,32 @@
import com.android.settingslib.core.lifecycle.events.OnPause;
import com.android.settingslib.core.lifecycle.events.OnResume;
import com.android.settingslib.widget.SelectorWithWidgetPreference;
+import com.android.settingslib.widget.LayoutPreference;
+import com.android.settingslib.widget.MainSwitchPreference;
+
+import java.util.ArrayList;
+import java.util.Arrays;
public class PreventRingingGesturePreferenceController extends AbstractPreferenceController
- implements SelectorWithWidgetPreference.OnClickListener, LifecycleObserver,
+ implements Preference.OnPreferenceChangeListener, LifecycleObserver,
OnResume, OnPause, PreferenceControllerMixin {
- @VisibleForTesting
+ static final String KEY_MASTER = "gesture_prevent_ringing_switch";
static final String KEY_VIBRATE = "prevent_ringing_option_vibrate";
-
- @VisibleForTesting
static final String KEY_MUTE = "prevent_ringing_option_mute";
+ static final String KEY_NORMAL = "prevent_ringing_option_normal";
+
+ static final String KEY_CYCLE = "prevent_ringing_option_cycle";
private final String PREF_KEY_VIDEO = "gesture_prevent_ringing_video";
private final String KEY = "gesture_prevent_ringing_category";
private final Context mContext;
- @VisibleForTesting
PreferenceCategory mPreferenceCategory;
- @VisibleForTesting
- SelectorWithWidgetPreference mVibratePref;
- @VisibleForTesting
- SelectorWithWidgetPreference mMutePref;
+ MainSwitchPreference mMasterSwitch;
+ SwitchPreference mNormalPref;
+ SwitchPreference mVibratePref;
+ SwitchPreference mMutePref;
private SettingObserver mSettingObserver;
@@ -76,8 +86,10 @@ public void displayPreference(PreferenceScreen screen) {
return;
}
mPreferenceCategory = screen.findPreference(getPreferenceKey());
- mVibratePref = makeRadioPreference(KEY_VIBRATE, R.string.prevent_ringing_option_vibrate);
- mMutePref = makeRadioPreference(KEY_MUTE, R.string.prevent_ringing_option_mute);
+ mMasterSwitch = screen.findPreference(KEY_MASTER);
+ mNormalPref = makeSwitchPreference(KEY_NORMAL, R.string.prevent_ringing_option_normal);
+ mVibratePref = makeSwitchPreference(KEY_VIBRATE, R.string.prevent_ringing_option_vibrate);
+ mMutePref = makeSwitchPreference(KEY_MUTE, R.string.prevent_ringing_option_mute);
if (mPreferenceCategory != null) {
mSettingObserver = new SettingObserver(mPreferenceCategory);
@@ -100,35 +112,66 @@ public String getVideoPrefKey() {
}
@Override
- public void onRadioButtonClicked(SelectorWithWidgetPreference preference) {
- int preventRingingSetting = keyToSetting(preference.getKey());
- if (preventRingingSetting != Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.VOLUME_HUSH_GESTURE, Settings.Secure.VOLUME_HUSH_VIBRATE)) {
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.VOLUME_HUSH_GESTURE, preventRingingSetting);
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ final boolean isAdd = (Boolean) newValue;
+ final String preventRingingSetting = keyToSetting(preference.getKey());
+ String settingsValue = Settings.Secure.getString(
+ mContext.getContentResolver(), Settings.Secure.VOLUME_HUSH_GESTURE);
+ if (settingsValue == null) settingsValue = DERP_VOLUME_HUSH_OFF;
+ ArrayList currentValue = new ArrayList();
+ currentValue.addAll(Arrays.asList(settingsValue.split(",", 0)));
+
+ if (isAdd) {
+ if (currentValue.get(0).equals(DERP_VOLUME_HUSH_OFF))
+ currentValue.clear();
+ if (!currentValue.contains(preventRingingSetting))
+ currentValue.add(preventRingingSetting);
+ } else {
+ if (currentValue.size() == 1 ||
+ preventRingingSetting.equals(DERP_VOLUME_HUSH_OFF)) {
+ currentValue.clear();
+ currentValue.add(DERP_VOLUME_HUSH_OFF);
+ if (mMasterSwitch != null) mMasterSwitch.setChecked(false);
+ } else {
+ currentValue.remove(preventRingingSetting);
+ }
+ }
+
+ String value = "";
+ boolean first = true;
+ for (String str : currentValue) {
+ if (first) {
+ value += str;
+ first = false;
+ continue;
+ }
+ value += "," + str;
}
+ Settings.Secure.putString(mContext.getContentResolver(),
+ Settings.Secure.VOLUME_HUSH_GESTURE, value);
+ return true;
}
@Override
public void updateState(Preference preference) {
- int preventRingingSetting = Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.VOLUME_HUSH_GESTURE, Settings.Secure.VOLUME_HUSH_VIBRATE);
- final boolean isVibrate = preventRingingSetting == Settings.Secure.VOLUME_HUSH_VIBRATE;
- final boolean isMute = preventRingingSetting == Settings.Secure.VOLUME_HUSH_MUTE;
- if (mVibratePref != null && mVibratePref.isChecked() != isVibrate) {
+ final String preventRingingSetting = Settings.Secure.getString(
+ mContext.getContentResolver(), Settings.Secure.VOLUME_HUSH_GESTURE);
+
+ final boolean enabled = preventRingingSetting != null &&
+ !preventRingingSetting.equals(DERP_VOLUME_HUSH_OFF);
+ if (mVibratePref != null) mVibratePref.setEnabled(enabled);
+ if (mMutePref != null) mMutePref.setEnabled(enabled);
+ if (mNormalPref != null) mNormalPref.setEnabled(enabled);
+
+ final boolean isVibrate = enabled && preventRingingSetting.contains(DERP_VOLUME_HUSH_VIBRATE);
+ final boolean isMute = enabled && preventRingingSetting.contains(DERP_VOLUME_HUSH_MUTE);
+ final boolean isNormal = enabled && preventRingingSetting.contains(DERP_VOLUME_HUSH_NORMAL);
+ if (mVibratePref != null && mVibratePref.isChecked() != isVibrate)
mVibratePref.setChecked(isVibrate);
- }
- if (mMutePref != null && mMutePref.isChecked() != isMute) {
+ if (mMutePref != null && mMutePref.isChecked() != isMute)
mMutePref.setChecked(isMute);
- }
-
- if (preventRingingSetting == Settings.Secure.VOLUME_HUSH_OFF) {
- mVibratePref.setEnabled(false);
- mMutePref.setEnabled(false);
- } else {
- mVibratePref.setEnabled(true);
- mMutePref.setEnabled(true);
- }
+ if (mNormalPref != null && mNormalPref.isChecked() != isNormal)
+ mNormalPref.setChecked(isNormal);
}
@Override
@@ -146,23 +189,24 @@ public void onPause() {
}
}
- private int keyToSetting(String key) {
+ private String keyToSetting(String key) {
switch (key) {
case KEY_MUTE:
- return Settings.Secure.VOLUME_HUSH_MUTE;
+ return DERP_VOLUME_HUSH_MUTE;
case KEY_VIBRATE:
- return Settings.Secure.VOLUME_HUSH_VIBRATE;
+ return DERP_VOLUME_HUSH_VIBRATE;
+ case KEY_NORMAL:
+ return DERP_VOLUME_HUSH_NORMAL;
default:
- return Settings.Secure.VOLUME_HUSH_OFF;
+ return DERP_VOLUME_HUSH_OFF;
}
}
- private SelectorWithWidgetPreference makeRadioPreference(String key, int titleId) {
- SelectorWithWidgetPreference pref = new SelectorWithWidgetPreference(
- mPreferenceCategory.getContext());
+ private SwitchPreference makeSwitchPreference(String key, int titleId) {
+ SwitchPreference pref = new SwitchPreference(mPreferenceCategory.getContext());
pref.setKey(key);
pref.setTitle(titleId);
- pref.setOnClickListener(this);
+ pref.setOnPreferenceChangeListener(this);
mPreferenceCategory.addPreference(pref);
return pref;
}
diff --git a/src/com/android/settings/gestures/PreventRingingParentPreferenceController.java b/src/com/android/settings/gestures/PreventRingingParentPreferenceController.java
index 03b0259449c..924d6c2826c 100644
--- a/src/com/android/settings/gestures/PreventRingingParentPreferenceController.java
+++ b/src/com/android/settings/gestures/PreventRingingParentPreferenceController.java
@@ -17,8 +17,10 @@
package com.android.settings.gestures;
import static android.provider.Settings.Secure.VOLUME_HUSH_GESTURE;
-import static android.provider.Settings.Secure.VOLUME_HUSH_MUTE;
-import static android.provider.Settings.Secure.VOLUME_HUSH_VIBRATE;
+import static android.provider.Settings.Secure.DERP_VOLUME_HUSH_MUTE;
+import static android.provider.Settings.Secure.DERP_VOLUME_HUSH_NORMAL;
+import static android.provider.Settings.Secure.DERP_VOLUME_HUSH_OFF;
+import static android.provider.Settings.Secure.DERP_VOLUME_HUSH_VIBRATE;
import android.content.ContentResolver;
import android.content.Context;
@@ -39,6 +41,10 @@
import com.google.common.annotations.VisibleForTesting;
+import java.lang.StringBuilder;
+import java.util.ArrayList;
+import java.util.Arrays;
+
/** The controller manages the behaviour of the Prevent Ringing gesture setting. */
public class PreventRingingParentPreferenceController extends TogglePreferenceController
implements LifecycleObserver, OnStart, OnStop {
@@ -68,53 +74,58 @@ public boolean isChecked() {
return false;
}
- final int preventRinging = Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.VOLUME_HUSH_GESTURE,
- Settings.Secure.VOLUME_HUSH_VIBRATE);
- return preventRinging != Settings.Secure.VOLUME_HUSH_OFF;
+ String preventRinging = Settings.Secure.getString(
+ mContext.getContentResolver(), VOLUME_HUSH_GESTURE);
+ if (preventRinging == null) preventRinging = DERP_VOLUME_HUSH_OFF;
+ return !preventRinging.equals(DERP_VOLUME_HUSH_OFF);
}
@Override
public boolean setChecked(boolean isChecked) {
- final int preventRingingSetting = Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.VOLUME_HUSH_GESTURE, Settings.Secure.VOLUME_HUSH_VIBRATE);
- final int newRingingSetting = preventRingingSetting == Settings.Secure.VOLUME_HUSH_OFF
- ? Settings.Secure.VOLUME_HUSH_VIBRATE
- : preventRingingSetting;
-
- return Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.VOLUME_HUSH_GESTURE, isChecked
+ String preventRingingSetting = Settings.Secure.getString(
+ mContext.getContentResolver(), VOLUME_HUSH_GESTURE);
+ if (preventRingingSetting == null) preventRingingSetting = DERP_VOLUME_HUSH_OFF;
+
+ final String newRingingSetting = preventRingingSetting.equals(DERP_VOLUME_HUSH_OFF)
+ ? DERP_VOLUME_HUSH_VIBRATE : preventRingingSetting;
+
+ return Settings.Secure.putString(mContext.getContentResolver(),
+ VOLUME_HUSH_GESTURE, isChecked
? newRingingSetting
- : Settings.Secure.VOLUME_HUSH_OFF);
+ : DERP_VOLUME_HUSH_OFF);
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
- final int value = Settings.Secure.getInt(
- mContext.getContentResolver(), SECURE_KEY, VOLUME_HUSH_VIBRATE);
- CharSequence summary;
+ String value = Settings.Secure.getString(
+ mContext.getContentResolver(), SECURE_KEY);
+ if (value == null) value = DERP_VOLUME_HUSH_OFF;
+ StringBuilder summary = new StringBuilder(
+ mContext.getString(R.string.switch_off_text));
if (isVolumePowerKeyChordSetToHush()) {
- switch (value) {
- case VOLUME_HUSH_VIBRATE:
- summary = mContext.getText(R.string.prevent_ringing_option_vibrate_summary);
- break;
- case VOLUME_HUSH_MUTE:
- summary = mContext.getText(R.string.prevent_ringing_option_mute_summary);
- break;
- // VOLUME_HUSH_OFF
- default:
- summary = mContext.getText(R.string.switch_off_text);
+ if (!value.equals(DERP_VOLUME_HUSH_OFF)) {
+ ArrayList values =
+ new ArrayList<>(Arrays.asList(value.split(",", 0)));
+ if (!values.isEmpty()) {
+ summary = new StringBuilder(
+ mContext.getString(R.string.switch_on_text)
+ + " (" + getStringForMode(values.remove(0)));
+ for (String str : values)
+ summary.append(", ").append(getStringForMode(str));
+ summary.append(")");
+ }
}
preference.setEnabled(true);
mPreference.setSwitchEnabled(true);
} else {
- summary = mContext.getText(R.string.prevent_ringing_option_unavailable_lpp_summary);
+ summary = new StringBuilder(mContext.getString(
+ R.string.prevent_ringing_option_unavailable_lpp_summary));
preference.setEnabled(false);
mPreference.setSwitchEnabled(false);
}
- preference.setSummary(summary);
+ preference.setSummary(summary.toString());
}
@Override
@@ -203,4 +214,15 @@ public void onChange(boolean selfChange, Uri uri) {
}
}
}
+
+ private String getStringForMode(String mode) {
+ switch (mode) {
+ case DERP_VOLUME_HUSH_VIBRATE:
+ return mContext.getText(R.string.prevent_ringing_option_vibrate).toString();
+ case DERP_VOLUME_HUSH_MUTE:
+ return mContext.getText(R.string.prevent_ringing_option_mute).toString();
+ }
+ // DERP_VOLUME_HUSH_NORMAL
+ return mContext.getText(R.string.prevent_ringing_option_normal).toString();
+ }
}
diff --git a/src/com/android/settings/gestures/PreventRingingSwitchPreferenceController.java b/src/com/android/settings/gestures/PreventRingingSwitchPreferenceController.java
index 9c0e0bffae7..1f42ca38542 100644
--- a/src/com/android/settings/gestures/PreventRingingSwitchPreferenceController.java
+++ b/src/com/android/settings/gestures/PreventRingingSwitchPreferenceController.java
@@ -24,6 +24,7 @@
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
+import androidx.preference.SwitchPreference;
import com.android.settings.R;
import com.android.settings.core.PreferenceControllerMixin;
@@ -34,10 +35,11 @@ public class PreventRingingSwitchPreferenceController extends AbstractPreference
implements PreferenceControllerMixin, OnCheckedChangeListener {
private static final String KEY = "gesture_prevent_ringing_switch";
+ private static final String KEY_VIBRATE = "prevent_ringing_option_vibrate";
private final Context mContext;
- @VisibleForTesting
MainSwitchPreference mSwitch;
+ SwitchPreference mVibratePref;
public PreventRingingSwitchPreferenceController(Context context) {
super(context);
@@ -53,17 +55,18 @@ public String getPreferenceKey() {
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
if (isAvailable()) {
+ mVibratePref = screen.findPreference(KEY_VIBRATE);
Preference pref = screen.findPreference(getPreferenceKey());
if (pref != null) {
pref.setOnPreferenceClickListener(preference -> {
- int preventRinging = Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.VOLUME_HUSH_GESTURE,
- Settings.Secure.VOLUME_HUSH_VIBRATE);
- boolean isChecked = preventRinging != Settings.Secure.VOLUME_HUSH_OFF;
- Settings.Secure.putInt(mContext.getContentResolver(),
+ String preventRinging = Settings.Secure.getString(mContext.getContentResolver(),
+ Settings.Secure.VOLUME_HUSH_GESTURE);
+ boolean isChecked = preventRinging != null &&
+ !preventRinging.equals(Settings.Secure.DERP_VOLUME_HUSH_OFF);
+ Settings.Secure.putString(mContext.getContentResolver(),
Settings.Secure.VOLUME_HUSH_GESTURE, isChecked
- ? Settings.Secure.VOLUME_HUSH_OFF
- : Settings.Secure.VOLUME_HUSH_VIBRATE);
+ ? Settings.Secure.DERP_VOLUME_HUSH_OFF
+ : Settings.Secure.DERP_VOLUME_HUSH_VIBRATE);
return true;
});
mSwitch = (MainSwitchPreference) pref;
@@ -82,9 +85,10 @@ public void setChecked(boolean isChecked) {
@Override
public void updateState(Preference preference) {
- int preventRingingSetting = Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.VOLUME_HUSH_GESTURE, Settings.Secure.VOLUME_HUSH_VIBRATE);
- setChecked(preventRingingSetting != Settings.Secure.VOLUME_HUSH_OFF);
+ String preventRingingSetting = Settings.Secure.getString(mContext.getContentResolver(),
+ Settings.Secure.VOLUME_HUSH_GESTURE);
+ setChecked(preventRingingSetting != null &&
+ !preventRingingSetting.equals(Settings.Secure.DERP_VOLUME_HUSH_OFF));
}
@Override
@@ -95,15 +99,23 @@ public boolean isAvailable() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- final int preventRingingSetting = Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.VOLUME_HUSH_GESTURE, Settings.Secure.VOLUME_HUSH_VIBRATE);
- final int newRingingSetting = preventRingingSetting == Settings.Secure.VOLUME_HUSH_OFF
- ? Settings.Secure.VOLUME_HUSH_VIBRATE
+ final String preventRingingSetting = Settings.Secure.getString(mContext.getContentResolver(),
+ Settings.Secure.VOLUME_HUSH_GESTURE);
+ final String newRingingSetting =
+ preventRingingSetting == null ||
+ preventRingingSetting.equals(Settings.Secure.DERP_VOLUME_HUSH_OFF)
+ ? Settings.Secure.DERP_VOLUME_HUSH_VIBRATE
: preventRingingSetting;
- Settings.Secure.putInt(mContext.getContentResolver(),
+ if ((preventRingingSetting == null
+ || preventRingingSetting.equals(Settings.Secure.DERP_VOLUME_HUSH_OFF))
+ && mVibratePref != null) {
+ mVibratePref.setChecked(true);
+ }
+
+ Settings.Secure.putString(mContext.getContentResolver(),
Settings.Secure.VOLUME_HUSH_GESTURE, isChecked
? newRingingSetting
- : Settings.Secure.VOLUME_HUSH_OFF);
+ : Settings.Secure.DERP_VOLUME_HUSH_OFF);
}
}
diff --git a/src/com/android/settings/gestures/ScreenOffUdfpsPreferenceController.java b/src/com/android/settings/gestures/ScreenOffUdfpsPreferenceController.java
new file mode 100644
index 00000000000..d1202cb4cac
--- /dev/null
+++ b/src/com/android/settings/gestures/ScreenOffUdfpsPreferenceController.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.gestures;
+
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.hardware.display.AmbientDisplayConfiguration;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+
+import androidx.annotation.VisibleForTesting;
+
+public class ScreenOffUdfpsPreferenceController extends GesturePreferenceController {
+
+ private final int ON = 1;
+ private final int OFF = 0;
+
+ private static final String PREF_KEY_VIDEO = "gesture_screen_off_udfps_video";
+
+ private static final String SECURE_KEY = "screen_off_udfps_enabled";
+
+ private AmbientDisplayConfiguration mAmbientConfig;
+ @UserIdInt
+ private final int mUserId;
+
+ public ScreenOffUdfpsPreferenceController(Context context, String key) {
+ super(context, key);
+ mUserId = UserHandle.myUserId();
+ }
+
+ public ScreenOffUdfpsPreferenceController setConfig(AmbientDisplayConfiguration config) {
+ mAmbientConfig = config;
+ return this;
+ }
+
+ private static boolean screenOffUdfpsAvailable(AmbientDisplayConfiguration config) {
+ return !TextUtils.isEmpty(config.udfpsLongPressSensorType());
+ }
+
+ public static boolean isSuggestionComplete(Context context, SharedPreferences prefs) {
+ return isSuggestionComplete(new AmbientDisplayConfiguration(context), prefs);
+ }
+
+ @VisibleForTesting
+ static boolean isSuggestionComplete(AmbientDisplayConfiguration config,
+ SharedPreferences prefs) {
+ return !screenOffUdfpsAvailable(config)
+ || prefs.getBoolean(ScreenOffUdfpsSettings.PREF_KEY_SUGGESTION_COMPLETE, false);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ // No hardware support for Screen-Off UDFPS
+ if (!screenOffUdfpsAvailable(getAmbientConfig())) {
+ return UNSUPPORTED_ON_DEVICE;
+ }
+
+ return AVAILABLE;
+ }
+
+ @Override
+ public boolean isSliceable() {
+ return TextUtils.equals(getPreferenceKey(), "gesture_screen_off_udfps");
+ }
+
+ @Override
+ public boolean isPublicSlice() {
+ return true;
+ }
+
+ @Override
+ public boolean setChecked(boolean isChecked) {
+ return Settings.Secure.putInt(mContext.getContentResolver(), SECURE_KEY,
+ isChecked ? ON : OFF);
+ }
+
+ @Override
+ protected String getVideoPrefKey() {
+ return PREF_KEY_VIDEO;
+ }
+
+ @Override
+ public boolean isChecked() {
+ return getAmbientConfig().screenOffUdfpsEnabled(mUserId);
+ }
+
+ private AmbientDisplayConfiguration getAmbientConfig() {
+ if (mAmbientConfig == null) {
+ mAmbientConfig = new AmbientDisplayConfiguration(mContext);
+ }
+ return mAmbientConfig;
+ }
+}
diff --git a/src/com/android/settings/gestures/ScreenOffUdfpsSettings.java b/src/com/android/settings/gestures/ScreenOffUdfpsSettings.java
new file mode 100644
index 00000000000..763ffb69bf7
--- /dev/null
+++ b/src/com/android/settings/gestures/ScreenOffUdfpsSettings.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.gestures;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.hardware.display.AmbientDisplayConfiguration;
+import android.provider.SearchIndexableResource;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.search.SearchIndexable;
+
+import java.util.Arrays;
+import java.util.List;
+
+@SearchIndexable
+public class ScreenOffUdfpsSettings extends DashboardFragment {
+
+ private static final String TAG = "ScreenOffUdfps";
+
+ public static final String PREF_KEY_SUGGESTION_COMPLETE =
+ "pref_screen_off_udfps_suggestion_complete";
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ SuggestionFeatureProvider suggestionFeatureProvider =
+ FeatureFactory.Companion.getFeatureFactory().getSuggestionFeatureProvider();
+ SharedPreferences prefs = suggestionFeatureProvider.getSharedPrefs(context);
+ prefs.edit().putBoolean(PREF_KEY_SUGGESTION_COMPLETE, true).apply();
+
+ use(ScreenOffUdfpsPreferenceController.class)
+ .setConfig(new AmbientDisplayConfiguration(context));
+ }
+
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.PAGE_UNKNOWN;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.screen_off_udfps_settings;
+ }
+
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider(R.xml.screen_off_udfps_settings);
+}
diff --git a/src/com/android/settings/gestures/SwipeToScreenshotGestureSettings.java b/src/com/android/settings/gestures/SwipeToScreenshotGestureSettings.java
new file mode 100644
index 00000000000..ae0f4e3ae92
--- /dev/null
+++ b/src/com/android/settings/gestures/SwipeToScreenshotGestureSettings.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.gestures;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.provider.SearchIndexableResource;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.search.SearchIndexable;
+
+import java.util.Arrays;
+import java.util.List;
+
+@SearchIndexable
+public class SwipeToScreenshotGestureSettings extends DashboardFragment {
+
+ private static final String TAG = "SwipeToScreenshotGestureSettings";
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return -1;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.swipe_to_screenshot_gesture_settings;
+ }
+
+ public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider() {
+ @Override
+ public List getXmlResourcesToIndex(
+ Context context, boolean enabled) {
+ final SearchIndexableResource sir = new SearchIndexableResource(context);
+ sir.xmlResId = R.xml.swipe_to_screenshot_gesture_settings;
+ return Arrays.asList(sir);
+ }
+ };
+}
diff --git a/src/com/android/settings/gestures/SwipeToScreenshotPreferenceController.java b/src/com/android/settings/gestures/SwipeToScreenshotPreferenceController.java
new file mode 100644
index 00000000000..121428eaaea
--- /dev/null
+++ b/src/com/android/settings/gestures/SwipeToScreenshotPreferenceController.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.gestures;
+
+import static android.provider.Settings.System.THREE_FINGER_GESTURE;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.provider.Settings;
+import android.text.TextUtils;
+
+public class SwipeToScreenshotPreferenceController extends GesturePreferenceController {
+
+ private final int ON = 1;
+ private final int OFF = 0;
+
+ private static final String PREF_KEY_VIDEO = "swipe_to_screenshot_video";
+
+ public SwipeToScreenshotPreferenceController(Context context, String key) {
+ super(context, key);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+
+ @Override
+ public boolean isSliceable() {
+ return TextUtils.equals(getPreferenceKey(), "swipe_to_screenshot");
+ }
+
+ @Override
+ protected String getVideoPrefKey() {
+ return PREF_KEY_VIDEO;
+ }
+
+ @Override
+ public boolean setChecked(boolean isChecked) {
+ return Settings.System.putInt(mContext.getContentResolver(), THREE_FINGER_GESTURE,
+ isChecked ? ON : OFF);
+ }
+
+ @Override
+ public boolean isChecked() {
+ return Settings.System.getInt(mContext.getContentResolver(), THREE_FINGER_GESTURE, 0) != 0;
+ }
+}
diff --git a/src/com/android/settings/gestures/SystemNavigationGestureSettings.java b/src/com/android/settings/gestures/SystemNavigationGestureSettings.java
index cfaee006d39..48a0cf31cea 100644
--- a/src/com/android/settings/gestures/SystemNavigationGestureSettings.java
+++ b/src/com/android/settings/gestures/SystemNavigationGestureSettings.java
@@ -59,6 +59,8 @@
import com.android.settingslib.widget.IllustrationPreference;
import com.android.settingslib.widget.SelectorWithWidgetPreference;
+import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen;
+
import java.util.ArrayList;
import java.util.List;
@@ -209,6 +211,9 @@ protected List extends CandidateInfo> getCandidates() {
final Context c = getContext();
List candidates = new ArrayList<>();
+ boolean isTaskbarEnabled = Settings.System.getInt(getContext().getContentResolver(),
+ Settings.System.ENABLE_TASKBAR, isLargeScreen(getContext()) ? 1 : 0) == 1;
+
if (SystemNavigationPreferenceController.isOverlayPackageAvailable(c,
NAV_BAR_MODE_GESTURAL_OVERLAY)) {
candidates.add(new CandidateInfoExtra(
@@ -216,7 +221,7 @@ protected List extends CandidateInfo> getCandidates() {
c.getText(R.string.edge_to_edge_navigation_summary),
KEY_SYSTEM_NAV_GESTURAL, true /* enabled */));
}
- if (SystemNavigationPreferenceController.isOverlayPackageAvailable(c,
+ if (!isTaskbarEnabled && SystemNavigationPreferenceController.isOverlayPackageAvailable(c,
NAV_BAR_MODE_2BUTTON_OVERLAY)) {
candidates.add(new CandidateInfoExtra(
c.getText(R.string.swipe_up_to_switch_apps_title),
diff --git a/src/com/android/settings/gestures/SystemNavigationPreferenceController.java b/src/com/android/settings/gestures/SystemNavigationPreferenceController.java
index ccdb2577ca2..87955aaf130 100644
--- a/src/com/android/settings/gestures/SystemNavigationPreferenceController.java
+++ b/src/com/android/settings/gestures/SystemNavigationPreferenceController.java
@@ -23,6 +23,10 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.view.Display;
+import android.view.IWindowManager;
+import android.view.WindowManagerGlobal;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
@@ -54,9 +58,19 @@ public CharSequence getSummary() {
/** Returns {@code true} if gesture is available. */
public static boolean isGestureAvailable(Context context) {
+ boolean hasNavigationBar = false;
+ final boolean configEnabled = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_swipe_up_gesture_setting_available);
+
+ try {
+ IWindowManager windowManager = WindowManagerGlobal.getWindowManagerService();
+ hasNavigationBar = windowManager.hasNavigationBar(Display.DEFAULT_DISPLAY);
+ } catch (RemoteException ex) {
+ // no window manager? good luck with that
+ }
// Skip if the swipe up settings are not available
- if (!context.getResources().getBoolean(
- com.android.internal.R.bool.config_swipe_up_gesture_setting_available)) {
+ // or if on-screen navbar is disabled (for devices with hardware keys)
+ if (!configEnabled || !hasNavigationBar) {
return false;
}
diff --git a/src/com/android/settings/homepage/TopLevelSettings.java b/src/com/android/settings/homepage/TopLevelSettings.java
index 99441b0cc50..cbcdf296bf0 100644
--- a/src/com/android/settings/homepage/TopLevelSettings.java
+++ b/src/com/android/settings/homepage/TopLevelSettings.java
@@ -88,7 +88,7 @@ public TopLevelSettings(TopLevelHighlightMixin highlightMixin) {
@Override
protected int getPreferenceScreenResId() {
- return Flags.homepageRevamp() ? R.xml.top_level_settings_v2 : R.xml.top_level_settings;
+ return Flags.homepageRevamp() ? R.xml.top_level_settings_v2 : R.xml.derp_top_level_settings;
}
@Override
@@ -350,7 +350,9 @@ protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen
@Override
protected Preference createPreference(Tile tile) {
- return new HomepagePreference(getPrefContext());
+ Preference p = new HomepagePreference(getPrefContext());
+ p.setLayoutResource(R.layout.derp_dashboard_preference_middle);
+ return p;
}
void reloadHighlightMenuKey() {
@@ -394,7 +396,7 @@ default void init() {
new BaseSearchIndexProvider(
Flags.homepageRevamp()
? R.xml.top_level_settings_v2
- : R.xml.top_level_settings) {
+ : R.xml.derp_top_level_settings) {
@Override
protected boolean isPageSearchEnabled(Context context) {
diff --git a/src/com/android/settings/homepage/contextualcards/CardContentProvider.java b/src/com/android/settings/homepage/contextualcards/CardContentProvider.java
index 7ad550611c7..859c8112786 100644
--- a/src/com/android/settings/homepage/contextualcards/CardContentProvider.java
+++ b/src/com/android/settings/homepage/contextualcards/CardContentProvider.java
@@ -186,7 +186,7 @@ public int update(Uri uri, ContentValues values, String selection, String[] sele
@VisibleForTesting
void maybeEnableStrictMode() {
- if (Build.IS_DEBUGGABLE && ThreadUtils.isMainThread()) {
+ if (Build.IS_ENG && ThreadUtils.isMainThread()) {
enableStrictMode();
}
}
diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImpl.java b/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImpl.java
index 5059d90a15e..5c5cdfed2ea 100644
--- a/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImpl.java
+++ b/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImpl.java
@@ -76,7 +76,7 @@ int resetDismissedTime(long threshold) {
+ CardDatabaseHelper.CardColumns.DISMISSED_TIMESTAMP + " IS NOT NULL";
final String[] selectionArgs = {String.valueOf(threshold)};
final int rowsUpdated = database.update(CARD_TABLE, values, selection, selectionArgs);
- if (Build.IS_DEBUGGABLE) {
+ if (Build.IS_ENG) {
Log.d(TAG, "Reset " + rowsUpdated + " records of dismissed time.");
}
return rowsUpdated;
diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardsFragment.java b/src/com/android/settings/homepage/contextualcards/ContextualCardsFragment.java
index a66e066343a..5c0a33c7c05 100644
--- a/src/com/android/settings/homepage/contextualcards/ContextualCardsFragment.java
+++ b/src/com/android/settings/homepage/contextualcards/ContextualCardsFragment.java
@@ -45,7 +45,7 @@ public class ContextualCardsFragment extends InstrumentedFragment implements
FocusRecyclerView.FocusListener {
private static final String TAG = "ContextualCardsFragment";
- private static final boolean DEBUG = Build.IS_DEBUGGABLE;
+ private static final boolean DEBUG = Build.IS_ENG;
@VisibleForTesting
static boolean sRestartLoaderNeeded;
diff --git a/src/com/android/settings/homepage/contextualcards/slices/DarkThemeSlice.java b/src/com/android/settings/homepage/contextualcards/slices/DarkThemeSlice.java
index 974af9fef4e..5a909ad5655 100644
--- a/src/com/android/settings/homepage/contextualcards/slices/DarkThemeSlice.java
+++ b/src/com/android/settings/homepage/contextualcards/slices/DarkThemeSlice.java
@@ -49,7 +49,7 @@
public class DarkThemeSlice implements CustomSliceable {
private static final String TAG = "DarkThemeSlice";
- private static final boolean DEBUG = Build.IS_DEBUGGABLE;
+ private static final boolean DEBUG = Build.IS_ENG;
private static final int BATTERY_LEVEL_THRESHOLD = 50;
private static final int DELAY_TIME_EXECUTING_DARK_THEME = 200;
diff --git a/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardController.java b/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardController.java
index b348d3a536b..36b23de2402 100644
--- a/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardController.java
+++ b/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardController.java
@@ -103,7 +103,7 @@ void showFeedbackDialog(ContextualCard card) {
@VisibleForTesting
boolean isFeedbackEnabled(String email) {
- return !TextUtils.isEmpty(email) && Build.IS_DEBUGGABLE;
+ return !TextUtils.isEmpty(email) && Build.IS_ENG;
}
private String getSimpleCardName(ContextualCard card) {
diff --git a/src/com/android/settings/inputmethod/KeyboardSettings.java b/src/com/android/settings/inputmethod/KeyboardSettings.java
index 38f73438cf1..86c2c7f5b30 100644
--- a/src/com/android/settings/inputmethod/KeyboardSettings.java
+++ b/src/com/android/settings/inputmethod/KeyboardSettings.java
@@ -36,6 +36,8 @@
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.search.SearchIndexable;
+import com.android.internal.derp.hardware.LineageHardwareManager;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -47,6 +49,7 @@ public class KeyboardSettings extends DashboardFragment {
private static final String KEY_KEYBOARDS_CATEGORY = "keyboards_category";
private static final String KEY_POINTER_CATEGORY = "pointer_category";
+ private static final String KEY_TOUCH_HOVERING = "feature_touch_hovering";
@Override
public int getMetricsCategory() {
@@ -110,5 +113,15 @@ private static List buildPreferenceControllers(
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
- new BaseSearchIndexProvider(R.xml.keyboard_settings);
-}
\ No newline at end of file
+ new BaseSearchIndexProvider(R.xml.keyboard_settings) {
+ @Override
+ public List getNonIndexableKeys(Context context) {
+ List keys = super.getNonIndexableKeys(context);
+ LineageHardwareManager hardware = LineageHardwareManager.getInstance(context);
+ if (!hardware.isSupported(LineageHardwareManager.FEATURE_TOUCH_HOVERING)) {
+ keys.add(KEY_TOUCH_HOVERING);
+ }
+ return keys;
+ }
+ };
+}
diff --git a/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java b/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java
index 5ba1c848b89..1bd665698de 100644
--- a/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java
+++ b/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java
@@ -70,6 +70,7 @@ public final class PhysicalKeyboardFragment extends SettingsPreferenceFragment
private static final String KEYBOARD_OPTIONS_CATEGORY = "keyboard_options_category";
private static final String KEYBOARD_A11Y_CATEGORY = "keyboard_a11y_category";
+ private static final String KEYBOARD_EXTRAS_CATEGORY = "keyboard_extras_category";
private static final String SHOW_VIRTUAL_KEYBOARD_SWITCH = "show_virtual_keyboard_switch";
private static final String ACCESSIBILITY_BOUNCE_KEYS = "accessibility_bounce_keys";
private static final String ACCESSIBILITY_SLOW_KEYS = "accessibility_slow_keys";
@@ -99,6 +100,8 @@ public final class PhysicalKeyboardFragment extends SettingsPreferenceFragment
private PreferenceCategory mKeyboardAssistanceCategory;
@Nullable
private PreferenceCategory mKeyboardA11yCategory = null;
+ @NonNull
+ private PreferenceCategory mKeyboardExtrasCategory = null;
@Nullable
private TwoStatePreference mShowVirtualKeyboardSwitch = null;
@Nullable
@@ -129,6 +132,8 @@ public void onCreatePreferences(Bundle bundle, String s) {
mImm = Preconditions.checkNotNull(activity.getSystemService(InputMethodManager.class));
mKeyboardAssistanceCategory = Preconditions.checkNotNull(
findPreference(KEYBOARD_OPTIONS_CATEGORY));
+ mKeyboardExtrasCategory = Preconditions.checkNotNull(
+ findPreference(KEYBOARD_EXTRAS_CATEGORY));
mShowVirtualKeyboardSwitch = Objects.requireNonNull(
mKeyboardAssistanceCategory.findPreference(SHOW_VIRTUAL_KEYBOARD_SWITCH));
@@ -311,6 +316,8 @@ private void updateHardKeyboards(@NonNull List newHardKe
}
mKeyboardAssistanceCategory.setOrder(1);
preferenceScreen.addPreference(mKeyboardAssistanceCategory);
+ mKeyboardExtrasCategory.setOrder(99);
+ preferenceScreen.addPreference(mKeyboardExtrasCategory);
if (mSupportsFirmwareUpdate) {
mFeatureProvider.addFirmwareUpdateCategory(getPrefContext(), preferenceScreen);
}
diff --git a/src/com/android/settings/location/AgpsPreferenceController.java b/src/com/android/settings/location/AgpsPreferenceController.java
new file mode 100644
index 00000000000..0b0bb73a6f0
--- /dev/null
+++ b/src/com/android/settings/location/AgpsPreferenceController.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.location;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.provider.Settings;
+
+import androidx.preference.SwitchPreferenceCompat;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+public class AgpsPreferenceController extends LocationBasePreferenceController {
+ private static final String KEY_ASSISTED_GPS = "assisted_gps";
+
+ private SwitchPreferenceCompat mAgpsPreference;
+
+ public AgpsPreferenceController(Context context, String key) {
+ super(context, key);
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY_ASSISTED_GPS;
+ }
+
+ @AvailabilityStatus
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mAgpsPreference = (SwitchPreferenceCompat) screen.findPreference(KEY_ASSISTED_GPS);
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ if (mAgpsPreference != null) {
+ mAgpsPreference.setChecked(Settings.Global.getInt(
+ mContext.getContentResolver(), Settings.Global.ASSISTED_GPS_ENABLED, 1) == 1);
+ }
+ }
+
+ @Override
+ public boolean handlePreferenceTreeClick(Preference preference) {
+ if (KEY_ASSISTED_GPS.equals(preference.getKey())) {
+ final ContentResolver cr = mContext.getContentResolver();
+ final boolean switchState = mAgpsPreference.isChecked();
+ Settings.Global.putInt(cr, Settings.Global.ASSISTED_GPS_ENABLED,
+ switchState ? 1 : 0);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onLocationModeChanged(int mode, boolean restricted) {}
+}
diff --git a/src/com/android/settings/location/LocationSettings.java b/src/com/android/settings/location/LocationSettings.java
index 87e8817808f..bcba10f62e6 100644
--- a/src/com/android/settings/location/LocationSettings.java
+++ b/src/com/android/settings/location/LocationSettings.java
@@ -120,6 +120,7 @@ public void onAttach(Context context) {
use(LocationForWorkPreferenceController.class).init(this);
use(LocationSettingsFooterPreferenceController.class).init(this);
use(LocationForPrivateProfilePreferenceController.class).init(this);
+ use(AgpsPreferenceController.class).init(this);
}
@Override
diff --git a/src/com/android/settings/media/AppVolumeSlice.java b/src/com/android/settings/media/AppVolumeSlice.java
new file mode 100644
index 00000000000..e653ffc0fb6
--- /dev/null
+++ b/src/com/android/settings/media/AppVolumeSlice.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2022 Project Kaleidoscope
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.media;
+
+import static android.app.slice.Slice.EXTRA_RANGE_VALUE;
+import static android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;
+
+import static com.android.settings.slices.CustomSliceRegistry.APP_VOLUME_SLICE_URI;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.media.AudioManager;
+import android.media.AppVolume;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.core.graphics.drawable.IconCompat;
+import androidx.slice.Slice;
+import androidx.slice.builders.ListBuilder;
+import androidx.slice.builders.ListBuilder.InputRangeBuilder;
+import androidx.slice.builders.SliceAction;
+
+import com.android.settings.R;
+import com.android.settings.Utils;
+import com.android.settings.slices.CustomSliceable;
+import com.android.settings.slices.SliceBroadcastReceiver;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class AppVolumeSlice implements CustomSliceable {
+
+ private static final String TAG = "AppVolumeSlice";
+ private static final String PACKAGE_NAME = "package_name";
+ private static final String ACTION_LAUNCH_DIALOG = "action_launch_dialog";
+
+ private final Context mContext;
+
+ private final AudioManager mAudioManager;
+
+ public AppVolumeSlice(Context context) {
+ mContext = context;
+ mAudioManager = context.getSystemService(AudioManager.class);
+ }
+
+ @Override
+ public void onNotifyChange(Intent intent) {
+ final int newPosition = intent.getIntExtra(EXTRA_RANGE_VALUE, -1);
+ final String packageName = intent.getStringExtra(PACKAGE_NAME);
+ if (!TextUtils.isEmpty(packageName)) {
+ mAudioManager.setAppVolume(packageName, newPosition / 100.0f);
+ return;
+ }
+ }
+
+ @Override
+ public Slice getSlice() {
+ final ListBuilder listBuilder = new ListBuilder(mContext, getUri(), ListBuilder.INFINITY)
+ .setAccentColor(COLOR_NOT_TINTED);
+
+ // Only displaying active tracks
+ final List appVols = new ArrayList<>();
+ for (AppVolume vol : mAudioManager.listAppVolumes()) {
+ if (vol.isActive()) {
+ appVols.add(vol);
+ }
+ }
+ if (appVols.isEmpty()) {
+ Log.d(TAG, "No active tracks");
+ return listBuilder.build();
+ }
+
+ for (AppVolume vol : appVols) {
+ final CharSequence appName = Utils.getApplicationLabel(
+ mContext, vol.getPackageName());
+ IconCompat icon = getApplicationIcon(vol.getPackageName());
+ final SliceAction primarySliceAction = SliceAction.create(
+ getBroadcastIntent(mContext), icon, ListBuilder.ICON_IMAGE, appName);
+ listBuilder.addInputRange(new InputRangeBuilder()
+ .setTitleItem(icon, ListBuilder.ICON_IMAGE)
+ .setTitle(appName)
+ .setInputAction(getSliderInputAction(vol.getPackageName()))
+ .setMax(100)
+ .setValue((int)(vol.getVolume() * 100))
+ .setPrimaryAction(primarySliceAction));
+ }
+ return listBuilder.build();
+ }
+
+ private IconCompat getApplicationIcon(String packageName) {
+ PackageManager pm = mContext.getPackageManager();
+ try {
+ ApplicationInfo ai = pm.getApplicationInfo(packageName, 0);
+ Resources resources = pm.getResourcesForApplication(ai);
+ IconCompat icon = IconCompat.createWithResource(resources, packageName, ai.icon);
+ return icon;
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Failed to get icon of " + packageName, e);
+ }
+
+ final Bitmap bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+ return IconCompat.createWithBitmap(bitmap);
+ }
+
+
+ private PendingIntent getSliderInputAction(String packageName) {
+ final int requestCode = packageName.hashCode();
+ final Intent intent = new Intent(getUri().toString())
+ .setData(getUri())
+ .putExtra(PACKAGE_NAME, packageName)
+ .setClass(mContext, SliceBroadcastReceiver.class);
+ return PendingIntent.getBroadcast(mContext, requestCode, intent,
+ PendingIntent.FLAG_MUTABLE);
+ }
+
+ @Override
+ public Uri getUri() {
+ return APP_VOLUME_SLICE_URI;
+ }
+
+ @Override
+ public Intent getIntent() {
+ return null;
+ }
+
+ @Override
+ public int getSliceHighlightMenuRes() {
+ return R.string.menu_key_sound;
+ }
+}
diff --git a/src/com/android/settings/network/PrivateDnsModeDialogPreference.java b/src/com/android/settings/network/PrivateDnsModeDialogPreference.java
index 3b99777720a..a5317ff4909 100644
--- a/src/com/android/settings/network/PrivateDnsModeDialogPreference.java
+++ b/src/com/android/settings/network/PrivateDnsModeDialogPreference.java
@@ -72,9 +72,13 @@ public class PrivateDnsModeDialogPreference extends CustomDialogPreferenceCompat
// DNS_MODE -> RadioButton id
private static final Map PRIVATE_DNS_MAP;
+ // Only used in Settings, update on additions to ConnectivitySettingsUtils
+ private static final int PRIVATE_DNS_MODE_CLOUDFLARE = 4;
+
static {
PRIVATE_DNS_MAP = new HashMap<>();
PRIVATE_DNS_MAP.put(PRIVATE_DNS_MODE_OFF, R.id.private_dns_mode_off);
+ PRIVATE_DNS_MAP.put(PRIVATE_DNS_MODE_CLOUDFLARE, R.id.private_dns_mode_cloudflare);
PRIVATE_DNS_MAP.put(PRIVATE_DNS_MODE_OPPORTUNISTIC, R.id.private_dns_mode_opportunistic);
PRIVATE_DNS_MAP.put(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME, R.id.private_dns_mode_provider);
}
@@ -144,6 +148,15 @@ protected void onBindDialogView(View view) {
final ContentResolver contentResolver = context.getContentResolver();
mMode = ConnectivitySettingsManager.getPrivateDnsMode(context);
+ if (mMode == PRIVATE_DNS_MODE_PROVIDER_HOSTNAME) {
+ final String privateDnsHostname =
+ ConnectivitySettingsManager.getPrivateDnsHostname(context);
+ final String cloudflareHostname =
+ context.getString(R.string.private_dns_hostname_cloudflare);
+ if (privateDnsHostname.equals(cloudflareHostname)) {
+ mMode = PRIVATE_DNS_MODE_CLOUDFLARE;
+ }
+ }
mEditText = view.findViewById(R.id.private_dns_mode_provider_hostname);
mEditText.addTextChangedListener(this);
@@ -156,6 +169,9 @@ protected void onBindDialogView(View view) {
// Initial radio button text
final RadioButton offRadioButton = view.findViewById(R.id.private_dns_mode_off);
offRadioButton.setText(com.android.settingslib.R.string.private_dns_mode_off);
+ final RadioButton cloudflareRadioButton =
+ view.findViewById(R.id.private_dns_mode_cloudflare);
+ cloudflareRadioButton.setText(R.string.private_dns_mode_cloudflare);
final RadioButton opportunisticRadioButton =
view.findViewById(R.id.private_dns_mode_opportunistic);
opportunisticRadioButton.setText(
@@ -182,15 +198,21 @@ protected void onBindDialogView(View view) {
public void onClick(DialogInterface dialog, int which) {
if (which == DialogInterface.BUTTON_POSITIVE) {
final Context context = getContext();
+ int modeToSet = mMode;
if (mMode == PRIVATE_DNS_MODE_PROVIDER_HOSTNAME) {
// Only clickable if hostname is valid, so we could save it safely
ConnectivitySettingsManager.setPrivateDnsHostname(context,
mEditText.getText().toString());
+ } else if (mMode == PRIVATE_DNS_MODE_CLOUDFLARE) {
+ final String cloudflareHostname =
+ context.getString(R.string.private_dns_hostname_cloudflare);
+ ConnectivitySettingsManager.setPrivateDnsHostname(context, cloudflareHostname);
+ modeToSet = PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
}
FeatureFactory.getFeatureFactory().getMetricsFeatureProvider().action(context,
- SettingsEnums.ACTION_PRIVATE_DNS_MODE, mMode);
- ConnectivitySettingsManager.setPrivateDnsMode(context, mMode);
+ SettingsEnums.ACTION_PRIVATE_DNS_MODE, modeToSet);
+ ConnectivitySettingsManager.setPrivateDnsMode(context, modeToSet);
}
}
@@ -198,6 +220,8 @@ public void onClick(DialogInterface dialog, int which) {
public void onCheckedChanged(RadioGroup group, int checkedId) {
if (checkedId == R.id.private_dns_mode_off) {
mMode = PRIVATE_DNS_MODE_OFF;
+ } else if (checkedId == R.id.private_dns_mode_cloudflare) {
+ mMode = PRIVATE_DNS_MODE_CLOUDFLARE;
} else if (checkedId == R.id.private_dns_mode_opportunistic) {
mMode = PRIVATE_DNS_MODE_OPPORTUNISTIC;
} else if (checkedId == R.id.private_dns_mode_provider) {
diff --git a/src/com/android/settings/network/PrivateDnsPreferenceController.java b/src/com/android/settings/network/PrivateDnsPreferenceController.java
index 21e4926f490..17aaaeb869a 100644
--- a/src/com/android/settings/network/PrivateDnsPreferenceController.java
+++ b/src/com/android/settings/network/PrivateDnsPreferenceController.java
@@ -65,6 +65,9 @@ public class PrivateDnsPreferenceController extends BasePreferenceController
Settings.Global.getUriFor(PRIVATE_DNS_SPECIFIER),
};
+ // Only used in Settings, update on additions to ConnectivitySettingsUtils
+ private static final int PRIVATE_DNS_MODE_CLOUDFLARE = 4;
+
private final Handler mHandler;
private final ContentObserver mSettingsObserver;
private final ConnectivityManager mConnectivityManager;
@@ -129,15 +132,24 @@ public CharSequence getSummary() {
switch (mode) {
case PRIVATE_DNS_MODE_OFF:
return res.getString(com.android.settingslib.R.string.private_dns_mode_off);
+ case PRIVATE_DNS_MODE_CLOUDFLARE:
case PRIVATE_DNS_MODE_OPPORTUNISTIC:
return dnsesResolved ? res.getString(R.string.private_dns_mode_on)
: res.getString(
com.android.settingslib.R.string.private_dns_mode_opportunistic);
case PRIVATE_DNS_MODE_PROVIDER_HOSTNAME:
- return dnsesResolved
- ? PrivateDnsModeDialogPreference.getHostnameFromSettings(cr)
- : res.getString(
- com.android.settingslib.R.string.private_dns_mode_provider_failure);
+ if (!dnsesResolved) {
+ return res.getString(
+ com.android.settingslib.R.string.private_dns_mode_provider_failure);
+ }
+ final String privateDnsHostname =
+ ConnectivitySettingsManager.getPrivateDnsHostname(mContext);
+ final String cloudflareHostname =
+ res.getString(R.string.private_dns_hostname_cloudflare);
+ if (privateDnsHostname.equals(cloudflareHostname)) {
+ return res.getString(R.string.private_dns_mode_cloudflare);
+ }
+ return PrivateDnsModeDialogPreference.getHostnameFromSettings(cr);
}
return "";
}
diff --git a/src/com/android/settings/notification/ChargingVibroPreferenceController.java b/src/com/android/settings/notification/ChargingVibroPreferenceController.java
new file mode 100644
index 00000000000..b1f9f165ce6
--- /dev/null
+++ b/src/com/android/settings/notification/ChargingVibroPreferenceController.java
@@ -0,0 +1,39 @@
+/*
+ * 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.settings.notification;
+
+import static com.android.settings.notification.SettingPref.TYPE_GLOBAL;
+
+import android.content.Context;
+
+import android.provider.Settings.Global;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+public class ChargingVibroPreferenceController extends SettingPrefController {
+
+ private static final String KEY_CHARGING_VIBRO = "charging_vibro";
+
+ public ChargingVibroPreferenceController(Context context, SettingsPreferenceFragment parent,
+ Lifecycle lifecycle) {
+ super(context, parent, lifecycle);
+ mPreference = new SettingPref(
+ TYPE_GLOBAL, KEY_CHARGING_VIBRO, Global.CHARGING_VIBRATION_ENABLED, DEFAULT_ON);
+
+ }
+
+}
diff --git a/src/com/android/settings/notification/ConfigureNotificationSettings.java b/src/com/android/settings/notification/ConfigureNotificationSettings.java
index 19222612f3a..c0cd5f7cdc1 100644
--- a/src/com/android/settings/notification/ConfigureNotificationSettings.java
+++ b/src/com/android/settings/notification/ConfigureNotificationSettings.java
@@ -23,15 +23,23 @@
import android.app.Application;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.database.ContentObserver;
import android.os.Bundle;
+import android.os.Looper;
+import android.os.Handler;
import android.os.UserHandle;
+import android.provider.Settings;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.Fragment;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.Preference.OnPreferenceChangeListener;
+import androidx.preference.SwitchPreferenceCompat;
import com.android.settings.R;
import com.android.settings.RingtonePreference;
@@ -46,7 +54,7 @@
@SearchIndexable
public class ConfigureNotificationSettings extends DashboardFragment implements
- OnActivityResultListener {
+ OnActivityResultListener, OnPreferenceChangeListener {
private static final String TAG = "ConfigNotiSettings";
@VisibleForTesting
@@ -57,11 +65,17 @@ public class ConfigureNotificationSettings extends DashboardFragment implements
private static final int REQUEST_CODE = 200;
private static final String SELECTED_PREFERENCE_KEY = "selected_preference";
private static final String KEY_ADVANCED_CATEGORY = "configure_notifications_advanced";
+ private static final String KEY_HEADS_UP = "heads_up_notifications_enabled";
private RingtonePreference mRequestPreference;
private NotificationAssistantPreferenceController mNotificationAssistantPreferenceController;
+ private SwitchPreferenceCompat mHeadsUp;
+
+ private final HeadsUpObserver mHeadsUpObserver = new HeadsUpObserver();
+ private boolean mHeadsUpSelfChange = false;
+
@Override
public int getMetricsCategory() {
return SettingsEnums.CONFIGURE_NOTIFICATION;
@@ -81,6 +95,21 @@ public void onCreate(Bundle icicle) {
replaceEnterpriseStringSummary("lock_screen_work_redact",
WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_SUMMARY,
R.string.lock_screen_notifs_redact_work_summary);
+
+ PreferenceScreen prefScreen = getPreferenceScreen();
+ final ContentResolver resolver = getActivity().getContentResolver();
+
+ mHeadsUp = (SwitchPreferenceCompat) findPreference(KEY_HEADS_UP);
+ boolean enabled = Settings.Global.getInt(resolver, KEY_HEADS_UP, 1) == 1;
+ mHeadsUp.setChecked(enabled);
+ mHeadsUp.setOnPreferenceChangeListener(this);
+ mHeadsUpObserver.observe();
+ }
+
+ @Override
+ public void onDestroy() {
+ mHeadsUpObserver.stop();
+ super.onDestroy();
}
@Override
@@ -159,6 +188,46 @@ public void onSaveInstanceState(Bundle outState) {
}
}
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ ContentResolver resolver = getActivity().getContentResolver();
+ if (preference == mHeadsUp) {
+ boolean enabled = (Boolean) newValue;
+ mHeadsUpSelfChange = true;
+ Settings.Global.putInt(resolver, KEY_HEADS_UP, enabled ? 1 : 0);
+ return true;
+ }
+ return false;
+ }
+
+ private class HeadsUpObserver extends ContentObserver {
+ HeadsUpObserver() {
+ super(new Handler(Looper.getMainLooper()));
+ }
+
+ void observe() {
+ getActivity().getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(KEY_HEADS_UP),
+ false, this, UserHandle.USER_ALL);
+ }
+
+ void stop() {
+ getActivity().getContentResolver().unregisterContentObserver(this);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ if (mHeadsUp == null || selfChange) return;
+ if (mHeadsUpSelfChange) {
+ mHeadsUpSelfChange = false;
+ return;
+ }
+ final boolean enabled = Settings.Global.getInt(
+ getActivity().getContentResolver(), KEY_HEADS_UP, 1) == 1;
+ mHeadsUp.setChecked(enabled);
+ }
+ }
+
/**
* For Search.
*/
diff --git a/src/com/android/settings/notification/IncreasingRingPreferenceController.java b/src/com/android/settings/notification/IncreasingRingPreferenceController.java
new file mode 100644
index 00000000000..2152451f8b3
--- /dev/null
+++ b/src/com/android/settings/notification/IncreasingRingPreferenceController.java
@@ -0,0 +1,35 @@
+/*
+ * 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.settings.notification;
+
+import android.content.Context;
+
+import com.android.settings.Utils;
+import com.android.settings.core.BasePreferenceController;
+
+public class IncreasingRingPreferenceController extends BasePreferenceController {
+ private static final String KEY_INCREASING_RING = "increasing_ring";
+
+ public IncreasingRingPreferenceController(Context context) {
+ super(context, KEY_INCREASING_RING);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return Utils.isVoiceCapable(mContext) ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+ }
+}
diff --git a/src/com/android/settings/notification/IncreasingRingVolumePreference.java b/src/com/android/settings/notification/IncreasingRingVolumePreference.java
new file mode 100644
index 00000000000..0b2dd6cfe61
--- /dev/null
+++ b/src/com/android/settings/notification/IncreasingRingVolumePreference.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2014 CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.provider.Settings;
+import android.text.format.Formatter;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import androidx.core.content.res.TypedArrayUtils;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.settings.R;
+
+public class IncreasingRingVolumePreference extends Preference
+ implements Handler.Callback, SeekBar.OnSeekBarChangeListener {
+ private static final String TAG = "IncreasingRingMinVolumePreference";
+
+ public interface Callback {
+ void onSampleStarting(IncreasingRingVolumePreference pref);
+ }
+
+ private SeekBar mStartVolumeSeekBar;
+ private SeekBar mRampUpTimeSeekBar;
+ private TextView mRampUpTimeValue;
+
+ private Ringtone mRingtone;
+ private Callback mCallback;
+
+ private Handler mHandler;
+ private final Handler mMainHandler = new Handler(this);
+
+ private static final int MSG_START_SAMPLE = 1;
+ private static final int MSG_STOP_SAMPLE = 2;
+ private static final int MSG_INIT_SAMPLE = 3;
+ private static final int MSG_SET_VOLUME = 4;
+ private static final int CHECK_RINGTONE_PLAYBACK_DELAY_MS = 1000;
+
+ public IncreasingRingVolumePreference(Context context) {
+ this(context, null);
+ }
+
+ public IncreasingRingVolumePreference(Context context, AttributeSet attrs) {
+ this(context, attrs, TypedArrayUtils.getAttr(context,
+ androidx.preference.R.attr.preferenceStyle,
+ android.R.attr.preferenceStyle));
+ }
+
+ public IncreasingRingVolumePreference(Context context, AttributeSet attrs,
+ int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public IncreasingRingVolumePreference(Context context, AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ setLayoutResource(R.layout.preference_increasing_ring);
+ initHandler();
+ }
+
+ public void setCallback(Callback callback) {
+ mCallback = callback;
+ }
+
+ public void onActivityResume() {
+ initHandler();
+ }
+
+ public void onActivityStop() {
+ if (mHandler != null) {
+ postStopSample();
+ mHandler.getLooper().quitSafely();
+ mHandler = null;
+ }
+ }
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_START_SAMPLE:
+ onStartSample((float) msg.arg1 / 1000F);
+ break;
+ case MSG_STOP_SAMPLE:
+ onStopSample();
+ break;
+ case MSG_INIT_SAMPLE:
+ onInitSample();
+ break;
+ case MSG_SET_VOLUME:
+ onSetVolume((float) msg.arg1 / 1000F);
+ break;
+ }
+ return true;
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+
+ initHandler();
+
+ final SeekBar seekBar = (SeekBar) holder.findViewById(R.id.start_volume);
+ if (seekBar == mStartVolumeSeekBar) return;
+
+ mStartVolumeSeekBar = seekBar;
+ mRampUpTimeSeekBar = (SeekBar) holder.findViewById(R.id.ramp_up_time);
+ mRampUpTimeValue = (TextView) holder.findViewById(R.id.ramp_up_time_value);
+
+ final ContentResolver cr = getContext().getContentResolver();
+ float startVolume = Settings.System.getFloat(cr,
+ Settings.System.INCREASING_RING_START_VOLUME, 0.1f);
+ int rampUpTime = Settings.System.getInt(cr,
+ Settings.System.INCREASING_RING_RAMP_UP_TIME, 10);
+
+ mStartVolumeSeekBar.setProgress(Math.round(startVolume * 1000F));
+ mStartVolumeSeekBar.setOnSeekBarChangeListener(this);
+ mRampUpTimeSeekBar.setOnSeekBarChangeListener(this);
+ mRampUpTimeSeekBar.setProgress((rampUpTime / 5) - 1);
+ mRampUpTimeValue.setText(
+ Formatter.formatShortElapsedTime(getContext(), rampUpTime * 1000));
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ if (seekBar == mStartVolumeSeekBar) {
+ postStartSample(seekBar.getProgress());
+ }
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch) {
+ ContentResolver cr = getContext().getContentResolver();
+ if (fromTouch && seekBar == mStartVolumeSeekBar) {
+ Settings.System.putFloat(cr,
+ Settings.System.INCREASING_RING_START_VOLUME, (float) progress / 1000F);
+ } else if (seekBar == mRampUpTimeSeekBar) {
+ int seconds = (progress + 1) * 5;
+ mRampUpTimeValue.setText(
+ Formatter.formatShortElapsedTime(getContext(), seconds * 1000));
+ if (fromTouch) {
+ Settings.System.putInt(cr,
+ Settings.System.INCREASING_RING_RAMP_UP_TIME, seconds);
+ }
+ }
+ }
+
+ private void initHandler() {
+ if (mHandler != null) return;
+
+ HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler");
+ thread.start();
+
+ mHandler = new Handler(thread.getLooper(), this);
+ mHandler.sendEmptyMessage(MSG_INIT_SAMPLE);
+ }
+
+ private void onInitSample() {
+ mRingtone = RingtoneManager.getRingtone(getContext(),
+ Settings.System.DEFAULT_RINGTONE_URI);
+ if (mRingtone != null) {
+ mRingtone.setStreamType(AudioManager.STREAM_RING);
+ mRingtone.setAudioAttributes(
+ new AudioAttributes.Builder(mRingtone.getAudioAttributes())
+ .setFlags(AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY |
+ AudioAttributes.FLAG_BYPASS_MUTE)
+ .build());
+ }
+ }
+
+ private void postStartSample(int progress) {
+ boolean playing = isSamplePlaying();
+ mHandler.removeMessages(MSG_START_SAMPLE);
+ mHandler.removeMessages(MSG_SET_VOLUME);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_SAMPLE, progress, 0),
+ playing ? CHECK_RINGTONE_PLAYBACK_DELAY_MS : 0);
+ if (playing) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_VOLUME, progress, 0));
+ }
+ }
+
+ private void onStartSample(float volume) {
+ if (mRingtone == null) {
+ return;
+ }
+ if (!isSamplePlaying()) {
+ if (mCallback != null) {
+ mCallback.onSampleStarting(this);
+ }
+ try {
+ mRingtone.play();
+ } catch (Throwable e) {
+ Log.w(TAG, "Error playing ringtone", e);
+ }
+ }
+ mRingtone.setVolume(volume);
+ }
+
+ private void onSetVolume(float volume) {
+ if (mRingtone != null) {
+ mRingtone.setVolume(volume);
+ }
+ }
+
+ private boolean isSamplePlaying() {
+ return mRingtone != null && mRingtone.isPlaying();
+ }
+
+ public void stopSample() {
+ if (mHandler != null) {
+ postStopSample();
+ }
+ }
+
+ private void postStopSample() {
+ // remove pending delayed start messages
+ mHandler.removeMessages(MSG_START_SAMPLE);
+ mHandler.removeMessages(MSG_STOP_SAMPLE);
+ mHandler.sendEmptyMessage(MSG_STOP_SAMPLE);
+ }
+
+ private void onStopSample() {
+ if (mRingtone != null) {
+ mRingtone.stop();
+ }
+ }
+}
diff --git a/src/com/android/settings/notification/IncreasingRingVolumePreferenceController.java b/src/com/android/settings/notification/IncreasingRingVolumePreferenceController.java
new file mode 100644
index 00000000000..78da2485a27
--- /dev/null
+++ b/src/com/android/settings/notification/IncreasingRingVolumePreferenceController.java
@@ -0,0 +1,109 @@
+/*
+ * 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.settings.notification;
+
+import android.content.Context;
+
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.Utils;
+import com.android.settings.slices.SliceData;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnResume;
+import com.android.settingslib.core.lifecycle.events.OnStop;
+
+/**
+ * Base class for preference controller that handles VolumeSeekBarPreference
+ */
+public class IncreasingRingVolumePreferenceController
+ extends AdjustVolumeRestrictedPreferenceController
+ implements LifecycleObserver, OnResume, OnStop {
+
+ private static final String KEY_INCREASING_RING_VOLUME = "increasing_ring_volume";
+
+ private IncreasingRingVolumePreference mPreference;
+ private IncreasingRingVolumePreference.Callback mCallback;
+ private AudioHelper mHelper;
+
+ public IncreasingRingVolumePreferenceController(Context context) {
+ super(context, KEY_INCREASING_RING_VOLUME);
+ mHelper = new AudioHelper(context);
+ }
+
+ public void setCallback(IncreasingRingVolumePreference.Callback callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ if (isAvailable()) {
+ mPreference = screen.findPreference(getPreferenceKey());
+ mPreference.setCallback(mCallback);
+ }
+ }
+
+ @Override
+ public void onResume() {
+ if (mPreference != null) {
+ mPreference.onActivityResume();
+ }
+ }
+
+ @Override
+ public void onStop() {
+ if (mPreference != null) {
+ mPreference.onActivityStop();
+ }
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY_INCREASING_RING_VOLUME;
+ }
+
+ @Override
+ public int getSliderPosition() {
+ return 0;
+ }
+
+ @Override
+ public boolean setSliderPosition(int position) {
+ return false;
+ }
+
+ @Override
+ public int getMin() {
+ return 0;
+ }
+
+ @Override
+ public int getMax() {
+ return 0;
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return Utils.isVoiceCapable(mContext) && !mHelper.isSingleVolume() ?
+ AVAILABLE : UNSUPPORTED_ON_DEVICE;
+ }
+
+ @Override
+ public int getSliceType() {
+ return SliceData.SliceType.INTENT;
+ }
+}
diff --git a/src/com/android/settings/notification/NotificationBackend.java b/src/com/android/settings/notification/NotificationBackend.java
index 0661de7a5e7..0d9a5f48213 100644
--- a/src/com/android/settings/notification/NotificationBackend.java
+++ b/src/com/android/settings/notification/NotificationBackend.java
@@ -93,6 +93,7 @@ public AppRow loadAppRow(Context context, PackageManager pm, ApplicationInfo app
row.userId = UserHandle.getUserId(row.uid);
row.blockedChannelCount = getBlockedChannelCount(row.pkg, row.uid);
row.channelCount = getChannelCount(row.pkg, row.uid);
+ row.soundTimeout = getNotificationSoundTimeout(row.pkg, row.uid);
recordAggregatedUsageEvents(context, row);
return row;
}
@@ -664,6 +665,25 @@ void setNm(INotificationManager inm) {
sINM = inm;
}
+ public long getNotificationSoundTimeout(String pkg, int uid) {
+ try {
+ return sINM.getNotificationSoundTimeout(pkg, uid);
+ } catch (Exception e) {
+ Log.w(TAG, "Error calling NoMan", e);
+ return 0;
+ }
+ }
+
+ public boolean setNotificationSoundTimeout(String pkg, int uid, long timeout) {
+ try {
+ sINM.setNotificationSoundTimeout(pkg, uid, timeout);
+ return true;
+ } catch (Exception e) {
+ Log.w(TAG, "Error calling NoMan", e);
+ return false;
+ }
+ }
+
/**
* NotificationsSentState contains how often an app sends notifications and how recently it sent
* one.
@@ -697,6 +717,7 @@ public static class AppRow extends Row {
public int userId;
public int blockedChannelCount;
public int channelCount;
+ public long soundTimeout;
public Map sentByChannel;
public NotificationsSentState sentByApp;
}
diff --git a/src/com/android/settings/notification/NotificationVibrationPatternPreferenceController.java b/src/com/android/settings/notification/NotificationVibrationPatternPreferenceController.java
new file mode 100644
index 00000000000..8f60872043b
--- /dev/null
+++ b/src/com/android/settings/notification/NotificationVibrationPatternPreferenceController.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2022 Yet Another AOSP Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.provider.Settings;
+
+/**
+ * This class allows choosing a vibration pattern for notifications
+ */
+public class NotificationVibrationPatternPreferenceController extends VibrationPatternPreferenceController {
+
+ private static final String KEY_VIB_PATTERN = "notification_vibration_pattern";
+ private static final String KEY_CUSTOM_VIB_CATEGORY = "custom_notification_vibration_pattern";
+ private static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250};
+ private static final int VIBRATE_PATTERN_MAXLEN = 8 * 2 + 1; // up to eight bumps
+
+ private final long[] mDefaultPattern;
+ private final int[] mDefaultPatternAmp;
+
+ public NotificationVibrationPatternPreferenceController(Context context) {
+ super(context);
+ mDefaultPattern = getLongArray(context.getResources(),
+ com.android.internal.R.array.config_defaultNotificationVibePattern,
+ VIBRATE_PATTERN_MAXLEN,
+ DEFAULT_VIBRATE_PATTERN);
+
+ // making a full amp array according to what we have in config
+ int[] ampArr = new int[mDefaultPattern.length];
+ for (int i = 0; i < mDefaultPattern.length; i++) {
+ if (i % 2 == 0) ampArr[i] = 0;
+ else ampArr[i] = 255;
+ }
+ mDefaultPatternAmp = ampArr;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return mVibrator.hasVibrator();
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY_VIB_PATTERN;
+ }
+
+ @Override
+ protected String getCustomPreferenceKey() {
+ return KEY_CUSTOM_VIB_CATEGORY;
+ }
+
+ @Override
+ protected String getSettingsKey() {
+ return Settings.System.NOTIFICATION_VIBRATION_PATTERN;
+ }
+
+ @Override
+ protected long[] getDefaultPattern() {
+ return mDefaultPattern;
+ }
+
+ @Override
+ protected int[] getDefaultPatternAmp() {
+ return mDefaultPatternAmp;
+ }
+
+ private static long[] getLongArray(Resources resources, int resId, int maxLength, long[] def) {
+ int[] ar = resources.getIntArray(resId);
+ if (ar == null) return def;
+ final int len = ar.length > maxLength ? maxLength : ar.length;
+ long[] out = new long[len];
+ for (int i = 0; i < len; i++) out[i] = ar[i];
+ return out;
+ }
+}
diff --git a/src/com/android/settings/notification/PhoneRingtone2PreferenceController.java b/src/com/android/settings/notification/PhoneRingtone2PreferenceController.java
new file mode 100644
index 00000000000..2acda015e5d
--- /dev/null
+++ b/src/com/android/settings/notification/PhoneRingtone2PreferenceController.java
@@ -0,0 +1,69 @@
+/*
+ * 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.settings.notification;
+
+import android.content.Context;
+import android.media.RingtoneManager;
+import android.telephony.TelephonyManager;
+
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.DefaultRingtonePreference;
+import com.android.settings.Utils;
+
+public class PhoneRingtone2PreferenceController extends RingtonePreferenceControllerBase {
+
+ private static final int SLOT_ID = 1;
+ private static final String KEY_PHONE_RINGTONE2 = "ringtone2";
+
+ public PhoneRingtone2PreferenceController(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+
+ DefaultRingtonePreference ringtonePreference =
+ (DefaultRingtonePreference) screen.findPreference(KEY_PHONE_RINGTONE2);
+ ringtonePreference.setSlotId(SLOT_ID);
+ ringtonePreference.setEnabled(hasCard());
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY_PHONE_RINGTONE2;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ TelephonyManager telephonyManager =
+ (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ return Utils.isVoiceCapable(mContext) && telephonyManager.isMultiSimEnabled();
+ }
+
+ @Override
+ public int getRingtoneType() {
+ return RingtoneManager.TYPE_RINGTONE;
+ }
+
+ private boolean hasCard() {
+ TelephonyManager telephonyManager =
+ (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ return telephonyManager.hasIccCard(SLOT_ID);
+ }
+}
diff --git a/src/com/android/settings/notification/PhoneRingtonePreferenceController.java b/src/com/android/settings/notification/PhoneRingtonePreferenceController.java
index 7bd78feeb3c..1c70e1acf33 100644
--- a/src/com/android/settings/notification/PhoneRingtonePreferenceController.java
+++ b/src/com/android/settings/notification/PhoneRingtonePreferenceController.java
@@ -19,16 +19,39 @@
import android.content.Context;
import android.media.RingtoneManager;
+import android.telephony.TelephonyManager;
+
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.DefaultRingtonePreference;
+import com.android.settings.R;
+
import com.android.settings.Utils;
public class PhoneRingtonePreferenceController extends RingtonePreferenceControllerBase {
+ private static final int SLOT_ID = 0;
private static final String KEY_PHONE_RINGTONE = "phone_ringtone";
public PhoneRingtonePreferenceController(Context context) {
super(context);
}
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+
+ TelephonyManager telephonyManager =
+ (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ if (telephonyManager.isMultiSimEnabled()) {
+ // For Multi SIM device, shoud show "Phone ringtone - SIM 1" for slot1 ringtone setting.
+ DefaultRingtonePreference ringtonePreference =
+ (DefaultRingtonePreference) screen.findPreference(KEY_PHONE_RINGTONE);
+ ringtonePreference.setTitle(mContext.getString(R.string.ringtone1_title));
+ ringtonePreference.setEnabled(hasCard());
+ }
+ }
+
@Override
public String getPreferenceKey() {
return KEY_PHONE_RINGTONE;
@@ -43,4 +66,10 @@ public boolean isAvailable() {
public int getRingtoneType() {
return RingtoneManager.TYPE_RINGTONE;
}
+
+ private boolean hasCard() {
+ TelephonyManager telephonyManager =
+ (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ return telephonyManager.hasIccCard(SLOT_ID);
+ }
}
diff --git a/src/com/android/settings/notification/RingtonePreferenceControllerBase.java b/src/com/android/settings/notification/RingtonePreferenceControllerBase.java
index 29b9266335d..359ea71abdf 100644
--- a/src/com/android/settings/notification/RingtonePreferenceControllerBase.java
+++ b/src/com/android/settings/notification/RingtonePreferenceControllerBase.java
@@ -24,6 +24,7 @@
import androidx.preference.Preference;
+import com.android.settings.RingtonePreference;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.utils.ThreadUtils;
@@ -51,8 +52,8 @@ public void updateState(Preference preference) {
}
private void updateSummary(Preference preference) {
- final Uri ringtoneUri = RingtoneManager.getActualDefaultRingtoneUri(
- mContext, getRingtoneType());
+ final Uri ringtoneUri = RingtoneManager.getActualDefaultRingtoneUriBySlot(mContext,
+ getRingtoneType(), ((RingtonePreference)preference).getSlotId());
final CharSequence summary;
try {
diff --git a/src/com/android/settings/notification/SoundSettings.java b/src/com/android/settings/notification/SoundSettings.java
index 4575708bbbd..5f76ab825f6 100644
--- a/src/com/android/settings/notification/SoundSettings.java
+++ b/src/com/android/settings/notification/SoundSettings.java
@@ -66,6 +66,8 @@ public class SoundSettings extends DashboardFragment implements OnActivityResult
@VisibleForTesting
final VolumePreferenceCallback mVolumeCallback = new VolumePreferenceCallback();
+ private final IncreasingRingVolumePreferenceCallback mIncreasingRingVolumeCallback =
+ new IncreasingRingVolumePreferenceCallback();
@VisibleForTesting
final Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
@@ -73,6 +75,7 @@ public void handleMessage(Message msg) {
switch (msg.what) {
case STOP_SAMPLE:
mVolumeCallback.stopSample();
+ mIncreasingRingVolumeCallback.stopSample();
break;
}
}
@@ -122,6 +125,7 @@ public int getHelpResource() {
public void onPause() {
super.onPause();
mVolumeCallback.stopSample();
+ mIncreasingRingVolumeCallback.stopSample();
}
@Override
@@ -209,6 +213,11 @@ public void onAttach(Context context) {
controller.setCallback(mVolumeCallback);
getSettingsLifecycle().addObserver(controller);
}
+
+ IncreasingRingVolumePreferenceController irvpc =
+ use(IncreasingRingVolumePreferenceController.class);
+ irvpc.setCallback(mIncreasingRingVolumeCallback);
+ getLifecycle().addObserver(irvpc);
}
// === Volumes ===
@@ -218,6 +227,7 @@ final class VolumePreferenceCallback implements VolumeSeekBarPreference.Callback
@Override
public void onSampleStarting(SeekBarVolumizer sbv) {
+ mIncreasingRingVolumeCallback.stopSample();
if (mCurrent != null) {
mHandler.removeMessages(STOP_SAMPLE);
mHandler.sendEmptyMessageDelayed(STOP_SAMPLE, SAMPLE_CUTOFF);
@@ -248,6 +258,26 @@ public void stopSample() {
}
}
+ final class IncreasingRingVolumePreferenceCallback implements
+ IncreasingRingVolumePreference.Callback {
+ private IncreasingRingVolumePreference mPlayingPref;
+
+ @Override
+ public void onSampleStarting(IncreasingRingVolumePreference pref) {
+ mPlayingPref = pref;
+ mVolumeCallback.stopSample();
+ mHandler.removeMessages(STOP_SAMPLE);
+ mHandler.sendEmptyMessageDelayed(STOP_SAMPLE, SAMPLE_CUTOFF);
+ }
+
+ public void stopSample() {
+ if (mPlayingPref != null) {
+ mPlayingPref.stopSample();
+ mPlayingPref = null;
+ }
+ }
+ };
+
private static List buildPreferenceControllers(Context context,
SoundSettings fragment, Lifecycle lifecycle) {
final List controllers = new ArrayList<>();
@@ -256,8 +286,13 @@ private static List buildPreferenceControllers(Con
// === Phone & notification ringtone ===
controllers.add(new PhoneRingtonePreferenceController(context));
+ controllers.add(new PhoneRingtone2PreferenceController(context));
controllers.add(new AlarmRingtonePreferenceController(context));
controllers.add(new NotificationRingtonePreferenceController(context));
+ controllers.add(new IncreasingRingPreferenceController(context));
+ controllers.add(new IncreasingRingVolumePreferenceController(context));
+ controllers.add(new VibrationPatternPreferenceController(context));
+ controllers.add(new NotificationVibrationPatternPreferenceController(context));
// === Other Sound Settings ===
final DialPadTonePreferenceController dialPadTonePreferenceController =
@@ -266,6 +301,8 @@ private static List buildPreferenceControllers(Con
new ScreenLockSoundPreferenceController(context, fragment, lifecycle);
final ChargingSoundPreferenceController chargingSoundPreferenceController =
new ChargingSoundPreferenceController(context, fragment, lifecycle);
+ final ChargingVibroPreferenceController chargingVibroPreferenceController =
+ new ChargingVibroPreferenceController(context, fragment, lifecycle);
final DockingSoundPreferenceController dockingSoundPreferenceController =
new DockingSoundPreferenceController(context, fragment, lifecycle);
final TouchSoundPreferenceController touchSoundPreferenceController =
@@ -282,6 +319,7 @@ private static List buildPreferenceControllers(Con
controllers.add(dialPadTonePreferenceController);
controllers.add(screenLockSoundPreferenceController);
controllers.add(chargingSoundPreferenceController);
+ controllers.add(chargingVibroPreferenceController);
controllers.add(dockingSoundPreferenceController);
controllers.add(touchSoundPreferenceController);
controllers.add(vibrateIconPreferenceController);
diff --git a/src/com/android/settings/notification/VibrateIconPreferenceController.java b/src/com/android/settings/notification/VibrateIconPreferenceController.java
index d772b47d59e..e538dd12f0f 100644
--- a/src/com/android/settings/notification/VibrateIconPreferenceController.java
+++ b/src/com/android/settings/notification/VibrateIconPreferenceController.java
@@ -40,6 +40,6 @@ public VibrateIconPreferenceController(Context context, SettingsPreferenceFragme
@Override
public boolean isAvailable() {
- return mHasVibrator;
+ return false;
}
}
diff --git a/src/com/android/settings/notification/VibrationPatternPreferenceController.java b/src/com/android/settings/notification/VibrationPatternPreferenceController.java
new file mode 100644
index 00000000000..d755c0ff2f5
--- /dev/null
+++ b/src/com/android/settings/notification/VibrationPatternPreferenceController.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2020-2022 Yet Another AOSP Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.net.Uri;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.provider.Settings;
+
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.Utils;
+import com.android.settingslib.core.AbstractPreferenceController;
+
+/**
+ * This class allows choosing a vibration pattern while ringing
+ */
+public class VibrationPatternPreferenceController extends AbstractPreferenceController
+ implements Preference.OnPreferenceChangeListener {
+
+ private static final String KEY_VIB_PATTERN = "vibration_pattern";
+ private static final String KEY_CUSTOM_VIB_CATEGORY = "custom_vibration_pattern";
+
+ private ListPreference mVibPattern;
+ private Preference mCustomVibCategory;
+
+ protected final Vibrator mVibrator;
+
+ private static class VibrationEffectProxy {
+ public VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
+ return VibrationEffect.createWaveform(timings, amplitudes, repeat);
+ }
+ }
+
+ private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+ .build();
+
+ private static final long[] SIMPLE_VIBRATION_PATTERN = {
+ 0, // No delay before starting
+ 1000, // How long to vibrate
+ 1000, // How long to wait before vibrating again
+ 1000, // How long to vibrate
+ 1000, // How long to wait before vibrating again
+ };
+
+ private static final long[] DZZZ_DA_VIBRATION_PATTERN = {
+ 0, // No delay before starting
+ 500, // How long to vibrate
+ 200, // Delay
+ 70, // How long to vibrate
+ 720, // How long to wait before vibrating again
+ };
+
+ private static final long[] MM_MM_MM_VIBRATION_PATTERN = {
+ 0, // No delay before starting
+ 300, // How long to vibrate
+ 400, // Delay
+ 300, // How long to vibrate
+ 400, // Delay
+ 300, // How long to vibrate
+ 1400, // How long to wait before vibrating again
+ };
+
+ private static final long[] DA_DA_DZZZ_VIBRATION_PATTERN = {
+ 0, // No delay before starting
+ 70, // How long to vibrate
+ 80, // Delay
+ 70, // How long to vibrate
+ 180, // Delay
+ 600, // How long to vibrate
+ 1050, // How long to wait before vibrating again
+ };
+
+ private static final long[] DA_DZZZ_DA_VIBRATION_PATTERN = {
+ 0, // No delay before starting
+ 80, // How long to vibrate
+ 200, // Delay
+ 600, // How long to vibrate
+ 150, // Delay
+ 20, // How long to vibrate
+ 1050, // How long to wait before vibrating again
+ };
+
+ private static final int[] SEVEN_ELEMENTS_VIBRATION_AMPLITUDE = {
+ 0, // No delay before starting
+ 255, // Vibrate full amplitude
+ 0, // No amplitude while waiting
+ 255,
+ 0,
+ 255,
+ 0,
+ };
+
+ private static final int[] FIVE_ELEMENTS_VIBRATION_AMPLITUDE = {
+ 0, // No delay before starting
+ 255, // Vibrate full amplitude
+ 0, // No amplitude while waiting
+ 255,
+ 0,
+ };
+
+ public VibrationPatternPreferenceController(Context context) {
+ super(context);
+ mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return Utils.isVoiceCapable(mContext) && mVibrator.hasVibrator();
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY_VIB_PATTERN;
+ }
+
+ protected String getCustomPreferenceKey() {
+ return KEY_CUSTOM_VIB_CATEGORY;
+ }
+
+ protected String getSettingsKey() {
+ return Settings.System.RINGTONE_VIBRATION_PATTERN;
+ }
+
+ protected long[] getDefaultPattern() {
+ return SIMPLE_VIBRATION_PATTERN;
+ }
+
+ protected int[] getDefaultPatternAmp() {
+ return FIVE_ELEMENTS_VIBRATION_AMPLITUDE;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mVibPattern = screen.findPreference(getPreferenceKey());
+ int vibPattern = Settings.System.getInt(
+ mContext.getContentResolver(), getSettingsKey(), 0);
+ mVibPattern.setValueIndex(vibPattern);
+ mVibPattern.setSummary(mVibPattern.getEntries()[vibPattern]);
+ mVibPattern.setOnPreferenceChangeListener(this);
+
+ mCustomVibCategory = screen.findPreference(getCustomPreferenceKey());
+ mCustomVibCategory.setVisible(vibPattern == 5);
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if (preference == mVibPattern) {
+ int vibPattern = Integer.valueOf((String) newValue);
+ Settings.System.putInt(
+ mContext.getContentResolver(), getSettingsKey(), vibPattern);
+ mVibPattern.setSummary(mVibPattern.getEntries()[vibPattern]);
+ boolean isCustom = vibPattern == 5;
+ mCustomVibCategory.setVisible(isCustom);
+ if (!isCustom) previewPattern();
+ return true;
+ }
+ return false;
+ }
+
+ private void previewPattern() {
+ VibrationEffect effect;
+ VibrationEffectProxy vibrationEffectProxy = new VibrationEffectProxy();
+ int vibPattern = Settings.System.getInt(
+ mContext.getContentResolver(), getSettingsKey(), 0);
+ switch (vibPattern) {
+ case 1:
+ effect = vibrationEffectProxy.createWaveform(DZZZ_DA_VIBRATION_PATTERN,
+ FIVE_ELEMENTS_VIBRATION_AMPLITUDE, -1);
+ break;
+ case 2:
+ effect = vibrationEffectProxy.createWaveform(MM_MM_MM_VIBRATION_PATTERN,
+ SEVEN_ELEMENTS_VIBRATION_AMPLITUDE, -1);
+ break;
+ case 3:
+ effect = vibrationEffectProxy.createWaveform(DA_DA_DZZZ_VIBRATION_PATTERN,
+ SEVEN_ELEMENTS_VIBRATION_AMPLITUDE, -1);
+ break;
+ case 4:
+ effect = vibrationEffectProxy.createWaveform(DA_DZZZ_DA_VIBRATION_PATTERN,
+ SEVEN_ELEMENTS_VIBRATION_AMPLITUDE, -1);
+ break;
+ default:
+ case 0:
+ effect = vibrationEffectProxy.createWaveform(getDefaultPattern(),
+ getDefaultPatternAmp(), -1);
+ break;
+ }
+ mVibrator.vibrate(effect, VIBRATION_ATTRIBUTES);
+ }
+}
diff --git a/src/com/android/settings/notification/app/AppNotificationSettings.java b/src/com/android/settings/notification/app/AppNotificationSettings.java
index 89756b7b839..de17e46f599 100644
--- a/src/com/android/settings/notification/app/AppNotificationSettings.java
+++ b/src/com/android/settings/notification/app/AppNotificationSettings.java
@@ -84,6 +84,7 @@ protected List createPreferenceControllers(Context
mControllers.add(new BlockPreferenceController(context, mDependentFieldListener, mBackend));
mControllers.add(new FullScreenIntentPermissionPreferenceController(context, mBackend));
mControllers.add(new BadgePreferenceController(context, mBackend));
+ mControllers.add(new SoundTimeoutPreferenceController(context, mBackend));
mControllers.add(new AllowSoundPreferenceController(
context, mDependentFieldListener, mBackend));
mControllers.add(new ImportancePreferenceController(
@@ -95,7 +96,7 @@ protected List createPreferenceControllers(Context
mControllers.add(new SoundPreferenceController(context, this,
mDependentFieldListener, mBackend));
mControllers.add(new LightsPreferenceController(context, mBackend));
- mControllers.add(new VibrationPreferenceController(context, mBackend));
+ mControllers.add(new VibrationPreferenceController(context, mBackend, mDependentFieldListener));
mControllers.add(new VisibilityPreferenceController(context, new LockPatternUtils(context),
mBackend));
mControllers.add(new DndPreferenceController(context, mBackend));
diff --git a/src/com/android/settings/notification/app/ChannelNotificationSettings.java b/src/com/android/settings/notification/app/ChannelNotificationSettings.java
index a7aba5284d6..1afaa8a88ee 100644
--- a/src/com/android/settings/notification/app/ChannelNotificationSettings.java
+++ b/src/com/android/settings/notification/app/ChannelNotificationSettings.java
@@ -127,7 +127,8 @@ protected List createPreferenceControllers(Context
context, mDependentFieldListener, mBackend));
mControllers.add(new SoundPreferenceController(context, this,
mDependentFieldListener, mBackend));
- mControllers.add(new VibrationPreferenceController(context, mBackend));
+ mControllers.add(new VibrationPreferenceController(context, mBackend, mDependentFieldListener));
+ mControllers.add(new CustomVibrationPreferenceController(context, mBackend));
mControllers.add(new AppLinkPreferenceController(context));
mControllers.add(new VisibilityPreferenceController(context, new LockPatternUtils(context),
mBackend));
diff --git a/src/com/android/settings/notification/app/ConversationNotificationSettings.java b/src/com/android/settings/notification/app/ConversationNotificationSettings.java
index 02ea6c060ec..3987ba05110 100644
--- a/src/com/android/settings/notification/app/ConversationNotificationSettings.java
+++ b/src/com/android/settings/notification/app/ConversationNotificationSettings.java
@@ -90,7 +90,8 @@ protected List createPreferenceControllers(Context
context, mDependentFieldListener, mBackend));
mControllers.add(new SoundPreferenceController(context, this,
mDependentFieldListener, mBackend));
- mControllers.add(new VibrationPreferenceController(context, mBackend));
+ mControllers.add(new VibrationPreferenceController(context, mBackend, mDependentFieldListener));
+ mControllers.add(new CustomVibrationPreferenceController(context, mBackend));
mControllers.add(new VisibilityPreferenceController(context, new LockPatternUtils(context),
mBackend));
mControllers.add(new LightsPreferenceController(context, mBackend));
diff --git a/src/com/android/settings/notification/app/CustomVibrationPreferenceController.java b/src/com/android/settings/notification/app/CustomVibrationPreferenceController.java
new file mode 100644
index 00000000000..a04301f0ce0
--- /dev/null
+++ b/src/com/android/settings/notification/app/CustomVibrationPreferenceController.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2022 Yet Another AOSP Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification.app;
+
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.media.AudioAttributes;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settings.notification.NotificationBackend;
+import com.android.settingslib.RestrictedSwitchPreference;
+import org.derpfest.support.preferences.ProperSeekBarPreference;
+
+public class CustomVibrationPreferenceController extends NotificationPreferenceController
+ implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener {
+
+ private static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250};
+ private static final int[] VIBRATION_AMPLITUDE = {0, 255, 0, 255};
+ private static final int VIBRATE_PATTERN_MAXLEN = 8 * 2 + 1; // up to eight bumps
+ private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+ .build();
+
+ private static final String KEY = "custom_vibrate";
+ private static final String KEY1 = "custom_vibrate1";
+ private static final String KEY2 = "custom_vibrate2";
+ private static final String KEY3 = "custom_vibrate3";
+ private static final String CATEGORY_KEY = "custom_vibrate_seek_bars";
+ private final Vibrator mVibrator;
+ private final long[] mDefaultPattern;
+
+ private RestrictedSwitchPreference mPreference;
+ private ProperSeekBarPreference mSeekBar1;
+ private ProperSeekBarPreference mSeekBar2;
+ private ProperSeekBarPreference mSeekBar3;
+ private PreferenceCategory mBarsCategory;
+
+ public CustomVibrationPreferenceController(Context context, NotificationBackend backend) {
+ super(context, backend);
+ mVibrator = context.getSystemService(Vibrator.class);
+ mDefaultPattern = getLongArray(context.getResources(),
+ com.android.internal.R.array.config_defaultNotificationVibePattern,
+ VIBRATE_PATTERN_MAXLEN,
+ DEFAULT_VIBRATE_PATTERN);
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mPreference = screen.findPreference(KEY);
+ mPreference.setOnPreferenceChangeListener(this);
+ mSeekBar1 = screen.findPreference(KEY1);
+ mSeekBar1.setOnPreferenceChangeListener(this);
+ mSeekBar2 = screen.findPreference(KEY2);
+ mSeekBar2.setOnPreferenceChangeListener(this);
+ mSeekBar3 = screen.findPreference(KEY3);
+ mSeekBar3.setOnPreferenceChangeListener(this);
+ mBarsCategory = screen.findPreference(CATEGORY_KEY);
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ if (!super.isAvailable() || mChannel == null) {
+ if (mBarsCategory != null)
+ mBarsCategory.setVisible(false);
+ return false;
+ }
+ final boolean avail = checkCanBeVisible(NotificationManager.IMPORTANCE_DEFAULT)
+ && mChannel.shouldVibrate()
+ && mChannel.getVibrationPattern() == null
+ && !isDefaultChannel()
+ && mVibrator != null
+ && mVibrator.hasVibrator();
+ if (mBarsCategory != null && !avail)
+ mBarsCategory.setVisible(false);
+ return avail;
+ }
+
+ @Override
+ boolean isIncludedInFilter() {
+ return mPreferenceFilter.contains(NotificationChannel.EDIT_VIBRATION);
+ }
+
+ public void updateState(Preference preference) {
+ if (mChannel == null) return;
+ if (mPreference == null) mPreference = (RestrictedSwitchPreference) preference;
+ mPreference.setDisabledByAdmin(mAdmin);
+ mPreference.setEnabled(!mPreference.isDisabledByAdmin());
+ final boolean isChecked = mChannel.shouldVibrate() &&
+ mChannel.getCustomVibrationPattern() != null;
+ mPreference.setChecked(isChecked);
+ mBarsCategory.setVisible(isChecked);
+ if (isChecked) getValues();
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ int pref = 0;
+ if (preference == mPreference) {
+ Boolean value = (Boolean) newValue;
+ mBarsCategory.setVisible(value);
+ if (value) getValues();
+ else setValues(false, 0, 0);
+ return true;
+ } else if (preference == mSeekBar1) {
+ pref = 1;
+ } else if (preference == mSeekBar2) {
+ pref = 2;
+ } else if (preference == mSeekBar3) {
+ pref = 3;
+ }
+ return setValues(mPreference.isChecked(), pref,
+ ((Integer) newValue).longValue());
+ }
+
+ private boolean setValues(boolean enabled, int pref, long val) {
+ mBarsCategory.setVisible(enabled);
+ if (enabled) {
+ final long val1 = pref == 1 ? val : mSeekBar1.getValue();
+ final long val2 = pref == 2 ? val : mSeekBar2.getValue();
+ final long val3 = pref == 3 ? val : mSeekBar3.getValue();
+ if (val1 == 0 && val2 == 0 && val3 == 0)
+ return false;
+ final long[] pattern = {0, val1, val2, val3};
+ mChannel.setCustomVibrationPattern(pattern);
+ previewPattern(pattern);
+ } else {
+ mChannel.setCustomVibrationPattern(null);
+ }
+ saveChannel();
+ return true;
+ }
+
+ private void getValues() {
+ long[] pattern = mChannel.getCustomVibrationPattern();
+ if (pattern == null) pattern = mDefaultPattern;
+ if (pattern.length < 4) pattern = new long[] {0, 100, 150, 100};
+ mSeekBar1.setValue(Math.round(pattern[1]));
+ mSeekBar2.setValue(Math.round(pattern[2]));
+ mSeekBar3.setValue(Math.round(pattern[3]));
+ }
+
+ private void previewPattern(long[] pattern) {
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ pattern, VIBRATION_AMPLITUDE, -1);
+ mVibrator.vibrate(effect, VIBRATION_ATTRIBUTES);
+ }
+
+ private static long[] getLongArray(Resources resources, int resId, int maxLength, long[] def) {
+ int[] ar = resources.getIntArray(resId);
+ if (ar == null) {
+ return def;
+ }
+ final int len = ar.length > maxLength ? maxLength : ar.length;
+ long[] out = new long[len];
+ for (int i = 0; i < len; i++) {
+ out[i] = ar[i];
+ }
+ return out;
+ }
+}
diff --git a/src/com/android/settings/notification/app/HighImportancePreferenceController.java b/src/com/android/settings/notification/app/HighImportancePreferenceController.java
index d60668b9abe..4807d73a8fa 100644
--- a/src/com/android/settings/notification/app/HighImportancePreferenceController.java
+++ b/src/com/android/settings/notification/app/HighImportancePreferenceController.java
@@ -21,9 +21,11 @@
import android.app.NotificationChannel;
import android.content.Context;
+import android.provider.Settings;
import androidx.preference.Preference;
+import com.android.settings.R;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.notification.NotificationBackend;
import com.android.settingslib.RestrictedSwitchPreference;
@@ -68,13 +70,22 @@ boolean isIncludedInFilter() {
@Override
public void updateState(Preference preference) {
if (mAppRow != null && mChannel != null) {
- preference.setEnabled(mAdmin == null && isChannelConfigurable(mChannel));
+ preference.setEnabled(isGlobalHeadsUpEnabled() && mAdmin == null
+ && isChannelConfigurable(mChannel));
RestrictedSwitchPreference pref = (RestrictedSwitchPreference) preference;
pref.setChecked(mChannel.getImportance() >= IMPORTANCE_HIGH);
+ refreshSummary(preference);
}
}
+ @Override
+ public CharSequence getSummary() {
+ return mContext.getString(isGlobalHeadsUpEnabled()
+ ? R.string.notification_channel_summary_high
+ : R.string.heads_up_off);
+ }
+
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (mChannel != null) {
@@ -87,4 +98,10 @@ public boolean onPreferenceChange(Preference preference, Object newValue) {
}
return true;
}
+
+ private boolean isGlobalHeadsUpEnabled() {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
+ Settings.Global.HEADS_UP_OFF) == Settings.Global.HEADS_UP_ON;
+ }
}
diff --git a/src/com/android/settings/notification/app/SoundTimeoutPreferenceController.java b/src/com/android/settings/notification/app/SoundTimeoutPreferenceController.java
new file mode 100644
index 00000000000..1fb9a215a88
--- /dev/null
+++ b/src/com/android/settings/notification/app/SoundTimeoutPreferenceController.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification.app;
+
+import android.content.Context;
+import android.provider.Settings;
+
+import androidx.preference.Preference;
+
+import com.android.settings.RestrictedListPreference;
+import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settings.notification.NotificationBackend;
+
+public class SoundTimeoutPreferenceController extends NotificationPreferenceController
+ implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener {
+
+ private static final String TAG = "SoundTimeoutPreferenceController";
+ private static final String KEY_SOUND_TIMEOUT = "sound_timeout";
+
+ public SoundTimeoutPreferenceController(Context context,
+ NotificationBackend backend) {
+ super(context, backend);
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY_SOUND_TIMEOUT;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ if (!super.isAvailable()) {
+ return false;
+ }
+ if (mAppRow == null) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ boolean isIncludedInFilter() {
+ return false;
+ }
+
+ public void updateState(Preference preference) {
+ if (mAppRow != null) {
+ RestrictedListPreference pref = (RestrictedListPreference) preference;
+ pref.setDisabledByAdmin(mAdmin);
+
+ pref.setSummary("%s");
+ pref.setValue(Long.toString(mAppRow.soundTimeout));
+ }
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if (mAppRow != null) {
+ mAppRow.soundTimeout = Long.valueOf((String) newValue);
+ mBackend.setNotificationSoundTimeout(mAppRow.pkg, mAppRow.uid, mAppRow.soundTimeout);
+ }
+ return true;
+ }
+
+}
diff --git a/src/com/android/settings/notification/app/VibrationPreferenceController.java b/src/com/android/settings/notification/app/VibrationPreferenceController.java
index 34d1a543b56..69664ca3169 100644
--- a/src/com/android/settings/notification/app/VibrationPreferenceController.java
+++ b/src/com/android/settings/notification/app/VibrationPreferenceController.java
@@ -32,10 +32,13 @@ public class VibrationPreferenceController extends NotificationPreferenceControl
private static final String KEY_VIBRATE = "vibrate";
private final Vibrator mVibrator;
+ private NotificationSettings.DependentFieldListener mDependentFieldListener;
- public VibrationPreferenceController(Context context, NotificationBackend backend) {
+ public VibrationPreferenceController(Context context, NotificationBackend backend,
+ NotificationSettings.DependentFieldListener dependentFieldListener) {
super(context, backend);
mVibrator = context.getSystemService(Vibrator.class);
+ mDependentFieldListener = dependentFieldListener;
}
@Override
@@ -74,6 +77,7 @@ public boolean onPreferenceChange(Preference preference, Object newValue) {
final boolean vibrate = (Boolean) newValue;
mChannel.enableVibration(vibrate);
saveChannel();
+ mDependentFieldListener.onFieldValueChanged();
}
return true;
}
diff --git a/src/com/android/settings/notification/history/NotificationStation.java b/src/com/android/settings/notification/history/NotificationStation.java
index e79a4ac58c6..ec6fc8e93ec 100644
--- a/src/com/android/settings/notification/history/NotificationStation.java
+++ b/src/com/android/settings/notification/history/NotificationStation.java
@@ -208,6 +208,9 @@ public void onActivityCreated(Bundle savedInstanceState) {
public void onResume() {
logd("onResume()");
super.onResume();
+ // Set correct title on the action bar
+ getActivity().setTitle(R.string.notification_log_title);
+
try {
mListener.registerAsSystemService(mContext, new ComponentName(mContext.getPackageName(),
this.getClass().getCanonicalName()), ActivityManager.getCurrentUser());
diff --git a/src/com/android/settings/notification/zen/ZenModeEventRuleSettings.java b/src/com/android/settings/notification/zen/ZenModeEventRuleSettings.java
index 9b0f3fc88a6..fa243e8cf7f 100644
--- a/src/com/android/settings/notification/zen/ZenModeEventRuleSettings.java
+++ b/src/com/android/settings/notification/zen/ZenModeEventRuleSettings.java
@@ -28,7 +28,7 @@
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeConfig.EventInfo;
-import androidx.preference.DropDownPreference;
+import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceChangeListener;
import androidx.preference.PreferenceScreen;
@@ -50,8 +50,8 @@ public class ZenModeEventRuleSettings extends ZenModeRuleSettingsBase {
public static final String ACTION = Settings.ACTION_ZEN_MODE_EVENT_RULE_SETTINGS;
- private DropDownPreference mCalendar;
- private DropDownPreference mReply;
+ private ListPreference mCalendar;
+ private ListPreference mReply;
private EventInfo mEvent;
@@ -128,7 +128,7 @@ protected void onCreateInternal() {
mCreate = true;
final PreferenceScreen root = getPreferenceScreen();
- mCalendar = (DropDownPreference) root.findPreference(KEY_CALENDAR);
+ mCalendar = (ListPreference) root.findPreference(KEY_CALENDAR);
mCalendar.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
@@ -143,7 +143,7 @@ public boolean onPreferenceChange(Preference preference, Object newValue) {
}
});
- mReply = (DropDownPreference) root.findPreference(KEY_REPLY);
+ mReply = (ListPreference) root.findPreference(KEY_REPLY);
mReply.setEntries(new CharSequence[] {
getString(R.string.zen_mode_event_rule_reply_any_except_no),
getString(R.string.zen_mode_event_rule_reply_yes_or_maybe),
diff --git a/src/com/android/settings/panel/AppVolumePanel.java b/src/com/android/settings/panel/AppVolumePanel.java
new file mode 100644
index 00000000000..876a15cf576
--- /dev/null
+++ b/src/com/android/settings/panel/AppVolumePanel.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 Project Kaleidoscope
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.panel;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.provider.Settings;
+
+import com.android.settings.R;
+import com.android.settings.slices.CustomSliceRegistry;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class AppVolumePanel implements PanelContent {
+
+ private final Context mContext;
+
+ public static AppVolumePanel create(Context context) {
+ return new AppVolumePanel(context);
+ }
+
+ private AppVolumePanel(Context context) {
+ mContext = context.getApplicationContext();
+ }
+
+ @Override
+ public CharSequence getTitle() {
+ return mContext.getText(R.string.app_volume);
+ }
+
+ @Override
+ public List getSlices() {
+ final List uris = new ArrayList<>();
+ uris.add(CustomSliceRegistry.APP_VOLUME_SLICE_URI);
+ return uris;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.PANEL_VOLUME;
+ }
+
+ @Override
+ public Intent getSeeMoreIntent() {
+ return new Intent(Settings.ACTION_SOUND_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ }
+
+}
diff --git a/src/com/android/settings/panel/PanelFeatureProviderImpl.java b/src/com/android/settings/panel/PanelFeatureProviderImpl.java
index a0aeec60856..4d7acea49f3 100644
--- a/src/com/android/settings/panel/PanelFeatureProviderImpl.java
+++ b/src/com/android/settings/panel/PanelFeatureProviderImpl.java
@@ -87,6 +87,8 @@ public PanelContent getPanel(Context context, Bundle bundle) {
return VolumePanel.create(context);
}
}
+ case Settings.Panel.ACTION_APP_VOLUME:
+ return AppVolumePanel.create(context);
}
throw new IllegalStateException("No matching panel for: " + panelType);
diff --git a/src/com/android/settings/password/ChooseLockPassword.java b/src/com/android/settings/password/ChooseLockPassword.java
index a6453005ab9..da75b8136f3 100644
--- a/src/com/android/settings/password/ChooseLockPassword.java
+++ b/src/com/android/settings/password/ChooseLockPassword.java
@@ -1086,7 +1086,8 @@ private void startSaveAndFinish() {
mUserId);
mSaveAndFinishWorker.start(mLockPatternUtils,
- mChosenPassword, mCurrentCredential, mUserId);
+ mChosenPassword, mCurrentCredential, mUserId,
+ mLockPatternUtils.getLockPatternSize(mUserId));
}
@Override
diff --git a/src/com/android/settings/password/ChooseLockPattern.java b/src/com/android/settings/password/ChooseLockPattern.java
index c331991a486..a3afe19670a 100644
--- a/src/com/android/settings/password/ChooseLockPattern.java
+++ b/src/com/android/settings/password/ChooseLockPattern.java
@@ -102,7 +102,8 @@ public static class IntentBuilder {
private final Intent mIntent;
public IntentBuilder(Context context) {
- mIntent = new Intent(context, ChooseLockPattern.class);
+ mIntent = new Intent(context, ChooseLockPatternSize.class);
+ mIntent.putExtra("className", ChooseLockPattern.class.getName());
mIntent.putExtra(ChooseLockGeneric.CONFIRM_CREDENTIALS, false);
}
@@ -211,19 +212,14 @@ public static class ChooseLockPatternFragment extends InstrumentedFragment
protected FooterButton mSkipOrClearButton;
protected FooterButton mNextButton;
@VisibleForTesting protected LockscreenCredential mChosenPattern;
+ private byte mPatternSize = LockPatternUtils.PATTERN_SIZE_DEFAULT;
private ColorStateList mDefaultHeaderColorList;
private View mSudContent;
/**
* The patten used during the help screen to show how to draw a pattern.
*/
- private final List mAnimatePattern =
- Collections.unmodifiableList(Lists.newArrayList(
- LockPatternView.Cell.of(0, 0),
- LockPatternView.Cell.of(0, 1),
- LockPatternView.Cell.of(1, 1),
- LockPatternView.Cell.of(2, 1)
- ));
+ private List mAnimatePattern;
@Override
public void onActivityResult(int requestCode, int resultCode,
@@ -268,12 +264,13 @@ public void onPatternCleared() {
mLockPatternView.removeCallbacks(mClearPatternRunnable);
}
- public void onPatternDetected(List pattern) {
+ public void onPatternDetected(List pattern,
+ byte patternSize) {
if (mUiStage == Stage.NeedToConfirm || mUiStage == Stage.ConfirmWrong) {
if (mChosenPattern == null) throw new IllegalStateException(
"null chosen pattern in stage 'need to confirm");
try (LockscreenCredential confirmPattern =
- LockscreenCredential.createPattern(pattern)) {
+ LockscreenCredential.createPattern(pattern, patternSize)) {
if (mChosenPattern.equals(confirmPattern)) {
updateStage(Stage.ChoiceConfirmed);
} else {
@@ -284,7 +281,8 @@ public void onPatternDetected(List pattern) {
if (pattern.size() < LockPatternUtils.MIN_LOCK_PATTERN_SIZE) {
updateStage(Stage.ChoiceTooShort);
} else {
- mChosenPattern = LockscreenCredential.createPattern(pattern);
+ mChosenPattern = LockscreenCredential.createPattern(
+ pattern, patternSize);
updateStage(Stage.FirstChoiceValid);
}
} else {
@@ -542,6 +540,16 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container,
mSudContent.setPadding(mSudContent.getPaddingLeft(), 0, mSudContent.getPaddingRight(),
0);
+ mPatternSize = getActivity().getIntent().getByteExtra("pattern_size",
+ LockPatternUtils.PATTERN_SIZE_DEFAULT);
+ LockPatternView.Cell.updateSize(mPatternSize);
+ mAnimatePattern = Collections.unmodifiableList(Lists.newArrayList(
+ LockPatternView.Cell.of(0, 0, mPatternSize),
+ LockPatternView.Cell.of(0, 1, mPatternSize),
+ LockPatternView.Cell.of(1, 1, mPatternSize),
+ LockPatternView.Cell.of(2, 1, mPatternSize)
+ ));
+
return layout;
}
@@ -555,6 +563,8 @@ public void onViewCreated(View view, Bundle savedInstanceState) {
mLockPatternView = (LockPatternView) view.findViewById(R.id.lockPattern);
mLockPatternView.setOnPatternListener(mChooseNewLockPatternListener);
mLockPatternView.setFadePattern(false);
+ mLockPatternView.setLockPatternUtils(mLockPatternUtils);
+ mLockPatternView.setLockPatternSize(mPatternSize);
mFooterText = (TextView) view.findViewById(R.id.footerText);
@@ -601,6 +611,9 @@ public void onViewCreated(View view, Bundle savedInstanceState) {
// restore from previous state
mChosenPattern = savedInstanceState.getParcelable(KEY_PATTERN_CHOICE);
mCurrentCredential = savedInstanceState.getParcelable(KEY_CURRENT_PATTERN);
+ mLockPatternView.setPattern(DisplayMode.Correct,
+ LockPatternUtils.byteArrayToPattern(
+ mChosenPattern.getCredential(), mPatternSize));
updateStage(Stage.values()[savedInstanceState.getInt(KEY_UI_STAGE)]);
@@ -859,7 +872,7 @@ private void startSaveAndFinish() {
}
}
mSaveAndFinishWorker.start(mLockPatternUtils,
- mChosenPattern, mCurrentCredential, mUserId);
+ mChosenPattern, mCurrentCredential, mUserId, mPatternSize);
}
@Override
diff --git a/src/com/android/settings/password/ChooseLockPatternSize.java b/src/com/android/settings/password/ChooseLockPatternSize.java
new file mode 100644
index 00000000000..601e4576952
--- /dev/null
+++ b/src/com/android/settings/password/ChooseLockPatternSize.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2012-2013 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.password;
+
+import android.content.Intent;
+import android.content.res.Resources.Theme;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import androidx.preference.Preference;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockscreenCredential;
+import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.SetupWizardUtils;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.utils.SettingsDividerItemDecoration;
+
+import com.google.android.setupdesign.GlifPreferenceLayout;
+import com.google.android.setupdesign.util.ThemeHelper;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
+public class ChooseLockPatternSize extends SettingsActivity {
+
+ @Override
+ public Intent getIntent() {
+ Intent modIntent = new Intent(super.getIntent());
+ modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ChooseLockPatternSizeFragment.class.getName());
+ return modIntent;
+ }
+
+ @Override
+ protected boolean isValidFragment(String fragmentName) {
+ if (ChooseLockPatternSizeFragment.class.getName().equals(fragmentName)) return true;
+ return false;
+ }
+
+ @Override
+ protected boolean isToolbarEnabled() {
+ return false;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ setTheme(SetupWizardUtils.getTheme(this, getIntent()));
+ ThemeHelper.trySetDynamicColor(this);
+ super.onCreate(savedInstanceState);
+ findViewById(R.id.content_parent).setFitsSystemWindows(false);
+ }
+
+ public static class ChooseLockPatternSizeFragment extends SettingsPreferenceFragment {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (!(getActivity() instanceof ChooseLockPatternSize)) {
+ throw new SecurityException("Fragment contained in wrong activity");
+ }
+ addPreferencesFromResource(R.xml.security_settings_pattern_size);
+ }
+
+ @Override
+ public boolean onPreferenceTreeClick(Preference preference) {
+ final String key = preference.getKey();
+
+ byte patternSize;
+ if ("lock_pattern_size_4".equals(key)) {
+ patternSize = 4;
+ } else if ("lock_pattern_size_5".equals(key)) {
+ patternSize = 5;
+ } else if ("lock_pattern_size_6".equals(key)) {
+ patternSize = 6;
+ } else {
+ patternSize = 3;
+ }
+
+ Bundle extras = getActivity().getIntent().getExtras();
+ Intent intent = new Intent();
+ intent.setClassName(getActivity(), extras.getString("className"));
+ intent.putExtras(extras);
+ intent.putExtra("pattern_size", patternSize);
+ intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
+ startActivity(intent);
+
+ finish();
+ return true;
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ GlifPreferenceLayout layout = (GlifPreferenceLayout) view;
+ layout.setDividerItemDecoration(new SettingsDividerItemDecoration(getContext()));
+
+ layout.setIcon(getContext().getDrawable(R.drawable.ic_lock));
+
+ if (getActivity() != null) {
+ getActivity().setTitle(R.string.lock_settings_picker_pattern_size_message);
+ }
+
+ layout.setHeaderText(R.string.lock_settings_picker_pattern_size_message);
+
+ // Remove the padding on the start of the header text.
+ if (ThemeHelper.shouldApplyMaterialYouStyle(getContext())) {
+ final LinearLayout headerLayout = layout.findManagedViewById(
+ com.google.android.setupdesign.R.id.sud_layout_header);
+ if (headerLayout != null) {
+ headerLayout.setPadding(0, layout.getPaddingTop(), 0,
+ layout.getPaddingBottom());
+ }
+ }
+
+ // Use the dividers in SetupWizardRecyclerLayout. Suppress the dividers in
+ // PreferenceFragment.
+ setDivider(null);
+ }
+
+ @Override
+ public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent,
+ Bundle savedInstanceState) {
+ GlifPreferenceLayout layout = (GlifPreferenceLayout) parent;
+ return layout.onCreateRecyclerView(inflater, parent, savedInstanceState);
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsEvent.DERP;
+ }
+ }
+}
diff --git a/src/com/android/settings/password/ChooseLockSettingsHelper.java b/src/com/android/settings/password/ChooseLockSettingsHelper.java
index 91875cc88d6..0f907cd0eb1 100644
--- a/src/com/android/settings/password/ChooseLockSettingsHelper.java
+++ b/src/com/android/settings/password/ChooseLockSettingsHelper.java
@@ -475,6 +475,9 @@ private boolean launchConfirmationActivity(int request, CharSequence title, Char
intent.setClassName(SETTINGS_PACKAGE_NAME, activityClass.getName());
intent.putExtra(SettingsBaseActivity.EXTRA_PAGE_TRANSITION_TYPE,
SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE);
+ if (userId == LockPatternUtils.USER_FRP) {
+ intent.putExtra("className", ConfirmLockPattern.class.getName());
+ }
Intent inIntent = mFragment != null ? mFragment.getActivity().getIntent() :
mActivity.getIntent();
@@ -555,7 +558,9 @@ private Optional> determineAppropriateActivityClass(boolean returnCrede
? ConfirmLockPassword.InternalActivity.class
: ConfirmLockPassword.class);
case KeyguardManager.PATTERN:
- return Optional.of(returnCredentials || forceVerifyPath
+ return Optional.of(userId == LockPatternUtils.USER_FRP
+ ? ChooseLockPatternSize.class
+ : returnCredentials || forceVerifyPath
? ConfirmLockPattern.InternalActivity.class
: ConfirmLockPattern.class);
}
diff --git a/src/com/android/settings/password/ConfirmLockPassword.java b/src/com/android/settings/password/ConfirmLockPassword.java
index b9b1810da25..35f6a82e3c0 100644
--- a/src/com/android/settings/password/ConfirmLockPassword.java
+++ b/src/com/android/settings/password/ConfirmLockPassword.java
@@ -628,7 +628,8 @@ public void onRemoteLockscreenValidationResult(
mLockPatternUtils,
mRemoteLockscreenValidationFragment.getLockscreenCredential(),
/* currentCredential= */ null,
- mEffectiveUserId);
+ mEffectiveUserId,
+ mLockPatternUtils.getLockPatternSize(mEffectiveUserId));
} else {
mCredentialCheckResultTracker.setResult(/* matched= */ true, new Intent(),
/* timeoutMs= */ 0, mEffectiveUserId);
diff --git a/src/com/android/settings/password/ConfirmLockPattern.java b/src/com/android/settings/password/ConfirmLockPattern.java
index 3415478c912..06fdc496ff8 100644
--- a/src/com/android/settings/password/ConfirmLockPattern.java
+++ b/src/com/android/settings/password/ConfirmLockPattern.java
@@ -115,6 +115,7 @@ public static class ConfirmLockPatternFragment extends ConfirmDeviceCredentialBa
private DisappearAnimationUtils mDisappearAnimationUtils;
private boolean mIsManagedProfile;
+ private byte mPatternSize;
// required constructor for fragments
public ConfirmLockPatternFragment() {
@@ -141,6 +142,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container,
mSudContent.setPadding(mSudContent.getPaddingLeft(), 0, mSudContent.getPaddingRight(),
0);
mIsManagedProfile = UserManager.get(getActivity()).isManagedProfile(mEffectiveUserId);
+ mPatternSize = mLockPatternUtils.getLockPatternSize(mEffectiveUserId);
// make it so unhandled touch events within the unlock screen go to the
// lock pattern view.
@@ -155,6 +157,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container,
mDetailsText = intent.getCharSequenceExtra(
ConfirmDeviceCredentialBaseFragment.DETAILS_TEXT);
mCheckBoxLabel = intent.getCharSequenceExtra(KeyguardManager.EXTRA_CHECKBOX_LABEL);
+ mPatternSize = intent.getByteExtra("pattern_size", mPatternSize);
}
if (TextUtils.isEmpty(mHeaderText) && mIsManagedProfile) {
mHeaderText = mDevicePolicyManager.getOrganizationNameForUser(mUserId);
@@ -162,6 +165,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container,
mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
mEffectiveUserId));
+ mLockPatternView.setLockPatternSize(mPatternSize);
mLockPatternView.setOnPatternListener(mConfirmExistingLockPatternListener);
mLockPatternView.setOnTouchListener((v, event) -> {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
@@ -497,14 +501,15 @@ public void onPatternCellAdded(List pattern) {
}
- public void onPatternDetected(List pattern) {
+ public void onPatternDetected(List pattern, byte patternSize) {
if (mPendingLockCheck != null || mDisappearing) {
return;
}
mLockPatternView.setEnabled(false);
- final LockscreenCredential credential = LockscreenCredential.createPattern(pattern);
+ final LockscreenCredential credential = LockscreenCredential.createPattern(pattern,
+ patternSize);
if (mRemoteValidation) {
validateGuess(credential);
@@ -643,7 +648,8 @@ public void onRemoteLockscreenValidationResult(
mLockPatternUtils,
mRemoteLockscreenValidationFragment.getLockscreenCredential(),
/* currentCredential= */ null,
- mEffectiveUserId);
+ mEffectiveUserId,
+ mLockPatternUtils.getLockPatternSize(mEffectiveUserId));
} else {
mCredentialCheckResultTracker.setResult(/* matched= */ true, new Intent(),
/* timeoutMs= */ 0, mEffectiveUserId);
diff --git a/src/com/android/settings/password/SaveAndFinishWorker.java b/src/com/android/settings/password/SaveAndFinishWorker.java
index 40054b77645..5592071e7a4 100644
--- a/src/com/android/settings/password/SaveAndFinishWorker.java
+++ b/src/com/android/settings/password/SaveAndFinishWorker.java
@@ -53,6 +53,7 @@ public class SaveAndFinishWorker extends Fragment {
private LockscreenCredential mUnificationProfileCredential;
private LockscreenCredential mChosenCredential;
private LockscreenCredential mCurrentCredential;
+ private byte mPatternSize;
private boolean mBlocking;
@@ -76,9 +77,10 @@ public SaveAndFinishWorker setListener(Listener listener) {
@VisibleForTesting
void prepare(LockPatternUtils utils, LockscreenCredential chosenCredential,
- LockscreenCredential currentCredential, int userId) {
+ LockscreenCredential currentCredential, int userId, byte patternSize) {
mUtils = utils;
mUserId = userId;
+ mPatternSize = patternSize;
// This will be a no-op for non managed profiles.
mWasSecureBefore = mUtils.isSecure(mUserId);
mFinished = false;
@@ -90,8 +92,8 @@ void prepare(LockPatternUtils utils, LockscreenCredential chosenCredential,
}
public void start(LockPatternUtils utils, LockscreenCredential chosenCredential,
- LockscreenCredential currentCredential, int userId) {
- prepare(utils, chosenCredential, currentCredential, userId);
+ LockscreenCredential currentCredential, int userId, byte patternSize) {
+ prepare(utils, chosenCredential, currentCredential, userId, patternSize);
if (mBlocking) {
finish(saveAndVerifyInBackground().second);
} else {
@@ -107,6 +109,7 @@ public void start(LockPatternUtils utils, LockscreenCredential chosenCredential,
@VisibleForTesting
Pair saveAndVerifyInBackground() {
final int userId = mUserId;
+ mUtils.setLockPatternSize(mPatternSize, userId);
try {
if (!mUtils.setLockCredential(mChosenCredential, mCurrentCredential, userId)) {
return Pair.create(false, null);
diff --git a/src/com/android/settings/password/SetupChooseLockPattern.java b/src/com/android/settings/password/SetupChooseLockPattern.java
index 55b3847125e..e0e598064ff 100644
--- a/src/com/android/settings/password/SetupChooseLockPattern.java
+++ b/src/com/android/settings/password/SetupChooseLockPattern.java
@@ -47,7 +47,8 @@
public class SetupChooseLockPattern extends ChooseLockPattern {
public static Intent modifyIntentForSetup(Context context, Intent chooseLockPatternIntent) {
- chooseLockPatternIntent.setClass(context, SetupChooseLockPattern.class);
+ chooseLockPatternIntent.setClass(context, ChooseLockPatternSize.class);
+ chooseLockPatternIntent.putExtra("className", SetupChooseLockPattern.class.getName());
return chooseLockPatternIntent;
}
diff --git a/src/com/android/settings/preference/SystemSettingMainSwitchPreference.java b/src/com/android/settings/preference/SystemSettingMainSwitchPreference.java
new file mode 100644
index 00000000000..92a95bcf358
--- /dev/null
+++ b/src/com/android/settings/preference/SystemSettingMainSwitchPreference.java
@@ -0,0 +1,32 @@
+package com.android.settings.preference;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import org.derpfest.support.preferences.SystemSettingsStore;
+
+import com.android.settingslib.widget.MainSwitchPreference;
+
+public class SystemSettingMainSwitchPreference extends MainSwitchPreference {
+
+ public SystemSettingMainSwitchPreference(Context context, AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ setPreferenceDataStore(new SystemSettingsStore(context.getContentResolver()));
+ }
+
+ public SystemSettingMainSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ setPreferenceDataStore(new SystemSettingsStore(context.getContentResolver()));
+ }
+
+ public SystemSettingMainSwitchPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setPreferenceDataStore(new SystemSettingsStore(context.getContentResolver()));
+ }
+
+ public SystemSettingMainSwitchPreference(Context context) {
+ super(context);
+ setPreferenceDataStore(new SystemSettingsStore(context.getContentResolver()));
+ }
+}
diff --git a/src/com/android/settings/preference/SystemSettingPrimarySwitchPreference.java b/src/com/android/settings/preference/SystemSettingPrimarySwitchPreference.java
new file mode 100644
index 00000000000..092f418ba22
--- /dev/null
+++ b/src/com/android/settings/preference/SystemSettingPrimarySwitchPreference.java
@@ -0,0 +1,32 @@
+package com.android.settings.preference;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import org.derpfest.support.preferences.SystemSettingsStore;
+
+import com.android.settingslib.PrimarySwitchPreference;
+
+public class SystemSettingPrimarySwitchPreference extends PrimarySwitchPreference {
+
+ public SystemSettingPrimarySwitchPreference(Context context, AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ setPreferenceDataStore(new SystemSettingsStore(context.getContentResolver()));
+ }
+
+ public SystemSettingPrimarySwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ setPreferenceDataStore(new SystemSettingsStore(context.getContentResolver()));
+ }
+
+ public SystemSettingPrimarySwitchPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setPreferenceDataStore(new SystemSettingsStore(context.getContentResolver()));
+ }
+
+ public SystemSettingPrimarySwitchPreference(Context context) {
+ super(context);
+ setPreferenceDataStore(new SystemSettingsStore(context.getContentResolver()));
+ }
+}
diff --git a/src/com/android/settings/privatespace/HidePrivateSpaceController.java b/src/com/android/settings/privatespace/HidePrivateSpaceController.java
index 81814adc9db..fca598d80a9 100644
--- a/src/com/android/settings/privatespace/HidePrivateSpaceController.java
+++ b/src/com/android/settings/privatespace/HidePrivateSpaceController.java
@@ -46,10 +46,7 @@ public HidePrivateSpaceController(Context context, String key) {
@Override
@AvailabilityStatus
public int getAvailabilityStatus() {
- return android.os.Flags.allowPrivateProfile()
- && android.multiuser.Flags.enablePrivateSpaceFeatures()
- ? AVAILABLE
- : UNSUPPORTED_ON_DEVICE;
+ return UNSUPPORTED_ON_DEVICE;
}
@Override
diff --git a/src/com/android/settings/security/LockscreenDashboardFragment.java b/src/com/android/settings/security/LockscreenDashboardFragment.java
index 2e4a1f28917..0c79e0198b0 100644
--- a/src/com/android/settings/security/LockscreenDashboardFragment.java
+++ b/src/com/android/settings/security/LockscreenDashboardFragment.java
@@ -30,13 +30,17 @@
import android.provider.Settings;
import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.display.AmbientDisplayAlwaysOnPreferenceController;
import com.android.settings.display.AmbientDisplayNotificationsPreferenceController;
+import com.android.settings.display.DozeOnChargePreferenceController;
+import com.android.settings.display.AODSchedulePreferenceController;
import com.android.settings.gestures.DoubleTapScreenPreferenceController;
import com.android.settings.gestures.PickupGesturePreferenceController;
+import com.android.settings.gestures.ScreenOffUdfpsPreferenceController;
import com.android.settings.notification.LockScreenNotificationPreferenceController;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.security.screenlock.LockScreenPreferenceController;
@@ -53,9 +57,6 @@
@SearchIndexable
public class LockscreenDashboardFragment extends DashboardFragment
implements OwnerInfoPreferenceController.OwnerInfoCallback {
-
- public static final String KEY_AMBIENT_DISPLAY_ALWAYS_ON = "ambient_display_always_on";
-
private static final String TAG = "LockscreenDashboardFragment";
@VisibleForTesting
@@ -70,7 +71,6 @@ public class LockscreenDashboardFragment extends DashboardFragment
static final String KEY_ADD_USER_FROM_LOCK_SCREEN =
"security_lockscreen_add_users_when_locked";
-
private AmbientDisplayConfiguration mConfig;
private OwnerInfoPreferenceController mOwnerInfoPreferenceController;
@VisibleForTesting
@@ -110,9 +110,12 @@ public int getHelpResource() {
public void onAttach(Context context) {
super.onAttach(context);
use(AmbientDisplayAlwaysOnPreferenceController.class).setConfig(getConfig(context));
+ use(DozeOnChargePreferenceController.class).setConfig(getConfig(context));
use(AmbientDisplayNotificationsPreferenceController.class).setConfig(getConfig(context));
use(DoubleTapScreenPreferenceController.class).setConfig(getConfig(context));
use(PickupGesturePreferenceController.class).setConfig(getConfig(context));
+ use(AODSchedulePreferenceController.class).setConfig(getConfig(context));
+ use(ScreenOffUdfpsPreferenceController.class).setConfig(getConfig(context));
mControlsContentObserver = new ContentObserver(
new Handler(Looper.getMainLooper())) {
@@ -149,6 +152,7 @@ protected List createPreferenceControllers(Context
controllers.add(notificationController);
mOwnerInfoPreferenceController = new OwnerInfoPreferenceController(context, this);
controllers.add(mOwnerInfoPreferenceController);
+ controllers.add(new DozeOnChargePreferenceController(context, lifecycle));
return controllers;
}
@@ -177,6 +181,7 @@ public List createPreferenceControllers(
controllers.add(new LockScreenNotificationPreferenceController(context));
controllers.add(new OwnerInfoPreferenceController(
context, null /* fragment */));
+ controllers.add(new DozeOnChargePreferenceController(context, null /* lifecycle */));
return controllers;
}
diff --git a/src/com/android/settings/security/TrustRestrictUsbPreferenceController.java b/src/com/android/settings/security/TrustRestrictUsbPreferenceController.java
new file mode 100644
index 00000000000..5c88ec3ad91
--- /dev/null
+++ b/src/com/android/settings/security/TrustRestrictUsbPreferenceController.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2022 LibreMoblieOS Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.security;
+
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.hardware.usb.IUsbManager;
+import android.hardware.usb.UsbManager;
+import com.android.settings.core.BasePreferenceController;
+
+import java.util.NoSuchElementException;
+
+public class TrustRestrictUsbPreferenceController extends BasePreferenceController {
+
+ public static final String KEY = "trust_restrict_usb";
+
+ private Context mContext;
+
+ private boolean mIsUsb1_3 = false;
+
+ public TrustRestrictUsbPreferenceController(Context context, String key) {
+ super(context, key);
+
+ mContext = context;
+
+ IUsbManager usbMgr = IUsbManager.Stub.asInterface(ServiceManager.getService(
+ Context.USB_SERVICE));
+ try {
+ int version = usbMgr.getUsbHalVersion();
+
+ if (version >= UsbManager.USB_HAL_V1_3)
+ mIsUsb1_3 = true;
+ } catch (RemoteException e) {
+ // ignore, the hal is not available
+ }
+ }
+
+ public TrustRestrictUsbPreferenceController(Context context) {
+ this(context, KEY);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ boolean exists = (mIsUsb1_3);
+ return (exists ? AVAILABLE : UNSUPPORTED_ON_DEVICE);
+ }
+
+}
diff --git a/src/com/android/settings/security/screenlock/AbstractPatternSwitchPreferenceController.java b/src/com/android/settings/security/screenlock/AbstractPatternSwitchPreferenceController.java
new file mode 100644
index 00000000000..e2102aade4e
--- /dev/null
+++ b/src/com/android/settings/security/screenlock/AbstractPatternSwitchPreferenceController.java
@@ -0,0 +1,72 @@
+/*
+ * 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.settings.security.screenlock;
+
+import android.content.Context;
+
+import androidx.preference.Preference;
+import androidx.preference.TwoStatePreference;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settingslib.core.AbstractPreferenceController;
+
+public abstract class AbstractPatternSwitchPreferenceController
+ extends AbstractPreferenceController
+ implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener {
+
+ private final String mKey;
+ private final int mUserId;
+ private final LockPatternUtils mLockPatternUtils;
+
+ public AbstractPatternSwitchPreferenceController(Context context, String key,
+ int userId, LockPatternUtils lockPatternUtils) {
+ super(context);
+ mKey = key;
+ mUserId = userId;
+ mLockPatternUtils = lockPatternUtils;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return isPatternLock();
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return mKey;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ ((TwoStatePreference) preference).setChecked(isEnabled(mLockPatternUtils, mUserId));
+ }
+
+ private boolean isPatternLock() {
+ return mLockPatternUtils.getCredentialTypeForUser(mUserId)
+ == LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ setEnabled(mLockPatternUtils, mUserId, (Boolean) newValue);
+ return true;
+ }
+
+ protected abstract boolean isEnabled(LockPatternUtils utils, int userId);
+ protected abstract void setEnabled(LockPatternUtils utils, int userId, boolean enabled);
+}
diff --git a/src/com/android/settings/security/screenlock/PatternDotsVisiblePreferenceController.java b/src/com/android/settings/security/screenlock/PatternDotsVisiblePreferenceController.java
new file mode 100644
index 00000000000..8da044d449b
--- /dev/null
+++ b/src/com/android/settings/security/screenlock/PatternDotsVisiblePreferenceController.java
@@ -0,0 +1,41 @@
+/*
+ * 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.settings.security.screenlock;
+
+import android.content.Context;
+
+import com.android.internal.widget.LockPatternUtils;
+
+public class PatternDotsVisiblePreferenceController
+ extends AbstractPatternSwitchPreferenceController {
+ private static final String PREF_KEY = "visibledots";
+
+ public PatternDotsVisiblePreferenceController(Context context, int userId,
+ LockPatternUtils lockPatternUtils) {
+ super(context, PREF_KEY, userId, lockPatternUtils);
+ }
+
+ @Override
+ protected boolean isEnabled(LockPatternUtils utils, int userId) {
+ return utils.isVisibleDotsEnabled(userId);
+ }
+
+ @Override
+ protected void setEnabled(LockPatternUtils utils, int userId, boolean enabled) {
+ utils.setVisibleDotsEnabled(enabled, userId);
+ }
+}
diff --git a/src/com/android/settings/security/screenlock/PatternErrorVisiblePreferenceController.java b/src/com/android/settings/security/screenlock/PatternErrorVisiblePreferenceController.java
new file mode 100644
index 00000000000..b9a18c194fd
--- /dev/null
+++ b/src/com/android/settings/security/screenlock/PatternErrorVisiblePreferenceController.java
@@ -0,0 +1,41 @@
+/*
+ * 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.settings.security.screenlock;
+
+import android.content.Context;
+
+import com.android.internal.widget.LockPatternUtils;
+
+public class PatternErrorVisiblePreferenceController
+ extends AbstractPatternSwitchPreferenceController {
+ private static final String PREF_KEY = "visible_error_pattern";
+
+ public PatternErrorVisiblePreferenceController(Context context, int userId,
+ LockPatternUtils lockPatternUtils) {
+ super(context, PREF_KEY, userId, lockPatternUtils);
+ }
+
+ @Override
+ protected boolean isEnabled(LockPatternUtils utils, int userId) {
+ return utils.isShowErrorPath(userId);
+ }
+
+ @Override
+ protected void setEnabled(LockPatternUtils utils, int userId, boolean enabled) {
+ utils.setShowErrorPath(enabled, userId);
+ }
+}
diff --git a/src/com/android/settings/security/screenlock/PatternVisiblePreferenceController.java b/src/com/android/settings/security/screenlock/PatternVisiblePreferenceController.java
index ea3403bd947..2f8b641a87e 100644
--- a/src/com/android/settings/security/screenlock/PatternVisiblePreferenceController.java
+++ b/src/com/android/settings/security/screenlock/PatternVisiblePreferenceController.java
@@ -18,52 +18,23 @@
import android.content.Context;
-import androidx.preference.Preference;
-import androidx.preference.TwoStatePreference;
-
import com.android.internal.widget.LockPatternUtils;
-import com.android.settings.core.PreferenceControllerMixin;
-import com.android.settingslib.core.AbstractPreferenceController;
-
-public class PatternVisiblePreferenceController extends AbstractPreferenceController
- implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener {
+public class PatternVisiblePreferenceController extends AbstractPatternSwitchPreferenceController {
private static final String PREF_KEY = "visiblepattern";
- private final int mUserId;
- private final LockPatternUtils mLockPatternUtils;
-
public PatternVisiblePreferenceController(Context context, int userId,
LockPatternUtils lockPatternUtils) {
- super(context);
- mUserId = userId;
- mLockPatternUtils = lockPatternUtils;
- }
-
- @Override
- public boolean isAvailable() {
- return isPatternLock();
+ super(context, PREF_KEY, userId, lockPatternUtils);
}
@Override
- public String getPreferenceKey() {
- return PREF_KEY;
- }
-
- @Override
- public void updateState(Preference preference) {
- ((TwoStatePreference) preference).setChecked(
- mLockPatternUtils.isVisiblePatternEnabled(mUserId));
- }
-
- private boolean isPatternLock() {
- return mLockPatternUtils.getCredentialTypeForUser(mUserId)
- == LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+ protected boolean isEnabled(LockPatternUtils utils, int userId) {
+ return utils.isVisiblePatternEnabled(userId);
}
@Override
- public boolean onPreferenceChange(Preference preference, Object newValue) {
- mLockPatternUtils.setVisiblePatternEnabled((Boolean) newValue, mUserId);
- return true;
+ protected void setEnabled(LockPatternUtils utils, int userId, boolean enabled) {
+ utils.setVisiblePatternEnabled(enabled, userId);
}
}
diff --git a/src/com/android/settings/security/screenlock/PinScramblePreferenceController.java b/src/com/android/settings/security/screenlock/PinScramblePreferenceController.java
new file mode 100644
index 00000000000..5f21b35f070
--- /dev/null
+++ b/src/com/android/settings/security/screenlock/PinScramblePreferenceController.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2018 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.security.screenlock;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import androidx.preference.Preference;
+import androidx.preference.TwoStatePreference;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settingslib.core.AbstractPreferenceController;
+
+import android.provider.Settings;
+
+public class PinScramblePreferenceController extends AbstractPreferenceController
+ implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener {
+
+ static final String KEY_LOCKSCREEN_SCRAMBLE_PIN_LAYOUT = "lockscreen_scramble_pin_layout";
+
+ private final int mUserId;
+ private final LockPatternUtils mLockPatternUtils;
+
+ public PinScramblePreferenceController(Context context, int userId,
+ LockPatternUtils lockPatternUtils) {
+ super(context);
+ mUserId = userId;
+ mLockPatternUtils = lockPatternUtils;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return isPinLock();
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY_LOCKSCREEN_SCRAMBLE_PIN_LAYOUT;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ ((TwoStatePreference) preference).setChecked(Settings.System.getInt(
+ mContext.getContentResolver(),
+ Settings.System.LOCKSCREEN_PIN_SCRAMBLE_LAYOUT,
+ 0) == 1);
+ }
+
+ private boolean isPinLock() {
+ int quality = mLockPatternUtils.getKeyguardStoredPasswordQuality(mUserId);
+ boolean hasPin = quality == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC ||
+ quality == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
+ return mLockPatternUtils.isSecure(mUserId) && hasPin;
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ Settings.System.putInt(
+ mContext.getContentResolver(),
+ Settings.System.LOCKSCREEN_PIN_SCRAMBLE_LAYOUT,
+ (Boolean) newValue ? 1 : 0);
+ return true;
+ }
+}
diff --git a/src/com/android/settings/security/screenlock/ScreenLockSettings.java b/src/com/android/settings/security/screenlock/ScreenLockSettings.java
index 1c66b711382..0d67ea338d0 100644
--- a/src/com/android/settings/security/screenlock/ScreenLockSettings.java
+++ b/src/com/android/settings/security/screenlock/ScreenLockSettings.java
@@ -82,12 +82,20 @@ private static List buildPreferenceControllers(Con
context, MY_USER_ID, lockPatternUtils));
controllers.add(new PinPrivacyPreferenceController(
context, MY_USER_ID, lockPatternUtils));
+ controllers.add(new PatternErrorVisiblePreferenceController(
+ context, MY_USER_ID, lockPatternUtils));
+ controllers.add(new PatternDotsVisiblePreferenceController(
+ context, MY_USER_ID, lockPatternUtils));
controllers.add(new PowerButtonInstantLockPreferenceController(
context, MY_USER_ID, lockPatternUtils));
controllers.add(new LockAfterTimeoutPreferenceController(
context, MY_USER_ID, lockPatternUtils));
controllers.add(new AutoPinConfirmPreferenceController(
context, MY_USER_ID, lockPatternUtils, parent));
+ controllers.add(new PinScramblePreferenceController(
+ context, MY_USER_ID, lockPatternUtils));
+ controllers.add(new StatusBarPreferenceController(
+ context, MY_USER_ID, lockPatternUtils));
controllers.add(new OwnerInfoPreferenceController(context, parent));
return controllers;
}
diff --git a/src/com/android/settings/security/screenlock/StatusBarPreferenceController.java b/src/com/android/settings/security/screenlock/StatusBarPreferenceController.java
new file mode 100644
index 00000000000..d119c36c46c
--- /dev/null
+++ b/src/com/android/settings/security/screenlock/StatusBarPreferenceController.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2018 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.security.screenlock;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+
+import androidx.preference.Preference;
+import androidx.preference.TwoStatePreference;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settingslib.core.AbstractPreferenceController;
+
+import android.provider.Settings;
+
+public class StatusBarPreferenceController extends AbstractPreferenceController
+ implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener {
+
+ static final String KEY_STATUS_BAR_SHOWN_ON_SECURE_KEYGUARD =
+ "status_bar_shown_on_secure_keyguard";
+
+ private final int mUserId;
+ private final LockPatternUtils mLockPatternUtils;
+
+ public StatusBarPreferenceController(Context context, int userId,
+ LockPatternUtils lockPatternUtils) {
+ super(context);
+ mUserId = userId;
+ mLockPatternUtils = lockPatternUtils;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ if (!mLockPatternUtils.isSecure(mUserId)) {
+ return false;
+ }
+ switch (mLockPatternUtils.getKeyguardStoredPasswordQuality(mUserId)) {
+ case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
+ case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
+ case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX:
+ case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
+ case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
+ case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
+ case DevicePolicyManager.PASSWORD_QUALITY_MANAGED:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY_STATUS_BAR_SHOWN_ON_SECURE_KEYGUARD;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ ((TwoStatePreference) preference).setChecked(Settings.Secure.getInt(
+ mContext.getContentResolver(),
+ Settings.Secure.QS_TILES_TOGGLEABLE_ON_LOCK_SCREEN,
+ 1) == 1);
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ Settings.Secure.putInt(
+ mContext.getContentResolver(),
+ Settings.Secure.QS_TILES_TOGGLEABLE_ON_LOCK_SCREEN,
+ (Boolean) newValue ? 1 : 0);
+ return true;
+ }
+}
diff --git a/src/com/android/settings/slices/CustomSliceRegistry.java b/src/com/android/settings/slices/CustomSliceRegistry.java
index 8f301066e4a..e4bd841111a 100644
--- a/src/com/android/settings/slices/CustomSliceRegistry.java
+++ b/src/com/android/settings/slices/CustomSliceRegistry.java
@@ -35,6 +35,7 @@
import com.android.settings.homepage.contextualcards.slices.FaceSetupSlice;
import com.android.settings.homepage.contextualcards.slices.LowStorageSlice;
import com.android.settings.location.LocationSlice;
+import com.android.settings.media.AppVolumeSlice;
import com.android.settings.media.MediaOutputIndicatorSlice;
import com.android.settings.media.RemoteMediaSlice;
import com.android.settings.network.ProviderModelSlice;
@@ -293,6 +294,16 @@ public class CustomSliceRegistry {
.appendPath("always_on_display")
.build();
+ /**
+ * Backing Uri for the App Volume Slice.
+ */
+ public static Uri APP_VOLUME_SLICE_URI = new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(SettingsSliceProvider.SLICE_AUTHORITY)
+ .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
+ .appendPath("app_volume")
+ .build();
+
@VisibleForTesting
static final Map> sUriToSlice;
@@ -307,6 +318,7 @@ public class CustomSliceRegistry {
sUriToSlice.put(ALWAYS_ON_SLICE_URI, AlwaysOnDisplaySlice.class);
sUriToSlice.put(MEDIA_OUTPUT_INDICATOR_SLICE_URI, MediaOutputIndicatorSlice.class);
sUriToSlice.put(REMOTE_MEDIA_SLICE_URI, RemoteMediaSlice.class);
+ sUriToSlice.put(APP_VOLUME_SLICE_URI, AppVolumeSlice.class);
// Slices for contextual card.
sUriToSlice.put(FACE_ENROLL_SLICE_URI, FaceSetupSlice.class);
diff --git a/src/com/android/settings/slices/SlicesDatabaseHelper.java b/src/com/android/settings/slices/SlicesDatabaseHelper.java
index cad045e549b..a5ecc345186 100644
--- a/src/com/android/settings/slices/SlicesDatabaseHelper.java
+++ b/src/com/android/settings/slices/SlicesDatabaseHelper.java
@@ -246,6 +246,6 @@ private boolean isLocaleIndexed() {
@VisibleForTesting
String getBuildTag() {
- return Build.FINGERPRINT;
+ return Build.VERSION.INCREMENTAL;
}
-}
\ No newline at end of file
+}
diff --git a/src/com/android/settings/sound/AdaptivePlaybackParentPreferenceController.java b/src/com/android/settings/sound/AdaptivePlaybackParentPreferenceController.java
new file mode 100644
index 00000000000..61383fa3eb5
--- /dev/null
+++ b/src/com/android/settings/sound/AdaptivePlaybackParentPreferenceController.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2020-2021 Paranoid Android
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.sound;
+
+import static android.provider.Settings.System.ADAPTIVE_PLAYBACK_ENABLED;
+import static android.provider.Settings.System.ADAPTIVE_PLAYBACK_TIMEOUT;
+
+import static com.android.settings.sound.AdaptivePlaybackSoundPreferenceController.ADAPTIVE_PLAYBACK_TIMEOUT_10_MIN;
+import static com.android.settings.sound.AdaptivePlaybackSoundPreferenceController.ADAPTIVE_PLAYBACK_TIMEOUT_1_MIN;
+import static com.android.settings.sound.AdaptivePlaybackSoundPreferenceController.ADAPTIVE_PLAYBACK_TIMEOUT_2_MIN;
+import static com.android.settings.sound.AdaptivePlaybackSoundPreferenceController.ADAPTIVE_PLAYBACK_TIMEOUT_30_SECS;
+import static com.android.settings.sound.AdaptivePlaybackSoundPreferenceController.ADAPTIVE_PLAYBACK_TIMEOUT_5_MIN;
+import static com.android.settings.sound.AdaptivePlaybackSoundPreferenceController.ADAPTIVE_PLAYBACK_TIMEOUT_NONE;
+
+import android.content.Context;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+
+public class AdaptivePlaybackParentPreferenceController extends BasePreferenceController {
+
+ public AdaptivePlaybackParentPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ boolean enabled = Settings.System.getIntForUser(
+ mContext.getContentResolver(), ADAPTIVE_PLAYBACK_ENABLED, 0,
+ UserHandle.USER_CURRENT) != 0;
+ int timeout = Settings.System.getIntForUser(
+ mContext.getContentResolver(), ADAPTIVE_PLAYBACK_TIMEOUT,
+ ADAPTIVE_PLAYBACK_TIMEOUT_30_SECS,
+ UserHandle.USER_CURRENT);
+ int summary = R.string.adaptive_playback_disabled_summary;
+ if (!enabled) {
+ return mContext.getText(summary);
+ }
+ switch (timeout) {
+ case ADAPTIVE_PLAYBACK_TIMEOUT_NONE ->
+ summary = R.string.adaptive_playback_timeout_none_summary;
+ case ADAPTIVE_PLAYBACK_TIMEOUT_30_SECS ->
+ summary = R.string.adaptive_playback_timeout_30_secs_summary;
+ case ADAPTIVE_PLAYBACK_TIMEOUT_1_MIN ->
+ summary = R.string.adaptive_playback_timeout_1_min_summary;
+ case ADAPTIVE_PLAYBACK_TIMEOUT_2_MIN ->
+ summary = R.string.adaptive_playback_timeout_2_min_summary;
+ case ADAPTIVE_PLAYBACK_TIMEOUT_5_MIN ->
+ summary = R.string.adaptive_playback_timeout_5_min_summary;
+ case ADAPTIVE_PLAYBACK_TIMEOUT_10_MIN ->
+ summary = R.string.adaptive_playback_timeout_10_min_summary;
+ }
+ return mContext.getText(summary);
+ }
+}
diff --git a/src/com/android/settings/sound/AdaptivePlaybackSoundPreferenceController.java b/src/com/android/settings/sound/AdaptivePlaybackSoundPreferenceController.java
new file mode 100644
index 00000000000..9cf0be44cf3
--- /dev/null
+++ b/src/com/android/settings/sound/AdaptivePlaybackSoundPreferenceController.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2020-2021 Paranoid Android
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.sound;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
+import com.android.settingslib.widget.SelectorWithWidgetPreference;
+
+public class AdaptivePlaybackSoundPreferenceController extends BasePreferenceController
+ implements SelectorWithWidgetPreference.OnClickListener, LifecycleObserver, OnStart, OnStop {
+
+ private static final String KEY_NO_TIMEOUT = "adaptive_playback_timeout_none";
+ private static final String KEY_30_SECS = "adaptive_playback_timeout_30_secs";
+ private static final String KEY_1_MIN = "adaptive_playback_timeout_1_min";
+ private static final String KEY_2_MIN = "adaptive_playback_timeout_2_min";
+ private static final String KEY_5_MIN = "adaptive_playback_timeout_5_min";
+ private static final String KEY_10_MIN = "adaptive_playback_timeout_10_min";
+
+ static final int ADAPTIVE_PLAYBACK_TIMEOUT_NONE = 0;
+ static final int ADAPTIVE_PLAYBACK_TIMEOUT_30_SECS = 30000;
+ static final int ADAPTIVE_PLAYBACK_TIMEOUT_1_MIN = 60000;
+ static final int ADAPTIVE_PLAYBACK_TIMEOUT_2_MIN = 120000;
+ static final int ADAPTIVE_PLAYBACK_TIMEOUT_5_MIN = 300000;
+ static final int ADAPTIVE_PLAYBACK_TIMEOUT_10_MIN = 600000;
+
+ private boolean mAdaptivePlaybackEnabled;
+ private int mAdaptivePlaybackTimeout;
+
+ private PreferenceCategory mPreferenceCategory;
+ private SelectorWithWidgetPreference mTimeoutNonePref;
+ private SelectorWithWidgetPreference mTimeout30SecPref;
+ private SelectorWithWidgetPreference mTimeout1MinPref;
+ private SelectorWithWidgetPreference mTimeout2MinPref;
+ private SelectorWithWidgetPreference mTimeout5MinPref;
+ private SelectorWithWidgetPreference mTimeout10MinPref;
+
+ private final SettingObserver mSettingObserver;
+
+ public AdaptivePlaybackSoundPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+
+ mSettingObserver = new SettingObserver(new Handler(Looper.getMainLooper()));
+ mAdaptivePlaybackEnabled = Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.ADAPTIVE_PLAYBACK_ENABLED, 0, UserHandle.USER_CURRENT) != 0;
+ mAdaptivePlaybackTimeout = Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.ADAPTIVE_PLAYBACK_TIMEOUT, ADAPTIVE_PLAYBACK_TIMEOUT_30_SECS,
+ UserHandle.USER_CURRENT);
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+
+ mPreferenceCategory = screen.findPreference(getPreferenceKey());
+ mTimeoutNonePref = makeRadioPreference(KEY_NO_TIMEOUT,
+ R.string.adaptive_playback_timeout_none);
+ mTimeout30SecPref = makeRadioPreference(KEY_30_SECS,
+ R.string.adaptive_playback_timeout_30_secs);
+ mTimeout1MinPref = makeRadioPreference(KEY_1_MIN, R.string.adaptive_playback_timeout_1_min);
+ mTimeout2MinPref = makeRadioPreference(KEY_2_MIN, R.string.adaptive_playback_timeout_2_min);
+ mTimeout5MinPref = makeRadioPreference(KEY_5_MIN, R.string.adaptive_playback_timeout_5_min);
+ mTimeout10MinPref = makeRadioPreference(KEY_10_MIN,
+ R.string.adaptive_playback_timeout_10_min);
+ updateState(null);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE_UNSEARCHABLE;
+ }
+
+ @Override
+ public void onRadioButtonClicked(SelectorWithWidgetPreference preference) {
+ int adaptivePlaybackTimeout = keyToSetting(preference.getKey());
+ if (adaptivePlaybackTimeout != Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.ADAPTIVE_PLAYBACK_TIMEOUT, ADAPTIVE_PLAYBACK_TIMEOUT_30_SECS,
+ UserHandle.USER_CURRENT)) {
+ Settings.System.putIntForUser(mContext.getContentResolver(),
+ Settings.System.ADAPTIVE_PLAYBACK_TIMEOUT, adaptivePlaybackTimeout,
+ UserHandle.USER_CURRENT);
+ }
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ final boolean isTimeoutNone = mAdaptivePlaybackEnabled
+ && mAdaptivePlaybackTimeout == ADAPTIVE_PLAYBACK_TIMEOUT_NONE;
+ final boolean isTimeout30Sec = mAdaptivePlaybackEnabled
+ && mAdaptivePlaybackTimeout == ADAPTIVE_PLAYBACK_TIMEOUT_30_SECS;
+ final boolean isTimeout1Min = mAdaptivePlaybackEnabled
+ && mAdaptivePlaybackTimeout == ADAPTIVE_PLAYBACK_TIMEOUT_1_MIN;
+ final boolean isTimeout2Min = mAdaptivePlaybackEnabled
+ && mAdaptivePlaybackTimeout == ADAPTIVE_PLAYBACK_TIMEOUT_2_MIN;
+ final boolean isTimeout5Min = mAdaptivePlaybackEnabled
+ && mAdaptivePlaybackTimeout == ADAPTIVE_PLAYBACK_TIMEOUT_5_MIN;
+ final boolean isTimeout10Min = mAdaptivePlaybackEnabled
+ && mAdaptivePlaybackTimeout == ADAPTIVE_PLAYBACK_TIMEOUT_10_MIN;
+ if (mTimeoutNonePref != null && mTimeoutNonePref.isChecked() != isTimeoutNone) {
+ mTimeoutNonePref.setChecked(isTimeoutNone);
+ }
+ if (mTimeout30SecPref != null && mTimeout30SecPref.isChecked() != isTimeout30Sec) {
+ mTimeout30SecPref.setChecked(isTimeout30Sec);
+ }
+ if (mTimeout1MinPref != null && mTimeout1MinPref.isChecked() != isTimeout1Min) {
+ mTimeout1MinPref.setChecked(isTimeout1Min);
+ }
+ if (mTimeout2MinPref != null && mTimeout2MinPref.isChecked() != isTimeout2Min) {
+ mTimeout2MinPref.setChecked(isTimeout2Min);
+ }
+ if (mTimeout5MinPref != null && mTimeout5MinPref.isChecked() != isTimeout5Min) {
+ mTimeout5MinPref.setChecked(isTimeout5Min);
+ }
+ if (mTimeout10MinPref != null && mTimeout10MinPref.isChecked() != isTimeout10Min) {
+ mTimeout10MinPref.setChecked(isTimeout10Min);
+ }
+
+ if (mAdaptivePlaybackEnabled) {
+ mPreferenceCategory.setEnabled(true);
+ mTimeoutNonePref.setEnabled(true);
+ mTimeout30SecPref.setEnabled(true);
+ mTimeout1MinPref.setEnabled(true);
+ mTimeout2MinPref.setEnabled(true);
+ mTimeout5MinPref.setEnabled(true);
+ mTimeout10MinPref.setEnabled(true);
+ } else {
+ mPreferenceCategory.setEnabled(false);
+ mTimeoutNonePref.setEnabled(false);
+ mTimeout30SecPref.setEnabled(false);
+ mTimeout1MinPref.setEnabled(false);
+ mTimeout2MinPref.setEnabled(false);
+ mTimeout5MinPref.setEnabled(false);
+ mTimeout10MinPref.setEnabled(false);
+ }
+ }
+
+ @Override
+ public void onStart() {
+ mSettingObserver.observe();
+ }
+
+ @Override
+ public void onStop() {
+ mContext.getContentResolver().unregisterContentObserver(mSettingObserver);
+ }
+
+ private static int keyToSetting(String key) {
+ switch (key) {
+ case KEY_NO_TIMEOUT:
+ return ADAPTIVE_PLAYBACK_TIMEOUT_NONE;
+ case KEY_1_MIN:
+ return ADAPTIVE_PLAYBACK_TIMEOUT_1_MIN;
+ case KEY_2_MIN:
+ return ADAPTIVE_PLAYBACK_TIMEOUT_2_MIN;
+ case KEY_5_MIN:
+ return ADAPTIVE_PLAYBACK_TIMEOUT_5_MIN;
+ case KEY_10_MIN:
+ return ADAPTIVE_PLAYBACK_TIMEOUT_10_MIN;
+ default:
+ return ADAPTIVE_PLAYBACK_TIMEOUT_30_SECS;
+ }
+ }
+
+ private SelectorWithWidgetPreference makeRadioPreference(String key, int titleId) {
+ SelectorWithWidgetPreference pref = new SelectorWithWidgetPreference(mPreferenceCategory.getContext());
+ pref.setKey(key);
+ pref.setTitle(titleId);
+ pref.setOnClickListener(this);
+ mPreferenceCategory.addPreference(pref);
+ return pref;
+ }
+
+ private final class SettingObserver extends ContentObserver {
+ private final Uri ADAPTIVE_PLAYBACK = Settings.System.getUriFor(
+ Settings.System.ADAPTIVE_PLAYBACK_ENABLED);
+ private final Uri ADAPTIVE_PLAYBACK_TIMEOUT = Settings.System.getUriFor(
+ Settings.System.ADAPTIVE_PLAYBACK_TIMEOUT);
+
+ public SettingObserver(Handler handler) {
+ super(handler);
+ }
+
+ public void observe() {
+ final ContentResolver cr = mContext.getContentResolver();
+ cr.registerContentObserver(ADAPTIVE_PLAYBACK, false, this, UserHandle.USER_ALL);
+ cr.registerContentObserver(ADAPTIVE_PLAYBACK_TIMEOUT, false, this, UserHandle.USER_ALL);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (ADAPTIVE_PLAYBACK.equals(uri) || ADAPTIVE_PLAYBACK_TIMEOUT.equals(uri)) {
+ mAdaptivePlaybackEnabled = Settings.System.getIntForUser(
+ mContext.getContentResolver(), Settings.System.ADAPTIVE_PLAYBACK_ENABLED, 0,
+ UserHandle.USER_CURRENT) != 0;
+ mAdaptivePlaybackTimeout = Settings.System.getIntForUser(
+ mContext.getContentResolver(), Settings.System.ADAPTIVE_PLAYBACK_TIMEOUT,
+ ADAPTIVE_PLAYBACK_TIMEOUT_30_SECS, UserHandle.USER_CURRENT);
+ updateState(null);
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/sound/AdaptivePlaybackSoundSettings.java b/src/com/android/settings/sound/AdaptivePlaybackSoundSettings.java
new file mode 100644
index 00000000000..e74723e1a26
--- /dev/null
+++ b/src/com/android/settings/sound/AdaptivePlaybackSoundSettings.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020-2021 Paranoid Android
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.sound;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+
+public class AdaptivePlaybackSoundSettings extends DashboardFragment {
+
+ private static final String TAG = "AdaptivePlaybackSoundSettings";
+
+ @Override
+ public int getMetricsCategory() {
+ return -1;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.adaptive_playback_sound_settings;
+ }
+
+ public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider(R.xml.adaptive_playback_sound_settings);
+}
diff --git a/src/com/android/settings/sound/AdaptivePlaybackSwitchPreferenceController.java b/src/com/android/settings/sound/AdaptivePlaybackSwitchPreferenceController.java
new file mode 100644
index 00000000000..5b11ae1e5cb
--- /dev/null
+++ b/src/com/android/settings/sound/AdaptivePlaybackSwitchPreferenceController.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2020-2022 Paranoid Android
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.sound;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.widget.SettingsMainSwitchPreferenceController;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
+import com.android.settingslib.widget.MainSwitchPreference;
+
+public class AdaptivePlaybackSwitchPreferenceController extends
+ SettingsMainSwitchPreferenceController implements LifecycleObserver, OnStart, OnStop {
+
+ private MainSwitchPreference mPreference;
+ private final SettingsObserver mSettingsObserver;
+
+ public AdaptivePlaybackSwitchPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ mSettingsObserver = new SettingsObserver(new Handler(Looper.getMainLooper()));
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+
+ @Override
+ public boolean isChecked() {
+ return Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.ADAPTIVE_PLAYBACK_ENABLED, 0, UserHandle.USER_CURRENT) != 0;
+ }
+
+ @Override
+ public boolean setChecked(boolean isChecked) {
+ Settings.System.putIntForUser(mContext.getContentResolver(),
+ Settings.System.ADAPTIVE_PLAYBACK_ENABLED, isChecked ? 1 : 0,
+ UserHandle.USER_CURRENT);
+ return true;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mPreference = screen.findPreference(getPreferenceKey());
+ }
+
+ @Override
+ public void onStart() {
+ mSettingsObserver.observe();
+ }
+
+ @Override
+ public void onStop() {
+ mContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
+ }
+
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ Settings.System.putIntForUser(mContext.getContentResolver(),
+ Settings.System.ADAPTIVE_PLAYBACK_ENABLED, isChecked ? 1 : 0,
+ UserHandle.USER_CURRENT);
+ }
+
+ @Override
+ public int getSliceHighlightMenuRes() {
+ return NO_RES;
+ }
+
+ private final class SettingsObserver extends ContentObserver {
+ private final Uri ADAPTIVE_PLAYBACK = Settings.System.getUriFor(
+ Settings.System.ADAPTIVE_PLAYBACK_ENABLED);
+
+ public SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ public void observe() {
+ mContext.getContentResolver().registerContentObserver(ADAPTIVE_PLAYBACK, false, this,
+ UserHandle.USER_ALL);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ super.onChange(selfChange, uri);
+ if (ADAPTIVE_PLAYBACK.equals(uri)) {
+ mPreference.setChecked(isChecked());
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/sound/CustomNotificationVibrationPreferenceController.java b/src/com/android/settings/sound/CustomNotificationVibrationPreferenceController.java
new file mode 100644
index 00000000000..735aa24ff68
--- /dev/null
+++ b/src/com/android/settings/sound/CustomNotificationVibrationPreferenceController.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 Yet Another AOSP Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.sound;
+
+import android.content.Context;
+import android.provider.Settings;
+
+public class CustomNotificationVibrationPreferenceController extends CustomVibrationPreferenceController {
+ private static final String DEFAULT_SETTINGS_VALUE = "80,40,0";
+
+ public CustomNotificationVibrationPreferenceController(Context context) {
+ super(context);
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return mVibrator.hasVibrator();
+ }
+
+ @Override
+ protected String getSettingsKey() {
+ return Settings.System.CUSTOM_NOTIFICATION_VIBRATION_PATTERN;
+ }
+
+ @Override
+ protected String getDefaultValue() {
+ return DEFAULT_SETTINGS_VALUE;
+ }
+
+ @Override
+ protected long getDelay() {
+ return 120;
+ }
+}
diff --git a/src/com/android/settings/sound/CustomNotificationVibrationPreferenceFragment.java b/src/com/android/settings/sound/CustomNotificationVibrationPreferenceFragment.java
new file mode 100644
index 00000000000..0b03893cd62
--- /dev/null
+++ b/src/com/android/settings/sound/CustomNotificationVibrationPreferenceFragment.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 Yet Another AOSP Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.sound;
+
+import android.content.Context;
+
+import androidx.annotation.XmlRes;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settingslib.core.AbstractPreferenceController;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Settings for custom notification vibration pattern
+ */
+public class CustomNotificationVibrationPreferenceFragment extends DashboardFragment {
+
+ private static final String TAG = "CustomNotificationVibrationPreferenceFragment";
+
+ @Override
+ public void addPreferencesFromResource(@XmlRes int preferencesResId) {
+ super.addPreferencesFromResource(preferencesResId);
+ getActivity().setTitle(getContext().getText(
+ R.string.notification_vibration_pattern_title));
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsEvent.DERP;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.custom_vibration_pattern;
+ }
+
+ @Override
+ protected List createPreferenceControllers(Context context) {
+ final List controllers = new ArrayList<>();
+ controllers.add(new CustomNotificationVibrationPreferenceController(context));
+ return controllers;
+ }
+}
diff --git a/src/com/android/settings/sound/CustomVibrationPreferenceController.java b/src/com/android/settings/sound/CustomVibrationPreferenceController.java
new file mode 100644
index 00000000000..f64f38c6c75
--- /dev/null
+++ b/src/com/android/settings/sound/CustomVibrationPreferenceController.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2020 Yet Another AOSP Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.sound;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.net.Uri;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.provider.Settings;
+
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.sound.CustomVibrationPreferenceController;
+import com.android.settings.Utils;
+import com.android.settingslib.core.AbstractPreferenceController;
+
+import org.derpfest.support.preferences.ProperSeekBarPreference;
+
+/**
+ * This class allows choosing a vibration pattern while ringing
+ */
+public class CustomVibrationPreferenceController extends AbstractPreferenceController
+ implements Preference.OnPreferenceChangeListener {
+
+ private static final String KEY = "custom_vibration_pattern";
+ private static final String KEY_CUSTOM_VIB1 = "custom_vibration_pattern1";
+ private static final String KEY_CUSTOM_VIB2 = "custom_vibration_pattern2";
+ private static final String KEY_CUSTOM_VIB3 = "custom_vibration_pattern3";
+ private static final String DEFAULT_SETTINGS_VALUE = "0,800,800";
+
+ private ProperSeekBarPreference mCustomVib1;
+ private ProperSeekBarPreference mCustomVib2;
+ private ProperSeekBarPreference mCustomVib3;
+
+ protected final Vibrator mVibrator;
+
+ private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+ .build();
+
+ private static final int[] SEVEN_ELEMENTS_VIBRATION_AMPLITUDE = {
+ 0, // No delay before starting
+ 255, // Vibrate full amplitude
+ 0, // No amplitude while waiting
+ 255,
+ 0,
+ 255,
+ 0,
+ };
+
+ public CustomVibrationPreferenceController(Context context) {
+ super(context);
+ mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
+ }
+
+ private static class VibrationEffectProxy {
+ public VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
+ return VibrationEffect.createWaveform(timings, amplitudes, repeat);
+ }
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+
+ mCustomVib1 = (ProperSeekBarPreference) screen.findPreference(KEY_CUSTOM_VIB1);
+ mCustomVib2 = (ProperSeekBarPreference) screen.findPreference(KEY_CUSTOM_VIB2);
+ mCustomVib3 = (ProperSeekBarPreference) screen.findPreference(KEY_CUSTOM_VIB3);
+ updateCustomVibPreferences();
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if (preference == mCustomVib1) {
+ updateCustomVib(0, (Integer) newValue);
+ return true;
+ } else if (preference == mCustomVib2) {
+ updateCustomVib(1, (Integer) newValue);
+ return true;
+ } else if (preference == mCustomVib3) {
+ updateCustomVib(2, (Integer) newValue);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return Utils.isVoiceCapable(mContext) && mVibrator.hasVibrator();
+ }
+
+ protected String getSettingsKey() {
+ return Settings.System.CUSTOM_RINGTONE_VIBRATION_PATTERN;
+ }
+
+ protected String getDefaultValue() {
+ return DEFAULT_SETTINGS_VALUE;
+ }
+
+ protected long getDelay() {
+ return 400;
+ }
+
+ private void updateCustomVibPreferences() {
+ String value = Settings.System.getString(
+ mContext.getContentResolver(), getSettingsKey());
+ if (value != null) {
+ String[] customPattern = value.split(",", 3);
+ mCustomVib1.setValue(Integer.parseInt(customPattern[0]));
+ mCustomVib2.setValue(Integer.parseInt(customPattern[1]));
+ mCustomVib3.setValue(Integer.parseInt(customPattern[2]));
+ } else { // set default
+ String[] defaultPattern = getDefaultValue().split(",", 3);
+ mCustomVib1.setValue(Integer.parseInt(defaultPattern[0]));
+ mCustomVib2.setValue(Integer.parseInt(defaultPattern[1]));
+ mCustomVib3.setValue(Integer.parseInt(defaultPattern[2]));
+ Settings.System.putString(
+ mContext.getContentResolver(), getSettingsKey(), getDefaultValue());
+ }
+ mCustomVib1.setOnPreferenceChangeListener(this);
+ mCustomVib2.setOnPreferenceChangeListener(this);
+ mCustomVib3.setOnPreferenceChangeListener(this);
+ }
+
+ private void updateCustomVib(int index, int value) {
+ String[] customPattern = Settings.System.getString(mContext.getContentResolver(),
+ getSettingsKey()).split(",", 3);
+ customPattern[index] = String.valueOf(value);
+ Settings.System.putString(mContext.getContentResolver(),
+ getSettingsKey(), String.join(
+ ",", customPattern[0], customPattern[1], customPattern[2]));
+ previewPattern();
+ }
+
+ private void previewPattern() {
+ VibrationEffect effect;
+ VibrationEffectProxy vibrationEffectProxy = new VibrationEffectProxy();
+ String[] customVib = Settings.System.getString(
+ mContext.getContentResolver(),
+ getSettingsKey()).split(",", 3);
+ long[] customVibPattern = {
+ 0, // No delay before starting
+ Long.parseLong(customVib[0]), // How long to vibrate
+ getDelay(), // Delay
+ Long.parseLong(customVib[1]), // How long to vibrate
+ getDelay(), // Delay
+ Long.parseLong(customVib[2]), // How long to vibrate
+ getDelay(), // How long to wait before vibrating again
+ };
+ effect = vibrationEffectProxy.createWaveform(customVibPattern,
+ SEVEN_ELEMENTS_VIBRATION_AMPLITUDE, -1);
+ mVibrator.vibrate(effect, VIBRATION_ATTRIBUTES);
+ }
+
+}
diff --git a/src/com/android/settings/sound/CustomVibrationPreferenceFragment.java b/src/com/android/settings/sound/CustomVibrationPreferenceFragment.java
new file mode 100644
index 00000000000..cd713a005af
--- /dev/null
+++ b/src/com/android/settings/sound/CustomVibrationPreferenceFragment.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2020 Yet Another AOSP Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.sound;
+
+import android.content.Context;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settingslib.core.AbstractPreferenceController;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Settings for custom ringtone vibration pattern
+ */
+public class CustomVibrationPreferenceFragment extends DashboardFragment {
+
+ private static final String TAG = "CustomVibrationPreferenceFragment";
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsEvent.DERP;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.custom_vibration_pattern;
+ }
+
+ @Override
+ protected List createPreferenceControllers(Context context) {
+ final List controllers = new ArrayList<>();
+ controllers.add(new CustomVibrationPreferenceController(context));
+ return controllers;
+ }
+}
diff --git a/src/com/android/settings/sound/HapticSettings.java b/src/com/android/settings/sound/HapticSettings.java
new file mode 100644
index 00000000000..497936441a8
--- /dev/null
+++ b/src/com/android/settings/sound/HapticSettings.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 The Nameless-AOSP Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.settings.sound;
+
+import android.os.Bundle;
+
+import androidx.preference.Preference;
+
+import com.android.internal.logging.nano.MetricsProto;
+
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+
+public class HapticSettings extends SettingsPreferenceFragment {
+
+ @Override
+ public void onCreate(Bundle savedInstance) {
+ super.onCreate(savedInstance);
+ addPreferencesFromResource(R.xml.haptic_settings);
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsProto.MetricsEvent.DERP;
+ }
+}
diff --git a/src/com/android/settings/sound/PulseSettings.java b/src/com/android/settings/sound/PulseSettings.java
new file mode 100644
index 00000000000..1f0662caab2
--- /dev/null
+++ b/src/com/android/settings/sound/PulseSettings.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2020 The Dirty Unicorns Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.sound;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.provider.SearchIndexableResource;
+import android.provider.Settings;
+
+import androidx.preference.PreferenceCategory;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.Preference.OnPreferenceChangeListener;
+import androidx.preference.SwitchPreferenceCompat;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.search.Indexable;
+import org.derpfest.support.colorpicker.ColorPickerPreference;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class PulseSettings extends SettingsPreferenceFragment implements
+ Preference.OnPreferenceChangeListener {
+ private static final String TAG = PulseSettings.class.getSimpleName();
+ private static final String NAVBAR_PULSE_ENABLED_KEY = "navbar_pulse_enabled";
+ private static final String LOCKSCREEN_PULSE_ENABLED_KEY = "lockscreen_pulse_enabled";
+ private static final String AMBIENT_PULSE_ENABLED_KEY = "ambient_pulse_enabled";
+ private static final String PULSE_SMOOTHING_KEY = "pulse_smoothing_enabled";
+ private static final String PULSE_COLOR_MODE_KEY = "pulse_color_mode";
+ private static final String PULSE_COLOR_MODE_CHOOSER_KEY = "pulse_color_user";
+ private static final String PULSE_COLOR_MODE_LAVA_SPEED_KEY = "pulse_lavalamp_speed";
+ private static final String PULSE_RENDER_CATEGORY_SOLID = "pulse_2";
+ private static final String PULSE_RENDER_CATEGORY_FADING = "pulse_fading_bars_category";
+ private static final String PULSE_RENDER_MODE_KEY = "pulse_render_style";
+ private static final String PULSE_CUSTOM_GRAVITY = "pulse_custom_gravity";
+ private static final String VISUALIZER_CENTER_MIRRORED = "visualizer_center_mirrored";
+ private static final String PULSE_VERTICAL_MIRROR = "pulse_vertical_mirror";
+ private static final int RENDER_STYLE_FADING_BARS = 0;
+ private static final int RENDER_STYLE_SOLID_LINES = 1;
+ private static final int COLOR_TYPE_ACCENT = 0;
+ private static final int COLOR_TYPE_USER = 1;
+ private static final int COLOR_TYPE_LAVALAMP = 2;
+ private static final int COLOR_TYPE_AUTO = 3;
+
+ private static final String PULSE_SETTINGS_FOOTER = "pulse_settings_footer";
+
+ private SwitchPreferenceCompat mNavbarPulse;
+ private SwitchPreferenceCompat mLockscreenPulse;
+ private SwitchPreferenceCompat mAmbientPulse;
+ private SwitchPreferenceCompat mPulseSmoothing;
+ private SwitchPreferenceCompat mPulseCenterMirrored;
+ private SwitchPreferenceCompat mPulseVerticalMirror;
+ private Preference mRenderMode;
+ private ListPreference mColorModePref;
+ private ColorPickerPreference mColorPickerPref;
+ private ListPreference mPulseGravity;
+ private Preference mLavaSpeedPref;
+ private Preference mFooterPref;
+
+ private PreferenceCategory mFadingBarsCat;
+ private PreferenceCategory mSolidBarsCat;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.pulse_settings);
+
+ ContentResolver resolver = getContext().getContentResolver();
+
+ mNavbarPulse = (SwitchPreferenceCompat) findPreference(NAVBAR_PULSE_ENABLED_KEY);
+ boolean navbarPulse = Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.NAVBAR_PULSE_ENABLED, 0, UserHandle.USER_CURRENT) != 0;
+ mNavbarPulse.setChecked(navbarPulse);
+ mNavbarPulse.setOnPreferenceChangeListener(this);
+
+ mLockscreenPulse = (SwitchPreferenceCompat) findPreference(LOCKSCREEN_PULSE_ENABLED_KEY);
+ boolean lockscreenPulse = Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.LOCKSCREEN_PULSE_ENABLED, 0, UserHandle.USER_CURRENT) != 0;
+ mLockscreenPulse.setChecked(lockscreenPulse);
+ mLockscreenPulse.setOnPreferenceChangeListener(this);
+
+ mAmbientPulse = (SwitchPreferenceCompat) findPreference(AMBIENT_PULSE_ENABLED_KEY);
+ boolean ambientPulse = Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.AMBIENT_PULSE_ENABLED, 0, UserHandle.USER_CURRENT) != 0;
+ mAmbientPulse.setChecked(ambientPulse);
+ mAmbientPulse.setOnPreferenceChangeListener(this);
+
+ mColorModePref = (ListPreference) findPreference(PULSE_COLOR_MODE_KEY);
+ mColorPickerPref = (ColorPickerPreference) findPreference(PULSE_COLOR_MODE_CHOOSER_KEY);
+ mLavaSpeedPref = findPreference(PULSE_COLOR_MODE_LAVA_SPEED_KEY);
+ mColorModePref.setOnPreferenceChangeListener(this);
+
+ mRenderMode = findPreference(PULSE_RENDER_MODE_KEY);
+ mRenderMode.setOnPreferenceChangeListener(this);
+
+ mFadingBarsCat = (PreferenceCategory) findPreference(
+ PULSE_RENDER_CATEGORY_FADING);
+ mSolidBarsCat = (PreferenceCategory) findPreference(
+ PULSE_RENDER_CATEGORY_SOLID);
+
+ mPulseSmoothing = (SwitchPreferenceCompat) findPreference(PULSE_SMOOTHING_KEY);
+
+ mPulseCenterMirrored = (SwitchPreferenceCompat) findPreference(VISUALIZER_CENTER_MIRRORED);
+
+ mPulseVerticalMirror = (SwitchPreferenceCompat) findPreference(PULSE_VERTICAL_MIRROR);
+
+ mPulseGravity = (ListPreference) findPreference(PULSE_CUSTOM_GRAVITY);
+
+ mFooterPref = findPreference(PULSE_SETTINGS_FOOTER);
+ mFooterPref.setTitle(R.string.pulse_help_policy_notice_summary);
+
+ updateAllPrefs();
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ ContentResolver resolver = getContext().getContentResolver();
+ if (preference == mNavbarPulse) {
+ boolean val = (Boolean) newValue;
+ Settings.Secure.putIntForUser(resolver,
+ Settings.Secure.NAVBAR_PULSE_ENABLED, val ? 1 : 0, UserHandle.USER_CURRENT);
+ updateAllPrefs();
+ return true;
+ } else if (preference == mLockscreenPulse) {
+ boolean val = (Boolean) newValue;
+ Settings.Secure.putIntForUser(resolver,
+ Settings.Secure.LOCKSCREEN_PULSE_ENABLED, val ? 1 : 0, UserHandle.USER_CURRENT);
+ updateAllPrefs();
+ return true;
+ } else if (preference == mAmbientPulse) {
+ boolean val = (Boolean) newValue;
+ Settings.Secure.putIntForUser(resolver,
+ Settings.Secure.AMBIENT_PULSE_ENABLED, val ? 1 : 0, UserHandle.USER_CURRENT);
+ updateAllPrefs();
+ return true;
+ } else if (preference == mColorModePref) {
+ updateColorPrefs(Integer.valueOf(String.valueOf(newValue)));
+ return true;
+ } else if (preference == mRenderMode) {
+ updateRenderCategories(Integer.valueOf(String.valueOf(newValue)));
+ return true;
+ }
+ return false;
+ }
+
+ private void updateAllPrefs() {
+ ContentResolver resolver = getContext().getContentResolver();
+
+ boolean navbarPulse = Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.NAVBAR_PULSE_ENABLED, 0, UserHandle.USER_CURRENT) != 0;
+ boolean lockscreenPulse = Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.LOCKSCREEN_PULSE_ENABLED, 0, UserHandle.USER_CURRENT) != 0;
+ boolean ambientPulse = Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.AMBIENT_PULSE_ENABLED, 0, UserHandle.USER_CURRENT) != 0;
+
+ mPulseSmoothing.setEnabled(navbarPulse || lockscreenPulse || ambientPulse);
+
+ mPulseCenterMirrored.setEnabled(navbarPulse || lockscreenPulse || ambientPulse);
+
+ mPulseVerticalMirror.setEnabled(navbarPulse || lockscreenPulse || ambientPulse);
+
+ mPulseGravity.setEnabled(navbarPulse || lockscreenPulse || ambientPulse);
+
+ mColorModePref.setEnabled(navbarPulse || lockscreenPulse || ambientPulse);
+ if (navbarPulse || lockscreenPulse) {
+ int colorMode = Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.PULSE_COLOR_MODE, COLOR_TYPE_LAVALAMP, UserHandle.USER_CURRENT);
+ updateColorPrefs(colorMode);
+ } else {
+ mColorPickerPref.setEnabled(false);
+ mLavaSpeedPref.setEnabled(false);
+ }
+
+ mRenderMode.setEnabled(navbarPulse || lockscreenPulse || ambientPulse);
+ if (navbarPulse || lockscreenPulse || ambientPulse) {
+ int renderMode = Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.PULSE_RENDER_STYLE, RENDER_STYLE_SOLID_LINES, UserHandle.USER_CURRENT);
+ updateRenderCategories(renderMode);
+ } else {
+ mFadingBarsCat.setEnabled(false);
+ mSolidBarsCat.setEnabled(false);
+ }
+
+ mFooterPref.setEnabled(navbarPulse || lockscreenPulse);
+ }
+
+ private void updateColorPrefs(int val) {
+ switch (val) {
+ case COLOR_TYPE_ACCENT:
+ mColorPickerPref.setEnabled(false);
+ mLavaSpeedPref.setEnabled(false);
+ break;
+ case COLOR_TYPE_USER:
+ mColorPickerPref.setEnabled(true);
+ mLavaSpeedPref.setEnabled(false);
+ break;
+ case COLOR_TYPE_LAVALAMP:
+ mColorPickerPref.setEnabled(false);
+ mLavaSpeedPref.setEnabled(true);
+ break;
+ case COLOR_TYPE_AUTO:
+ mColorPickerPref.setEnabled(false);
+ mLavaSpeedPref.setEnabled(false);
+ break;
+ }
+ }
+
+ private void updateRenderCategories(int mode) {
+ mFadingBarsCat.setEnabled(mode == RENDER_STYLE_FADING_BARS);
+ mSolidBarsCat.setEnabled(mode == RENDER_STYLE_SOLID_LINES);
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsProto.MetricsEvent.DERP;
+ }
+
+ public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider() {
+ @Override
+ public List getXmlResourcesToIndex(
+ Context context, boolean enabled) {
+ final SearchIndexableResource sir = new SearchIndexableResource(context);
+ sir.xmlResId = R.xml.pulse_settings;
+ return Arrays.asList(sir);
+ }
+
+ @Override
+ protected boolean isPageSearchEnabled(Context context) {
+ return true;
+ }
+ };
+}
diff --git a/src/com/android/settings/sound/TouchHapticPreferenceController.java b/src/com/android/settings/sound/TouchHapticPreferenceController.java
new file mode 100644
index 00000000000..95676eb2f7a
--- /dev/null
+++ b/src/com/android/settings/sound/TouchHapticPreferenceController.java
@@ -0,0 +1,35 @@
+package com.android.settings.sound;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
+
+import com.android.settings.core.BasePreferenceController;
+
+public class TouchHapticPreferenceController extends BasePreferenceController {
+
+ public TouchHapticPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+
+ @Override
+ public int getSliceHighlightMenuRes() {
+ return 0;
+ }
+}
diff --git a/src/com/android/settings/system/AdditionalSystemUpdatePreferenceController.java b/src/com/android/settings/system/AdditionalSystemUpdatePreferenceController.java
index 868f10f3b50..7dc9ffce2e1 100644
--- a/src/com/android/settings/system/AdditionalSystemUpdatePreferenceController.java
+++ b/src/com/android/settings/system/AdditionalSystemUpdatePreferenceController.java
@@ -29,9 +29,12 @@ public AdditionalSystemUpdatePreferenceController(Context context) {
@Override
public int getAvailabilityStatus() {
+ /*
return mContext.getResources().getBoolean(
com.android.settings.R.bool.config_additional_system_update_setting_enable)
? AVAILABLE
: UNSUPPORTED_ON_DEVICE;
+ */
+ return UNSUPPORTED_ON_DEVICE;
}
-}
\ No newline at end of file
+}
diff --git a/src/com/android/settings/system/SystemDashboardFragment.java b/src/com/android/settings/system/SystemDashboardFragment.java
index 678b675e6ad..1513f07583d 100644
--- a/src/com/android/settings/system/SystemDashboardFragment.java
+++ b/src/com/android/settings/system/SystemDashboardFragment.java
@@ -16,6 +16,7 @@
package com.android.settings.system;
import android.app.settings.SettingsEnums;
+import android.content.Context;
import android.os.Bundle;
import androidx.preference.Preference;
@@ -26,6 +27,11 @@
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;
+import com.android.settings.deviceinfo.UpdatePreferenceController;
+import com.android.settingslib.core.AbstractPreferenceController;
+
+import java.util.ArrayList;
+import java.util.List;
@SearchIndexable
public class SystemDashboardFragment extends DashboardFragment {
@@ -76,6 +82,16 @@ private int getVisiblePreferenceCount(PreferenceGroup group) {
return visibleCount;
}
+ protected List getPreferenceControllers(Context context) {
+ return buildPreferenceControllers(context);
+ }
+
+ private static List buildPreferenceControllers(Context context) {
+ final List controllers = new ArrayList<>();
+ controllers.add(new UpdatePreferenceController(context));
+ return controllers;
+ }
+
/**
* For Search.
*/
diff --git a/src/com/android/settings/system/SystemUpdatePreferenceController.kt b/src/com/android/settings/system/SystemUpdatePreferenceController.kt
index 87a402e2236..525b52681c3 100644
--- a/src/com/android/settings/system/SystemUpdatePreferenceController.kt
+++ b/src/com/android/settings/system/SystemUpdatePreferenceController.kt
@@ -40,9 +40,12 @@ open class SystemUpdatePreferenceController(context: Context, preferenceKey: Str
private lateinit var preference: Preference
override fun getAvailabilityStatus() =
+ /*
if (mContext.resources.getBoolean(R.bool.config_show_system_update_settings) &&
userManager.isAdminUser
) AVAILABLE else UNSUPPORTED_ON_DEVICE
+ */
+ UNSUPPORTED_ON_DEVICE
override fun displayPreference(screen: PreferenceScreen) {
super.displayPreference(screen)
diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java
index bf21c9b913a..dcee497f7d7 100644
--- a/src/com/android/settings/users/UserSettings.java
+++ b/src/com/android/settings/users/UserSettings.java
@@ -1544,7 +1544,7 @@ private Drawable centerAndTint(Drawable icon) {
int getRealUsersCount() {
return (int) mUserManager.getUsers()
.stream()
- .filter(user -> !user.isGuest() && !user.isProfile())
+ .filter(user -> !user.isGuest() && !user.isProfile() && !user.isParallel())
.count();
}
diff --git a/src/com/android/settings/vpn2/AppManagementFragment.java b/src/com/android/settings/vpn2/AppManagementFragment.java
index 00c8f5994ce..65ea4da5049 100644
--- a/src/com/android/settings/vpn2/AppManagementFragment.java
+++ b/src/com/android/settings/vpn2/AppManagementFragment.java
@@ -27,10 +27,12 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.UserInfo;
import android.net.VpnManager;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
@@ -39,6 +41,7 @@
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import androidx.preference.Preference;
+import androidx.preference.SwitchPreferenceCompat;
import com.android.internal.net.VpnConfig;
import com.android.internal.util.ArrayUtils;
@@ -63,6 +66,7 @@ public class AppManagementFragment extends SettingsPreferenceFragment
private static final String ARG_PACKAGE_NAME = "package";
private static final String KEY_VERSION = "version";
+ private static final String KEY_GLOBAL_VPN = "global_vpn";
private static final String KEY_ALWAYS_ON_VPN = "always_on_vpn";
private static final String KEY_LOCKDOWN_VPN = "lockdown_vpn";
private static final String KEY_FORGET_VPN = "forget_vpn";
@@ -80,6 +84,7 @@ public class AppManagementFragment extends SettingsPreferenceFragment
// UI preference
private Preference mPreferenceVersion;
+ private SwitchPreferenceCompat mPreferenceGlobal;
private RestrictedSwitchPreference mPreferenceAlwaysOn;
private RestrictedSwitchPreference mPreferenceLockdown;
private RestrictedPreference mPreferenceForget;
@@ -126,10 +131,16 @@ public void onCreate(Bundle savedState) {
mFeatureProvider = FeatureFactory.getFeatureFactory().getAdvancedVpnFeatureProvider();
mPreferenceVersion = findPreference(KEY_VERSION);
+ mPreferenceGlobal = (SwitchPreferenceCompat) findPreference(KEY_GLOBAL_VPN);
mPreferenceAlwaysOn = (RestrictedSwitchPreference) findPreference(KEY_ALWAYS_ON_VPN);
mPreferenceLockdown = (RestrictedSwitchPreference) findPreference(KEY_LOCKDOWN_VPN);
mPreferenceForget = (RestrictedPreference) findPreference(KEY_FORGET_VPN);
+ if (mUserId != UserHandle.USER_SYSTEM) {
+ removePreference(KEY_GLOBAL_VPN);
+ }
+
+ mPreferenceGlobal.setOnPreferenceChangeListener(this);
mPreferenceAlwaysOn.setOnPreferenceChangeListener(this);
mPreferenceLockdown.setOnPreferenceChangeListener(this);
mPreferenceForget.setOnPreferenceClickListener(this);
@@ -163,6 +174,8 @@ public boolean onPreferenceClick(Preference preference) {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
switch (preference.getKey()) {
+ case KEY_GLOBAL_VPN:
+ return onGlobalVpnClick((Boolean) newValue);
case KEY_ALWAYS_ON_VPN:
return onAlwaysOnVpnClick((Boolean) newValue, mPreferenceLockdown.isChecked());
case KEY_LOCKDOWN_VPN:
@@ -202,6 +215,11 @@ private boolean onAlwaysOnVpnClick(final boolean alwaysOnSetting, final boolean
return setAlwaysOnVpnByUI(alwaysOnSetting, lockdown);
}
+ private boolean onGlobalVpnClick(final boolean global) {
+ return Settings.Global.putString(getContext().getContentResolver(),
+ Settings.Global.GLOBAL_VPN_APP, global ? mPackageName : "");
+ }
+
@Override
public void onConfirmLockdown(Bundle options, boolean isEnabled, boolean isLockdown) {
setAlwaysOnVpnByUI(isEnabled, isLockdown);
@@ -235,7 +253,18 @@ private void updateUI() {
final boolean alwaysOn = isVpnAlwaysOn();
final boolean lockdown = alwaysOn
&& VpnUtils.isAnyLockdownActive(getActivity());
-
+ final boolean anyVpnActive = isAnyVpnActive();
+ final boolean globalVpn = isGlobalVpn();
+
+ mPreferenceGlobal.setEnabled(!anyVpnActive);
+ mPreferenceGlobal.setChecked(globalVpn);
+ if (globalVpn) {
+ mPreferenceGlobal.setSummary(R.string.global_vpn_summary_on);
+ } else if (anyVpnActive) {
+ mPreferenceGlobal.setSummary(R.string.global_vpn_summary_any_vpn_active);
+ } else {
+ mPreferenceGlobal.setSummary(R.string.global_vpn_summary);
+ }
mPreferenceAlwaysOn.setChecked(alwaysOn);
mPreferenceLockdown.setChecked(lockdown);
updateRestrictedViews();
@@ -298,6 +327,11 @@ private boolean isVpnAlwaysOn() {
return mPackageName.equals(getAlwaysOnVpnPackage());
}
+ private boolean isGlobalVpn() {
+ return mPackageName.equals(Settings.Global.getString(
+ getContext().getContentResolver(), Settings.Global.GLOBAL_VPN_APP));
+ }
+
/**
* @return false if the intent doesn't contain an existing package or can't retrieve activated
* vpn info.
@@ -352,6 +386,18 @@ private boolean isAnotherVpnActive() {
return config != null && !TextUtils.equals(config.user, mPackageName);
}
+ /**
+ * @return {@code true} if any VPN (VpnService or legacy) is connected or set as always-on.
+ */
+ private boolean isAnyVpnActive() {
+ for (UserInfo userInfo : UserManager.get(getContext()).getUsers()) {
+ if (mVpnManager.getVpnConfig(userInfo.id) != null) {
+ return true;
+ }
+ }
+ return false;
+ }
+
public static class CannotConnectFragment extends InstrumentedDialogFragment {
private static final String TAG = "CannotConnect";
private static final String ARG_VPN_LABEL = "label";
diff --git a/src/com/android/settings/wfd/WifiDisplaySettings.java b/src/com/android/settings/wfd/WifiDisplaySettings.java
index 2ec69c43016..2575225e81c 100644
--- a/src/com/android/settings/wfd/WifiDisplaySettings.java
+++ b/src/com/android/settings/wfd/WifiDisplaySettings.java
@@ -348,22 +348,26 @@ public void onBindViewHolder(PreferenceViewHolder view) {
super.onBindViewHolder(view);
Button b = (Button) view.findViewById(R.id.left_button);
- b.setText(R.string.wifi_display_pause);
- b.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- mDisplayManager.pauseWifiDisplay();
- }
- });
+ if (b != null) {
+ b.setText(R.string.wifi_display_pause);
+ b.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mDisplayManager.pauseWifiDisplay();
+ }
+ });
+ }
b = (Button) view.findViewById(R.id.right_button);
- b.setText(R.string.wifi_display_resume);
- b.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- mDisplayManager.resumeWifiDisplay();
- }
- });
+ if (b != null) {
+ b.setText(R.string.wifi_display_resume);
+ b.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mDisplayManager.resumeWifiDisplay();
+ }
+ });
+ }
}
});
mCertCategory.setLayoutResource(R.layout.two_buttons_panel);
diff --git a/src/com/android/settings/wifi/WifiConfigController.java b/src/com/android/settings/wifi/WifiConfigController.java
index 39c77a17924..7a565d8480d 100644
--- a/src/com/android/settings/wifi/WifiConfigController.java
+++ b/src/com/android/settings/wifi/WifiConfigController.java
@@ -174,6 +174,7 @@ public class WifiConfigController implements TextWatcher,
private String mMultipleCertSetString;
private String mUseSystemCertsString;
private String mDoNotProvideEapUserCertString;
+ private String mDoNotValidateEapServerString;
private Spinner mSecuritySpinner;
@VisibleForTesting Spinner mEapMethodSpinner;
@@ -281,6 +282,8 @@ private void initWifiConfigController(AccessPoint accessPoint, int mode) {
mUseSystemCertsString = mContext.getString(R.string.wifi_use_system_certs);
mDoNotProvideEapUserCertString =
mContext.getString(R.string.wifi_do_not_provide_eap_user_cert);
+ mDoNotValidateEapServerString =
+ mContext.getString(R.string.wifi_do_not_validate_eap_server);
if (Flags.androidVWifiApi() && mAccessPointSecurity == WifiEntry.SECURITY_WEP) {
LinearLayout wepWarningLayout =
@@ -566,7 +569,8 @@ boolean isSubmittable() {
// Disallow submit if the user has not selected a CA certificate for an EAP network
// configuration.
enabled = false;
- } else if (mEapDomainView != null
+ } else if (!caCertSelection.equals(mDoNotValidateEapServerString)
+ && mEapDomainView != null
&& mView.findViewById(R.id.l_domain).getVisibility() != View.GONE
&& TextUtils.isEmpty(mEapDomainView.getText().toString())) {
// Disallow submit if the user chooses to use a certificate for EAP server
@@ -588,6 +592,7 @@ boolean isSubmittable() {
}
void showWarningMessagesIfAppropriate() {
+ mView.findViewById(R.id.no_ca_cert_warning).setVisibility(View.GONE);
mView.findViewById(R.id.no_user_cert_warning).setVisibility(View.GONE);
mView.findViewById(R.id.no_domain_warning).setVisibility(View.GONE);
mView.findViewById(R.id.ssid_too_long_warning).setVisibility(View.GONE);
@@ -600,7 +605,13 @@ void showWarningMessagesIfAppropriate() {
}
if (mEapCaCertSpinner != null
&& mView.findViewById(R.id.l_ca_cert).getVisibility() != View.GONE) {
- if (mEapDomainView != null
+ String caCertSelection = (String) mEapCaCertSpinner.getSelectedItem();
+ if (caCertSelection.equals(mDoNotValidateEapServerString)) {
+ // Display warning if user chooses not to validate the EAP server with a
+ // user-supplied CA certificate in an EAP network configuration.
+ mView.findViewById(R.id.no_ca_cert_warning).setVisibility(View.VISIBLE);
+ } else if (!caCertSelection.equals(mUnspecifiedCertString)
+ && mEapDomainView != null
&& mView.findViewById(R.id.l_domain).getVisibility() != View.GONE
&& TextUtils.isEmpty(mEapDomainView.getText().toString())) {
// Display warning if user chooses to use a certificate without restricting the
@@ -744,7 +755,8 @@ public WifiConfiguration getConfig() {
config.enterpriseConfig.setCaCertificateAliases(null);
config.enterpriseConfig.setCaPath(null);
config.enterpriseConfig.setDomainSuffixMatch(mEapDomainView.getText().toString());
- if (caCert.equals(mUnspecifiedCertString)) {
+ if (caCert.equals(mUnspecifiedCertString)
+ || caCert.equals(mDoNotValidateEapServerString)) {
// ca_cert already set to null, so do nothing.
} else if (caCert.equals(mUseSystemCertsString)) {
config.enterpriseConfig.setCaPath(SYSTEM_CA_STORE_PATH);
@@ -778,7 +790,8 @@ public WifiConfiguration getConfig() {
}
// Only set OCSP option if there is a valid CA certificate.
- if (caCert.equals(mUnspecifiedCertString)) {
+ if (caCert.equals(mUnspecifiedCertString)
+ || caCert.equals(mDoNotValidateEapServerString)) {
config.enterpriseConfig.setOcsp(WifiEnterpriseConfig.OCSP_NONE);
} else {
config.enterpriseConfig.setOcsp(mEapOcspSpinner.getSelectedItemPosition());
@@ -1089,7 +1102,7 @@ private void showSecurityFields(boolean refreshEapMethods, boolean refreshCertif
loadCertificates(
mEapCaCertSpinner,
androidKeystoreAliasLoader.getCaCertAliases(),
- null /* noCertificateString */,
+ mDoNotValidateEapServerString /* noCertificateString */,
false /* showMultipleCerts */,
true /* showUsePreinstalledCertOption */);
loadCertificates(
@@ -1173,7 +1186,7 @@ private void showSecurityFields(boolean refreshEapMethods, boolean refreshCertif
} else {
String[] caCerts = enterpriseConfig.getCaCertificateAliases();
if (caCerts == null) {
- setSelection(mEapCaCertSpinner, mUnspecifiedCertString);
+ setSelection(mEapCaCertSpinner, mDoNotValidateEapServerString);
} else if (caCerts.length == 1) {
setSelection(mEapCaCertSpinner, caCerts[0]);
} else {
@@ -1184,7 +1197,7 @@ private void showSecurityFields(boolean refreshEapMethods, boolean refreshCertif
loadCertificates(
mEapCaCertSpinner,
androidKeystoreAliasLoader.getCaCertAliases(),
- null /* noCertificateString */,
+ mDoNotValidateEapServerString /* noCertificateString */,
true /* showMultipleCerts */,
true /* showUsePreinstalledCertOption */);
setSelection(mEapCaCertSpinner, mMultipleCertSetString);
@@ -1317,7 +1330,8 @@ private void showEapFieldsByMethod(int eapMethod) {
if (mView.findViewById(R.id.l_ca_cert).getVisibility() != View.GONE) {
String eapCertSelection = (String) mEapCaCertSpinner.getSelectedItem();
- if (eapCertSelection.equals(mUnspecifiedCertString)) {
+ if (eapCertSelection.equals(mDoNotValidateEapServerString)
+ || eapCertSelection.equals(mUnspecifiedCertString)) {
// Domain suffix matching is not relevant if the user hasn't chosen a CA
// certificate yet, or chooses not to validate the EAP server.
setDomainInvisible();
@@ -1578,8 +1592,7 @@ void loadCertificates(
}).collect(Collectors.toList()));
}
- if (!TextUtils.isEmpty(noCertificateString)
- && mAccessPointSecurity != AccessPoint.SECURITY_EAP_SUITE_B) {
+ if (mAccessPointSecurity != AccessPoint.SECURITY_EAP_SUITE_B) {
certs.add(noCertificateString);
}
diff --git a/src/com/android/settings/wifi/WifiConfigController2.java b/src/com/android/settings/wifi/WifiConfigController2.java
index 70e08eb9033..72374682647 100644
--- a/src/com/android/settings/wifi/WifiConfigController2.java
+++ b/src/com/android/settings/wifi/WifiConfigController2.java
@@ -178,6 +178,7 @@ public class WifiConfigController2 implements TextWatcher,
private String mUseSystemCertsString;
private String mTrustOnFirstUse;
private String mDoNotProvideEapUserCertString;
+ private String mDoNotValidateEapServerString;
@VisibleForTesting String mInstallCertsString;
private Spinner mSecuritySpinner;
@@ -290,6 +291,8 @@ private void initWifiConfigController2(WifiEntry wifiEntry) {
mTrustOnFirstUse = mContext.getString(R.string.wifi_trust_on_first_use);
mDoNotProvideEapUserCertString =
mContext.getString(R.string.wifi_do_not_provide_eap_user_cert);
+ mDoNotValidateEapServerString =
+ mContext.getString(R.string.wifi_do_not_validate_eap_server);
mInstallCertsString = mContext.getString(R.string.wifi_install_credentials);
if (Flags.androidVWifiApi() && mWifiEntrySecurity == WifiEntry.SECURITY_WEP) {
@@ -344,7 +347,7 @@ private void initWifiConfigController2(WifiEntry wifiEntry) {
if (mPrivacySettingsSpinner != null) {
final int prefMacValue = WifiPrivacyPreferenceController2
- .translateMacRandomizedValueToPrefValue(config.macRandomizationSetting);
+ .translateMacRandomizedValueToPrefValue(mWifiEntry.getPrivacy());
mPrivacySettingsSpinner.setSelection(prefMacValue);
}
@@ -565,7 +568,8 @@ boolean isSubmittable() {
// Disallow submit if the user has not selected a CA certificate for an EAP network
// configuration.
enabled = false;
- } else if (mEapDomainView != null
+ } else if (!caCertSelection.equals(mDoNotValidateEapServerString)
+ && mEapDomainView != null
&& mView.findViewById(R.id.l_domain).getVisibility() != View.GONE
&& TextUtils.isEmpty(mEapDomainView.getText().toString())) {
// Disallow submit if the user chooses to use a certificate for EAP server
@@ -587,6 +591,7 @@ boolean isSubmittable() {
}
void showWarningMessagesIfAppropriate() {
+ mView.findViewById(R.id.no_ca_cert_warning).setVisibility(View.GONE);
mView.findViewById(R.id.no_user_cert_warning).setVisibility(View.GONE);
mView.findViewById(R.id.no_domain_warning).setVisibility(View.GONE);
mView.findViewById(R.id.ssid_too_long_warning).setVisibility(View.GONE);
@@ -599,7 +604,13 @@ void showWarningMessagesIfAppropriate() {
}
if (mEapCaCertSpinner != null
&& mView.findViewById(R.id.l_ca_cert).getVisibility() != View.GONE) {
- if (mEapDomainView != null
+ String caCertSelection = (String) mEapCaCertSpinner.getSelectedItem();
+ if (caCertSelection.equals(mDoNotValidateEapServerString)) {
+ // Display warning if user chooses not to validate the EAP server with a
+ // user-supplied CA certificate in an EAP network configuration.
+ mView.findViewById(R.id.no_ca_cert_warning).setVisibility(View.VISIBLE);
+ } else if (!caCertSelection.equals(mUnspecifiedCertString)
+ && mEapDomainView != null
&& mView.findViewById(R.id.l_domain).getVisibility() != View.GONE
&& TextUtils.isEmpty(mEapDomainView.getText().toString())) {
// Display warning if user chooses to use a certificate without restricting the
@@ -756,7 +767,8 @@ public WifiConfiguration getConfig() {
config.enterpriseConfig.setCaCertificateAliases(null);
config.enterpriseConfig.setCaPath(null);
config.enterpriseConfig.setDomainSuffixMatch(mEapDomainView.getText().toString());
- if (caCert.equals(mUnspecifiedCertString)) {
+ if (caCert.equals(mUnspecifiedCertString)
+ || caCert.equals(mDoNotValidateEapServerString)) {
// ca_cert already set to null, so do nothing.
} else if (mIsTrustOnFirstUseSupported && caCert.equals(mTrustOnFirstUse)) {
config.enterpriseConfig.enableTrustOnFirstUse(true);
@@ -791,7 +803,8 @@ public WifiConfiguration getConfig() {
}
// Only set certificate option if there is a valid CA certificate.
- if (caCert.equals(mUnspecifiedCertString)) {
+ if (caCert.equals(mUnspecifiedCertString)
+ || caCert.equals(mDoNotValidateEapServerString)) {
config.enterpriseConfig.setOcsp(WifiEnterpriseConfig.OCSP_NONE);
config.enterpriseConfig.setMinimumTlsVersion(WifiEnterpriseConfig.TLS_V1_0);
} else {
@@ -1105,7 +1118,7 @@ protected void showSecurityFields(boolean refreshEapMethods, boolean refreshCert
loadCertificates(
mEapCaCertSpinner,
mAndroidKeystoreAliasLoader.getCaCertAliases(),
- null /* noCertificateString */,
+ mDoNotValidateEapServerString /* noCertificateString */,
false /* showMultipleCerts */,
true /* showUsePreinstalledCertOption */);
loadCertificates(
@@ -1189,7 +1202,7 @@ protected void showSecurityFields(boolean refreshEapMethods, boolean refreshCert
&& enterpriseConfig.isTrustOnFirstUseEnabled()) {
setSelection(mEapCaCertSpinner, mTrustOnFirstUse);
} else {
- setSelection(mEapCaCertSpinner, mUnspecifiedCertString);
+ setSelection(mEapCaCertSpinner, mDoNotValidateEapServerString);
}
} else if (caCerts.length == 1) {
setSelection(mEapCaCertSpinner, caCerts[0]);
@@ -1198,7 +1211,7 @@ protected void showSecurityFields(boolean refreshEapMethods, boolean refreshCert
loadCertificates(
mEapCaCertSpinner,
mAndroidKeystoreAliasLoader.getCaCertAliases(),
- null /* noCertificateString */,
+ mDoNotValidateEapServerString /* noCertificateString */,
true /* showMultipleCerts */,
true /* showUsePreinstalledCertOption */);
setSelection(mEapCaCertSpinner, mMultipleCertSetString);
@@ -1338,7 +1351,8 @@ private void showEapFieldsByMethod(int eapMethod) {
if (mView.findViewById(R.id.l_ca_cert).getVisibility() != View.GONE) {
String eapCertSelection = (String) mEapCaCertSpinner.getSelectedItem();
- if (eapCertSelection.equals(mUnspecifiedCertString)
+ if (eapCertSelection.equals(mDoNotValidateEapServerString)
+ || eapCertSelection.equals(mUnspecifiedCertString)
|| (mIsTrustOnFirstUseSupported
&& eapCertSelection.equals(mTrustOnFirstUse))) {
setMinTlsVerInvisible();
@@ -1614,8 +1628,7 @@ void loadCertificates(
}).collect(Collectors.toList()));
}
- if (!TextUtils.isEmpty(noCertificateString)
- && mWifiEntrySecurity != WifiEntry.SECURITY_EAP_SUITE_B) {
+ if (mWifiEntrySecurity != WifiEntry.SECURITY_EAP_SUITE_B) {
certs.add(noCertificateString);
}
diff --git a/src/com/android/settings/wifi/WifiTimeoutPreferenceController.java b/src/com/android/settings/wifi/WifiTimeoutPreferenceController.java
new file mode 100644
index 00000000000..8e42ae56fe3
--- /dev/null
+++ b/src/com/android/settings/wifi/WifiTimeoutPreferenceController.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2020 The Calyx Institute
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.wifi;
+
+import android.content.Context;
+import android.os.UserManager;
+import android.net.wifi.WifiManager;
+import android.provider.Settings;
+import android.util.Log;
+
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.core.PreferenceControllerMixin;
+
+public class WifiTimeoutPreferenceController extends BasePreferenceController implements
+ PreferenceControllerMixin, Preference.OnPreferenceChangeListener {
+ private static final String TAG = "WifiTimeoutPrefCtrl";
+
+ public static final int FALLBACK_WIFI_TIMEOUT_VALUE = 0;
+
+ private final String mWifiTimeoutKey;
+
+ protected WifiManager mWifiManager;
+
+ public WifiTimeoutPreferenceController(Context context, String key) {
+ super(context, key);
+ mWifiTimeoutKey = key;
+
+ mWifiManager = context.getSystemService(WifiManager.class);
+ if (mWifiManager == null) {
+ Log.e(TAG, "Wifi is not supported on this device");
+ return;
+ }
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ if (mWifiManager != null) {
+ return UserManager.get(mContext).isAdminUser() ? AVAILABLE : DISABLED_FOR_USER;
+ }
+ return UNSUPPORTED_ON_DEVICE;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return mWifiTimeoutKey;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ final ListPreference timeoutListPreference = (ListPreference) preference;
+ final long currentTimeout = Settings.Global.getLong(mContext.getContentResolver(),
+ Settings.Global.WIFI_OFF_TIMEOUT, FALLBACK_WIFI_TIMEOUT_VALUE);
+ timeoutListPreference.setValue(String.valueOf(currentTimeout));
+ updateTimeoutPreferenceDescription(timeoutListPreference,
+ Long.parseLong(timeoutListPreference.getValue()));
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ try {
+ long value = Long.parseLong((String) newValue);
+ Settings.Global.putLong(mContext.getContentResolver(),
+ Settings.Global.WIFI_OFF_TIMEOUT, value);
+ updateTimeoutPreferenceDescription((ListPreference) preference, value);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "could not persist wifi timeout setting", e);
+ }
+ return true;
+ }
+
+ public static CharSequence getTimeoutDescription(
+ long currentTimeout, CharSequence[] entries, CharSequence[] values) {
+ if (currentTimeout < 0 || entries == null || values == null
+ || values.length != entries.length) {
+ return null;
+ }
+
+ for (int i = 0; i < values.length; i++) {
+ long timeout = Long.parseLong(values[i].toString());
+ if (currentTimeout == timeout) {
+ return entries[i];
+ }
+ }
+ return null;
+ }
+
+ private void updateTimeoutPreferenceDescription(ListPreference preference,
+ long currentTimeout) {
+ final CharSequence[] entries = preference.getEntries();
+ final CharSequence[] values = preference.getEntryValues();
+ final CharSequence timeoutDescription = getTimeoutDescription(
+ currentTimeout, entries, values);
+ String summary = "";
+ if (timeoutDescription != null) {
+ if (currentTimeout != 0)
+ summary = mContext.getString(R.string.wifi_timeout_summary, timeoutDescription);
+ else
+ summary = mContext.getString(R.string.wifi_timeout_summary2);
+ }
+ preference.setSummary(summary);
+ }
+}
diff --git a/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragment.java b/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragment.java
index 3d437e22e7d..f81fe50db13 100644
--- a/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragment.java
+++ b/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragment.java
@@ -17,7 +17,9 @@
package com.android.settings.wifi.dpp;
import android.app.settings.SettingsEnums;
+import android.content.ClipboardManager;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -37,6 +39,7 @@
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
+import android.widget.Toast;
import androidx.annotation.Nullable;
@@ -134,6 +137,18 @@ public void onViewCreated(View view, Bundle savedInstanceState) {
} else {
passwordView.setText(getString(R.string.wifi_dpp_wifi_password, password));
}
+
+ passwordView.setOnClickListener(v -> {
+ Toast.makeText(getContext(), R.string.longpress_to_clipboard, Toast.LENGTH_SHORT).show();
+ });
+
+ passwordView.setOnLongClickListener(v -> {
+ ClipboardManager cm = (ClipboardManager) getContext()
+ .getSystemService(Context.CLIPBOARD_SERVICE);
+ cm.setText(password);
+ Toast.makeText(getContext(), R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show();
+ return true;
+ });
}
final Intent intent = new Intent().setComponent(getNearbySharingComponent());
diff --git a/src/com/android/settings/wifi/p2p/WifiP2pPreferenceController.java b/src/com/android/settings/wifi/p2p/WifiP2pPreferenceController.java
index 5d73fa464f1..2a5e8c6bace 100644
--- a/src/com/android/settings/wifi/p2p/WifiP2pPreferenceController.java
+++ b/src/com/android/settings/wifi/p2p/WifiP2pPreferenceController.java
@@ -15,6 +15,8 @@
*/
package com.android.settings.wifi.p2p;
+import static android.content.pm.PackageManager.FEATURE_WIFI_DIRECT;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -92,8 +94,7 @@ public void onPause() {
@Override
public boolean isAvailable() {
- // Always show preference.
- return true;
+ return mContext.getPackageManager().hasSystemFeature(FEATURE_WIFI_DIRECT);
}
@Override
public String getPreferenceKey() {
diff --git a/src/org/derpfest/settings/ambient/AmbientBatterySettings.java b/src/org/derpfest/settings/ambient/AmbientBatterySettings.java
new file mode 100644
index 00000000000..c5827c8613b
--- /dev/null
+++ b/src/org/derpfest/settings/ambient/AmbientBatterySettings.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The PixelDust Project
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+
+package org.derpfest.settings.ambient;
+
+import android.os.Bundle;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.search.SearchIndexable;
+
+@SearchIndexable
+public class AmbientBatterySettings extends SettingsPreferenceFragment {
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsEvent.DERP;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.ambient_battery_settings);
+ }
+
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider(R.xml.ambient_battery_settings);
+}
diff --git a/src/org/derpfest/settings/themes/LockClockFontsPicker.java b/src/org/derpfest/settings/themes/LockClockFontsPicker.java
new file mode 100644
index 00000000000..95871a1013a
--- /dev/null
+++ b/src/org/derpfest/settings/themes/LockClockFontsPicker.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2021 AospExtended ROM Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.derpfest.settings.themes;
+
+import static android.os.UserHandle.USER_SYSTEM;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.pm.PackageManager;
+import android.graphics.Typeface;
+import android.graphics.drawable.AnimationDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.provider.SearchIndexableResource;
+import android.provider.Settings;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.Gravity;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+import android.text.TextUtils;
+import androidx.preference.PreferenceViewHolder;
+import android.view.ViewGroup.LayoutParams;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.preference.Preference;
+import androidx.preference.Preference.OnPreferenceChangeListener;
+import androidx.preference.PreferenceScreen;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settings.R;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.search.Indexable;
+import com.android.settings.SettingsPreferenceFragment;
+
+import com.bumptech.glide.Glide;
+
+import com.android.internal.util.derp.ThemeUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Arrays;
+
+import org.json.JSONObject;
+import org.json.JSONException;
+
+public class LockClockFontsPicker extends SettingsPreferenceFragment {
+
+ private RecyclerView mRecyclerView;
+ private ThemeUtils mThemeUtils;
+ private String mCategory = "android.theme.customization.lockscreen_clock_font";
+
+ private List mPkgs;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getActivity().setTitle(R.string.theme_customization_lock_clock_title);
+
+ mThemeUtils = new ThemeUtils(getActivity());
+ mPkgs = mThemeUtils.getOverlayPackagesForCategory(mCategory, "android");
+ }
+
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(
+ R.layout.item_view, container, false);
+
+ mRecyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);
+ GridLayoutManager gridLayoutManager = new GridLayoutManager(getActivity(), 1);
+ mRecyclerView.setLayoutManager(gridLayoutManager);
+ Adapter mAdapter = new Adapter(getActivity());
+ mRecyclerView.setAdapter(mAdapter);
+
+ return view;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsEvent.DERP;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ }
+
+ public class Adapter extends RecyclerView.Adapter {
+ Context context;
+ String mSelectedPkg;
+ String mAppliedPkg;
+
+ public Adapter(Context context) {
+ this.context = context;
+ }
+
+ @Override
+ public CustomViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.lock_clock_fonts_option, parent, false);
+ CustomViewHolder vh = new CustomViewHolder(v);
+ return vh;
+ }
+
+ @Override
+ public void onBindViewHolder(CustomViewHolder holder, final int position) {
+ String pkg = mPkgs.get(position);
+ String label = getLabel(holder.itemView.getContext(), pkg);
+
+ String currentPackageName = mThemeUtils.getOverlayInfos(mCategory).stream()
+ .filter(info -> info.isEnabled())
+ .map(info -> info.packageName)
+ .findFirst()
+ .orElse("android");
+
+ holder.title.setTextSize(28);
+ holder.title.setTypeface(getTypeface(holder.title.getContext(), pkg));
+ holder.name.setVisibility(View.VISIBLE);
+ holder.name.setText("android".equals(pkg) ? "Default" : label);
+
+ if (currentPackageName.equals(pkg)) {
+ mAppliedPkg = pkg;
+ if (mSelectedPkg == null) {
+ mSelectedPkg = pkg;
+ }
+ }
+
+ holder.itemView.setActivated(pkg == mSelectedPkg);
+ holder.itemView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ updateActivatedStatus(mSelectedPkg, false);
+ updateActivatedStatus(pkg, true);
+ mSelectedPkg = pkg;
+ enableOverlays(position);
+ }
+ });
+ }
+
+ @Override
+ public int getItemCount() {
+ return mPkgs.size();
+ }
+
+ public class CustomViewHolder extends RecyclerView.ViewHolder {
+ TextView name;
+ TextView title;
+ public CustomViewHolder(View itemView) {
+ super(itemView);
+ title = (TextView) itemView.findViewById(R.id.option_title);
+ name = (TextView) itemView.findViewById(R.id.option_label);
+ }
+ }
+
+ private void updateActivatedStatus(String pkg, boolean isActivated) {
+ int index = mPkgs.indexOf(pkg);
+ if (index < 0) {
+ return;
+ }
+ RecyclerView.ViewHolder holder = mRecyclerView.findViewHolderForAdapterPosition(index);
+ if (holder != null && holder.itemView != null) {
+ holder.itemView.setActivated(isActivated);
+ }
+ }
+ }
+
+ public Typeface getTypeface(Context context, String pkg) {
+ try {
+ PackageManager pm = context.getPackageManager();
+ Resources res = pkg.equals("android") ? Resources.getSystem()
+ : pm.getResourcesForApplication(pkg);
+ return Typeface.create(res.getString(
+ res.getIdentifier("config_clockFontFamily",
+ "string", pkg)), Typeface.NORMAL);
+ }
+ catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ public String getLabel(Context context, String pkg) {
+ PackageManager pm = context.getPackageManager();
+ try {
+ return pm.getApplicationInfo(pkg, 0)
+ .loadLabel(pm).toString();
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ }
+ return pkg;
+ }
+
+ public void enableOverlays(int position) {
+ mThemeUtils.setOverlayEnabled(mCategory, mPkgs.get(position), "android");
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDetailsHeaderControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDetailsHeaderControllerTest.java
deleted file mode 100644
index 4fb78fbee48..00000000000
--- a/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDetailsHeaderControllerTest.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * 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.settings.connecteddevice.usb;
-
-import static android.hardware.usb.UsbPortStatus.DATA_ROLE_DEVICE;
-import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SINK;
-
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.hardware.usb.UsbManager;
-
-import androidx.fragment.app.FragmentActivity;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.preference.PreferenceManager;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.R;
-import com.android.settings.testutils.shadow.ShadowEntityHeaderController;
-import com.android.settings.widget.EntityHeaderController;
-import com.android.settingslib.core.lifecycle.Lifecycle;
-import com.android.settingslib.testutils.DrawableTestHelper;
-import com.android.settingslib.widget.LayoutPreference;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Answers;
-import org.mockito.ArgumentMatcher;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Config;
-
-@RunWith(RobolectricTestRunner.class)
-@Config(shadows = {
- ShadowEntityHeaderController.class,
- com.android.settings.testutils.shadow.ShadowFragment.class,
-})
-public class UsbDetailsHeaderControllerTest {
-
- private UsbDetailsHeaderController mDetailsHeaderController;
- private Context mContext;
- private Lifecycle mLifecycle;
- private LifecycleOwner mLifecycleOwner;
- private LayoutPreference mPreference;
- private PreferenceManager mPreferenceManager;
- private PreferenceScreen mScreen;
-
- @Mock
- private UsbBackend mUsbBackend;
- @Mock
- private UsbDetailsFragment mFragment;
- @Mock
- private FragmentActivity mActivity;
- @Mock(answer = Answers.RETURNS_DEEP_STUBS)
- private EntityHeaderController mHeaderController;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- mContext = RuntimeEnvironment.application;
- mLifecycleOwner = () -> mLifecycle;
- mLifecycle = new Lifecycle(mLifecycleOwner);
- mPreferenceManager = new PreferenceManager(mContext);
- mScreen = mPreferenceManager.createPreferenceScreen(mContext);
-
- when(mFragment.getActivity()).thenReturn(mActivity);
- when(mActivity.getApplicationContext()).thenReturn(mContext);
- when(mFragment.getContext()).thenReturn(mContext);
- when(mFragment.getPreferenceManager()).thenReturn(mPreferenceManager);
- when(mFragment.getPreferenceScreen()).thenReturn(mScreen);
-
- ShadowEntityHeaderController.setUseMock(mHeaderController);
- mDetailsHeaderController = new UsbDetailsHeaderController(mContext, mFragment, mUsbBackend);
- mPreference = new LayoutPreference(
- mContext, com.android.settingslib.widget.preference.layout.R.layout.settings_entity_header);
- mPreference.setKey(mDetailsHeaderController.getPreferenceKey());
- mScreen.addPreference(mPreference);
- }
-
- @After
- public void tearDown() {
- ShadowEntityHeaderController.reset();
- }
-
- @Test
- public void displayRefresh_charging_shouldSetHeader() {
- mDetailsHeaderController.displayPreference(mScreen);
- mDetailsHeaderController.refresh(true, UsbManager.FUNCTION_NONE, POWER_ROLE_SINK,
- DATA_ROLE_DEVICE);
- verify(mHeaderController).setLabel(mContext.getString(R.string.usb_pref));
- verify(mHeaderController).setIcon(argThat((ArgumentMatcher) t -> {
- DrawableTestHelper.assertDrawableResId(t, R.drawable.ic_usb);
- return true;
- }));
- verify(mHeaderController).done(true);
- }
-}
diff --git a/tests/robotests/src/com/android/settings/display/darkmode/DarkModeScheduleSelectorControllerTest.java b/tests/robotests/src/com/android/settings/display/darkmode/DarkModeScheduleSelectorControllerTest.java
index d95635e6437..136a9eda5a6 100644
--- a/tests/robotests/src/com/android/settings/display/darkmode/DarkModeScheduleSelectorControllerTest.java
+++ b/tests/robotests/src/com/android/settings/display/darkmode/DarkModeScheduleSelectorControllerTest.java
@@ -34,7 +34,7 @@
import android.location.LocationManager;
import android.os.PowerManager;
-import androidx.preference.DropDownPreference;
+import androidx.preference.ListPreference;
import androidx.preference.PreferenceScreen;
import androidx.test.core.app.ApplicationProvider;
@@ -53,7 +53,7 @@ public class DarkModeScheduleSelectorControllerTest {
private DarkModeScheduleSelectorController mController;
private String mPreferenceKey = "key";
@Mock
- private DropDownPreference mPreference;
+ private ListPreference mPreference;
@Mock
private PreferenceScreen mScreen;
private Context mContext;
@@ -94,7 +94,7 @@ public void setUp() {
.thenReturn("custom_bedtime");
when(mResources.getStringArray(R.array.dark_ui_scheduler_with_bedtime_preference_titles))
.thenReturn(new String[]{"never", "auto", "custom", "custom_bedtime"});
- mPreference = spy(new DropDownPreference(mContext));
+ mPreference = spy(new ListPreference(mContext));
mPreference.setEntryValues(new CharSequence[]{"never", "auto", "custom"});
doNothing().when(mPreference).setValueIndex(anyInt());
when(mLocationManager.isLocationEnabled()).thenReturn(true);
diff --git a/tests/robotests/src/com/android/settings/gestures/PreventRingingGesturePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/gestures/PreventRingingGesturePreferenceControllerTest.java
index f537ccb729e..90539b4f1e3 100644
--- a/tests/robotests/src/com/android/settings/gestures/PreventRingingGesturePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/gestures/PreventRingingGesturePreferenceControllerTest.java
@@ -57,9 +57,6 @@ public void setUp() {
when(mResources.getBoolean(com.android.internal.R.bool.config_volumeHushGestureEnabled))
.thenReturn(true);
mController = new PreventRingingGesturePreferenceController(mContext, null);
- mController.mPreferenceCategory = new PreferenceCategory(mContext);
- mController.mVibratePref = new SelectorWithWidgetPreference(mContext);
- mController.mMutePref = new SelectorWithWidgetPreference(mContext);
}
@Test
@@ -77,74 +74,4 @@ public void testIsAvailable_configIsFalse_shouldReturnFalse() {
assertThat(mController.isAvailable()).isFalse();
}
-
- @Test
- public void testUpdateState_mute() {
- Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.VOLUME_HUSH_GESTURE,
- Settings.Secure.VOLUME_HUSH_MUTE);
- mController.updateState(mPreference);
- assertThat(mController.mVibratePref.isEnabled()).isTrue();
- assertThat(mController.mMutePref.isEnabled()).isTrue();
- assertThat(mController.mVibratePref.isChecked()).isFalse();
- assertThat(mController.mMutePref.isChecked()).isTrue();
- }
-
- @Test
- public void testUpdateState_vibrate() {
- Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.VOLUME_HUSH_GESTURE,
- Settings.Secure.VOLUME_HUSH_VIBRATE);
- mController.updateState(mPreference);
- assertThat(mController.mVibratePref.isEnabled()).isTrue();
- assertThat(mController.mMutePref.isEnabled()).isTrue();
- assertThat(mController.mVibratePref.isChecked()).isTrue();
- assertThat(mController.mMutePref.isChecked()).isFalse();
- }
-
- @Test
- public void testUpdateState_off() {
- Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.VOLUME_HUSH_GESTURE,
- Settings.Secure.VOLUME_HUSH_OFF);
- mController.updateState(mPreference);
- assertThat(mController.mVibratePref.isEnabled()).isFalse();
- assertThat(mController.mMutePref.isEnabled()).isFalse();
- assertThat(mController.mVibratePref.isChecked()).isFalse();
- assertThat(mController.mMutePref.isChecked()).isFalse();
- }
-
- @Test
- public void testUpdateState_other() {
- Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.VOLUME_HUSH_GESTURE,
- 7);
- mController.updateState(mPreference);
- assertThat(mController.mVibratePref.isChecked()).isFalse();
- assertThat(mController.mMutePref.isChecked()).isFalse();
- }
-
- @Test
- public void testRadioButtonClicked_mute() {
- SelectorWithWidgetPreference rbPref = new SelectorWithWidgetPreference(mContext);
- rbPref.setKey(PreventRingingGesturePreferenceController.KEY_MUTE);
-
- Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.VOLUME_HUSH_GESTURE,
- Settings.Secure.VOLUME_HUSH_OFF);
- mController.onRadioButtonClicked(rbPref);
-
- assertThat(Settings.Secure.VOLUME_HUSH_MUTE).isEqualTo(
- Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.VOLUME_HUSH_GESTURE, Settings.Secure.VOLUME_HUSH_OFF));
- }
-
- @Test
- public void testRadioButtonClicked_vibrate() {
- SelectorWithWidgetPreference rbPref = new SelectorWithWidgetPreference(mContext);
- rbPref.setKey(PreventRingingGesturePreferenceController.KEY_VIBRATE);
-
- Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.VOLUME_HUSH_GESTURE,
- Settings.Secure.VOLUME_HUSH_OFF);
- mController.onRadioButtonClicked(rbPref);
-
- assertThat(Settings.Secure.VOLUME_HUSH_VIBRATE).isEqualTo(
- Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.VOLUME_HUSH_GESTURE, Settings.Secure.VOLUME_HUSH_OFF));
- }
}
diff --git a/tests/robotests/src/com/android/settings/notification/PhoneRingtone2PreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/PhoneRingtone2PreferenceControllerTest.java
new file mode 100644
index 00000000000..87fa6a5038c
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/PhoneRingtone2PreferenceControllerTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.settings.notification;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.media.RingtoneManager;
+import android.telephony.TelephonyManager;
+
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.DefaultRingtonePreference;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowApplication;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+public class PhoneRingtone2PreferenceControllerTest {
+
+ @Mock
+ private TelephonyManager mTelephonyManager;
+ @Mock
+ private PreferenceScreen mPreferenceScreen;
+ @Mock
+ private DefaultRingtonePreference mPreference;
+
+ private Context mContext;
+ private PhoneRingtone2PreferenceController mController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ ShadowApplication shadowContext = ShadowApplication.getInstance();
+ shadowContext.setSystemService(Context.TELEPHONY_SERVICE, mTelephonyManager);
+ mContext = RuntimeEnvironment.application;
+ mController = new PhoneRingtone2PreferenceController(mContext);
+ }
+
+ @Test
+ public void displayPreference_shouldSetSlotId() {
+ when(mPreferenceScreen.findPreference(mController.getPreferenceKey()))
+ .thenReturn(mPreference);
+ mController.displayPreference(mPreferenceScreen);
+
+ verify(mPreference).setSlotId(1);
+ }
+
+ @Test
+ public void isAvailable_notVoiceCapable_shouldReturnFalse() {
+ when(mTelephonyManager.isVoiceCapable()).thenReturn(false);
+
+ assertThat(mController.isAvailable()).isFalse();
+ }
+
+ @Test
+ public void isAvailable_notMultiSimEnabled_shouldReturnFalse() {
+ when(mTelephonyManager.isMultiSimEnabled()).thenReturn(false);
+
+ assertThat(mController.isAvailable()).isFalse();
+ }
+
+ @Test
+ public void isAvailable_VoiceCapable_and_MultiSimEnabled_shouldReturnTrue() {
+ when(mTelephonyManager.isVoiceCapable()).thenReturn(true);
+ when(mTelephonyManager.isMultiSimEnabled()).thenReturn(true);
+
+ assertThat(mController.isAvailable()).isTrue();
+ }
+
+ @Test
+ public void getRingtoneType_shouldReturnRingtone() {
+ assertThat(mController.getRingtoneType()).isEqualTo(RingtoneManager.TYPE_RINGTONE);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/PhoneRingtonePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/PhoneRingtonePreferenceControllerTest.java
index 24e845851a4..3b9894203e6 100644
--- a/tests/robotests/src/com/android/settings/notification/PhoneRingtonePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/PhoneRingtonePreferenceControllerTest.java
@@ -18,12 +18,17 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.media.RingtoneManager;
import android.telephony.TelephonyManager;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.DefaultRingtonePreference;
+import com.android.settings.R;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -38,6 +43,10 @@ public class PhoneRingtonePreferenceControllerTest {
@Mock
private TelephonyManager mTelephonyManager;
+ @Mock
+ private PreferenceScreen mPreferenceScreen;
+ @Mock
+ private DefaultRingtonePreference mPreference;
private Context mContext;
private PhoneRingtonePreferenceController mController;
@@ -51,6 +60,16 @@ public void setUp() {
mController = new PhoneRingtonePreferenceController(mContext);
}
+ @Test
+ public void displayPreference_shouldUpdateTitle_for_MultiSimDevice() {
+ when(mTelephonyManager.isMultiSimEnabled()).thenReturn(true);
+ when(mPreferenceScreen.findPreference(mController.getPreferenceKey()))
+ .thenReturn(mPreference);
+ mController.displayPreference(mPreferenceScreen);
+
+ verify(mPreference).setTitle(mContext.getString(R.string.ringtone1_title));
+
+
@Test
public void isAvailable_notVoiceCapable_shouldReturnFalse() {
when(mTelephonyManager.isVoiceCapable()).thenReturn(false);
diff --git a/tests/robotests/src/com/android/settings/wifi/details2/WifiMeteredPreferenceController2Test.java b/tests/robotests/src/com/android/settings/wifi/details2/WifiMeteredPreferenceController2Test.java
index c63fa60437b..58b3ea918bb 100644
--- a/tests/robotests/src/com/android/settings/wifi/details2/WifiMeteredPreferenceController2Test.java
+++ b/tests/robotests/src/com/android/settings/wifi/details2/WifiMeteredPreferenceController2Test.java
@@ -22,7 +22,7 @@
import android.content.Context;
-import androidx.preference.DropDownPreference;
+import androidx.preference.ListPreference;
import com.android.settings.R;
import com.android.wifitrackerlib.WifiEntry;
@@ -47,7 +47,7 @@ public class WifiMeteredPreferenceController2Test {
private WifiMeteredPreferenceController2 mPreferenceController;
private Context mContext;
- private DropDownPreference mDropDownPreference;
+ private ListPreference mListPreference;
@Before
public void setUp() {
@@ -56,35 +56,35 @@ public void setUp() {
mPreferenceController = spy(
new WifiMeteredPreferenceController2(mContext, mWifiEntry));
- mDropDownPreference = new DropDownPreference(mContext);
- mDropDownPreference.setEntries(R.array.wifi_metered_entries);
- mDropDownPreference.setEntryValues(R.array.wifi_metered_values);
+ mListPreference = new ListPreference(mContext);
+ mListPreference.setEntries(R.array.wifi_metered_entries);
+ mListPreference.setEntryValues(R.array.wifi_metered_values);
}
@Test
public void testUpdateState_wifiMetered_setCorrectValue() {
doReturn(METERED_OVERRIDE_METERED).when(mPreferenceController).getMeteredOverride();
- mPreferenceController.updateState(mDropDownPreference);
+ mPreferenceController.updateState(mListPreference);
- assertThat(mDropDownPreference.getEntry()).isEqualTo("Treat as metered");
+ assertThat(mListPreference.getEntry()).isEqualTo("Treat as metered");
}
@Test
public void testUpdateState_wifiNotMetered_setCorrectValue() {
doReturn(METERED_OVERRIDE_NOT_METERED).when(mPreferenceController).getMeteredOverride();
- mPreferenceController.updateState(mDropDownPreference);
+ mPreferenceController.updateState(mListPreference);
- assertThat(mDropDownPreference.getEntry()).isEqualTo("Treat as unmetered");
+ assertThat(mListPreference.getEntry()).isEqualTo("Treat as unmetered");
}
@Test
public void testUpdateState_wifiAuto_setCorrectValue() {
doReturn(METERED_OVERRIDE_NONE).when(mPreferenceController).getMeteredOverride();
- mPreferenceController.updateState(mDropDownPreference);
+ mPreferenceController.updateState(mListPreference);
- assertThat(mDropDownPreference.getEntry()).isEqualTo("Detect automatically");
+ assertThat(mListPreference.getEntry()).isEqualTo("Detect automatically");
}
}
diff --git a/tests/uitests/src/com/android/settings/ui/SoundSettingsTest.java b/tests/uitests/src/com/android/settings/ui/SoundSettingsTest.java
index f981f7fe73e..72d4a1140d7 100644
--- a/tests/uitests/src/com/android/settings/ui/SoundSettingsTest.java
+++ b/tests/uitests/src/com/android/settings/ui/SoundSettingsTest.java
@@ -21,6 +21,7 @@
import android.provider.Settings;
import android.system.helpers.SettingsHelper;
import android.system.helpers.SettingsHelper.SettingsType;
+import android.telephony.TelephonyManager;
import android.test.InstrumentationTestCase;
import androidx.test.filters.MediumTest;
@@ -42,6 +43,7 @@ public class SoundSettingsTest extends InstrumentationTestCase {
private UiDevice mDevice;
private ContentResolver mResolver;
private SettingsHelper mHelper;
+ private TelephonyManager mTelephonyManager;
private final Map ringtoneSounds = Map.of(
@@ -100,6 +102,8 @@ public void setUp() throws Exception {
mDevice.setOrientationNatural();
mResolver = getInstrumentation().getContext().getContentResolver();
mHelper = new SettingsHelper();
+ mTelephonyManager = (TelephonyManager) getInstrumentation().getContext()
+ .getSystemService(Context.TELEPHONY_SERVICE);
}
@Override
@@ -174,26 +178,49 @@ private void launchSoundSettings() throws Exception {
public void testPhoneRingtoneNone() throws Exception {
launchSoundSettings();
mHelper.clickSetting("Phone ringtone");
- verifyRingtone(new RingtoneSetting("None", "null"),
- Settings.System.RINGTONE);
+ if (mTelephonyManager.isMultiSimEnabled()) {
+ mHelper.clickSetting("Phone ringtone - SIM 1");
+ verifyRingtone(new RingtoneSetting("None", "null"), Settings.System.RINGTONE);
+ mHelper.clickSetting("Phone ringtone - SIM 2");
+ verifyRingtone(new RingtoneSetting("None", "null"), Settings.System.RINGTONE2);
+ } else {
+ mHelper.clickSetting("Phone ringtone");
+ verifyRingtone(new RingtoneSetting("None", "null"), Settings.System.RINGTONE);
+ }
}
@MediumTest
@Suppress
public void testPhoneRingtoneHangouts() throws Exception {
launchSoundSettings();
- mHelper.clickSetting("Phone ringtone");
- verifyRingtone(new RingtoneSetting("Hangouts Call", "31"), Settings.System.RINGTONE);
+ if (mTelephonyManager.isMultiSimEnabled()) {
+ mHelper.clickSetting("Phone ringtone - SIM 1");
+ verifyRingtone(new RingtoneSetting("Hangouts Call", "31"), Settings.System.RINGTONE);
+ mHelper.clickSetting("Phone ringtone - SIM 2");
+ verifyRingtone(new RingtoneSetting("Hangouts Call", "31"), Settings.System.RINGTONE2);
+ } else {
+ mHelper.clickSetting("Phone ringtone");
+ verifyRingtone(new RingtoneSetting("Hangouts Call", "31"), Settings.System.RINGTONE);
+ }
}
@MediumTest
public void testPhoneRingtone() throws Exception {
launchSoundSettings();
- mHelper.clickSetting("Phone ringtone");
String ringtone = ringtoneSounds.get(mDevice.getProductName()).toString();
String ringtoneSettingValue = ringtoneCodes.get(mDevice.getProductName()).toString();
- verifyRingtone(new RingtoneSetting(ringtone, ringtoneSettingValue),
- Settings.System.RINGTONE);
+ if (mTelephonyManager.isMultiSimEnabled()) {
+ mHelper.clickSetting("Phone ringtone - SIM 1");
+ verifyRingtone(new RingtoneSetting(ringtone, ringtoneSettingValue),
+ Settings.System.RINGTONE);
+ mHelper.clickSetting("Phone ringtone - SIM 2");
+ verifyRingtone(new RingtoneSetting(ringtone, ringtoneSettingValue),
+ Settings.System.RINGTONE2);
+ } else {
+ mHelper.clickSetting("Phone ringtone");
+ verifyRingtone(new RingtoneSetting(ringtone, ringtoneSettingValue),
+ Settings.System.RINGTONE);
+ }
}
@MediumTest
diff --git a/tests/unit/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.java b/tests/unit/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.java
index 9a5399c5f07..5f02b04f7e0 100644
--- a/tests/unit/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.java
+++ b/tests/unit/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.java
@@ -37,6 +37,7 @@
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.android.settings.core.BasePreferenceController;
import com.android.settings.testutils.ResourcesUtils;
import org.junit.Before;
@@ -88,9 +89,26 @@ public void setup() {
mCategory.setKey(categoryKey);
mScreen.addPreference(mCategory);
+ doReturn(mSubscriptionInfo).when(mController).getSubscriptionInfo(anyInt());
doReturn(mSecondPreference).when(mController).createNewPreference(mContext);
}
+ @Test
+ public void getAvailabilityStatus_isVoiceCapable_shouldBeAVAILABLE() {
+ when(mTelephonyManager.isVoiceCapable()).thenReturn(true);
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(
+ BasePreferenceController.AVAILABLE);
+ }
+
+ @Test
+ public void getAvailabilityStatus_isNotVoiceCapable_shouldBeUNSUPPORTED_ON_DEVICE() {
+ when(mTelephonyManager.isVoiceCapable()).thenReturn(false);
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(
+ BasePreferenceController.UNSUPPORTED_ON_DEVICE);
+ }
+
@Test
public void displayPreference_multiSim_shouldAddSecondPreference() {
when(mTelephonyManager.getPhoneCount()).thenReturn(2);
@@ -105,7 +123,6 @@ public void displayPreference_multiSim_shouldAddSecondPreference() {
@Test
public void updateState_singleSim_shouldUpdateTitleAndPhoneNumber() {
final String phoneNumber = "1111111111";
- doReturn(mSubscriptionInfo).when(mController).getSubscriptionInfo(anyInt());
doReturn(phoneNumber).when(mController).getFormattedPhoneNumber(mSubscriptionInfo);
when(mTelephonyManager.getPhoneCount()).thenReturn(1);
mController.displayPreference(mScreen);
@@ -119,7 +136,6 @@ public void updateState_singleSim_shouldUpdateTitleAndPhoneNumber() {
@Test
public void updateState_multiSim_shouldUpdateTitleAndPhoneNumberOfMultiplePreferences() {
final String phoneNumber = "1111111111";
- doReturn(mSubscriptionInfo).when(mController).getSubscriptionInfo(anyInt());
doReturn(phoneNumber).when(mController).getFormattedPhoneNumber(mSubscriptionInfo);
when(mTelephonyManager.getPhoneCount()).thenReturn(2);
mController.displayPreference(mScreen);
@@ -137,11 +153,11 @@ public void updateState_multiSim_shouldUpdateTitleAndPhoneNumberOfMultiplePrefer
@Test
public void getSummary_cannotGetActiveSubscriptionInfo_shouldShowUnknown() {
when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(null);
- mController.displayPreference(mScreen);
- mController.updateState(mPreference);
+ CharSequence primaryNumber = mController.getSummary();
- verify(mPreference).setSummary(ResourcesUtils.getResourcesString(
+ assertThat(primaryNumber).isNotNull();
+ assertThat(primaryNumber).isEqualTo(ResourcesUtils.getResourcesString(
mContext, "device_info_default"));
}
@@ -150,10 +166,9 @@ public void getSummary_getEmptySubscriptionInfo_shouldShowUnknown() {
List infos = new ArrayList<>();
when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(infos);
- mController.displayPreference(mScreen);
- mController.updateState(mPreference);
+ CharSequence primaryNumber = mController.getSummary();
- verify(mPreference).setSummary(ResourcesUtils.getResourcesString(
+ assertThat(primaryNumber).isEqualTo(ResourcesUtils.getResourcesString(
mContext, "device_info_default"));
}
}
|