package de.robv.android.xposed.installer; import android.annotation.SuppressLint; import android.app.Activity; import android.app.Application; import android.app.Application.ActivityLifecycleCallbacks; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.os.FileUtils; import android.os.Handler; import android.preference.PreferenceManager; import android.util.Log; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import de.robv.android.xposed.installer.util.AssetUtil; import de.robv.android.xposed.installer.util.DownloadsUtil; import de.robv.android.xposed.installer.util.InstallZipUtil; import de.robv.android.xposed.installer.util.InstallZipUtil.XposedProp; import de.robv.android.xposed.installer.util.NotificationUtil; import de.robv.android.xposed.installer.util.RepoLoader; public class XposedApp extends Application implements ActivityLifecycleCallbacks { public static final String TAG = "XposedInstaller"; @SuppressLint("SdCardPath") public static final String BASE_DIR = "/data/data/de.robv.android.xposed.installer/"; public static final String ENABLED_MODULES_LIST_FILE = XposedApp.BASE_DIR + "conf/enabled_modules.list"; private static final String[] XPOSED_PROP_FILES = new String[]{ "/su/xposed/xposed.prop", // official systemless "/system/xposed.prop", // classical }; public static int WRITE_EXTERNAL_PERMISSION = 69; private static XposedApp mInstance = null; private static Thread mUiThread; private static Handler mMainHandler; private boolean mIsUiLoaded = false; private SharedPreferences mPref; private XposedProp mXposedProp; public static XposedApp getInstance() { return mInstance; } public static void runOnUiThread(Runnable action) { if (Thread.currentThread() != mUiThread) { mMainHandler.post(action); } else { action.run(); } } public static void postOnUiThread(Runnable action) { mMainHandler.post(action); } // This method is hooked by XposedBridge to return the current version public static int getActiveXposedVersion() { return -1; } public static int getInstalledXposedVersion() { XposedProp prop = getXposedProp(); return prop != null ? prop.getVersionInt() : -1; } public static XposedProp getXposedProp() { synchronized (mInstance) { return mInstance.mXposedProp; } } public static SharedPreferences getPreferences() { return mInstance.mPref; } public static void installApk(Context context, DownloadsUtil.DownloadInfo info) { Intent installIntent = new Intent(Intent.ACTION_INSTALL_PACKAGE); installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); installIntent.setDataAndType(Uri.fromFile(new File(info.localFilename)), DownloadsUtil.MIME_TYPE_APK); installIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, context.getApplicationInfo().packageName); context.startActivity(installIntent); } public static String getDownloadPath() { return getPreferences().getString("download_location", Environment.getExternalStorageDirectory() + "/XposedInstaller"); } public void onCreate() { super.onCreate(); mInstance = this; mUiThread = Thread.currentThread(); mMainHandler = new Handler(); mPref = PreferenceManager.getDefaultSharedPreferences(this); reloadXposedProp(); createDirectories(); NotificationUtil.init(); AssetUtil.removeBusybox(); registerActivityLifecycleCallbacks(this); } private void createDirectories() { mkdirAndChmod("bin", 00771); mkdirAndChmod("conf", 00771); mkdirAndChmod("log", 00777); } private void mkdirAndChmod(String dir, int permissions) { dir = BASE_DIR + dir; new File(dir).mkdir(); FileUtils.setPermissions(dir, permissions, -1, -1); } public void reloadXposedProp() { XposedProp prop = null; for (String path : XPOSED_PROP_FILES) { File file = new File(path); if (file.canRead()) { FileInputStream is = null; try { is = new FileInputStream(file); prop = InstallZipUtil.parseXposedProp(is); break; } catch (IOException e) { Log.e(XposedApp.TAG, "Could not read " + file.getPath(), e); } finally { if (is != null) { try { is.close(); } catch (IOException ignored) { } } } } } synchronized (this) { mXposedProp = prop; } } // TODO find a better way to trigger actions only when any UI is shown for the first time @Override public synchronized void onActivityCreated(Activity activity, Bundle savedInstanceState) { if (mIsUiLoaded) return; RepoLoader.getInstance().triggerFirstLoadIfNecessary(); mIsUiLoaded = true; } @Override public synchronized void onActivityResumed(Activity activity) { } @Override public synchronized void onActivityPaused(Activity activity) { } @Override public void onActivityStarted(Activity activity) { } @Override public void onActivityStopped(Activity activity) { } @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) { } @Override public void onActivityDestroyed(Activity activity) { } }