android support! thanks to TachiWeb devs.

This commit is contained in:
Aria Moradi
2021-01-02 04:57:20 +03:30
parent ced07d4e1e
commit 1e46a0c78c
291 changed files with 68699 additions and 16 deletions

View File

@@ -0,0 +1,17 @@
package xyz.nulldev.androidcompat
import android.app.Application
import org.kodein.di.DI
import org.kodein.di.conf.global
import org.kodein.di.instance
import xyz.nulldev.androidcompat.androidimpl.CustomContext
class AndroidCompat {
val context: CustomContext by DI.global.instance()
fun startApp(application: Application) {
application.attach(context)
application.onCreate()
}
}

View File

@@ -0,0 +1,30 @@
package xyz.nulldev.androidcompat
import org.kodein.di.DI
import org.kodein.di.conf.global
import xyz.nulldev.androidcompat.bytecode.ModApplier
import xyz.nulldev.androidcompat.config.ApplicationInfoConfigModule
import xyz.nulldev.androidcompat.config.FilesConfigModule
import xyz.nulldev.androidcompat.config.SystemConfigModule
import xyz.nulldev.ts.config.GlobalConfigManager
/**
* Initializes the Android compatibility module
*/
class AndroidCompatInitializer {
val modApplier by lazy { ModApplier() }
fun init() {
modApplier.apply()
DI.global.addImport(AndroidCompatModule().create())
//Register config modules
GlobalConfigManager.registerModules(
FilesConfigModule.register(GlobalConfigManager.config),
ApplicationInfoConfigModule.register(GlobalConfigManager.config),
SystemConfigModule.register(GlobalConfigManager.config)
)
}
}

View File

@@ -0,0 +1,39 @@
package xyz.nulldev.androidcompat
import android.content.Context
import org.kodein.di.DI
import org.kodein.di.bind
import org.kodein.di.conf.global
import org.kodein.di.instance
import org.kodein.di.singleton
import xyz.nulldev.androidcompat.androidimpl.CustomContext
import xyz.nulldev.androidcompat.androidimpl.FakePackageManager
import xyz.nulldev.androidcompat.info.ApplicationInfoImpl
import xyz.nulldev.androidcompat.io.AndroidFiles
import xyz.nulldev.androidcompat.pm.PackageController
import xyz.nulldev.androidcompat.service.ServiceSupport
/**
* AndroidCompatModule
*/
class AndroidCompatModule {
fun create() = DI.Module("AndroidCompat") {
bind<AndroidFiles>() with singleton { AndroidFiles() }
bind<ApplicationInfoImpl>() with singleton { ApplicationInfoImpl() }
bind<ServiceSupport>() with singleton { ServiceSupport() }
bind<FakePackageManager>() with singleton { FakePackageManager() }
bind<PackageController>() with singleton { PackageController() }
//Context
bind<CustomContext>() with singleton { CustomContext() }
bind<Context>() with singleton {
val context: Context by DI.global.instance<CustomContext>()
context
}
}
}

View File

@@ -0,0 +1,738 @@
/*
* Copyright 2016 Andy Bao
*
* 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 xyz.nulldev.androidcompat.androidimpl;
import android.content.*;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.DatabaseErrorHandler;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.Uri;
import android.os.*;
import android.view.Display;
import android.view.DisplayAdjustments;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.kodein.di.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import xyz.nulldev.androidcompat.info.ApplicationInfoImpl;
import xyz.nulldev.androidcompat.io.AndroidFiles;
import xyz.nulldev.androidcompat.io.sharedprefs.JsonSharedPreferences;
import xyz.nulldev.androidcompat.service.ServiceSupport;
import xyz.nulldev.androidcompat.util.KodeinGlobalHelper;
import java.io.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Custom context implementation.
*
* TODO Deal with packagemanager for extension sources
*/
public class CustomContext extends Context implements DIAware {
private DI kodein;
public CustomContext() {
this(KodeinGlobalHelper.kodein());
}
public CustomContext(DI kodein) {
this.kodein = kodein;
//Init configs
androidFiles = KodeinGlobalHelper.instance(AndroidFiles.class, getDi());
applicationInfo = KodeinGlobalHelper.instance(ApplicationInfoImpl.class, getDi());
serviceSupport = KodeinGlobalHelper.instance(ServiceSupport.class, getDi());
fakePackageManager = KodeinGlobalHelper.instance(FakePackageManager.class, getDi());
}
@NotNull
@Override
public DI getDi() {
return kodein;
}
private AndroidFiles androidFiles;
private ApplicationInfoImpl applicationInfo;
private ServiceSupport serviceSupport;
private FakePackageManager fakePackageManager;
private Logger logger = LoggerFactory.getLogger(CustomContext.class);
private Map<String, Object> serviceMap = new HashMap<>();
{
serviceMap.put(Context.CONNECTIVITY_SERVICE, ConnectivityManager.INSTANCE);
serviceMap.put(Context.POWER_SERVICE, PowerManager.INSTANCE);
}
@Override
public AssetManager getAssets() {
return null;
}
@Override
public Resources getResources() {
return null;
}
@Override
public PackageManager getPackageManager() {
return fakePackageManager;
}
@Override
public ContentResolver getContentResolver() {
return null;
}
@Override
public Looper getMainLooper() {
return null;
}
@Override
public Context getApplicationContext() {
return this;
}
@Override
public void setTheme(int i) {
}
@Override
public Resources.Theme getTheme() {
return null;
}
@Override
public ClassLoader getClassLoader() {
return this.getClass().getClassLoader();
}
@Override
public String getPackageName() {
return null;
}
@Override
public String getBasePackageName() {
return null;
}
@Override
public String getOpPackageName() {
return null;
}
@Override
public ApplicationInfo getApplicationInfo() {
return applicationInfo;
}
@Override
public String getPackageResourcePath() {
return null;
}
@Override
public String getPackageCodePath() {
return null;
}
/** Fake shared prefs! **/
private Map<String, SharedPreferences> prefs = new HashMap<>(); //Cache
private File sharedPrefsFileFromString(String s) {
return new File(androidFiles.getPrefsDir(), s + ".json");
}
@Override
public synchronized SharedPreferences getSharedPreferences(String s, int i) {
SharedPreferences preferences = prefs.get(s);
//Create new shared preferences if one does not exist
if(preferences == null) {
preferences = getSharedPreferences(sharedPrefsFileFromString(s), i);
prefs.put(s, preferences);
}
return preferences;
}
public SharedPreferences getSharedPreferences(File file, int mode) {
return new JsonSharedPreferences(file);
}
@Override
public boolean moveSharedPreferencesFrom(Context sourceContext, String name) {
return false;
}
@Override
public boolean deleteSharedPreferences(String name) {
prefs.remove(name);
return sharedPrefsFileFromString(name).delete();
}
@Override
public FileInputStream openFileInput(String s) throws FileNotFoundException {
return null;
}
@Override
public FileOutputStream openFileOutput(String s, int i) throws FileNotFoundException {
return null;
}
@Override
public boolean deleteFile(String s) {
return false;
}
@Override
public File getFileStreamPath(String s) {
return null;
}
@Override
public File getSharedPreferencesPath(String name) {
return null;
}
@Override
public File getDataDir() {
return androidFiles.getDataDir();
}
@Override
public File getFilesDir() {
return androidFiles.getFilesDir();
}
@Override
public File getNoBackupFilesDir() {
return androidFiles.getNoBackupFilesDir();
}
@Override
public File getExternalFilesDir(String s) {
return androidFiles.getExternalFilesDirs().get(0);
}
@Override
public File[] getExternalFilesDirs(String s) {
List<File> files = androidFiles.getExternalFilesDirs();
return files.toArray(new File[files.size()]);
}
@Override
public File getObbDir() {
return androidFiles.getObbDirs().get(0);
}
@Override
public File[] getObbDirs() {
List<File> files = androidFiles.getObbDirs();
return files.toArray(new File[files.size()]);
}
@Override
public File getCacheDir() {
return androidFiles.getCacheDir();
}
@Override
public File getCodeCacheDir() {
return androidFiles.getCodeCacheDir();
}
@Override
public File getExternalCacheDir() {
return androidFiles.getExternalCacheDirs().get(0);
}
@Override
public File[] getExternalCacheDirs() {
List<File> files = androidFiles.getExternalCacheDirs();
return files.toArray(new File[files.size()]);
}
@Override
public File[] getExternalMediaDirs() {
List<File> files = androidFiles.getExternalMediaDirs();
return files.toArray(new File[files.size()]);
}
@Override
public String[] fileList() {
return new String[0];
}
@Override
public File getDir(String s, int i) {
return null;
}
@Override
public SQLiteDatabase openOrCreateDatabase(String s, int i, SQLiteDatabase.CursorFactory cursorFactory) {
return openOrCreateDatabase(s, i, cursorFactory, null);
}
@Override
public SQLiteDatabase openOrCreateDatabase(String s, int i, SQLiteDatabase.CursorFactory cursorFactory, DatabaseErrorHandler databaseErrorHandler) {
return SQLiteDatabase.openOrCreateDatabase(getDatabasePath(s).getAbsolutePath(), cursorFactory, databaseErrorHandler);
}
@Override
public boolean moveDatabaseFrom(Context sourceContext, String name) {
return false;
}
@Override
public boolean deleteDatabase(String s) {
return false;
}
@Override
public File getDatabasePath(String s) {
return new File(new File(androidFiles.getRootDir(), "databases"), s);
}
@Override
public String[] databaseList() {
return new String[0];
}
@Override
public Drawable getWallpaper() {
return null;
}
@Override
public Drawable peekWallpaper() {
return null;
}
@Override
public int getWallpaperDesiredMinimumWidth() {
return 0;
}
@Override
public int getWallpaperDesiredMinimumHeight() {
return 0;
}
@Override
public void setWallpaper(Bitmap bitmap) throws IOException {
}
@Override
public void setWallpaper(InputStream inputStream) throws IOException {
}
@Override
public void clearWallpaper() throws IOException {
}
@Override
public void startActivity(Intent intent) {
}
@Override
public void startActivity(Intent intent, Bundle bundle) {
}
@Override
public void startActivities(Intent[] intents) {
}
@Override
public void startActivities(Intent[] intents, Bundle bundle) {
}
@Override
public void startIntentSender(IntentSender intentSender, Intent intent, int i, int i1, int i2) throws IntentSender.SendIntentException {
}
@Override
public void startIntentSender(IntentSender intentSender, Intent intent, int i, int i1, int i2, Bundle bundle) throws IntentSender.SendIntentException {
}
@Override
public void sendBroadcast(Intent intent) {
}
@Override
public void sendBroadcast(Intent intent, String s) {
}
@Override
public void sendBroadcastMultiplePermissions(Intent intent, String[] receiverPermissions) {
}
@Override
public void sendBroadcast(Intent intent, String receiverPermission, Bundle options) {
}
@Override
public void sendBroadcast(Intent intent, String receiverPermission, int appOp) {
}
@Override
public void sendOrderedBroadcast(Intent intent, String s) {
}
@Override
public void sendOrderedBroadcast(Intent intent, String s, BroadcastReceiver broadcastReceiver, Handler handler, int i, String s1, Bundle bundle) {
}
@Override
public void sendOrderedBroadcast(Intent intent, String receiverPermission, Bundle options, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras) {
}
@Override
public void sendOrderedBroadcast(Intent intent, String receiverPermission, int appOp, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras) {
}
@Override
public void sendBroadcastAsUser(Intent intent, UserHandle userHandle) {
}
@Override
public void sendBroadcastAsUser(Intent intent, UserHandle userHandle, String s) {
}
@Override
public void sendBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission, int appOp) {
}
@Override
public void sendOrderedBroadcastAsUser(Intent intent, UserHandle userHandle, String s, BroadcastReceiver broadcastReceiver, Handler handler, int i, String s1, Bundle bundle) {
}
@Override
public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission, int appOp, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras) {
}
@Override
public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission, int appOp, Bundle options, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras) {
}
@Override
public void sendStickyBroadcast(Intent intent) {
}
@Override
public void sendStickyOrderedBroadcast(Intent intent, BroadcastReceiver broadcastReceiver, Handler handler, int i, String s, Bundle bundle) {
}
@Override
public void removeStickyBroadcast(Intent intent) {
}
@Override
public void sendStickyBroadcastAsUser(Intent intent, UserHandle userHandle) {
}
@Override
public void sendStickyBroadcastAsUser(Intent intent, UserHandle user, Bundle options) {
}
@Override
public void sendStickyOrderedBroadcastAsUser(Intent intent, UserHandle userHandle, BroadcastReceiver broadcastReceiver, Handler handler, int i, String s, Bundle bundle) {
}
@Override
public void removeStickyBroadcastAsUser(Intent intent, UserHandle userHandle) {
}
@Override
public Intent registerReceiver(BroadcastReceiver broadcastReceiver, IntentFilter intentFilter) {
return null;
}
@Override
public Intent registerReceiver(BroadcastReceiver broadcastReceiver, IntentFilter intentFilter, String s, Handler handler) {
return null;
}
@Override
public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user, IntentFilter filter, String broadcastPermission, Handler scheduler) {
return null;
}
public void unregisterReceiver(BroadcastReceiver broadcastReceiver) {
}
@Override
public ComponentName startService(Intent intent) {
serviceSupport.startService(this, intent);
return intent.getComponent();
}
@Override
public boolean stopService(Intent intent) {
serviceSupport.stopService(this, intent);
return true;
}
@Override
public ComponentName startServiceAsUser(Intent service, UserHandle user) {
logger.warn("An attempt was made to start the service: '{}' as another user! Since multiple user services are currently not supported, the service will be started as the current user!", service.getComponent().getClassName());
return startService(service);
}
@Override
public boolean stopServiceAsUser(Intent service, UserHandle user) {
logger.warn("An attempt was made to stop the service: '{}' as another user! Since multiple user services are currently not supported, the service will be stopped as the current user!", service.getComponent().getClassName());
return stopService(service);
}
@Override
public boolean bindService(Intent intent, ServiceConnection serviceConnection, int i) {
return false;
}
@Override
public void unbindService(ServiceConnection serviceConnection) {
}
@Override
public boolean startInstrumentation(ComponentName componentName, String s, Bundle bundle) {
return false;
}
@Override
public Object getSystemService(String s) {
return serviceMap.get(s);
}
@Override
public String getSystemServiceName(Class<?> aClass) {
return null;
}
@Override
public int checkPermission(String s, int i, int i1) {
return PackageManager.PERMISSION_GRANTED;
}
@Override
public int checkPermission(String permission, int pid, int uid, IBinder callerToken) {
return PackageManager.PERMISSION_GRANTED;
}
@Override
public int checkCallingPermission(String s) {
return PackageManager.PERMISSION_GRANTED;
}
@Override
public int checkCallingOrSelfPermission(String s) {
return PackageManager.PERMISSION_GRANTED;
}
@Override
public int checkSelfPermission(String s) {
return PackageManager.PERMISSION_GRANTED;
}
@Override
public void enforcePermission(String s, int i, int i1, String s1) {
}
@Override
public void enforceCallingPermission(String s, String s1) {
}
@Override
public void enforceCallingOrSelfPermission(String s, String s1) {
}
@Override
public void grantUriPermission(String s, Uri uri, int i) {
}
@Override
public void revokeUriPermission(Uri uri, int i) {
}
@Override
public int checkUriPermission(Uri uri, int i, int i1, int i2) {
return 0;
}
@Override
public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags, IBinder callerToken) {
return 0;
}
@Override
public int checkCallingUriPermission(Uri uri, int i) {
return 0;
}
@Override
public int checkCallingOrSelfUriPermission(Uri uri, int i) {
return 0;
}
@Override
public int checkUriPermission(Uri uri, String s, String s1, int i, int i1, int i2) {
return 0;
}
@Override
public void enforceUriPermission(Uri uri, int i, int i1, int i2, String s) {
}
@Override
public void enforceCallingUriPermission(Uri uri, int i, String s) {
}
@Override
public void enforceCallingOrSelfUriPermission(Uri uri, int i, String s) {
}
@Override
public void enforceUriPermission(Uri uri, String s, String s1, int i, int i1, int i2, String s2) {
}
@Override
public Context createPackageContext(String s, int i) throws PackageManager.NameNotFoundException {
return null;
}
@Override
public Context createPackageContextAsUser(String packageName, int flags, UserHandle user) throws PackageManager.NameNotFoundException {
return null;
}
@Override
public Context createApplicationContext(ApplicationInfo application, int flags) throws PackageManager.NameNotFoundException {
return null;
}
@Override
public int getUserId() {
return 0;
}
@Override
public Context createConfigurationContext(Configuration configuration) {
return null;
}
@Override
public Context createDisplayContext(Display display) {
return null;
}
@Override
public Context createDeviceProtectedStorageContext() {
return null;
}
@Override
public Context createCredentialProtectedStorageContext() {
return null;
}
@Override
public DisplayAdjustments getDisplayAdjustments(int displayId) {
return null;
}
@Override
public Display getDisplay() {
return null;
}
@Override
public boolean isDeviceProtectedStorage() {
return false;
}
@Override
public boolean isCredentialProtectedStorage() {
return false;
}
@NotNull
@Override
public DIContext<?> getDiContext() {
return getDi().getDiContext();
}
@Nullable
@Override
public DITrigger getDiTrigger() {
return null;
}
}

View File

@@ -0,0 +1,849 @@
package xyz.nulldev.androidcompat.androidimpl;
import android.app.PackageInstallObserver;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.*;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Handler;
import android.os.UserHandle;
import kotlin.NotImplementedError;
import xyz.nulldev.androidcompat.pm.InstalledPackage;
import xyz.nulldev.androidcompat.pm.PackageController;
import xyz.nulldev.androidcompat.util.KodeinGlobalHelper;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class FakePackageManager extends PackageManager {
private PackageController controller = KodeinGlobalHelper.instance(PackageController.class);
@Override
public PackageInfo getPackageInfo(String packageName, int flags) throws NameNotFoundException {
InstalledPackage installedPackage = controller.findPackage(packageName);
if(installedPackage == null) throw new NameNotFoundException();
return installedPackage.getInfo();
}
@Override
public PackageInfo getPackageInfo(VersionedPackage versionedPackage, int flags) throws NameNotFoundException {
throw new NotImplementedError();
}
@Override
public PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId) throws NameNotFoundException {
return getPackageInfo(packageName, userId);
}
@Override
public String[] currentToCanonicalPackageNames(String[] names) {
throw new NotImplementedError();
}
@Override
public String[] canonicalToCurrentPackageNames(String[] names) {
throw new NotImplementedError();
}
@Override
public Intent getLaunchIntentForPackage(String packageName) {
throw new NotImplementedError();
}
@Override
public Intent getLeanbackLaunchIntentForPackage(String packageName) {
throw new NotImplementedError();
}
@Override
public int[] getPackageGids(String packageName) throws NameNotFoundException {
return new int[0];
}
@Override
public int[] getPackageGids(String packageName, int flags) throws NameNotFoundException {
return new int[0];
}
@Override
public int getPackageUid(String packageName, int flags) throws NameNotFoundException {
return 0;
}
@Override
public int getPackageUidAsUser(String packageName, int userId) throws NameNotFoundException {
return 0;
}
@Override
public int getPackageUidAsUser(String packageName, int flags, int userId) throws NameNotFoundException {
return 0;
}
@Override
public PermissionInfo getPermissionInfo(String name, int flags) throws NameNotFoundException {
throw new NotImplementedError();
}
@Override
public List<PermissionInfo> queryPermissionsByGroup(String group, int flags) throws NameNotFoundException {
throw new NotImplementedError();
}
@Override
public boolean isPermissionReviewModeEnabled() {
return false;
}
@Override
public PermissionGroupInfo getPermissionGroupInfo(String name, int flags) throws NameNotFoundException {
throw new NotImplementedError();
}
@Override
public List<PermissionGroupInfo> getAllPermissionGroups(int flags) {
throw new NotImplementedError();
}
@Override
public ApplicationInfo getApplicationInfo(String packageName, int flags) throws NameNotFoundException {
return getPackageInfo(packageName, flags).applicationInfo;
}
@Override
public ApplicationInfo getApplicationInfoAsUser(String packageName, int flags, int userId) throws NameNotFoundException {
return getPackageInfoAsUser(packageName, flags, userId).applicationInfo;
}
@Override
public ActivityInfo getActivityInfo(ComponentName component, int flags) throws NameNotFoundException {
throw new NotImplementedError();
}
@Override
public ActivityInfo getReceiverInfo(ComponentName component, int flags) throws NameNotFoundException {
throw new NotImplementedError();
}
@Override
public ServiceInfo getServiceInfo(ComponentName component, int flags) throws NameNotFoundException {
throw new NotImplementedError();
}
@Override
public ProviderInfo getProviderInfo(ComponentName component, int flags) throws NameNotFoundException {
throw new NotImplementedError();
}
//TODO Return loaded extensions
@Override
public List<PackageInfo> getInstalledPackages(int flags) {
return controller.listInstalled().stream().map(InstalledPackage::getInfo).collect(Collectors.toList());
}
@Override
public List<PackageInfo> getPackagesHoldingPermissions(String[] permissions, int flags) {
throw new NotImplementedError();
}
@Override
public List<PackageInfo> getInstalledPackagesAsUser(int flags, int userId) {
throw new NotImplementedError();
}
@Override
public int checkPermission(String permName, String pkgName) {
return PackageManager.PERMISSION_GRANTED;
}
@Override
public boolean isPermissionRevokedByPolicy(String permName, String pkgName) {
return false;
}
@Override
public String getPermissionControllerPackageName() {
return null;
}
@Override
public boolean addPermission(PermissionInfo info) {
return true;
}
@Override
public boolean addPermissionAsync(PermissionInfo info) {
return true;
}
@Override
public void removePermission(String name) {
}
@Override
public void grantRuntimePermission(String packageName, String permissionName, UserHandle user) {
}
@Override
public void revokeRuntimePermission(String packageName, String permissionName, UserHandle user) {
}
@Override
public int getPermissionFlags(String permissionName, String packageName, UserHandle user) {
return 0;
}
@Override
public void updatePermissionFlags(String permissionName, String packageName, int flagMask, int flagValues, UserHandle user) {
throw new NotImplementedError();
}
@Override
public boolean shouldShowRequestPermissionRationale(String permission) {
return true;
}
@Override
public int checkSignatures(String pkg1, String pkg2) {
return 0;
}
@Override
public int checkSignatures(int uid1, int uid2) {
return 0;
}
@Override
public String[] getPackagesForUid(int uid) {
return new String[0];
}
@Override
public String getNameForUid(int uid) {
return null;
}
@Override
public String[] getNamesForUids(int[] uids) {
return new String[0];
}
@Override
public int getUidForSharedUser(String sharedUserName) throws NameNotFoundException {
return 0;
}
@Override
public List<ApplicationInfo> getInstalledApplications(int flags) {
return getInstalledPackages(flags).stream().map((it) -> it.applicationInfo).collect(Collectors.toList());
}
@Override
public List<ApplicationInfo> getInstalledApplicationsAsUser(int flags, int userId) {
return getInstalledApplications(flags);
}
@Override
public List<InstantAppInfo> getInstantApps() {
return new ArrayList<>();
}
@Override
public Drawable getInstantAppIcon(String packageName) {
throw new NotImplementedError();
}
@Override
public boolean isInstantApp() {
return false;
}
@Override
public boolean isInstantApp(String packageName) {
return false;
}
@Override
public int getInstantAppCookieMaxBytes() {
return 0;
}
@Override
public int getInstantAppCookieMaxSize() {
return 0;
}
@Override
public byte[] getInstantAppCookie() {
return new byte[0];
}
@Override
public void clearInstantAppCookie() {
}
@Override
public void updateInstantAppCookie(byte[] cookie) {
}
@Override
public boolean setInstantAppCookie(byte[] cookie) {
return false;
}
@Override
public String[] getSystemSharedLibraryNames() {
return new String[0];
}
@Override
public List<SharedLibraryInfo> getSharedLibraries(int flags) {
return null;
}
@Override
public List<SharedLibraryInfo> getSharedLibrariesAsUser(int flags, int userId) {
return null;
}
@Override
public String getServicesSystemSharedLibraryPackageName() {
return null;
}
@Override
public String getSharedSystemSharedLibraryPackageName() {
return null;
}
@Override
public ChangedPackages getChangedPackages(int sequenceNumber) {
return null;
}
@Override
public FeatureInfo[] getSystemAvailableFeatures() {
return new FeatureInfo[0];
}
@Override
public boolean hasSystemFeature(String name) {
return false;
}
@Override
public boolean hasSystemFeature(String name, int version) {
return false;
}
@Override
public ResolveInfo resolveActivity(Intent intent, int flags) {
return null;
}
@Override
public ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId) {
return null;
}
@Override
public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) {
return null;
}
@Override
public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, int flags, int userId) {
return null;
}
@Override
public List<ResolveInfo> queryIntentActivityOptions(ComponentName caller, Intent[] specifics, Intent intent, int flags) {
return null;
}
@Override
public List<ResolveInfo> queryBroadcastReceivers(Intent intent, int flags) {
return null;
}
@Override
public List<ResolveInfo> queryBroadcastReceiversAsUser(Intent intent, int flags, int userId) {
return null;
}
@Override
public ResolveInfo resolveService(Intent intent, int flags) {
return null;
}
@Override
public List<ResolveInfo> queryIntentServices(Intent intent, int flags) {
return null;
}
@Override
public List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int flags, int userId) {
return null;
}
@Override
public List<ResolveInfo> queryIntentContentProvidersAsUser(Intent intent, int flags, int userId) {
return null;
}
@Override
public List<ResolveInfo> queryIntentContentProviders(Intent intent, int flags) {
return null;
}
@Override
public ProviderInfo resolveContentProvider(String name, int flags) {
return null;
}
@Override
public ProviderInfo resolveContentProviderAsUser(String name, int flags, int userId) {
return null;
}
@Override
public List<ProviderInfo> queryContentProviders(String processName, int uid, int flags) {
return null;
}
@Override
public InstrumentationInfo getInstrumentationInfo(ComponentName className, int flags) throws NameNotFoundException {
return null;
}
@Override
public List<InstrumentationInfo> queryInstrumentation(String targetPackage, int flags) {
return null;
}
@Override
public Drawable getDrawable(String packageName, int resid, ApplicationInfo appInfo) {
return null;
}
@Override
public Drawable getActivityIcon(ComponentName activityName) throws NameNotFoundException {
return null;
}
@Override
public Drawable getActivityIcon(Intent intent) throws NameNotFoundException {
return null;
}
@Override
public Drawable getActivityBanner(ComponentName activityName) throws NameNotFoundException {
return null;
}
@Override
public Drawable getActivityBanner(Intent intent) throws NameNotFoundException {
return null;
}
@Override
public Drawable getDefaultActivityIcon() {
return null;
}
@Override
public Drawable getApplicationIcon(ApplicationInfo info) {
return null;
}
@Override
public Drawable getApplicationIcon(String packageName) throws NameNotFoundException {
return null;
}
@Override
public Drawable getApplicationBanner(ApplicationInfo info) {
return null;
}
@Override
public Drawable getApplicationBanner(String packageName) throws NameNotFoundException {
return null;
}
@Override
public Drawable getActivityLogo(ComponentName activityName) throws NameNotFoundException {
return null;
}
@Override
public Drawable getActivityLogo(Intent intent) throws NameNotFoundException {
return null;
}
@Override
public Drawable getApplicationLogo(ApplicationInfo info) {
return null;
}
@Override
public Drawable getApplicationLogo(String packageName) throws NameNotFoundException {
return null;
}
@Override
public Drawable getUserBadgedIcon(Drawable icon, UserHandle user) {
return null;
}
@Override
public Drawable getUserBadgedDrawableForDensity(Drawable drawable, UserHandle user, Rect badgeLocation, int badgeDensity) {
return null;
}
@Override
public Drawable getUserBadgeForDensity(UserHandle user, int density) {
return null;
}
@Override
public Drawable getUserBadgeForDensityNoBackground(UserHandle user, int density) {
return null;
}
@Override
public CharSequence getUserBadgedLabel(CharSequence label, UserHandle user) {
return null;
}
@Override
public CharSequence getText(String packageName, int resid, ApplicationInfo appInfo) {
return null;
}
@Override
public XmlResourceParser getXml(String packageName, int resid, ApplicationInfo appInfo) {
return null;
}
@Override
public CharSequence getApplicationLabel(ApplicationInfo info) {
return info.nonLocalizedLabel;
}
@Override
public Resources getResourcesForActivity(ComponentName activityName) throws NameNotFoundException {
return null;
}
@Override
public Resources getResourcesForApplication(ApplicationInfo app) throws NameNotFoundException {
return null;
}
@Override
public Resources getResourcesForApplication(String appPackageName) throws NameNotFoundException {
return null;
}
@Override
public Resources getResourcesForApplicationAsUser(String appPackageName, int userId) throws NameNotFoundException {
return null;
}
@Override
public void installPackage(Uri packageURI, PackageInstallObserver observer, int flags, String installerPackageName) {
}
@Override
public int installExistingPackage(String packageName) throws NameNotFoundException {
return 0;
}
@Override
public int installExistingPackage(String packageName, int installReason) throws NameNotFoundException {
return 0;
}
@Override
public int installExistingPackageAsUser(String packageName, int userId) throws NameNotFoundException {
return 0;
}
@Override
public void verifyPendingInstall(int id, int verificationCode) {
}
@Override
public void extendVerificationTimeout(int id, int verificationCodeAtTimeout, long millisecondsToDelay) {
}
@Override
public void verifyIntentFilter(int verificationId, int verificationCode, List<String> failedDomains) {
}
@Override
public int getIntentVerificationStatusAsUser(String packageName, int userId) {
return 0;
}
@Override
public boolean updateIntentVerificationStatusAsUser(String packageName, int status, int userId) {
return false;
}
@Override
public List<IntentFilterVerificationInfo> getIntentFilterVerifications(String packageName) {
return null;
}
@Override
public List<IntentFilter> getAllIntentFilters(String packageName) {
return null;
}
@Override
public String getDefaultBrowserPackageNameAsUser(int userId) {
return null;
}
@Override
public boolean setDefaultBrowserPackageNameAsUser(String packageName, int userId) {
return false;
}
@Override
public void setInstallerPackageName(String targetPackage, String installerPackageName) {
}
@Override
public void setUpdateAvailable(String packageName, boolean updateAvaialble) {
}
@Override
public String getInstallerPackageName(String packageName) {
return null;
}
@Override
public void freeStorage(String volumeUuid, long freeStorageSize, IntentSender pi) {
}
@Override
public void addPackageToPreferred(String packageName) {
}
@Override
public void removePackageFromPreferred(String packageName) {
}
@Override
public List<PackageInfo> getPreferredPackages(int flags) {
return null;
}
@Override
public void addPreferredActivity(IntentFilter filter, int match, ComponentName[] set, ComponentName activity) {
}
@Override
public void replacePreferredActivity(IntentFilter filter, int match, ComponentName[] set, ComponentName activity) {
}
@Override
public void clearPackagePreferredActivities(String packageName) {
}
@Override
public int getPreferredActivities(List<IntentFilter> outFilters, List<ComponentName> outActivities, String packageName) {
return 0;
}
@Override
public ComponentName getHomeActivities(List<ResolveInfo> outActivities) {
return null;
}
@Override
public void setComponentEnabledSetting(ComponentName componentName, int newState, int flags) {
}
@Override
public int getComponentEnabledSetting(ComponentName componentName) {
return 0;
}
@Override
public void setApplicationEnabledSetting(String packageName, int newState, int flags) {
}
@Override
public int getApplicationEnabledSetting(String packageName) {
return 0;
}
@Override
public void flushPackageRestrictionsAsUser(int userId) {
}
@Override
public boolean setApplicationHiddenSettingAsUser(String packageName, boolean hidden, UserHandle userHandle) {
return false;
}
@Override
public boolean getApplicationHiddenSettingAsUser(String packageName, UserHandle userHandle) {
return false;
}
@Override
public boolean isSafeMode() {
return false;
}
@Override
public void addOnPermissionsChangeListener(OnPermissionsChangedListener listener) {
}
@Override
public void removeOnPermissionsChangeListener(OnPermissionsChangedListener listener) {
}
@Override
public KeySet getKeySetByAlias(String packageName, String alias) {
return null;
}
@Override
public KeySet getSigningKeySet(String packageName) {
return null;
}
@Override
public boolean isSignedBy(String packageName, KeySet ks) {
return false;
}
@Override
public boolean isSignedByExactly(String packageName, KeySet ks) {
return false;
}
@Override
public String[] setPackagesSuspendedAsUser(String[] packageNames, boolean suspended, int userId) {
return new String[0];
}
@Override
public boolean isPackageSuspendedForUser(String packageName, int userId) {
return false;
}
@Override
public int getMoveStatus(int moveId) {
return 0;
}
@Override
public void registerMoveCallback(MoveCallback callback, Handler handler) {
}
@Override
public void unregisterMoveCallback(MoveCallback callback) {
}
@Override
public boolean isUpgrade() {
return false;
}
@Override
public PackageInstaller getPackageInstaller() {
return null;
}
@Override
public void addCrossProfileIntentFilter(IntentFilter filter, int sourceUserId, int targetUserId, int flags) {
}
@Override
public void clearCrossProfileIntentFilters(int sourceUserId) {
}
@Override
public Drawable loadItemIcon(PackageItemInfo itemInfo, ApplicationInfo appInfo) {
return null;
}
@Override
public Drawable loadUnbadgedItemIcon(PackageItemInfo itemInfo, ApplicationInfo appInfo) {
return null;
}
@Override
public boolean isPackageAvailable(String packageName) {
return false;
}
@Override
public int getInstallReason(String packageName, UserHandle user) {
return 0;
}
@Override
public boolean canRequestPackageInstalls() {
return false;
}
@Override
public ComponentName getInstantAppResolverSettingsComponent() {
return null;
}
@Override
public ComponentName getInstantAppInstallerComponent() {
return null;
}
@Override
public String getInstantAppAndroidId(String packageName, UserHandle user) {
return null;
}
@Override
public void registerDexModule(String dexModulePath, DexModuleRegisterCallback callback) {
}
}

View File

@@ -0,0 +1,22 @@
package xyz.nulldev.androidcompat.bytecode
import javassist.CtClass
import mu.KotlinLogging
/**
* Applies Javassist modifications
*/
class ModApplier {
val logger = KotlinLogging.logger {}
fun apply() {
logger.info { "Applying Javassist mods..." }
val modifiedClasses = mutableListOf<CtClass>()
modifiedClasses.forEach {
it.toClass()
}
}
}

View File

@@ -0,0 +1,18 @@
package xyz.nulldev.androidcompat.config
import com.typesafe.config.Config
import xyz.nulldev.ts.config.ConfigModule
/**
* Application info config.
*/
class ApplicationInfoConfigModule(config: Config) : ConfigModule(config) {
val packageName = config.getString("packageName")!!
val debug = config.getBoolean("debug")
companion object {
fun register(config: Config)
= ApplicationInfoConfigModule(config.getConfig("android.app"))
}
}

View File

@@ -0,0 +1,33 @@
package xyz.nulldev.androidcompat.config
import com.typesafe.config.Config
import xyz.nulldev.ts.config.ConfigModule
/**
* Files configuration modules. Specifies where to store the Android files.
*/
class FilesConfigModule(config: Config) : ConfigModule(config) {
val dataDir = config.getString("dataDir")!!
val filesDir = config.getString("filesDir")!!
val noBackupFilesDir = config.getString("noBackupFilesDir")!!
val externalFilesDirs: MutableList<String> = config.getStringList("externalFilesDirs")!!
val obbDirs: MutableList<String> = config.getStringList("obbDirs")!!
val cacheDir = config.getString("cacheDir")!!
val codeCacheDir = config.getString("codeCacheDir")!!
val externalCacheDirs: MutableList<String> = config.getStringList("externalCacheDirs")!!
val externalMediaDirs: MutableList<String> = config.getStringList("externalMediaDirs")!!
val rootDir = config.getString("rootDir")!!
val externalStorageDir = config.getString("externalStorageDir")!!
val downloadCacheDir = config.getString("downloadCacheDir")!!
val databasesDir = config.getString("databasesDir")!!
val prefsDir = config.getString("prefsDir")!!
val packageDir = config.getString("packageDir")!!
companion object {
fun register(config: Config)
= FilesConfigModule(config.getConfig("android.files"))
}
}

View File

@@ -0,0 +1,21 @@
package xyz.nulldev.androidcompat.config
import com.typesafe.config.Config
import xyz.nulldev.ts.config.ConfigModule
class SystemConfigModule(val config: Config) : ConfigModule(config) {
val isDebuggable = config.getBoolean("isDebuggable")
val propertyPrefix = "properties."
fun getStringProperty(property: String) = config.getString("$propertyPrefix$property")!!
fun getIntProperty(property: String) = config.getInt("$propertyPrefix$property")
fun getLongProperty(property: String) = config.getLong("$propertyPrefix$property")
fun getBooleanProperty(property: String) = config.getBoolean("$propertyPrefix$property")
fun hasProperty(property: String) = config.hasPath("$propertyPrefix$property")
companion object {
fun register(config: Config)
= SystemConfigModule(config.getConfig("android.system"))
}
}

View File

@@ -0,0 +1,841 @@
package xyz.nulldev.androidcompat.db
import java.io.InputStream
import java.io.Reader
import java.math.BigDecimal
import java.net.URL
import java.sql.*
import java.sql.Array
import java.sql.Date
import java.util.*
class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
private val cachedContent = mutableListOf<ResultSetEntry>()
private val columnCache = mutableMapOf<String, Int>()
private var lastReturnWasNull = false
private var cursor = 0
var resultSetLength = 0
val parentMetadata = parent.metaData
val columnCount = parentMetadata.columnCount
val columnLabels = (1 .. columnCount).map {
parentMetadata.getColumnLabel(it)
}.toTypedArray()
init {
val columnCount = columnCount
// TODO
// Profiling reveals that this is a bottleneck (average ms for this call is: 48ms)
// How can we optimize this?
// We need to fill the cache as the set is loaded
//Fill cache
while(parent.next()) {
cachedContent += ResultSetEntry().apply {
for(i in 1 .. columnCount)
data += parent.getObject(i)
}
resultSetLength++
}
}
private fun notImplemented(): Nothing {
throw UnsupportedOperationException("This class currently does not support this operation!")
}
private fun cursorValid(): Boolean {
return isAfterLast || isBeforeFirst
}
private fun internalMove(row: Int) {
if(cursor < 0) cursor = 0
else if(cursor > resultSetLength + 1) cursor = resultSetLength + 1
else cursor = row
}
private fun obj(column: Int): Any? {
val obj = cachedContent[cursor - 1].data[column - 1]
lastReturnWasNull = obj == null
return obj
}
private fun obj(column: String?): Any? {
return obj(cachedFindColumn(column))
}
private fun cachedFindColumn(column: String?)
= columnCache.getOrPut(column!!, {
findColumn(column)
})
override fun getNClob(columnIndex: Int): NClob {
return obj(columnIndex) as NClob
}
override fun getNClob(columnLabel: String?): NClob {
return obj(columnLabel) as NClob
}
override fun updateNString(columnIndex: Int, nString: String?) {
notImplemented()
}
override fun updateNString(columnLabel: String?, nString: String?) {
notImplemented()
}
override fun updateBinaryStream(columnIndex: Int, x: InputStream?, length: Int) {
notImplemented()
}
override fun updateBinaryStream(columnLabel: String?, x: InputStream?, length: Int) {
notImplemented()
}
override fun updateBinaryStream(columnIndex: Int, x: InputStream?, length: Long) {
notImplemented()
}
override fun updateBinaryStream(columnLabel: String?, x: InputStream?, length: Long) {
notImplemented()
}
override fun updateBinaryStream(columnIndex: Int, x: InputStream?) {
notImplemented()
}
override fun updateBinaryStream(columnLabel: String?, x: InputStream?) {
notImplemented()
}
override fun updateTimestamp(columnIndex: Int, x: Timestamp?) {
notImplemented()
}
override fun updateTimestamp(columnLabel: String?, x: Timestamp?) {
notImplemented()
}
override fun updateNCharacterStream(columnIndex: Int, x: Reader?, length: Long) {
notImplemented()
}
override fun updateNCharacterStream(columnLabel: String?, reader: Reader?, length: Long) {
notImplemented()
}
override fun updateNCharacterStream(columnIndex: Int, x: Reader?) {
notImplemented()
}
override fun updateNCharacterStream(columnLabel: String?, reader: Reader?) {
notImplemented()
}
override fun updateInt(columnIndex: Int, x: Int) {
notImplemented()
}
override fun updateInt(columnLabel: String?, x: Int) {
notImplemented()
}
override fun moveToInsertRow() {
notImplemented()
}
override fun getDate(columnIndex: Int): Date {
//TODO Maybe?
notImplemented()
}
override fun getDate(columnLabel: String?): Date {
//TODO Maybe?
notImplemented()
}
override fun getDate(columnIndex: Int, cal: Calendar?): Date {
//TODO Maybe?
notImplemented()
}
override fun getDate(columnLabel: String?, cal: Calendar?): Date {
//TODO Maybe?
notImplemented()
}
override fun beforeFirst() {
//TODO Maybe?
notImplemented()
}
override fun updateFloat(columnIndex: Int, x: Float) {
notImplemented()
}
override fun updateFloat(columnLabel: String?, x: Float) {
notImplemented()
}
override fun getBoolean(columnIndex: Int): Boolean {
return obj(columnIndex) as Boolean
}
override fun getBoolean(columnLabel: String?): Boolean {
return obj(columnLabel) as Boolean
}
override fun isFirst(): Boolean {
return cursor - 1 < resultSetLength
}
override fun getBigDecimal(columnIndex: Int, scale: Int): BigDecimal {
//TODO Maybe?
notImplemented()
}
override fun getBigDecimal(columnLabel: String?, scale: Int): BigDecimal {
//TODO Maybe?
notImplemented()
}
override fun getBigDecimal(columnIndex: Int): BigDecimal {
return obj(columnIndex) as BigDecimal
}
override fun getBigDecimal(columnLabel: String?): BigDecimal {
return obj(columnLabel) as BigDecimal
}
override fun updateBytes(columnIndex: Int, x: ByteArray?) {
notImplemented()
}
override fun updateBytes(columnLabel: String?, x: ByteArray?) {
notImplemented()
}
override fun isLast(): Boolean {
return cursor == resultSetLength
}
override fun insertRow() {
notImplemented()
}
override fun getTime(columnIndex: Int): Time {
//TODO Maybe?
notImplemented()
}
override fun getTime(columnLabel: String?): Time {
//TODO Maybe?
notImplemented()
}
override fun getTime(columnIndex: Int, cal: Calendar?): Time {
//TODO Maybe?
notImplemented()
}
override fun getTime(columnLabel: String?, cal: Calendar?): Time {
//TODO Maybe?
notImplemented()
}
override fun rowDeleted() = false
override fun last(): Boolean {
internalMove(resultSetLength)
return cursorValid()
}
override fun isAfterLast(): Boolean {
return cursor > resultSetLength
}
override fun relative(rows: Int): Boolean {
internalMove(cursor + rows)
return cursorValid()
}
override fun absolute(row: Int): Boolean {
if(row > 0) {
internalMove(row)
} else {
last()
for(i in 1 .. row)
previous()
}
return cursorValid()
}
override fun getSQLXML(columnIndex: Int): SQLXML? {
//TODO Maybe?
notImplemented()
}
override fun getSQLXML(columnLabel: String?): SQLXML? {
//TODO Maybe?
notImplemented()
}
override fun <T : Any?> unwrap(iface: Class<T>?): T {
if(thisIsWrapperFor(iface))
return this as T
else
return parent.unwrap(iface)
}
override fun next(): Boolean {
internalMove(cursor + 1)
return cursorValid()
}
override fun getFloat(columnIndex: Int): Float {
return obj(columnIndex) as Float
}
override fun getFloat(columnLabel: String?): Float {
return obj(columnLabel) as Float
}
override fun wasNull() = lastReturnWasNull
override fun getRow(): Int {
return cursor
}
override fun first(): Boolean {
internalMove(1)
return cursorValid()
}
override fun updateAsciiStream(columnIndex: Int, x: InputStream?, length: Int) {
notImplemented()
}
override fun updateAsciiStream(columnLabel: String?, x: InputStream?, length: Int) {
notImplemented()
}
override fun updateAsciiStream(columnIndex: Int, x: InputStream?, length: Long) {
notImplemented()
}
override fun updateAsciiStream(columnLabel: String?, x: InputStream?, length: Long) {
notImplemented()
}
override fun updateAsciiStream(columnIndex: Int, x: InputStream?) {
notImplemented()
}
override fun updateAsciiStream(columnLabel: String?, x: InputStream?) {
notImplemented()
}
override fun getURL(columnIndex: Int): URL {
return obj(columnIndex) as URL
}
override fun getURL(columnLabel: String?): URL {
return obj(columnLabel) as URL
}
override fun updateShort(columnIndex: Int, x: Short) {
notImplemented()
}
override fun updateShort(columnLabel: String?, x: Short) {
notImplemented()
}
override fun getType() = ResultSet.TYPE_SCROLL_INSENSITIVE
override fun updateNClob(columnIndex: Int, nClob: NClob?) {
notImplemented()
}
override fun updateNClob(columnLabel: String?, nClob: NClob?) {
notImplemented()
}
override fun updateNClob(columnIndex: Int, reader: Reader?, length: Long) {
notImplemented()
}
override fun updateNClob(columnLabel: String?, reader: Reader?, length: Long) {
notImplemented()
}
override fun updateNClob(columnIndex: Int, reader: Reader?) {
notImplemented()
}
override fun updateNClob(columnLabel: String?, reader: Reader?) {
notImplemented()
}
override fun updateRef(columnIndex: Int, x: Ref?) {
notImplemented()
}
override fun updateRef(columnLabel: String?, x: Ref?) {
notImplemented()
}
override fun updateObject(columnIndex: Int, x: Any?, scaleOrLength: Int) {
notImplemented()
}
override fun updateObject(columnIndex: Int, x: Any?) {
notImplemented()
}
override fun updateObject(columnLabel: String?, x: Any?, scaleOrLength: Int) {
notImplemented()
}
override fun updateObject(columnLabel: String?, x: Any?) {
notImplemented()
}
override fun afterLast() {
internalMove(resultSetLength + 1)
}
override fun updateLong(columnIndex: Int, x: Long) {
notImplemented()
}
override fun updateLong(columnLabel: String?, x: Long) {
notImplemented()
}
override fun getBlob(columnIndex: Int): Blob {
//TODO Maybe?
notImplemented()
}
override fun getBlob(columnLabel: String?): Blob {
//TODO Maybe?
notImplemented()
}
override fun updateClob(columnIndex: Int, x: Clob?) {
notImplemented()
}
override fun updateClob(columnLabel: String?, x: Clob?) {
notImplemented()
}
override fun updateClob(columnIndex: Int, reader: Reader?, length: Long) {
notImplemented()
}
override fun updateClob(columnLabel: String?, reader: Reader?, length: Long) {
notImplemented()
}
override fun updateClob(columnIndex: Int, reader: Reader?) {
notImplemented()
}
override fun updateClob(columnLabel: String?, reader: Reader?) {
notImplemented()
}
override fun getByte(columnIndex: Int): Byte {
return obj(columnIndex) as Byte
}
override fun getByte(columnLabel: String?): Byte {
return obj(columnLabel) as Byte
}
override fun getString(columnIndex: Int): String? {
return obj(columnIndex) as String?
}
override fun getString(columnLabel: String?): String? {
return obj(columnLabel) as String?
}
override fun updateSQLXML(columnIndex: Int, xmlObject: SQLXML?) {
notImplemented()
}
override fun updateSQLXML(columnLabel: String?, xmlObject: SQLXML?) {
notImplemented()
}
override fun updateDate(columnIndex: Int, x: Date?) {
notImplemented()
}
override fun updateDate(columnLabel: String?, x: Date?) {
notImplemented()
}
override fun getObject(columnIndex: Int): Any? {
return obj(columnIndex)
}
override fun getObject(columnLabel: String?): Any? {
return obj(columnLabel)
}
override fun getObject(columnIndex: Int, map: MutableMap<String, Class<*>>?): Any {
//TODO Maybe?
notImplemented()
}
override fun getObject(columnLabel: String?, map: MutableMap<String, Class<*>>?): Any {
//TODO Maybe?
notImplemented()
}
override fun <T : Any?> getObject(columnIndex: Int, type: Class<T>?): T {
return obj(columnIndex) as T
}
override fun <T : Any?> getObject(columnLabel: String?, type: Class<T>?): T {
return obj(columnLabel) as T
}
override fun previous(): Boolean {
internalMove(cursor - 1)
return cursorValid()
}
override fun updateDouble(columnIndex: Int, x: Double) {
notImplemented()
}
override fun updateDouble(columnLabel: String?, x: Double) {
notImplemented()
}
private fun castToLong(obj: Any?): Long {
if(obj == null) return 0
else if(obj is Long) return obj
else if(obj is Number) return obj.toLong()
else throw IllegalStateException("Object is not a long!")
}
override fun getLong(columnIndex: Int): Long {
return castToLong(obj(columnIndex))
}
override fun getLong(columnLabel: String?): Long {
return castToLong(obj(columnLabel))
}
override fun getClob(columnIndex: Int): Clob {
//TODO Maybe?
notImplemented()
}
override fun getClob(columnLabel: String?): Clob {
//TODO Maybe?
notImplemented()
}
override fun updateBlob(columnIndex: Int, x: Blob?) {
notImplemented()
}
override fun updateBlob(columnLabel: String?, x: Blob?) {
notImplemented()
}
override fun updateBlob(columnIndex: Int, inputStream: InputStream?, length: Long) {
notImplemented()
}
override fun updateBlob(columnLabel: String?, inputStream: InputStream?, length: Long) {
notImplemented()
}
override fun updateBlob(columnIndex: Int, inputStream: InputStream?) {
notImplemented()
}
override fun updateBlob(columnLabel: String?, inputStream: InputStream?) {
notImplemented()
}
override fun updateByte(columnIndex: Int, x: Byte) {
notImplemented()
}
override fun updateByte(columnLabel: String?, x: Byte) {
notImplemented()
}
override fun updateRow() {
notImplemented()
}
override fun deleteRow() {
notImplemented()
}
override fun getNString(columnIndex: Int): String {
return obj(columnIndex) as String
}
override fun getNString(columnLabel: String?): String {
return obj(columnLabel) as String
}
override fun getArray(columnIndex: Int): Array {
//TODO Maybe?
notImplemented()
}
override fun getArray(columnLabel: String?): Array {
//TODO Maybe?
notImplemented()
}
override fun cancelRowUpdates() {
notImplemented()
}
override fun updateString(columnIndex: Int, x: String?) {
notImplemented()
}
override fun updateString(columnLabel: String?, x: String?) {
notImplemented()
}
override fun setFetchDirection(direction: Int) {
notImplemented()
}
override fun getCharacterStream(columnIndex: Int): Reader {
return getNCharacterStream(columnIndex)
}
override fun getCharacterStream(columnLabel: String?): Reader {
return getNCharacterStream(columnLabel)
}
override fun isBeforeFirst(): Boolean {
return cursor - 1 < resultSetLength
}
override fun updateBoolean(columnIndex: Int, x: Boolean) {
notImplemented()
}
override fun updateBoolean(columnLabel: String?, x: Boolean) {
notImplemented()
}
override fun refreshRow() {
notImplemented()
}
override fun rowUpdated() = false
override fun updateBigDecimal(columnIndex: Int, x: BigDecimal?) {
notImplemented()
}
override fun updateBigDecimal(columnLabel: String?, x: BigDecimal?) {
notImplemented()
}
override fun getShort(columnIndex: Int): Short {
return obj(columnIndex) as Short
}
override fun getShort(columnLabel: String?): Short {
return obj(columnLabel) as Short
}
override fun getAsciiStream(columnIndex: Int): InputStream {
return getBinaryStream(columnIndex)
}
override fun getAsciiStream(columnLabel: String?): InputStream {
return getBinaryStream(columnLabel)
}
override fun updateTime(columnIndex: Int, x: Time?) {
notImplemented()
}
override fun updateTime(columnLabel: String?, x: Time?) {
notImplemented()
}
override fun getTimestamp(columnIndex: Int): Timestamp {
//TODO Maybe?
notImplemented()
}
override fun getTimestamp(columnLabel: String?): Timestamp {
//TODO Maybe?
notImplemented()
}
override fun getTimestamp(columnIndex: Int, cal: Calendar?): Timestamp {
//TODO Maybe?
notImplemented()
}
override fun getTimestamp(columnLabel: String?, cal: Calendar?): Timestamp {
//TODO Maybe?
notImplemented()
}
override fun getRef(columnIndex: Int): Ref {
//TODO Maybe?
notImplemented()
}
override fun getRef(columnLabel: String?): Ref {
//TODO Maybe?
notImplemented()
}
override fun getConcurrency() = ResultSet.CONCUR_READ_ONLY
override fun updateRowId(columnIndex: Int, x: RowId?) {
notImplemented()
}
override fun updateRowId(columnLabel: String?, x: RowId?) {
notImplemented()
}
override fun getNCharacterStream(columnIndex: Int): Reader {
return getBinaryStream(columnIndex).reader()
}
override fun getNCharacterStream(columnLabel: String?): Reader {
return getBinaryStream(columnLabel).reader()
}
override fun updateArray(columnIndex: Int, x: Array?) {
notImplemented()
}
override fun updateArray(columnLabel: String?, x: Array?) {
notImplemented()
}
override fun getBytes(columnIndex: Int): ByteArray {
return obj(columnIndex) as ByteArray
}
override fun getBytes(columnLabel: String?): ByteArray {
return obj(columnLabel) as ByteArray
}
override fun getDouble(columnIndex: Int): Double {
return obj(columnIndex) as Double
}
override fun getDouble(columnLabel: String?): Double {
return obj(columnLabel) as Double
}
override fun getUnicodeStream(columnIndex: Int): InputStream {
return getBinaryStream(columnIndex)
}
override fun getUnicodeStream(columnLabel: String?): InputStream {
return getBinaryStream(columnLabel)
}
override fun rowInserted() = false
private fun thisIsWrapperFor(iface: Class<*>?) = this.javaClass.isInstance(iface)
override fun isWrapperFor(iface: Class<*>?): Boolean {
return thisIsWrapperFor(iface) || parent.isWrapperFor(iface)
}
override fun getInt(columnIndex: Int): Int {
return obj(columnIndex) as Int
}
override fun getInt(columnLabel: String?): Int {
return obj(columnLabel) as Int
}
override fun updateNull(columnIndex: Int) {
notImplemented()
}
override fun updateNull(columnLabel: String?) {
notImplemented()
}
override fun getRowId(columnIndex: Int): RowId {
//TODO Maybe?
notImplemented()
}
override fun getRowId(columnLabel: String?): RowId {
//TODO Maybe?
notImplemented()
}
override fun getMetaData(): ResultSetMetaData {
return object : ResultSetMetaData by parentMetadata {
override fun isReadOnly(column: Int) = true
override fun isWritable(column: Int) = false
override fun isDefinitelyWritable(column: Int) = false
override fun getColumnCount() = this@ScrollableResultSet.columnCount
override fun getColumnLabel(column: Int): String {
return columnLabels[column - 1]
}
}
}
override fun getBinaryStream(columnIndex: Int): InputStream {
return (obj(columnIndex) as ByteArray).inputStream()
}
override fun getBinaryStream(columnLabel: String?): InputStream {
return (obj(columnLabel) as ByteArray).inputStream()
}
override fun updateCharacterStream(columnIndex: Int, x: Reader?, length: Int) {
notImplemented()
}
override fun updateCharacterStream(columnLabel: String?, reader: Reader?, length: Int) {
notImplemented()
}
override fun updateCharacterStream(columnIndex: Int, x: Reader?, length: Long) {
notImplemented()
}
override fun updateCharacterStream(columnLabel: String?, reader: Reader?, length: Long) {
notImplemented()
}
override fun updateCharacterStream(columnIndex: Int, x: Reader?) {
notImplemented()
}
override fun updateCharacterStream(columnLabel: String?, reader: Reader?) {
notImplemented()
}
class ResultSetEntry {
val data = mutableListOf<Any?>()
}
}

View File

@@ -0,0 +1,22 @@
package xyz.nulldev.androidcompat.info
import android.content.pm.ApplicationInfo
import org.kodein.di.DI
import org.kodein.di.DIAware
import org.kodein.di.conf.global
import org.kodein.di.instance
import xyz.nulldev.androidcompat.config.ApplicationInfoConfigModule
import xyz.nulldev.ts.config.ConfigManager
class ApplicationInfoImpl(override val di: DI = DI.global) : ApplicationInfo(), DIAware {
val configManager: ConfigManager by di.instance()
val appInfoConfig: ApplicationInfoConfigModule
get() = configManager.module()
val debug: Boolean get() = appInfoConfig.debug
init {
super.packageName = appInfoConfig.packageName
}
}

View File

@@ -0,0 +1,38 @@
package xyz.nulldev.androidcompat.io
import xyz.nulldev.androidcompat.config.FilesConfigModule
import xyz.nulldev.ts.config.ConfigManager
import xyz.nulldev.ts.config.GlobalConfigManager
import java.io.File
/**
* Android file constants.
*/
class AndroidFiles(val configManager: ConfigManager = GlobalConfigManager) {
val filesConfig: FilesConfigModule
get() = configManager.module()
val dataDir: File get() = registerFile(filesConfig.dataDir)
val filesDir: File get() = registerFile(filesConfig.filesDir)
val noBackupFilesDir: File get() = registerFile(filesConfig.noBackupFilesDir)
val externalFilesDirs: List<File> get() = filesConfig.externalFilesDirs.map { registerFile(it) }
val obbDirs: List<File> get() = filesConfig.obbDirs.map { registerFile(it) }
val cacheDir: File get() = registerFile(filesConfig.cacheDir)
val codeCacheDir: File get() = registerFile(filesConfig.codeCacheDir)
val externalCacheDirs: List<File> get() = filesConfig.externalCacheDirs.map { registerFile(it) }
val externalMediaDirs: List<File> get() = filesConfig.externalMediaDirs.map { registerFile(it) }
val rootDir: File get() = registerFile(filesConfig.rootDir)
val externalStorageDir: File get() = registerFile(filesConfig.externalStorageDir)
val downloadCacheDir: File get() = registerFile(filesConfig.downloadCacheDir)
val databasesDir: File get() = registerFile(filesConfig.databasesDir)
val prefsDir: File get() = registerFile(filesConfig.prefsDir)
val packagesDir: File get() = registerFile(filesConfig.packageDir)
fun registerFile(file: String): File {
return File(file).apply {
mkdirs()
}
}
}

View File

@@ -0,0 +1,385 @@
/*
* Copyright 2016 Andy Bao
*
* 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 xyz.nulldev.androidcompat.io.sharedprefs;
import android.content.SharedPreferences;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.*;
/**
* JSON implementation of Android's SharedPreferences.
*/
public class JsonSharedPreferences implements SharedPreferences {
private static final String KEY_TYPE = "t";
private static final String KEY_VALUE = "v";
private Map<String, Object> prefs = new HashMap<>(); //In-memory preference values
private List<OnSharedPreferenceChangeListener> listeners = new ArrayList<>(); //Change listeners
private File file; //Where the values should be stored
private Logger logger = LoggerFactory.getLogger(JsonSharedPreferences.class);
/**
* Create a SharedPreference instance from a file
* @param file The file to load the prefs from, use null to create an empty SharedPreferences instance.
*/
public JsonSharedPreferences(File file) {
this.file = file;
//Load previous values if they exist
if(file != null) {
if(file.exists()) {
try {
loadFromString(new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8));
} catch (IOException e) {
logger.error("Failed to read shared prefs from String!", e);
}
}
}
}
/**
* Load the SharedPreferences from a String.
* @param string The String to load the prefs from.
*/
public synchronized void loadFromString(String string) {
try {
JSONObject jsonObject = new JSONObject(string);
//Changes are need to be applied atomically so we temporarily store all changes in a separate map
Map<String, Object> tempMap = new HashMap<>();
//Loop through all preference objects in JSON
for (String key : jsonObject.keySet()) {
JSONObject object = jsonObject.getJSONObject(key);
//Load the object's Java type
String typeString = object.getString(KEY_TYPE);
PrefType type = PrefType.valueOf(typeString);
Object res;
//Map the JSON object's value to it's Java value
switch (type) {
case String:
res = object.getString(KEY_VALUE);
break;
case StringSet:
Set<String> set = new HashSet<>();
JSONArray array = object.getJSONArray(KEY_VALUE);
for (int i = 0; i < array.length(); i++) {
set.add(array.getString(i));
}
res = set;
break;
case Int:
res = object.getInt(KEY_VALUE);
break;
case Long:
res = object.getLong(KEY_VALUE);
break;
case Float:
res = Float.parseFloat(object.getString(KEY_VALUE));
break;
case Boolean:
res = object.getBoolean(KEY_VALUE);
break;
default:
case Null:
res = null;
break;
}
//Queue the loaded object for placement into the in-memory preference map
tempMap.put(key, res);
}
//Apply all changes made to the in-memory preference map atomically
prefs = tempMap;
} catch (JSONException e) {
throw new RuntimeException("Error parsing JSON shared preferences!", e);
}
}
/**
* Serialize the JSON prefs to a String.
* @return The serialized prefs.
*/
public synchronized String saveToString() {
return saveToJSONObject().toString();
}
/**
* Serialize the JSON prefs to a JSONObject
* @return The serialized prefs.
*/
public synchronized JSONObject saveToJSONObject() {
JSONObject object = new JSONObject();
//Loop through every preference in the in-memory preference map
for (Map.Entry<String, Object> entry : prefs.entrySet()) {
JSONObject entryObj = new JSONObject();
Object value = entry.getValue();
//Determine the object's type
PrefType type = PrefType.fromObject(value);
if (type == PrefType.Float) {
value = value.toString();
} else if (type == PrefType.StringSet) {
JSONArray arrayObj = new JSONArray();
Set<String> casted = (Set<String>) value;
for (String item : casted) {
arrayObj.put(item);
}
value = arrayObj;
}
//Put the preference's type and value into a JSON object
entryObj.put(KEY_TYPE, type.name());
entryObj.put(KEY_VALUE, value);
object.put(entry.getKey(), entryObj);
}
return object;
}
@Override
public synchronized Map<String, ?> getAll() {
return new HashMap<>(prefs);
}
private <T> T fallbackIfNull(T obj, T fallback) {
if (obj == null) {
return fallback;
}
return obj;
}
@Override
public synchronized String getString(String s, String s1) {
return fallbackIfNull((String) prefs.get(s), s1);
}
@Override
public synchronized Set<String> getStringSet(String s, Set<String> set) {
return fallbackIfNull((Set<String>) prefs.get(s), set);
}
@Override
public synchronized int getInt(String s, int i) {
return fallbackIfNull((Integer) prefs.get(s), i);
}
@Override
public synchronized long getLong(String s, long l) {
return fallbackIfNull((Long) prefs.get(s), l);
}
@Override
public synchronized float getFloat(String s, float v) {
return fallbackIfNull((Float) prefs.get(s), v);
}
@Override
public synchronized boolean getBoolean(String s, boolean b) {
return fallbackIfNull((Boolean) prefs.get(s), b);
}
public synchronized Object get(String s, Object b) {
return fallbackIfNull(prefs.get(s), b);
}
@Override
public synchronized boolean contains(String s) {
return prefs.containsKey(s);
}
@Override
public JsonSharedPreferencesEditor edit() {
return new JsonSharedPreferencesEditor();
}
@Override
public synchronized void registerOnSharedPreferenceChangeListener(
OnSharedPreferenceChangeListener onSharedPreferenceChangeListener) {
if (!listeners.contains(onSharedPreferenceChangeListener)) {
listeners.add(onSharedPreferenceChangeListener);
}
}
@Override
public synchronized void unregisterOnSharedPreferenceChangeListener(
OnSharedPreferenceChangeListener onSharedPreferenceChangeListener) {
listeners.remove(onSharedPreferenceChangeListener);
}
public class JsonSharedPreferencesEditor implements Editor {
Map<String, Object> prefsClone = new HashMap<>(prefs);
List<String> affectedKeys = new ArrayList<>(); //List of all affected keys to invoke listeners on once changes applied
private JsonSharedPreferencesEditor() {
}
private void recordChange(String key) {
if (!affectedKeys.contains(key)) {
affectedKeys.add(key);
}
}
public synchronized Editor put(String s, Object o) {
PrefType.fromObject(o); // Will throw if 'o' is invalid
prefsClone.put(s, o);
recordChange(s);
return this;
}
@Override
public synchronized Editor putString(String s, String s1) {
prefsClone.put(s, s1);
recordChange(s);
return this;
}
@Override
public synchronized Editor putStringSet(String s, Set<String> set) {
Set<String> clonedSet = new HashSet<>();
clonedSet.addAll(set);
prefsClone.put(s, clonedSet);
recordChange(s);
return this;
}
@Override
public synchronized Editor putInt(String s, int i) {
prefsClone.put(s, i);
recordChange(s);
return this;
}
@Override
public synchronized Editor putLong(String s, long l) {
prefsClone.put(s, l);
recordChange(s);
return this;
}
@Override
public synchronized Editor putFloat(String s, float v) {
prefsClone.put(s, v);
recordChange(s);
return this;
}
@Override
public synchronized Editor putBoolean(String s, boolean b) {
prefsClone.put(s, b);
recordChange(s);
return this;
}
@Override
public synchronized Editor remove(String s) {
prefsClone.remove(s);
recordChange(s);
return this;
}
@Override
public synchronized Editor clear() {
prefsClone.keySet().forEach(this::recordChange);
prefsClone.clear();
return this;
}
@Override
public synchronized boolean commit() {
synchronized (JsonSharedPreferences.this) {
//Backup prefs
Map<String, Object> oldPrefs = prefs;
prefs = prefsClone;
if(file != null) {
//Delete old on-disk copy of preferences
if(file.exists()) {
file.delete();
}
//Save new preferences to disk
String string = saveToString();
try (PrintWriter writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)))) {
writer.print(string);
} catch (IOException e) {
logger.error("Failed to save shared prefs!", e);
prefs = oldPrefs;
return false;
}
}
//Invoke preference change listeners
for (String key : affectedKeys) {
for (OnSharedPreferenceChangeListener listener : listeners) {
listener.onSharedPreferenceChanged(JsonSharedPreferences.this, key);
}
}
affectedKeys.clear();
return true;
}
}
@Override
public void apply() {
//TODO Threading?
commit();
}
}
public File getFile() {
return file;
}
public void setFile(File file) {
this.file = file;
}
/** Preference types (for storing what type of object a preference is)**/
private enum PrefType {
String,
StringSet,
Int,
Long,
Float,
Boolean,
Null;
public static PrefType fromObject(Object object) {
if (object == null) {
return Null;
}
if (object instanceof String) {
return String;
} else if (object instanceof Set || Set.class.isAssignableFrom(object.getClass())) {
return StringSet;
} else if (object instanceof Integer) {
return Int;
} else if (object instanceof Long) {
return Long;
} else if (object instanceof Float) {
return Float;
} else if (object instanceof Boolean) {
return Boolean;
}
throw new IllegalArgumentException("Could not find type of object: " + object);
}
}
}

View File

@@ -0,0 +1,93 @@
package xyz.nulldev.androidcompat.pm
import android.content.pm.PackageInfo
import android.content.pm.Signature
import android.os.Bundle
import com.android.apksig.ApkVerifier
import com.googlecode.d2j.dex.Dex2jar
import net.dongliu.apk.parser.ApkFile
import net.dongliu.apk.parser.ApkParsers
import org.w3c.dom.Element
import org.w3c.dom.Node
import org.w3c.dom.NodeList
import java.io.File
import javax.imageio.ImageIO
import javax.xml.parsers.DocumentBuilderFactory
data class InstalledPackage(val root: File) {
val apk = File(root, "package.apk")
val jar = File(root, "translated.jar")
val icon = File(root, "icon.png")
val info: PackageInfo
get() = ApkParsers.getMetaInfo(apk).toPackageInfo(root, apk).also {
val parsed = ApkFile(apk)
val dbFactory = DocumentBuilderFactory.newInstance()
val dBuilder = dbFactory.newDocumentBuilder()
val doc = parsed.manifestXml.byteInputStream().use {
dBuilder.parse(it)
}
it.applicationInfo.metaData = Bundle().apply {
val appTag = doc.getElementsByTagName("application").item(0)
appTag?.childNodes?.toList()?.filter {
it.nodeType == Node.ELEMENT_NODE
}?.map {
it as Element
}?.filter {
it.tagName == "meta-data"
}?.map {
putString(it.attributes.getNamedItem("android:name").nodeValue,
it.attributes.getNamedItem("android:value").nodeValue)
}
}
it.signatures = (parsed.apkSingers.flatMap { it.certificateMetas }
/*+ parsed.apkV2Singers.flatMap { it.certificateMetas }*/) // Blocked by: https://github.com/hsiafan/apk-parser/issues/72
.map { Signature(it.data) }.toTypedArray()
}
fun verify(): Boolean {
val res = ApkVerifier.Builder(apk)
.build()
.verify()
return res.isVerified
}
fun writeIcon() {
try {
val icons = ApkFile(apk).allIcons
val read = icons.filter { it.isFile }.map {
it.data.inputStream().use {
ImageIO.read(it)
}
}.sortedByDescending { it.width * it.height }.firstOrNull() ?: return
ImageIO.write(read, "png", icon)
} catch(e: Exception) {
icon.delete()
}
}
fun writeJar() {
try {
Dex2jar.from(apk).to(jar.toPath())
} catch(e: Exception) {
jar.delete()
}
}
private fun NodeList.toList(): List<Node> {
val out = mutableListOf<Node>()
for(i in 0 until length)
out += item(i)
return out
}
}

View File

@@ -0,0 +1,87 @@
package xyz.nulldev.androidcompat.pm
import net.dongliu.apk.parser.ApkParsers
import org.kodein.di.DI
import org.kodein.di.conf.global
import org.kodein.di.instance
import xyz.nulldev.androidcompat.io.AndroidFiles
import java.io.File
class PackageController {
private val androidFiles by DI.global.instance<AndroidFiles>()
private val uninstallListeners = mutableListOf<(String) -> Unit>()
fun registerUninstallListener(listener: (String) -> Unit) {
uninstallListeners.add(listener)
}
fun unregisterUninstallListener(listener: (String) -> Unit) {
uninstallListeners.remove(listener)
}
private fun findRoot(apk: File): File {
val pn = ApkParsers.getMetaInfo(apk).packageName
return File(androidFiles.packagesDir, pn)
}
fun installPackage(apk: File, allowReinstall: Boolean) {
val root = findRoot(apk)
if (root.exists()) {
if (!allowReinstall) {
throw IllegalStateException("Package already installed!")
} else {
// TODO Compare past and new signature
root.deleteRecursively()
}
}
try {
root.mkdirs()
val installed = InstalledPackage(root)
apk.copyTo(installed.apk)
installed.writeIcon()
installed.writeJar()
if (!installed.jar.exists()) {
throw IllegalStateException("Failed to translate APK dex!")
}
} catch(t: Throwable) {
root.deleteRecursively()
throw t
}
}
fun listInstalled(): List<InstalledPackage> {
return androidFiles.packagesDir.listFiles().orEmpty().filter {
it.isDirectory
}.map {
InstalledPackage(it)
}
}
fun deletePackage(pack: InstalledPackage) {
if(!pack.root.exists()) error("Package was never installed!")
val packageName = pack.info.packageName
pack.root.deleteRecursively()
uninstallListeners.forEach {
it(packageName)
}
}
fun findPackage(packageName: String): InstalledPackage? {
val file = File(androidFiles.packagesDir, packageName)
return if(file.exists())
InstalledPackage(file)
else
null
}
fun findJarFromApk(apkFile: File): File? {
val pkgName = ApkParsers.getMetaInfo(apkFile).packageName
return findPackage(pkgName)?.jar
}
}

View File

@@ -0,0 +1,27 @@
package xyz.nulldev.androidcompat.pm
import android.content.pm.ApplicationInfo
import android.content.pm.FeatureInfo
import android.content.pm.PackageInfo
import net.dongliu.apk.parser.bean.ApkMeta
import java.io.File
fun ApkMeta.toPackageInfo(root: File, apk: File): PackageInfo {
return PackageInfo().also {
it.packageName = packageName
it.versionCode = versionCode.toInt()
it.versionName = versionName
it.reqFeatures = usesFeatures.map {
FeatureInfo().apply {
name = it.name
}
}.toTypedArray()
it.applicationInfo = ApplicationInfo().apply {
packageName = it.packageName
nonLocalizedLabel = label
sourceDir = apk.absolutePath
}
}
}

View File

@@ -0,0 +1,27 @@
package xyz.nulldev.androidcompat.res;
import xyz.nulldev.androidcompat.info.ApplicationInfoImpl;
import xyz.nulldev.androidcompat.util.KodeinGlobalHelper;
import java.text.SimpleDateFormat;
import java.util.Calendar;
/**
* BuildConfig compat class.
*/
public class BuildConfigCompat {
private static ApplicationInfoImpl applicationInfo = KodeinGlobalHelper.instance(ApplicationInfoImpl.class);
public static final boolean DEBUG = applicationInfo.getDebug();
//We assume application ID = package name
public static final String APPLICATION_ID = applicationInfo.packageName;
//TODO Build time is hardcoded currently
public static final String BUILD_TIME;
static {
Calendar cal = Calendar.getInstance();
cal.set(2000, Calendar.JANUARY, 1);
BUILD_TIME = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'").format(cal.getTime());
}
}

View File

@@ -0,0 +1,7 @@
package xyz.nulldev.androidcompat.res
class DrawableResource(val location: String) : Resource {
override fun getType() = DrawableResource::class.java
override fun getValue() = javaClass.getResourceAsStream(location)
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright 2016 Andy Bao
*
* 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 xyz.nulldev.androidcompat.res;
import java.util.HashMap;
import java.util.Map;
/**
* Custom implementation of R.java.
*/
public class RCompat {
//Resources stored in memory
private static Map<Integer, Resource> resources = new HashMap<>();
//The last used resource ID, used to generate new resource IDs
private static int lastRes = 0;
/**
* Add a new String resource.
* @param s The value of the String resource.
* @return The added String resource.
*/
public static int sres(String s) {
return res(new StringResource(s));
}
public static int dres(String s) {
return res(new DrawableResource(s));
}
/**
* Add a resource.
* @param res The resource to add.
* @return The ID of the added resource.
*/
public static int res(Resource res) {
int nextRes = lastRes++;
resources.put(nextRes, res);
return nextRes;
}
/**
* Get a string resource
* @param id The id of the resource
* @return The string resource
*/
public static String getString(int id) {
return cast(resources.get(id), StringResource.class).getValue();
}
/**
* Convenience method for casting resources.
* @param resource The resource to cast
* @param output The class of the output resource type
* @param <T> The type of the output resource
* @return The casted resource
*/
private static <T extends Resource> T cast(Resource resource, Class<T> output) {
if(resource.getType().equals(output)) {
return (T) resource;
} else {
throw new IllegalArgumentException("This resource is not of type: " + output.getSimpleName() + "!");
}
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright 2016 Andy Bao
*
* 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 xyz.nulldev.androidcompat.res
/**
* A static Android resource.
*/
interface Resource {
fun getType(): Class<out Resource>
fun getValue(): Any?
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2016 Andy Bao
*
* 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 xyz.nulldev.androidcompat.res
/**
* String resource.
*/
class StringResource(val string: String) : Resource {
override fun getValue() = string
override fun getType() = StringResource::class.java
}

View File

@@ -0,0 +1,68 @@
package xyz.nulldev.androidcompat.service
import android.app.Service
import android.content.Context
import android.content.Intent
import mu.KotlinLogging
import java.util.concurrent.ConcurrentHashMap
import kotlin.concurrent.thread
/**
* Service emulation class
*
* TODO Possibly handle starting services via bindService
*/
class ServiceSupport {
val runningServices = ConcurrentHashMap<String, Service>()
private val logger = KotlinLogging.logger {}
fun startService(context: Context, intent: Intent) {
val name = intentToClassName(intent)
logger.debug { "Starting service: $name" }
val service = serviceInstanceFromClass(name)
runningServices[name] = service
//Setup service
thread {
callOnCreate(service)
//TODO Handle more complex cases
service.onStartCommand(intent, 0, 0)
}
}
fun stopService(context: Context, intent: Intent) {
val name = intentToClassName(intent)
stopService(name)
}
fun stopService(name: String) {
logger.debug { "Stopping service: $name" }
val service = runningServices.remove(name)
if(service == null) {
logger.warn { "An attempt was made to stop a service that is not running: $name" }
} else {
thread {
service.onDestroy()
}
}
}
fun stopSelf(service: Service) {
stopService(service.javaClass.name)
}
fun callOnCreate(service: Service) = service.onCreate()
fun intentToClassName(intent: Intent) = intent.component.className!!
fun serviceInstanceFromClass(className: String): Service {
val clazzObj = Class.forName(className)
return clazzObj.getDeclaredConstructor().newInstance() as? Service
?: throw IllegalArgumentException("$className is not a Service!")
}
}

View File

@@ -0,0 +1,67 @@
package xyz.nulldev.androidcompat.util
import android.content.Context
import org.kodein.di.DI
import org.kodein.di.conf.global
import org.kodein.di.instance
import xyz.nulldev.androidcompat.androidimpl.CustomContext
import xyz.nulldev.androidcompat.androidimpl.FakePackageManager
import xyz.nulldev.androidcompat.info.ApplicationInfoImpl
import xyz.nulldev.androidcompat.io.AndroidFiles
import xyz.nulldev.androidcompat.pm.PackageController
import xyz.nulldev.androidcompat.service.ServiceSupport
/**
* Helper class to allow access to Kodein from Java
*/
object KodeinGlobalHelper {
/**
* Get the Kodein object
*/
@JvmStatic
fun kodein() = DI.global
/**
* Get a dependency
*/
@JvmStatic
fun <T : Any> instance(type: Class<T>, kodein: DI? = null): T {
return when(type) {
AndroidFiles::class.java -> {
val instance: AndroidFiles by (kodein ?: kodein()).instance()
instance as T
}
ApplicationInfoImpl::class.java -> {
val instance: ApplicationInfoImpl by (kodein ?: kodein()).instance()
instance as T
}
ServiceSupport::class.java -> {
val instance: ServiceSupport by (kodein ?: kodein()).instance()
instance as T
}
FakePackageManager::class.java -> {
val instance: FakePackageManager by (kodein ?: kodein()).instance()
instance as T
}
PackageController::class.java -> {
val instance: PackageController by (kodein ?: kodein()).instance()
instance as T
}
CustomContext::class.java -> {
val instance: CustomContext by (kodein ?: kodein()).instance()
instance as T
}
Context::class.java -> {
val instance: Context by (kodein ?: kodein()).instance()
instance as T
}
else -> throw IllegalArgumentException("Kodein instance not found")
}
}
@JvmStatic
fun <T : Any> instance(type: Class<T>): T {
return instance(type, null)
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2016 Andy Bao
*
* 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 xyz.nulldev.androidcompat.util
import android.net.Uri
import java.io.File
import java.net.URI
/**
* Utilites to convert between Java and Android Uris.
*/
fun Uri.java() = URI(this.toString())
fun Uri.file() = File(this.path)
fun URI.android() = Uri.parse(this.toString())!!