Commit a6c8b740 authored by Natalie Chouinard's avatar Natalie Chouinard Committed by Commit Bot

Migrate Developer Settings

Migrate the Developer Settings page from deprecated Framework
preferences to the Preferences Support Library.

This is the first fragment migrated that includes a ChromeBasePreference
so some base classes and methods have been duplicated here to support
simultaneous compatibility between Framework and Support Library
Preferences.

Bug: 966244
Change-Id: I2bd8919361207576231f99d799a5a7e95819d81e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1632557
Commit-Queue: Natalie Chouinard <chouinard@chromium.org>
Reviewed-by: default avatarTheresa <twellington@chromium.org>
Cr-Commit-Position: refs/heads/master@{#666356}
parent 46b49e8a
...@@ -1216,6 +1216,7 @@ chrome_java_sources = [ ...@@ -1216,6 +1216,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/preferences/ChromeBaseCheckBoxPreference.java", "java/src/org/chromium/chrome/browser/preferences/ChromeBaseCheckBoxPreference.java",
"java/src/org/chromium/chrome/browser/preferences/ChromeBaseListPreference.java", "java/src/org/chromium/chrome/browser/preferences/ChromeBaseListPreference.java",
"java/src/org/chromium/chrome/browser/preferences/ChromeBasePreference.java", "java/src/org/chromium/chrome/browser/preferences/ChromeBasePreference.java",
"java/src/org/chromium/chrome/browser/preferences/ChromeBasePreferenceCompat.java",
"java/src/org/chromium/chrome/browser/preferences/ChromeImageViewPreference.java", "java/src/org/chromium/chrome/browser/preferences/ChromeImageViewPreference.java",
"java/src/org/chromium/chrome/browser/preferences/ChromePreferenceManager.java", "java/src/org/chromium/chrome/browser/preferences/ChromePreferenceManager.java",
"java/src/org/chromium/chrome/browser/preferences/ChromeSwitchPreference.java", "java/src/org/chromium/chrome/browser/preferences/ChromeSwitchPreference.java",
...@@ -1229,6 +1230,7 @@ chrome_java_sources = [ ...@@ -1229,6 +1230,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/preferences/LocationSettings.java", "java/src/org/chromium/chrome/browser/preferences/LocationSettings.java",
"java/src/org/chromium/chrome/browser/preferences/MainPreferences.java", "java/src/org/chromium/chrome/browser/preferences/MainPreferences.java",
"java/src/org/chromium/chrome/browser/preferences/ManagedPreferenceDelegate.java", "java/src/org/chromium/chrome/browser/preferences/ManagedPreferenceDelegate.java",
"java/src/org/chromium/chrome/browser/preferences/ManagedPreferenceDelegateCompat.java",
"java/src/org/chromium/chrome/browser/preferences/ManagedPreferencesUtils.java", "java/src/org/chromium/chrome/browser/preferences/ManagedPreferencesUtils.java",
"java/src/org/chromium/chrome/browser/preferences/ManageSyncPreferences.java", "java/src/org/chromium/chrome/browser/preferences/ManageSyncPreferences.java",
"java/src/org/chromium/chrome/browser/preferences/NotificationsPreferences.java", "java/src/org/chromium/chrome/browser/preferences/NotificationsPreferences.java",
...@@ -1251,6 +1253,7 @@ chrome_java_sources = [ ...@@ -1251,6 +1253,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/preferences/SyncedAccountPreference.java", "java/src/org/chromium/chrome/browser/preferences/SyncedAccountPreference.java",
"java/src/org/chromium/chrome/browser/preferences/TextAndButtonPreference.java", "java/src/org/chromium/chrome/browser/preferences/TextAndButtonPreference.java",
"java/src/org/chromium/chrome/browser/preferences/TextMessagePreference.java", "java/src/org/chromium/chrome/browser/preferences/TextMessagePreference.java",
"java/src/org/chromium/chrome/browser/preferences/TextMessagePreferenceCompat.java",
"java/src/org/chromium/chrome/browser/preferences/TextScalePreference.java", "java/src/org/chromium/chrome/browser/preferences/TextScalePreference.java",
"java/src/org/chromium/chrome/browser/preferences/autofill/AndroidPaymentAppPreference.java", "java/src/org/chromium/chrome/browser/preferences/autofill/AndroidPaymentAppPreference.java",
"java/src/org/chromium/chrome/browser/preferences/autofill/AndroidPaymentAppsFragment.java", "java/src/org/chromium/chrome/browser/preferences/autofill/AndroidPaymentAppsFragment.java",
......
...@@ -260,6 +260,11 @@ ...@@ -260,6 +260,11 @@
<item name="android:paddingEnd">@dimen/pref_list_padding_kitkat</item> <item name="android:paddingEnd">@dimen/pref_list_padding_kitkat</item>
</style> </style>
<style name="Theme.Chromium.PreferenceItemNoDividers">
<item name="allowDividerAbove">false</item>
<item name="allowDividerBelow">false</item>
</style>
<style name="PreferenceActionBarModern" parent="@style/Widget.AppCompat.Light.ActionBar.Solid"> <style name="PreferenceActionBarModern" parent="@style/Widget.AppCompat.Light.ActionBar.Solid">
<item name="titleTextStyle">@style/TextAppearance.BlackHeadline</item> <item name="titleTextStyle">@style/TextAppearance.BlackHeadline</item>
</style> </style>
......
...@@ -3,15 +3,17 @@ ...@@ -3,15 +3,17 @@
Use of this source code is governed by a BSD-style license that can be Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. --> found in the LICENSE file. -->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" android:orderingFromXml="true"> <android.support.v7.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
<Preference android:orderingFromXml="true">
<android.support.v7.preference.Preference
android:fragment="org.chromium.chrome.browser.preferences.developer.TracingPreferences" android:fragment="org.chromium.chrome.browser.preferences.developer.TracingPreferences"
android:key="tracing" android:key="tracing"
android:title="Tracing"/> android:title="Tracing" />
<org.chromium.chrome.browser.preferences.TextMessagePreference <org.chromium.chrome.browser.preferences.TextMessagePreferenceCompat
android:key="beta_stable_hint" style="@style/Theme.Chromium.PreferenceItemNoDividers"
android:title="Hint: You can also enable Developer options on Beta/Stable channels by tapping the Chrome version in &quot;Settings > About Chrome&quot; multiple times."
android:enabled="false"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"/> android:layout_height="wrap_content"
</PreferenceScreen> android:enabled="false"
android:key="beta_stable_hint"
android:title="Hint: You can also enable Developer options on Beta/Stable channels by tapping the Chrome version in &quot;Settings > About Chrome&quot; multiple times." />
</android.support.v7.preference.PreferenceScreen>
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.preferences;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceViewHolder;
import android.util.AttributeSet;
import android.widget.TextView;
import org.chromium.chrome.R;
/**
* A preference that supports some Chrome-specific customizations:
*
* 1. This preference supports being managed. If this preference is managed (as determined by its
* ManagedPreferenceDelegate), it updates its appearance and behavior appropriately: shows an
* enterprise icon, disables clicks, etc.
*
* 2. This preference can have a multiline title.
* 3. This preference can set an icon color in XML through app:iconTint. Note that if a
* ColorStateList is set, only the default color will be used.
*
* TODO(crbug.com/967022): This class is analogous to {@link ChromeBasePreference}, but extends the
* Preference Support Library rather than the deprecated Framework preferences. Once all {@link
* ChromeBasePreference}s have been migrated to the Support Library, {@link ChromeBasePreference}
* will be removed in favor of {@link ChromeBasePreferenceCompat}.
*/
public class ChromeBasePreferenceCompat extends Preference {
private ColorStateList mIconTint;
private ManagedPreferenceDelegateCompat mManagedPrefDelegate;
/**
* Constructor for use in Java.
*/
public ChromeBasePreferenceCompat(Context context) {
this(context, null);
}
/**
* Constructor for inflating from XML.
*/
public ChromeBasePreferenceCompat(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ChromeBasePreference);
mIconTint = a.getColorStateList(R.styleable.ChromeBasePreference_iconTint);
a.recycle();
}
/**
* Sets the ManagedPreferenceDelegate which will determine whether this preference is managed.
*/
public void setManagedPreferenceDelegate(ManagedPreferenceDelegateCompat delegate) {
mManagedPrefDelegate = delegate;
ManagedPreferencesUtils.initPreference(mManagedPrefDelegate, this);
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
((TextView) holder.findViewById(android.R.id.title)).setSingleLine(false);
Drawable icon = getIcon();
if (icon != null && mIconTint != null) {
icon.setColorFilter(mIconTint.getDefaultColor(), PorterDuff.Mode.SRC_IN);
}
ManagedPreferencesUtils.onBindViewToPreference(mManagedPrefDelegate, this, holder.itemView);
}
@Override
protected void onClick() {
if (ManagedPreferencesUtils.onClickPreference(mManagedPrefDelegate, this)) return;
super.onClick();
}
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.preferences;
import android.support.v7.preference.Preference;
/**
* A delegate that determines whether a Preference is managed by enterprise policy. This is used
* in various Preference subclasses (e.g. ChromeSwitchPreference) to determine whether to show
* an enterprise icon next to the Preference and whether to disable clicks on the Preference.
*
* An implementation of this delegate should override isPreferenceControlledByPolicy() and,
* optionally, isPreferenceClickDisabledByPolicy(). Example:
*
* class RocketManagedPreferenceDelegate extends ManagedPreferenceDelegate {
* @Override
* public boolean isPreferenceControlledByPolicy(Preference preference) {
* if ("enable_rockets".equals(preference.getKey())) {
* return RocketUtils.isEnableRocketsManaged();
* }
* return false;
* }
* }
*
* ChromeSwitchPreference enableRocketsPref = ...;
* enableRocketsPref.setManagedPreferenceDelegate(new RocketManagedPreferenceDelegate());
*
* TODO(crbug.com/967022): This class is analogous to {@link ManagedPreferenceDelegate}, but is
* implemented for Support Library preferences rather than the deprecated Framework preferences.
* Once all managed preferences have been migrated to the Support Library, {@link
* ManagedPreferenceDelegate} will be removed in favor of this class.
*/
public interface ManagedPreferenceDelegateCompat {
/**
* Returns whether the given Preference is controlled by an enterprise policy.
* @param preference the {@link Preference} under consideration.
* @return whether the given Preference is controlled by an enterprise policy.
*/
boolean isPreferenceControlledByPolicy(Preference preference);
/**
* Returns whether the given Preference is controlled by the supervised user's custodian.
* @param preference the {@link Preference} under consideration.
* @return whether the given Preference is controlled by the supervised user's custodian.
*/
default boolean isPreferenceControlledByCustodian(Preference preference) {
return false;
}
/**
* Returns whether clicking on the given Preference is disabled due to a policy. The default
* implementation just returns whether the preference is not modifiable by the user.
* However, some preferences that are controlled by policy may still be clicked to show an
* informational subscreen, in which case this method needs a custom implementation.
*/
// TODO(bauerb): Rename to isPreferenceClickDisabled.
default boolean isPreferenceClickDisabledByPolicy(Preference preference) {
return isPreferenceControlledByPolicy(preference)
|| isPreferenceControlledByCustodian(preference);
}
}
\ No newline at end of file
...@@ -103,6 +103,10 @@ public class ManagedPreferencesUtils { ...@@ -103,6 +103,10 @@ public class ManagedPreferencesUtils {
* *
* This should be called once, before the preference is displayed. * This should be called once, before the preference is displayed.
* *
* TODO(crbug.com/967022): Remove this method once all fragments are migrated to the Support
* Library in favor of {@link #initPreference(ManagedPreferenceDelegateCompat,
* android.support.v7.preference.Preference)}.
*
* @param delegate The delegate that controls whether the preference is managed. May be null, * @param delegate The delegate that controls whether the preference is managed. May be null,
* then this method does nothing. * then this method does nothing.
* @param preference The Preference that is being initialized * @param preference The Preference that is being initialized
...@@ -126,6 +130,41 @@ public class ManagedPreferencesUtils { ...@@ -126,6 +130,41 @@ public class ManagedPreferencesUtils {
} }
} }
/**
* Initializes the Preference based on the state of any policies that may affect it,
* e.g. by showing a managed icon or disabling clicks on the preference. If |preference| is an
* instance of ChromeImageViewPreference, the icon is not set since the ImageView widget will
* display the managed icons.
*
* This should be called once, before the preference is displayed.
*
* @param delegate The delegate that controls whether the preference is managed. May be null,
* then this method does nothing.
* @param preference The Preference that is being initialized
*/
public static void initPreference(@Nullable ManagedPreferenceDelegateCompat delegate,
android.support.v7.preference.Preference preference) {
if (delegate == null) return;
// TODO(chouinard): A compat version of ChromeImageViewPreference hasn't been created yet.
// Once it is, uncomment this section.
/*
if (!(preference instanceof ChromeImageViewPreference)) {
preference.setIcon(getManagedIconDrawable(delegate, preference));
}
*/
if (delegate.isPreferenceClickDisabledByPolicy(preference)) {
// Disable the views and prevent the Preference from mucking with the enabled state.
preference.setShouldDisableView(false);
// Prevent default click behavior.
preference.setFragment(null);
preference.setIntent(null);
preference.setOnPreferenceClickListener(null);
}
}
/** /**
* Disables the Preference's views if the preference is not clickable. * Disables the Preference's views if the preference is not clickable.
* *
...@@ -134,6 +173,10 @@ public class ManagedPreferencesUtils { ...@@ -134,6 +173,10 @@ public class ManagedPreferencesUtils {
* *
* This should be called from the Preference's onBindView() method. * This should be called from the Preference's onBindView() method.
* *
* TODO(crbug.com/967022): Remove this method once all fragments are migrated to the Support
* Library in favor of {@link #onBindViewToPreference(ManagedPreferenceDelegateCompat,
* android.support.v7.preference.Preference, View)}.
*
* @param delegate The delegate that controls whether the preference is managed. May be null, * @param delegate The delegate that controls whether the preference is managed. May be null,
* then this method does nothing. * then this method does nothing.
* @param preference The Preference that owns the view * @param preference The Preference that owns the view
...@@ -158,6 +201,38 @@ public class ManagedPreferencesUtils { ...@@ -158,6 +201,38 @@ public class ManagedPreferencesUtils {
} }
} }
/**
* Disables the Preference's views if the preference is not clickable.
*
* Note: this disables the View instead of disabling the Preference, so that the Preference
* still receives click events, which will trigger a "Managed by your administrator" toast.
*
* This should be called from the Preference's onBindView() method.
*
* @param delegate The delegate that controls whether the preference is managed. May be null,
* then this method does nothing.
* @param preference The Preference that owns the view
* @param view The View that was bound to the Preference
*/
public static void onBindViewToPreference(@Nullable ManagedPreferenceDelegateCompat delegate,
android.support.v7.preference.Preference preference, View view) {
if (delegate == null) return;
if (delegate.isPreferenceClickDisabledByPolicy(preference)) {
ViewUtils.setEnabledRecursive(view, false);
}
// Append managed information to summary if necessary.
TextView summaryView = view.findViewById(android.R.id.summary);
CharSequence summary =
ManagedPreferencesUtils.getSummaryWithManagedInfo(delegate, preference,
summaryView.getVisibility() == View.VISIBLE ? summaryView.getText() : null);
if (!TextUtils.isEmpty(summary)) {
summaryView.setText(summary);
summaryView.setVisibility(View.VISIBLE);
}
}
/** /**
* Calls onBindViewToPreference() above. Then, if the ChromeImageViewPreference is managed, the * Calls onBindViewToPreference() above. Then, if the ChromeImageViewPreference is managed, the
* widget ImageView is set to the appropriate managed icon, and its onClick listener is set to * widget ImageView is set to the appropriate managed icon, and its onClick listener is set to
...@@ -197,6 +272,10 @@ public class ManagedPreferencesUtils { ...@@ -197,6 +272,10 @@ public class ManagedPreferencesUtils {
* *
* This should be called from the Preference's onClick() method. * This should be called from the Preference's onClick() method.
* *
* TODO(crbug.com/967022): Remove this method once all fragments are migrated to the Support
* Library in favor of {@link #onClickPreference(ManagedPreferenceDelegateCompat,
* android.support.v7.preference.Preference)}.
*
* @param delegate The delegate that controls whether the preference is managed. May be null, * @param delegate The delegate that controls whether the preference is managed. May be null,
* then this method does nothing and returns false. * then this method does nothing and returns false.
* @param preference The Preference that was clicked. * @param preference The Preference that was clicked.
...@@ -222,6 +301,39 @@ public class ManagedPreferencesUtils { ...@@ -222,6 +301,39 @@ public class ManagedPreferencesUtils {
} }
/** /**
* Intercepts the click event if the given Preference is managed and shows a toast in that case.
*
* This should be called from the Preference's onClick() method.
*
* @param delegate The delegate that controls whether the preference is managed. May be null,
* then this method does nothing and returns false.
* @param preference The Preference that was clicked.
* @return true if the click event was handled by this helper and shouldn't be further
* propagated; false otherwise.
*/
public static boolean onClickPreference(@Nullable ManagedPreferenceDelegateCompat delegate,
android.support.v7.preference.Preference preference) {
if (delegate == null || !delegate.isPreferenceClickDisabledByPolicy(preference)) {
return false;
}
if (delegate.isPreferenceControlledByPolicy(preference)) {
showManagedByAdministratorToast(preference.getContext());
} else if (delegate.isPreferenceControlledByCustodian(preference)) {
showManagedByParentToast(preference.getContext());
} else {
// If the preference is disabled, it should be either because it's managed by enterprise
// policy or by the custodian.
assert false;
}
return true;
}
/**
* TODO(crbug.com/967022): Remove this method once all fragments are migrated to the Support
* Library in favor of {@link #getSummaryWithManagedInfo(ManagedPreferenceDelegateCompat,
* android.support.v7.preference.Preference, CharSequence)}.
*
* @param delegate The {@link ManagedPreferenceDelegate} that controls whether the preference is * @param delegate The {@link ManagedPreferenceDelegate} that controls whether the preference is
* managed. * managed.
* @param preference The {@link Preference} that the summary should be used for. * @param preference The {@link Preference} that the summary should be used for.
...@@ -246,6 +358,33 @@ public class ManagedPreferencesUtils { ...@@ -246,6 +358,33 @@ public class ManagedPreferencesUtils {
return String.format(Locale.getDefault(), "%s\n%s", summary, extraSummary); return String.format(Locale.getDefault(), "%s\n%s", summary, extraSummary);
} }
/**
* @param delegate The {@link ManagedPreferenceDelegateCompat} that controls whether the
* preference is
* managed.
* @param preference The {@link android.support.v7.preference.Preference} that the summary
* should be used for.
* @param summary The original summary without the managed information.
* @return The summary appended with information about whether the specified preference is
* managed.
*/
private static CharSequence getSummaryWithManagedInfo(
@Nullable ManagedPreferenceDelegateCompat delegate,
android.support.v7.preference.Preference preference, @Nullable CharSequence summary) {
if (delegate == null) return summary;
String extraSummary = null;
if (delegate.isPreferenceControlledByPolicy(preference)) {
extraSummary = preference.getContext().getString(R.string.managed_by_your_organization);
} else if (delegate.isPreferenceControlledByCustodian(preference)) {
extraSummary = preference.getContext().getString(getManagedByParentStringRes());
}
if (TextUtils.isEmpty(extraSummary)) return summary;
if (TextUtils.isEmpty(summary)) return extraSummary;
return String.format(Locale.getDefault(), "%s\n%s", summary, extraSummary);
}
private static @StringRes int getManagedByParentStringRes() { private static @StringRes int getManagedByParentStringRes() {
boolean singleParentIsManager = boolean singleParentIsManager =
PrefServiceBridge.getInstance().getSupervisedUserSecondCustodianName().isEmpty(); PrefServiceBridge.getInstance().getSupervisedUserSecondCustodianName().isEmpty();
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.preferences;
import android.content.Context;
import android.support.v7.preference.PreferenceViewHolder;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TextView;
/**
* A preference that displays informational text.
*
* TODO(crbug.com/967022): This class is analogous to {@link TextMessagePreference}, but extends
* {@link ChromeBasePreferenceCompat} rather than {@link ChromeBasePreference}. Once all {@link
* TextMessagePreference}-containing fragments have been migrated to the Support Library, remove
* {@link TextMessagePreference} in favor of this class.
*/
public class TextMessagePreferenceCompat extends ChromeBasePreferenceCompat {
/**
* Constructor for inflating from XML.
*/
public TextMessagePreferenceCompat(Context context, AttributeSet attrs) {
super(context, attrs);
setSelectable(false);
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
TextView titleView = (TextView) holder.findViewById(android.R.id.title);
if (!TextUtils.isEmpty(getTitle())) {
titleView.setVisibility(View.VISIBLE);
titleView.setSingleLine(false);
titleView.setMaxLines(Integer.MAX_VALUE);
titleView.setMovementMethod(LinkMovementMethod.getInstance());
} else {
titleView.setVisibility(View.GONE);
}
TextView summaryView = (TextView) holder.findViewById(android.R.id.summary);
// No need to manually toggle visibility for summary - it is done in super.onBindView.
summaryView.setMovementMethod(LinkMovementMethod.getInstance());
}
}
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
package org.chromium.chrome.browser.preferences.developer; package org.chromium.chrome.browser.preferences.developer;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceFragment; import android.support.v7.preference.PreferenceFragmentCompat;
import org.chromium.base.ContextUtils; import org.chromium.base.ContextUtils;
import org.chromium.chrome.R; import org.chromium.chrome.R;
...@@ -17,7 +17,7 @@ import org.chromium.components.version_info.VersionConstants; ...@@ -17,7 +17,7 @@ import org.chromium.components.version_info.VersionConstants;
/** /**
* Settings fragment containing preferences aimed at Chrome and web developers. * Settings fragment containing preferences aimed at Chrome and web developers.
*/ */
public class DeveloperPreferences extends PreferenceFragment { public class DeveloperPreferences extends PreferenceFragmentCompat {
private static final String UI_PREF_BETA_STABLE_HINT = "beta_stable_hint"; private static final String UI_PREF_BETA_STABLE_HINT = "beta_stable_hint";
private static final String PREF_DEVELOPER_ENABLED = "developer"; private static final String PREF_DEVELOPER_ENABLED = "developer";
...@@ -39,8 +39,7 @@ public class DeveloperPreferences extends PreferenceFragment { ...@@ -39,8 +39,7 @@ public class DeveloperPreferences extends PreferenceFragment {
} }
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreatePreferences(Bundle savedInstanceState, String s) {
super.onCreate(savedInstanceState);
getActivity().setTitle(MSG_DEVELOPER_OPTIONS_TITLE); getActivity().setTitle(MSG_DEVELOPER_OPTIONS_TITLE);
PreferenceUtils.addPreferencesFromResource(this, R.xml.developer_preferences); PreferenceUtils.addPreferencesFromResource(this, R.xml.developer_preferences);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment