diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml index 46bd305f005..f06ed2eef28 100644 --- a/res/xml/development_settings.xml +++ b/res/xml/development_settings.xml @@ -532,6 +532,11 @@ android:title="@string/usb_audio_disable_routing" android:summary="@string/usb_audio_disable_routing_summary" /> + + + + + + + + + + diff --git a/src/com/android/settings/development/transcode/TranscodeGlobalTogglePreferenceController.java b/src/com/android/settings/development/transcode/TranscodeGlobalTogglePreferenceController.java new file mode 100644 index 00000000000..b11bb580d9f --- /dev/null +++ b/src/com/android/settings/development/transcode/TranscodeGlobalTogglePreferenceController.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 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.transcode; + +import android.content.Context; +import android.os.SystemProperties; + +import com.android.settings.core.TogglePreferenceController; + +/** + * The controller for the "Enable transcode for all apps" switch on the transcode settings + * screen. + */ +public class TranscodeGlobalTogglePreferenceController extends TogglePreferenceController { + + private static final String TRANSCODE_ENABLED_PROP_KEY = "persist.sys.fuse.transcode"; + + public TranscodeGlobalTogglePreferenceController(Context context, + String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public boolean isChecked() { + return SystemProperties.getBoolean(TRANSCODE_ENABLED_PROP_KEY, false); + } + + @Override + public boolean setChecked(boolean isChecked) { + SystemProperties.set(TRANSCODE_ENABLED_PROP_KEY, String.valueOf(isChecked)); + return true; + } +} diff --git a/src/com/android/settings/development/transcode/TranscodeSettingsFragment.java b/src/com/android/settings/development/transcode/TranscodeSettingsFragment.java new file mode 100644 index 00000000000..e3dc9bfc8eb --- /dev/null +++ b/src/com/android/settings/development/transcode/TranscodeSettingsFragment.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 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.transcode; + +import android.app.settings.SettingsEnums; +import android.content.Context; + +import com.android.settings.R; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settingslib.development.DevelopmentSettingsEnabler; +import com.android.settingslib.search.SearchIndexable; + +/** + * Fragment for native transcode settings in Developer options. + */ +@SearchIndexable +public class TranscodeSettingsFragment extends DashboardFragment { + private static final String TAG = "TranscodeSettings"; + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.transcode_settings; + } + + @Override + public int getMetricsCategory() { + return SettingsEnums.TRANSCODE_SETTINGS; + } + + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider(R.xml.transcode_settings) { + + @Override + protected boolean isPageSearchEnabled(Context context) { + return DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(context); + } + }; +} diff --git a/src/com/android/settings/development/transcode/TranscodeSkipAppsPreferenceController.java b/src/com/android/settings/development/transcode/TranscodeSkipAppsPreferenceController.java new file mode 100644 index 00000000000..defe821e907 --- /dev/null +++ b/src/com/android/settings/development/transcode/TranscodeSkipAppsPreferenceController.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 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.transcode; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.SystemProperties; + +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; +import androidx.preference.SwitchPreference; + +import com.android.settings.core.BasePreferenceController; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * The controller for the "Skip transcoding for apps" section on the transcode settings + * screen. + */ +public class TranscodeSkipAppsPreferenceController extends BasePreferenceController implements + Preference.OnPreferenceChangeListener { + + private static final String SKIP_SELECTED_APPS_PROP_KEY = + "persist.sys.fuse.transcode_skip_uids"; + + private final PackageManager mPackageManager; + private final List mUidsToSkip = new ArrayList<>(); + + public TranscodeSkipAppsPreferenceController(Context context, + String preferenceKey) { + super(context, preferenceKey); + mPackageManager = context.getPackageManager(); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + final Context context = screen.getContext(); + mUidsToSkip.addAll(Arrays.asList( + SystemProperties.get(SKIP_SELECTED_APPS_PROP_KEY).split(","))); + Intent launcherIntent = new Intent(Intent.ACTION_MAIN); + launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER); + List apps = mPackageManager.queryIntentActivities(launcherIntent, 0); + for (ResolveInfo app : apps) { + String uid = String.valueOf(app.activityInfo.applicationInfo.uid); + SwitchPreference preference = new SwitchPreference(context); + preference.setTitle(app.loadLabel(mPackageManager)); + preference.setIcon(app.loadIcon(mPackageManager)); + preference.setKey(uid); + preference.setChecked(isSkippedForTranscoding(uid)); + preference.setOnPreferenceChangeListener(this); + + screen.addPreference(preference); + } + } + + @Override + public boolean onPreferenceChange(Preference preference, Object o) { + boolean value = (Boolean) o; + String uidStr = preference.getKey(); + if (value) { + mUidsToSkip.add(uidStr); + } else { + mUidsToSkip.remove(uidStr); + } + SystemProperties.set(SKIP_SELECTED_APPS_PROP_KEY, String.join(",", mUidsToSkip)); + return true; + } + + private boolean isSkippedForTranscoding(String uid) { + return mUidsToSkip.contains(uid); + } +} diff --git a/tests/robotests/src/com/android/settings/development/transcode/TranscodeGlobalTogglePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/transcode/TranscodeGlobalTogglePreferenceControllerTest.java new file mode 100644 index 00000000000..f4e3e71fcb5 --- /dev/null +++ b/tests/robotests/src/com/android/settings/development/transcode/TranscodeGlobalTogglePreferenceControllerTest.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 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.transcode; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.os.SystemProperties; + +import androidx.test.core.app.ApplicationProvider; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class TranscodeGlobalTogglePreferenceControllerTest { + + private static final String TRANSCODE_ENABLED_PROP_KEY = "persist.sys.fuse.transcode"; + + private TranscodeGlobalTogglePreferenceController mController; + + @Before + public void setUp() { + Context context = ApplicationProvider.getApplicationContext(); + mController = new TranscodeGlobalTogglePreferenceController(context, "test_key"); + } + + @Test + public void isAvailable_shouldReturnTrue() { + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void isChecked_whenEnabled_shouldReturnTrue() { + SystemProperties.set(TRANSCODE_ENABLED_PROP_KEY, "true"); + assertThat(mController.isChecked()).isTrue(); + } + + @Test + public void isChecked_whenDisabled_shouldReturnTrue() { + SystemProperties.set(TRANSCODE_ENABLED_PROP_KEY, "false"); + assertThat(mController.isChecked()).isFalse(); + } + + @Test + public void setChecked_withTrue_shouldUpdateSystemProperty() { + // Simulate the UI being clicked. + mController.setChecked(true); + + // Verify the system property was updated with the UI value. + assertThat(SystemProperties.getBoolean(TRANSCODE_ENABLED_PROP_KEY, false)).isTrue(); + } + + @Test + public void setChecked_withFalse_shouldUpdateSystemProperty() { + // Simulate the UI being clicked. + mController.setChecked(false); + + // Verify the system property was updated with the UI value. + assertThat(SystemProperties.getBoolean(TRANSCODE_ENABLED_PROP_KEY, true)).isFalse(); + } +} diff --git a/tests/robotests/src/com/android/settings/development/transcode/TranscodeSkipAppsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/transcode/TranscodeSkipAppsPreferenceControllerTest.java new file mode 100644 index 00000000000..f1fce9f8241 --- /dev/null +++ b/tests/robotests/src/com/android/settings/development/transcode/TranscodeSkipAppsPreferenceControllerTest.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 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.transcode; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.graphics.drawable.Drawable; +import android.os.SystemProperties; + +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; +import androidx.preference.SwitchPreference; +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.R; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.Shadows; +import org.robolectric.shadows.ShadowPackageManager; + +import java.util.Collections; + +@RunWith(RobolectricTestRunner.class) +public class TranscodeSkipAppsPreferenceControllerTest { + + private static final int APPLICATION_UID = 1234; + private static final String SKIP_SELECTED_APPS_PROP_KEY = + "persist.sys.fuse.transcode_skip_uids"; + + @Mock + private PreferenceScreen mScreen; + private Context mContext; + private ShadowPackageManager mShadowPackageManager; + private TranscodeSkipAppsPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = ApplicationProvider.getApplicationContext(); + mShadowPackageManager = Shadows.shadowOf(mContext.getPackageManager()); + mController = new TranscodeSkipAppsPreferenceController(mContext, "test_key"); + Preference preference = new Preference(mContext); + + when(mScreen.getContext()).thenReturn(mContext); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(preference); + } + + @Test + public void isAlwaysAvailable() { + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void display_hasLaunchAbleApps_shouldDisplay() { + Intent launcherIntent = new Intent(Intent.ACTION_MAIN); + launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER); + final ResolveInfo resolveInfo = new FakeResolveInfo(mContext); + ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.uid = APPLICATION_UID; + ActivityInfo activityInfo = new ActivityInfo(); + activityInfo.applicationInfo = applicationInfo; + resolveInfo.activityInfo = activityInfo; + mShadowPackageManager.setResolveInfosForIntent(launcherIntent, + Collections.singletonList(resolveInfo)); + + mController.displayPreference(mScreen); + + verify(mScreen, atLeastOnce()).addPreference(any(Preference.class)); + } + + @Test + public void preferenceChecked_shouldSkipApp() { + // First ensure that the app is not in skip list. + SystemProperties.set(SKIP_SELECTED_APPS_PROP_KEY, String.valueOf(-1)); + SwitchPreference switchPreference = createPreference(/* defaultCheckedState = */ false); + + switchPreference.performClick(); + + // Verify that the app is added to skip list. + assertThat(SystemProperties.get(SKIP_SELECTED_APPS_PROP_KEY)).contains( + String.valueOf(APPLICATION_UID)); + } + + @Test + public void preferenceUnchecked_shouldNotSkipApp() { + // First ensure that the app is in skip list. + SystemProperties.set(SKIP_SELECTED_APPS_PROP_KEY, String.valueOf(APPLICATION_UID)); + SwitchPreference switchPreference = createPreference(/* defaultCheckedState = */ true); + + switchPreference.performClick(); + + // Verify that the app is removed from skip list. + assertThat(SystemProperties.get(SKIP_SELECTED_APPS_PROP_KEY)).doesNotContain( + String.valueOf(APPLICATION_UID)); + } + + private SwitchPreference createPreference(boolean defaultCheckedState) { + SwitchPreference preference = new SwitchPreference(mContext); + preference.setTitle("Test Pref"); + preference.setIcon(R.drawable.ic_settings_24dp); + preference.setKey(String.valueOf(APPLICATION_UID)); + preference.setChecked(defaultCheckedState); + preference.setOnPreferenceChangeListener(mController); + return preference; + } + + private static class FakeResolveInfo extends ResolveInfo { + + private final Context mContext; + + FakeResolveInfo(Context context) { + this.mContext = context; + } + + @Override + public CharSequence loadLabel(PackageManager pm) { + return "TestName"; + } + + @Override + public Drawable loadIcon(PackageManager pm) { + return mContext.getDrawable(R.drawable.ic_settings_24dp); + } + } +}