Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4795662e authored by Pawan Wagh's avatar Pawan Wagh Committed by Gerrit Code Review
Browse files

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

parents 12ff1e24 a697fba5
Loading
Loading
Loading
Loading
+22 −0
Original line number Diff line number Diff line
@@ -231,6 +231,28 @@
            </intent-filter>
        </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"
                  android:exported="false"
                  android:theme="@style/Theme.SubSettings"
+44 −0
Original line number Diff line number Diff line
/*
 * 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);
    }
}
+6 −65
Original line number Diff line number Diff line
@@ -23,17 +23,11 @@ import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.PowerManager;
import android.os.RecoverySystem;
import android.os.SystemProperties;
import android.os.SystemUpdateManager;
import android.os.UpdateEngine;
import android.os.UpdateEngineStable;
import android.os.UpdateEngineStableCallback;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.service.oemlock.OemLockManager;
import android.system.Os;
import android.system.OsConstants;
import android.util.Log;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
@@ -59,7 +53,6 @@ import com.google.common.util.concurrent.MoreExecutors;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@@ -80,10 +73,6 @@ public class Enable16kPagesPreferenceController extends DeveloperOptionsPreferen
    private static final String TAG = "Enable16kPages";
    private static final String REBOOT_REASON = "toggle16k";
    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_16K_PAGE_SIZE = 1;

@@ -97,9 +86,6 @@ public class Enable16kPagesPreferenceController extends DeveloperOptionsPreferen
    private static final int OFFSET_TO_FILE_NAME = 30;
    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 boolean mEnable16k;

@@ -112,12 +98,12 @@ public class Enable16kPagesPreferenceController extends DeveloperOptionsPreferen
            @NonNull Context context, @NonNull DevelopmentSettingsDashboardFragment fragment) {
        super(context);
        this.mFragment = fragment;
        mEnable16k = (PAGE_SIZE == PAGE_SIZE_16KB);
        mEnable16k = Enable16kUtils.isUsing16kbPages();
    }

    @Override
    public boolean isAvailable() {
        return SystemProperties.getBoolean(DEV_OPTION_PROPERTY, false);
        return Enable16kUtils.is16KbToggleAvailable();
    }

    @Override
@@ -129,12 +115,12 @@ public class Enable16kPagesPreferenceController extends DeveloperOptionsPreferen
    public boolean onPreferenceChange(Preference preference, Object newValue) {
        mEnable16k = (Boolean) newValue;
        // Prompt user to do oem unlock first
        if (!isDeviceOEMUnlocked()) {
        if (!Enable16kUtils.isDeviceOEMUnlocked(mContext)) {
            Enable16KOemUnlockDialog.show(mFragment);
            return false;
        }

        if (isDataf2fs()) {
        if (!Enable16kUtils.isDataExt4()) {
            EnableExt4WarningDialog.show(mFragment, this);
            return false;
        }
@@ -145,7 +131,7 @@ public class Enable16kPagesPreferenceController extends DeveloperOptionsPreferen
    @Override
    public void updateState(Preference preference) {
        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 =
                Settings.Global.getInt(
                        mContext.getContentResolver(),
@@ -169,7 +155,7 @@ public class Enable16kPagesPreferenceController extends DeveloperOptionsPreferen
    @Override
    protected void onDeveloperOptionsSwitchEnabled() {
        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(
                mContext.getContentResolver(), Settings.Global.ENABLE_16K_PAGES, currentStatus);
    }
@@ -432,51 +418,6 @@ public class Enable16kPagesPreferenceController extends DeveloperOptionsPreferen
        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
    private File getOtaFile() throws FileNotFoundException {
        String otaPath = mEnable16k ? OTA_16K_PATH : OTA_4K_PATH;
+119 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}
+119 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}
Loading