--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2012-2016 The CyanogenMod 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.
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Setting checkbox title for root access -->
+ <string name="root_access">Root access</string>
+ <string name="root_access_warning_title">Allow root access?</string>
+ <string name="root_access_warning_message">Allowing apps to request root access is very dangerous and could compromise the security of your system!</string>
+ <string name="root_access_none">Disabled</string>
+ <string name="root_access_apps">Apps only</string>
+ <string name="root_access_adb">ADB only</string>
+ <string name="root_access_all">Apps and ADB</string>
+</resources>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2012-2015 The CyanogenMod Project
+ Copyright (C) 2018 The LinegeOS Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <!-- Arrays for root access capability -->
+ <string-array name="root_access_entries" translatable="false">
+ <item>@string/root_access_none</item>
+ <item>@string/root_access_apps</item>
+ <item>@string/root_access_adb</item>
+ <item>@string/root_access_all</item>
+ </string-array>
+
+ <string-array name="root_access_values" translatable="false">
+ <item>0</item>
+ <item>1</item>
+ <item>2</item>
+ <item>3</item>
+ </string-array>
+
+ <string-array name="root_access_entries_adb" translatable="false">
+ <item>@string/root_access_none</item>
+ <item>@string/root_access_adb</item>
+ </string-array>
+
+ <string-array name="root_access_values_adb" translatable="false">
+ <item>0</item>
+ <item>2</item>
+ </string-array>
+</resources>
android:fragment="com.android.settings.development.qstile.DevelopmentTileConfigFragment"
settings:searchable="false" />
+ <ListPreference
+ android:key="root_access"
+ android:title="@string/root_access"
+ android:persistent="false" />
+
<!-- Configure trust agent behavior -->
<SwitchPreference
android:key="security_setting_trust_agents_extend_unlock"
public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFragment
implements SwitchBar.OnSwitchChangeListener, OemUnlockDialogHost, AdbDialogHost,
AdbClearKeysDialogHost, LogPersistDialogHost,
- BluetoothA2dpHwOffloadRebootDialog.OnA2dpHwDialogConfirmedListener {
+ BluetoothA2dpHwOffloadRebootDialog.OnA2dpHwDialogConfirmedListener,
+ RootAccessDialogHost {
private static final String TAG = "DevSettingsDashboard";
}
@Override
+ public void onRootAccessDialogConfirmed() {
+ final RootAccessPreferenceController controller =
+ getDevelopmentOptionsController(RootAccessPreferenceController.class);
+ controller.onRootAccessDialogConfirmed();
+ }
+
+ @Override
+ public void onRootAccessDialogDismissed() {
+ final RootAccessPreferenceController controller =
+ getDevelopmentOptionsController(RootAccessPreferenceController.class);
+ controller.onRootAccessDialogDismissed();
+ }
+
+ @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
boolean handledResult = false;
for (AbstractPreferenceController controller : mPreferenceControllers) {
controllers.add(new ShortcutManagerThrottlingPreferenceController(context));
controllers.add(new BubbleGlobalPreferenceController(context));
controllers.add(new EnableGnssRawMeasFullTrackingPreferenceController(context));
+ controllers.add(new RootAccessPreferenceController(context, fragment));
controllers.add(new DefaultLaunchPreferenceController(context, "running_apps"));
controllers.add(new DefaultLaunchPreferenceController(context, "demo_mode"));
controllers.add(new DefaultLaunchPreferenceController(context, "quick_settings_tiles"));
--- /dev/null
+/*
+ * 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;
+
+/**
+ * Interface for RootAccessWarningDialogFragment callbacks.
+ */
+public interface RootAccessDialogHost {
+
+ /**
+ * Called when the user presses ok on the warning dialog.
+ */
+ void onRootAccessDialogConfirmed();
+
+ /**
+ * Called when the user dismisses or cancels the warning dialog.
+ */
+ void onRootAccessDialogDismissed();
+}
--- /dev/null
+/*
+ * Copyright (C) 2018 The LineageOS Project
+ * Copyright (C) 2019 The Android-x86 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 android.content.Context;
+import android.os.Build;
+import android.os.SystemProperties;
+import android.os.UserManager;
+import android.provider.Settings;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settingslib.development.DeveloperOptionsPreferenceController;
+
+import java.io.File;
+
+public class RootAccessPreferenceController extends DeveloperOptionsPreferenceController
+ implements Preference.OnPreferenceChangeListener, PreferenceControllerMixin {
+
+ private static final String TAG = "RootAccessPreferenceController";
+ private static final String PREF_KEY = "root_access";
+
+ private static final String ROOT_ACCESS_PROPERTY = "persist.sys.root_access";
+
+ private final DevelopmentSettingsDashboardFragment mFragment;
+ private Object mPendingRootAccessValue;
+
+ public RootAccessPreferenceController(Context context,
+ DevelopmentSettingsDashboardFragment fragment) {
+ super(context);
+
+ mFragment = fragment;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ // User builds don't get root, and eng always gets root
+ return Build.IS_DEBUGGABLE || "eng".equals(Build.TYPE);
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return PREF_KEY;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+
+ final File file = new File("/system/xbin/su");
+ if (file.exists()) {
+ ((ListPreference) mPreference).setEntries(R.array.root_access_entries);
+ ((ListPreference) mPreference).setEntryValues(R.array.root_access_values);
+ } else {
+ ((ListPreference) mPreference).setEntries(R.array.root_access_entries_adb);
+ ((ListPreference) mPreference).setEntryValues(R.array.root_access_values_adb);
+ }
+
+ updatePreference();
+
+ if (!isAdminUser()) {
+ mPreference.setEnabled(false);
+ }
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if ("0".equals(newValue.toString())) {
+ writeRootAccessOptions(newValue);
+ } else {
+ mPendingRootAccessValue = newValue;
+ RootAccessWarningDialog.show(mFragment, this);
+ }
+ return true;
+ }
+
+ @Override
+ protected void onDeveloperOptionsSwitchEnabled() {
+ if (isAdminUser()) {
+ mPreference.setEnabled(true);
+ }
+ }
+
+ public void onRootAccessDialogConfirmed() {
+ writeRootAccessOptions(mPendingRootAccessValue);
+ }
+
+ public void onRootAccessDialogDismissed() {
+ updatePreference();
+ }
+
+ private void writeRootAccessOptions(Object newValue) {
+ String oldValue = SystemProperties.get(ROOT_ACCESS_PROPERTY, "0");
+ SystemProperties.set(ROOT_ACCESS_PROPERTY, newValue.toString());
+ if (Integer.valueOf(newValue.toString()) < 2 && !oldValue.equals(newValue)
+ && SystemProperties.getInt("service.adb.root", 0) == 1) {
+ SystemProperties.set("service.adb.root", "0");
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.ADB_ENABLED, 0);
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.ADB_ENABLED, 1);
+ }
+ updatePreference();
+ }
+
+ private void updatePreference() {
+ String value = SystemProperties.get(ROOT_ACCESS_PROPERTY, "0");
+ ((ListPreference) mPreference).setValue(value);
+ ((ListPreference) mPreference).setSummary(mContext.getResources()
+ .getStringArray(R.array.root_access_entries)[Integer.valueOf(value)]);
+ }
+
+ @VisibleForTesting
+ boolean isAdminUser() {
+ return ((UserManager) mContext.getSystemService(Context.USER_SERVICE)).isAdminUser();
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2018 The LineageOS Project
+ * Copyright (C) 2019 The Android-x86 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 android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.FragmentManager;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.R;
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+
+public class RootAccessWarningDialog extends InstrumentedDialogFragment implements
+ DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
+
+ public static final String TAG = "RootAccessWarningDialog";
+
+ public static void show(DevelopmentSettingsDashboardFragment host,
+ RootAccessPreferenceController controller) {
+ final FragmentManager manager = host.getActivity().getSupportFragmentManager();
+ if (manager.findFragmentByTag(TAG) == null) {
+ final RootAccessWarningDialog dialog = new RootAccessWarningDialog();
+ dialog.setTargetFragment(host, 0 /* requestCode */);
+ dialog.show(manager, TAG);
+ }
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsProto.MetricsEvent.TYPE_UNKNOWN;
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ return new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.root_access_warning_title)
+ .setMessage(R.string.root_access_warning_message)
+ .setPositiveButton(android.R.string.ok, this /* onClickListener */)
+ .setNegativeButton(android.R.string.cancel, this /* onClickListener */)
+ .create();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ final RootAccessDialogHost host = (RootAccessDialogHost) getTargetFragment();
+ if (host != null) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ host.onRootAccessDialogConfirmed();
+ } else {
+ host.onRootAccessDialogDismissed();
+ }
+ }
+ }
+
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ super.onDismiss(dialog);
+ final RootAccessDialogHost host = (RootAccessDialogHost) getTargetFragment();
+ if (host != null) {
+ host.onRootAccessDialogDismissed();
+ }
+ }
+}