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

Commit f0d429bc authored by Rohit Sekhar's avatar Rohit Sekhar
Browse files

Merge branch '3570os-a15-fp_moments' into 'v3.4-a15'

FP6: parts: Enable support for moments && reorganize

See merge request e/devices/android_device_fairphone_FP6!15
parents 9ee59ab6 0d8fd7c5
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -167,6 +167,9 @@ PRODUCT_COPY_FILES += \
PRODUCT_COPY_FILES += \
    $(LOCAL_PATH)/configs/permissions/privapp-permissions-fpcam.xml:$(TARGET_COPY_OUT_SYSTEM)/etc/permissions/privapp-permissions-fpcam.xml

# Fairphone Moments
$(call inherit-product, external/FairphoneMoments/config.mk)

# GPS
PRODUCT_COPY_FILES += \
    frameworks/native/data/etc/android.hardware.location.gps.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.location.gps.xml
+1 −0
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@

    <!-- Toast -->
    <string name="sensor_access">Camera &amp; mic access</string>
    <string name="fairphone_moments">Fairphone Moments</string>
    <string name="do_not_disturb">Do Not Disturb</string>
    <string name="flight_mode">Airplane mode</string>
    <string name="torch">Torch</string>
+6 −0
Original line number Diff line number Diff line
@@ -14,6 +14,12 @@
        android:summary="@string/sensor_privacy_summary"
        android:defaultValue="true" />

    <org.lineageos.settings.preference.RadioButtonPreference
        android:key="fairphone_moments"
        android:title="@string/fairphone_moments_title"
        android:summary="@string/fairphone_moments_summary"
        android:defaultValue="false" />

    <org.lineageos.settings.preference.RadioButtonPreference
        android:key="do_not_disturb"
        android:title="@string/do_not_disturb_title"
+27 −0
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@ public class AppPrefs {

    private static final String KEY_FIRST_BOOT = "first_boot";
    private static final String KEY_FPCAMERA = "fp_camera_pref";
    private static final String KEY_CURRENT_LAUNCHER = "current_launcher";

    private AppPrefs(Context context) {
        this.context = context.getApplicationContext();
@@ -27,6 +28,19 @@ public class AppPrefs {
        return instance;
    }

    public String getSavedHomeApp() {
        return prefs.getString(KEY_CURRENT_LAUNCHER, "");
    }

    public void saveDefaultHomeApp(String packageName) {
        prefs.edit().putString(KEY_CURRENT_LAUNCHER, packageName).apply();
    }

    public void saveCurrentHomeApp() {
        String currentHomeApp = getDefaultHomeAppPackageName();
        saveDefaultHomeApp(currentHomeApp);
    }

    public boolean getIsFirstBoot() {
        return prefs.getBoolean(KEY_FIRST_BOOT, true);
    }
@@ -42,4 +56,17 @@ public class AppPrefs {
    public void setFpCameraEnabled(boolean enabled) {
        prefs.edit().putBoolean(KEY_FPCAMERA, enabled).apply();
    }

    private String getDefaultHomeAppPackageName() {
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_HOME);
        ResolveInfo resolveInfo =
                context.getPackageManager()
                        .resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);

        if (resolveInfo != null && resolveInfo.activityInfo != null) {
            return resolveInfo.activityInfo.packageName;
        }
        return "";
    }
}
+256 −0
Original line number Diff line number Diff line
package org.lineageos.settings.switchcust;

import android.app.ActivityManager;
import android.app.role.RoleManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.UserHandle;
import android.util.Log;

import androidx.core.content.ContextCompat;

import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;

import org.lineageos.settings.AppPrefs;

public class LauncherSwitcherService {
    private static final String TAG = "LauncherSwitcherService";

    /** Callback interface for launcher switching operations */
    public interface LauncherSwitchCallback {
        void onSuccess();
        void onError(Exception exception);
    }

    public void switchToUserLauncher(
            Context context, LauncherSwitchCallback callback) {
        Executor executor = ContextCompat.getMainExecutor(context);

        executor.execute(
                () -> {
                    try {
                        AppPrefs appPrefs = AppPrefs.getInstance(context);
                        String userLauncherApp = appPrefs.getSavedHomeApp();
                        boolean shouldShowOverlayAnimation = shouldShowOverlayAnimation(context);

                        if (!isPackageAvailable(context, userLauncherApp)) {
                            userLauncherApp = SwitchConstants.PACKAGE_MOMENTS;
                        }

                        // Create a final variable for use in lambda
                        final String finalUserLauncherApp = userLauncherApp;

                        setDefaultHomeAppAsync(context, finalUserLauncherApp)
                                .thenAccept(
                                        success -> {
                                            if (success) {
                                                startSwitchStateChangeActivity(
                                                        context, false, shouldShowOverlayAnimation);
                                                callback.onSuccess();
                                            } else {
                                                callback.onError(
                                                        new Exception(
                                                                "Failed to set default launcher"));
                                            }
                                        })
                                .exceptionally(
                                        throwable -> {
                                            String fallbackLauncher =
                                                    SwitchConstants.PACKAGE_BLISS;
                                            Log.e(
                                                    TAG,
                                                    "Exception occurred: "
                                                            + throwable.getMessage());
                                            callback.onError(
                                                    new Exception(
                                                            "Async operation failed", throwable));
                                            return null;
                                        });

                    } catch (Exception e) {
                        callback.onError(e);
                    }
                });
    }

    public void switchToFairphoneMoments(
            Context context, LauncherSwitchCallback callback) {
        Executor executor = ContextCompat.getMainExecutor(context);

        executor.execute(
                () -> {
                    try {
                        if (!isFairphoneMomentsAvailable(context)) {
                            callback.onError(new Exception("Fairphone Moments is not available"));
                            return;
                        }

                        AppPrefs appPrefs = AppPrefs.getInstance(context);
                        String currentHomeApp = getDefaultHomeAppPackageName(context);
                        appPrefs.saveDefaultHomeApp(currentHomeApp);
                        boolean shouldShowOverlayAnimation = shouldShowOverlayAnimation(context);

                        setDefaultHomeAppAsync(context, SwitchConstants.PACKAGE_MOMENTS)
                                .thenAccept(
                                        success -> {
                                            if (success) {
                                                startSwitchStateChangeActivity(
                                                        context, true, shouldShowOverlayAnimation);
                                                callback.onSuccess();
                                            } else {
                                                callback.onError(
                                                        new Exception(
                                                                "Failed to set default launcher"));
                                            }
                                        })
                                .exceptionally(
                                        throwable -> {
                                            callback.onError(
                                                    new Exception(
                                                            "Eror setting default launcher",
                                                            throwable));
                                            return null;
                                        });

                    } catch (Exception e) {
                        callback.onError(e);
                    }
                });
    }

    private CompletableFuture<Boolean> setDefaultHomeAppAsync(
            Context context, String packageName) {
        CompletableFuture<Boolean> future = new CompletableFuture<>();

        Log.d(TAG, "Setting default home app: " + packageName);

        try {
            RoleManager roleManager = getRoleManager(context);
            if (roleManager == null) {
                future.complete(false);
                return future;
            }

            int foregroundUser = ActivityManager.getCurrentUser();
            roleManager.addRoleHolderAsUser(
                    RoleManager.ROLE_HOME,
                    packageName,
                    0,
                    UserHandle.of(foregroundUser),
                    ContextCompat.getMainExecutor(context),
                    future::complete);
        } catch (Exception e) {
            Log.e(TAG, "Error setting " + packageName + " as default home app", e);
            future.complete(false);
        }

        return future;
    }

    private String getDefaultHomeAppPackageName(Context context) {
        RoleManager roleManager = getRoleManager(context);
        if (roleManager == null) {
            throw new IllegalStateException("RoleManager is not available");
        }

        String packageName = roleManager.getDefaultApplication(RoleManager.ROLE_HOME);
        Log.d(TAG, "Default home app package name: " + packageName);

        if (packageName == null) {
            throw new IllegalStateException("Could not get default home app package name");
        }

        if (SwitchConstants.PACKAGE_MOMENTS.equals(packageName)) {
            return SwitchConstants.PACKAGE_BLISS;
        } else {
            return packageName;
        }
    }

    /** Start switch state change activity in detox launcher to display overlay. */
    private void startSwitchStateChangeActivity(
            Context context, boolean detoxEnabled, boolean showOverlay) {
        Intent intent = new Intent(SwitchConstants.ACTION_SHOW_SWITCH_BUTTON_HINT);
        intent.putExtra(
                SwitchConstants.EXTRA_SWITCH_BUTTON_STATE,
                detoxEnabled
                        ? SwitchConstants.EXTRA_SWITCH_BUTTON_STATE_ENABLED
                        : SwitchConstants.EXTRA_SWITCH_BUTTON_STATE_DISABLED);
        intent.putExtra(SwitchConstants.EXTRA_SHOW_OVERLAY, showOverlay);
        intent.setPackage(SwitchConstants.PACKAGE_MOMENTS);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);

        context.startActivityAsUser(intent, UserHandle.SYSTEM);
    }

    /**
     * Detox Launcher should display an overlay animation when user is interacting with any app, or
     * what is the same: it should NOT display an overlay animation when user is interacting with
     * stock launcher or spring launcher
     */
    private boolean shouldShowOverlayAnimation(Context context) {
        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        if (am == null) {
            return true;
        }

        try {
            // Get the foreground task
            List<ActivityManager.RunningTaskInfo> runningTasks = am.getRunningTasks(1);
            String defaultHomeAppPackage = getDefaultHomeAppPackageName(context);

            if (runningTasks != null && !runningTasks.isEmpty()) {
                ActivityManager.RunningTaskInfo task = runningTasks.get(0);
                if (task != null && task.topActivity != null) {
                    String topPackageName = task.topActivity.getPackageName();
                    String topClassName = task.topActivity.getClassName();

                    Log.d(TAG, "Top activity package: " + topPackageName);
                    Log.d(TAG, "Top activity name: " + topClassName);
                    Log.d(TAG, "Default home app package: " + defaultHomeAppPackage);

                    if (defaultHomeAppPackage.equals(topPackageName)) {
                        return false;
                    }
                    if (SwitchConstants.PACKAGE_MOMENTS.equals(topPackageName)
                            && SwitchConstants.FAIRPHONE_MOMENTS_HOME_ACTIVITY.equals(
                                    topClassName)) {
                        return false;
                    }
                    return true;
                }
            }
        } catch (SecurityException e) {
            Log.e(TAG, "Permission needed for getRunningTasks", e);
        } catch (Exception e) {
            Log.e(TAG, "Error checking overlay animation requirement", e);
        }

        return true;
    }

    private RoleManager getRoleManager(Context context) {
        try {
            return context.getSystemService(RoleManager.class);
        } catch (Exception e) {
            Log.e(TAG, "Failed to get RoleManager", e);
            return null;
        }
    }

    private boolean isPackageAvailable(Context context, String packageName) {
        try {
            context.getPackageManager().getPackageInfo(packageName, 0);
            return true;
        } catch (PackageManager.NameNotFoundException e) {
            return false;
        }
    }

    private boolean isFairphoneMomentsAvailable(Context context) {
        return isPackageAvailable(context, SwitchConstants.PACKAGE_MOMENTS);
    }
}
Loading