Add system prompt for system language change after <Set Menu Language> CEC message

Requires ro.hdmi.set_menu_language=true

Test: build; flash; manual test
Bug: 190810909
Change-Id: Ic0664e077e975bba0929dd871c3a4c9ab385814c
This commit is contained in:
Michal Olech
2022-01-25 15:37:50 +01:00
parent 142dcfb101
commit 6f1aecf1bf
13 changed files with 380 additions and 1 deletions

View File

@@ -104,6 +104,12 @@ public final class HdmiControlManager {
public static final String EXTRA_MESSAGE_EXTRA_PARAM1 =
"android.hardware.hdmi.extra.MESSAGE_EXTRA_PARAM1";
/**
* Used as an extra field in the Set Menu Language intent. Contains the requested locale.
* @hide
*/
public static final String EXTRA_LOCALE = "android.hardware.hdmi.extra.LOCALE";
/**
* Volume value for mute state.
*/

View File

@@ -10631,6 +10631,15 @@ public final class Settings {
@Readable
public static final String FAST_PAIR_SCAN_ENABLED = "fast_pair_scan_enabled";
/**
* Setting to store denylisted system languages by the CEC {@code <Set Menu Language>}
* confirmation dialog.
*
* @hide
*/
public static final String HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST =
"hdmi_cec_set_menu_language_denylist";
/**
* These entries are considered common between the personal and the managed profile,
* since the managed profile doesn't get to change them.

View File

@@ -2887,6 +2887,11 @@
<string name="config_sensorUseStartedActivity" translatable="false"
>com.android.systemui/com.android.systemui.sensorprivacy.SensorUseStartedActivity</string>
<!-- Component name of the activity used to ask a user to confirm system language change after
receiving <Set Menu Language> CEC message. -->
<string name="config_hdmiCecSetMenuLanguageActivity"
>com.android.systemui/com.android.systemui.hdmi.HdmiCecSetMenuLanguageActivity</string>
<!-- Name of the dialog that is used to request the user's consent for a Platform VPN -->
<string name="config_platformVpnConfirmDialogComponent" translatable="false"
>com.android.vpndialogs/com.android.vpndialogs.PlatformVpnConfirmDialog</string>

View File

@@ -375,6 +375,7 @@
<java-symbol type="string" name="config_usbConfirmActivity" />
<java-symbol type="string" name="config_usbResolverActivity" />
<java-symbol type="string" name="config_sensorUseStartedActivity" />
<java-symbol type="string" name="config_hdmiCecSetMenuLanguageActivity" />
<java-symbol type="integer" name="config_minNumVisibleRecentTasks_lowRam" />
<java-symbol type="integer" name="config_maxNumVisibleRecentTasks_lowRam" />
<java-symbol type="integer" name="config_minNumVisibleRecentTasks_grid" />

View File

@@ -74,5 +74,6 @@
<permission name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS" />
<permission name="android.permission.FORCE_STOP_PACKAGES" />
<permission name="android.permission.ACCESS_FPS_COUNTER" />
<permission name="android.permission.CHANGE_CONFIGURATION" />
</privapp-permissions>
</permissions>

View File

@@ -302,6 +302,9 @@
<!-- For clipboard overlay -->
<uses-permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" />
<!-- To change system language (HDMI CEC) -->
<uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
<protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" />
@@ -485,6 +488,16 @@
android:excludeFromRecents="true">
</activity>
<!-- started from HdmiCecLocalDevicePlayback -->
<activity android:name=".hdmi.HdmiCecSetMenuLanguageActivity"
android:exported="true"
android:launchMode="singleTop"
android:permission="android.permission.CHANGE_CONFIGURATION"
android:theme="@style/BottomSheet"
android:finishOnCloseSystemDialogs="true"
android:excludeFromRecents="true">
</activity>
<!-- started from SensoryPrivacyService -->
<activity android:name=".sensorprivacy.SensorUseStartedActivity"
android:exported="true"

View File

@@ -123,6 +123,18 @@
<!-- Message of notification shown when trying to enable USB debugging but a secondary user is the current foreground user. -->
<string name="usb_debugging_secondary_user_message">The user currently signed in to this device can\'t turn on USB debugging. To use this feature, switch to the primary user.</string>
<!-- Title of confirmation dialog for wireless debugging [CHAR LIMIT=80] -->
<string name="hdmi_cec_set_menu_language_title">Do you want to change the system language to <xliff:g id="language" example="German">%1$s</xliff:g>?</string>
<!-- Description for the <Set Menu Language> confirmation dialog [CHAR LIMIT=NONE] -->
<string name="hdmi_cec_set_menu_language_description">System language change requested by another device</string>
<!-- Button label for accepting language change [CHAR LIMIT=25] -->
<string name="hdmi_cec_set_menu_language_accept">Change language</string>
<!-- Button label for declining language change [CHAR LIMIT=25] -->
<string name="hdmi_cec_set_menu_language_decline">Keep current language</string>
<!-- Title of confirmation dialog for wireless debugging [CHAR LIMIT=NONE] -->
<string name="wifi_debugging_title">Allow wireless debugging on this network?</string>

View File

@@ -49,6 +49,7 @@ import com.android.systemui.dock.DockManager;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.hdmi.HdmiCecSetMenuLanguageHelper;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
@@ -250,6 +251,7 @@ public class Dependency {
@Inject Lazy<LocationController> mLocationController;
@Inject Lazy<RotationLockController> mRotationLockController;
@Inject Lazy<ZenModeController> mZenModeController;
@Inject Lazy<HdmiCecSetMenuLanguageHelper> mHdmiCecSetMenuLanguageHelper;
@Inject Lazy<HotspotController> mHotspotController;
@Inject Lazy<CastController> mCastController;
@Inject Lazy<FlashlightController> mFlashlightController;

View File

@@ -19,6 +19,7 @@ package com.android.systemui.dagger;
import android.app.Activity;
import com.android.systemui.ForegroundServicesDialog;
import com.android.systemui.hdmi.HdmiCecSetMenuLanguageActivity;
import com.android.systemui.keyguard.WorkLockActivity;
import com.android.systemui.people.PeopleSpaceActivity;
import com.android.systemui.people.widget.LaunchConversationActivity;
@@ -120,4 +121,11 @@ public abstract class DefaultActivityBinder {
@IntoMap
@ClassKey(TvUnblockSensorActivity.class)
public abstract Activity bindTvUnblockSensorActivity(TvUnblockSensorActivity activity);
/** Inject into HdmiCecSetMenuLanguageActivity. */
@Binds
@IntoMap
@ClassKey(HdmiCecSetMenuLanguageActivity.class)
public abstract Activity bindHdmiCecSetMenuLanguageActivity(
HdmiCecSetMenuLanguageActivity activity);
}

View File

@@ -0,0 +1,98 @@
/*
* 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.systemui.hdmi;
import android.hardware.hdmi.HdmiControlManager;
import android.os.Bundle;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.tv.TvBottomSheetActivity;
import javax.inject.Inject;
/**
* Confirmation dialog shown when Set Menu Language CEC message was received.
*/
public class HdmiCecSetMenuLanguageActivity extends TvBottomSheetActivity
implements View.OnClickListener {
private static final String TAG = HdmiCecSetMenuLanguageActivity.class.getSimpleName();
private final HdmiCecSetMenuLanguageHelper mHdmiCecSetMenuLanguageHelper;
@Inject
public HdmiCecSetMenuLanguageActivity(
HdmiCecSetMenuLanguageHelper hdmiCecSetMenuLanguageHelper) {
mHdmiCecSetMenuLanguageHelper = hdmiCecSetMenuLanguageHelper;
}
@Override
public final void onCreate(Bundle b) {
super.onCreate(b);
getWindow().addPrivateFlags(
WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
String languageTag = getIntent().getStringExtra(HdmiControlManager.EXTRA_LOCALE);
mHdmiCecSetMenuLanguageHelper.setLocale(languageTag);
if (mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()) {
finish();
}
}
@Override
public void onResume() {
super.onResume();
CharSequence title = getString(R.string.hdmi_cec_set_menu_language_title,
mHdmiCecSetMenuLanguageHelper.getLocale().getDisplayLanguage());
CharSequence text = getString(R.string.hdmi_cec_set_menu_language_description);
initUI(title, text);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.bottom_sheet_positive_button) {
mHdmiCecSetMenuLanguageHelper.acceptLocale();
} else {
mHdmiCecSetMenuLanguageHelper.declineLocale();
}
finish();
}
void initUI(CharSequence title, CharSequence text) {
TextView titleTextView = findViewById(R.id.bottom_sheet_title);
TextView contentTextView = findViewById(R.id.bottom_sheet_body);
ImageView icon = findViewById(R.id.bottom_sheet_icon);
ImageView secondIcon = findViewById(R.id.bottom_sheet_second_icon);
Button okButton = findViewById(R.id.bottom_sheet_positive_button);
Button cancelButton = findViewById(R.id.bottom_sheet_negative_button);
titleTextView.setText(title);
contentTextView.setText(text);
icon.setImageResource(com.android.internal.R.drawable.ic_settings_language);
secondIcon.setVisibility(View.GONE);
okButton.setText(R.string.hdmi_cec_set_menu_language_accept);
okButton.setOnClickListener(this);
cancelButton.setText(R.string.hdmi_cec_set_menu_language_decline);
cancelButton.setOnClickListener(this);
cancelButton.requestFocus();
}
}

View File

@@ -0,0 +1,97 @@
/*
* 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.systemui.hdmi;
import android.provider.Settings;
import com.android.internal.app.LocalePicker;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.util.settings.SecureSettings;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Locale;
import java.util.concurrent.Executor;
import javax.inject.Inject;
/**
* Helper class to separate model and view for system language change initiated by HDMI CEC.
*/
@SysUISingleton
public class HdmiCecSetMenuLanguageHelper {
private static final String TAG = HdmiCecSetMenuLanguageHelper.class.getSimpleName();
private static final String SEPARATOR = ",";
private final Executor mBackgroundExecutor;
private final SecureSettings mSecureSettings;
private Locale mLocale;
private HashSet<String> mDenylist;
@Inject
public HdmiCecSetMenuLanguageHelper(@Background Executor executor,
SecureSettings secureSettings) {
mBackgroundExecutor = executor;
mSecureSettings = secureSettings;
String denylist = mSecureSettings.getString(
Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST);
mDenylist = new HashSet<>(denylist == null
? Collections.EMPTY_SET
: Arrays.asList(denylist.split(SEPARATOR)));
}
/**
* Set internal locale based on given language tag.
*/
public void setLocale(String languageTag) {
mLocale = Locale.forLanguageTag(languageTag);
}
/**
* Returns the locale from {@code <Set Menu Language>} CEC message.
*/
public Locale getLocale() {
return mLocale;
}
/**
* Returns whether the locale from {@code <Set Menu Language>} CEC message was already
* denylisted.
*/
public boolean isLocaleDenylisted() {
return mDenylist.contains(mLocale.toLanguageTag());
}
/**
* Accepts the new locale and updates system language.
*/
public void acceptLocale() {
mBackgroundExecutor.execute(() -> LocalePicker.updateLocale(mLocale));
}
/**
* Declines the locale and puts it on the denylist.
*/
public void declineLocale() {
mDenylist.add(mLocale.toLanguageTag());
mSecureSettings.putString(Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST,
String.join(SEPARATOR, mDenylist));
}
}

View File

@@ -0,0 +1,104 @@
/*
* 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.systemui.hdmi;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.provider.Settings;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.util.settings.SecureSettings;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.Locale;
import java.util.concurrent.Executor;
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class HdmiCecSetMenuLanguageHelperTest extends SysuiTestCase {
private HdmiCecSetMenuLanguageHelper mHdmiCecSetMenuLanguageHelper;
@Mock
private Executor mExecutor;
@Mock
private SecureSettings mSecureSettings;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mSecureSettings.getString(
Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST)).thenReturn(null);
mHdmiCecSetMenuLanguageHelper =
new HdmiCecSetMenuLanguageHelper(mExecutor, mSecureSettings);
}
@Test
public void testSetGetLocale() {
mHdmiCecSetMenuLanguageHelper.setLocale("en");
assertThat(mHdmiCecSetMenuLanguageHelper.getLocale()).isEqualTo(Locale.ENGLISH);
}
@Test
public void testIsLocaleDenylisted_EmptyByDefault() {
mHdmiCecSetMenuLanguageHelper.setLocale("en");
assertThat(mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()).isEqualTo(false);
}
@Test
public void testIsLocaleDenylisted_AcceptLanguage() {
mHdmiCecSetMenuLanguageHelper.setLocale("de");
mHdmiCecSetMenuLanguageHelper.acceptLocale();
assertThat(mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()).isEqualTo(false);
verify(mExecutor).execute(any());
}
@Test
public void testIsLocaleDenylisted_DeclineLanguage() {
mHdmiCecSetMenuLanguageHelper.setLocale("de");
mHdmiCecSetMenuLanguageHelper.declineLocale();
assertThat(mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()).isEqualTo(true);
verify(mSecureSettings).putString(
Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST, "de");
}
@Test
public void testIsLocaleDenylisted_DeclineTwoLanguages() {
mHdmiCecSetMenuLanguageHelper.setLocale("de");
mHdmiCecSetMenuLanguageHelper.declineLocale();
assertThat(mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()).isEqualTo(true);
verify(mSecureSettings).putString(
Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST, "de");
mHdmiCecSetMenuLanguageHelper.setLocale("pl");
mHdmiCecSetMenuLanguageHelper.declineLocale();
assertThat(mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()).isEqualTo(true);
verify(mSecureSettings).putString(
Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST, "de,pl");
}
}

View File

@@ -17,10 +17,15 @@
package com.android.server.hdmi;
import android.annotation.CallSuper;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.tv.cec.V1_0.SendMessageResult;
import android.os.Binder;
import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
@@ -408,7 +413,7 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
// locale from being chosen. 'eng' in the CEC command, for instance,
// will always be mapped to en-AU among other variants like en-US, en-GB,
// an en-IN, which may not be the expected one.
LocalePicker.updateLocale(localeInfo.getLocale());
startSetMenuLanguageActivity(localeInfo.getLocale());
return Constants.HANDLED;
}
}
@@ -420,6 +425,24 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
}
}
private void startSetMenuLanguageActivity(Locale locale) {
final long identity = Binder.clearCallingIdentity();
try {
Context context = mService.getContext();
Intent intent = new Intent();
intent.putExtra(HdmiControlManager.EXTRA_LOCALE, locale.toLanguageTag());
intent.setComponent(
ComponentName.unflattenFromString(context.getResources().getString(
com.android.internal.R.string.config_hdmiCecSetMenuLanguageActivity)));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivityAsUser(intent, context.getUser());
} catch (ActivityNotFoundException e) {
Slog.e(TAG, "unable to start HdmiCecSetMenuLanguageActivity");
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
@Constants.HandleMessageResult
protected int handleSetSystemAudioMode(HdmiCecMessage message) {