Skip to content

Commit

Permalink
App-wide improvements and refactoring (#19)
Browse files Browse the repository at this point in the history
  • Loading branch information
razinj committed Apr 20, 2023
1 parent fba09f9 commit 40f9a15
Show file tree
Hide file tree
Showing 111 changed files with 5,136 additions and 4,052 deletions.
1 change: 0 additions & 1 deletion .ruby-version

This file was deleted.

5 changes: 3 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
source 'https://rubygems.org'

# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
ruby File.read(File.join(__dir__, '.ruby-version')).strip
gem 'cocoapods', '~> 1.11', '>= 1.11.3'
ruby '>= 2.6.10'

gem 'cocoapods', '>= 1.11.3'
6 changes: 3 additions & 3 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ android {
applicationId "com.razinj.context_launcher"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 13
versionName "1.5.0"
versionCode 14
versionName "2.0.0"
archivesBaseName = "context-launcher-v$versionName-$versionCode"
}

Expand Down Expand Up @@ -160,7 +160,7 @@ android {
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.0.0")
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'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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);
}
}
}
182 changes: 77 additions & 105 deletions android/app/src/main/java/com/razinj/context_launcher/AppsModule.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
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.Callback;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
Expand All @@ -23,168 +32,131 @@
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 BroadcastReceiver packageChangeBroadcastReceiver;
private static DeviceEventManagerModule.RCTDeviceEventEmitter rctDeviceEventEmitter;

// TODO: Can these values be in a separate file?
// Package change intent action
public static String PACKAGE_UPDATE_ACTION = "packageUpdateAction";
// Package change event
public static String PACKAGE_CHANGE_EVENT = "packageChange";
public static String PACKAGE_CHANGE_NAME = "packageName";
public static String PACKAGE_CHANGE_IS_REMOVED = "isRemoved";
private static BroadcastReceiver packageChangeBroadcastReceiver;

AppsModule(ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext = reactContext;
initializePackageChangeBroadcastReceiver(reactContext);
initializePackageChangeBroadcastReceiver();
}

private void initializePackageChangeBroadcastReceiver(ReactApplicationContext reactContext) {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(PACKAGE_UPDATE_ACTION);
@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 (rctDeviceEventEmitter == null) {
rctDeviceEventEmitter = reactContext.getJSModule((DeviceEventManagerModule.RCTDeviceEventEmitter.class));
}
if (!reactContext.hasActiveReactInstance()) return;

Bundle extras = intent.getExtras();
sendPackageChangeEvent((String) extras.get(PACKAGE_CHANGE_NAME), (Boolean) extras.get(PACKAGE_CHANGE_IS_REMOVED));
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);
}
};

this.reactContext.registerReceiver(packageChangeBroadcastReceiver, intentFilter);
}
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(PACKAGE_UPDATE_ACTION);

@Override
public void onCatalystInstanceDestroy() {
this.reactContext.unregisterReceiver(packageChangeBroadcastReceiver);
reactContext.registerReceiver(packageChangeBroadcastReceiver, intentFilter);
}

@NonNull
@Override
public String getName() {
return "AppsModule";
}

private static class AppDetails {
CharSequence name;
CharSequence label;
@ReactMethod
public void getApplications(Promise promise) {
PackageManager pm = reactContext.getPackageManager();
List<AppDetails> apps = new ArrayList<>();
List<PackageInfo> packages;

AppDetails(CharSequence name, CharSequence label) {
this.name = name;
this.label = label;
// Get installed packages
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
packages = pm.getInstalledPackages(PackageManager.PackageInfoFlags.of(0));
} else {
packages = pm.getInstalledPackages(0);
}

@NonNull
public String toString() {
return "{\"label\":\"" + this.label + "\",\"name\":\"" + this.name + "\"}";
}
}
// 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;

@ReactMethod
public void getApplications(Callback callBack) {
List<AppDetails> apps = new ArrayList<>();
List<PackageInfo> packages = this.reactContext
.getPackageManager()
.getInstalledPackages(0);

for (final PackageInfo packageInfo : packages) {
// TODO: Output a warning when package intent is null
if (getPackageLaunchIntent(packageInfo.packageName) == null) continue;

apps.add(new AppDetails(
packageInfo.packageName,
packageInfo.applicationInfo.loadLabel(this.reactContext.getPackageManager())
));
apps.add(new AppDetails(packageInfo.packageName, packageInfo.applicationInfo.loadLabel(pm).toString(), Utils.getEncodedIcon(pm, packageInfo.packageName)));
}
}

callBack.invoke(apps.toString());
promise.resolve(apps.toString());
}

@ReactMethod
private void launchApplication(String packageName) {
Intent intent = getPackageLaunchIntent(packageName);
Intent intent = reactContext.getPackageManager().getLaunchIntentForPackage(packageName);

// TODO: Output error message and/or return an error to RN
if (intent == null) return;

this.reactContext.startActivity(intent);
reactContext.startActivity(intent);
}

@ReactMethod
public void showApplicationDetails(String packageName) {
startActivity(packageName, new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS));
}
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK).setData(Uri.fromParts("package", packageName, null));

@ReactMethod
public void requestApplicationUninstall(String packageName) {
startActivity(packageName, new Intent(Intent.ACTION_DELETE));
reactContext.startActivity(intent);
}

@ReactMethod
public void getApplicationIcon(String packageName, Callback callBack) {
callBack.invoke(Utils.getEncodedIcon(this.reactContext, packageName));
}

public static void sendPackageChangeEvent(String packageName, Boolean isRemoved) {
WritableMap map = Arguments.createMap();
map.putString(PACKAGE_CHANGE_NAME, packageName);
map.putBoolean(PACKAGE_CHANGE_IS_REMOVED, isRemoved);

rctDeviceEventEmitter.emit(PACKAGE_CHANGE_EVENT, map);
}

private void startActivity(String packageName, Intent intent) {
// TODO: Check if the flag can be removed or not (read more about the behavior)
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setData(Uri.fromParts("package", packageName, null));

this.reactContext.startActivity(intent);
}
public void requestApplicationUninstall(String packageName) {
Intent intent = new Intent(Intent.ACTION_DELETE).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK).setData(Uri.fromParts("package", packageName, null));

private Intent getPackageLaunchIntent(String packageName) {
return this.reactContext.getPackageManager().getLaunchIntentForPackage(packageName);
reactContext.startActivity(intent);
}

@ReactMethod
public void addListener(String eventName) {
// TODO: Should this method exist?
// Set up any upstream listeners or background tasks as necessary
// Required for NativeEventEmitter
}

@ReactMethod
public void removeListeners(Integer count) {
// TODO: Should this method exist?
// Remove upstream listeners, stop unnecessary background tasks
}

private PackageInfo getPackageInfo() throws Exception {
return getReactApplicationContext().getPackageManager().getPackageInfo(getReactApplicationContext().getPackageName(), 0);
// Required for NativeEventEmitter
}

@Override
public Map<String, Object> getConstants() {
String appVersion, buildNumber, packageName;
String appVersion;
String buildNumber;
String packageName;

try {
appVersion = getPackageInfo().versionName;
buildNumber = Integer.toString(getPackageInfo().versionCode);
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 (Exception e) {
appVersion = "unknown";
buildNumber = "unknown";
packageName = "unknown";
} catch (PackageManager.NameNotFoundException e) {
appVersion = SHORT_NOT_AVAILABLE;
buildNumber = SHORT_NOT_AVAILABLE;
packageName = SHORT_NOT_AVAILABLE;
}

final Map<String, Object> constants = new HashMap<>();
Map<String, Object> constants = new HashMap<>();

constants.put("appVersion", appVersion);
constants.put("buildNumber", buildNumber);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
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";
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
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;
Expand All @@ -12,6 +14,8 @@
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 {
Expand All @@ -22,13 +26,16 @@ public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}

@NonNull
@Override
protected List<ReactPackage> getPackages() {
List<ReactPackage> packages = new PackageList(this).getPackages();
packages.add(new CustomAppPackage());
return packages;
}

@NonNull
@Contract(pure = true)
@Override
protected String getJSMainModuleName() {
return "index";
Expand Down
Loading

0 comments on commit 40f9a15

Please sign in to comment.