diff --git a/.eslintrc.js b/.eslintrc.js
index 2dbffb7..f78a4ae 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1,6 +1,6 @@
module.exports = {
root: true,
- extends: ['@react-native-community', 'prettier'],
+ extends: ['@react-native', 'prettier'],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
overrides: [
diff --git a/.gitignore b/.gitignore
index b99afd1..9d03b55 100644
--- a/.gitignore
+++ b/.gitignore
@@ -65,3 +65,6 @@ coverage/
# vscode
.vscode/
+
+# testing
+/coverage
diff --git a/.node-version b/.node-version
deleted file mode 100644
index 3c03207..0000000
--- a/.node-version
+++ /dev/null
@@ -1 +0,0 @@
-18
diff --git a/Gemfile b/Gemfile
index 1142b1b..8d72c37 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,6 +1,9 @@
source 'https://rubygems.org'
# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
-ruby '>= 2.6.10'
+ruby ">= 2.6.10"
-gem 'cocoapods', '>= 1.11.3'
+# Cocoapods 1.15 introduced a bug which break the build. We will remove the upper
+# bound in the template on Cocoapods with next React Native release.
+gem 'cocoapods', '>= 1.13', '< 1.15'
+gem 'activesupport', '>= 6.1.7.5', '< 7.1.0'
diff --git a/README.md b/README.md
index 405fa5b..4e69648 100644
--- a/README.md
+++ b/README.md
@@ -24,7 +24,7 @@ cd android && ./gradlew build
Install and launch app in an android device/emulator:
```shell
-npm run android
+npm run start:all
```
## Build
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 22f3e40..9851d3e 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -1,8 +1,7 @@
apply plugin: "com.android.application"
+apply plugin: "org.jetbrains.kotlin.android"
apply plugin: "com.facebook.react"
-import com.android.build.OutputFile
-
/**
* This is the configuration block to customize your React Native Android app.
* By default you don't need to apply any configuration, just uncomment the lines you need.
@@ -13,15 +12,17 @@ react {
// root = file("../")
// The folder where the react-native NPM package is. Default is ../node_modules/react-native
// reactNativeDir = file("../node_modules/react-native")
- // The folder where the react-native Codegen package is. Default is ../node_modules/react-native-codegen
- // codegenDir = file("../node_modules/react-native-codegen")
+ // The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen
+ // codegenDir = file("../node_modules/@react-native/codegen")
// The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js
// cliFile = file("../node_modules/react-native/cli.js")
+
/* Variants */
// The list of variants to that are debuggable. For those we're going to
// skip the bundling of the JS bundle and the assets. By default is just 'debug'.
// If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.
// debuggableVariants = ["liteDebug", "prodDebug"]
+
/* Bundling */
// A list containing the node command and its flags. Default is just 'node'.
// nodeExecutableAndArgs = ["node"]
@@ -41,6 +42,7 @@ react {
// A list of extra flags to pass to the 'bundle' commands.
// See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle
// extraPackagerArgs = []
+
/* Hermes Commands */
// The hermes compiler command to run. By default it is 'hermesc'
// hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc"
@@ -49,14 +51,6 @@ react {
// hermesFlags = ["-O", "-output-source-map"]
}
-/**
- * Set this to true to create four separate APKs instead of one,
- * one for each native architecture. This is useful if you don't
- * use App Bundles (https://developer.android.com/guide/app-bundle/)
- * and want to have separate APKs to upload to the Play Store.
- */
-def enableSeparateBuildPerCPUArchitecture = false
-
/**
* Set this to true to Run Proguard on Release builds to minify the Java bytecode.
*/
@@ -75,49 +69,34 @@ def enableProguardInReleaseBuilds = true
*/
def jscFlavor = 'org.webkit:android-jsc:+'
-/**
- * Private function to get the list of Native Architectures you want to build.
- * This reads the value from reactNativeArchitectures in your gradle.properties
- * file and works together with the --active-arch-only flag of react-native run-android.
- */
-def reactNativeArchitectures() {
- def value = project.getProperties().get("reactNativeArchitectures")
- return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
-}
-
android {
+ kotlinOptions {
+ jvmTarget = '17'
+ }
ndkVersion rootProject.ext.ndkVersion
+ buildToolsVersion rootProject.ext.buildToolsVersion
+ compileSdk rootProject.ext.compileSdkVersion
- compileSdkVersion rootProject.ext.compileSdkVersion
namespace "com.razinj.context_launcher"
-
defaultConfig {
applicationId "com.razinj.context_launcher"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
- versionCode 16
- versionName "2.0.2"
+ versionCode 17
+ versionName "2.0.3"
archivesBaseName = "context-launcher-v$versionName-$versionCode"
}
- splits {
- abi {
- reset()
- enable enableSeparateBuildPerCPUArchitecture
- universalApk false // If true, also generate a universal APK
- include(*reactNativeArchitectures())
- }
- }
signingConfigs {
release {
if (project.hasProperty('CONTEXT_LAUNCHER_UPLOAD_STORE_FILE')) {
- project.logger.lifecycle('Release upload keystore file found.')
+ project.logger.lifecycle('[!] Release upload keystore file found.')
storeFile file(CONTEXT_LAUNCHER_UPLOAD_STORE_FILE)
storePassword CONTEXT_LAUNCHER_UPLOAD_STORE_PASSWORD
keyAlias CONTEXT_LAUNCHER_UPLOAD_KEY_ALIAS
keyPassword CONTEXT_LAUNCHER_UPLOAD_KEY_PASSWORD
} else {
- project.logger.lifecycle('Release upload keystore file not found.')
+ project.logger.lifecycle('[X] Release upload keystore file not found.')
}
}
debug {
@@ -139,33 +118,13 @@ android {
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
-
- // applicationVariants are e.g. debug, release
- applicationVariants.all { variant ->
- variant.outputs.each { output ->
- // For each separate APK per architecture, set a unique version code as described here:
- // https://developer.android.com/studio/build/configure-apk-splits.html
- // Example: versionCode 1 will generate 1001 for armeabi-v7a, 1002 for x86, etc.
- def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
- def abi = output.getFilter(OutputFile.ABI)
- if (abi != null) { // null for the universal-debug, universal-release variants
- output.versionCodeOverride =
- defaultConfig.versionCode * 1000 + versionCodes.get(abi)
- }
-
- }
- }
}
dependencies {
// The version of react-native is set by the React Native Gradle Plugin
implementation("com.facebook.react:react-android")
- implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
- debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}")
- debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
- exclude group: 'com.squareup.okhttp3', module: 'okhttp'
- }
- debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}")
+ implementation 'androidx.core:core-ktx:1.13.1'
+
if (hermesEnabled.toBoolean()) {
implementation("com.facebook.react:hermes-android")
} else {
@@ -179,4 +138,26 @@ project.ext.vectoricons = [
iconFontNames: ['MaterialCommunityIcons.ttf']
]
-apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
+apply from: file("../../node_modules/react-native-vector-icons/fonts.gradle")
+
+/*
+Below code is a fix for this issue:
+
+* What went wrong:
+A problem was found with the configuration of task ':app:lintAnalyzeDebug' (type 'AndroidLintAnalysisTask').
+ - Gradle detected a problem with the following location: '/Users/nizar/dev/context_launcher/android/app/build/intermediates/ReactNativeVectorIcons'.
+
+ Reason: Task ':app:lintAnalyzeDebug' uses this output of task ':app:copyReactNativeVectorIconFonts' without declaring an explicit or implicit dependency. This can lead to incorrect results being produced, depending on what order the tasks are executed.
+
+ Possible solutions:
+ 1. Declare task ':app:copyReactNativeVectorIconFonts' as an input of ':app:lintAnalyzeDebug'.
+ 2. Declare an explicit dependency on ':app:copyReactNativeVectorIconFonts' from ':app:lintAnalyzeDebug' using Task#dependsOn.
+ 3. Declare an explicit dependency on ':app:copyReactNativeVectorIconFonts' from ':app:lintAnalyzeDebug' using Task#mustRunAfter.
+
+Inspired from: https://github.com/oblador/react-native-vector-icons/issues/1584
+*/
+tasks.configureEach {
+ if (name == 'lintAnalyzeDebug') {
+ dependsOn('copyReactNativeVectorIconFonts')
+ }
+}
diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml
index 4b185bc..eb98c01 100644
--- a/android/app/src/debug/AndroidManifest.xml
+++ b/android/app/src/debug/AndroidManifest.xml
@@ -2,12 +2,8 @@
-
-
-
-
+ tools:ignore="GoogleAppIndexingWarning"/>
diff --git a/android/app/src/debug/java/com/razinj/context_launcher/ReactNativeFlipper.java b/android/app/src/debug/java/com/razinj/context_launcher/ReactNativeFlipper.java
deleted file mode 100644
index 0b506cc..0000000
--- a/android/app/src/debug/java/com/razinj/context_launcher/ReactNativeFlipper.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/**
- * Copyright (c) Meta Platforms, Inc. and affiliates.
- *
- *
This source code is licensed under the MIT license found in the LICENSE file in the root
- * directory of this source tree.
- */
-package com.razinj.context_launcher;
-
-import android.content.Context;
-
-import com.facebook.flipper.android.AndroidFlipperClient;
-import com.facebook.flipper.android.utils.FlipperUtils;
-import com.facebook.flipper.core.FlipperClient;
-import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin;
-import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin;
-import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin;
-import com.facebook.flipper.plugins.inspector.DescriptorMapping;
-import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
-import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;
-import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;
-import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
-import com.facebook.react.ReactInstanceEventListener;
-import com.facebook.react.ReactInstanceManager;
-import com.facebook.react.bridge.ReactContext;
-import com.facebook.react.modules.network.NetworkingModule;
-
-/**
- * Class responsible of loading Flipper inside your React Native application. This is the debug
- * flavor of it. Here you can add your own plugins and customize the Flipper setup.
- */
-public class ReactNativeFlipper {
-
- public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
- if (FlipperUtils.shouldEnableFlipper(context)) {
- final FlipperClient client = AndroidFlipperClient.getInstance(context);
-
- client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()));
- client.addPlugin(new DatabasesFlipperPlugin(context));
- client.addPlugin(new SharedPreferencesFlipperPlugin(context));
- client.addPlugin(CrashReporterPlugin.getInstance());
-
- NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
- NetworkingModule.setCustomClientBuilder(builder -> builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin)));
- client.addPlugin(networkFlipperPlugin);
- client.start();
-
- // Fresco Plugin needs to ensure that ImagePipelineFactory is initialized
- // Hence we run if after all native modules have been initialized
- ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
- if (reactContext == null) {
- reactInstanceManager.addReactInstanceEventListener(
- new ReactInstanceEventListener() {
- @Override
- public void onReactContextInitialized(ReactContext reactContext) {
- reactInstanceManager.removeReactInstanceEventListener(this);
- reactContext.runOnNativeModulesQueueThread(() -> client.addPlugin(new FrescoFlipperPlugin()));
- }
- });
- } else {
- client.addPlugin(new FrescoFlipperPlugin());
- }
- }
- }
-}
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 884b413..a2a81b6 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -8,6 +8,7 @@
tools:ignore="QueryAllPackagesPermission" />
+
-
+
+
+
diff --git a/android/app/src/main/java/com/razinj/context_launcher/AppDetails.java b/android/app/src/main/java/com/razinj/context_launcher/AppDetails.java
deleted file mode 100644
index 66ce9f8..0000000
--- a/android/app/src/main/java/com/razinj/context_launcher/AppDetails.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.razinj.context_launcher;
-
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-public class AppDetails {
- String packageName;
- String name;
- String icon;
-
- AppDetails(String packageName, String name, String icon) {
- this.packageName = packageName;
- this.name = name;
- this.icon = icon;
- }
-
- @NonNull
- public String toString() {
- try {
- JSONObject appDetails = new JSONObject();
-
- appDetails.put("packageName", this.packageName);
- appDetails.put("name", this.name);
- appDetails.put("icon", this.icon);
-
- return appDetails.toString();
- } catch (JSONException e) {
- Log.e("AppsModule", "Couldn't construct app details JSON: " + e.getMessage());
- throw new RuntimeException(e);
- }
- }
-}
diff --git a/android/app/src/main/java/com/razinj/context_launcher/AppDetails.kt b/android/app/src/main/java/com/razinj/context_launcher/AppDetails.kt
new file mode 100644
index 0000000..fa773cf
--- /dev/null
+++ b/android/app/src/main/java/com/razinj/context_launcher/AppDetails.kt
@@ -0,0 +1,26 @@
+package com.razinj.context_launcher
+
+import android.util.Log
+import org.json.JSONException
+import org.json.JSONObject
+
+class AppDetails internal constructor(
+ private var packageName: String,
+ private var name: String,
+ private var icon: String?,
+) {
+ override fun toString(): String {
+ try {
+ val appDetails = JSONObject()
+
+ appDetails.put("packageName", this.packageName)
+ appDetails.put("name", this.name)
+ appDetails.put("icon", this.icon)
+
+ return appDetails.toString()
+ } catch (e: JSONException) {
+ Log.e("AppsModule", "Couldn't construct app details JSON: " + e.message)
+ throw RuntimeException(e)
+ }
+ }
+}
diff --git a/android/app/src/main/java/com/razinj/context_launcher/AppProvider.java b/android/app/src/main/java/com/razinj/context_launcher/AppProvider.java
deleted file mode 100644
index d492cf8..0000000
--- a/android/app/src/main/java/com/razinj/context_launcher/AppProvider.java
+++ /dev/null
@@ -1,110 +0,0 @@
-package com.razinj.context_launcher;
-
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.LauncherApps;
-import android.os.Build;
-import android.os.IBinder;
-import android.os.Process;
-import android.os.UserHandle;
-
-import androidx.annotation.Nullable;
-import androidx.core.app.NotificationCompat;
-
-public class AppProvider extends Service {
- private PackageChangeReceiver packageChangeReceiver;
-
- @Override
- public void onCreate() {
- final LauncherApps launcherApps = (LauncherApps) getSystemService(Context.LAUNCHER_APPS_SERVICE);
- assert launcherApps != null;
-
- launcherApps.registerCallback(new LauncherAppsCallback() {
- @Override
- public void onPackageAdded(String packageName, UserHandle user) {
- if (user.equals(Process.myUserHandle())) return;
-
- PackageChangeReceiver.handleEvent(AppProvider.this, Intent.ACTION_PACKAGE_ADDED, packageName, false);
- }
-
- @Override
- public void onPackageChanged(String packageName, UserHandle user) {
- if (user.equals(Process.myUserHandle())) return;
-
- PackageChangeReceiver.handleEvent(AppProvider.this, Intent.ACTION_PACKAGE_CHANGED, packageName, true);
- }
-
- @Override
- public void onPackageRemoved(String packageName, UserHandle user) {
- if (user.equals(Process.myUserHandle())) return;
-
- PackageChangeReceiver.handleEvent(AppProvider.this, Intent.ACTION_PACKAGE_REMOVED, packageName, false);
- }
- });
-
- this.packageChangeReceiver = new PackageChangeReceiver();
-
- IntentFilter appChangedIntentFilter = new IntentFilter();
- appChangedIntentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
- appChangedIntentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
- appChangedIntentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- appChangedIntentFilter.addDataScheme("package");
- appChangedIntentFilter.addDataScheme("file");
-
- registerReceiver(packageChangeReceiver, appChangedIntentFilter);
-
- super.onCreate();
-
- startForegroundCustom();
- }
-
- private void startForegroundCustom() {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
- startForeground(1, new Notification());
- return;
- }
-
- // Notification channel
- String channelId = BuildConfig.APPLICATION_ID;
- String channelName = "AppProvider Channel";
- NotificationChannel notificationChannel = new NotificationChannel(
- channelId,
- channelName,
- NotificationManager.IMPORTANCE_NONE
- );
- notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
-
- // Notification manager
- NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- assert notificationManager != null;
- notificationManager.createNotificationChannel(notificationChannel);
-
- // Notification builder
- NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, channelId);
- Notification notification = notificationBuilder
- .setPriority(NotificationManager.IMPORTANCE_NONE)
- .setCategory(Notification.CATEGORY_SERVICE)
- .setAutoCancel(false)
- .setOngoing(true)
- .setSilent(true)
- .build();
- startForeground(1, notification);
- }
-
- @Override
- public void onDestroy() {
- this.unregisterReceiver(packageChangeReceiver);
- super.onDestroy();
- }
-
- @Nullable
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
-}
diff --git a/android/app/src/main/java/com/razinj/context_launcher/AppProvider.kt b/android/app/src/main/java/com/razinj/context_launcher/AppProvider.kt
new file mode 100644
index 0000000..3d9a9a0
--- /dev/null
+++ b/android/app/src/main/java/com/razinj/context_launcher/AppProvider.kt
@@ -0,0 +1,121 @@
+package com.razinj.context_launcher
+
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.Service
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.LauncherApps
+import android.os.Build
+import android.os.IBinder
+import android.os.Process
+import android.os.UserHandle
+import androidx.core.app.NotificationCompat
+
+class AppProvider : Service() {
+ private var packageChangeReceiver: PackageChangeReceiver? = null
+
+ override fun onCreate() {
+ val launcherApps = (getSystemService(LAUNCHER_APPS_SERVICE) as LauncherApps)
+ launcherApps.registerCallback(
+ object : LauncherAppsCallback() {
+ override fun onPackageAdded(
+ packageName: String,
+ user: UserHandle,
+ ) {
+ if (user == Process.myUserHandle()) return
+
+ PackageChangeReceiver.handleEvent(
+ this@AppProvider,
+ Intent.ACTION_PACKAGE_ADDED,
+ packageName,
+ )
+ }
+
+ override fun onPackageChanged(
+ packageName: String,
+ user: UserHandle,
+ ) {
+ if (user == Process.myUserHandle()) return
+
+ PackageChangeReceiver.handleEvent(
+ this@AppProvider,
+ Intent.ACTION_PACKAGE_CHANGED,
+ packageName,
+ )
+ }
+
+ override fun onPackageRemoved(
+ packageName: String,
+ user: UserHandle,
+ ) {
+ if (user == Process.myUserHandle()) return
+
+ PackageChangeReceiver.handleEvent(
+ this@AppProvider,
+ Intent.ACTION_PACKAGE_REMOVED,
+ packageName,
+ )
+ }
+ },
+ )
+
+ this.packageChangeReceiver = PackageChangeReceiver()
+
+ val appChangedIntentFilter = IntentFilter()
+ appChangedIntentFilter.addAction(Intent.ACTION_PACKAGE_ADDED)
+ appChangedIntentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED)
+ appChangedIntentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED)
+ appChangedIntentFilter.addDataScheme("package")
+ appChangedIntentFilter.addDataScheme("file")
+
+ registerReceiver(packageChangeReceiver, appChangedIntentFilter)
+
+ super.onCreate()
+
+ startForegroundCustom()
+ }
+
+ private fun startForegroundCustom() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+ startForeground(1, Notification())
+ return
+ }
+
+ // Notification channel
+ val channelId = BuildConfig.APPLICATION_ID
+ val channelName = "AppProvider Channel"
+ val notificationChannel =
+ NotificationChannel(
+ channelId,
+ channelName,
+ NotificationManager.IMPORTANCE_NONE,
+ )
+ notificationChannel.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
+
+ // Notification manager
+ val notificationManager = (getSystemService(NOTIFICATION_SERVICE) as NotificationManager)
+ notificationManager.createNotificationChannel(notificationChannel)
+
+ // Notification builder
+ val notificationBuilder = NotificationCompat.Builder(this, channelId)
+ val notification =
+ notificationBuilder
+ .setPriority(NotificationManager.IMPORTANCE_NONE)
+ .setCategory(Notification.CATEGORY_SERVICE)
+ .setAutoCancel(false)
+ .setOngoing(true)
+ .setSilent(true)
+ .build()
+
+ startForeground(1, notification)
+ }
+
+ override fun onDestroy() {
+ this.unregisterReceiver(packageChangeReceiver)
+ super.onDestroy()
+ }
+
+ override fun onBind(intent: Intent): IBinder? = null
+}
diff --git a/android/app/src/main/java/com/razinj/context_launcher/AppsModule.java b/android/app/src/main/java/com/razinj/context_launcher/AppsModule.java
deleted file mode 100644
index 2ba174a..0000000
--- a/android/app/src/main/java/com/razinj/context_launcher/AppsModule.java
+++ /dev/null
@@ -1,171 +0,0 @@
-package com.razinj.context_launcher;
-
-import static com.razinj.context_launcher.Constants.PACKAGE_CHANGE_EVENT;
-import static com.razinj.context_launcher.Constants.PACKAGE_CHANGE_IS_REMOVED;
-import static com.razinj.context_launcher.Constants.PACKAGE_CHANGE_NAME;
-import static com.razinj.context_launcher.Constants.PACKAGE_UPDATE_ACTION;
-import static com.razinj.context_launcher.Constants.SHORT_NOT_AVAILABLE;
-import static com.razinj.context_launcher.Utils.getPackageInfo;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.provider.Settings;
-
-import androidx.annotation.NonNull;
-
-import com.facebook.react.bridge.Arguments;
-import com.facebook.react.bridge.Promise;
-import com.facebook.react.bridge.ReactApplicationContext;
-import com.facebook.react.bridge.ReactContextBaseJavaModule;
-import com.facebook.react.bridge.ReactMethod;
-import com.facebook.react.bridge.WritableMap;
-import com.facebook.react.modules.core.DeviceEventManagerModule;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.stream.Collectors;
-
-public class AppsModule extends ReactContextBaseJavaModule {
- private final ReactApplicationContext reactContext;
- private static BroadcastReceiver packageChangeBroadcastReceiver;
-
- AppsModule(ReactApplicationContext reactContext) {
- super(reactContext);
- this.reactContext = reactContext;
- initializePackageChangeBroadcastReceiver();
- }
-
- @NonNull
- @Override
- public String getName() {
- return "AppsModule";
- }
-
- @Override
- public void onCatalystInstanceDestroy() {
- reactContext.unregisterReceiver(packageChangeBroadcastReceiver);
- }
-
- private void initializePackageChangeBroadcastReceiver() {
- packageChangeBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (!reactContext.hasActiveReactInstance()) return;
-
- Bundle extras = intent.getExtras();
- WritableMap map = Arguments.createMap();
-
- map.putString(PACKAGE_CHANGE_NAME, extras.getString(PACKAGE_CHANGE_NAME));
- map.putBoolean(PACKAGE_CHANGE_IS_REMOVED, extras.getBoolean(PACKAGE_CHANGE_IS_REMOVED));
-
- reactContext.getJSModule((DeviceEventManagerModule.RCTDeviceEventEmitter.class)).emit(PACKAGE_CHANGE_EVENT, map);
- }
- };
-
- IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(PACKAGE_UPDATE_ACTION);
-
- reactContext.registerReceiver(packageChangeBroadcastReceiver, intentFilter);
- }
-
- @ReactMethod
- public void getApplications(Promise promise) {
- new Thread(() -> {
- PackageManager pm = reactContext.getPackageManager();
- List apps = new ArrayList<>();
- List packages;
-
- // Get installed packages
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- packages = pm.getInstalledPackages(PackageManager.PackageInfoFlags.of(0));
- } else {
- packages = pm.getInstalledPackages(0);
- }
-
- // Filter and map to AppDetails
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- apps = packages.stream().filter(packageInfo -> Objects.nonNull(pm.getLaunchIntentForPackage(packageInfo.packageName))).map(packageInfo -> new AppDetails(packageInfo.packageName, packageInfo.applicationInfo.loadLabel(pm).toString(), Utils.getEncodedIcon(pm, packageInfo.packageName))).collect(Collectors.toList());
- } else {
- for (PackageInfo packageInfo : packages) {
- if (Objects.isNull(pm.getLaunchIntentForPackage(packageInfo.packageName))) {
- continue;
- }
-
- apps.add(new AppDetails(packageInfo.packageName, packageInfo.applicationInfo.loadLabel(pm).toString(), Utils.getEncodedIcon(pm, packageInfo.packageName)));
- }
- }
-
- promise.resolve(apps.toString());
- }).start();
- }
-
- @ReactMethod
- private void launchApplication(String packageName) {
- Intent intent = reactContext.getPackageManager().getLaunchIntentForPackage(packageName);
-
- reactContext.startActivity(intent);
- }
-
- @ReactMethod
- public void showApplicationDetails(String packageName) {
- Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK).setData(Uri.fromParts("package", packageName, null));
-
- reactContext.startActivity(intent);
- }
-
- @ReactMethod
- public void requestApplicationUninstall(String packageName) {
- Intent intent = new Intent(Intent.ACTION_DELETE).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK).setData(Uri.fromParts("package", packageName, null));
-
- reactContext.startActivity(intent);
- }
-
- @ReactMethod
- public void addListener(String eventName) {
- // Required for NativeEventEmitter
- }
-
- @ReactMethod
- public void removeListeners(Integer count) {
- // Required for NativeEventEmitter
- }
-
- @Override
- public Map getConstants() {
- String appVersion;
- String buildNumber;
- String packageName;
-
- try {
- appVersion = getPackageInfo(reactContext).versionName;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- buildNumber = Long.toString(getPackageInfo(reactContext).getLongVersionCode());
- } else {
- buildNumber = Long.toString(getPackageInfo(reactContext).versionCode);
- }
- packageName = getReactApplicationContext().getPackageName();
- } catch (PackageManager.NameNotFoundException e) {
- appVersion = SHORT_NOT_AVAILABLE;
- buildNumber = SHORT_NOT_AVAILABLE;
- packageName = SHORT_NOT_AVAILABLE;
- }
-
- Map constants = new HashMap<>();
-
- constants.put("appVersion", appVersion);
- constants.put("buildNumber", buildNumber);
- constants.put("packageName", packageName);
-
- return constants;
- }
-}
diff --git a/android/app/src/main/java/com/razinj/context_launcher/AppsModule.kt b/android/app/src/main/java/com/razinj/context_launcher/AppsModule.kt
new file mode 100644
index 0000000..b064151
--- /dev/null
+++ b/android/app/src/main/java/com/razinj/context_launcher/AppsModule.kt
@@ -0,0 +1,177 @@
+package com.razinj.context_launcher
+
+import android.annotation.SuppressLint
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.PackageManager
+import android.net.Uri
+import android.os.Build
+import android.provider.Settings
+import com.facebook.react.bridge.Arguments
+import com.facebook.react.bridge.Promise
+import com.facebook.react.bridge.ReactApplicationContext
+import com.facebook.react.bridge.ReactContextBaseJavaModule
+import com.facebook.react.bridge.ReactMethod
+import com.facebook.react.modules.core.DeviceEventManagerModule
+
+class AppsModule internal constructor(
+ private val reactContext: ReactApplicationContext,
+) : ReactContextBaseJavaModule(reactContext) {
+ private var packageChangeBroadcastReceiver: BroadcastReceiver? = null
+
+ init {
+ initializePackageChangeBroadcastReceiver()
+ }
+
+ override fun getName(): String = "AppsModule"
+
+ override fun invalidate() {
+ reactContext.unregisterReceiver(packageChangeBroadcastReceiver)
+ }
+
+ private fun initializePackageChangeBroadcastReceiver() {
+ packageChangeBroadcastReceiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(
+ context: Context,
+ intent: Intent,
+ ) {
+ if (!reactContext.hasActiveReactInstance()) return
+
+ val extras = intent.extras ?: return
+ val map = Arguments.createMap()
+
+ map.putString(
+ Constants.PACKAGE_CHANGE_NAME,
+ extras.getString(Constants.PACKAGE_CHANGE_NAME),
+ )
+ map.putBoolean(
+ Constants.PACKAGE_CHANGE_IS_REMOVED,
+ extras.getBoolean(Constants.PACKAGE_CHANGE_IS_REMOVED),
+ )
+
+ reactContext
+ .getJSModule((DeviceEventManagerModule.RCTDeviceEventEmitter::class.java))
+ .emit(
+ Constants.PACKAGE_CHANGE_EVENT,
+ map,
+ )
+ }
+ }
+
+ val intentFilter = IntentFilter()
+ intentFilter.addAction(Constants.PACKAGE_UPDATE_ACTION)
+
+ @SuppressLint("UnspecifiedRegisterReceiverFlag") // Suppress lint warning in the else statement
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ reactContext.registerReceiver(
+ packageChangeBroadcastReceiver,
+ intentFilter,
+ Context.RECEIVER_EXPORTED,
+ )
+ } else {
+ reactContext.registerReceiver(packageChangeBroadcastReceiver, intentFilter)
+ }
+ }
+
+ @ReactMethod
+ fun getApplications(promise: Promise) {
+ Thread {
+ val pm = reactContext.packageManager
+ val apps = mutableListOf()
+
+ val packages =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ pm.getInstalledPackages(PackageManager.PackageInfoFlags.of(0))
+ } else {
+ pm.getInstalledPackages(0)
+ }
+
+ for (packageInfo in packages) {
+ // Only use packages that are launch-able
+ pm.getLaunchIntentForPackage(packageInfo.packageName) ?: continue
+
+ apps.add(
+ AppDetails(
+ packageInfo.packageName,
+ packageInfo.applicationInfo.loadLabel(pm).toString(),
+ Utils.getEncodedIcon(pm, packageInfo.packageName),
+ ),
+ )
+ }
+
+ promise.resolve(apps.toString())
+ }.start()
+ }
+
+ @ReactMethod
+ fun launchApplication(packageName: String) {
+ val intent = reactContext.packageManager.getLaunchIntentForPackage(packageName)
+
+ reactContext.startActivity(intent)
+ }
+
+ @ReactMethod
+ fun showApplicationDetails(packageName: String) {
+ val intent =
+ Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .setData(Uri.fromParts("package", packageName, null))
+
+ reactContext.startActivity(intent)
+ }
+
+ @ReactMethod
+ fun requestApplicationUninstall(packageName: String) {
+ val intent =
+ Intent(Intent.ACTION_DELETE)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .setData(Uri.fromParts("package", packageName, null))
+
+ reactContext.startActivity(intent)
+ }
+
+ @ReactMethod
+ fun addListener(eventName: String) {
+ // Required for NativeEventEmitter
+ // See: https://github.com/facebook/react-native/commit/114be1d2170bae2d29da749c07b45acf931e51e2
+ }
+
+ @ReactMethod
+ fun removeListeners(count: Int) {
+ // Required for NativeEventEmitter
+ // See: https://github.com/facebook/react-native/commit/114be1d2170bae2d29da749c07b45acf931e51e2
+ }
+
+ override fun getConstants(): MutableMap {
+ val constants: MutableMap = mutableMapOf()
+
+ try {
+ val packageInfo = Utils.getPackageInfo(reactContext)
+ val appVersion = packageInfo.versionName
+ val buildNumber =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ packageInfo.longVersionCode.toString()
+ } else {
+ packageInfo.versionCode.toString()
+ }
+ val packageName = reactApplicationContext.packageName
+
+ constants.apply {
+ put("appVersion", appVersion ?: Constants.SHORT_NOT_AVAILABLE)
+ put("buildNumber", buildNumber)
+ put("packageName", packageName ?: Constants.SHORT_NOT_AVAILABLE)
+ }
+ } catch (e: PackageManager.NameNotFoundException) {
+ constants.apply {
+ put("appVersion", Constants.SHORT_NOT_AVAILABLE)
+ put("buildNumber", Constants.SHORT_NOT_AVAILABLE)
+ put("packageName", Constants.SHORT_NOT_AVAILABLE)
+ }
+ }
+
+ return constants
+ }
+}
diff --git a/android/app/src/main/java/com/razinj/context_launcher/Constants.java b/android/app/src/main/java/com/razinj/context_launcher/Constants.java
deleted file mode 100644
index 36a3dea..0000000
--- a/android/app/src/main/java/com/razinj/context_launcher/Constants.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.razinj.context_launcher;
-
-public class Constants {
-
- private Constants() {
- super();
- }
-
- // Package change intent action
- public static final String PACKAGE_UPDATE_ACTION = "packageUpdateAction";
- // Package change event
- public static final String PACKAGE_CHANGE_EVENT = "packageChange";
- public static final String PACKAGE_CHANGE_NAME = "packageName";
- public static final String PACKAGE_CHANGE_IS_REMOVED = "isRemoved";
- // Misc
- public static final String SHORT_NOT_AVAILABLE = "N/A";
-}
diff --git a/android/app/src/main/java/com/razinj/context_launcher/Constants.kt b/android/app/src/main/java/com/razinj/context_launcher/Constants.kt
new file mode 100644
index 0000000..6ced04a
--- /dev/null
+++ b/android/app/src/main/java/com/razinj/context_launcher/Constants.kt
@@ -0,0 +1,14 @@
+package com.razinj.context_launcher
+
+object Constants {
+ // Package change intent action
+ const val PACKAGE_UPDATE_ACTION: String = "packageUpdateAction"
+
+ // Package change event
+ const val PACKAGE_CHANGE_EVENT: String = "packageChange"
+ const val PACKAGE_CHANGE_NAME: String = "packageName"
+ const val PACKAGE_CHANGE_IS_REMOVED: String = "isRemoved"
+
+ // Misc
+ const val SHORT_NOT_AVAILABLE: String = "N/A"
+}
diff --git a/android/app/src/main/java/com/razinj/context_launcher/CustomAppPackage.java b/android/app/src/main/java/com/razinj/context_launcher/CustomAppPackage.java
deleted file mode 100644
index 20d8e5f..0000000
--- a/android/app/src/main/java/com/razinj/context_launcher/CustomAppPackage.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package com.razinj.context_launcher;
-
-import androidx.annotation.NonNull;
-
-import com.facebook.react.ReactPackage;
-import com.facebook.react.bridge.NativeModule;
-import com.facebook.react.bridge.ReactApplicationContext;
-import com.facebook.react.uimanager.ViewManager;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-public class CustomAppPackage implements ReactPackage {
-
- @NonNull
- @Override
- public List createViewManagers(@NonNull ReactApplicationContext reactContext) {
- return Collections.emptyList();
- }
-
- @NonNull
- @Override
- public List createNativeModules(@NonNull ReactApplicationContext reactContext) {
- List modules = new ArrayList<>();
- modules.add(new AppsModule(reactContext));
- return modules;
- }
-
-}
diff --git a/android/app/src/main/java/com/razinj/context_launcher/CustomAppPackage.kt b/android/app/src/main/java/com/razinj/context_launcher/CustomAppPackage.kt
new file mode 100644
index 0000000..c1314ca
--- /dev/null
+++ b/android/app/src/main/java/com/razinj/context_launcher/CustomAppPackage.kt
@@ -0,0 +1,16 @@
+package com.razinj.context_launcher
+
+import android.view.View
+import com.facebook.react.ReactPackage
+import com.facebook.react.bridge.NativeModule
+import com.facebook.react.bridge.ReactApplicationContext
+import com.facebook.react.uimanager.ReactShadowNode
+import com.facebook.react.uimanager.ViewManager
+
+class CustomAppPackage : ReactPackage {
+ override fun createViewManagers(reactContext: ReactApplicationContext): MutableList>> =
+ mutableListOf()
+
+ override fun createNativeModules(reactContext: ReactApplicationContext): MutableList =
+ listOf(AppsModule(reactContext)).toMutableList()
+}
diff --git a/android/app/src/main/java/com/razinj/context_launcher/LauncherAppsCallback.java b/android/app/src/main/java/com/razinj/context_launcher/LauncherAppsCallback.java
deleted file mode 100644
index 10c9fd8..0000000
--- a/android/app/src/main/java/com/razinj/context_launcher/LauncherAppsCallback.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package com.razinj.context_launcher;
-
-import android.content.pm.LauncherApps;
-import android.os.UserHandle;
-
-/**
- * Empty implementation of LauncherApps.Callback so we do not need to override all methods when
- * only parts of LauncherApps.Callback are needed.
- */
-public class LauncherAppsCallback extends LauncherApps.Callback {
- @Override
- public void onPackageRemoved(String packageName, UserHandle user) {
- }
-
- @Override
- public void onPackageAdded(String packageName, UserHandle user) {
- }
-
- @Override
- public void onPackageChanged(String packageName, UserHandle user) {
- }
-
- @Override
- public void onPackagesAvailable(String[] packageNames, UserHandle user, boolean replacing) {
- }
-
- @Override
- public void onPackagesUnavailable(String[] packageNames, UserHandle user, boolean replacing) {
- }
-}
diff --git a/android/app/src/main/java/com/razinj/context_launcher/LauncherAppsCallback.kt b/android/app/src/main/java/com/razinj/context_launcher/LauncherAppsCallback.kt
new file mode 100644
index 0000000..2170dd6
--- /dev/null
+++ b/android/app/src/main/java/com/razinj/context_launcher/LauncherAppsCallback.kt
@@ -0,0 +1,42 @@
+package com.razinj.context_launcher
+
+import android.content.pm.LauncherApps
+import android.os.UserHandle
+
+/**
+ * Empty implementation of LauncherApps.Callback so we do not need to override all methods when
+ * only parts of LauncherApps.Callback are needed.
+ */
+open class LauncherAppsCallback : LauncherApps.Callback() {
+ override fun onPackageRemoved(
+ packageName: String,
+ user: UserHandle,
+ ) {
+ }
+
+ override fun onPackageAdded(
+ packageName: String,
+ user: UserHandle,
+ ) {
+ }
+
+ override fun onPackageChanged(
+ packageName: String,
+ user: UserHandle,
+ ) {
+ }
+
+ override fun onPackagesAvailable(
+ packageNames: Array,
+ user: UserHandle,
+ replacing: Boolean,
+ ) {
+ }
+
+ override fun onPackagesUnavailable(
+ packageNames: Array,
+ user: UserHandle,
+ replacing: Boolean,
+ ) {
+ }
+}
diff --git a/android/app/src/main/java/com/razinj/context_launcher/MainActivity.java b/android/app/src/main/java/com/razinj/context_launcher/MainActivity.java
deleted file mode 100644
index 604d31a..0000000
--- a/android/app/src/main/java/com/razinj/context_launcher/MainActivity.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.razinj.context_launcher;
-
-import com.facebook.react.ReactActivity;
-import com.facebook.react.ReactActivityDelegate;
-import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
-import com.facebook.react.defaults.DefaultReactActivityDelegate;
-
-import java.util.Objects;
-
-public class MainActivity extends ReactActivity {
-
- /**
- * Returns the name of the main component registered from JavaScript. This is used to schedule
- * rendering of the component.
- */
- @Override
- protected String getMainComponentName() {
- return "context_launcher";
- }
-
- /**
- * Returns the instance of the {@link ReactActivityDelegate}. Here we use a util class {@link
- * DefaultReactActivityDelegate} which allows you to easily enable Fabric and Concurrent React
- * (aka React 18) with two boolean flags.
- */
- @Override
- protected ReactActivityDelegate createReactActivityDelegate() {
- return new DefaultReactActivityDelegate(
- this,
- Objects.requireNonNull(getMainComponentName()),
- // If you opted-in for the New Architecture, we enable the Fabric Renderer.
- DefaultNewArchitectureEntryPoint.getFabricEnabled(), // fabricEnabled
- // If you opted-in for the New Architecture, we enable Concurrent React (i.e. React 18).
- DefaultNewArchitectureEntryPoint.getConcurrentReactEnabled() // concurrentRootEnabled
- );
- }
-}
diff --git a/android/app/src/main/java/com/razinj/context_launcher/MainActivity.kt b/android/app/src/main/java/com/razinj/context_launcher/MainActivity.kt
new file mode 100644
index 0000000..75abbe5
--- /dev/null
+++ b/android/app/src/main/java/com/razinj/context_launcher/MainActivity.kt
@@ -0,0 +1,20 @@
+package com.razinj.context_launcher
+
+import com.facebook.react.ReactActivity
+import com.facebook.react.ReactActivityDelegate
+import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
+import com.facebook.react.defaults.DefaultReactActivityDelegate
+
+class MainActivity : ReactActivity() {
+ /**
+ * Returns the name of the main component registered from JavaScript. This is used to schedule
+ * rendering of the component.
+ */
+ override fun getMainComponentName(): String = "context_launcher"
+
+ /**
+ * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate]
+ * which allows you to enable New Architecture with a single boolean flags [fabricEnabled]
+ */
+ override fun createReactActivityDelegate(): ReactActivityDelegate = DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)
+}
diff --git a/android/app/src/main/java/com/razinj/context_launcher/MainApplication.java b/android/app/src/main/java/com/razinj/context_launcher/MainApplication.java
deleted file mode 100644
index 30cefa0..0000000
--- a/android/app/src/main/java/com/razinj/context_launcher/MainApplication.java
+++ /dev/null
@@ -1,76 +0,0 @@
-package com.razinj.context_launcher;
-
-import android.app.Application;
-import android.content.Intent;
-import android.os.Build;
-
-import androidx.annotation.NonNull;
-
-import com.facebook.react.PackageList;
-import com.facebook.react.ReactApplication;
-import com.facebook.react.ReactNativeHost;
-import com.facebook.react.ReactPackage;
-import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
-import com.facebook.react.defaults.DefaultReactNativeHost;
-import com.facebook.soloader.SoLoader;
-
-import org.jetbrains.annotations.Contract;
-
-import java.util.List;
-
-public class MainApplication extends Application implements ReactApplication {
-
- private final ReactNativeHost mReactNativeHost = new DefaultReactNativeHost(this) {
- @Override
- public boolean getUseDeveloperSupport() {
- return BuildConfig.DEBUG;
- }
-
- @NonNull
- @Override
- protected List getPackages() {
- List packages = new PackageList(this).getPackages();
- packages.add(new CustomAppPackage());
- return packages;
- }
-
- @NonNull
- @Contract(pure = true)
- @Override
- protected String getJSMainModuleName() {
- return "index";
- }
-
- @Override
- protected boolean isNewArchEnabled() {
- return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
- }
-
- @Override
- protected Boolean isHermesEnabled() {
- return BuildConfig.IS_HERMES_ENABLED;
- }
- };
-
- @Override
- public ReactNativeHost getReactNativeHost() {
- return mReactNativeHost;
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
- SoLoader.init(this, false);
- if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
- // If you opted-in for the New Architecture, we load the native entry point for this app.
- DefaultNewArchitectureEntryPoint.load();
- }
- ReactNativeFlipper.initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- startForegroundService(new Intent(this, AppProvider.class));
- } else {
- startService(new Intent(this, AppProvider.class));
- }
- }
-}
diff --git a/android/app/src/main/java/com/razinj/context_launcher/MainApplication.kt b/android/app/src/main/java/com/razinj/context_launcher/MainApplication.kt
new file mode 100644
index 0000000..4385336
--- /dev/null
+++ b/android/app/src/main/java/com/razinj/context_launcher/MainApplication.kt
@@ -0,0 +1,52 @@
+package com.razinj.context_launcher
+
+import android.app.Application
+import android.content.Intent
+import android.os.Build
+import com.facebook.react.PackageList
+import com.facebook.react.ReactApplication
+import com.facebook.react.ReactHost
+import com.facebook.react.ReactNativeHost
+import com.facebook.react.ReactPackage
+import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
+import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
+import com.facebook.react.defaults.DefaultReactNativeHost
+import com.facebook.soloader.SoLoader
+
+class MainApplication :
+ Application(),
+ ReactApplication {
+ override val reactNativeHost: ReactNativeHost =
+ object : DefaultReactNativeHost(this) {
+ override fun getPackages(): List =
+ PackageList(this).packages.apply {
+ add(CustomAppPackage())
+ }
+
+ override fun getJSMainModuleName(): String = "index"
+
+ override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
+
+ override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
+ override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
+ }
+
+ override val reactHost: ReactHost
+ get() = getDefaultReactHost(applicationContext, reactNativeHost)
+
+ override fun onCreate() {
+ super.onCreate()
+ SoLoader.init(this, false)
+
+ if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
+ // If you opted-in for the New Architecture, we load the native entry point for this app.
+ load()
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ startForegroundService(Intent(this, AppProvider::class.java))
+ } else {
+ startService(Intent(this, AppProvider::class.java))
+ }
+ }
+}
diff --git a/android/app/src/main/java/com/razinj/context_launcher/PackageChangeReceiver.java b/android/app/src/main/java/com/razinj/context_launcher/PackageChangeReceiver.java
deleted file mode 100644
index 6e61cd1..0000000
--- a/android/app/src/main/java/com/razinj/context_launcher/PackageChangeReceiver.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package com.razinj.context_launcher;
-
-import static com.razinj.context_launcher.Constants.PACKAGE_CHANGE_IS_REMOVED;
-import static com.razinj.context_launcher.Constants.PACKAGE_CHANGE_NAME;
-import static com.razinj.context_launcher.Constants.PACKAGE_UPDATE_ACTION;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-import androidx.annotation.NonNull;
-
-public class PackageChangeReceiver extends BroadcastReceiver {
-
- @Override
- public void onReceive(@NonNull Context context, @NonNull Intent intent) {
- String packageName = intent.getData().getSchemeSpecificPart();
-
- if (packageName.equalsIgnoreCase(context.getPackageName())) return;
-
- handleEvent(context, intent.getAction(), packageName, intent.getBooleanExtra(Intent.EXTRA_REPLACING, false));
- }
-
- public static void handleEvent(Context context, @NonNull String action, String packageName, boolean replacing) {
- if (!action.equals(Intent.ACTION_PACKAGE_ADDED) && !action.equals(Intent.ACTION_PACKAGE_CHANGED) && !action.equals(Intent.ACTION_PACKAGE_REMOVED)) {
- return;
- }
-
- Intent intent = new Intent();
- intent.setAction(PACKAGE_UPDATE_ACTION);
- intent.putExtra(PACKAGE_CHANGE_NAME, packageName);
-
- if (action.equals(Intent.ACTION_PACKAGE_ADDED) || action.equals(Intent.ACTION_PACKAGE_CHANGED)) {
- Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(packageName);
- // Ignore plugin apps
- if (launchIntent == null) return;
-
- intent.putExtra(PACKAGE_CHANGE_IS_REMOVED, Boolean.FALSE);
- } else if (replacing) {
- intent.putExtra(PACKAGE_CHANGE_IS_REMOVED, Boolean.FALSE);
- } else {
- intent.putExtra(PACKAGE_CHANGE_IS_REMOVED, Boolean.TRUE);
- }
-
- context.sendBroadcast(intent);
- }
-}
diff --git a/android/app/src/main/java/com/razinj/context_launcher/PackageChangeReceiver.kt b/android/app/src/main/java/com/razinj/context_launcher/PackageChangeReceiver.kt
new file mode 100644
index 0000000..05ae714
--- /dev/null
+++ b/android/app/src/main/java/com/razinj/context_launcher/PackageChangeReceiver.kt
@@ -0,0 +1,58 @@
+package com.razinj.context_launcher
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+
+class PackageChangeReceiver : BroadcastReceiver() {
+ override fun onReceive(
+ context: Context,
+ intent: Intent,
+ ) {
+ val packageName = intent.data?.schemeSpecificPart
+ val action = intent.action
+
+ if (packageName == null || action == null) {
+ return
+ }
+ if (packageName.equals(context.packageName, ignoreCase = true)) {
+ return
+ }
+
+ handleEvent(
+ context,
+ action,
+ packageName,
+ )
+ }
+
+ companion object {
+ fun handleEvent(
+ context: Context,
+ action: String,
+ packageName: String,
+ ) {
+ if (action != Intent.ACTION_PACKAGE_ADDED &&
+ action != Intent.ACTION_PACKAGE_CHANGED &&
+ action != Intent.ACTION_PACKAGE_REMOVED
+ ) {
+ return
+ }
+
+ val intent = Intent()
+ intent.setAction(Constants.PACKAGE_UPDATE_ACTION)
+ intent.putExtra(Constants.PACKAGE_CHANGE_NAME, packageName)
+
+ if (action == Intent.ACTION_PACKAGE_ADDED || action == Intent.ACTION_PACKAGE_CHANGED) {
+ // Ignore plugin apps
+ context.packageManager.getLaunchIntentForPackage(packageName) ?: return
+
+ intent.putExtra(Constants.PACKAGE_CHANGE_IS_REMOVED, false)
+ } else {
+ intent.putExtra(Constants.PACKAGE_CHANGE_IS_REMOVED, true)
+ }
+
+ context.sendBroadcast(intent)
+ }
+ }
+}
diff --git a/android/app/src/main/java/com/razinj/context_launcher/Utils.java b/android/app/src/main/java/com/razinj/context_launcher/Utils.java
deleted file mode 100644
index bff0bcc..0000000
--- a/android/app/src/main/java/com/razinj/context_launcher/Utils.java
+++ /dev/null
@@ -1,64 +0,0 @@
-package com.razinj.context_launcher;
-
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.util.Base64;
-
-import androidx.annotation.NonNull;
-
-import com.facebook.react.bridge.ReactApplicationContext;
-
-import java.io.ByteArrayOutputStream;
-
-public class Utils {
-
- private Utils() {
- super();
- }
-
- public static String getEncodedIcon(@NonNull PackageManager pm, String packageName) {
- try {
- return getEncodedIcon(pm.getApplicationIcon(packageName));
- } catch (PackageManager.NameNotFoundException nameNotFoundException) {
- return "NOT_FOUND";
- }
- }
-
- public static String getEncodedIcon(@NonNull Drawable drawable) {
- Bitmap bitmap;
-
- // Single color bitmap will be created of 1x1 pixel
- bitmap = Bitmap.createBitmap(
- drawable.getIntrinsicWidth() > 0 ? drawable.getIntrinsicWidth() : 1,
- drawable.getIntrinsicHeight() > 0 ? drawable.getIntrinsicHeight() : 1,
- Bitmap.Config.ARGB_8888
- );
-
- final Canvas canvas = new Canvas(bitmap);
-
- drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
- drawable.draw(canvas);
-
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
- bitmap.compress(Bitmap.CompressFormat.WEBP_LOSSY, 50, byteArrayOutputStream);
- } else {
- bitmap.compress(Bitmap.CompressFormat.WEBP, 50, byteArrayOutputStream);
- }
-
- return Base64.encodeToString(byteArrayOutputStream.toByteArray(), Base64.NO_WRAP);
- }
-
- public static PackageInfo getPackageInfo(ReactApplicationContext reactContext) throws PackageManager.NameNotFoundException {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- return reactContext.getPackageManager().getPackageInfo(reactContext.getPackageName(), PackageManager.PackageInfoFlags.of(0));
- } else {
- return reactContext.getPackageManager().getPackageInfo(reactContext.getPackageName(), 0);
- }
- }
-}
diff --git a/android/app/src/main/java/com/razinj/context_launcher/Utils.kt b/android/app/src/main/java/com/razinj/context_launcher/Utils.kt
new file mode 100644
index 0000000..9f961cb
--- /dev/null
+++ b/android/app/src/main/java/com/razinj/context_launcher/Utils.kt
@@ -0,0 +1,60 @@
+package com.razinj.context_launcher
+
+import android.content.pm.PackageInfo
+import android.content.pm.PackageManager
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.drawable.Drawable
+import android.os.Build
+import android.util.Base64
+import com.facebook.react.bridge.ReactApplicationContext
+import java.io.ByteArrayOutputStream
+
+object Utils {
+ fun getEncodedIcon(
+ pm: PackageManager,
+ packageName: String?,
+ ): String =
+ try {
+ getEncodedIcon(pm.getApplicationIcon(packageName!!))
+ } catch (nameNotFoundException: PackageManager.NameNotFoundException) {
+ "NOT_FOUND"
+ }
+
+ private fun getEncodedIcon(drawable: Drawable): String {
+ // Single color bitmap will be created of 1x1 pixel
+ val bitmap =
+ Bitmap.createBitmap(
+ if (drawable.intrinsicWidth > 0) drawable.intrinsicWidth else 1,
+ if (drawable.intrinsicHeight > 0) drawable.intrinsicHeight else 1,
+ Bitmap.Config.ARGB_8888,
+ )
+
+ val canvas = Canvas(bitmap)
+
+ drawable.setBounds(0, 0, canvas.width, canvas.height)
+ drawable.draw(canvas)
+
+ val byteArrayOutputStream = ByteArrayOutputStream()
+
+ // See: https://developer.android.com/reference/android/graphics/Bitmap.CompressFormat#summary
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ bitmap.compress(Bitmap.CompressFormat.WEBP_LOSSY, 50, byteArrayOutputStream)
+ } else {
+ bitmap.compress(Bitmap.CompressFormat.WEBP, 50, byteArrayOutputStream)
+ }
+
+ return Base64.encodeToString(byteArrayOutputStream.toByteArray(), Base64.NO_WRAP)
+ }
+
+ @Throws(PackageManager.NameNotFoundException::class)
+ fun getPackageInfo(reactContext: ReactApplicationContext): PackageInfo =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ reactContext.packageManager.getPackageInfo(
+ reactContext.packageName,
+ PackageManager.PackageInfoFlags.of(0),
+ )
+ } else {
+ reactContext.packageManager.getPackageInfo(reactContext.packageName, 0)
+ }
+}
diff --git a/android/app/src/main/res/drawable/rn_edit_text_material.xml b/android/app/src/main/res/drawable/rn_edit_text_material.xml
index f35d996..73b37e4 100644
--- a/android/app/src/main/res/drawable/rn_edit_text_material.xml
+++ b/android/app/src/main/res/drawable/rn_edit_text_material.xml
@@ -20,7 +20,7 @@
android:insetBottom="@dimen/abc_edit_text_inset_bottom_material">
-