diff --git a/.gitignore b/.gitignore index 60d30a20..a1c87b21 100644 --- a/.gitignore +++ b/.gitignore @@ -1,41 +1,12 @@ -# built application files -*.apk -*.ap_ - -# files for the dex VM -*.dex - -# Java class files -*.class - -# generated files -bin/ -gen/ - -# Local configuration file (sdk path, etc) -local.properties - -# Eclipse project files -.classpath -.project -.settings - -# IntelliJ IDEA project files -.idea -*.iml -*.eml -classes - -# Command line -build.xml - -# Mac specific +.settings/org.eclipse.jdt.core.prefs +*/.settings/org.eclipse.jdt.core.prefs +**/bin/* +**/gen/* +**/build/* +**/.idea/* +**/*.iml +.gradle +/local.properties +/.idea/workspace.xml .DS_Store - -# Sonar -.sonar - -# ProGuard -proguard_logs/dump.txt -proguard_logs/seeds.txt -proguard_logs/usage.txt +.metadata/* \ No newline at end of file diff --git a/DevDrawer/build.gradle b/DevDrawer/build.gradle new file mode 100644 index 00000000..496499d8 --- /dev/null +++ b/DevDrawer/build.gradle @@ -0,0 +1,27 @@ +repositories { + mavenCentral() +} +apply plugin: 'com.android.application' + +android { + compileSdkVersion 26 + buildToolsVersion "26.0.2" + + defaultConfig { + applicationId "com.owentech.DevDrawer" + minSdkVersion 7 + targetSdkVersion 19 + versionCode 16 + versionName "1.6.1" + } +} + +dependencies { + + // You must install or update the Support Repository through the SDK manager to use this dependency. + // The Support Repository (separate from the corresponding library) can be found in the Extras category. + // compile 'com.android.support:appcompat-v7:18.0.0' + + compile project(':libraries:libsuperuser') + compile 'com.google.code.gson:gson:2.2.4' +} diff --git a/libs/gson-2.2.2.jar b/DevDrawer/libs/gson-2.2.2.jar similarity index 100% rename from libs/gson-2.2.2.jar rename to DevDrawer/libs/gson-2.2.2.jar diff --git a/AndroidManifest.xml b/DevDrawer/src/main/AndroidManifest.xml old mode 100755 new mode 100644 similarity index 95% rename from AndroidManifest.xml rename to DevDrawer/src/main/AndroidManifest.xml index 3b40212f..3df2ab0f --- a/AndroidManifest.xml +++ b/DevDrawer/src/main/AndroidManifest.xml @@ -1,9 +1,6 @@ - + package="com.owentech.DevDrawer"> diff --git a/src/android/app/ActivityManagerNative.java b/DevDrawer/src/main/java/android/app/ActivityManagerNative.java similarity index 100% rename from src/android/app/ActivityManagerNative.java rename to DevDrawer/src/main/java/android/app/ActivityManagerNative.java diff --git a/src/android/app/IActivityManager.java b/DevDrawer/src/main/java/android/app/IActivityManager.java similarity index 100% rename from src/android/app/IActivityManager.java rename to DevDrawer/src/main/java/android/app/IActivityManager.java diff --git a/src/com/owentech/DevDrawer/activities/ChooseActivityDialog.java b/DevDrawer/src/main/java/com/owentech/DevDrawer/activities/ChooseActivityDialog.java similarity index 100% rename from src/com/owentech/DevDrawer/activities/ChooseActivityDialog.java rename to DevDrawer/src/main/java/com/owentech/DevDrawer/activities/ChooseActivityDialog.java diff --git a/src/com/owentech/DevDrawer/activities/ClickHandlingActivity.java b/DevDrawer/src/main/java/com/owentech/DevDrawer/activities/ClickHandlingActivity.java old mode 100755 new mode 100644 similarity index 100% rename from src/com/owentech/DevDrawer/activities/ClickHandlingActivity.java rename to DevDrawer/src/main/java/com/owentech/DevDrawer/activities/ClickHandlingActivity.java diff --git a/src/com/owentech/DevDrawer/activities/EditDialog.java b/DevDrawer/src/main/java/com/owentech/DevDrawer/activities/EditDialog.java old mode 100755 new mode 100644 similarity index 100% rename from src/com/owentech/DevDrawer/activities/EditDialog.java rename to DevDrawer/src/main/java/com/owentech/DevDrawer/activities/EditDialog.java diff --git a/src/com/owentech/DevDrawer/activities/LegacyDialog.java b/DevDrawer/src/main/java/com/owentech/DevDrawer/activities/LegacyDialog.java similarity index 100% rename from src/com/owentech/DevDrawer/activities/LegacyDialog.java rename to DevDrawer/src/main/java/com/owentech/DevDrawer/activities/LegacyDialog.java diff --git a/src/com/owentech/DevDrawer/activities/LocaleSwitcher.java b/DevDrawer/src/main/java/com/owentech/DevDrawer/activities/LocaleSwitcher.java similarity index 100% rename from src/com/owentech/DevDrawer/activities/LocaleSwitcher.java rename to DevDrawer/src/main/java/com/owentech/DevDrawer/activities/LocaleSwitcher.java diff --git a/DevDrawer/src/main/java/com/owentech/DevDrawer/activities/MainActivity.java b/DevDrawer/src/main/java/com/owentech/DevDrawer/activities/MainActivity.java new file mode 100644 index 00000000..2cb57399 --- /dev/null +++ b/DevDrawer/src/main/java/com/owentech/DevDrawer/activities/MainActivity.java @@ -0,0 +1,258 @@ +package com.owentech.DevDrawer.activities; + +import android.app.Activity; +import android.appwidget.AppWidgetManager; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.Build; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.AutoCompleteTextView; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.RemoteViews; +import android.widget.Toast; + +import com.owentech.DevDrawer.R; +import com.owentech.DevDrawer.adapters.FilterListAdapter; +import com.owentech.DevDrawer.adapters.PartialMatchAdapter; +import com.owentech.DevDrawer.appwidget.DDWidgetProvider; +import com.owentech.DevDrawer.utils.AddAllAppsAsync; +import com.owentech.DevDrawer.utils.Constants; +import com.owentech.DevDrawer.utils.Database; + +import java.text.Collator; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class MainActivity extends Activity implements TextWatcher { + + Database database; + + ImageView addButton; + AutoCompleteTextView addPackageAutoComplete; + FilterListAdapter lviewAdapter; + ListView listView; + PartialMatchAdapter partialMatchAdapter; + + List appPackages; + + @Override + public void onCreate(Bundle state) { + super.onCreate(state); + + setContentView(R.layout.main); + + // Set up ActionBar to use custom view (Robot Light font) + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB) { + getActionBar().setDisplayShowTitleEnabled(false); + LayoutInflater inflater = LayoutInflater.from(this); + View customView = inflater.inflate(R.layout.custom_ab_title, null); + getActionBar().setCustomView(customView); + getActionBar().setDisplayShowCustomEnabled(true); + } + + // Create the database tables + database = new Database(this); + database.createTables(); + + // Setup view components + addButton = (ImageView) findViewById(R.id.addButton); + addPackageAutoComplete = (AutoCompleteTextView) findViewById(R.id.addPackageEditText); + listView = (ListView) findViewById(R.id.packagesListView); + + appPackages = getExistingPackages(); + + partialMatchAdapter = new PartialMatchAdapter(this, appPackages); + addPackageAutoComplete.setAdapter(partialMatchAdapter); + addPackageAutoComplete.addTextChangedListener(this); + + // Update the ListView from the database + updateListView(); + + addButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + + if (addPackageAutoComplete.getText().length() != 0) // Check something entered + { + // Check filter doesn't exist + if (!database.doesFilterExist(addPackageAutoComplete.getText().toString())) { + // Add the filter to the database + database.addFilterToDatabase(addPackageAutoComplete.getText().toString()); + + // Check existing apps and add to installed apps table if they match new filter + new AddAllAppsAsync(getApplicationContext(), addPackageAutoComplete.getText().toString()).execute(); + + addPackageAutoComplete.setText(""); + updateListView(); + + } else { + Toast.makeText(getApplicationContext(), "Filter already exists", Toast.LENGTH_SHORT).show(); + } + } + + } + }); + + + } + + @Override + public void onBackPressed() { + + Intent intent = getIntent(); + Bundle extras = intent.getExtras(); + int appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; + if (extras != null) { + appWidgetId = extras.getInt( + AppWidgetManager.EXTRA_APPWIDGET_ID, + AppWidgetManager.INVALID_APPWIDGET_ID); + } + + if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) { + AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this); + RemoteViews widget = DDWidgetProvider.getRemoteViews(this, appWidgetId); + appWidgetManager.updateAppWidget(appWidgetId, widget); + Intent resultValue = new Intent(); + resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); + setResult(RESULT_OK, resultValue); + finish(); + } + + super.onBackPressed(); + } + + // Method to re-populate the ListView + public void updateListView() { + lviewAdapter = null; + lviewAdapter = new FilterListAdapter(this, database.getAllFiltersInDatabase()); + listView.setAdapter(lviewAdapter); + lviewAdapter.notifyDataSetChanged(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + // Catch the return from the EditDialog + if (resultCode == Constants.EDIT_DIALOG_CHANGE) { + Bundle bundle = data.getExtras(); + + Database database = new Database(this); + database.amendFilterEntryTo(bundle.getString("id"), bundle.getString("newText")); + updateListView(); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + menu.add(0, Constants.MENU_SHORTCUT, 0, "Create Legacy Shortcut").setShowAsAction(MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW); + menu.add(0, Constants.MENU_SETTINGS, 0, "Settings").setIcon(R.drawable.ic_action_settings_white).setShowAsAction(MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW); + //menu.add(0, Constants.MENU_LOCALE_SWITCHER, 0, "Locale Switcher").setIcon(R.drawable.ic_action_globe).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + } else { + menu.add(0, Constants.MENU_SHORTCUT, 0, "Create Shortcut"); + menu.add(0, Constants.MENU_SETTINGS, 0, "Settings"); + //menu.add(0, Constants.MENU_LOCALE_SWITCHER, 0, "Locale Switcher"); + } + return true; + } + + @Override + public boolean onMenuItemSelected(int featureId, MenuItem item) { + switch (item.getItemId()) { + case Constants.MENU_SHORTCUT: { + addShortcut(this); + break; + } + case Constants.MENU_SETTINGS: { + startActivity(new Intent(MainActivity.this, PrefActivity.class)); + break; + } + case Constants.MENU_LOCALE_SWITCHER: { + + startActivity(new Intent(this, LocaleSwitcher.class)); + break; + } + } + return false; + } + + public void addShortcut(Context context) { + Intent shortcutIntent = new Intent(this, LegacyDialog.class); + shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + + Intent intent = new Intent(); + intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); + intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, "^DevDrawer"); + intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(context, R.drawable.shortcut_icon)); + intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); + String shortcutUri = intent.toUri(MODE_WORLD_WRITEABLE); + context.sendBroadcast(intent); + } + + @Override + protected void onStop() { + super.onStop(); + //TODO is this really needed? It makes the prefActivity to close the app on backpress + // this is called to prevent a new app, back pressed, opening this activity + finish(); + } + + // Method to get all apps installed and return as List + List getExistingPackages() { + // get installed applications + PackageManager pm = this.getPackageManager(); + Intent intent = new Intent(Intent.ACTION_MAIN, null); + intent.addCategory(Intent.CATEGORY_LAUNCHER); + List list = pm.queryIntentActivities(intent, + PackageManager.PERMISSION_GRANTED); + + Set appSet = new HashSet(); + + for (ResolveInfo rInfo : list) { + String appName = rInfo.activityInfo.applicationInfo.packageName.toString(); + appSet.add(appName); + while (appName.length() > 0) { + int lastIndex = appName.lastIndexOf("."); + if (lastIndex > 0) { + appName = appName.substring(0, lastIndex); + appSet.add(appName + ".*"); + } else { + appName = ""; + } + } + } + + Collator collator = Collator.getInstance(); + ArrayList appList = new ArrayList(appSet); + Collections.sort(appList, collator); + return appList; + } + + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { + } + + @Override + public void afterTextChanged(Editable editable) { + partialMatchAdapter.getFilter().filter(editable.toString()); + } + +} diff --git a/src/com/owentech/DevDrawer/activities/PrefActivity.java b/DevDrawer/src/main/java/com/owentech/DevDrawer/activities/PrefActivity.java similarity index 100% rename from src/com/owentech/DevDrawer/activities/PrefActivity.java rename to DevDrawer/src/main/java/com/owentech/DevDrawer/activities/PrefActivity.java diff --git a/src/com/owentech/DevDrawer/adapters/ActivityListAdapter.java b/DevDrawer/src/main/java/com/owentech/DevDrawer/adapters/ActivityListAdapter.java similarity index 100% rename from src/com/owentech/DevDrawer/adapters/ActivityListAdapter.java rename to DevDrawer/src/main/java/com/owentech/DevDrawer/adapters/ActivityListAdapter.java diff --git a/src/com/owentech/DevDrawer/adapters/FilterListAdapter.java b/DevDrawer/src/main/java/com/owentech/DevDrawer/adapters/FilterListAdapter.java old mode 100755 new mode 100644 similarity index 100% rename from src/com/owentech/DevDrawer/adapters/FilterListAdapter.java rename to DevDrawer/src/main/java/com/owentech/DevDrawer/adapters/FilterListAdapter.java diff --git a/src/com/owentech/DevDrawer/adapters/LegacyListAdapter.java b/DevDrawer/src/main/java/com/owentech/DevDrawer/adapters/LegacyListAdapter.java similarity index 100% rename from src/com/owentech/DevDrawer/adapters/LegacyListAdapter.java rename to DevDrawer/src/main/java/com/owentech/DevDrawer/adapters/LegacyListAdapter.java diff --git a/src/com/owentech/DevDrawer/adapters/LocaleListAdapter.java b/DevDrawer/src/main/java/com/owentech/DevDrawer/adapters/LocaleListAdapter.java similarity index 100% rename from src/com/owentech/DevDrawer/adapters/LocaleListAdapter.java rename to DevDrawer/src/main/java/com/owentech/DevDrawer/adapters/LocaleListAdapter.java diff --git a/src/com/owentech/DevDrawer/adapters/PartialMatchAdapter.java b/DevDrawer/src/main/java/com/owentech/DevDrawer/adapters/PartialMatchAdapter.java similarity index 100% rename from src/com/owentech/DevDrawer/adapters/PartialMatchAdapter.java rename to DevDrawer/src/main/java/com/owentech/DevDrawer/adapters/PartialMatchAdapter.java diff --git a/src/com/owentech/DevDrawer/appwidget/DDWidgetProvider.java b/DevDrawer/src/main/java/com/owentech/DevDrawer/appwidget/DDWidgetProvider.java old mode 100755 new mode 100644 similarity index 100% rename from src/com/owentech/DevDrawer/appwidget/DDWidgetProvider.java rename to DevDrawer/src/main/java/com/owentech/DevDrawer/appwidget/DDWidgetProvider.java diff --git a/src/com/owentech/DevDrawer/appwidget/DDWidgetService.java b/DevDrawer/src/main/java/com/owentech/DevDrawer/appwidget/DDWidgetService.java old mode 100755 new mode 100644 similarity index 100% rename from src/com/owentech/DevDrawer/appwidget/DDWidgetService.java rename to DevDrawer/src/main/java/com/owentech/DevDrawer/appwidget/DDWidgetService.java diff --git a/src/com/owentech/DevDrawer/appwidget/DDWidgetViewsFactory.java b/DevDrawer/src/main/java/com/owentech/DevDrawer/appwidget/DDWidgetViewsFactory.java old mode 100755 new mode 100644 similarity index 93% rename from src/com/owentech/DevDrawer/appwidget/DDWidgetViewsFactory.java rename to DevDrawer/src/main/java/com/owentech/DevDrawer/appwidget/DDWidgetViewsFactory.java index 461d6a54..3c720080 --- a/src/com/owentech/DevDrawer/appwidget/DDWidgetViewsFactory.java +++ b/DevDrawer/src/main/java/com/owentech/DevDrawer/appwidget/DDWidgetViewsFactory.java @@ -8,6 +8,7 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.graphics.Bitmap; +import android.graphics.Canvas; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; @@ -212,9 +213,21 @@ public void getApps() } + private Bitmap getBitmapFromDrawable(Drawable drawable) { + final Bitmap bmp = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(bmp); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + return bmp; + } + // Method to return a bitmap from drawable public Bitmap convertFromDrawable(Drawable d) { - return ((BitmapDrawable)d).getBitmap(); + if (d instanceof BitmapDrawable) { + return ((BitmapDrawable) d).getBitmap(); + } else { + return getBitmapFromDrawable(d); + } } } diff --git a/src/com/owentech/DevDrawer/receivers/AppInstalledReceiver.java b/DevDrawer/src/main/java/com/owentech/DevDrawer/receivers/AppInstalledReceiver.java old mode 100755 new mode 100644 similarity index 100% rename from src/com/owentech/DevDrawer/receivers/AppInstalledReceiver.java rename to DevDrawer/src/main/java/com/owentech/DevDrawer/receivers/AppInstalledReceiver.java diff --git a/src/com/owentech/DevDrawer/receivers/AppUninstalledReceiver.java b/DevDrawer/src/main/java/com/owentech/DevDrawer/receivers/AppUninstalledReceiver.java old mode 100755 new mode 100644 similarity index 100% rename from src/com/owentech/DevDrawer/receivers/AppUninstalledReceiver.java rename to DevDrawer/src/main/java/com/owentech/DevDrawer/receivers/AppUninstalledReceiver.java diff --git a/src/com/owentech/DevDrawer/utils/AddAllAppsAsync.java b/DevDrawer/src/main/java/com/owentech/DevDrawer/utils/AddAllAppsAsync.java similarity index 100% rename from src/com/owentech/DevDrawer/utils/AddAllAppsAsync.java rename to DevDrawer/src/main/java/com/owentech/DevDrawer/utils/AddAllAppsAsync.java diff --git a/src/com/owentech/DevDrawer/utils/Constants.java b/DevDrawer/src/main/java/com/owentech/DevDrawer/utils/Constants.java old mode 100755 new mode 100644 similarity index 100% rename from src/com/owentech/DevDrawer/utils/Constants.java rename to DevDrawer/src/main/java/com/owentech/DevDrawer/utils/Constants.java diff --git a/src/com/owentech/DevDrawer/utils/Database.java b/DevDrawer/src/main/java/com/owentech/DevDrawer/utils/Database.java old mode 100755 new mode 100644 similarity index 100% rename from src/com/owentech/DevDrawer/utils/Database.java rename to DevDrawer/src/main/java/com/owentech/DevDrawer/utils/Database.java diff --git a/src/com/owentech/DevDrawer/utils/PackageCollection.java b/DevDrawer/src/main/java/com/owentech/DevDrawer/utils/PackageCollection.java old mode 100755 new mode 100644 similarity index 100% rename from src/com/owentech/DevDrawer/utils/PackageCollection.java rename to DevDrawer/src/main/java/com/owentech/DevDrawer/utils/PackageCollection.java diff --git a/src/com/owentech/DevDrawer/utils/RootFeatures.java b/DevDrawer/src/main/java/com/owentech/DevDrawer/utils/RootFeatures.java similarity index 100% rename from src/com/owentech/DevDrawer/utils/RootFeatures.java rename to DevDrawer/src/main/java/com/owentech/DevDrawer/utils/RootFeatures.java diff --git a/src/com/owentech/DevDrawer/utils/RootLocaleSwitcher.java b/DevDrawer/src/main/java/com/owentech/DevDrawer/utils/RootLocaleSwitcher.java similarity index 100% rename from src/com/owentech/DevDrawer/utils/RootLocaleSwitcher.java rename to DevDrawer/src/main/java/com/owentech/DevDrawer/utils/RootLocaleSwitcher.java diff --git a/res/drawable-hdpi/btn_default_disabled_focused_holo_light.9.png b/DevDrawer/src/main/res/drawable-hdpi/btn_default_disabled_focused_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-hdpi/btn_default_disabled_focused_holo_light.9.png rename to DevDrawer/src/main/res/drawable-hdpi/btn_default_disabled_focused_holo_light.9.png diff --git a/res/drawable-hdpi/btn_default_disabled_holo_light.9.png b/DevDrawer/src/main/res/drawable-hdpi/btn_default_disabled_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-hdpi/btn_default_disabled_holo_light.9.png rename to DevDrawer/src/main/res/drawable-hdpi/btn_default_disabled_holo_light.9.png diff --git a/res/drawable-hdpi/btn_default_focused_holo_light.9.png b/DevDrawer/src/main/res/drawable-hdpi/btn_default_focused_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-hdpi/btn_default_focused_holo_light.9.png rename to DevDrawer/src/main/res/drawable-hdpi/btn_default_focused_holo_light.9.png diff --git a/res/drawable-hdpi/btn_default_normal_holo_light.9.png b/DevDrawer/src/main/res/drawable-hdpi/btn_default_normal_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-hdpi/btn_default_normal_holo_light.9.png rename to DevDrawer/src/main/res/drawable-hdpi/btn_default_normal_holo_light.9.png diff --git a/res/drawable-hdpi/btn_default_pressed_holo_light.9.png b/DevDrawer/src/main/res/drawable-hdpi/btn_default_pressed_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-hdpi/btn_default_pressed_holo_light.9.png rename to DevDrawer/src/main/res/drawable-hdpi/btn_default_pressed_holo_light.9.png diff --git a/res/drawable-hdpi/ic_action_add.png b/DevDrawer/src/main/res/drawable-hdpi/ic_action_add.png similarity index 100% rename from res/drawable-hdpi/ic_action_add.png rename to DevDrawer/src/main/res/drawable-hdpi/ic_action_add.png diff --git a/res/drawable-hdpi/ic_action_add_grey.png b/DevDrawer/src/main/res/drawable-hdpi/ic_action_add_grey.png similarity index 100% rename from res/drawable-hdpi/ic_action_add_grey.png rename to DevDrawer/src/main/res/drawable-hdpi/ic_action_add_grey.png diff --git a/res/drawable-hdpi/ic_action_clear.png b/DevDrawer/src/main/res/drawable-hdpi/ic_action_clear.png similarity index 100% rename from res/drawable-hdpi/ic_action_clear.png rename to DevDrawer/src/main/res/drawable-hdpi/ic_action_clear.png diff --git a/res/drawable-hdpi/ic_action_clear_grey.png b/DevDrawer/src/main/res/drawable-hdpi/ic_action_clear_grey.png similarity index 100% rename from res/drawable-hdpi/ic_action_clear_grey.png rename to DevDrawer/src/main/res/drawable-hdpi/ic_action_clear_grey.png diff --git a/res/drawable-hdpi/ic_action_clear_white.png b/DevDrawer/src/main/res/drawable-hdpi/ic_action_clear_white.png similarity index 100% rename from res/drawable-hdpi/ic_action_clear_white.png rename to DevDrawer/src/main/res/drawable-hdpi/ic_action_clear_white.png diff --git a/res/drawable-hdpi/ic_action_edit.png b/DevDrawer/src/main/res/drawable-hdpi/ic_action_edit.png similarity index 100% rename from res/drawable-hdpi/ic_action_edit.png rename to DevDrawer/src/main/res/drawable-hdpi/ic_action_edit.png diff --git a/res/drawable-hdpi/ic_action_edit_grey.png b/DevDrawer/src/main/res/drawable-hdpi/ic_action_edit_grey.png similarity index 100% rename from res/drawable-hdpi/ic_action_edit_grey.png rename to DevDrawer/src/main/res/drawable-hdpi/ic_action_edit_grey.png diff --git a/res/drawable-hdpi/ic_action_globe.png b/DevDrawer/src/main/res/drawable-hdpi/ic_action_globe.png similarity index 100% rename from res/drawable-hdpi/ic_action_globe.png rename to DevDrawer/src/main/res/drawable-hdpi/ic_action_globe.png diff --git a/res/drawable-hdpi/ic_action_link.png b/DevDrawer/src/main/res/drawable-hdpi/ic_action_link.png similarity index 100% rename from res/drawable-hdpi/ic_action_link.png rename to DevDrawer/src/main/res/drawable-hdpi/ic_action_link.png diff --git a/res/drawable-hdpi/ic_action_more.png b/DevDrawer/src/main/res/drawable-hdpi/ic_action_more.png similarity index 100% rename from res/drawable-hdpi/ic_action_more.png rename to DevDrawer/src/main/res/drawable-hdpi/ic_action_more.png diff --git a/res/drawable-hdpi/ic_action_more_grey.png b/DevDrawer/src/main/res/drawable-hdpi/ic_action_more_grey.png similarity index 100% rename from res/drawable-hdpi/ic_action_more_grey.png rename to DevDrawer/src/main/res/drawable-hdpi/ic_action_more_grey.png diff --git a/res/drawable-hdpi/ic_action_more_white.png b/DevDrawer/src/main/res/drawable-hdpi/ic_action_more_white.png similarity index 100% rename from res/drawable-hdpi/ic_action_more_white.png rename to DevDrawer/src/main/res/drawable-hdpi/ic_action_more_white.png diff --git a/res/drawable-hdpi/ic_action_settings.png b/DevDrawer/src/main/res/drawable-hdpi/ic_action_settings.png similarity index 100% rename from res/drawable-hdpi/ic_action_settings.png rename to DevDrawer/src/main/res/drawable-hdpi/ic_action_settings.png diff --git a/res/drawable-hdpi/ic_action_settings_grey.png b/DevDrawer/src/main/res/drawable-hdpi/ic_action_settings_grey.png similarity index 100% rename from res/drawable-hdpi/ic_action_settings_grey.png rename to DevDrawer/src/main/res/drawable-hdpi/ic_action_settings_grey.png diff --git a/res/drawable-hdpi/ic_action_settings_white.png b/DevDrawer/src/main/res/drawable-hdpi/ic_action_settings_white.png similarity index 100% rename from res/drawable-hdpi/ic_action_settings_white.png rename to DevDrawer/src/main/res/drawable-hdpi/ic_action_settings_white.png diff --git a/res/drawable-hdpi/ic_action_trash.png b/DevDrawer/src/main/res/drawable-hdpi/ic_action_trash.png similarity index 100% rename from res/drawable-hdpi/ic_action_trash.png rename to DevDrawer/src/main/res/drawable-hdpi/ic_action_trash.png diff --git a/res/drawable-hdpi/ic_action_trash_grey.png b/DevDrawer/src/main/res/drawable-hdpi/ic_action_trash_grey.png similarity index 100% rename from res/drawable-hdpi/ic_action_trash_grey.png rename to DevDrawer/src/main/res/drawable-hdpi/ic_action_trash_grey.png diff --git a/res/drawable-hdpi/ic_action_trash_white.png b/DevDrawer/src/main/res/drawable-hdpi/ic_action_trash_white.png similarity index 100% rename from res/drawable-hdpi/ic_action_trash_white.png rename to DevDrawer/src/main/res/drawable-hdpi/ic_action_trash_white.png diff --git a/res/drawable-hdpi/ic_launcher.png b/DevDrawer/src/main/res/drawable-hdpi/ic_launcher.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-hdpi/ic_launcher.png rename to DevDrawer/src/main/res/drawable-hdpi/ic_launcher.png diff --git a/res/drawable-hdpi/list_activated_holo.9.png b/DevDrawer/src/main/res/drawable-hdpi/list_activated_holo.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-hdpi/list_activated_holo.9.png rename to DevDrawer/src/main/res/drawable-hdpi/list_activated_holo.9.png diff --git a/res/drawable-hdpi/list_focused_holo.9.png b/DevDrawer/src/main/res/drawable-hdpi/list_focused_holo.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-hdpi/list_focused_holo.9.png rename to DevDrawer/src/main/res/drawable-hdpi/list_focused_holo.9.png diff --git a/res/drawable-hdpi/list_longpressed_holo.9.png b/DevDrawer/src/main/res/drawable-hdpi/list_longpressed_holo.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-hdpi/list_longpressed_holo.9.png rename to DevDrawer/src/main/res/drawable-hdpi/list_longpressed_holo.9.png diff --git a/res/drawable-hdpi/list_pressed_holo_light.9.png b/DevDrawer/src/main/res/drawable-hdpi/list_pressed_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-hdpi/list_pressed_holo_light.9.png rename to DevDrawer/src/main/res/drawable-hdpi/list_pressed_holo_light.9.png diff --git a/res/drawable-hdpi/list_selector_disabled_holo_light.9.png b/DevDrawer/src/main/res/drawable-hdpi/list_selector_disabled_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-hdpi/list_selector_disabled_holo_light.9.png rename to DevDrawer/src/main/res/drawable-hdpi/list_selector_disabled_holo_light.9.png diff --git a/res/drawable-hdpi/shortcut_icon.png b/DevDrawer/src/main/res/drawable-hdpi/shortcut_icon.png similarity index 100% rename from res/drawable-hdpi/shortcut_icon.png rename to DevDrawer/src/main/res/drawable-hdpi/shortcut_icon.png diff --git a/res/drawable-hdpi/textfield_activated_holo_light.9.png b/DevDrawer/src/main/res/drawable-hdpi/textfield_activated_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-hdpi/textfield_activated_holo_light.9.png rename to DevDrawer/src/main/res/drawable-hdpi/textfield_activated_holo_light.9.png diff --git a/res/drawable-hdpi/textfield_default_holo_light.9.png b/DevDrawer/src/main/res/drawable-hdpi/textfield_default_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-hdpi/textfield_default_holo_light.9.png rename to DevDrawer/src/main/res/drawable-hdpi/textfield_default_holo_light.9.png diff --git a/res/drawable-hdpi/textfield_disabled_focused_holo_light.9.png b/DevDrawer/src/main/res/drawable-hdpi/textfield_disabled_focused_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-hdpi/textfield_disabled_focused_holo_light.9.png rename to DevDrawer/src/main/res/drawable-hdpi/textfield_disabled_focused_holo_light.9.png diff --git a/res/drawable-hdpi/textfield_disabled_holo_light.9.png b/DevDrawer/src/main/res/drawable-hdpi/textfield_disabled_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-hdpi/textfield_disabled_holo_light.9.png rename to DevDrawer/src/main/res/drawable-hdpi/textfield_disabled_holo_light.9.png diff --git a/res/drawable-hdpi/textfield_focused_holo_light.9.png b/DevDrawer/src/main/res/drawable-hdpi/textfield_focused_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-hdpi/textfield_focused_holo_light.9.png rename to DevDrawer/src/main/res/drawable-hdpi/textfield_focused_holo_light.9.png diff --git a/res/drawable-mdpi/btn_default_disabled_focused_holo_light.9.png b/DevDrawer/src/main/res/drawable-mdpi/btn_default_disabled_focused_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-mdpi/btn_default_disabled_focused_holo_light.9.png rename to DevDrawer/src/main/res/drawable-mdpi/btn_default_disabled_focused_holo_light.9.png diff --git a/res/drawable-mdpi/btn_default_disabled_holo_light.9.png b/DevDrawer/src/main/res/drawable-mdpi/btn_default_disabled_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-mdpi/btn_default_disabled_holo_light.9.png rename to DevDrawer/src/main/res/drawable-mdpi/btn_default_disabled_holo_light.9.png diff --git a/res/drawable-mdpi/btn_default_focused_holo_light.9.png b/DevDrawer/src/main/res/drawable-mdpi/btn_default_focused_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-mdpi/btn_default_focused_holo_light.9.png rename to DevDrawer/src/main/res/drawable-mdpi/btn_default_focused_holo_light.9.png diff --git a/res/drawable-mdpi/btn_default_normal_holo_light.9.png b/DevDrawer/src/main/res/drawable-mdpi/btn_default_normal_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-mdpi/btn_default_normal_holo_light.9.png rename to DevDrawer/src/main/res/drawable-mdpi/btn_default_normal_holo_light.9.png diff --git a/res/drawable-mdpi/btn_default_pressed_holo_light.9.png b/DevDrawer/src/main/res/drawable-mdpi/btn_default_pressed_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-mdpi/btn_default_pressed_holo_light.9.png rename to DevDrawer/src/main/res/drawable-mdpi/btn_default_pressed_holo_light.9.png diff --git a/res/drawable-mdpi/ic_action_add.png b/DevDrawer/src/main/res/drawable-mdpi/ic_action_add.png similarity index 100% rename from res/drawable-mdpi/ic_action_add.png rename to DevDrawer/src/main/res/drawable-mdpi/ic_action_add.png diff --git a/res/drawable-mdpi/ic_action_add_grey.png b/DevDrawer/src/main/res/drawable-mdpi/ic_action_add_grey.png similarity index 100% rename from res/drawable-mdpi/ic_action_add_grey.png rename to DevDrawer/src/main/res/drawable-mdpi/ic_action_add_grey.png diff --git a/res/drawable-mdpi/ic_action_clear.png b/DevDrawer/src/main/res/drawable-mdpi/ic_action_clear.png similarity index 100% rename from res/drawable-mdpi/ic_action_clear.png rename to DevDrawer/src/main/res/drawable-mdpi/ic_action_clear.png diff --git a/res/drawable-mdpi/ic_action_clear_grey.png b/DevDrawer/src/main/res/drawable-mdpi/ic_action_clear_grey.png similarity index 100% rename from res/drawable-mdpi/ic_action_clear_grey.png rename to DevDrawer/src/main/res/drawable-mdpi/ic_action_clear_grey.png diff --git a/res/drawable-mdpi/ic_action_clear_white.png b/DevDrawer/src/main/res/drawable-mdpi/ic_action_clear_white.png similarity index 100% rename from res/drawable-mdpi/ic_action_clear_white.png rename to DevDrawer/src/main/res/drawable-mdpi/ic_action_clear_white.png diff --git a/res/drawable-mdpi/ic_action_edit.png b/DevDrawer/src/main/res/drawable-mdpi/ic_action_edit.png similarity index 100% rename from res/drawable-mdpi/ic_action_edit.png rename to DevDrawer/src/main/res/drawable-mdpi/ic_action_edit.png diff --git a/res/drawable-mdpi/ic_action_edit_grey.png b/DevDrawer/src/main/res/drawable-mdpi/ic_action_edit_grey.png similarity index 100% rename from res/drawable-mdpi/ic_action_edit_grey.png rename to DevDrawer/src/main/res/drawable-mdpi/ic_action_edit_grey.png diff --git a/res/drawable-mdpi/ic_action_globe.png b/DevDrawer/src/main/res/drawable-mdpi/ic_action_globe.png similarity index 100% rename from res/drawable-mdpi/ic_action_globe.png rename to DevDrawer/src/main/res/drawable-mdpi/ic_action_globe.png diff --git a/res/drawable-mdpi/ic_action_link.png b/DevDrawer/src/main/res/drawable-mdpi/ic_action_link.png similarity index 100% rename from res/drawable-mdpi/ic_action_link.png rename to DevDrawer/src/main/res/drawable-mdpi/ic_action_link.png diff --git a/res/drawable-mdpi/ic_action_settings.png b/DevDrawer/src/main/res/drawable-mdpi/ic_action_settings.png similarity index 100% rename from res/drawable-mdpi/ic_action_settings.png rename to DevDrawer/src/main/res/drawable-mdpi/ic_action_settings.png diff --git a/res/drawable-mdpi/ic_action_settings_grey.png b/DevDrawer/src/main/res/drawable-mdpi/ic_action_settings_grey.png similarity index 100% rename from res/drawable-mdpi/ic_action_settings_grey.png rename to DevDrawer/src/main/res/drawable-mdpi/ic_action_settings_grey.png diff --git a/res/drawable-mdpi/ic_action_settings_white.png b/DevDrawer/src/main/res/drawable-mdpi/ic_action_settings_white.png similarity index 100% rename from res/drawable-mdpi/ic_action_settings_white.png rename to DevDrawer/src/main/res/drawable-mdpi/ic_action_settings_white.png diff --git a/res/drawable-mdpi/ic_action_trash.png b/DevDrawer/src/main/res/drawable-mdpi/ic_action_trash.png similarity index 100% rename from res/drawable-mdpi/ic_action_trash.png rename to DevDrawer/src/main/res/drawable-mdpi/ic_action_trash.png diff --git a/res/drawable-mdpi/ic_action_trash_grey.png b/DevDrawer/src/main/res/drawable-mdpi/ic_action_trash_grey.png similarity index 100% rename from res/drawable-mdpi/ic_action_trash_grey.png rename to DevDrawer/src/main/res/drawable-mdpi/ic_action_trash_grey.png diff --git a/res/drawable-mdpi/ic_action_trash_white.png b/DevDrawer/src/main/res/drawable-mdpi/ic_action_trash_white.png similarity index 100% rename from res/drawable-mdpi/ic_action_trash_white.png rename to DevDrawer/src/main/res/drawable-mdpi/ic_action_trash_white.png diff --git a/res/drawable-mdpi/ic_launcher.png b/DevDrawer/src/main/res/drawable-mdpi/ic_launcher.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-mdpi/ic_launcher.png rename to DevDrawer/src/main/res/drawable-mdpi/ic_launcher.png diff --git a/res/drawable-mdpi/list_activated_holo.9.png b/DevDrawer/src/main/res/drawable-mdpi/list_activated_holo.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-mdpi/list_activated_holo.9.png rename to DevDrawer/src/main/res/drawable-mdpi/list_activated_holo.9.png diff --git a/res/drawable-mdpi/list_focused_holo.9.png b/DevDrawer/src/main/res/drawable-mdpi/list_focused_holo.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-mdpi/list_focused_holo.9.png rename to DevDrawer/src/main/res/drawable-mdpi/list_focused_holo.9.png diff --git a/res/drawable-mdpi/list_longpressed_holo.9.png b/DevDrawer/src/main/res/drawable-mdpi/list_longpressed_holo.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-mdpi/list_longpressed_holo.9.png rename to DevDrawer/src/main/res/drawable-mdpi/list_longpressed_holo.9.png diff --git a/res/drawable-mdpi/list_pressed_holo_light.9.png b/DevDrawer/src/main/res/drawable-mdpi/list_pressed_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-mdpi/list_pressed_holo_light.9.png rename to DevDrawer/src/main/res/drawable-mdpi/list_pressed_holo_light.9.png diff --git a/res/drawable-mdpi/list_selector_disabled_holo_light.9.png b/DevDrawer/src/main/res/drawable-mdpi/list_selector_disabled_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-mdpi/list_selector_disabled_holo_light.9.png rename to DevDrawer/src/main/res/drawable-mdpi/list_selector_disabled_holo_light.9.png diff --git a/res/drawable-mdpi/shortcut_icon.png b/DevDrawer/src/main/res/drawable-mdpi/shortcut_icon.png similarity index 100% rename from res/drawable-mdpi/shortcut_icon.png rename to DevDrawer/src/main/res/drawable-mdpi/shortcut_icon.png diff --git a/res/drawable-mdpi/textfield_activated_holo_light.9.png b/DevDrawer/src/main/res/drawable-mdpi/textfield_activated_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-mdpi/textfield_activated_holo_light.9.png rename to DevDrawer/src/main/res/drawable-mdpi/textfield_activated_holo_light.9.png diff --git a/res/drawable-mdpi/textfield_default_holo_light.9.png b/DevDrawer/src/main/res/drawable-mdpi/textfield_default_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-mdpi/textfield_default_holo_light.9.png rename to DevDrawer/src/main/res/drawable-mdpi/textfield_default_holo_light.9.png diff --git a/res/drawable-mdpi/textfield_disabled_focused_holo_light.9.png b/DevDrawer/src/main/res/drawable-mdpi/textfield_disabled_focused_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-mdpi/textfield_disabled_focused_holo_light.9.png rename to DevDrawer/src/main/res/drawable-mdpi/textfield_disabled_focused_holo_light.9.png diff --git a/res/drawable-mdpi/textfield_disabled_holo_light.9.png b/DevDrawer/src/main/res/drawable-mdpi/textfield_disabled_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-mdpi/textfield_disabled_holo_light.9.png rename to DevDrawer/src/main/res/drawable-mdpi/textfield_disabled_holo_light.9.png diff --git a/res/drawable-mdpi/textfield_focused_holo_light.9.png b/DevDrawer/src/main/res/drawable-mdpi/textfield_focused_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-mdpi/textfield_focused_holo_light.9.png rename to DevDrawer/src/main/res/drawable-mdpi/textfield_focused_holo_light.9.png diff --git a/res/drawable-xhdpi/btn_default_disabled_focused_holo_light.9.png b/DevDrawer/src/main/res/drawable-xhdpi/btn_default_disabled_focused_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-xhdpi/btn_default_disabled_focused_holo_light.9.png rename to DevDrawer/src/main/res/drawable-xhdpi/btn_default_disabled_focused_holo_light.9.png diff --git a/res/drawable-xhdpi/btn_default_disabled_holo_light.9.png b/DevDrawer/src/main/res/drawable-xhdpi/btn_default_disabled_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-xhdpi/btn_default_disabled_holo_light.9.png rename to DevDrawer/src/main/res/drawable-xhdpi/btn_default_disabled_holo_light.9.png diff --git a/res/drawable-xhdpi/btn_default_focused_holo_light.9.png b/DevDrawer/src/main/res/drawable-xhdpi/btn_default_focused_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-xhdpi/btn_default_focused_holo_light.9.png rename to DevDrawer/src/main/res/drawable-xhdpi/btn_default_focused_holo_light.9.png diff --git a/res/drawable-xhdpi/btn_default_normal_holo_light.9.png b/DevDrawer/src/main/res/drawable-xhdpi/btn_default_normal_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-xhdpi/btn_default_normal_holo_light.9.png rename to DevDrawer/src/main/res/drawable-xhdpi/btn_default_normal_holo_light.9.png diff --git a/res/drawable-xhdpi/btn_default_pressed_holo_light.9.png b/DevDrawer/src/main/res/drawable-xhdpi/btn_default_pressed_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-xhdpi/btn_default_pressed_holo_light.9.png rename to DevDrawer/src/main/res/drawable-xhdpi/btn_default_pressed_holo_light.9.png diff --git a/res/drawable-xhdpi/clear_data.png b/DevDrawer/src/main/res/drawable-xhdpi/clear_data.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-xhdpi/clear_data.png rename to DevDrawer/src/main/res/drawable-xhdpi/clear_data.png diff --git a/res/drawable-xhdpi/ic_action_add.png b/DevDrawer/src/main/res/drawable-xhdpi/ic_action_add.png similarity index 100% rename from res/drawable-xhdpi/ic_action_add.png rename to DevDrawer/src/main/res/drawable-xhdpi/ic_action_add.png diff --git a/res/drawable-xhdpi/ic_action_add_grey.png b/DevDrawer/src/main/res/drawable-xhdpi/ic_action_add_grey.png similarity index 100% rename from res/drawable-xhdpi/ic_action_add_grey.png rename to DevDrawer/src/main/res/drawable-xhdpi/ic_action_add_grey.png diff --git a/res/drawable-xhdpi/ic_action_clear.png b/DevDrawer/src/main/res/drawable-xhdpi/ic_action_clear.png similarity index 100% rename from res/drawable-xhdpi/ic_action_clear.png rename to DevDrawer/src/main/res/drawable-xhdpi/ic_action_clear.png diff --git a/res/drawable-xhdpi/ic_action_clear_grey.png b/DevDrawer/src/main/res/drawable-xhdpi/ic_action_clear_grey.png similarity index 100% rename from res/drawable-xhdpi/ic_action_clear_grey.png rename to DevDrawer/src/main/res/drawable-xhdpi/ic_action_clear_grey.png diff --git a/res/drawable-xhdpi/ic_action_clear_white.png b/DevDrawer/src/main/res/drawable-xhdpi/ic_action_clear_white.png similarity index 100% rename from res/drawable-xhdpi/ic_action_clear_white.png rename to DevDrawer/src/main/res/drawable-xhdpi/ic_action_clear_white.png diff --git a/res/drawable-xhdpi/ic_action_edit.png b/DevDrawer/src/main/res/drawable-xhdpi/ic_action_edit.png similarity index 100% rename from res/drawable-xhdpi/ic_action_edit.png rename to DevDrawer/src/main/res/drawable-xhdpi/ic_action_edit.png diff --git a/res/drawable-xhdpi/ic_action_edit_grey.png b/DevDrawer/src/main/res/drawable-xhdpi/ic_action_edit_grey.png similarity index 100% rename from res/drawable-xhdpi/ic_action_edit_grey.png rename to DevDrawer/src/main/res/drawable-xhdpi/ic_action_edit_grey.png diff --git a/res/drawable-xhdpi/ic_action_globe.png b/DevDrawer/src/main/res/drawable-xhdpi/ic_action_globe.png similarity index 100% rename from res/drawable-xhdpi/ic_action_globe.png rename to DevDrawer/src/main/res/drawable-xhdpi/ic_action_globe.png diff --git a/res/drawable-xhdpi/ic_action_link.png b/DevDrawer/src/main/res/drawable-xhdpi/ic_action_link.png similarity index 100% rename from res/drawable-xhdpi/ic_action_link.png rename to DevDrawer/src/main/res/drawable-xhdpi/ic_action_link.png diff --git a/res/drawable-xhdpi/ic_action_settings.png b/DevDrawer/src/main/res/drawable-xhdpi/ic_action_settings.png similarity index 100% rename from res/drawable-xhdpi/ic_action_settings.png rename to DevDrawer/src/main/res/drawable-xhdpi/ic_action_settings.png diff --git a/res/drawable-xhdpi/ic_action_settings_grey.png b/DevDrawer/src/main/res/drawable-xhdpi/ic_action_settings_grey.png similarity index 100% rename from res/drawable-xhdpi/ic_action_settings_grey.png rename to DevDrawer/src/main/res/drawable-xhdpi/ic_action_settings_grey.png diff --git a/res/drawable-xhdpi/ic_action_settings_white.png b/DevDrawer/src/main/res/drawable-xhdpi/ic_action_settings_white.png similarity index 100% rename from res/drawable-xhdpi/ic_action_settings_white.png rename to DevDrawer/src/main/res/drawable-xhdpi/ic_action_settings_white.png diff --git a/res/drawable-xhdpi/ic_action_trash.png b/DevDrawer/src/main/res/drawable-xhdpi/ic_action_trash.png similarity index 100% rename from res/drawable-xhdpi/ic_action_trash.png rename to DevDrawer/src/main/res/drawable-xhdpi/ic_action_trash.png diff --git a/res/drawable-xhdpi/ic_action_trash_grey.png b/DevDrawer/src/main/res/drawable-xhdpi/ic_action_trash_grey.png similarity index 100% rename from res/drawable-xhdpi/ic_action_trash_grey.png rename to DevDrawer/src/main/res/drawable-xhdpi/ic_action_trash_grey.png diff --git a/res/drawable-xhdpi/ic_action_trash_white.png b/DevDrawer/src/main/res/drawable-xhdpi/ic_action_trash_white.png similarity index 100% rename from res/drawable-xhdpi/ic_action_trash_white.png rename to DevDrawer/src/main/res/drawable-xhdpi/ic_action_trash_white.png diff --git a/res/drawable-xhdpi/ic_launcher.png b/DevDrawer/src/main/res/drawable-xhdpi/ic_launcher.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-xhdpi/ic_launcher.png rename to DevDrawer/src/main/res/drawable-xhdpi/ic_launcher.png diff --git a/res/drawable-xhdpi/list_activated_holo.9.png b/DevDrawer/src/main/res/drawable-xhdpi/list_activated_holo.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-xhdpi/list_activated_holo.9.png rename to DevDrawer/src/main/res/drawable-xhdpi/list_activated_holo.9.png diff --git a/res/drawable-xhdpi/list_focused_holo.9.png b/DevDrawer/src/main/res/drawable-xhdpi/list_focused_holo.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-xhdpi/list_focused_holo.9.png rename to DevDrawer/src/main/res/drawable-xhdpi/list_focused_holo.9.png diff --git a/res/drawable-xhdpi/list_longpressed_holo.9.png b/DevDrawer/src/main/res/drawable-xhdpi/list_longpressed_holo.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-xhdpi/list_longpressed_holo.9.png rename to DevDrawer/src/main/res/drawable-xhdpi/list_longpressed_holo.9.png diff --git a/res/drawable-xhdpi/list_pressed_holo_light.9.png b/DevDrawer/src/main/res/drawable-xhdpi/list_pressed_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-xhdpi/list_pressed_holo_light.9.png rename to DevDrawer/src/main/res/drawable-xhdpi/list_pressed_holo_light.9.png diff --git a/res/drawable-xhdpi/list_selector_disabled_holo_light.9.png b/DevDrawer/src/main/res/drawable-xhdpi/list_selector_disabled_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-xhdpi/list_selector_disabled_holo_light.9.png rename to DevDrawer/src/main/res/drawable-xhdpi/list_selector_disabled_holo_light.9.png diff --git a/res/drawable-xhdpi/shortcut_icon.png b/DevDrawer/src/main/res/drawable-xhdpi/shortcut_icon.png similarity index 100% rename from res/drawable-xhdpi/shortcut_icon.png rename to DevDrawer/src/main/res/drawable-xhdpi/shortcut_icon.png diff --git a/res/drawable-xhdpi/textfield_activated_holo_light.9.png b/DevDrawer/src/main/res/drawable-xhdpi/textfield_activated_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-xhdpi/textfield_activated_holo_light.9.png rename to DevDrawer/src/main/res/drawable-xhdpi/textfield_activated_holo_light.9.png diff --git a/res/drawable-xhdpi/textfield_default_holo_light.9.png b/DevDrawer/src/main/res/drawable-xhdpi/textfield_default_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-xhdpi/textfield_default_holo_light.9.png rename to DevDrawer/src/main/res/drawable-xhdpi/textfield_default_holo_light.9.png diff --git a/res/drawable-xhdpi/textfield_disabled_focused_holo_light.9.png b/DevDrawer/src/main/res/drawable-xhdpi/textfield_disabled_focused_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-xhdpi/textfield_disabled_focused_holo_light.9.png rename to DevDrawer/src/main/res/drawable-xhdpi/textfield_disabled_focused_holo_light.9.png diff --git a/res/drawable-xhdpi/textfield_disabled_holo_light.9.png b/DevDrawer/src/main/res/drawable-xhdpi/textfield_disabled_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-xhdpi/textfield_disabled_holo_light.9.png rename to DevDrawer/src/main/res/drawable-xhdpi/textfield_disabled_holo_light.9.png diff --git a/res/drawable-xhdpi/textfield_focused_holo_light.9.png b/DevDrawer/src/main/res/drawable-xhdpi/textfield_focused_holo_light.9.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-xhdpi/textfield_focused_holo_light.9.png rename to DevDrawer/src/main/res/drawable-xhdpi/textfield_focused_holo_light.9.png diff --git a/res/drawable-xxhdpi/ic_launcher.png b/DevDrawer/src/main/res/drawable-xxhdpi/ic_launcher.png old mode 100755 new mode 100644 similarity index 100% rename from res/drawable-xxhdpi/ic_launcher.png rename to DevDrawer/src/main/res/drawable-xxhdpi/ic_launcher.png diff --git a/res/drawable-xxhdpi/shortcut_icon.png b/DevDrawer/src/main/res/drawable-xxhdpi/shortcut_icon.png similarity index 100% rename from res/drawable-xxhdpi/shortcut_icon.png rename to DevDrawer/src/main/res/drawable-xxhdpi/shortcut_icon.png diff --git a/res/drawable/add_imageview.xml b/DevDrawer/src/main/res/drawable/add_imageview.xml similarity index 100% rename from res/drawable/add_imageview.xml rename to DevDrawer/src/main/res/drawable/add_imageview.xml diff --git a/res/drawable/background_repeat.xml b/DevDrawer/src/main/res/drawable/background_repeat.xml similarity index 100% rename from res/drawable/background_repeat.xml rename to DevDrawer/src/main/res/drawable/background_repeat.xml diff --git a/res/drawable/background_repeat_dark.xml b/DevDrawer/src/main/res/drawable/background_repeat_dark.xml similarity index 100% rename from res/drawable/background_repeat_dark.xml rename to DevDrawer/src/main/res/drawable/background_repeat_dark.xml diff --git a/res/drawable/background_texture.png b/DevDrawer/src/main/res/drawable/background_texture.png similarity index 100% rename from res/drawable/background_texture.png rename to DevDrawer/src/main/res/drawable/background_texture.png diff --git a/res/drawable/background_texture_dark.png b/DevDrawer/src/main/res/drawable/background_texture_dark.png similarity index 100% rename from res/drawable/background_texture_dark.png rename to DevDrawer/src/main/res/drawable/background_texture_dark.png diff --git a/res/drawable/btn_default_holo_light.xml b/DevDrawer/src/main/res/drawable/btn_default_holo_light.xml old mode 100755 new mode 100644 similarity index 100% rename from res/drawable/btn_default_holo_light.xml rename to DevDrawer/src/main/res/drawable/btn_default_holo_light.xml diff --git a/res/drawable/clear_imageview.xml b/DevDrawer/src/main/res/drawable/clear_imageview.xml similarity index 100% rename from res/drawable/clear_imageview.xml rename to DevDrawer/src/main/res/drawable/clear_imageview.xml diff --git a/res/drawable/clear_imageview_dark.xml b/DevDrawer/src/main/res/drawable/clear_imageview_dark.xml similarity index 100% rename from res/drawable/clear_imageview_dark.xml rename to DevDrawer/src/main/res/drawable/clear_imageview_dark.xml diff --git a/res/drawable/delete_imageview.xml b/DevDrawer/src/main/res/drawable/delete_imageview.xml similarity index 100% rename from res/drawable/delete_imageview.xml rename to DevDrawer/src/main/res/drawable/delete_imageview.xml diff --git a/res/drawable/delete_imageview_dark.xml b/DevDrawer/src/main/res/drawable/delete_imageview_dark.xml similarity index 100% rename from res/drawable/delete_imageview_dark.xml rename to DevDrawer/src/main/res/drawable/delete_imageview_dark.xml diff --git a/res/drawable/devdrawer_widget_preview.png b/DevDrawer/src/main/res/drawable/devdrawer_widget_preview.png similarity index 100% rename from res/drawable/devdrawer_widget_preview.png rename to DevDrawer/src/main/res/drawable/devdrawer_widget_preview.png diff --git a/res/drawable/edit_imageview.xml b/DevDrawer/src/main/res/drawable/edit_imageview.xml similarity index 100% rename from res/drawable/edit_imageview.xml rename to DevDrawer/src/main/res/drawable/edit_imageview.xml diff --git a/res/drawable/edit_text_holo_light.xml b/DevDrawer/src/main/res/drawable/edit_text_holo_light.xml old mode 100755 new mode 100644 similarity index 100% rename from res/drawable/edit_text_holo_light.xml rename to DevDrawer/src/main/res/drawable/edit_text_holo_light.xml diff --git a/res/drawable/list_selector_background_transition_holo_light.xml b/DevDrawer/src/main/res/drawable/list_selector_background_transition_holo_light.xml old mode 100755 new mode 100644 similarity index 100% rename from res/drawable/list_selector_background_transition_holo_light.xml rename to DevDrawer/src/main/res/drawable/list_selector_background_transition_holo_light.xml diff --git a/res/drawable/list_selector_holo_light.xml b/DevDrawer/src/main/res/drawable/list_selector_holo_light.xml old mode 100755 new mode 100644 similarity index 100% rename from res/drawable/list_selector_holo_light.xml rename to DevDrawer/src/main/res/drawable/list_selector_holo_light.xml diff --git a/res/drawable/listview_button_background_normal.png b/DevDrawer/src/main/res/drawable/listview_button_background_normal.png similarity index 100% rename from res/drawable/listview_button_background_normal.png rename to DevDrawer/src/main/res/drawable/listview_button_background_normal.png diff --git a/res/drawable/listview_button_background_pressed.png b/DevDrawer/src/main/res/drawable/listview_button_background_pressed.png similarity index 100% rename from res/drawable/listview_button_background_pressed.png rename to DevDrawer/src/main/res/drawable/listview_button_background_pressed.png diff --git a/res/drawable/listview_button_selector.xml b/DevDrawer/src/main/res/drawable/listview_button_selector.xml similarity index 100% rename from res/drawable/listview_button_selector.xml rename to DevDrawer/src/main/res/drawable/listview_button_selector.xml diff --git a/res/drawable/more_imageview.xml b/DevDrawer/src/main/res/drawable/more_imageview.xml similarity index 100% rename from res/drawable/more_imageview.xml rename to DevDrawer/src/main/res/drawable/more_imageview.xml diff --git a/res/drawable/more_imageview_dark.xml b/DevDrawer/src/main/res/drawable/more_imageview_dark.xml similarity index 100% rename from res/drawable/more_imageview_dark.xml rename to DevDrawer/src/main/res/drawable/more_imageview_dark.xml diff --git a/res/drawable/settings_imageview.xml b/DevDrawer/src/main/res/drawable/settings_imageview.xml similarity index 100% rename from res/drawable/settings_imageview.xml rename to DevDrawer/src/main/res/drawable/settings_imageview.xml diff --git a/res/drawable/settings_imageview_dark.xml b/DevDrawer/src/main/res/drawable/settings_imageview_dark.xml similarity index 100% rename from res/drawable/settings_imageview_dark.xml rename to DevDrawer/src/main/res/drawable/settings_imageview_dark.xml diff --git a/res/layout-land/list_item_more.xml b/DevDrawer/src/main/res/layout-land/list_item_more.xml old mode 100755 new mode 100644 similarity index 100% rename from res/layout-land/list_item_more.xml rename to DevDrawer/src/main/res/layout-land/list_item_more.xml diff --git a/res/layout-sw600dp/list_item_more.xml b/DevDrawer/src/main/res/layout-sw600dp/list_item_more.xml old mode 100755 new mode 100644 similarity index 100% rename from res/layout-sw600dp/list_item_more.xml rename to DevDrawer/src/main/res/layout-sw600dp/list_item_more.xml diff --git a/res/layout/activity_choice.xml b/DevDrawer/src/main/res/layout/activity_choice.xml similarity index 100% rename from res/layout/activity_choice.xml rename to DevDrawer/src/main/res/layout/activity_choice.xml diff --git a/res/layout/activity_choice_list_item.xml b/DevDrawer/src/main/res/layout/activity_choice_list_item.xml similarity index 100% rename from res/layout/activity_choice_list_item.xml rename to DevDrawer/src/main/res/layout/activity_choice_list_item.xml diff --git a/res/layout/custom_ab_title.xml b/DevDrawer/src/main/res/layout/custom_ab_title.xml similarity index 100% rename from res/layout/custom_ab_title.xml rename to DevDrawer/src/main/res/layout/custom_ab_title.xml diff --git a/res/layout/dropdown_list_item.xml b/DevDrawer/src/main/res/layout/dropdown_list_item.xml similarity index 100% rename from res/layout/dropdown_list_item.xml rename to DevDrawer/src/main/res/layout/dropdown_list_item.xml diff --git a/res/layout/edit_dialog.xml b/DevDrawer/src/main/res/layout/edit_dialog.xml old mode 100755 new mode 100644 similarity index 100% rename from res/layout/edit_dialog.xml rename to DevDrawer/src/main/res/layout/edit_dialog.xml diff --git a/res/layout/legacy_dialog.xml b/DevDrawer/src/main/res/layout/legacy_dialog.xml similarity index 100% rename from res/layout/legacy_dialog.xml rename to DevDrawer/src/main/res/layout/legacy_dialog.xml diff --git a/res/layout/list_item.xml b/DevDrawer/src/main/res/layout/list_item.xml old mode 100755 new mode 100644 similarity index 100% rename from res/layout/list_item.xml rename to DevDrawer/src/main/res/layout/list_item.xml diff --git a/res/layout/list_item_more.xml b/DevDrawer/src/main/res/layout/list_item_more.xml old mode 100755 new mode 100644 similarity index 100% rename from res/layout/list_item_more.xml rename to DevDrawer/src/main/res/layout/list_item_more.xml diff --git a/res/layout/list_item_more_legacy.xml b/DevDrawer/src/main/res/layout/list_item_more_legacy.xml old mode 100755 new mode 100644 similarity index 100% rename from res/layout/list_item_more_legacy.xml rename to DevDrawer/src/main/res/layout/list_item_more_legacy.xml diff --git a/res/layout/locale_list_item.xml b/DevDrawer/src/main/res/layout/locale_list_item.xml similarity index 100% rename from res/layout/locale_list_item.xml rename to DevDrawer/src/main/res/layout/locale_list_item.xml diff --git a/res/layout/locale_switcher.xml b/DevDrawer/src/main/res/layout/locale_switcher.xml similarity index 100% rename from res/layout/locale_switcher.xml rename to DevDrawer/src/main/res/layout/locale_switcher.xml diff --git a/res/layout/main.xml b/DevDrawer/src/main/res/layout/main.xml old mode 100755 new mode 100644 similarity index 100% rename from res/layout/main.xml rename to DevDrawer/src/main/res/layout/main.xml diff --git a/res/layout/package_list_item.xml b/DevDrawer/src/main/res/layout/package_list_item.xml old mode 100755 new mode 100644 similarity index 100% rename from res/layout/package_list_item.xml rename to DevDrawer/src/main/res/layout/package_list_item.xml diff --git a/res/layout/widget_layout.xml b/DevDrawer/src/main/res/layout/widget_layout.xml old mode 100755 new mode 100644 similarity index 100% rename from res/layout/widget_layout.xml rename to DevDrawer/src/main/res/layout/widget_layout.xml diff --git a/res/layout/widget_layout_dark.xml b/DevDrawer/src/main/res/layout/widget_layout_dark.xml similarity index 100% rename from res/layout/widget_layout_dark.xml rename to DevDrawer/src/main/res/layout/widget_layout_dark.xml diff --git a/res/values-v11/bools.xml b/DevDrawer/src/main/res/values-v11/bools.xml similarity index 100% rename from res/values-v11/bools.xml rename to DevDrawer/src/main/res/values-v11/bools.xml diff --git a/res/values-v11/devdrawertheme_styles.xml b/DevDrawer/src/main/res/values-v11/devdrawertheme_styles.xml old mode 100755 new mode 100644 similarity index 100% rename from res/values-v11/devdrawertheme_styles.xml rename to DevDrawer/src/main/res/values-v11/devdrawertheme_styles.xml diff --git a/res/values-v11/devdrawertheme_themes.xml b/DevDrawer/src/main/res/values-v11/devdrawertheme_themes.xml old mode 100755 new mode 100644 similarity index 100% rename from res/values-v11/devdrawertheme_themes.xml rename to DevDrawer/src/main/res/values-v11/devdrawertheme_themes.xml diff --git a/res/values/bools.xml b/DevDrawer/src/main/res/values/bools.xml similarity index 100% rename from res/values/bools.xml rename to DevDrawer/src/main/res/values/bools.xml diff --git a/res/values/colors.xml b/DevDrawer/src/main/res/values/colors.xml similarity index 100% rename from res/values/colors.xml rename to DevDrawer/src/main/res/values/colors.xml diff --git a/res/values/devdrawertheme_styles.xml b/DevDrawer/src/main/res/values/devdrawertheme_styles.xml old mode 100755 new mode 100644 similarity index 100% rename from res/values/devdrawertheme_styles.xml rename to DevDrawer/src/main/res/values/devdrawertheme_styles.xml diff --git a/res/values/devdrawertheme_themes.xml b/DevDrawer/src/main/res/values/devdrawertheme_themes.xml old mode 100755 new mode 100644 similarity index 100% rename from res/values/devdrawertheme_themes.xml rename to DevDrawer/src/main/res/values/devdrawertheme_themes.xml diff --git a/res/values/strings.xml b/DevDrawer/src/main/res/values/strings.xml old mode 100755 new mode 100644 similarity index 100% rename from res/values/strings.xml rename to DevDrawer/src/main/res/values/strings.xml diff --git a/res/xml/preferences.xml b/DevDrawer/src/main/res/xml/preferences.xml similarity index 100% rename from res/xml/preferences.xml rename to DevDrawer/src/main/res/xml/preferences.xml diff --git a/res/xml/widget_info.xml b/DevDrawer/src/main/res/xml/widget_info.xml old mode 100755 new mode 100644 similarity index 100% rename from res/xml/widget_info.xml rename to DevDrawer/src/main/res/xml/widget_info.xml diff --git a/ant.properties b/ant.properties deleted file mode 100755 index b0971e89..00000000 --- a/ant.properties +++ /dev/null @@ -1,17 +0,0 @@ -# This file is used to override default values used by the Ant build system. -# -# This file must be checked into Version Control Systems, as it is -# integral to the build system of your project. - -# This file is only used by the Ant script. - -# You can use this to override default values such as -# 'source.dir' for the location of your java source folder and -# 'out.dir' for the location of your output folder. - -# You can also use it define how the release builds are signed by declaring -# the following properties: -# 'key.store' for the location of your keystore and -# 'key.alias' for the name of the key to use. -# The password will be asked during the build when you use the 'release' target. - diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..21d3ff91 --- /dev/null +++ b/build.gradle @@ -0,0 +1,8 @@ +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.3.3' + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..8c0fb64a Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..f74a1cdf --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue May 29 20:56:04 AEST 2018 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.7-all.zip diff --git a/gradlew b/gradlew new file mode 100644 index 00000000..91a7e269 --- /dev/null +++ b/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 00000000..8a0b282a --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/libraries/libsuperuser/.classpath b/libraries/libsuperuser/.classpath new file mode 100644 index 00000000..0b084083 --- /dev/null +++ b/libraries/libsuperuser/.classpath @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/libraries/libsuperuser/.project b/libraries/libsuperuser/.project new file mode 100644 index 00000000..6844aa08 --- /dev/null +++ b/libraries/libsuperuser/.project @@ -0,0 +1,38 @@ + + + libsuperuser + + + + + + com.android.ide.eclipse.adt.ResourceManagerBuilder + + + + + com.android.ide.eclipse.adt.PreCompilerBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + com.saikoa.dexguard.eclipse.adt.ApkBuilder + + + + + com.android.ide.eclipse.adt.ApkBuilder + + + + + + com.android.ide.eclipse.adt.AndroidNature + org.eclipse.jdt.core.javanature + + diff --git a/libraries/libsuperuser/AndroidManifest.xml b/libraries/libsuperuser/AndroidManifest.xml new file mode 100644 index 00000000..a829063a --- /dev/null +++ b/libraries/libsuperuser/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/libraries/libsuperuser/build.gradle b/libraries/libsuperuser/build.gradle new file mode 100644 index 00000000..f4d835a5 --- /dev/null +++ b/libraries/libsuperuser/build.gradle @@ -0,0 +1,14 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 26 + buildToolsVersion "26.0.2" + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src'] + res.srcDirs = ['res'] + } + } +} \ No newline at end of file diff --git a/proguard-project.txt b/libraries/libsuperuser/proguard-project.txt old mode 100755 new mode 100644 similarity index 100% rename from proguard-project.txt rename to libraries/libsuperuser/proguard-project.txt diff --git a/project.properties b/libraries/libsuperuser/project.properties old mode 100755 new mode 100644 similarity index 89% rename from project.properties rename to libraries/libsuperuser/project.properties index 6a9834ff..b5ebc372 --- a/project.properties +++ b/libraries/libsuperuser/project.properties @@ -11,6 +11,5 @@ #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt # Project target. -target=android-17 -android.library.reference.1=../libsuperuser - +target=android-4 +android.library=true diff --git a/libraries/libsuperuser/src/eu/chainfire/libsuperuser/Application.java b/libraries/libsuperuser/src/eu/chainfire/libsuperuser/Application.java new file mode 100644 index 00000000..454740ad --- /dev/null +++ b/libraries/libsuperuser/src/eu/chainfire/libsuperuser/Application.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2012-2013 Jorrit "Chainfire" Jongma + * + * 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 eu.chainfire.libsuperuser; + +import android.content.Context; +import android.os.Handler; +import android.widget.Toast; + +/** + * Base application class to extend from, solving some issues with + * toasts and AsyncTasks you are likely to run into + */ +public class Application extends android.app.Application { + /** + * Shows a toast message + * + * @param context Any context belonging to this application + * @param message The message to show + */ + public static void toast(Context context, String message) { + // this is a static method so it is easier to call, + // as the context checking and casting is done for you + + if (context == null) return; + + if (!(context instanceof Application)) { + context = context.getApplicationContext(); + } + + if (context instanceof Application) { + final Context c = context; + final String m = message; + + ((Application)context).runInApplicationThread(new Runnable() { + @Override + public void run() { + Toast.makeText(c, m, Toast.LENGTH_LONG).show(); + } + }); + } + } + + private static Handler mApplicationHandler = new Handler(); + + /** + * Run a runnable in the main application thread + * + * @param r Runnable to run + */ + public void runInApplicationThread(Runnable r) { + mApplicationHandler.post(r); + } + + @Override + public void onCreate() { + super.onCreate(); + + try { + // workaround bug in AsyncTask, can show up (for example) when you toast from a service + // this makes sure AsyncTask's internal handler is created from the right (main) thread + Class.forName("android.os.AsyncTask"); + } catch (ClassNotFoundException e) { + } + } +} diff --git a/libraries/libsuperuser/src/eu/chainfire/libsuperuser/Debug.java b/libraries/libsuperuser/src/eu/chainfire/libsuperuser/Debug.java new file mode 100644 index 00000000..2d059e3e --- /dev/null +++ b/libraries/libsuperuser/src/eu/chainfire/libsuperuser/Debug.java @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2012-2013 Jorrit "Chainfire" Jongma + * + * 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 eu.chainfire.libsuperuser; + +import android.os.Looper; +import android.util.Log; + +/** + * Utility class for logging and debug features that (by default) does nothing when not in debug mode + */ +public class Debug { + + // ----- DEBUGGING ----- + + private static boolean debug = BuildConfig.DEBUG; + + /** + *

Enable or disable debug mode

+ * + *

By default, debug mode is enabled for development + * builds and disabled for exported APKs - see + * BuildConfig.DEBUG

+ * + * @param enabled Enable debug mode ? + */ + public static void setDebug(boolean enable) { + debug = enable; + } + + /** + *

Is debug mode enabled ?

+ * + * @return Debug mode enabled + */ + public static boolean getDebug() { + return debug; + } + + // ----- LOGGING ----- + + public interface OnLogListener { + public void onLog(int type, String typeIndicator, String message); + } + + public static final String TAG = "libsuperuser"; + + public static final int LOG_GENERAL = 0x0001; + public static final int LOG_COMMAND = 0x0002; + public static final int LOG_OUTPUT = 0x0004; + + public static final int LOG_NONE = 0x0000; + public static final int LOG_ALL = 0xFFFF; + + private static int logTypes = LOG_ALL; + + private static OnLogListener logListener = null; + + /** + *

Log a message (internal)

+ * + *

Current debug and enabled logtypes decide what gets logged - + * even if a custom callback is registered

+ * + * @param type Type of message to log + * @param typeIndicator String indicator for message type + * @param message The message to log + */ + private static void logCommon(int type, String typeIndicator, String message) { + if (debug && ((logTypes & type) == type)) { + if (logListener != null) { + logListener.onLog(type, typeIndicator, message); + } else { + Log.d(TAG, "[" + TAG + "][" + typeIndicator + "]" + (!message.startsWith("[") && !message.startsWith(" ") ? " " : "") + message); + } + } + } + + /** + *

Log a "general" message

+ * + *

These messages are infrequent and mostly occur at startup/shutdown or on error

+ * + * @param message The message to log + */ + public static void log(String message) { + logCommon(LOG_GENERAL, "G", message); + } + + /** + *

Log a "per-command" message

+ * + *

This could produce a lot of output if the client runs many commands in the session

+ * + * @param message The message to log + */ + public static void logCommand(String message) { + logCommon(LOG_COMMAND, "C", message); + } + + /** + *

Log a line of stdout/stderr output

+ * + *

This could produce a lot of output if the shell commands are noisy

+ * + * @param message The message to log + */ + public static void logOutput(String message) { + logCommon(LOG_OUTPUT, "O", message); + } + + /** + *

Enable or disable logging specific types of message

+ * + *

You may | (or) LOG_* constants together. Note that + * debug mode must also be enabled for actual logging to + * occur.

+ * + * @param type LOG_* constants + * @param enabled Enable or disable + */ + public static void setLogTypeEnabled(int type, boolean enable) { + if (enable) { + logTypes |= type; + } else { + logTypes &= ~type; + } + } + + /** + *

Is logging for specific types of messages enabled ?

+ * + *

You may | (or) LOG_* constants together, to learn if + * all passed message types are enabled for logging. Note + * that debug mode must also be enabled for actual logging + * to occur.

+ * + * @param type LOG_* constants + */ + public static boolean getLogTypeEnabled(int type) { + return ((logTypes & type) == type); + } + + /** + *

Is logging for specific types of messages enabled ?

+ * + *

You may | (or) LOG_* constants together, to learn if + * all message types are enabled for logging. Takes + * debug mode into account for the result.

+ * + * @param type LOG_* constants + */ + public static boolean getLogTypeEnabledEffective(int type) { + return getDebug() && getLogTypeEnabled(type); + } + + /** + *

Register a custom log handler

+ * + *

Replaces the log method (write to logcat) with your own + * handler. Whether your handler gets called is still dependent + * on debug mode and message types being enabled for logging.

+ * + * @param onLogListener Custom log listener or NULL to revert to default + */ + public static void setOnLogListener(OnLogListener onLogListener) { + logListener = onLogListener; + } + + /** + *

Get the currently registered custom log handler

+ * + * @return Current custom log handler or NULL if none is present + */ + public static OnLogListener getOnLogListener() { + return logListener; + } + + // ----- SANITY CHECKS ----- + + private static boolean sanityChecks = true; + + /** + *

Enable or disable sanity checks

+ * + *

Enables or disables the library crashing when su is called + * from the main thread.

+ * + * @param enabled Enable or disable + */ + public static void setSanityChecksEnabled(boolean enable) { + sanityChecks = enable; + } + + /** + *

Are sanity checks enabled ?

+ * + *

Note that debug mode must also be enabled for actual + * sanity checks to occur.

+ * + * @return True if enabled + */ + public static boolean getSanityChecksEnabled() { + return sanityChecks; + } + + /** + *

Are sanity checks enabled ?

+ * + *

Takes debug mode into account for the result.

+ * + * @return True if enabled + */ + public static boolean getSanityChecksEnabledEffective() { + return getDebug() && getSanityChecksEnabled(); + } + + /** + *

Are we running on the main thread ?

+ * + * @return Running on main thread ? + */ + public static boolean onMainThread() { + return ((Looper.myLooper() != null) && (Looper.myLooper() == Looper.getMainLooper())); + } + +} diff --git a/libraries/libsuperuser/src/eu/chainfire/libsuperuser/Shell.java b/libraries/libsuperuser/src/eu/chainfire/libsuperuser/Shell.java new file mode 100644 index 00000000..403b8802 --- /dev/null +++ b/libraries/libsuperuser/src/eu/chainfire/libsuperuser/Shell.java @@ -0,0 +1,1254 @@ +/* + * Copyright (C) 2012-2013 Jorrit "Chainfire" Jongma + * + * 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 eu.chainfire.libsuperuser; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import android.os.Handler; +import android.os.Looper; + +import eu.chainfire.libsuperuser.StreamGobbler.OnLineListener; + +/** + * Class providing functionality to execute commands in a (root) shell + */ +public class Shell { + /** + *

Runs commands using the supplied shell, and returns the output, or null in + * case of errors.

+ * + *

This method is deprecated and only provided for backwards compatibility. + * Use {@link #run(String, String[], String[], boolean)} instead, and see that + * same method for usage notes.

+ * + * @param shell The shell to use for executing the commands + * @param commands The commands to execute + * @param wantSTDERR Return STDERR in the output ? + * @return Output of the commands, or null in case of an error + */ + @Deprecated + public static List run(String shell, String[] commands, boolean wantSTDERR) { + return run(shell, commands, null, wantSTDERR); + } + + /** + *

Runs commands using the supplied shell, and returns the output, or null in + * case of errors.

+ * + *

Note that due to compatibility with older Android versions, + * wantSTDERR is not implemented using redirectErrorStream, but rather appended + * to the output. STDOUT and STDERR are thus not guaranteed to be in the correct + * order in the output.

+ * + *

Note as well that this code will intentionally crash when run in debug mode + * from the main thread of the application. You should always execute shell + * commands from a background thread.

+ * + *

When in debug mode, the code will also excessively log the commands passed to + * and the output returned from the shell.

+ * + *

Though this function uses background threads to gobble STDOUT and STDERR so + * a deadlock does not occur if the shell produces massive output, the output is + * still stored in a List<String>, and as such doing something like 'ls -lR /' + * will probably have you run out of memory.

+ * + * @param shell The shell to use for executing the commands + * @param commands The commands to execute + * @param environment List of all environment variables (in 'key=value' format) or null for defaults + * @param wantSTDERR Return STDERR in the output ? + * @return Output of the commands, or null in case of an error + */ + public static List run(String shell, String[] commands, String[] environment, boolean wantSTDERR) { + String shellUpper = shell.toUpperCase(Locale.ENGLISH); + + if (Debug.getSanityChecksEnabledEffective() && Debug.onMainThread()) { + // check if we're running in the main thread, and if so, crash if we're in debug mode, + // to let the developer know attention is needed here. + + Debug.log(ShellOnMainThreadException.EXCEPTION_COMMAND); + throw new ShellOnMainThreadException(ShellOnMainThreadException.EXCEPTION_COMMAND); + } + Debug.logCommand(String.format("[%s%%] START", shellUpper)); + + List res = Collections.synchronizedList(new ArrayList()); + + try { + // Combine passed environment with system environment + if (environment != null) { + Map newEnvironment = new HashMap(); + newEnvironment.putAll(System.getenv()); + int split; + for (String entry : environment) { + if ((split = entry.indexOf("=")) >= 0) { + newEnvironment.put(entry.substring(0, split), entry.substring(split + 1)); + } + } + int i = 0; + environment = new String[newEnvironment.size()]; + for (Map.Entry entry : newEnvironment.entrySet()) { + environment[i] = entry.getKey() + "=" + entry.getValue(); + i++; + } + } + + // setup our process, retrieve STDIN stream, and STDOUT/STDERR gobblers + Process process = Runtime.getRuntime().exec(shell, environment); + DataOutputStream STDIN = new DataOutputStream(process.getOutputStream()); + StreamGobbler STDOUT = new StreamGobbler(shellUpper + "-", process.getInputStream(), res); + StreamGobbler STDERR = new StreamGobbler(shellUpper + "*", process.getErrorStream(), wantSTDERR ? res : null); + + // start gobbling and write our commands to the shell + STDOUT.start(); + STDERR.start(); + for (String write : commands) { + Debug.logCommand(String.format("[%s+] %s", shellUpper, write)); + STDIN.writeBytes(write + "\n"); + STDIN.flush(); + } + STDIN.writeBytes("exit\n"); + STDIN.flush(); + + // wait for our process to finish, while we gobble away in the background + process.waitFor(); + + // make sure our threads are done gobbling, our streams are closed, and the process is + // destroyed - while the latter two shouldn't be needed in theory, and may even produce + // warnings, in "normal" Java they are required for guaranteed cleanup of resources, so + // lets be safe and do this on Android as well + try { + STDIN.close(); + } catch (IOException e) { + } + STDOUT.join(); + STDERR.join(); + process.destroy(); + + // in case of su, 255 usually indicates access denied + if (shell.equals("su") && (process.exitValue() == 255)) { + res = null; + } + } catch (IOException e) { + // shell probably not found + res = null; + } catch (InterruptedException e) { + // this should really be re-thrown + res = null; + } + + Debug.logCommand(String.format("[%s%%] END", shell.toUpperCase(Locale.ENGLISH))); + return res; + } + + protected static String[] availableTestCommands = new String[] { + "echo -BOC-", + "id" + }; + + /** + * See if the shell is alive, and if so, check the UID + * + * @param ret Standard output from running availableTestCommands + * @param checkForRoot true if we are expecting this shell to be running as root + * @return true on success, false on error + */ + protected static boolean parseAvailableResult(List ret, boolean checkForRoot) { + if (ret == null) return false; + + // this is only one of many ways this can be done + boolean echo_seen = false; + + for (String line : ret) { + if (line.contains("uid=")) { + // id command is working, let's see if we are actually root + return !checkForRoot || line.contains("uid=0"); + } else if (line.contains("-BOC-")) { + // if we end up here, at least the su command starts some kind of shell, + // let's hope it has root privileges - no way to know without additional + // native binaries + echo_seen = true; + } + } + + return echo_seen; + } + + /** + * This class provides utility functions to easily execute commands using SH + */ + public static class SH { + /** + * Runs command and return output + * + * @param command The command to run + * @return Output of the command, or null in case of an error + */ + public static List run(String command) { + return Shell.run("sh", new String[] { command }, null, false); + } + + /** + * Runs commands and return output + * + * @param commands The commands to run + * @return Output of the commands, or null in case of an error + */ + public static List run(List commands) { + return Shell.run("sh", commands.toArray(new String[commands.size()]), null, false); + } + + /** + * Runs commands and return output + * + * @param commands The commands to run + * @return Output of the commands, or null in case of an error + */ + public static List run(String[] commands) { + return Shell.run("sh", commands, null, false); + } + } + + /** + * This class provides utility functions to easily execute commands using SU + * (root shell), as well as detecting whether or not root is available, and + * if so which version. + */ + public static class SU { + /** + * Runs command as root (if available) and return output + * + * @param command The command to run + * @return Output of the command, or null if root isn't available or in case of an error + */ + public static List run(String command) { + return Shell.run("su", new String[] { command }, null, false); + } + + /** + * Runs commands as root (if available) and return output + * + * @param commands The commands to run + * @return Output of the commands, or null if root isn't available or in case of an error + */ + public static List run(List commands) { + return Shell.run("su", commands.toArray(new String[commands.size()]), null, false); + } + + /** + * Runs commands as root (if available) and return output + * + * @param commands The commands to run + * @return Output of the commands, or null if root isn't available or in case of an error + */ + public static List run(String[] commands) { + return Shell.run("su", commands, null, false); + } + + /** + * Detects whether or not superuser access is available, by checking the output + * of the "id" command if available, checking if a shell runs at all otherwise + * + * @return True if superuser access available + */ + public static boolean available() { + // this is only one of many ways this can be done + + List ret = run(Shell.availableTestCommands); + return Shell.parseAvailableResult(ret, true); + } + + /** + *

Detects the version of the su binary installed (if any), if supported by the binary. + * Most binaries support two different version numbers, the public version that is + * displayed to users, and an internal version number that is used for version number + * comparisons. Returns null if su not available or retrieving the version isn't supported.

+ * + *

Note that su binary version and GUI (APK) version can be completely different.

+ * + * @param internal Request human-readable version or application internal version + * @return String containing the su version or null + */ + public static String version(boolean internal) { + // we add an additional exit call, because the command + // line options are not available in all su versions, + // thus potentially launching a shell instead + + List ret = Shell.run("sh", new String[] { + internal ? "su -V" : "su -v", + "exit" + }, null, false); + if (ret == null) return null; + + for (String line : ret) { + if (!internal) { + if (line.contains(".")) return line; + } else { + try { + if (Integer.parseInt(line) > 0) return line; + } catch(NumberFormatException e) { + } + } + } + return null; + } + } + + /** + * Command result callback, notifies the recipient of the completion of a command + * block, including the (last) exit code, and the full output + */ + public interface OnCommandResultListener { + /** + *

Command result callback

+ * + *

Depending on how and on which thread the shell was created, this callback + * may be executed on one of the gobbler threads. In that case, it is important + * the callback returns as quickly as possible, as delays in this callback may + * pause the native process or even result in a deadlock

+ * + *

See {@link Shell.Interactive} for threading details

+ * + * @param commandCode Value previously supplied to addCommand + * @param exitCode Exit code of the last command in the block + * @param output All output generated by the command block + */ + public void onCommandResult(int commandCode, int exitCode, List output); + + // for any onCommandResult callback + public static final int WATCHDOG_EXIT = -1; + public static final int SHELL_DIED = -2; + + // for Interactive.open() callbacks only + public static final int SHELL_EXEC_FAILED = -3; + public static final int SHELL_WRONG_UID = -4; + public static final int SHELL_RUNNING = 0; + } + + /** + * Internal class to store command block properties + */ + private static class Command { + private static int commandCounter = 0; + + private final String[] commands; + private final int code; + private final OnCommandResultListener onCommandResultListener; + private final String marker; + + public Command(String[] commands, int code, OnCommandResultListener onCommandResultListener) { + this.commands = commands; + this.code = code; + this.onCommandResultListener = onCommandResultListener; + this.marker = UUID.randomUUID().toString() + String.format("-%08x", ++commandCounter); + } + } + + /** + * Builder class for {@link Shell.Interactive} + */ + public static class Builder { + private Handler handler = null; + private boolean autoHandler = true; + private String shell = "sh"; + private boolean wantSTDERR = false; + private List commands = new LinkedList(); + private Map environment = new HashMap(); + private OnLineListener onSTDOUTLineListener = null; + private OnLineListener onSTDERRLineListener = null; + private int watchdogTimeout = 0; + + /** + *

Set a custom handler that will be used to post all callbacks to

+ * + *

See {@link Shell.Interactive} for further details on threading and handlers

+ * + * @param handler Handler to use + * @return This Builder object for method chaining + */ + public Builder setHandler(Handler handler) { this.handler = handler; return this; } + + /** + *

Automatically create a handler if possible ? Default to true

+ * + *

See {@link Shell.Interactive} for further details on threading and handlers

+ * + * @param autoHandler Auto-create handler ? + * @return This Builder object for method chaining + */ + public Builder setAutoHandler(boolean autoHandler) { this.autoHandler = autoHandler; return this; } + + /** + * Set shell binary to use. Usually "sh" or "su", do not use a full path + * unless you have a good reason to + * + * @param shell Shell to use + * @return This Builder object for method chaining + */ + public Builder setShell(String shell) { this.shell = shell; return this; } + + /** + * Convenience function to set "sh" as used shell + * + * @return This Builder object for method chaining + */ + public Builder useSH() { return setShell("sh"); } + + /** + * Convenience function to set "su" as used shell + * + * @return This Builder object for method chaining + */ + public Builder useSU() { return setShell("su"); } + + /** + * Set if error output should be appended to command block result output + * + * @param wantSTDERR Want error output ? + * @return This Builder object for method chaining + */ + public Builder setWantSTDERR(boolean wantSTDERR) { this.wantSTDERR = wantSTDERR; return this; } + + /** + * Add or update an environment variable + * + * @param key Key of the environment variable + * @param value Value of the environment variable + * @return This Builder object for method chaining + */ + public Builder addEnvironment(String key, String value) { environment.put(key, value); return this; } + + /** + * Add or update environment variables + * + * @param addEnvironment Map of environment variables + * @return This Builder object for method chaining + */ + public Builder addEnvironment(Map addEnvironment) { environment.putAll(addEnvironment); return this; } + + /** + * Add a command to execute + * + * @param command Command to execute + * @return This Builder object for method chaining + */ + public Builder addCommand(String command) { return addCommand(command, 0, null); } + + /** + *

Add a command to execute, with a callback to be called on completion

+ * + *

The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details

+ * + * @param command Command to execute + * @param code User-defined value passed back to the callback + * @param onCommandResultListener Callback to be called on completion + * @return This Builder object for method chaining + */ + public Builder addCommand(String command, int code, OnCommandResultListener onCommandResultListener) { return addCommand(new String[] { command }, code, onCommandResultListener); } + + /** + * Add commands to execute + * + * @param commands Commands to execute + * @return This Builder object for method chaining + */ + public Builder addCommand(List commands) { return addCommand(commands, 0, null); } + + /** + *

Add commands to execute, with a callback to be called on completion (of all commands)

+ * + *

The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details

+ * + * @param commands Commands to execute + * @param code User-defined value passed back to the callback + * @param onCommandResultListener Callback to be called on completion (of all commands) + * @return This Builder object for method chaining + */ + public Builder addCommand(List commands, int code, OnCommandResultListener onCommandResultListener) { return addCommand(commands.toArray(new String[commands.size()]), code, onCommandResultListener); } + + /** + * Add commands to execute + * + * @param commands Commands to execute + * @return This Builder object for method chaining + */ + public Builder addCommand(String[] commands) { return addCommand(commands, 0, null); } + + /** + *

Add commands to execute, with a callback to be called on completion (of all commands)

+ * + *

The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details

+ * + * @param commands Commands to execute + * @param code User-defined value passed back to the callback + * @param onCommandResultListener Callback to be called on completion (of all commands) + * @return This Builder object for method chaining + */ + public Builder addCommand(String[] commands, int code, OnCommandResultListener onCommandResultListener) { this.commands.add(new Command(commands, code, onCommandResultListener)); return this; } + + /** + *

Set a callback called for every line output to STDOUT by the shell

+ * + *

The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details

+ * + * @param onLineListener Callback to be called for each line + * @return This Builder object for method chaining + */ + public Builder setOnSTDOUTLineListener(OnLineListener onLineListener) { this.onSTDOUTLineListener = onLineListener; return this; } + + /** + *

Set a callback called for every line output to STDERR by the shell

+ * + *

The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details

+ * + * @param onLineListener Callback to be called for each line + * @return This Builder object for method chaining + */ + public Builder setOnSTDERRLineListener(OnLineListener onLineListener) { this.onSTDERRLineListener = onLineListener; return this; } + + /** + *

Enable command timeout callback

+ * + *

This will invoke the onCommandResult() callback with exitCode WATCHDOG_EXIT if a command takes longer than watchdogTimeout + * seconds to complete.

+ * + *

If a watchdog timeout occurs, it generally means that the Interactive session is out of sync with the shell process. The + * caller should close the current session and open a new one.

+ * + * @param watchdogTimeout Timeout, in seconds; 0 to disable + * @return This Builder object for method chaining + */ + public Builder setWatchdogTimeout(int watchdogTimeout) { this.watchdogTimeout = watchdogTimeout; return this; } + + /** + *

Enable/disable reduced logcat output

+ * + *

Note that this is a global setting

+ * + * @param useMinimal true for reduced output, false for full output + * @return This Builder object for method chaining + */ + public Builder setMinimalLogging(boolean useMinimal) { + Debug.setLogTypeEnabled(Debug.LOG_COMMAND | Debug.LOG_OUTPUT, !useMinimal); + return this; + } + + /** + * Construct a {@link Shell.Interactive} instance, and start the shell + */ + public Interactive open() { return new Interactive(this, null); } + + /** + * Construct a {@link Shell.Interactive} instance, try to start the shell, and + * call onCommandResultListener to report success or failure + * + * @param onCommandResultListener Callback to return shell open status + */ + public Interactive open(OnCommandResultListener onCommandResultListener) { + return new Interactive(this, onCommandResultListener); + } + } + + /** + *

An interactive shell - initially created with {@link Shell.Builder} - that + * executes blocks of commands you supply in the background, optionally calling + * callbacks as each block completes.

+ * + *

STDERR output can be supplied as well, but due to compatibility with older + * Android versions, wantSTDERR is not implemented using redirectErrorStream, + * but rather appended to the output. STDOUT and STDERR are thus not guaranteed to + * be in the correct order in the output.

+ * + *

Note as well that the close() and waitForIdle() methods will intentionally + * crash when run in debug mode from the main thread of the application. Any blocking + * call should be run from a background thread.

+ * + *

When in debug mode, the code will also excessively log the commands passed to + * and the output returned from the shell.

+ * + *

Though this function uses background threads to gobble STDOUT and STDERR so + * a deadlock does not occur if the shell produces massive output, the output is + * still stored in a List<String>, and as such doing something like 'ls -lR /' + * will probably have you run out of memory when using a + * {@link Shell.OnCommandResultListener}. A work-around is to not supply this callback, + * but using (only) {@link Shell.Builder#setOnSTDOUTLineListener(OnLineListener)}. This + * way, an internal buffer will not be created and wasting your memory.

+ * + *

Callbacks, threads and handlers

+ * + *

On which thread the callbacks execute is dependent on your initialization. You can + * supply a custom Handler using {@link Shell.Builder#setHandler(Handler)} if needed. + * If you do not supply a custom Handler - unless you set {@link Shell.Builder#setAutoHandler(boolean)} + * to false - a Handler will be auto-created if the thread used for instantiation + * of the object has a Looper.

+ * + *

If no Handler was supplied and it was also not auto-created, all callbacks will + * be called from either the STDOUT or STDERR gobbler threads. These are important + * threads that should be blocked as little as possible, as blocking them may in rare + * cases pause the native process or even create a deadlock.

+ * + *

The main thread must certainly have a Looper, thus if you call {@link Shell.Builder#open()} + * from the main thread, a handler will (by default) be auto-created, and all the callbacks + * will be called on the main thread. While this is often convenient and easy to code with, + * you should be aware that if your callbacks are 'expensive' to execute, this may negatively + * impact UI performance.

+ * + *

Background threads usually do not have a Looper, so calling {@link Shell.Builder#open()} + * from such a background thread will (by default) result in all the callbacks being executed + * in one of the gobbler threads. You will have to make sure the code you execute in these callbacks + * is thread-safe.

+ */ + public static class Interactive { + private final Handler handler; + private final boolean autoHandler; + private final String shell; + private final boolean wantSTDERR; + private final List commands; + private final Map environment; + private final OnLineListener onSTDOUTLineListener; + private final OnLineListener onSTDERRLineListener; + private int watchdogTimeout; + + private Process process = null; + private DataOutputStream STDIN = null; + private StreamGobbler STDOUT = null; + private StreamGobbler STDERR = null; + private ScheduledThreadPoolExecutor watchdog = null; + + private volatile boolean running = false; + private volatile boolean idle = true; // read/write only synchronized + private volatile boolean closed = true; + private volatile int callbacks = 0; + private volatile int watchdogCount; + + private Object idleSync = new Object(); + private Object callbackSync = new Object(); + + private volatile int lastExitCode = 0; + private volatile String lastMarkerSTDOUT = null; + private volatile String lastMarkerSTDERR = null; + private volatile Command command = null; + private volatile List buffer = null; + + /** + * The only way to create an instance: Shell.Builder::open() + * + * @param builder Builder class to take values from + */ + private Interactive(final Builder builder, final OnCommandResultListener onCommandResultListener) { + autoHandler = builder.autoHandler; + shell = builder.shell; + wantSTDERR = builder.wantSTDERR; + commands = builder.commands; + environment = builder.environment; + onSTDOUTLineListener = builder.onSTDOUTLineListener; + onSTDERRLineListener = builder.onSTDERRLineListener; + watchdogTimeout = builder.watchdogTimeout; + + // If a looper is available, we offload the callbacks from the gobbling threads + // to whichever thread created us. Would normally do this in open(), + // but then we could not declare handler as final + if ((Looper.myLooper() != null) && (builder.handler == null) && autoHandler) { + handler = new Handler(); + } else { + handler = builder.handler; + } + + boolean ret = open(); + if (onCommandResultListener == null) { + return; + } else if (ret == false) { + onCommandResultListener.onCommandResult(0, OnCommandResultListener.SHELL_EXEC_FAILED, null); + return; + } + + // Allow up to 60 seconds for SuperSU/Superuser dialog, then enable the user-specified + // timeout for all subsequent operations + watchdogTimeout = 60; + addCommand(Shell.availableTestCommands, 0, new OnCommandResultListener() { + public void onCommandResult(int commandCode, int exitCode, List output) { + if (exitCode == OnCommandResultListener.SHELL_RUNNING && + Shell.parseAvailableResult(output, shell.equals("su")) != true) { + // shell is up, but it's brain-damaged + exitCode = OnCommandResultListener.SHELL_WRONG_UID; + } + watchdogTimeout = builder.watchdogTimeout; + onCommandResultListener.onCommandResult(0, exitCode, output); + } + }); + } + + @Override + protected void finalize() throws Throwable { + if (!closed && Debug.getSanityChecksEnabledEffective()) { + // waste of resources + Debug.log(ShellNotClosedException.EXCEPTION_NOT_CLOSED); + throw new ShellNotClosedException(); + } + super.finalize(); + } + + /** + * Add a command to execute + * + * @param command Command to execute + */ + public void addCommand(String command) { addCommand(command, 0, null); } + + /** + *

Add a command to execute, with a callback to be called on completion

+ * + *

The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details

+ * + * @param command Command to execute + * @param code User-defined value passed back to the callback + * @param onCommandResultListener Callback to be called on completion + */ + public void addCommand(String command, int code, OnCommandResultListener onCommandResultListener) { addCommand(new String[] { command }, code, onCommandResultListener); } + + /** + * Add commands to execute + * + * @param commands Commands to execute + */ + public void addCommand(List commands) { addCommand(commands, 0, null); } + + /** + *

Add commands to execute, with a callback to be called on completion (of all commands)

+ * + *

The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details

+ * + * @param commands Commands to execute + * @param code User-defined value passed back to the callback + * @param onCommandResultListener Callback to be called on completion (of all commands) + */ + public void addCommand(List commands, int code, OnCommandResultListener onCommandResultListener) { addCommand(commands.toArray(new String[commands.size()]), code, onCommandResultListener); } + + /** + * Add commands to execute + * + * @param commands Commands to execute + */ + public void addCommand(String[] commands) { addCommand(commands, 0, null); } + + /** + *

Add commands to execute, with a callback to be called on completion (of all commands)

+ * + *

The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details

+ * + * @param commands Commands to execute + * @param code User-defined value passed back to the callback + * @param onCommandResultListener Callback to be called on completion (of all commands) + */ + public synchronized void addCommand(String[] commands, int code, OnCommandResultListener onCommandResultListener) { + if (running) { + this.commands.add(new Command(commands, code, onCommandResultListener)); + runNextCommand(); + } + } + + /** + * Run the next command if any and if ready, signals idle state if no commands left + */ + private void runNextCommand() { + runNextCommand(true); + } + + /** + * Called from a ScheduledThreadPoolExecutor timer thread every second when there is an outstanding command + */ + private synchronized void handleWatchdog() { + final int exitCode; + + if (watchdog == null) return; + + if (!isRunning()) { + exitCode = OnCommandResultListener.SHELL_DIED; + Debug.log(String.format("[%s%%] SHELL_DIED", shell.toUpperCase(Locale.ENGLISH))); + } else if (watchdogCount++ < watchdogTimeout) { + return; + } else { + exitCode = OnCommandResultListener.WATCHDOG_EXIT; + Debug.log(String.format("[%s%%] WATCHDOG_EXIT", shell.toUpperCase(Locale.ENGLISH))); + } + + if (handler != null) { + final Command fCommand = command; + final List fBuffer = buffer; + startCallback(); + handler.post(new Runnable() { + @Override + public void run() { + try { + fCommand.onCommandResultListener.onCommandResult(fCommand.code, exitCode, fBuffer); + } finally { + endCallback(); + } + } + }); + } + + // prevent multiple callbacks for the same command + command = null; + buffer = null; + idle = true; + + watchdog.shutdown(); + watchdog = null; + kill(); + } + + /** + * Start the periodic timer when a command is submitted + */ + private void startWatchdog() { + if (watchdogTimeout == 0) { + return; + } + watchdogCount = 0; + watchdog = new ScheduledThreadPoolExecutor(1); + watchdog.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + handleWatchdog(); + } + }, 1, 1, TimeUnit.SECONDS); + } + + /** + * Disable the watchdog timer upon command completion + */ + private void stopWatchdog() { + if (watchdog != null) { + watchdog.shutdownNow(); + watchdog = null; + } + } + + /** + * Run the next command if any and if ready + * + * @param notifyIdle signals idle state if no commands left ? + */ + private void runNextCommand(boolean notifyIdle) { + // must always be called from a synchronized method + + boolean running = isRunning(); + if (!running) idle = true; + + if (running && idle && (commands.size() > 0)) { + Command command = commands.get(0); + commands.remove(0); + + buffer = null; + lastExitCode = 0; + lastMarkerSTDOUT = null; + lastMarkerSTDERR = null; + + if (command.commands.length > 0) { + try { + if (command.onCommandResultListener != null) { + // no reason to store the output if we don't have an OnCommandResultListener + // user should catch the output with an OnLineListener in this case + buffer = Collections.synchronizedList(new ArrayList()); + } + + idle = false; + this.command = command; + startWatchdog(); + for (String write : command.commands) { + Debug.logCommand(String.format("[%s+] %s", shell.toUpperCase(Locale.ENGLISH), write)); + STDIN.writeBytes(write + "\n"); + } + STDIN.writeBytes("echo " + command.marker + " $?\n"); + STDIN.writeBytes("echo " + command.marker + " >&2\n"); + STDIN.flush(); + } catch (IOException e) { + } + } else { + runNextCommand(false); + } + } + + if (idle && notifyIdle) { + synchronized(idleSync) { + idleSync.notifyAll(); + } + } + } + + /** + * Processes a STDOUT/STDERR line containing an end/exitCode marker + */ + private synchronized void processMarker() { + if (command.marker.equals(lastMarkerSTDOUT) && (command.marker.equals(lastMarkerSTDERR))) { + if (command.onCommandResultListener != null) { + if (buffer != null) { + if (handler != null) { + final List fBuffer = buffer; + final int fExitCode = lastExitCode; + final Command fCommand = command; + + startCallback(); + handler.post(new Runnable() { + @Override + public void run() { + try { + fCommand.onCommandResultListener.onCommandResult(fCommand.code, fExitCode, fBuffer); + } finally { + endCallback(); + } + } + }); + } else { + command.onCommandResultListener.onCommandResult(command.code, lastExitCode, buffer); + } + } + } + + stopWatchdog(); + command = null; + buffer = null; + idle = true; + runNextCommand(); + } + } + + /** + * Process a normal STDOUT/STDERR line + * + * @param line Line to process + * @param listener Callback to call or null + */ + private synchronized void processLine(String line, OnLineListener listener) { + if (listener != null) { + if (handler != null) { + final String fLine = line; + final OnLineListener fListener = listener; + + startCallback(); + handler.post(new Runnable() { + @Override + public void run() { + try { + fListener.onLine(fLine); + } finally { + endCallback(); + } + } + }); + } else { + listener.onLine(line); + } + } + } + + /** + * Add line to internal buffer + * + * @param line Line to add + */ + private synchronized void addBuffer(String line) { + if (buffer != null) { + buffer.add(line); + } + } + + /** + * Increase callback counter + */ + private void startCallback() { + synchronized (callbackSync) { + callbacks++; + } + } + + /** + * Decrease callback counter, signals callback complete state when dropped to 0 + */ + private void endCallback() { + synchronized (callbackSync) { + callbacks--; + if (callbacks == 0) { + callbackSync.notifyAll(); + } + } + } + + /** + * Internal call that launches the shell, starts gobbling, and starts executing commands. + * See {@link Shell.Interactive} + * + * @return Opened successfully ? + */ + private synchronized boolean open() { + Debug.log(String.format("[%s%%] START", shell.toUpperCase(Locale.ENGLISH))); + + try { + // setup our process, retrieve STDIN stream, and STDOUT/STDERR gobblers + if (environment.size() == 0) { + process = Runtime.getRuntime().exec(shell); + } else { + Map newEnvironment = new HashMap(); + newEnvironment.putAll(System.getenv()); + newEnvironment.putAll(environment); + int i = 0; + String[] env = new String[newEnvironment.size()]; + for (Map.Entry entry : newEnvironment.entrySet()) { + env[i] = entry.getKey() + "=" + entry.getValue(); + i++; + } + process = Runtime.getRuntime().exec(shell, env); + } + + STDIN = new DataOutputStream(process.getOutputStream()); + STDOUT = new StreamGobbler(shell.toUpperCase(Locale.ENGLISH) + "-", process.getInputStream(), new OnLineListener() { + @Override + public void onLine(String line) { + synchronized (Interactive.this) { + if (command == null) { + return; + } + if (line.startsWith(command.marker)) { + try { + lastExitCode = Integer.valueOf(line.substring(command.marker.length() + 1), 10); + } catch (Exception e) { + } + lastMarkerSTDOUT = command.marker; + processMarker(); + } else { + addBuffer(line); + processLine(line, onSTDOUTLineListener); + } + } + } + }); + STDERR = new StreamGobbler(shell.toUpperCase(Locale.ENGLISH) + "*", process.getErrorStream(), new OnLineListener() { + @Override + public void onLine(String line) { + synchronized (Interactive.this) { + if (command == null) { + return; + } + if (line.startsWith(command.marker)) { + lastMarkerSTDERR = command.marker; + processMarker(); + } else { + if (wantSTDERR) addBuffer(line); + processLine(line, onSTDERRLineListener); + } + } + } + }); + + // start gobbling and write our commands to the shell + STDOUT.start(); + STDERR.start(); + + running = true; + closed = false; + + runNextCommand(); + + return true; + } catch (IOException e) { + // shell probably not found + return false; + } + } + + /** + * Close shell and clean up all resources. Call this when you are done with the shell. + * If the shell is not idle (all commands completed) you should not call this method + * from the main UI thread because it may block for a long time. This method will + * intentionally crash your app (if in debug mode) if you try to do this anyway. + */ + public void close() { + boolean _idle = isIdle(); // idle must be checked synchronized + + synchronized (this) { + if (!running) return; + running = false; + closed = true; + } + + // This method should not be called from the main thread unless the shell is idle + // and can be cleaned up with (minimal) waiting. Only throw in debug mode. + if (!_idle && Debug.getSanityChecksEnabledEffective() && Debug.onMainThread()) { + Debug.log(ShellOnMainThreadException.EXCEPTION_NOT_IDLE); + throw new ShellOnMainThreadException(ShellOnMainThreadException.EXCEPTION_NOT_IDLE); + } + + if (!_idle) waitForIdle(); + + try { + STDIN.writeBytes("exit\n"); + STDIN.flush(); + + // wait for our process to finish, while we gobble away in the background + process.waitFor(); + + // make sure our threads are done gobbling, our streams are closed, and the process is + // destroyed - while the latter two shouldn't be needed in theory, and may even produce + // warnings, in "normal" Java they are required for guaranteed cleanup of resources, so + // lets be safe and do this on Android as well + try { + STDIN.close(); + } catch (IOException e) { + } + STDOUT.join(); + STDERR.join(); + stopWatchdog(); + process.destroy(); + } catch (IOException e) { + // shell probably not found + } catch (InterruptedException e) { + // this should really be re-thrown + } + + Debug.log(String.format("[%s%%] END", shell.toUpperCase(Locale.ENGLISH))); + } + + /** + * Try to clean up as much as possible from a shell that's gotten itself wedged. + * Hopefully the StreamGobblers will croak on their own when the other side of + * the pipe is closed. + */ + private synchronized void kill() { + running = false; + closed = true; + + try { + STDIN.close(); + } catch (IOException e) { + } + process.destroy(); + } + + /** + * Is our shell still running ? + * + * @return Shell running ? + */ + public boolean isRunning() { + try { + // if this throws, we're still running + process.exitValue(); + return false; + } catch (IllegalThreadStateException e) { + } + return true; + } + + /** + * Have all commands completed executing ? + * + * @return Shell idle ? + */ + public synchronized boolean isIdle() { + if (!isRunning()) { + idle = true; + synchronized(idleSync) { + idleSync.notifyAll(); + } + } + return idle; + } + + /** + *

Wait for idle state. As this is a blocking call, you should not call it from the main UI thread. + * If you do so and debug mode is enabled, this method will intentionally crash your app.

+ * + *

If not interrupted, this method will not return until all commands have finished executing. + * Note that this does not necessarily mean that all the callbacks have fired yet.

+ * + *

If no Handler is used, all callbacks will have been executed when this method returns. If + * a Handler is used, and this method is called from a different thread than associated with the + * Handler's Looper, all callbacks will have been executed when this method returns as well. + * If however a Handler is used but this method is called from the same thread as associated + * with the Handler's Looper, there is no way to know.

+ * + *

In practice this means that in most simple cases all callbacks will have completed when this + * method returns, but if you actually depend on this behavior, you should make certain this is + * indeed the case.

+ * + *

See {@link Shell.Interactive} for further details on threading and handlers

+ * + * @return True if wait complete, false if wait interrupted + */ + public boolean waitForIdle() { + if (Debug.getSanityChecksEnabledEffective() && Debug.onMainThread()) { + Debug.log(ShellOnMainThreadException.EXCEPTION_WAIT_IDLE); + throw new ShellOnMainThreadException(ShellOnMainThreadException.EXCEPTION_WAIT_IDLE); + } + + if (isRunning()) { + synchronized (idleSync) { + while (!idle) { + try { + idleSync.wait(); + } catch (InterruptedException e) { + return false; + } + } + } + + if ( + (handler != null) && + (handler.getLooper() != null) && + (handler.getLooper() != Looper.myLooper()) + ) { + // If the callbacks are posted to a different thread than this one, we can wait until + // all callbacks have called before returning. If we don't use a Handler at all, + // the callbacks are already called before we get here. If we do use a Handler but + // we use the same Looper, waiting here would actually block the callbacks from being + // called + + synchronized (callbackSync) { + while (callbacks > 0) { + try { + callbackSync.wait(); + } catch (InterruptedException e) { + return false; + } + } + } + } + } + + return true; + } + + /** + * Are we using a Handler to post callbacks ? + * + * @return Handler used ? + */ + public boolean hasHandler() { + return (handler != null); + } + } +} diff --git a/libraries/libsuperuser/src/eu/chainfire/libsuperuser/ShellNotClosedException.java b/libraries/libsuperuser/src/eu/chainfire/libsuperuser/ShellNotClosedException.java new file mode 100644 index 00000000..6b2d7ae1 --- /dev/null +++ b/libraries/libsuperuser/src/eu/chainfire/libsuperuser/ShellNotClosedException.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2012-2013 Jorrit "Chainfire" Jongma + * + * 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 eu.chainfire.libsuperuser; + +/** + * Exception class used to notify developer that a shell was not close()d + */ +@SuppressWarnings("serial") +public class ShellNotClosedException extends RuntimeException { + public static final String EXCEPTION_NOT_CLOSED = "Application did not close() interactive shell"; + + public ShellNotClosedException() { + super(EXCEPTION_NOT_CLOSED); + } +} diff --git a/libraries/libsuperuser/src/eu/chainfire/libsuperuser/ShellOnMainThreadException.java b/libraries/libsuperuser/src/eu/chainfire/libsuperuser/ShellOnMainThreadException.java new file mode 100644 index 00000000..70cf23dd --- /dev/null +++ b/libraries/libsuperuser/src/eu/chainfire/libsuperuser/ShellOnMainThreadException.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2012-2013 Jorrit "Chainfire" Jongma + * + * 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 eu.chainfire.libsuperuser; + +/** + * Exception class used to crash application when shell commands are executed + * from the main thread, and we are in debug mode. + */ +@SuppressWarnings("serial") +public class ShellOnMainThreadException extends RuntimeException { + public static final String EXCEPTION_COMMAND = "Application attempted to run a shell command from the main thread"; + public static final String EXCEPTION_NOT_IDLE = "Application attempted to wait for a non-idle shell to close on the main thread"; + public static final String EXCEPTION_WAIT_IDLE = "Application attempted to wait for a shell to become idle on the main thread"; + + public ShellOnMainThreadException(String message) { + super(message); + } +} diff --git a/libraries/libsuperuser/src/eu/chainfire/libsuperuser/StreamGobbler.java b/libraries/libsuperuser/src/eu/chainfire/libsuperuser/StreamGobbler.java new file mode 100644 index 00000000..893f75e0 --- /dev/null +++ b/libraries/libsuperuser/src/eu/chainfire/libsuperuser/StreamGobbler.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2012-2013 Jorrit "Chainfire" Jongma + * + * 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 eu.chainfire.libsuperuser; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.List; + +/** + * Thread utility class continuously reading from an InputStream + */ +public class StreamGobbler extends Thread { + /** + * Line callback interface + */ + public interface OnLineListener { + /** + *

Line callback

+ * + *

This callback should process the line as quickly as possible. + * Delays in this callback may pause the native process or even + * result in a deadlock

+ * + * @param line String that was gobbled + */ + public void onLine(String line); + } + + private String shell = null; + private BufferedReader reader = null; + private List writer = null; + private OnLineListener listener = null; + + /** + *

StreamGobbler constructor

+ * + *

We use this class because shell STDOUT and STDERR should be read as quickly as + * possible to prevent a deadlock from occurring, or Process.waitFor() never + * returning (as the buffer is full, pausing the native process)

+ * + * @param shell Name of the shell + * @param inputStream InputStream to read from + * @param outputList List to write to, or null + */ + public StreamGobbler(String shell, InputStream inputStream, List outputList) { + this.shell = shell; + reader = new BufferedReader(new InputStreamReader(inputStream)); + writer = outputList; + } + + /** + *

StreamGobbler constructor

+ * + *

We use this class because shell STDOUT and STDERR should be read as quickly as + * possible to prevent a deadlock from occurring, or Process.waitFor() never + * returning (as the buffer is full, pausing the native process)

+ * + * @param shell Name of the shell + * @param inputStream InputStream to read from + * @param onLineListener OnLineListener callback + */ + public StreamGobbler(String shell, InputStream inputStream, OnLineListener onLineListener) { + this.shell = shell; + reader = new BufferedReader(new InputStreamReader(inputStream)); + listener = onLineListener; + } + + @Override + public void run() { + // keep reading the InputStream until it ends (or an error occurs) + try { + String line = null; + while ((line = reader.readLine()) != null) { + Debug.logOutput(String.format("[%s] %s", shell, line)); + if (writer != null) writer.add(line); + if (listener != null) listener.onLine(line); + } + } catch (IOException e) { + } + + // make sure our stream is closed and resources will be freed + try { + reader.close(); + } catch (IOException e) { + } + } +} diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 00000000..54a9df8d --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +include ':DevDrawer' +include ':libraries:libsuperuser' \ No newline at end of file diff --git a/src/com/owentech/DevDrawer/activities/MainActivity.java b/src/com/owentech/DevDrawer/activities/MainActivity.java deleted file mode 100755 index bbe7b5c7..00000000 --- a/src/com/owentech/DevDrawer/activities/MainActivity.java +++ /dev/null @@ -1,281 +0,0 @@ -package com.owentech.DevDrawer.activities; - -import android.app.Activity; -import android.appwidget.AppWidgetManager; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.os.Build; -import android.os.Bundle; -import android.text.Editable; -import android.text.TextWatcher; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.widget.AutoCompleteTextView; -import android.widget.ImageView; -import android.widget.ListView; -import android.widget.RemoteViews; -import android.widget.Toast; - -import com.owentech.DevDrawer.R; -import com.owentech.DevDrawer.adapters.FilterListAdapter; -import com.owentech.DevDrawer.appwidget.DDWidgetProvider; -import com.owentech.DevDrawer.adapters.PartialMatchAdapter; -import com.owentech.DevDrawer.utils.AddAllAppsAsync; -import com.owentech.DevDrawer.utils.Constants; -import com.owentech.DevDrawer.utils.Database; - -import java.text.Collator; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -public class MainActivity extends Activity implements TextWatcher -{ - - Database database; - - ImageView addButton; - AutoCompleteTextView addPackageAutoComplete; - FilterListAdapter lviewAdapter; - ListView listView; - PartialMatchAdapter partialMatchAdapter; - - List appPackages; - - @Override - public void onCreate(Bundle state) - { - super.onCreate(state); - - setContentView(R.layout.main); - - // Set up ActionBar to use custom view (Robot Light font) - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB) - { - getActionBar().setDisplayShowTitleEnabled(false); - LayoutInflater inflater = LayoutInflater.from(this); - View customView = inflater.inflate(R.layout.custom_ab_title, null); - getActionBar().setCustomView(customView); - getActionBar().setDisplayShowCustomEnabled(true); - } - - // Create the database tables - database = new Database(this); - database.createTables(); - - // Setup view components - addButton = (ImageView) findViewById(R.id.addButton); - addPackageAutoComplete = (AutoCompleteTextView) findViewById(R.id.addPackageEditText); - listView = (ListView) findViewById(R.id.packagesListView); - - appPackages = getExistingPackages(); - - partialMatchAdapter = new PartialMatchAdapter(this, appPackages); - addPackageAutoComplete.setAdapter(partialMatchAdapter); - addPackageAutoComplete.addTextChangedListener(this); - - // Update the ListView from the database - updateListView(); - - addButton.setOnClickListener(new View.OnClickListener() - { - @Override - public void onClick(View view) - { - - if(addPackageAutoComplete.getText().length() != 0) // Check something entered - { - // Check filter doesn't exist - if(!database.doesFilterExist(addPackageAutoComplete.getText().toString())) - { - // Add the filter to the database - database.addFilterToDatabase(addPackageAutoComplete.getText().toString()); - - // Check existing apps and add to installed apps table if they match new filter - new AddAllAppsAsync(getApplicationContext(), addPackageAutoComplete.getText().toString()).execute(); - - addPackageAutoComplete.setText(""); - updateListView(); - - } - else - { - Toast.makeText(getApplicationContext(), "Filter already exists", Toast.LENGTH_SHORT).show(); - } - } - - } - }); - - - } - - @Override - public void onBackPressed() { - - Intent intent = getIntent(); - Bundle extras = intent.getExtras(); - int appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; - if (extras != null) { - appWidgetId = extras.getInt( - AppWidgetManager.EXTRA_APPWIDGET_ID, - AppWidgetManager.INVALID_APPWIDGET_ID); - } - - if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) { - AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this); - RemoteViews widget = DDWidgetProvider.getRemoteViews(this,appWidgetId); - appWidgetManager.updateAppWidget(appWidgetId, widget); - Intent resultValue = new Intent(); - resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); - setResult(RESULT_OK, resultValue); - finish(); - } - - super.onBackPressed(); - } - - // Method to re-populate the ListView - public void updateListView() - { - lviewAdapter = null; - lviewAdapter = new FilterListAdapter(this, database.getAllFiltersInDatabase()); - listView.setAdapter(lviewAdapter); - lviewAdapter.notifyDataSetChanged(); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) - { - super.onActivityResult(requestCode, resultCode, data); - - // Catch the return from the EditDialog - if(resultCode == Constants.EDIT_DIALOG_CHANGE) - { - Bundle bundle = data.getExtras(); - - Database database = new Database(this); - database.amendFilterEntryTo(bundle.getString("id"), bundle.getString("newText")); - updateListView(); - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) - { - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) - { - menu.add(0, Constants.MENU_SHORTCUT, 0, "Create Legacy Shortcut").setShowAsAction(MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW); - menu.add(0, Constants.MENU_SETTINGS, 0, "Settings").setIcon(R.drawable.ic_action_settings_white).setShowAsAction(MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW); - //menu.add(0, Constants.MENU_LOCALE_SWITCHER, 0, "Locale Switcher").setIcon(R.drawable.ic_action_globe).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); - } - else - { - menu.add(0, Constants.MENU_SHORTCUT, 0, "Create Shortcut"); - menu.add(0, Constants.MENU_SETTINGS, 0, "Settings"); - //menu.add(0, Constants.MENU_LOCALE_SWITCHER, 0, "Locale Switcher"); - } - return true; - } - - @Override - public boolean onMenuItemSelected(int featureId, MenuItem item) - { - switch(item.getItemId()) - { - case Constants.MENU_SHORTCUT: - { - addShortcut(this); - break; - } - case Constants.MENU_SETTINGS: - { - startActivity(new Intent(MainActivity.this, PrefActivity.class)); - break; - } - case Constants.MENU_LOCALE_SWITCHER: - { - - startActivity(new Intent(this, LocaleSwitcher.class)); - break; - } - } - return false; - } - - public void addShortcut(Context context) { - Intent shortcutIntent = new Intent(this, LegacyDialog.class); - shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - - Intent intent = new Intent(); - intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); - intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, "^DevDrawer"); - intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(context, R.drawable.shortcut_icon)); - intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); - String shortcutUri = intent.toUri(MODE_WORLD_WRITEABLE); - context.sendBroadcast(intent); - } - - @Override - protected void onStop() - { - super.onStop(); - //TODO is this really needed? It makes the prefActivity to close the app on backpress - // this is called to prevent a new app, back pressed, opening this activity - finish(); - } - - // Method to get all apps installed and return as List - List getExistingPackages() - { - // get installed applications - PackageManager pm = this.getPackageManager(); - Intent intent = new Intent(Intent.ACTION_MAIN, null); - intent.addCategory(Intent.CATEGORY_LAUNCHER); - List list= pm.queryIntentActivities(intent, - PackageManager.PERMISSION_GRANTED); - - Set appSet = new HashSet(); - - for (ResolveInfo rInfo : list) { - String appName = rInfo.activityInfo.applicationInfo.packageName.toString(); - appSet.add(appName); - while (appName.length() > 0) { - int lastIndex = appName.lastIndexOf("."); - if (lastIndex > 0) { - appName = appName.substring(0,lastIndex); - appSet.add(appName+".*"); - } else { - appName = ""; - } - } - } - - Collator collator = Collator.getInstance(); - ArrayList appList = new ArrayList(appSet); - Collections.sort(appList, collator); - return appList; - } - - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) - {} - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) - {} - - @Override - public void afterTextChanged(Editable editable) - { - partialMatchAdapter.getFilter().filter(editable.toString()); - } - -}