OmniLib: Move utils packages into private api and the rest, public

Change-Id: Ic9fa3ae9ae9c741b86bcb0fd7a52658d8af6dcbd
diff --git a/core/AndroidManifest.xml b/core/AndroidManifest.xml
new file mode 100644
index 0000000..5e3bcdd
--- /dev/null
+++ b/core/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--  Copyright (C) 2016 The OmniROM Project
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:tools="http://schemas.android.com/tools"
+    package="org.omnirom.omnilibcore">
+
+</manifest>
diff --git a/core/org/omnirom/omnilibcore/utils/DeviceKeyHandler.java b/core/org/omnirom/omnilibcore/utils/DeviceKeyHandler.java
new file mode 100644
index 0000000..39313ab
--- /dev/null
+++ b/core/org/omnirom/omnilibcore/utils/DeviceKeyHandler.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2012 The CyanogenMod Project
+ * Copyright (C) 2015-2021 The OmniROM 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 org.omnirom.omnilibcore.utils;
+
+import android.content.Intent;
+import android.hardware.SensorEvent;
+import android.view.KeyEvent;
+
+public interface DeviceKeyHandler {
+
+    /**
+     * Invoked when an unknown key was detected by the system, letting the device handle
+     * this special keys prior to pass the key to the active app.
+     *
+     * @param event The key event to be handled
+     * @return If the event is consume
+     */
+    public boolean handleKeyEvent(KeyEvent event);
+
+    /**
+     * Invoked when an unknown key was detected by the system,
+     * this should NOT handle the key just return if it WOULD be handled
+     *
+     * @param event The key event to be handled
+     * @return If the event will be consumed
+     */
+    public boolean canHandleKeyEvent(KeyEvent event);
+
+    /**
+     * Special key event that should be treated as
+     * a camera launch event
+     *
+     * @param event The key event to be handled
+     * @return If the event is a camera launch event
+     */
+    public boolean isCameraLaunchEvent(KeyEvent event);
+
+    /**
+     * Special key event that should be treated as
+     * a wake event
+     *
+     * @param event The key event to be handled
+     * @return If the event is a wake event
+     */
+    public boolean isWakeEvent(KeyEvent event);
+
+    /**
+     * Return false if this event should be ignored
+     *
+     * @param event The key event to be handled
+     * @return If the event should be ignored
+     */
+    public boolean isDisabledKeyEvent(KeyEvent event);
+
+    /**
+     * Return an Intent that should be launched for that KeyEvent
+     *
+     * @param event The key event to be handled
+     * @return an Intent or null
+     */
+    public Intent isActivityLaunchEvent(KeyEvent event);
+
+    default public String getCustomProxiSensor() {
+        return null;
+    }
+
+    default public boolean getCustomProxiIsNear(SensorEvent event) {
+        return false;
+    }
+}
diff --git a/core/org/omnirom/omnilibcore/utils/DeviceUtils.java b/core/org/omnirom/omnilibcore/utils/DeviceUtils.java
new file mode 100644
index 0000000..9ae10a8
--- /dev/null
+++ b/core/org/omnirom/omnilibcore/utils/DeviceUtils.java
@@ -0,0 +1,141 @@
+/*
+* Copyright (C) 2014-2021 The OmniROM 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 org.omnirom.omnilibcore.utils;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraManager;
+import android.hardware.SensorManager;
+import android.net.ConnectivityManager;
+import android.nfc.NfcAdapter;
+import android.os.SystemProperties;
+import android.os.Vibrator;
+import android.os.UserHandle;
+import android.telephony.TelephonyManager;
+import android.util.DisplayMetrics;
+import android.view.DisplayInfo;
+import android.view.WindowManager;
+import android.provider.Settings;
+
+import com.android.internal.telephony.PhoneConstants;
+import static android.hardware.Sensor.TYPE_LIGHT;
+import static android.hardware.Sensor.TYPE_PROXIMITY;
+
+import java.util.List;
+
+public class DeviceUtils {
+
+    // Device types
+    public static final int DEVICE_PHONE  = 0;
+    public static final int DEVICE_HYBRID = 1;
+    public static final int DEVICE_TABLET = 2;
+
+    public static boolean deviceSupportsNfc(Context context) {
+        return (NfcAdapter.getDefaultAdapter(context) != null) ||
+           context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC);
+    }
+
+    public static boolean deviceSupportsVibrator(Context ctx) {
+        Vibrator vibrator = (Vibrator) ctx.getSystemService(Context.VIBRATOR_SERVICE);
+        return vibrator.hasVibrator();
+    }
+
+    public static boolean deviceSupportsProximitySensor(Context context) {
+        SensorManager sm = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
+        return sm.getDefaultSensor(TYPE_PROXIMITY) != null;
+    }
+
+    public static boolean deviceSupportsLightSensor(Context context) {
+        SensorManager sm = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
+        return sm.getDefaultSensor(TYPE_LIGHT) != null;
+    }
+
+    public static boolean deviceSupportNavigationBar(Context context) {
+        return deviceSupportNavigationBarForUser(context, UserHandle.USER_CURRENT);
+    }
+
+    public static boolean deviceSupportNavigationBarForUser(Context context, int userId) {
+        final boolean showByDefault = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_showNavigationBar);
+        /*final int hasNavigationBar = Settings.System.getIntForUser(
+                context.getContentResolver(),
+                Settings.System.OMNI_NAVIGATION_BAR_SHOW, -1, userId);*/
+        final int hasNavigationBar = -1;
+        if (hasNavigationBar == -1) {
+            String navBarOverride = SystemProperties.get("qemu.hw.mainkeys");
+            if ("1".equals(navBarOverride)) {
+                return false;
+            } else if ("0".equals(navBarOverride)) {
+                return true;
+            } else {
+                return showByDefault;
+            }
+        } else {
+            return hasNavigationBar == 1;
+        }
+    }
+
+    private static int getScreenType(Context con) {
+        WindowManager wm = (WindowManager)con.getSystemService(Context.WINDOW_SERVICE);
+        DisplayInfo outDisplayInfo = new DisplayInfo();
+        wm.getDefaultDisplay().getDisplayInfo(outDisplayInfo);
+        int shortSize = Math.min(outDisplayInfo.logicalHeight, outDisplayInfo.logicalWidth);
+        int shortSizeDp =
+            shortSize * DisplayMetrics.DENSITY_DEFAULT / outDisplayInfo.logicalDensityDpi;
+        if (shortSizeDp < 600) {
+            return DEVICE_PHONE;
+        } else if (shortSizeDp < 720) {
+            return DEVICE_HYBRID;
+        } else {
+            return DEVICE_TABLET;
+        }
+    }
+
+    public static boolean isPhone(Context con) {
+        return getScreenType(con) == DEVICE_PHONE;
+    }
+
+    public static boolean isHybrid(Context con) {
+        return getScreenType(con) == DEVICE_HYBRID;
+    }
+
+    public static boolean isTablet(Context con) {
+        return getScreenType(con) == DEVICE_TABLET;
+    }
+
+    public static boolean isLandscapePhone(Context context) {
+        Configuration config = context.getResources().getConfiguration();
+        return config.orientation == Configuration.ORIENTATION_LANDSCAPE
+                && config.smallestScreenWidthDp < 600;
+    }
+
+    public static boolean isDataEncrypted() {
+        String voldState = SystemProperties.get("vold.decrypt");
+        return "1".equals(voldState) || "trigger_restart_min_framework".equals(voldState);
+    }
+
+    public static boolean isVoiceCapable(Context context) {
+        final TelephonyManager telephony =
+                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+        return telephony != null && telephony.isVoiceCapable();
+    }
+}
diff --git a/core/org/omnirom/omnilibcore/utils/OmniServiceLocator.java b/core/org/omnirom/omnilibcore/utils/OmniServiceLocator.java
new file mode 100644
index 0000000..5703910
--- /dev/null
+++ b/core/org/omnirom/omnilibcore/utils/OmniServiceLocator.java
@@ -0,0 +1,231 @@
+/*
+ *  Copyright (C) 2021 The OmniROM Project
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package org.omnirom.omnilibcore.utils;
+
+import android.content.Context;
+import android.net.Uri;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.webkit.URLUtil;
+
+public class OmniServiceLocator {
+
+    private static String getWalllpaperBaseUrl(Context context) {
+        String s = Settings.System.getString(context.getContentResolver(), "wallpaper_base_url");
+        if (TextUtils.isEmpty(s)) {
+            return "https://dl.omnirom.org/";
+        }
+        return s;
+    }
+
+    private static String getWalllpaperRootUri(Context context) {
+        String s = Settings.System.getString(context.getContentResolver(), "wallpaper_root_uri");
+        if (TextUtils.isEmpty(s)) {
+            return "images/wallpapers/";
+        }
+        return s;
+    }
+
+    private static String getWalllpaperQueryUri(Context context) {
+        String s = Settings.System.getString(context.getContentResolver(), "wallpaper_query_uri");
+        if (TextUtils.isEmpty(s)) {
+            return "images/wallpapers/thumbs/json_wallpapers_xml.php";
+        }
+        return s;
+    }
+
+    private static String getHeaderBaseUrl(Context context) {
+        String s = Settings.System.getString(context.getContentResolver(), "header_base_url");
+        if (TextUtils.isEmpty(s)) {
+            return "https://dl.omnirom.org/";
+        }
+        return s;
+    }
+
+    private static String getHeaderRootUri(Context context) {
+        String s = Settings.System.getString(context.getContentResolver(), "header_root_uri");
+        if (TextUtils.isEmpty(s)) {
+            return "images/headers/";
+        }
+        return s;
+    }
+
+    private static String getHeaderQueryUri(Context context) {
+        String s = Settings.System.getString(context.getContentResolver(), "header_query_uri");
+        if (TextUtils.isEmpty(s)) {
+            return "images/headers/thumbs/json_headers_xml.php";
+        }
+        return s;
+    }
+
+    // OmniStore is external apk and dont use this so its just FYI
+    private static String getStoreBaseUrl(Context context) {
+        String s = Settings.System.getString(context.getContentResolver(), "store_base_url");
+        if (TextUtils.isEmpty(s)) {
+            return "https://dl.omnirom.org/";
+        }
+        return s;
+    }
+
+    private static String getStoreRootUri(Context context) {
+        String s = Settings.System.getString(context.getContentResolver(), "store_root_uri");
+        if (TextUtils.isEmpty(s)) {
+            return "store/";
+        }
+        return s;
+    }
+
+    private static String getStoreQuertUri(Context context) {
+        String s = Settings.System.getString(context.getContentResolver(), "store_query_uri");
+        if (TextUtils.isEmpty(s)) {
+            return "store/apps.json";
+        }
+        return s;
+    }
+
+    private static String getBuildsBaseUrl(Context context) {
+        String s = Settings.System.getString(context.getContentResolver(), "builds_base_url");
+        if (TextUtils.isEmpty(s)) {
+            return "https://dl.omnirom.org/";
+        }
+        return s;
+    }
+
+    private static String getBuildsRootUri(Context context) {
+        String s = Settings.System.getString(context.getContentResolver(), "builds_root_uri");
+        if (TextUtils.isEmpty(s)) {
+            return null;
+        }
+        return s;
+    }
+
+    private static String getBuildsQueryUri(Context context) {
+        String s = Settings.System.getString(context.getContentResolver(), "builds_query_uri");
+        if (TextUtils.isEmpty(s)) {
+            return "json.php";
+        }
+        return s;
+    }
+
+    private static String getBuildsSecondaryBaseUrl(Context context) {
+        String s = Settings.System.getString(context.getContentResolver(), "builds_secondary_base_url");
+        if (TextUtils.isEmpty(s)) {
+            return "https://dl.omnirom.org/tmp/";
+        }
+        return s;
+    }
+
+    private static String getBuildsDeltaBaseUrl(Context context) {
+        String s = Settings.System.getString(context.getContentResolver(), "builds_delta_base_url");
+        if (TextUtils.isEmpty(s)) {
+            return "https://delta.omnirom.org/";
+        }
+        return s;
+    }
+
+    private static String getBuildsDeltaRootUri(Context context) {
+        String s = Settings.System.getString(context.getContentResolver(), "builds_delta_root_uri");
+        if (TextUtils.isEmpty(s)) {
+            return "weeklies/";
+        }
+        return s;
+    }
+
+    public static String buildWalllpaperQueryUrl(Context context) {
+        String queryUri = getWalllpaperQueryUri(context);
+        if (URLUtil.isNetworkUrl(queryUri)) {
+            return queryUri;
+        }
+        Uri base = Uri.parse(getWalllpaperBaseUrl(context));
+        Uri u = Uri.withAppendedPath(base, queryUri);
+        return u.toString();
+    }
+
+    public static String buildWalllpaperRootUrl(Context context) {
+        String rootUri = getWalllpaperRootUri(context);
+        if (TextUtils.isEmpty(rootUri)) {
+            return getWalllpaperBaseUrl(context);
+        }
+        if (URLUtil.isNetworkUrl(rootUri)) {
+            return rootUri;
+        }
+        Uri base = Uri.parse(getWalllpaperBaseUrl(context));
+        Uri u = Uri.withAppendedPath(base, rootUri);
+        return u.toString();
+    }
+    
+    public static String buildHeaderQueryUrl(Context context) {
+        String queryUri = getHeaderQueryUri(context);
+        if (URLUtil.isNetworkUrl(queryUri)) {
+            return queryUri;
+        }
+        Uri base = Uri.parse(getHeaderBaseUrl(context));
+        Uri u = Uri.withAppendedPath(base, queryUri);
+        return u.toString();
+    }
+
+    public static String buildHeaderRootUrl(Context context) {
+        String rootUri = getHeaderRootUri(context);
+        if (TextUtils.isEmpty(rootUri)) {
+            return getHeaderBaseUrl(context);
+        }
+        if (URLUtil.isNetworkUrl(rootUri)) {
+            return rootUri;
+        }
+        Uri base = Uri.parse(getHeaderBaseUrl(context));
+        Uri u = Uri.withAppendedPath(base, rootUri);
+        return u.toString();
+    }
+
+    public static String buildBuildsQueryUrl(Context context, boolean secondary) {
+        String queryUri = getBuildsQueryUri(context);
+        if (URLUtil.isNetworkUrl(queryUri)) {
+            return queryUri;
+        }
+        Uri base = Uri.parse(secondary ? getBuildsSecondaryBaseUrl(context) : getBuildsBaseUrl(context));
+        Uri u = Uri.withAppendedPath(base, queryUri);
+        return u.toString();
+    }
+
+    public static String buildBuildsRootUrl(Context context, boolean secondary) {
+        String rootUri = getBuildsRootUri(context);
+        if (TextUtils.isEmpty(rootUri)) {
+            return secondary ? getBuildsSecondaryBaseUrl(context) : getBuildsBaseUrl(context);
+        }
+        if (URLUtil.isNetworkUrl(rootUri)) {
+            return rootUri;
+        }
+        Uri base = Uri.parse(secondary ? getBuildsSecondaryBaseUrl(context) : getBuildsBaseUrl(context));
+        Uri u = Uri.withAppendedPath(base, rootUri);
+        return u.toString();
+    }
+
+    public static String buildBuildsDeltasRootUrl(Context context) {
+        String rootUri = getBuildsDeltaRootUri(context);
+        if (TextUtils.isEmpty(rootUri)) {
+            return getBuildsDeltaBaseUrl(context);
+        }
+        if (URLUtil.isNetworkUrl(rootUri)) {
+            return rootUri;
+        }
+        Uri base = Uri.parse(getBuildsDeltaBaseUrl(context));
+        Uri u = Uri.withAppendedPath(base, rootUri);
+        return u.toString();
+    }
+}
diff --git a/core/org/omnirom/omnilibcore/utils/OmniUtils.java b/core/org/omnirom/omnilibcore/utils/OmniUtils.java
new file mode 100644
index 0000000..bac06cc
--- /dev/null
+++ b/core/org/omnirom/omnilibcore/utils/OmniUtils.java
@@ -0,0 +1,179 @@
+/*
+* Copyright (C) 2017-2021 The OmniROM Project
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.omnirom.omnilibcore.utils;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.UserHandle;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.RectF;
+import android.hardware.input.InputManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.Vibrator;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.InputDevice;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.IWindowManager;
+import android.view.WindowManagerGlobal;
+
+public class OmniUtils {
+
+    public static final String OMNILIB_PACKAGE_NAME = "org.omnirom.omnilib";
+
+    public static final String ACTION_DISMISS_KEYGUARD = OMNILIB_PACKAGE_NAME +".ACTION_DISMISS_KEYGUARD";
+
+    public static final String DISMISS_KEYGUARD_EXTRA_INTENT = "launch";
+
+    public static void launchKeyguardDismissIntent(Context context, UserHandle user, Intent launchIntent) {
+        Intent keyguardIntent = new Intent(ACTION_DISMISS_KEYGUARD);
+        keyguardIntent.setPackage(OMNILIB_PACKAGE_NAME);
+        keyguardIntent.putExtra(DISMISS_KEYGUARD_EXTRA_INTENT, launchIntent);
+        context.sendBroadcastAsUser(keyguardIntent, user);
+    }
+
+    public static void sendKeycode(int keycode) {
+        sendKeycode(keycode, false);
+    }
+
+    public static void sendKeycode(int keycode, boolean longpress) {
+        long when = SystemClock.uptimeMillis();
+        final KeyEvent evDown = new KeyEvent(when, when, KeyEvent.ACTION_DOWN, keycode, 0,
+                0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
+                KeyEvent.FLAG_FROM_SYSTEM,
+                InputDevice.SOURCE_KEYBOARD);
+        final KeyEvent evUp = KeyEvent.changeAction(evDown, KeyEvent.ACTION_UP);
+
+        final Handler handler = new Handler(Looper.getMainLooper());
+        handler.post(new Runnable() {
+            @Override
+            public void run() {
+                InputManager.getInstance().injectInputEvent(evDown,
+                        InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
+            }
+        });
+        handler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                InputManager.getInstance().injectInputEvent(evUp,
+                        InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
+            }
+        }, longpress ? 750 : 20);
+    }
+
+    public static void goToSleep(Context context) {
+        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+        if(pm != null) {
+            pm.goToSleep(SystemClock.uptimeMillis());
+        }
+    }
+
+    /* e.g.
+        <integer-array name="config_defaultNotificationVibePattern">
+        <item>0</item>
+        <item>350</item>
+        <item>250</item>
+        <item>350</item>
+        </integer-array>
+    */
+    public static void vibrateResourcePattern(Context context, int resId) {
+        if (DeviceUtils.deviceSupportsVibrator(context)) {
+            int[] pattern = context.getResources().getIntArray(resId);
+            if (pattern == null) {
+                return;
+            }
+            long[] out = new long[pattern.length];
+            for (int i=0; i<pattern.length; i++) {
+                out[i] = pattern[i];
+            }
+            ((Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE)).vibrate(out, -1);
+        }
+    }
+
+    public static void vibratePattern(Context context, long[] pattern) {
+        if (DeviceUtils.deviceSupportsVibrator(context)) {
+            ((Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE)).vibrate(pattern, -1);
+        }
+    }
+
+    public static Bitmap scaleCenterInside(final Bitmap source, final int newWidth, final int newHeight) {
+        int sourceWidth = source.getWidth();
+        int sourceHeight = source.getHeight();
+
+        float widthRatio = (float) newWidth / sourceWidth;
+        float heightRatio = (float) newHeight / sourceHeight;
+        float ratio = Math.max(widthRatio, heightRatio);
+        float scaledWidth = sourceWidth * ratio;
+        float scaledHeight = sourceHeight * ratio;
+    
+        //Bitmap scaled = Bitmap.createScaledBitmap(source, (int)scaledWidth, (int)scaledHeight, true);
+
+        RectF targetRect = null;
+        if (newWidth > newHeight) {
+            float inset = (scaledHeight - newHeight) / 2;
+            targetRect = new RectF(0, -inset, newWidth, newHeight + inset);
+        } else {
+            float inset = (scaledWidth - newWidth) / 2;
+            targetRect = new RectF(-inset, 0, newWidth + inset, newHeight);
+        }
+        Bitmap dest = Bitmap.createBitmap(newWidth, newHeight, source.getConfig());
+        Canvas canvas = new Canvas(dest);
+        canvas.drawBitmap(source, null, targetRect, null);
+        return dest;
+    }
+
+    public static int getQSColumnsCount(Context context, int resourceCount) {
+        final int QS_COLUMNS_MIN = 2;
+        final Resources res = context.getResources();
+        int value = QS_COLUMNS_MIN;
+        if (res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
+            value = Settings.System.getIntForUser(
+                    context.getContentResolver(), "qs_layout_columns",
+                    resourceCount, UserHandle.USER_CURRENT);
+        } else {
+            value = Settings.System.getIntForUser(
+                    context.getContentResolver(), "qs_layout_columns_landscape",
+                    resourceCount, UserHandle.USER_CURRENT);
+        }
+        return Math.max(QS_COLUMNS_MIN, value);
+    }
+
+    public static int getQuickQSColumnsCount(Context context, int resourceCount) {
+        return getQSColumnsCount(context, resourceCount);
+    }
+
+    public static boolean getQSTileLabelHide(Context context) {
+        return Settings.System.getIntForUser(context.getContentResolver(),
+                Settings.System.OMNI_QS_TILE_LABEL_HIDE,
+                0, UserHandle.USER_CURRENT) != 0;
+    }
+
+    public static boolean getQSTileVerticalLayout(Context context, int defaultValue) {
+        return Settings.System.getIntForUser(context.getContentResolver(),
+                Settings.System.OMNI_QS_TILE_VERTICAL_LAYOUT,
+                defaultValue, UserHandle.USER_CURRENT) != 0;
+    }
+}
diff --git a/core/org/omnirom/omnilibcore/utils/OmniVibe.java b/core/org/omnirom/omnilibcore/utils/OmniVibe.java
new file mode 100644
index 0000000..387e933
--- /dev/null
+++ b/core/org/omnirom/omnilibcore/utils/OmniVibe.java
@@ -0,0 +1,111 @@
+/*
+ *  Copyright (C) 2021 The OmniROM Project
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package org.omnirom.omnilibcore.utils;
+
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.os.UserHandle;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.provider.Settings;
+import android.provider.Settings.Global;
+import android.view.HapticFeedbackConstants;
+
+public class OmniVibe{
+
+
+    // Vibrator pattern for haptic feedback of a long press.
+    private static long[] mLongPressVibePattern;
+
+    // Vibrator pattern for a short vibration when tapping on a day/month/year date of a Calendar.
+    private static long[] mCalendarDateVibePattern;
+
+    // Vibrator pattern for haptic feedback during boot when safe mode is enabled.
+    private static long[] mSafeModeEnabledVibePattern;
+
+    public static void OmniVibe(){
+    }
+
+    public static boolean performHapticFeedbackLw(int effectId, boolean always, Context mContext) {
+        final boolean hapticsDisabled = Settings.System.getIntForUser(mContext.getContentResolver(),
+                Settings.System.HAPTIC_FEEDBACK_ENABLED, 0, UserHandle.USER_CURRENT) == 0;
+        if (hapticsDisabled && !always) {
+            return false;
+        }
+
+        VibrationEffect effect = getVibrationEffect(effectId);
+        if (effect == null) {
+            return false;
+        }
+
+        Vibrator mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
+        if (mVibrator.hasVibrator()){
+            mVibrator.vibrate(VibrationEffect.createOneShot(50,
+                        VibrationEffect.DEFAULT_AMPLITUDE));
+        }
+        return true;
+    }
+
+    private static VibrationEffect getVibrationEffect(int effectId) {
+        long[] pattern;
+        switch (effectId) {
+            case HapticFeedbackConstants.CLOCK_TICK:
+            case HapticFeedbackConstants.CONTEXT_CLICK:
+                return VibrationEffect.get(VibrationEffect.EFFECT_TICK);
+            case HapticFeedbackConstants.KEYBOARD_RELEASE:
+            case HapticFeedbackConstants.TEXT_HANDLE_MOVE:
+            case HapticFeedbackConstants.VIRTUAL_KEY_RELEASE:
+            case HapticFeedbackConstants.ENTRY_BUMP:
+            case HapticFeedbackConstants.DRAG_CROSSING:
+            case HapticFeedbackConstants.GESTURE_END:
+                return VibrationEffect.get(VibrationEffect.EFFECT_TICK, false);
+            case HapticFeedbackConstants.KEYBOARD_TAP: // == KEYBOARD_PRESS
+            case HapticFeedbackConstants.VIRTUAL_KEY:
+            case HapticFeedbackConstants.EDGE_RELEASE:
+            case HapticFeedbackConstants.CONFIRM:
+            case HapticFeedbackConstants.GESTURE_START:
+                return VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+            case HapticFeedbackConstants.LONG_PRESS:
+            case HapticFeedbackConstants.EDGE_SQUEEZE:
+                return VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK);
+            case HapticFeedbackConstants.REJECT:
+                return VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
+
+            case HapticFeedbackConstants.CALENDAR_DATE:
+                pattern = mCalendarDateVibePattern;
+                break;
+            case HapticFeedbackConstants.SAFE_MODE_ENABLED:
+                pattern = mSafeModeEnabledVibePattern;
+                break;
+
+            default:
+                return null;
+        }
+        if (pattern.length == 0) {
+            // No vibration
+            return null;
+        } else if (pattern.length == 1) {
+            // One-shot vibration
+            return VibrationEffect.createOneShot(pattern[0], VibrationEffect.DEFAULT_AMPLITUDE);
+        } else {
+            // Pattern vibration
+            return VibrationEffect.createWaveform(pattern, -1);
+        }
+    }
+}
diff --git a/core/org/omnirom/omnilibcore/utils/PackageUtils.java b/core/org/omnirom/omnilibcore/utils/PackageUtils.java
new file mode 100644
index 0000000..639dc1d
--- /dev/null
+++ b/core/org/omnirom/omnilibcore/utils/PackageUtils.java
@@ -0,0 +1,46 @@
+/*
+* Copyright (C) 2014-2021 The OmniROM 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 org.omnirom.omnilibcore.utils;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+
+public class PackageUtils {
+
+    public static boolean isAppInstalled(Context context, String appUri) {
+        try {
+            PackageManager pm = context.getPackageManager();
+            pm.getPackageInfo(appUri, PackageManager.GET_ACTIVITIES);
+            return true;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    public static boolean isAvailableApp(String packageName, Context context) {
+        Context mContext = context;
+        final PackageManager pm = mContext.getPackageManager();
+        try {
+            pm.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES);
+            int enabled = pm.getApplicationEnabledSetting(packageName);
+            return enabled != PackageManager.COMPONENT_ENABLED_STATE_DISABLED &&
+                enabled != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
+        } catch (NameNotFoundException e) {
+            return false;
+        }
+    }
+}
diff --git a/core/org/omnirom/omnilibcore/utils/SystemKeyEventHandler.java b/core/org/omnirom/omnilibcore/utils/SystemKeyEventHandler.java
new file mode 100644
index 0000000..71c2a2e
--- /dev/null
+++ b/core/org/omnirom/omnilibcore/utils/SystemKeyEventHandler.java
@@ -0,0 +1,119 @@
+/*
+ *  Copyright (C) 2021 The OmniROM Project
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package org.omnirom.omnilibcore.utils;
+
+import android.os.Handler;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.ViewConfiguration;
+
+public class SystemKeyEventHandler {
+    private static final String TAG = "SystemKeyEventHandler";
+    private static final boolean DEBUG = false;
+    private boolean mDoubleTapPending;
+    private boolean mKeyPressed;
+    private boolean mKeyConsumed;
+    private Runnable mPressedAction;
+    private Runnable mDoubleTapAction;
+    private Runnable mLongPressAction;
+
+    private final Runnable mDoubleTapTimeoutRunnable = new Runnable() {
+        @Override
+        public void run() {
+            if (mDoubleTapPending) {
+                mDoubleTapPending = false;
+                if (mPressedAction != null) {
+                    mPressedAction.run();
+                }
+            }
+        }
+    };
+
+    public SystemKeyEventHandler(Runnable pressedAction, Runnable doubleTapAction,
+            Runnable longPressAction) {
+        mPressedAction = pressedAction;
+        mDoubleTapAction = doubleTapAction;
+        mLongPressAction = longPressAction;
+    }
+
+    public int handleKeyEvent(Handler handler, KeyEvent event) {
+        final int repeatCount = event.getRepeatCount();
+        final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
+        final boolean canceled = event.isCanceled();
+
+        if (DEBUG) {
+            Log.d(TAG, "handleKeyEvent " + event + " down = " + down + " repeatCount = " + repeatCount);
+        }
+        // If we have released the home key, and didn't do anything else
+        // while it was pressed, then it is time to go home!
+        if (!down) {
+            mKeyPressed = false;
+            if (mKeyConsumed) {
+                mKeyConsumed = false;
+                return -1;
+            }
+
+            if (canceled) {
+                return -1;
+            }
+
+            // Delay handling home if a double-tap is possible.
+            if (mDoubleTapAction != null) {
+                handler.removeCallbacks(mDoubleTapTimeoutRunnable); // just in case
+                mDoubleTapPending = true;
+                handler.postDelayed(mDoubleTapTimeoutRunnable,
+                        ViewConfiguration.getDoubleTapTimeout());
+                return -1;
+            }
+
+            if (mPressedAction != null) {
+                // Post to main thread to avoid blocking input pipeline.
+                handler.post(() -> {
+                    mPressedAction.run();
+                });
+            }
+            return -1;
+        }
+
+
+        if (repeatCount == 0) {
+            mKeyPressed = true;
+            if (mDoubleTapPending) {
+                mDoubleTapPending = false;
+                handler.removeCallbacks(mDoubleTapTimeoutRunnable);
+                handleDoubleTapOnKey();
+            }
+        } else if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {
+            if (mLongPressAction != null) {
+                // Post to main thread to avoid blocking input pipeline.
+                handler.post(() -> {
+                    mLongPressAction.run();
+                });
+            }
+        }
+        return -1;
+    }
+
+    private void handleDoubleTapOnKey() {
+        mKeyConsumed = true;
+        if (mDoubleTapAction != null) {
+            mDoubleTapAction.run();
+        }
+    }
+}
diff --git a/core/org/omnirom/omnilibcore/utils/TaskUtils.java b/core/org/omnirom/omnilibcore/utils/TaskUtils.java
new file mode 100644
index 0000000..aa57b44
--- /dev/null
+++ b/core/org/omnirom/omnilibcore/utils/TaskUtils.java
@@ -0,0 +1,102 @@
+/*
+* Copyright (C) 2014-2022 The OmniROM 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 org.omnirom.omnilibcore.utils;
+
+import android.app.Activity;
+import android.app.ActivityOptions;
+import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
+import android.content.Context;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+
+import org.omnirom.omnilib.R;
+
+import java.util.List;
+
+public class TaskUtils {
+
+    public static void toggleLastApp(Context context, int userId) {
+        final ActivityInfo homeInfo = new Intent(Intent.ACTION_MAIN)
+                .addCategory(Intent.CATEGORY_HOME).resolveActivityInfo(context.getPackageManager(), 0);
+        final ActivityManager am = (ActivityManager) context
+                .getSystemService(Activity.ACTIVITY_SERVICE);
+        final List<ActivityManager.RecentTaskInfo> tasks = am.getRecentTasks(8,
+                ActivityManager.RECENT_IGNORE_UNAVAILABLE |
+                ActivityManager.RECENT_WITH_EXCLUDED);
+
+        int lastAppId = 0;
+        for (int i = 1; i < tasks.size(); i++) {
+            final ActivityManager.RecentTaskInfo info = tasks.get(i);
+            boolean isExcluded = (info.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
+                    == Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
+            if (isExcluded) {
+                continue;
+            }
+            if (isCurrentHomeActivity(info.baseIntent.getComponent(), homeInfo)) {
+                continue;
+            }
+            lastAppId = info.persistentId;
+            break;
+        }
+        if (lastAppId > 0) {
+            ActivityOptions options = ActivityOptions.makeCustomAnimation(context,
+                    R.anim.last_app_in, R.anim.last_app_out);
+            try {
+                ActivityManagerNative.getDefault().startActivityFromRecents(
+                        lastAppId,  options.toBundle());
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    private static boolean isCurrentHomeActivity(ComponentName component,
+            ActivityInfo homeInfo) {
+        return homeInfo != null
+                && homeInfo.packageName.equals(component.getPackageName())
+                && homeInfo.name.equals(component.getClassName());
+    }
+
+    private static int getRunningTask(Context context) {
+        final ActivityManager am = (ActivityManager) context
+                .getSystemService(Context.ACTIVITY_SERVICE);
+
+        List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1);
+        if (tasks != null && !tasks.isEmpty()) {
+            return tasks.get(0).id;
+        }
+        return -1;
+    }
+
+    public static ActivityInfo getRunningActivityInfo(Context context) {
+        final ActivityManager am = (ActivityManager) context
+                .getSystemService(Context.ACTIVITY_SERVICE);
+        final PackageManager pm = context.getPackageManager();
+
+        List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1);
+        if (tasks != null && !tasks.isEmpty()) {
+            ActivityManager.RunningTaskInfo top = tasks.get(0);
+            try {
+                return pm.getActivityInfo(top.topActivity, 0);
+            } catch (PackageManager.NameNotFoundException e) {
+            }
+        }
+        return null;
+    }
+}