Skip to content

Commit

Permalink
Add Localization on Android (#590)
Browse files Browse the repository at this point in the history
This PR adds localization on android. 



https://github.com/user-attachments/assets/f077b194-d79e-4ca3-bdd6-806536d1627b


### Additional context: 
#### How Android changes the locale settings when the user does it in
the settings app?
Android changes the memory responsible for holding information about
locale and tigers a change in two types of persistent storage, one is
system prop `persist.sys.locale` and the second one is `system_locales`
prop in system level settings.
Before user changes the settings both of those fields are empty. When
Android boots it either sets a locale to the default one or to the one
specified in one of the persistent locations, it seems that if only one
location exist android will still retrive the information, but conflict
between `persist.sys.locale` and `system_locales` leads to unpredictable
behavior.
#### How to change the properties with adb? 
adb allows us to change system properties including `persist.sys.locale`
granted we have a root access, this is achieved either by using `adb
root` command or `su` command inside shell. The root access is only
possible on emulators without GMS (google mobile services). adb also
allows us to chage system settings using `adb shell settings` command
and it does not require root access, but permanent state change of
system_settings require `-writable-system` flag to be added on emulator
boot, so we do it.
#### How emulator command changes settings when using `-change-locale`
option?
Underneath emulator calls `adb shell su 0 setprop persist.sys.locale
${locale}`.
#### Uncovered case: 
Because of the above I did not find a way to reliably change
`persist.sys.locale` and scenario in which user manually changes locale
(in the settings app).

### Test Plan: 
1) Changing localization while using the device
- turn on IDE and select android device 
- check if localization settings are disabled during boot process: 
<img width="346" alt="Screenshot 2024-10-11 at 18 13 35"
src="https://github.com/user-attachments/assets/9070bb5a-5e6a-4bb2-9719-6f3d0f56aac6">

- change devices localization after it was booted up 
2) Changing localization while using other available device 
- turn on IDE and select ios device 
- change devices localization after it was booted up 
- switch devices to android and check if the localization was configured
correctly
  • Loading branch information
filip131311 authored Oct 23, 2024
1 parent a04959f commit cccc52c
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 6 deletions.
92 changes: 87 additions & 5 deletions packages/vscode-extension/src/devices/AndroidEmulatorDevice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { ChildProcess, exec, lineReader } from "../utilities/subprocess";
import { BuildResult } from "../builders/BuildManager";
import { AndroidSystemImageInfo, DeviceInfo, DevicePlatform } from "../common/DeviceManager";
import { Logger } from "../Logger";
import { AppPermissionType, DeviceSettings } from "../common/Project";
import { AppPermissionType, DeviceSettings, Locale } from "../common/Project";
import { getAndroidSystemImages } from "../utilities/sdkmanager";
import { EXPO_GO_PACKAGE_NAME, fetchExpoLaunchDeeplink } from "../builders/expoGo";
import { Platform } from "../utilities/platform";
Expand Down Expand Up @@ -78,7 +78,71 @@ export class AndroidEmulatorDevice extends DeviceBase {
}, DISPOSE_TIMEOUT);
}

private async shouldUpdateLocale(newLocale: Locale) {
const locale = newLocale.replace("_", "-");
const { stdout } = await exec(ADB_PATH, [
"-s",
this.serial!,
"shell",
"settings",
"get",
"system",
"system_locales",
]);

// if user did not use the device before it might not have system_locales property
// as en-US is the default locale, used by the system, when no setting is provided
// we assume that no value in stdout is the same as en-US
if ((stdout ?? "en-US") === locale) {
return false;
}
return true;
}

/**
* This method changes device locale by modifying system_locales system setting.
*/
private async changeLocale(newLocale: Locale): Promise<void> {
const locale = newLocale.replace("_", "-");

await exec(ADB_PATH, [
"-s",
this.serial!,
"shell",
"settings",
"put",
"system",
"system_locales",
locale,
]);

// this is needed to make sure that changes will persist
await exec(ADB_PATH, ["-s", this.serial!, "shell", "sync"]);

// TODO: Find a way to change or remove persist.sys.locale without root access. note: removing the whole
// data/property/persistent_properties file would also work as we persist device settings globally in Radon IDE
const { stdout } = await exec(ADB_PATH, [
"-s",
this.serial!,
"shell",
"getprop",
"persist.sys.locale",
]);

if (stdout) {
Logger.warn(
"Updating locale will not take effect as the device has altered locale via system settings which always takes precedence over the device setting the IDE uses."
);
}
}

async changeSettings(settings: DeviceSettings) {
let shouldRestart = false;
if (await this.shouldUpdateLocale(settings.locale)) {
shouldRestart = true;
await this.changeLocale(settings.locale);
}

await exec(ADB_PATH, [
"-s",
this.serial!,
Expand Down Expand Up @@ -125,10 +189,29 @@ export class AndroidEmulatorDevice extends DeviceBase {
settings.location.latitude.toString(),
]);
}
return false;
return shouldRestart;
}

/**
* This method restarts the emulator process using SIGKILL signal.
* Should be used for the situations when quick reboot is necessary
* and when we don't care about the emulator's process state
*/
private async forcefullyResetDevice() {
this.emulatorProcess?.kill(9);
await this.internalBootDevice();
}

async bootDevice(deviceSettings: DeviceSettings): Promise<void> {
await this.internalBootDevice();

let shouldRestart = await this.changeSettings(deviceSettings);
if (shouldRestart) {
await this.forcefullyResetDevice();
}
}

async bootDevice(settings: DeviceSettings) {
async internalBootDevice() {
// this prevents booting device with the same AVD twice
await ensureOldEmulatorProcessExited(this.avdId);

Expand All @@ -144,6 +227,7 @@ export class AndroidEmulatorDevice extends DeviceBase {
"-no-boot-anim",
"-grpc-use-token",
"-no-snapshot-save",
"-writable-system",
],
{ env: { ANDROID_AVD_HOME: avdDirectory } }
);
Expand Down Expand Up @@ -172,8 +256,6 @@ export class AndroidEmulatorDevice extends DeviceBase {
});

this.serial = await initPromise;

await this.changeSettings(settings);
}

async configureExpoDevMenu(packageName: string) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ function DeviceSettingsDropdown({ children, disabled }: DeviceSettingsDropdownPr
<span className="codicon codicon-location" />
Set Device Location
</DropdownMenu.Item>
{projectState.selectedDevice?.platform === DevicePlatform.IOS && <LocalizationItem />}
<LocalizationItem />
<Label>Permissions</Label>
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger className="dropdown-menu-item">
Expand Down

0 comments on commit cccc52c

Please sign in to comment.