Merge "Show persistent notification for page-agnostic mode" into main

This commit is contained in:
Pawan Wagh
2024-05-07 02:13:53 +00:00
committed by Gerrit Code Review
7 changed files with 384 additions and 67 deletions

View File

@@ -231,6 +231,28 @@
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver
android:name=".development.Enable16KBootReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<service
android:name=".development.PageAgnosticNotificationService"
android:enabled="true"
android:exported="false"
android:permission="android.permission.POST_NOTIFICATIONS"/>
<activity android:name=".development.PageAgnosticWarningActivity"
android:enabled="true"
android:launchMode="singleTask"
android:taskAffinity=""
android:excludeFromRecents="true"
android:theme="@*android:style/Theme.DeviceDefault.Dialog.Alert.DayNight"/>
<activity android:name=".SubSettings" <activity android:name=".SubSettings"
android:exported="false" android:exported="false"
android:theme="@style/Theme.SubSettings" android:theme="@style/Theme.SubSettings"

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2024 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;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.UserHandle;
import androidx.annotation.NonNull;
public class Enable16KBootReceiver extends BroadcastReceiver {
@Override
public void onReceive(@NonNull Context context, @NonNull Intent intent) {
String action = intent.getAction();
if (!Intent.ACTION_BOOT_COMPLETED.equals(action)) {
return;
}
// Do nothing if device is not in page-agnostic mode
if (!Enable16kUtils.isPageAgnosticModeOn(context)) {
return;
}
// start a service to post persistent notification
Intent startNotificationIntent = new Intent(context, PageAgnosticNotificationService.class);
context.startServiceAsUser(startNotificationIntent, UserHandle.SYSTEM);
}
}

View File

@@ -23,17 +23,11 @@ import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle; import android.os.PersistableBundle;
import android.os.PowerManager; import android.os.PowerManager;
import android.os.RecoverySystem; import android.os.RecoverySystem;
import android.os.SystemProperties;
import android.os.SystemUpdateManager; import android.os.SystemUpdateManager;
import android.os.UpdateEngine; import android.os.UpdateEngine;
import android.os.UpdateEngineStable; import android.os.UpdateEngineStable;
import android.os.UpdateEngineStableCallback; import android.os.UpdateEngineStableCallback;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings; import android.provider.Settings;
import android.service.oemlock.OemLockManager;
import android.system.Os;
import android.system.OsConstants;
import android.util.Log; import android.util.Log;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.ProgressBar; import android.widget.ProgressBar;
@@ -59,7 +53,6 @@ import com.google.common.util.concurrent.MoreExecutors;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
@@ -80,10 +73,6 @@ public class Enable16kPagesPreferenceController extends DeveloperOptionsPreferen
private static final String TAG = "Enable16kPages"; private static final String TAG = "Enable16kPages";
private static final String REBOOT_REASON = "toggle16k"; private static final String REBOOT_REASON = "toggle16k";
private static final String ENABLE_16K_PAGES = "enable_16k_pages"; private static final String ENABLE_16K_PAGES = "enable_16k_pages";
@VisibleForTesting
static final String DEV_OPTION_PROPERTY = "ro.product.build.16k_page.enabled";
private static final int ENABLE_4K_PAGE_SIZE = 0; private static final int ENABLE_4K_PAGE_SIZE = 0;
private static final int ENABLE_16K_PAGE_SIZE = 1; private static final int ENABLE_16K_PAGE_SIZE = 1;
@@ -97,9 +86,6 @@ public class Enable16kPagesPreferenceController extends DeveloperOptionsPreferen
private static final int OFFSET_TO_FILE_NAME = 30; private static final int OFFSET_TO_FILE_NAME = 30;
public static final String EXPERIMENTAL_UPDATE_TITLE = "Android 16K Kernel Experimental Update"; public static final String EXPERIMENTAL_UPDATE_TITLE = "Android 16K Kernel Experimental Update";
private static final long PAGE_SIZE = Os.sysconf(OsConstants._SC_PAGESIZE);
private static final int PAGE_SIZE_16KB = 16 * 1024;
private @NonNull DevelopmentSettingsDashboardFragment mFragment; private @NonNull DevelopmentSettingsDashboardFragment mFragment;
private boolean mEnable16k; private boolean mEnable16k;
@@ -112,12 +98,12 @@ public class Enable16kPagesPreferenceController extends DeveloperOptionsPreferen
@NonNull Context context, @NonNull DevelopmentSettingsDashboardFragment fragment) { @NonNull Context context, @NonNull DevelopmentSettingsDashboardFragment fragment) {
super(context); super(context);
this.mFragment = fragment; this.mFragment = fragment;
mEnable16k = (PAGE_SIZE == PAGE_SIZE_16KB); mEnable16k = Enable16kUtils.isUsing16kbPages();
} }
@Override @Override
public boolean isAvailable() { public boolean isAvailable() {
return SystemProperties.getBoolean(DEV_OPTION_PROPERTY, false); return Enable16kUtils.is16KbToggleAvailable();
} }
@Override @Override
@@ -129,12 +115,12 @@ public class Enable16kPagesPreferenceController extends DeveloperOptionsPreferen
public boolean onPreferenceChange(Preference preference, Object newValue) { public boolean onPreferenceChange(Preference preference, Object newValue) {
mEnable16k = (Boolean) newValue; mEnable16k = (Boolean) newValue;
// Prompt user to do oem unlock first // Prompt user to do oem unlock first
if (!isDeviceOEMUnlocked()) { if (!Enable16kUtils.isDeviceOEMUnlocked(mContext)) {
Enable16KOemUnlockDialog.show(mFragment); Enable16KOemUnlockDialog.show(mFragment);
return false; return false;
} }
if (isDataf2fs()) { if (!Enable16kUtils.isDataExt4()) {
EnableExt4WarningDialog.show(mFragment, this); EnableExt4WarningDialog.show(mFragment, this);
return false; return false;
} }
@@ -145,7 +131,7 @@ public class Enable16kPagesPreferenceController extends DeveloperOptionsPreferen
@Override @Override
public void updateState(Preference preference) { public void updateState(Preference preference) {
int defaultOptionValue = int defaultOptionValue =
PAGE_SIZE == PAGE_SIZE_16KB ? ENABLE_16K_PAGE_SIZE : ENABLE_4K_PAGE_SIZE; Enable16kUtils.isUsing16kbPages() ? ENABLE_16K_PAGE_SIZE : ENABLE_4K_PAGE_SIZE;
final int optionValue = final int optionValue =
Settings.Global.getInt( Settings.Global.getInt(
mContext.getContentResolver(), mContext.getContentResolver(),
@@ -169,7 +155,7 @@ public class Enable16kPagesPreferenceController extends DeveloperOptionsPreferen
@Override @Override
protected void onDeveloperOptionsSwitchEnabled() { protected void onDeveloperOptionsSwitchEnabled() {
int currentStatus = int currentStatus =
PAGE_SIZE == PAGE_SIZE_16KB ? ENABLE_16K_PAGE_SIZE : ENABLE_4K_PAGE_SIZE; Enable16kUtils.isUsing16kbPages() ? ENABLE_16K_PAGE_SIZE : ENABLE_4K_PAGE_SIZE;
Settings.Global.putInt( Settings.Global.putInt(
mContext.getContentResolver(), Settings.Global.ENABLE_16K_PAGES, currentStatus); mContext.getContentResolver(), Settings.Global.ENABLE_16K_PAGES, currentStatus);
} }
@@ -432,51 +418,6 @@ public class Enable16kPagesPreferenceController extends DeveloperOptionsPreferen
return infoBundle; return infoBundle;
} }
private boolean isDataf2fs() {
try (BufferedReader br = new BufferedReader(new FileReader("/proc/mounts"))) {
String line;
while ((line = br.readLine()) != null) {
final String[] fields = line.split(" ");
final String partition = fields[1];
final String fsType = fields[2];
if (partition.equals("/data") && fsType.equals("f2fs")) {
return true;
}
}
} catch (IOException e) {
Log.e(TAG, "Failed to read /proc/mounts");
displayToast(mContext.getString(R.string.format_ext4_failure_toast));
}
return false;
}
private boolean isDeviceOEMUnlocked() {
// OEM unlock is checked for bootloader, carrier and user. Check all three to ensure
// that device is unlocked and it is also allowed by user as well as carrier
final OemLockManager oemLockManager = mContext.getSystemService(OemLockManager.class);
final UserManager userManager = mContext.getSystemService(UserManager.class);
if (oemLockManager == null || userManager == null) {
Log.e(TAG, "Required services not found on device to check for OEM unlock state.");
return false;
}
// If either of device or carrier is not allowed to unlock, return false
if (!oemLockManager.isDeviceOemUnlocked()
|| !oemLockManager.isOemUnlockAllowedByCarrier()) {
Log.e(TAG, "Device is not OEM unlocked or it is not allowed by carrier");
return false;
}
final UserHandle userHandle = UserHandle.of(UserHandle.myUserId());
if (userManager.hasBaseUserRestriction(UserManager.DISALLOW_FACTORY_RESET, userHandle)) {
Log.e(TAG, "Factory reset is not allowed for user.");
return false;
}
return true;
}
// if BOARD_16K_OTA_MOVE_VENDOR, OTAs will be present on the /vendor partition // if BOARD_16K_OTA_MOVE_VENDOR, OTAs will be present on the /vendor partition
private File getOtaFile() throws FileNotFoundException { private File getOtaFile() throws FileNotFoundException {
String otaPath = mEnable16k ? OTA_16K_PATH : OTA_4K_PATH; String otaPath = mEnable16k ? OTA_16K_PATH : OTA_4K_PATH;

View File

@@ -0,0 +1,119 @@
/*
* Copyright (C) 2024 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;
import android.content.Context;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.service.oemlock.OemLockManager;
import android.system.Os;
import android.system.OsConstants;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class Enable16kUtils {
private static final long PAGE_SIZE = Os.sysconf(OsConstants._SC_PAGESIZE);
private static final int PAGE_SIZE_16KB = 16 * 1024;
@VisibleForTesting
static final String DEV_OPTION_PROPERTY = "ro.product.build.16k_page.enabled";
private static final String TAG = "Enable16kUtils";
/**
* @param context uses context to retrieve OEM unlock info
* @return true if device is OEM unlocked and factory reset is allowed for user.
*/
public static boolean isDeviceOEMUnlocked(@NonNull Context context) {
// OEM unlock is checked for bootloader, carrier and user. Check all three to ensure
// that device is unlocked and it is also allowed by user as well as carrier
final OemLockManager oemLockManager = context.getSystemService(OemLockManager.class);
final UserManager userManager = context.getSystemService(UserManager.class);
if (oemLockManager == null || userManager == null) {
Log.e(TAG, "Required services not found on device to check for OEM unlock state.");
return false;
}
// If either of device or carrier is not allowed to unlock, return false
if (!oemLockManager.isDeviceOemUnlocked()) {
Log.e(TAG, "Device is not OEM unlocked");
return false;
}
final UserHandle userHandle = UserHandle.of(UserHandle.myUserId());
if (userManager.hasBaseUserRestriction(UserManager.DISALLOW_FACTORY_RESET, userHandle)) {
Log.e(TAG, "Factory reset is not allowed for user.");
return false;
}
return true;
}
/**
* @return true if /data partition is ext4
*/
public static boolean isDataExt4() {
try (BufferedReader br = new BufferedReader(new FileReader("/proc/mounts"))) {
String line;
while ((line = br.readLine()) != null) {
Log.i(TAG, line);
final String[] fields = line.split(" ");
final String partition = fields[1];
final String fsType = fields[2];
if (partition.equals("/data") && fsType.equals("ext4")) {
return true;
}
}
} catch (IOException e) {
Log.e(TAG, "Failed to read /proc/mounts");
}
return false;
}
/**
* @return returns true if 16KB developer option is available for the device.
*/
public static boolean is16KbToggleAvailable() {
return SystemProperties.getBoolean(DEV_OPTION_PROPERTY, false);
}
/**
* 16kB page-agnostic mode requires /data to be ext4, ro.product.build.16k_page.enabled for
* device and Device OEM unlocked.
*
* @param context is needed to query OEM unlock state
* @return true if device is in page-agnostic mode.
*/
public static boolean isPageAgnosticModeOn(@NonNull Context context) {
return is16KbToggleAvailable() && isDeviceOEMUnlocked(context) && isDataExt4();
}
/**
* @return returns true if current page size is 16KB
*/
public static boolean isUsing16kbPages() {
return PAGE_SIZE == PAGE_SIZE_16KB;
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright (C) 2024 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;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.settings.R;
public class PageAgnosticNotificationService extends Service {
private static final String NOTIFICATION_CHANNEL_ID =
"com.android.settings.development.PageAgnosticNotificationService";
private static final int NOTIFICATION_ID = 1;
private NotificationManager mNotificationManager;
@Nullable
@Override
public IBinder onBind(@NonNull Intent intent) {
return null;
}
// create a notification channel to post persistent notification
private void createNotificationChannel() {
NotificationChannel channel =
new NotificationChannel(
NOTIFICATION_CHANNEL_ID,
getString(R.string.page_agnostic_notification_channel_name),
NotificationManager.IMPORTANCE_HIGH);
mNotificationManager = getSystemService(NotificationManager.class);
if (mNotificationManager != null) {
mNotificationManager.createNotificationChannel(channel);
}
}
@Override
public void onCreate() {
super.onCreate();
createNotificationChannel();
}
private Notification buildNotification() {
// Get the title and text according to page size
boolean isIn16kbMode = Enable16kUtils.isUsing16kbPages();
String title =
isIn16kbMode
? getString(R.string.page_agnostic_16k_pages_title)
: getString(R.string.page_agnostic_4k_pages_title);
String text =
isIn16kbMode
? getString(R.string.page_agnostic_16k_pages_text_short)
: getString(R.string.page_agnostic_4k_pages_text_short);
Intent notifyIntent = new Intent(this, PageAgnosticWarningActivity.class);
// Set the Activity to start in a new, empty task.
notifyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
// Create the PendingIntent.
PendingIntent notifyPendingIntent =
PendingIntent.getActivity(
this,
0,
notifyIntent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
Notification.Action action =
new Notification.Action.Builder(
R.drawable.empty_icon,
getString(R.string.page_agnostic_notification_action),
notifyPendingIntent)
.build();
Notification.Builder builder =
new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
.setContentTitle(title)
.setContentText(text)
.setOngoing(true)
.setSmallIcon(R.drawable.ic_settings_24dp)
.setStyle(new Notification.BigTextStyle().bigText(text))
.setContentIntent(notifyPendingIntent)
.addAction(action);
return builder.build();
}
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
Notification notification = buildNotification();
if (mNotificationManager != null) {
mNotificationManager.notify(NOTIFICATION_ID, notification);
}
// When clicked on notification, show dialog with full text
return Service.START_NOT_STICKY;
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright (C) 2024 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;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.text.Html;
import android.text.method.LinkMovementMethod;
import android.widget.TextView;
import androidx.annotation.NonNull;
import com.android.settings.R;
public class PageAgnosticWarningActivity extends Activity {
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
String title =
Enable16kUtils.isUsing16kbPages()
? getString(R.string.page_agnostic_16k_pages_title)
: getString(R.string.page_agnostic_4k_pages_title);
String warningText =
Enable16kUtils.isUsing16kbPages()
? getString(R.string.page_agnostic_16k_pages_text)
: getString(R.string.page_agnostic_4k_pages_text);
showWarningDialog(title, warningText);
}
// Create warning dialog and make links clickable
private void showWarningDialog(String title, String warningText) {
AlertDialog dialog =
new AlertDialog.Builder(this)
.setTitle(title)
.setMessage(Html.fromHtml(warningText, Html.FROM_HTML_MODE_COMPACT))
.setCancelable(false)
.setPositiveButton(
android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(
@NonNull DialogInterface dialog, int which) {
dialog.cancel();
finish();
}
})
.create();
dialog.show();
((TextView) dialog.findViewById(android.R.id.message))
.setMovementMethod(LinkMovementMethod.getInstance());
}
}

View File

@@ -62,13 +62,13 @@ public class Enable16kPagesPreferenceControllerTest {
@Test @Test
public void onSystemPropertyDisabled_shouldDisablePreference() { public void onSystemPropertyDisabled_shouldDisablePreference() {
SystemProperties.set(Enable16kPagesPreferenceController.DEV_OPTION_PROPERTY, "false"); SystemProperties.set(Enable16kUtils.DEV_OPTION_PROPERTY, "false");
assertThat(mController.isAvailable()).isEqualTo(false); assertThat(mController.isAvailable()).isEqualTo(false);
} }
@Test @Test
public void onSystemPropertyEnabled_shouldEnablePreference() { public void onSystemPropertyEnabled_shouldEnablePreference() {
SystemProperties.set(Enable16kPagesPreferenceController.DEV_OPTION_PROPERTY, "true"); SystemProperties.set(Enable16kUtils.DEV_OPTION_PROPERTY, "true");
assertThat(mController.isAvailable()).isEqualTo(true); assertThat(mController.isAvailable()).isEqualTo(true);
} }