diff --git a/.gitignore b/.gitignore index 812ec5ac9..bf4761ab1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,42 @@ -/.settings -/bin -/gen -/.checkstyle -/.classpath -/.project -/lint.xml -/project.properties +#Android generated +bin +gen +releases +lint.xml + +#Eclipse +.project +.classpath +.settings +.checkstyle + +#IntelliJ IDEA +.idea/ +*.iml +*.ipr +*.iws +classes +gen-external-apklibs + +#Maven +target +release.properties +pom.xml.* + +#Ant +build.xml +ant.properties +local.properties +proguard.cfg +proguard-project.txt + +#Gradle +build/ +.gradle +gradle.properties + +#Other +.DS_Store +tmp +misc +out/ diff --git a/Android.mk b/Android.mk deleted file mode 100644 index 1fafe2a8f..000000000 --- a/Android.mk +++ /dev/null @@ -1,29 +0,0 @@ -# -# Copyright (C) 2012 The CyanogenMod Project -# -# 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. -# - -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_SRC_FILES := $(call all-subdir-java-files) - -LOCAL_PACKAGE_NAME := CMFileManager -LOCAL_CERTIFICATE := platform -LOCAL_PROGUARD_FLAG_FILES := proguard.flags -LOCAL_AAPT_INCLUDE_ALL_RESOURCES := true - -include $(BUILD_PACKAGE) - -include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/AndroidManifest.xml b/AndroidManifest.xml deleted file mode 100644 index b7c37b486..000000000 --- a/AndroidManifest.xml +++ /dev/null @@ -1,183 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Backbone/build.gradle b/Backbone/build.gradle new file mode 100644 index 000000000..5c1f7187a --- /dev/null +++ b/Backbone/build.gradle @@ -0,0 +1,160 @@ +/* Define each version code component separately to facilitate cleaner version increments. + Suggestion courtesy of Jake Wharton: + https://plus.google.com/108284392618554783657/posts/6f5TcVPRZij */ +def versionMajor = 0 +def versionMinor = 0 +def versionPatch = 0 +def versionBuild = 3 // bump for dogfood builds, public betas, etc. + +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:0.6.+' + } +} +apply plugin: 'android' + +/* mavenCentral() is specified at this level in addition to the buildscript block above so that + Gradle knows which repository to use when resolving the dependancies listed below. See answer in + http://stackoverflow.com/questions/16671277/maven-dependencies-with-android-studio-gradle */ +repositories { + mavenCentral() +} + +dependencies { + compile 'com.android.support:support-v4:13.0.0' + compile 'de.greenrobot:eventbus:2.2.0' + compile files('libs/dashclock-api-r2.0.jar') +} + +android { + compileSdkVersion 17 + buildToolsVersion "17.0.0" + + final SEARCHES_PROVIDER_DEBUG = "me.toolify.backbone.debug.providers.recentsearches" + final SEARCHES_PROVIDER_RELEASE = "me.toolify.backbone.providers.recentsearches" + final SEARCHES_PROVIDER_ALPHA = "me.toolify.backbone.alpha.providers.recentsearches" + + final BOOKMARKS_PROVIDER_DEBUG = "me.toolify.backbone.debug.providers.bookmarks" + final BOOKMARKS_PROVIDER_RELEASE = "me.toolify.backbone.providers.bookmarks" + final BOOKMARKS_PROVIDER_ALPHA = "me.toolify.backbone.alpha.providers.bookmarks" + + defaultConfig { + packageName "me.toolify.backbone" + versionCode versionMajor * 10000 + versionMinor * 1000 + versionPatch * 100 + versionBuild + versionName "${versionMajor}.${versionMinor}.${versionPatch}.${versionBuild}" + minSdkVersion 14 + targetSdkVersion 17 + } + + /* Define a separate signing config for release builds, and we'll load the specifics in later + if the Toolify private credentials are present */ + signingConfigs { + release + } + + /* The buildConfig strings below allow content providers for each of the specified + build types to be simultaneously installed. If we did not do this, debug and alpha builds + would have content providers with the same package name as the release version, and could not + be installed side-by-side. Courtesy of Cyril Mottier: + https://plus.google.com/118417777153109946393/posts/EATUmhntaCQ */ + buildTypes { + debug { + buildConfig "public static final String SEARCHES_PROVIDER_AUTHORITY = \"" + SEARCHES_PROVIDER_DEBUG + "\";" + + "public static final String BOOKMARKS_PROVIDER_AUTHORITY = \"" + BOOKMARKS_PROVIDER_DEBUG + "\";" + packageNameSuffix ".debug" + versionNameSuffix "-debug" + } + + release { + buildConfig "public static final String SEARCHES_PROVIDER_AUTHORITY = \"" + SEARCHES_PROVIDER_RELEASE + "\";" + + "public static final String BOOKMARKS_PROVIDER_AUTHORITY = \"" + BOOKMARKS_PROVIDER_RELEASE + "\";" + signingConfig signingConfigs.release + } + + alpha.initWith(buildTypes.release) // base alpha buildType on release buildType + alpha { + buildConfig "public static final String SEARCHES_PROVIDER_AUTHORITY = \"" + SEARCHES_PROVIDER_ALPHA + "\";" + + "public static final String BOOKMARKS_PROVIDER_AUTHORITY = \"" + BOOKMARKS_PROVIDER_ALPHA + "\";" + packageNameSuffix ".alpha" + versionNameSuffix "-alpha" + } + } + + /* Plug in private keystore credentials if they exist + ================================================== + Note! A gradle.properties (private and not checked into source control) file at the root of + the project contains the properties referenced below. Building the Debug variant will work + without the file, but release and alpha will not until the properties file and its + referenced keystore file are present + ================================================== */ + if (project.hasProperty('storeFile') && project.hasProperty('storePassword') + && project.hasProperty('keyAlias') && project.hasProperty('keyPassword')) { + + if (project.hasProperty('storeFile')) { + signingConfigs.release.storeFile = file(storeFile) + } + + if (project.hasProperty('storePassword')) { + signingConfigs.release.storePassword = storePassword + } + + if (project.hasProperty('keyAlias')) { + signingConfigs.release.keyAlias = keyAlias + } + + if (project.hasProperty('keyPassword')) { + signingConfigs.release.keyPassword = keyPassword + } + } + + sourceSets { + main { + resources.srcDirs = ['libs/color-picker-view/src','libs/android-syntax-highlight/src','themes/src'] + } + + // Merge in build type specific resources + debug { + res.srcDirs = [ + 'src/main/res', + 'src/debug/res'] + } + + release { + res.srcDirs = [ + 'src/main/res', + 'src/release/res'] + } + + alpha { + res.srcDirs = [ + 'src/main/res', + 'src/alpha/res'] + } + } + + /* Now that each of the Android build types have been defined (including all of the background + stuff that is run from the gradle android plugin), go back in and change the + outputFile name of each build type to match template: "Backbone-v0.0.0.0-alpha" */ + android.applicationVariants.all { variant -> + + /* Define the outputFile names here. Alpha and debug builds are assigned corresponding + suffixes, and release has no suffix */ + def newName + if (variant.buildType.versionNameSuffix) { + newName = "Backbone-v${android.defaultConfig.versionName}${variant.buildType.versionNameSuffix}.apk" + } else { + newName = "Backbone-v${android.defaultConfig.versionName}.apk" + } + + /* Assign the new outputFile name, and put the APK in the parent directory (parentFile) of + the of the default APK (which is Backbone/build/apk) */ + variant.outputFile = new File(variant.outputFile.parentFile, newName) + } +} + +task wrapper(type: Wrapper) { + gradleVersion = '1.8' +} diff --git a/Backbone/libs/dashclock-api-r2.0.jar b/Backbone/libs/dashclock-api-r2.0.jar new file mode 100644 index 000000000..18f7bf121 Binary files /dev/null and b/Backbone/libs/dashclock-api-r2.0.jar differ diff --git a/Backbone/src/alpha/AndroidManifest.xml b/Backbone/src/alpha/AndroidManifest.xml new file mode 100644 index 000000000..dcc717603 --- /dev/null +++ b/Backbone/src/alpha/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/Backbone/src/alpha/res/values/strings.xml b/Backbone/src/alpha/res/values/strings.xml new file mode 100644 index 000000000..fc6a708bd --- /dev/null +++ b/Backbone/src/alpha/res/values/strings.xml @@ -0,0 +1,22 @@ + + + + + + + Backbone α + + \ No newline at end of file diff --git a/Backbone/src/debug/AndroidManifest.xml b/Backbone/src/debug/AndroidManifest.xml new file mode 100644 index 000000000..5861c7815 --- /dev/null +++ b/Backbone/src/debug/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/Backbone/src/debug/res/values/strings.xml b/Backbone/src/debug/res/values/strings.xml new file mode 100644 index 000000000..5f1b8604c --- /dev/null +++ b/Backbone/src/debug/res/values/strings.xml @@ -0,0 +1,22 @@ + + + + + + + Backbone ∂ + + \ No newline at end of file diff --git a/Backbone/src/main/AndroidManifest.xml b/Backbone/src/main/AndroidManifest.xml new file mode 100644 index 000000000..74ed56c4d --- /dev/null +++ b/Backbone/src/main/AndroidManifest.xml @@ -0,0 +1,210 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CHANGELOG.md b/Backbone/src/main/CHANGELOG.md similarity index 100% rename from CHANGELOG.md rename to Backbone/src/main/CHANGELOG.md diff --git a/Backbone/src/main/ic_launcher-web.png b/Backbone/src/main/ic_launcher-web.png new file mode 100644 index 000000000..b93d9a3c5 Binary files /dev/null and b/Backbone/src/main/ic_launcher-web.png differ diff --git a/Backbone/src/main/java/com/afzkl/development/mColorPicker/drawables/AlphaPatternDrawable.java b/Backbone/src/main/java/com/afzkl/development/mColorPicker/drawables/AlphaPatternDrawable.java new file mode 100644 index 000000000..5b69945d8 --- /dev/null +++ b/Backbone/src/main/java/com/afzkl/development/mColorPicker/drawables/AlphaPatternDrawable.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2010 Daniel Nilsson + * + * 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 com.afzkl.development.mColorPicker.drawables; + +import android.graphics.*; +import android.graphics.Bitmap.Config; +import android.graphics.drawable.Drawable; + +/** + * This drawable that draws a simple white and gray chessboard pattern. + * It's pattern you will often see as a background behind a + * partly transparent image in many applications. + * @author Daniel Nilsson + */ +@SuppressWarnings("all") +public class AlphaPatternDrawable extends Drawable { + + private int mRectangleSize = 10; + + private final Paint mPaint = new Paint(); + private final Paint mPaintWhite = new Paint(); + private final Paint mPaintGray = new Paint(); + + private int numRectanglesHorizontal; + private int numRectanglesVertical; + + /** + * Bitmap in which the pattern will be cahched. + */ + private Bitmap mBitmap; + + public AlphaPatternDrawable(int rectangleSize) { + mRectangleSize = rectangleSize; + mPaintWhite.setColor(0xffffffff); + mPaintGray.setColor(0xffcbcbcb); + } + + @Override + public void draw(Canvas canvas) { + canvas.drawBitmap(mBitmap, null, getBounds(), mPaint); + } + + @Override + public int getOpacity() { + return 0; + } + + @Override + public void setAlpha(int alpha) { + throw new UnsupportedOperationException("Alpha is not supported by this drawwable."); + } + + @Override + public void setColorFilter(ColorFilter cf) { + throw new UnsupportedOperationException("ColorFilter is not supported by this drawwable."); + } + + @Override + protected void onBoundsChange(Rect bounds) { + super.onBoundsChange(bounds); + + int height = bounds.height(); + int width = bounds.width(); + + numRectanglesHorizontal = (int) Math.ceil((width / mRectangleSize)); + numRectanglesVertical = (int) Math.ceil(height / mRectangleSize); + + generatePatternBitmap(); + + } + + /** + * This will generate a bitmap with the pattern + * as big as the rectangle we were allow to draw on. + * We do this to chache the bitmap so we don't need to + * recreate it each time draw() is called since it + * takes a few milliseconds. + */ + private void generatePatternBitmap() { + + if (getBounds().width() <= 0 || getBounds().height() <= 0) { + return; + } + + mBitmap = Bitmap.createBitmap(getBounds().width(), getBounds().height(), Config.ARGB_8888); + Canvas canvas = new Canvas(mBitmap); + + Rect r = new Rect(); + boolean verticalStartWhite = true; + for (int i = 0; i <= numRectanglesVertical; i++) { + + boolean isWhite = verticalStartWhite; + for (int j = 0; j <= numRectanglesHorizontal; j++) { + + r.top = i * mRectangleSize; + r.left = j * mRectangleSize; + r.bottom = r.top + mRectangleSize; + r.right = r.left + mRectangleSize; + + canvas.drawRect(r, isWhite ? mPaintWhite : mPaintGray); + + isWhite = !isWhite; + } + + verticalStartWhite = !verticalStartWhite; + + } + + } + +} diff --git a/Backbone/src/main/java/com/afzkl/development/mColorPicker/views/ColorDialogView.java b/Backbone/src/main/java/com/afzkl/development/mColorPicker/views/ColorDialogView.java new file mode 100644 index 000000000..691d1dee6 --- /dev/null +++ b/Backbone/src/main/java/com/afzkl/development/mColorPicker/views/ColorDialogView.java @@ -0,0 +1,489 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project + * + * 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 com.afzkl.development.mColorPicker.views; + +import com.afzkl.development.mColorPicker.views.ColorPickerView.OnColorChangedListener; +import android.app.Activity; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.text.*; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.ScrollView; +import android.widget.TextView; + +/** + * A view use directly into a dialog. It contains a one {@link ColorPickerView} + * and two {@link com.afzkl.development.mColorPicker.views.ColorPanelView} (the current color and the new color) + */ +public class ColorDialogView extends RelativeLayout + implements OnColorChangedListener, TextWatcher { + + private static final int DEFAULT_MARGIN_DP = 16; + private static final int DEFAULT_PANEL_HEIGHT_DP = 32; + private static final int DEFAULT_TEXT_SIZE_SP = 12; + private static final int DEFAULT_LABEL_TEXT_SIZE_SP = 18; + + private ColorPickerView mPickerView; + private ColorPanelView mCurrentColorView; + private ColorPanelView mNewColorView; + private TextView tvCurrent; + private TextView tvNew; + private TextView tvColorLabel; + private EditText etColor; + + private String mCurrentLabelText = "Current:"; //$NON-NLS-1$ + private String mNewLabelText = "New:"; //$NON-NLS-1$ + + private String mColorLabelText = "Color:"; //$NON-NLS-1$ + + /** + * Constructor of ColorDialogView + * + * @param context The current context + */ + public ColorDialogView(Context context) { + this(context, null); + } + + /** + * Constructor of ColorDialogView + * + * @param context The current context + * @param attrs The attributes of the XML tag that is inflating the view. + */ + public ColorDialogView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + /** + * Constructor of ColorDialogView + * + * @param context The current context + * @param attrs The attributes of the XML tag that is inflating the view. + * @param defStyle The default style to apply to this view. If 0, no style + * will be applied (beyond what is included in the theme). This may + * either be an attribute resource, whose value will be retrieved + * from the current theme, or an explicit style resource. + */ + public ColorDialogView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + /** + * Method that initializes the view. This method loads all the necessary + * information and create an appropriate layout for the view + */ + private void init() { + // To fight color branding. + ((Activity)getContext()).getWindow().setFormat(PixelFormat.RGBA_8888); + + // Create the scrollview over the dialog + final int dlgMarging = (int)convertDpToPixel(DEFAULT_MARGIN_DP); + ScrollView sv = new ScrollView(getContext()); + sv.setId(generateViewId()); + RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams( + android.view.ViewGroup.LayoutParams.MATCH_PARENT, + android.view.ViewGroup.LayoutParams.WRAP_CONTENT); + lp.setMargins(dlgMarging, 0, dlgMarging, 0); + sv.setLayoutParams(lp); + sv.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_INSET); + + // Now the vertical layout + LinearLayout ll = new LinearLayout(getContext()); + ll.setId(generateViewId()); + lp = new RelativeLayout.LayoutParams( + android.view.ViewGroup.LayoutParams.MATCH_PARENT, + android.view.ViewGroup.LayoutParams.MATCH_PARENT); + ll.setLayoutParams(lp); + ll.setOrientation(LinearLayout.VERTICAL); + sv.addView(ll); + + // Creates the color input field + int id = createColorInput(ll); + + // Creates the color picker + id = createColorPicker(ll, id); + + // Creates the current color and new color panels + id = createColorsPanel(ll, id); + + // Add the scrollview + addView(sv); + + // Sets the input color + this.etColor.setText(toHex(this.mNewColorView.getColor())); + } + + /** + * Method that creates the color input + * + * @param parent The parent layout + */ + private int createColorInput(ViewGroup parent) { + final int dlgMarging = (int)convertDpToPixel(DEFAULT_MARGIN_DP); + LinearLayout.LayoutParams lp2 = new LinearLayout.LayoutParams( + android.view.ViewGroup.LayoutParams.WRAP_CONTENT, + android.view.ViewGroup.LayoutParams.MATCH_PARENT); + lp2.setMargins(0, 0, dlgMarging, 0); + this.tvColorLabel = new TextView(getContext()); + this.tvColorLabel.setText(this.mColorLabelText); + this.tvColorLabel.setTextSize(TypedValue.COMPLEX_UNIT_SP, DEFAULT_LABEL_TEXT_SIZE_SP); + this.tvColorLabel.setGravity(Gravity.BOTTOM | Gravity.LEFT); + this.tvColorLabel.setLayoutParams(lp2); + + lp2 = new LinearLayout.LayoutParams( + android.view.ViewGroup.LayoutParams.WRAP_CONTENT, + android.view.ViewGroup.LayoutParams.WRAP_CONTENT); + this.etColor = new EditText(getContext()); + this.etColor.setSingleLine(); + this.etColor.setGravity(Gravity.TOP | Gravity.LEFT); + this.etColor.setCursorVisible(true); + this.etColor.setImeOptions( + EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_FULLSCREEN); + this.etColor.setInputType( + InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); + this.etColor.setLayoutParams(lp2); + InputFilter[] filters = new InputFilter[2]; + filters[0] = new InputFilter.LengthFilter(8); + filters[1] = new InputFilter() { + @Override + public CharSequence filter(CharSequence source, int start, + int end, Spanned dest, int dstart, int dend) { + if (start >= end) return ""; //$NON-NLS-1$ + String s = source.subSequence(start, end).toString(); + StringBuilder sb = new StringBuilder(); + int cc = s.length(); + for (int i = 0; i < cc; i++) { + char c = s.charAt(i); + if ((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F')) { + sb.append(c); + } + } + return sb.toString().toUpperCase(); + } + }; + this.etColor.setFilters(filters); + this.etColor.addTextChangedListener(this); + + LinearLayout ll1 = new LinearLayout(getContext()); + ll1.setId(generateViewId()); + LayoutParams lp = new LayoutParams( + android.view.ViewGroup.LayoutParams.MATCH_PARENT, + android.view.ViewGroup.LayoutParams.WRAP_CONTENT); + lp.setMargins(dlgMarging, 0, dlgMarging, 0); + lp.addRule(RelativeLayout.ALIGN_PARENT_TOP); + ll1.setLayoutParams(lp); + ll1.addView(this.tvColorLabel); + ll1.addView(this.etColor); + parent.addView(ll1); + + return ll1.getId(); + } + + /** + * Method that creates the color picker + * + * @param parent The parent layout + * @param belowOf The anchor view + * @return id The layout id + */ + private int createColorPicker(ViewGroup parent, int belowOf) { + final int dlgMarging = (int)convertDpToPixel(DEFAULT_MARGIN_DP); + this.mPickerView = new ColorPickerView(getContext()); + this.mPickerView.setId(generateViewId()); + this.mPickerView.setOnColorChangedListener(this); + LayoutParams lp = new LayoutParams( + android.view.ViewGroup.LayoutParams.MATCH_PARENT, + android.view.ViewGroup.LayoutParams.WRAP_CONTENT); + lp.setMargins(dlgMarging, 0, dlgMarging, 0); + lp.addRule(RelativeLayout.BELOW, belowOf); + this.mPickerView.setLayoutParams(lp); + parent.addView(this.mPickerView); + return this.mPickerView.getId(); + } + + /** + * Method that creates the colors panel (current and new) + * + * @param parent The parent layout + * @param belowOf The anchor view + * @return id The layout id + */ + private int createColorsPanel(ViewGroup parent, int belowOf) { + final int dlgMarging = (int)convertDpToPixel(DEFAULT_MARGIN_DP); + final int panelHeight = (int)convertDpToPixel(DEFAULT_PANEL_HEIGHT_DP); + LinearLayout.LayoutParams lp2 = new LinearLayout.LayoutParams( + android.view.ViewGroup.LayoutParams.MATCH_PARENT, + android.view.ViewGroup.LayoutParams.MATCH_PARENT, + 1); + + // Titles + this.tvCurrent = new TextView(getContext()); + lp2 = new LinearLayout.LayoutParams( + android.view.ViewGroup.LayoutParams.MATCH_PARENT, + android.view.ViewGroup.LayoutParams.WRAP_CONTENT, + 1); + this.tvCurrent.setLayoutParams(lp2); + this.tvCurrent.setText(this.mCurrentLabelText); + this.tvCurrent.setTextSize(TypedValue.COMPLEX_UNIT_SP, DEFAULT_TEXT_SIZE_SP); + this.tvNew = new TextView(getContext()); + this.tvNew.setLayoutParams(lp2); + this.tvNew.setText(this.mNewLabelText); + this.tvNew.setTextSize(TypedValue.COMPLEX_UNIT_SP, DEFAULT_TEXT_SIZE_SP); + TextView sep1 = new TextView(getContext()); + lp2 = new LinearLayout.LayoutParams( + android.view.ViewGroup.LayoutParams.WRAP_CONTENT, + android.view.ViewGroup.LayoutParams.WRAP_CONTENT, + 0); + lp2.setMargins(dlgMarging, 0, dlgMarging, 0); + sep1.setLayoutParams(lp2); + sep1.setText(" "); //$NON-NLS-1$ + sep1.setTextSize(TypedValue.COMPLEX_UNIT_SP, DEFAULT_TEXT_SIZE_SP); + + LinearLayout ll1 = new LinearLayout(getContext()); + ll1.setId(generateViewId()); + LayoutParams lp = new LayoutParams( + android.view.ViewGroup.LayoutParams.MATCH_PARENT, + android.view.ViewGroup.LayoutParams.WRAP_CONTENT); + lp.setMargins(dlgMarging, 0, dlgMarging, dlgMarging/2); + lp.addRule(RelativeLayout.BELOW, belowOf); + ll1.setLayoutParams(lp); + ll1.addView(this.tvCurrent); + ll1.addView(sep1); + ll1.addView(this.tvNew); + parent.addView(ll1); + + // Color panels + lp2 = new LinearLayout.LayoutParams( + android.view.ViewGroup.LayoutParams.MATCH_PARENT, + android.view.ViewGroup.LayoutParams.WRAP_CONTENT, + 1); + this.mCurrentColorView = new ColorPanelView(getContext()); + this.mCurrentColorView.setLayoutParams(lp2); + this.mNewColorView = new ColorPanelView(getContext()); + this.mNewColorView.setLayoutParams(lp2); + TextView sep2 = new TextView(getContext()); + lp2 = new LinearLayout.LayoutParams( + android.view.ViewGroup.LayoutParams.WRAP_CONTENT, + android.view.ViewGroup.LayoutParams.WRAP_CONTENT, + 0); + lp2.setMargins(dlgMarging, 0, dlgMarging, 0); + sep2.setLayoutParams(lp2); + sep2.setText("-"); //$NON-NLS-1$ + sep2.setTextSize(TypedValue.COMPLEX_UNIT_SP, DEFAULT_TEXT_SIZE_SP); + + LinearLayout ll2 = new LinearLayout(getContext()); + ll2.setId(generateViewId()); + lp = new LayoutParams( + android.view.ViewGroup.LayoutParams.MATCH_PARENT, panelHeight); + lp.setMargins(dlgMarging, 0, dlgMarging, dlgMarging/2); + lp.addRule(RelativeLayout.BELOW, ll1.getId()); + ll2.setLayoutParams(lp); + ll2.addView(this.mCurrentColorView); + ll2.addView(sep2); + ll2.addView(this.mNewColorView); + parent.addView(ll2); + + return ll2.getId(); + } + + /** + * Method that returns the color of the picker + * + * @return The ARGB color + */ + public int getColor() { + return this.mPickerView.getColor(); + } + + /** + * Method that set the color of the picker + * + * @param argb The ARGB color + */ + public void setColor(int argb) { + setColor(argb, false); + } + + /** + * Method that set the color of the picker + * + * @param argb The ARGB color + * @param fromEditText If the call comes from the EditText + */ + private void setColor(int argb, boolean fromEditText) { + this.mPickerView.setColor(argb, false); + this.mCurrentColorView.setColor(argb); + this.mNewColorView.setColor(argb); + if (!fromEditText) { + this.etColor.setText(toHex(this.mNewColorView.getColor())); + } + } + + /** + * Method that display/hide the alpha slider + * + * @param show If the alpha slider should be shown + */ + public void showAlphaSlider(boolean show) { + this.mPickerView.setAlphaSliderVisible(show); + } + + /** + * Set the text that should be shown in the alpha slider. + * Set to null to disable text. + * + * @param text Text that should be shown. + */ + public void setAlphaSliderText(String text) { + this.mPickerView.setAlphaSliderText(text); + } + + /** + * Set the text that should be shown in the actual color panel. + * Set to null to disable text. + * + * @param text Text that should be shown. + */ + public void setCurrentColorText(String text) { + this.mCurrentLabelText = text; + this.tvCurrent.setText(this.mCurrentLabelText); + } + + /** + * Set the text that should be shown in the new color panel. + * Set to null to disable text. + * + * @param text Text that should be shown. + */ + public void setNewColorText(String text) { + this.mNewLabelText = text; + this.tvNew.setText(this.mNewLabelText); + } + + /** + * Set the text that should be shown in the label of the color input. + * Set to null to disable text. + * + * @param text Text that should be shown. + */ + public void setColorLabelText(String text) { + this.mColorLabelText = text; + this.tvColorLabel.setText(this.mColorLabelText); + } + + /** + * {@inheritDoc} + */ + @Override + public void onColorChanged(int color) { + this.mNewColorView.setColor(color); + this.etColor.removeTextChangedListener(this); + this.etColor.setText(toHex(this.mNewColorView.getColor())); + this.etColor.addTextChangedListener(this); + } + + /** + * {@inheritDoc} + */ + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) {/**NON BLOCK**/} + + /** + * {@inheritDoc} + */ + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) {/**NON BLOCK**/} + + /** + * {@inheritDoc} + */ + @Override + public void afterTextChanged(Editable s) { + if (s.length() == 8) { + try { + setColor(toARGB(s.toString()), true); + } catch (Exception e) {/**NON BLOCK**/} + } + } + + /** + * This method converts dp unit to equivalent device specific value in pixels. + * + * @param ctx The current context + * @param dp A value in dp (Device independent pixels) unit + * @return float A float value to represent Pixels equivalent to dp according to device + */ + private float convertDpToPixel(float dp) { + Resources resources = getContext().getResources(); + DisplayMetrics metrics = resources.getDisplayMetrics(); + return dp * (metrics.densityDpi / 160f); + } + + /** + * Method that converts an ARGB color to its hex string color representation + * + * @param argb The ARGB color + * @return String The hex string representation of the color + */ + private static String toHex(int argb) { + StringBuilder sb = new StringBuilder(); + sb.append(toHexString((byte)Color.alpha(argb))); + sb.append(toHexString((byte)Color.red(argb))); + sb.append(toHexString((byte)Color.green(argb))); + sb.append(toHexString((byte)Color.blue(argb))); + return sb.toString(); + } + + /** + * Method that converts an hex string color representation to an ARGB color + * + * @param hex The hex string representation of the color + * @return int The ARGB color + */ + private static int toARGB(String hex) { + return Color.parseColor("#" + hex); //$NON-NLS-1$ + } + + /** + * Method that converts a byte into its hex string representation + * + * @param v The value to convert + * @return String The hex string representation + */ + private static String toHexString(byte v) { + String hex = Integer.toHexString(v & 0xff); + if (hex.length() == 1) { + hex = "0" + hex; //$NON-NLS-1$ + } + return hex.toUpperCase(); + } +} diff --git a/Backbone/src/main/java/com/afzkl/development/mColorPicker/views/ColorPanelView.java b/Backbone/src/main/java/com/afzkl/development/mColorPicker/views/ColorPanelView.java new file mode 100644 index 000000000..51884aa5a --- /dev/null +++ b/Backbone/src/main/java/com/afzkl/development/mColorPicker/views/ColorPanelView.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2010 Daniel Nilsson + * + * 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 com.afzkl.development.mColorPicker.views; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; +import android.util.AttributeSet; +import android.view.View; +import com.afzkl.development.mColorPicker.drawables.AlphaPatternDrawable; + +/** + * This class draws a panel which which will be filled with a color which can be set. + * It can be used to show the currently selected color which you will get from + * the {@link com.afzkl.development.mColorPicker.views.ColorPickerView}. + * @author Daniel Nilsson + * + */ +@SuppressWarnings("all") +public class ColorPanelView extends View{ + + /** + * The width in pixels of the border + * surrounding the color panel. + */ + private final static float BORDER_WIDTH_PX = 1; + + private static float mDensity = 1f; + + private int mBorderColor = 0xff6E6E6E; + private int mColor = 0xff000000; + + private Paint mBorderPaint; + private Paint mColorPaint; + + private RectF mDrawingRect; + private RectF mColorRect; + + private AlphaPatternDrawable mAlphaPattern; + + + public ColorPanelView(Context context) { + this(context, null); + } + + public ColorPanelView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ColorPanelView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + init(); + } + + private void init() { + mBorderPaint = new Paint(); + mColorPaint = new Paint(); + mDensity = getContext().getResources().getDisplayMetrics().density; + } + + + @Override + protected void onDraw(Canvas canvas) { + + final RectF rect = mColorRect; + + if (BORDER_WIDTH_PX > 0) { + mBorderPaint.setColor(mBorderColor); + canvas.drawRect(mDrawingRect, mBorderPaint); + } + + if (mAlphaPattern != null) { + mAlphaPattern.draw(canvas); + } + + mColorPaint.setColor(mColor); + + canvas.drawRect(rect, mColorPaint); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + + setMeasuredDimension(width, height); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + + mDrawingRect = new RectF(); + mDrawingRect.left = getPaddingLeft(); + mDrawingRect.right = w - getPaddingRight(); + mDrawingRect.top = getPaddingTop(); + mDrawingRect.bottom = h - getPaddingBottom(); + + setUpColorRect(); + + } + + private void setUpColorRect() { + final RectF dRect = mDrawingRect; + + float left = dRect.left + BORDER_WIDTH_PX; + float top = dRect.top + BORDER_WIDTH_PX; + float bottom = dRect.bottom - BORDER_WIDTH_PX; + float right = dRect.right - BORDER_WIDTH_PX; + + mColorRect = new RectF(left,top, right, bottom); + + mAlphaPattern = new AlphaPatternDrawable((int)(5 * mDensity)); + + mAlphaPattern.setBounds(Math.round(mColorRect.left), + Math.round(mColorRect.top), + Math.round(mColorRect.right), + Math.round(mColorRect.bottom)); + + } + + /** + * Set the color that should be shown by this view. + * @param color + */ + public void setColor(int color) { + mColor = color; + invalidate(); + } + + /** + * Get the color currently show by this view. + * @return + */ + public int getColor() { + return mColor; + } + + /** + * Set the color of the border surrounding the panel. + * @param color + */ + public void setBorderColor(int color) { + mBorderColor = color; + invalidate(); + } + + /** + * Get the color of the border surrounding the panel. + */ + public int getBorderColor() { + return mBorderColor; + } + +} diff --git a/Backbone/src/main/java/com/afzkl/development/mColorPicker/views/ColorPickerView.java b/Backbone/src/main/java/com/afzkl/development/mColorPicker/views/ColorPickerView.java new file mode 100644 index 000000000..c2808625b --- /dev/null +++ b/Backbone/src/main/java/com/afzkl/development/mColorPicker/views/ColorPickerView.java @@ -0,0 +1,989 @@ +/* + * Copyright (C) 2010 Daniel Nilsson + * + * 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 com.afzkl.development.mColorPicker.views; + +import android.content.Context; +import android.graphics.*; +import android.graphics.Paint.Align; +import android.graphics.Paint.Style; +import android.graphics.Shader.TileMode; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import com.afzkl.development.mColorPicker.drawables.AlphaPatternDrawable; + +import java.lang.reflect.Method; + +/** + * Displays a color picker to the user and allow them + * to select a color. A slider for the alpha channel is + * also available. Enable it by setting + * setAlphaSliderVisible(boolean) to true. + * @author Daniel Nilsson + */ +@SuppressWarnings("all") +public class ColorPickerView extends View{ + + public interface OnColorChangedListener{ + public void onColorChanged(int color); + } + + private final static int PANEL_SAT_VAL = 0; + private final static int PANEL_HUE = 1; + private final static int PANEL_ALPHA = 2; + + /** + * The width in pixels of the border + * surrounding all color panels. + */ + private final static float BORDER_WIDTH_PX = 1; + + /** + * The width in dp of the hue panel. + */ + private float HUE_PANEL_WIDTH = 30f; + /** + * The height in dp of the alpha panel + */ + private float ALPHA_PANEL_HEIGHT = 20f; + /** + * The distance in dp between the different + * color panels. + */ + private float PANEL_SPACING = 10f; + /** + * The radius in dp of the color palette tracker circle. + */ + private float PALETTE_CIRCLE_TRACKER_RADIUS = 5f; + /** + * The dp which the tracker of the hue or alpha panel + * will extend outside of its bounds. + */ + private float RECTANGLE_TRACKER_OFFSET = 2f; + + + private static float mDensity = 1f; + + private OnColorChangedListener mListener; + + private Paint mSatValPaint; + private Paint mSatValTrackerPaint; + + private Paint mHuePaint; + private Paint mHueTrackerPaint; + + private Paint mAlphaPaint; + private Paint mAlphaTextPaint; + + private Paint mBorderPaint; + + private Shader mValShader; + private Shader mSatShader; + private Shader mHueShader; + private Shader mAlphaShader; + + private int mAlpha = 0xff; + private float mHue = 360f; + private float mSat = 0f; + private float mVal = 0f; + + private String mAlphaSliderText = "Alpha"; + private int mSliderTrackerColor = 0xff1c1c1c; + private int mBorderColor = 0xff6E6E6E; + private boolean mShowAlphaPanel = false; + + /* + * To remember which panel that has the "focus" when + * processing hardware button data. + */ + private int mLastTouchedPanel = PANEL_SAT_VAL; + + /** + * Offset from the edge we must have or else + * the finger tracker will get clipped when + * it is drawn outside of the view. + */ + private float mDrawingOffset; + + + /* + * Distance form the edges of the view + * of where we are allowed to draw. + */ + private RectF mDrawingRect; + + private RectF mSatValRect; + private RectF mHueRect; + private RectF mAlphaRect; + + private AlphaPatternDrawable mAlphaPattern; + + private Point mStartTouchPoint = null; + + + public ColorPickerView(Context context) { + this(context, null); + } + + public ColorPickerView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ColorPickerView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + private void init() { + mDensity = getContext().getResources().getDisplayMetrics().density; + PALETTE_CIRCLE_TRACKER_RADIUS *= mDensity; + RECTANGLE_TRACKER_OFFSET *= mDensity; + HUE_PANEL_WIDTH *= mDensity; + ALPHA_PANEL_HEIGHT *= mDensity; + PANEL_SPACING = PANEL_SPACING * mDensity; + + mDrawingOffset = calculateRequiredOffset(); + + initPaintTools(); + + //Needed for receiving trackball motion events. + setFocusable(true); + setFocusableInTouchMode(true); + } + + private void initPaintTools() { + + mSatValPaint = new Paint(); + mSatValTrackerPaint = new Paint(); + mHuePaint = new Paint(); + mHueTrackerPaint = new Paint(); + mAlphaPaint = new Paint(); + mAlphaTextPaint = new Paint(); + mBorderPaint = new Paint(); + + + mSatValTrackerPaint.setStyle(Style.STROKE); + mSatValTrackerPaint.setStrokeWidth(2f * mDensity); + mSatValTrackerPaint.setAntiAlias(true); + + mHueTrackerPaint.setColor(mSliderTrackerColor); + mHueTrackerPaint.setStyle(Style.STROKE); + mHueTrackerPaint.setStrokeWidth(2f * mDensity); + mHueTrackerPaint.setAntiAlias(true); + + mAlphaTextPaint.setColor(0xff1c1c1c); + mAlphaTextPaint.setTextSize(14f * mDensity); + mAlphaTextPaint.setAntiAlias(true); + mAlphaTextPaint.setTextAlign(Align.CENTER); + mAlphaTextPaint.setFakeBoldText(true); + + + } + + private float calculateRequiredOffset() { + float offset = Math.max(PALETTE_CIRCLE_TRACKER_RADIUS, RECTANGLE_TRACKER_OFFSET); + offset = Math.max(offset, BORDER_WIDTH_PX * mDensity); + + return offset * 1.5f; + } + + private int[] buildHueColorArray() { + + int[] hue = new int[361]; + + int count = 0; + for (int i = hue.length -1; i >= 0; i--, count++) { + hue[count] = Color.HSVToColor(new float[]{i, 1f, 1f}); + } + + return hue; + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + checkHardwareAccelerationSupport(); + } + + @Override + protected void onDraw(Canvas canvas) { + + if (mDrawingRect.width() <= 0 || mDrawingRect.height() <= 0) return; + + drawSatValPanel(canvas); + drawHuePanel(canvas); + drawAlphaPanel(canvas); + + } + + private void drawSatValPanel(Canvas canvas) { + + final RectF rect = mSatValRect; + + if (BORDER_WIDTH_PX > 0) { + mBorderPaint.setColor(mBorderColor); + canvas.drawRect(mDrawingRect.left, mDrawingRect.top, rect.right + BORDER_WIDTH_PX, rect.bottom + BORDER_WIDTH_PX, mBorderPaint); + } + + if (mValShader == null) { + mValShader = new LinearGradient(rect.left, rect.top, rect.left, rect.bottom, + 0xffffffff, 0xff000000, TileMode.CLAMP); + } + + int rgb = Color.HSVToColor(new float[]{mHue,1f,1f}); + + mSatShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top, + 0xffffffff, rgb, TileMode.CLAMP); + ComposeShader mShader = new ComposeShader(mValShader, mSatShader, PorterDuff.Mode.MULTIPLY); + mSatValPaint.setShader(mShader); + + canvas.drawRect(rect, mSatValPaint); + + Point p = satValToPoint(mSat, mVal); + + mSatValTrackerPaint.setColor(0xff000000); + canvas.drawCircle(p.x, p.y, PALETTE_CIRCLE_TRACKER_RADIUS - 1f * mDensity, mSatValTrackerPaint); + + mSatValTrackerPaint.setColor(0xffdddddd); + canvas.drawCircle(p.x, p.y, PALETTE_CIRCLE_TRACKER_RADIUS, mSatValTrackerPaint); + + } + + private void drawHuePanel(Canvas canvas) { + + final RectF rect = mHueRect; + + if (BORDER_WIDTH_PX > 0) { + mBorderPaint.setColor(mBorderColor); + canvas.drawRect(rect.left - BORDER_WIDTH_PX, + rect.top - BORDER_WIDTH_PX, + rect.right + BORDER_WIDTH_PX, + rect.bottom + BORDER_WIDTH_PX, + mBorderPaint); + } + + if (mHueShader == null) { + mHueShader = new LinearGradient(rect.left, rect.top, rect.left, rect.bottom, buildHueColorArray(), null, TileMode.CLAMP); + mHuePaint.setShader(mHueShader); + } + + canvas.drawRect(rect, mHuePaint); + + float rectHeight = 4 * mDensity / 2; + + Point p = hueToPoint(mHue); + + RectF r = new RectF(); + r.left = rect.left - RECTANGLE_TRACKER_OFFSET; + r.right = rect.right + RECTANGLE_TRACKER_OFFSET; + r.top = p.y - rectHeight; + r.bottom = p.y + rectHeight; + + + canvas.drawRoundRect(r, 2, 2, mHueTrackerPaint); + + } + + private void drawAlphaPanel(Canvas canvas) { + + if (!mShowAlphaPanel || mAlphaRect == null || mAlphaPattern == null) return; + + final RectF rect = mAlphaRect; + + if (BORDER_WIDTH_PX > 0) { + mBorderPaint.setColor(mBorderColor); + canvas.drawRect(rect.left - BORDER_WIDTH_PX, + rect.top - BORDER_WIDTH_PX, + rect.right + BORDER_WIDTH_PX, + rect.bottom + BORDER_WIDTH_PX, + mBorderPaint); + } + + + mAlphaPattern.draw(canvas); + + float[] hsv = new float[]{mHue,mSat,mVal}; + int color = Color.HSVToColor(hsv); + int acolor = Color.HSVToColor(0, hsv); + + mAlphaShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top, + color, acolor, TileMode.CLAMP); + + + mAlphaPaint.setShader(mAlphaShader); + + canvas.drawRect(rect, mAlphaPaint); + + if (mAlphaSliderText != null && mAlphaSliderText!= "") { + canvas.drawText(mAlphaSliderText, rect.centerX(), rect.centerY() + 4 * mDensity, mAlphaTextPaint); + } + + float rectWidth = 4 * mDensity / 2; + + Point p = alphaToPoint(mAlpha); + + RectF r = new RectF(); + r.left = p.x - rectWidth; + r.right = p.x + rectWidth; + r.top = rect.top - RECTANGLE_TRACKER_OFFSET; + r.bottom = rect.bottom + RECTANGLE_TRACKER_OFFSET; + + canvas.drawRoundRect(r, 2, 2, mHueTrackerPaint); + + } + + + private Point hueToPoint(float hue) { + + final RectF rect = mHueRect; + final float height = rect.height(); + + Point p = new Point(); + + p.y = (int) (height - (hue * height / 360f) + rect.top); + p.x = (int) rect.left; + + return p; + } + + private Point satValToPoint(float sat, float val) { + + final RectF rect = mSatValRect; + final float height = rect.height(); + final float width = rect.width(); + + Point p = new Point(); + + p.x = (int) (sat * width + rect.left); + p.y = (int) ((1f - val) * height + rect.top); + + return p; + } + + private Point alphaToPoint(int alpha) { + + final RectF rect = mAlphaRect; + final float width = rect.width(); + + Point p = new Point(); + + p.x = (int) (width - (alpha * width / 0xff) + rect.left); + p.y = (int) rect.top; + + return p; + + } + + private float[] pointToSatVal(float x, float y) { + + final RectF rect = mSatValRect; + float[] result = new float[2]; + + float width = rect.width(); + float height = rect.height(); + + if (x < rect.left) { + x = 0f; + } + else if (x > rect.right) { + x = width; + } + else{ + x = x - rect.left; + } + + if (y < rect.top) { + y = 0f; + } + else if (y > rect.bottom) { + y = height; + } + else{ + y = y - rect.top; + } + + + result[0] = 1.f / width * x; + result[1] = 1.f - (1.f / height * y); + + return result; + } + + private float pointToHue(float y) { + + final RectF rect = mHueRect; + + float height = rect.height(); + + if (y < rect.top) { + y = 0f; + } + else if (y > rect.bottom) { + y = height; + } + else{ + y = y - rect.top; + } + + return 360f - (y * 360f / height); + } + + private int pointToAlpha(int x) { + + final RectF rect = mAlphaRect; + final int width = (int) rect.width(); + + if (x < rect.left) { + x = 0; + } + else if (x > rect.right) { + x = width; + } + else{ + x = x - (int)rect.left; + } + + return 0xff - (x * 0xff / width); + + } + + + @Override + public boolean onTrackballEvent(MotionEvent event) { + + float x = event.getX(); + float y = event.getY(); + + boolean update = false; + + + if (event.getAction() == MotionEvent.ACTION_MOVE) { + + switch(mLastTouchedPanel) { + + case PANEL_SAT_VAL: + + float sat, val; + + sat = mSat + x/50f; + val = mVal - y/50f; + + if (sat < 0f) { + sat = 0f; + } + else if (sat > 1f) { + sat = 1f; + } + + if (val < 0f) { + val = 0f; + } + else if (val > 1f) { + val = 1f; + } + + mSat = sat; + mVal = val; + + update = true; + + break; + + case PANEL_HUE: + + float hue = mHue - y * 10f; + + if (hue < 0f) { + hue = 0f; + } + else if (hue > 360f) { + hue = 360f; + } + + mHue = hue; + + update = true; + + break; + + case PANEL_ALPHA: + + if (!mShowAlphaPanel || mAlphaRect == null) { + update = false; + } + else{ + + int alpha = (int) (mAlpha - x*10); + + if (alpha < 0) { + alpha = 0; + } + else if (alpha > 0xff) { + alpha = 0xff; + } + + mAlpha = alpha; + + + update = true; + } + + break; + } + + + } + + + if (update) { + + if (mListener != null) { + mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[]{mHue, mSat, mVal})); + } + + invalidate(); + return true; + } + + + return super.onTrackballEvent(event); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + + boolean update = false; + + switch(event.getAction()) { + + case MotionEvent.ACTION_DOWN: + + mStartTouchPoint = new Point((int)event.getX(), (int)event.getY()); + + update = moveTrackersIfNeeded(event); + + break; + + case MotionEvent.ACTION_MOVE: + + update = moveTrackersIfNeeded(event); + + break; + + case MotionEvent.ACTION_UP: + + mStartTouchPoint = null; + + update = moveTrackersIfNeeded(event); + + break; + + } + + if (update) { + + if (mListener != null) { + mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[]{mHue, mSat, mVal})); + } + + invalidate(); + return true; + } + + + return super.onTouchEvent(event); + } + + private boolean moveTrackersIfNeeded(MotionEvent event) { + + if (mStartTouchPoint == null) return false; + + boolean update = false; + + int startX = mStartTouchPoint.x; + int startY = mStartTouchPoint.y; + + + if (mHueRect.contains(startX, startY)) { + mLastTouchedPanel = PANEL_HUE; + + mHue = pointToHue(event.getY()); + + update = true; + } + else if (mSatValRect.contains(startX, startY)) { + + mLastTouchedPanel = PANEL_SAT_VAL; + + float[] result = pointToSatVal(event.getX(), event.getY()); + + mSat = result[0]; + mVal = result[1]; + + update = true; + } + else if (mAlphaRect != null && mAlphaRect.contains(startX, startY)) { + + mLastTouchedPanel = PANEL_ALPHA; + + mAlpha = pointToAlpha((int)event.getX()); + + update = true; + } + + + return update; + } + + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + + int width = 0; + int height = 0; + + int widthMode = MeasureSpec.getMode(widthMeasureSpec); + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + + int widthAllowed = MeasureSpec.getSize(widthMeasureSpec); + int heightAllowed = MeasureSpec.getSize(heightMeasureSpec); + + + widthAllowed = chooseWidth(widthMode, widthAllowed); + heightAllowed = chooseHeight(heightMode, heightAllowed); + + + if (!mShowAlphaPanel) { + height = (int) (widthAllowed - PANEL_SPACING - HUE_PANEL_WIDTH); + + //If calculated height (based on the width) is more than the allowed height. + if (height > heightAllowed) { + height = heightAllowed; + width = (int) (height + PANEL_SPACING + HUE_PANEL_WIDTH); + } + else{ + width = widthAllowed; + } + } + else{ + + width = (int) (heightAllowed - ALPHA_PANEL_HEIGHT + HUE_PANEL_WIDTH); + + if (width > widthAllowed) { + width = widthAllowed; + height = (int) (widthAllowed - HUE_PANEL_WIDTH + ALPHA_PANEL_HEIGHT); + } + else{ + height = heightAllowed; + } + + + } + + + setMeasuredDimension(width, height); + } + + private int chooseWidth(int mode, int size) { + if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) { + return size; + } else { // (mode == MeasureSpec.UNSPECIFIED) + return getPrefferedWidth(); + } + } + + private int chooseHeight(int mode, int size) { + if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) { + return size; + } else { // (mode == MeasureSpec.UNSPECIFIED) + return getPreferedHeight(); + } + } + + private int getPrefferedWidth() { + + int width = getPreferedHeight(); + + if (mShowAlphaPanel) { + width -= (PANEL_SPACING + ALPHA_PANEL_HEIGHT); + } + + + return (int) (width + HUE_PANEL_WIDTH + PANEL_SPACING); + + } + + private int getPreferedHeight() { + + int height = (int)(200 * mDensity); + + if (mShowAlphaPanel) { + height += PANEL_SPACING + ALPHA_PANEL_HEIGHT; + } + + return height; + } + + + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + + mDrawingRect = new RectF(); + mDrawingRect.left = mDrawingOffset + getPaddingLeft(); + mDrawingRect.right = w - mDrawingOffset - getPaddingRight(); + mDrawingRect.top = mDrawingOffset + getPaddingTop(); + mDrawingRect.bottom = h - mDrawingOffset - getPaddingBottom(); + + setUpSatValRect(); + setUpHueRect(); + setUpAlphaRect(); + } + + private void setUpSatValRect() { + + final RectF dRect = mDrawingRect; + float panelSide = dRect.height() - BORDER_WIDTH_PX * 2; + + if (mShowAlphaPanel) { + panelSide -= PANEL_SPACING + ALPHA_PANEL_HEIGHT; + } + + float left = dRect.left + BORDER_WIDTH_PX; + float top = dRect.top + BORDER_WIDTH_PX; + float bottom = top + panelSide; + float right = left + panelSide; + + mSatValRect = new RectF(left,top, right, bottom); + } + + private void setUpHueRect() { + final RectF dRect = mDrawingRect; + + float left = dRect.right - HUE_PANEL_WIDTH + BORDER_WIDTH_PX; + float top = dRect.top + BORDER_WIDTH_PX; + float bottom = dRect.bottom - BORDER_WIDTH_PX - (mShowAlphaPanel ? (PANEL_SPACING + ALPHA_PANEL_HEIGHT) : 0); + float right = dRect.right - BORDER_WIDTH_PX; + + mHueRect = new RectF(left, top, right, bottom); + } + + private void setUpAlphaRect() { + + if (!mShowAlphaPanel) return; + + final RectF dRect = mDrawingRect; + + float left = dRect.left + BORDER_WIDTH_PX; + float top = dRect.bottom - ALPHA_PANEL_HEIGHT + BORDER_WIDTH_PX; + float bottom = dRect.bottom - BORDER_WIDTH_PX; + float right = dRect.right - BORDER_WIDTH_PX; + + mAlphaRect = new RectF(left, top, right, bottom); + + + mAlphaPattern = new AlphaPatternDrawable((int) (5 * mDensity)); + mAlphaPattern.setBounds(Math.round(mAlphaRect.left), Math + .round(mAlphaRect.top), Math.round(mAlphaRect.right), Math + .round(mAlphaRect.bottom)); + + + + } + + + /** + * Set a OnColorChangedListener to get notified when the color + * selected by the user has changed. + * @param listener + */ + public void setOnColorChangedListener(OnColorChangedListener listener) { + mListener = listener; + } + + /** + * Set the color of the border surrounding all panels. + * @param color + */ + public void setBorderColor(int color) { + mBorderColor = color; + invalidate(); + } + + /** + * Get the color of the border surrounding all panels. + */ + public int getBorderColor() { + return mBorderColor; + } + + /** + * Get the current color this view is showing. + * @return the current color. + */ + public int getColor() { + return Color.HSVToColor(mAlpha, new float[]{mHue,mSat,mVal}); + } + + /** + * Set the color the view should show. + * @param color The color that should be selected. + */ + public void setColor(int color) { + setColor(color, false); + } + + /** + * Set the color this view should show. + * @param color The color that should be selected. + * @param callback If you want to get a callback to + * your OnColorChangedListener. + */ + public void setColor(int color, boolean callback) { + + int alpha = Color.alpha(color); + int red = Color.red(color); + int blue = Color.blue(color); + int green = Color.green(color); + + float[] hsv = new float[3]; + + Color.RGBToHSV(red, green, blue, hsv); + + mAlpha = alpha; + mHue = hsv[0]; + mSat = hsv[1]; + mVal = hsv[2]; + + if (callback && mListener != null) { + mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[]{mHue, mSat, mVal})); + } + + invalidate(); + } + + /** + * Get the drawing offset of the color picker view. + * The drawing offset is the distance from the side of + * a panel to the side of the view minus the padding. + * Useful if you want to have your own panel below showing + * the currently selected color and want to align it perfectly. + * @return The offset in pixels. + */ + public float getDrawingOffset() { + return mDrawingOffset; + } + + /** + * Set if the user is allowed to adjust the alpha panel. Default is false. + * If it is set to false no alpha will be set. + * @param visible + */ + public void setAlphaSliderVisible(boolean visible) { + + if (mShowAlphaPanel != visible) { + mShowAlphaPanel = visible; + + /* + * Reset all shader to force a recreation. + * Otherwise they will not look right after + * the size of the view has changed. + */ + mValShader = null; + mSatShader = null; + mHueShader = null; + mAlphaShader = null;; + + requestLayout(); + } + + } + + public void setSliderTrackerColor(int color) { + mSliderTrackerColor = color; + + mHueTrackerPaint.setColor(mSliderTrackerColor); + + invalidate(); + } + + public int getSliderTrackerColor() { + return mSliderTrackerColor; + } + + /** + * Set the text that should be shown in the + * alpha slider. Set to null to disable text. + * @param res string resource id. + */ + public void setAlphaSliderText(int res) { + String text = getContext().getString(res); + setAlphaSliderText(text); + } + + /** + * Set the text that should be shown in the + * alpha slider. Set to null to disable text. + * @param text Text that should be shown. + */ + public void setAlphaSliderText(String text) { + mAlphaSliderText = text; + invalidate(); + } + + /** + * Get the current value of the text + * that will be shown in the alpha + * slider. + * @return + */ + public String getAlphaSliderText() { + return mAlphaSliderText; + } + + /** + * Method that checks the support for HardwareAcceleration. Check AOSP notice
+ *
+ *
+     * 'ComposeShader can only contain shaders of different types (a BitmapShader and a
+     * LinearGradient for instance, but not two instances of BitmapShader)'. But, 'If your
+     * application is affected by any of these missing features or limitations, you can turn
+     * off hardware acceleration for just the affected portion of your application by calling
+     * setLayerType(View.LAYER_TYPE_SOFTWARE, null).'
+     */
+    private void checkHardwareAccelerationSupport() {
+       // HardwareAcceleration sit is only available since ICS. 14 = ICS_VERSION_CODE
+       if (android.os.Build.VERSION.SDK_INT >= 14) {
+           try{
+               // We need to use reflection to get that method to avoid compilation errors
+               Method isHardwareAccelerated =
+                       getClass().getMethod("isHardwareAccelerated", new Class[]{});
+               Object o = isHardwareAccelerated.invoke(this, new Object[]{});
+               if (null != o && o instanceof Boolean && (Boolean)o) {
+                   // HardwareAcceleration is supported. Use SoftwareAcceleration
+                   Method setLayerType =
+                           getClass().getMethod(
+                                   "setLayerType", int.class, Paint.class);
+                   setLayerType.invoke(this, 1, (Paint)null);
+               }
+           } catch (Exception e) { /** NON BLOCK **/}
+       }
+   }
+
+}
diff --git a/Backbone/src/main/java/com/ash/syntaxhighlight/HighlightColors.java b/Backbone/src/main/java/com/ash/syntaxhighlight/HighlightColors.java
new file mode 100644
index 000000000..3bd18a9aa
--- /dev/null
+++ b/Backbone/src/main/java/com/ash/syntaxhighlight/HighlightColors.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * 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 com.ash.syntaxhighlight;
+
+import android.graphics.Color;
+
+/**
+ * An enumeration of all the color resources available for syntax highlight processors.
+ */
+public enum HighlightColors {
+
+    /**
+     * Text color
+     */
+    TEXT(
+            "ash_text", //$NON-NLS-1$
+            "ash_text_color", //$NON-NLS-1$
+            Color.argb(153, 0, 0, 0)),
+    /**
+     * Assignment text color
+     */
+    ASSIGNMENT(
+            "ash_assignment", //$NON-NLS-1$
+            "ash_assignment_color", //$NON-NLS-1$
+            Color.argb(153, 0, 0, 0)),
+    /**
+     * Single line comment color
+     */
+    SINGLE_LINE_COMMENT(
+            "ash_singleline_comment", //$NON-NLS-1$
+            "ash_singleline_comment_color", //$NON-NLS-1$
+            Color.argb(255, 63, 127, 95)),
+    /**
+     * Multiline line comment color
+     */
+    MULTILINE_LINE_COMMENT(
+            "ash_multiline_comment", //$NON-NLS-1$
+            "ash_multiline_comment_color", //$NON-NLS-1$
+            Color.argb(255, 127, 159, 191)),
+    /**
+     * Keyword color
+     */
+    KEYWORD(
+            "ash_keyword", //$NON-NLS-1$
+            "ash_keyword_color", //$NON-NLS-1$
+            Color.argb(255, 127, 0, 85)),
+    /**
+     * Quoted string color
+     */
+    QUOTED_STRING(
+            "ash_quoted_string", //$NON-NLS-1$
+            "ash_quoted_string_color", //$NON-NLS-1$
+            Color.argb(255, 42, 0, 255)),
+    /**
+     * Variable color
+     */
+    VARIABLE(
+            "ash_variable", //$NON-NLS-1$
+            "ash_variable_color", //$NON-NLS-1$
+            Color.argb(153, 0, 0, 192));
+
+
+    private final String mId;
+    private final String mResId;
+    private final int mDefault;
+
+    /**
+     * Constructor of HighlightColors
+     *
+     * @param id The id of the object
+     * @param resid The resource id
+     * @param def The default value
+     */
+    HighlightColors(String id, String resid, int def) {
+        this.mId = id;
+        this.mResId = resid;
+        this.mDefault = def;
+    }
+
+    /**
+     * Returns the identifier
+     *
+     * @return String The identifier
+     */
+    public String getId() {
+        return this.mId;
+    }
+
+    /**
+     * Returns the resource identifier
+     *
+     * @return String The resource identifier
+     */
+    public String getResId() {
+        return this.mResId;
+    }
+
+    /**
+     * Returns the default value
+     *
+     * @return String The default value
+     */
+    public int getDefault() {
+        return this.mDefault;
+    }
+
+}
diff --git a/Backbone/src/main/java/com/ash/syntaxhighlight/ISyntaxHighlightResourcesResolver.java b/Backbone/src/main/java/com/ash/syntaxhighlight/ISyntaxHighlightResourcesResolver.java
new file mode 100644
index 000000000..6494f573b
--- /dev/null
+++ b/Backbone/src/main/java/com/ash/syntaxhighlight/ISyntaxHighlightResourcesResolver.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * 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 com.ash.syntaxhighlight;
+
+/**
+ * An interface that should be implemented by the library caller, to
+ * resolve resources needed by the syntax processors.
+ *
+ * @see HighlightColors
+ */
+public interface ISyntaxHighlightResourcesResolver {
+
+    /**
+     * Method that returns a string
+     *
+     * @param id The color unique id
+     * @param resid The resource identifier
+     * @return CharSequence The string
+     */
+    CharSequence getString(String id, String resid);
+
+    /**
+     * Method that returns an integer
+     *
+     * @param id The color unique id
+     * @param resid The resource identifier
+     * @param def The default value
+     * @return int The integer value
+     */
+    int getInteger(String id, String resid, int def);
+
+    /**
+     * Method that returns a color
+     *
+     * @param id The color unique id
+     * @param resid The resource identifier
+     * @param def The default value
+     * @return int The color
+     */
+    int getColor(String id, String resid, int def);
+}
diff --git a/Backbone/src/main/java/com/ash/syntaxhighlight/RegExpUtil.java b/Backbone/src/main/java/com/ash/syntaxhighlight/RegExpUtil.java
new file mode 100644
index 000000000..01c7f507a
--- /dev/null
+++ b/Backbone/src/main/java/com/ash/syntaxhighlight/RegExpUtil.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * 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 com.ash.syntaxhighlight;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+/**
+ * A helper class for deal with patters
+ */
+public final class RegExpUtil {
+
+    /**
+     * A constant that is returned when the no expression matches.
+     */
+    public static final int NO_MATCH = -1;
+
+    /**
+     * New line pattern
+     */
+    public static final Pattern NEWLINE_PATTERN = Pattern.compile("(\r\n|\n|\r)"); //$NON-NLS-1$
+
+    /**
+     * Method that returns the last match position of a regexp,
+     *
+     * @param pattern The patter
+     * @param input The input
+     * @param withPattern Whether the return position should contains the pattern or not.
+     * @return int The matched position or -1
+     */
+    public static int getLastMatch(Pattern pattern, CharSequence input, boolean withPattern) {
+        Matcher m = pattern.matcher(input);
+        int p = NO_MATCH;
+        while (m.find()) {
+            p = withPattern ? m.start() : m.end();
+        }
+        return p;
+    }
+
+    /**
+     * Method that returns the next match position of a regexp,
+     *
+     * @param pattern The patter
+     * @param input The input
+     * @param withPattern Whether the return position should contains the pattern or not.
+     * @return int The matched position or -1
+     */
+    public static int getNextMatch(Pattern pattern, CharSequence input, boolean withPattern) {
+        Matcher m = pattern.matcher(input);
+        int p = NO_MATCH;
+        if (m.find()) {
+            return withPattern ? m.end() : m.start();
+        }
+        return p;
+    }
+}
diff --git a/Backbone/src/main/java/com/ash/syntaxhighlight/SyntaxHighlightFactory.java b/Backbone/src/main/java/com/ash/syntaxhighlight/SyntaxHighlightFactory.java
new file mode 100644
index 000000000..c4231b0f1
--- /dev/null
+++ b/Backbone/src/main/java/com/ash/syntaxhighlight/SyntaxHighlightFactory.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * 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 com.ash.syntaxhighlight;
+
+import com.ash.syntaxhighlight.spi.PropertiesSyntaxHighlightProcessor;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A factory of SyntaxHighlightProcessor classes.
+ */
+public class SyntaxHighlightFactory {
+
+    private static SyntaxHighlightFactory sFactory;
+
+    private final ArrayList mProcessors;
+
+    /**
+     * Constructor of SyntaxHighlightFactory
+     */
+    public SyntaxHighlightFactory() {
+        super();
+        this.mProcessors = new ArrayList();
+    }
+
+    /**
+     * Method that returns the default highlight factory instance
+     *
+     * @param resolver A class for allow the processor to obtain resources
+     * @return SyntaxHighlightFactory The default syntax highlight factory
+     */
+    public static final synchronized SyntaxHighlightFactory getDefaultFactory(
+            ISyntaxHighlightResourcesResolver resolver) {
+        if (sFactory == null) {
+            sFactory = createDefaultFactory(resolver);
+        }
+        return sFactory;
+    }
+
+    /**
+     * Method that returns the syntax highlight processor that can handle the file
+     *
+     * @param file The file to process
+     * @return SyntaxHighlightProcessor The syntax highlight processor
+     */
+    public SyntaxHighlightProcessor getSyntaxHighlightProcessor(File file) {
+        int cc = this.mProcessors.size();
+        for (int i = 0; i < cc; i++) {
+            SyntaxHighlightProcessor processor = this.mProcessors.get(i);
+            if (processor.accept(file)) {
+                return processor;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Method that return all the available syntax highlight processors.
+     *
+     * @return List the list available syntax highlight processors.
+     */
+    public List getAvailableSyntaxHighlightProcessors() {
+        return new ArrayList(this.mProcessors);
+    }
+
+    /**
+     * Method that create the default syntax highlight factory.
+     *
+     * @param resolver A class for allow the processor to obtain resources
+     * @return SyntaxHighlightFactory The default factory
+     */
+    private static SyntaxHighlightFactory createDefaultFactory(
+            ISyntaxHighlightResourcesResolver resolver) {
+        // TODO Read all processors classes of the SPI package
+        // For now we add all known syntax highlight processors
+        SyntaxHighlightFactory factory = new SyntaxHighlightFactory();
+        factory.mProcessors.add(new PropertiesSyntaxHighlightProcessor(resolver));
+        return factory;
+    }
+}
diff --git a/Backbone/src/main/java/com/ash/syntaxhighlight/SyntaxHighlightProcessor.java b/Backbone/src/main/java/com/ash/syntaxhighlight/SyntaxHighlightProcessor.java
new file mode 100644
index 000000000..a57485d76
--- /dev/null
+++ b/Backbone/src/main/java/com/ash/syntaxhighlight/SyntaxHighlightProcessor.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project
+ *
+ * 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 com.ash.syntaxhighlight;
+
+import android.text.Spannable;
+import android.text.Spanned;
+import android.text.style.ForegroundColorSpan;
+
+import java.io.File;
+
+/**
+ * The base class for all the syntax highlight processors.
+ */ +public abstract class SyntaxHighlightProcessor { + + protected final ISyntaxHighlightResourcesResolver mResourcesResolver; + + /** + * Constructor of SyntaxHighlightProcessor + * + * @param resolver A class for resolve resources + */ + public SyntaxHighlightProcessor(ISyntaxHighlightResourcesResolver resolver) { + super(); + this.mResourcesResolver = resolver; + } + + /** + * Method that request to the syntax highlight processor if it is able to parse + * the file + * + * @param file The file to check + * @return boolean If the syntax highlight processor accepts process the file + */ + protected abstract boolean accept(File file); + + /** + * Method that initializes the processor + */ + public abstract void initialize(); + + /** + * Method that request to the syntax highlight processor to do process and highlight a + * document. This method request a full process. + * + * @param spanable The spannable source to highlight + */ + public abstract void process(Spannable spanable); + + /** + * Method that request to the syntax highlight processor to process and highlight a + * document. This method request a partial process. + * + * @param spanable The spannable source to highlight + * @param start The start of spannable to process + * @param end The end of spannable to process + */ + public abstract void process(Spannable spanable, int start, int end); + + /** + * Method that cancels the active processor + */ + public abstract void cancel(); + + /** + * Method that clear all the existent spans + * + * @param spanable The spannable + */ + @SuppressWarnings("static-method") + public void clear(Spannable spanable) { + ForegroundColorSpan[] spans = + spanable.getSpans(0, spanable.length(), ForegroundColorSpan.class); + int cc = spans.length; + for (int i = 0; i < cc; i++) { + spanable.removeSpan(spans[i]); + } + } + + + /** + * Method that sets a new Spannable. + * + * @param spanable The spannable + * @param color The color of the span + * @param start The start of the span + * @param end The end of the span + */ + @SuppressWarnings("static-method") + protected void setSpan(Spannable spanable, int color, int start, int end) { + if (start == end) return; + spanable.setSpan( + new ForegroundColorSpan(color), + start, + end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } +} diff --git a/Backbone/src/main/java/com/ash/syntaxhighlight/scanners/NewLineScanner.java b/Backbone/src/main/java/com/ash/syntaxhighlight/scanners/NewLineScanner.java new file mode 100644 index 000000000..7ae262bcf --- /dev/null +++ b/Backbone/src/main/java/com/ash/syntaxhighlight/scanners/NewLineScanner.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project + * + * 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 com.ash.syntaxhighlight.scanners; + +import com.ash.syntaxhighlight.RegExpUtil; + +import java.util.regex.Matcher; + +/** + * An scanner to process an input, reporting every text into new lines. + */ +public class NewLineScanner extends Scanner { + + private final NewLineScannerListener mListener; + + /** + * The listener for the newline scanner + */ + public interface NewLineScannerListener { + /** + * When a new line is ready + * + * @param newline The newline detected + * @param start The start position of the new line within the input text + * @param end The end position of the new line within the input text + * @param sep The line separator detected + * @return boolean If processor must continue with the next line + */ + boolean onNewLine(CharSequence newline, int start, int end, CharSequence sep); + } + + /** + * Constructor of Scanner + * + * @param input The input + * @param listener The listener where return every new line + */ + public NewLineScanner(CharSequence input, NewLineScannerListener listener) { + super(input); + this.mListener = listener; + } + + /** + * {@inheritDoc} + */ + @Override + public void scan() { + if (this.mInput.length() == 0) return; + Matcher m = RegExpUtil.NEWLINE_PATTERN.matcher(this.mInput); + int next = 0; + while(m.find(next)) { + CharSequence line = this.mInput.subSequence(next, m.start()); + if (!this.mListener.onNewLine(line, next, m.start(), m.group())) { + return; + } + next = m.end(); + } + // The non-matched data + CharSequence line = this.mInput.subSequence(next, this.mInput.length()); + this.mListener.onNewLine(line, next, this.mInput.length(), null); + } +} diff --git a/Backbone/src/main/java/com/ash/syntaxhighlight/scanners/Scanner.java b/Backbone/src/main/java/com/ash/syntaxhighlight/scanners/Scanner.java new file mode 100644 index 000000000..1da5f1b77 --- /dev/null +++ b/Backbone/src/main/java/com/ash/syntaxhighlight/scanners/Scanner.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project + * + * 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 com.ash.syntaxhighlight.scanners; + + +/** + * The base class for all the scanners + */ +public abstract class Scanner { + + CharSequence mInput; + + /** + * Constructor of Scanner + * + * @param input The input + */ + public Scanner(CharSequence input) { + super(); + this.mInput = input; + } + + /** + * Method that starts the scan process + */ + public abstract void scan(); +} diff --git a/Backbone/src/main/java/com/ash/syntaxhighlight/spi/PropertiesSyntaxHighlightProcessor.java b/Backbone/src/main/java/com/ash/syntaxhighlight/spi/PropertiesSyntaxHighlightProcessor.java new file mode 100644 index 000000000..80d52db04 --- /dev/null +++ b/Backbone/src/main/java/com/ash/syntaxhighlight/spi/PropertiesSyntaxHighlightProcessor.java @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project + * + * 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 com.ash.syntaxhighlight.spi; + +import android.text.Spannable; +import android.text.style.ForegroundColorSpan; +import com.ash.syntaxhighlight.HighlightColors; +import com.ash.syntaxhighlight.ISyntaxHighlightResourcesResolver; +import com.ash.syntaxhighlight.RegExpUtil; +import com.ash.syntaxhighlight.SyntaxHighlightProcessor; +import com.ash.syntaxhighlight.scanners.NewLineScanner; +import com.ash.syntaxhighlight.scanners.NewLineScanner.NewLineScannerListener; + +import java.io.File; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A properties highlight processor class.
+ *
+ * The behaviour of this class is:
+ *
    + *
  • Comments start with # (only spaces are allowed prior to comment)
  • + *
  • Assignment character (=) separates key from value
  • + *
  • Arguments exists only in values, and are composed by {a digit}
  • + *
  • Values can be extended in multiple lines if line ends with the char "\". A + * comment in multiline breaks the multiline and starts a new property.
  • + *
+ *
+ * IMP! This class is not thread safe. Calling "process" methods should be + * done in a synchronous way. + */ +public class PropertiesSyntaxHighlightProcessor extends SyntaxHighlightProcessor { + + private static final String EXT_PROP = "prop"; //$NON-NLS-1$ + private static final String EXT_PROPERTIES = "properties"; //$NON-NLS-1$ + + private static final Pattern COMMENT = Pattern.compile("^\\s*#.*"); //$NON-NLS-1$ + private static final Pattern MULTILINE = Pattern.compile(".*\\\\\\s*$"); //$NON-NLS-1$ + private static final Pattern ASSIGNMENT = Pattern.compile("="); //$NON-NLS-1$ + private static final Pattern ARGUMENT = Pattern.compile("\\{\\d+\\}"); //$NON-NLS-1$ + + protected Spannable mSpannable; + private boolean mMultiLine; + + private int mKeyColor; + private int mAssignmentColor; + private int mCommentColor; + private int mValueColor; + private int mArgumentColor; + + /** + * Constructor of PropertiesSyntaxHighlightProcessor + * + * @param resolver A class for resolve resources + */ + public PropertiesSyntaxHighlightProcessor(ISyntaxHighlightResourcesResolver resolver) { + super(resolver); + initialize(); + } + + /** + * {@inheritDoc} + */ + @Override + protected boolean accept(File file) { + if (file == null) return false; + return file.getName().toLowerCase().endsWith(EXT_PROP) || + file.getName().toLowerCase().endsWith(EXT_PROPERTIES); + } + + /** + * {@inheritDoc} + */ + @Override + public void initialize() { + this.mMultiLine = false; + this.mSpannable = null; + if (this.mResourcesResolver != null) { + this.mKeyColor = this.mResourcesResolver.getColor( + HighlightColors.TEXT.getId(), + HighlightColors.TEXT.getResId(), + HighlightColors.TEXT.getDefault()); + this.mAssignmentColor = this.mResourcesResolver.getColor( + HighlightColors.ASSIGNMENT.getId(), + HighlightColors.ASSIGNMENT.getResId(), + HighlightColors.ASSIGNMENT.getDefault()); + this.mCommentColor = this.mResourcesResolver.getColor( + HighlightColors.SINGLE_LINE_COMMENT.getId(), + HighlightColors.SINGLE_LINE_COMMENT.getResId(), + HighlightColors.SINGLE_LINE_COMMENT.getDefault()); + this.mValueColor = this.mResourcesResolver.getColor( + HighlightColors.VARIABLE.getId(), + HighlightColors.VARIABLE.getResId(), + HighlightColors.VARIABLE.getDefault()); + this.mArgumentColor = this.mResourcesResolver.getColor( + HighlightColors.KEYWORD.getId(), + HighlightColors.KEYWORD.getResId(), + HighlightColors.KEYWORD.getDefault()); + } else { + // By default + this.mKeyColor = HighlightColors.TEXT.getDefault(); + this.mAssignmentColor = HighlightColors.TEXT.getDefault(); + this.mCommentColor = HighlightColors.SINGLE_LINE_COMMENT.getDefault(); + this.mValueColor = HighlightColors.VARIABLE.getDefault(); + this.mArgumentColor = HighlightColors.KEYWORD.getDefault(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void process(final Spannable spanable) { + this.mMultiLine = false; + this.mSpannable = spanable; + clear(spanable); + NewLineScanner scanner = new NewLineScanner(spanable, new NewLineScannerListener() { + @Override + public boolean onNewLine(CharSequence newline, int start, int end, CharSequence sep) { + processNewLine(newline, start, end); + return true; + } + + }); + scanner.scan(); + this.mSpannable = null; + } + + /** + * {@inheritDoc} + */ + @Override + public void process(final Spannable spanable, final int start, final int end) { + // We need a Retrieve the previous line + this.mMultiLine = false; + this.mSpannable = spanable; + CharSequence seqs = spanable.subSequence(0, start); + CharSequence seqe = spanable.subSequence(end, spanable.length()); + int s1 = RegExpUtil.getLastMatch(RegExpUtil.NEWLINE_PATTERN, seqs, false); + if (s1 == RegExpUtil.NO_MATCH) { + s1 = 0; + } + int e1 = RegExpUtil.getNextMatch(RegExpUtil.NEWLINE_PATTERN, seqe, false); + if (e1 == RegExpUtil.NO_MATCH) { + e1 = spanable.length(); + } else { + e1 += end; + } + + // Also, we need to know about if the previous line is multiline + if (s1 > 0) { + int s2 = RegExpUtil.getLastMatch(RegExpUtil.NEWLINE_PATTERN, seqs, true); + CharSequence seqnl = spanable.subSequence(0, s2); + int snl = RegExpUtil.getLastMatch(RegExpUtil.NEWLINE_PATTERN, seqnl, false); + Matcher mlm = MULTILINE.matcher( + spanable.subSequence(snl != RegExpUtil.NO_MATCH ? snl : 0, s2)); + this.mMultiLine = mlm.matches(); + } + + // Process the new line + if (s1 != e1) { + processNewLine(spanable.subSequence(s1, e1), s1, e1); + } + + // Now, multiline again (next line). We check always the next line, because we + // don't know if user delete multiline flag in the current line + e1 = RegExpUtil.getNextMatch(RegExpUtil.NEWLINE_PATTERN, seqe, true); + if (e1 != RegExpUtil.NO_MATCH) { + e1 += end; + seqe = spanable.subSequence(e1, spanable.length()); + int e2 = RegExpUtil.getNextMatch(RegExpUtil.NEWLINE_PATTERN, seqe, false); + if (e2 == RegExpUtil.NO_MATCH) { + e2 = spanable.length(); + } else { + e2 += e1; + } + processNewLine(spanable.subSequence(e1, e2), e1, e2); + } + + this.mSpannable = null; + } + + /** + * {@inheritDoc} + */ + @Override + public void cancel() { + // Not needed by this processor + } + + /** + * A method to process every new line + * + * @param newline The newline + * @param start The start position of the line + * @param end The end position of the line + * @hide + */ + void processNewLine(CharSequence newline, int start, int end) { + // Remove all spannable of the line (this processor doesn't multiline spans and + // only uses ForegroundColorSpan spans) + ForegroundColorSpan[] spans = + this.mSpannable.getSpans(start, end, ForegroundColorSpan.class); + int cc = spans.length; + for (int i = 0; i < cc; i++) { + this.mSpannable.removeSpan(spans[i]); + } + + // Find comment + Matcher cm = COMMENT.matcher(newline); + if (cm.matches()) { + // All the line is a comment + setSpan(this.mSpannable, this.mCommentColor, start, end); + this.mMultiLine = false; + return; + } + + // Has multiline + Matcher mlm = MULTILINE.matcher(newline); + boolean ml = mlm.matches(); + + //Find the assignment + int k = this.mMultiLine ? -1 : start; + int v = start; + int v2 = 0; + int a = -1; + if (!this.mMultiLine) { + Matcher am = ASSIGNMENT.matcher(newline); + if (am.find()) { + // Assignment found + v2 = am.start() + 1; + a = start + am.start(); + v = a + 1; + } + } + + // All the string is a key + if (!this.mMultiLine && a == -1) { + setSpan(this.mSpannable, this.mKeyColor, start, end); + + } else { + // Key + if (!this.mMultiLine) { + setSpan(this.mSpannable, this.mKeyColor, k, a); + } + // Assignment + if (!this.mMultiLine) { + setSpan(this.mSpannable, this.mAssignmentColor, a, a + 1); + } + // Value + setSpan(this.mSpannable, this.mValueColor, v, end); + // Argument + Matcher argm = ARGUMENT.matcher(newline); + while (argm.find(v2)) { + int s = start + argm.start(); + int e = start + argm.end(); + setSpan(this.mSpannable, this.mArgumentColor, s, e); + v2 = argm.end(); + } + } + + // Multiline? + this.mMultiLine = ml; + } +} diff --git a/src/com/cyanogenmod/filemanager/FileManagerApplication.java b/Backbone/src/main/java/me/toolify/backbone/FileManagerApplication.java similarity index 84% rename from src/com/cyanogenmod/filemanager/FileManagerApplication.java rename to Backbone/src/main/java/me/toolify/backbone/FileManagerApplication.java index a25126fb3..38b1e7780 100644 --- a/src/com/cyanogenmod/filemanager/FileManagerApplication.java +++ b/Backbone/src/main/java/me/toolify/backbone/FileManagerApplication.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager; +package me.toolify.backbone; import android.app.Application; import android.content.BroadcastReceiver; @@ -24,23 +24,24 @@ import android.content.pm.ApplicationInfo; import android.util.Log; -import com.cyanogenmod.filemanager.console.Console; -import com.cyanogenmod.filemanager.console.ConsoleAllocException; -import com.cyanogenmod.filemanager.console.ConsoleBuilder; -import com.cyanogenmod.filemanager.console.ConsoleHolder; -import com.cyanogenmod.filemanager.console.shell.PrivilegedConsole; -import com.cyanogenmod.filemanager.preferences.AccessMode; -import com.cyanogenmod.filemanager.preferences.FileManagerSettings; -import com.cyanogenmod.filemanager.preferences.ObjectStringIdentifier; -import com.cyanogenmod.filemanager.preferences.Preferences; -import com.cyanogenmod.filemanager.ui.ThemeManager; -import com.cyanogenmod.filemanager.ui.ThemeManager.Theme; -import com.cyanogenmod.filemanager.util.AIDHelper; -import com.cyanogenmod.filemanager.util.FileHelper; -import com.cyanogenmod.filemanager.util.MimeTypeHelper; +import me.toolify.backbone.console.Console; +import me.toolify.backbone.console.ConsoleAllocException; +import me.toolify.backbone.console.ConsoleBuilder; +import me.toolify.backbone.console.ConsoleHolder; +import me.toolify.backbone.console.shell.PrivilegedConsole; +import me.toolify.backbone.preferences.AccessMode; +import me.toolify.backbone.preferences.FileManagerSettings; +import me.toolify.backbone.preferences.ObjectStringIdentifier; +import me.toolify.backbone.preferences.Preferences; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.ui.ThemeManager.Theme; +import me.toolify.backbone.util.AIDHelper; +import me.toolify.backbone.util.MimeTypeHelper; import java.io.File; import java.io.FileInputStream; +import java.util.HashMap; +import java.util.Map; import java.util.Properties; /** @@ -55,11 +56,13 @@ public final class FileManagerApplication extends Application { private static boolean DEBUG = false; private static Properties sSystemProperties; + private static Map sOptionalCommandsMap; + /** * A constant that contains the main process name. * @hide */ - public static final String MAIN_PROCESS = "com.cyanogenmod.filemanager"; //$NON-NLS-1$ + public static final String MAIN_PROCESS = "me.toolify.backbone"; //$NON-NLS-1$ //Static resources private static FileManagerApplication sApp; @@ -68,6 +71,8 @@ public final class FileManagerApplication extends Application { private static boolean sIsDebuggable = false; private static boolean sIsDeviceRooted = false; + public static int NUM_PAGES = 2; + private final BroadcastReceiver mNotificationReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -235,6 +240,9 @@ private void init() { // Check if the device is rooted sIsDeviceRooted = areShellCommandsPresent(); + // Check optional commands + loadOptionalCommands(); + //Sets the default preferences if no value is set yet Preferences.loadDefaults(); @@ -301,6 +309,19 @@ public static boolean isDeviceRooted() { return sIsDeviceRooted; } + /** + * Method that returns if a command is present in the system + * + * @param commandId The command key + * @return boolean If the command is present + */ + public static boolean hasOptionalCommand(String commandId) { + if (!sOptionalCommandsMap.containsKey(commandId)) { + return false; + } + return sOptionalCommandsMap.get(commandId).booleanValue(); + } + /** * Method that returns a system property value * @@ -354,13 +375,11 @@ private static synchronized void allocBackgroundConsole(Context ctx) { if (ConsoleBuilder.isPrivileged()) { sBackgroundConsole = new ConsoleHolder( - ConsoleBuilder.createPrivilegedConsole( - ctx, FileHelper.ROOT_DIRECTORY)); + ConsoleBuilder.createPrivilegedConsole(ctx)); } else { sBackgroundConsole = new ConsoleHolder( - ConsoleBuilder.createNonPrivilegedConsole( - ctx, FileHelper.ROOT_DIRECTORY)); + ConsoleBuilder.createNonPrivilegedConsole(ctx)); } } catch (Exception e) { Log.e(TAG, @@ -389,8 +408,7 @@ public static void changeBackgroundConsoleToPriviligedConsole() sBackgroundConsole = new ConsoleHolder( ConsoleBuilder.createPrivilegedConsole( - getInstance().getApplicationContext(), - FileHelper.ROOT_DIRECTORY)); + getInstance().getApplicationContext())); } catch (Exception e) { try { if (sBackgroundConsole != null) { @@ -410,6 +428,9 @@ public static void changeBackgroundConsoleToPriviligedConsole() * @return boolean If the access mode of the application */ public static AccessMode getAccessMode() { + if (!sIsDeviceRooted) { + return AccessMode.SAFE; + } String defaultValue = ((ObjectStringIdentifier)FileManagerSettings. SETTINGS_ACCESS_MODE.getDefaultValue()).getId(); @@ -472,4 +493,37 @@ private boolean areShellCommandsPresent() { } return false; } + + @SuppressWarnings("boxing") + private void loadOptionalCommands() { + try { + sOptionalCommandsMap = new HashMap(); + + String shellCommands = getString(R.string.shell_optional_commands); + String[] commands = shellCommands.split(","); //$NON-NLS-1$ + int cc = commands.length; + if (cc == 0) { + Log.w(TAG, "No optional commands."); //$NON-NLS-1$ + return; + } + for (int i = 0; i < cc; i++) { + String c = commands[i].trim(); + String key = c.substring(0, c.indexOf("=")).trim(); //$NON-NLS-1$ + c = c.substring(c.indexOf("=")+1).trim(); //$NON-NLS-1$ + if (c.length() == 0) continue; + File cmd = new File(c); + Boolean found = Boolean.valueOf(cmd.exists() && cmd.isFile()); + sOptionalCommandsMap.put(key, found); + if (DEBUG) { + Log.w(TAG, + String.format( + "Optional command %s %s.", //$NON-NLS-1$ + c, found ? "found" : "not found")); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + } catch (Exception e) { + Log.e(TAG, + "Failed to read optional shell commands.", e); //$NON-NLS-1$ + } + } } diff --git a/Backbone/src/main/java/me/toolify/backbone/actionmode/PropertiesModeCallback.java b/Backbone/src/main/java/me/toolify/backbone/actionmode/PropertiesModeCallback.java new file mode 100644 index 000000000..866a8e7ca --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/actionmode/PropertiesModeCallback.java @@ -0,0 +1,474 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * Copyright (C) 2013 BrandroidTools + * + * 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 me.toolify.backbone.actionmode; + +import android.app.Activity; +import android.content.DialogInterface; +import android.view.ActionMode; +import android.view.InflateException; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.widget.ShareActionProvider; + +import java.util.List; + +import de.greenrobot.event.EventBus; +import me.toolify.backbone.FileManagerApplication; +import me.toolify.backbone.R; +import me.toolify.backbone.bus.events.BookmarkRefreshEvent; +import me.toolify.backbone.bus.events.ClosePropertiesDrawerEvent; +import me.toolify.backbone.listeners.OnCopyMoveListener; +import me.toolify.backbone.listeners.OnRequestRefreshListener; +import me.toolify.backbone.listeners.OnSelectionListener; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.preferences.AccessMode; +import me.toolify.backbone.ui.dialogs.InputNameDialog; +import me.toolify.backbone.ui.policy.BookmarksActionPolicy; +import me.toolify.backbone.ui.policy.CompressActionPolicy; +import me.toolify.backbone.ui.policy.CopyMoveActionPolicy; +import me.toolify.backbone.ui.policy.DeleteActionPolicy; +import me.toolify.backbone.ui.policy.ExecutionActionPolicy; +import me.toolify.backbone.ui.policy.InfoActionPolicy; +import me.toolify.backbone.ui.policy.IntentsActionPolicy; +import me.toolify.backbone.ui.policy.NavigationActionPolicy; +import me.toolify.backbone.ui.policy.NewActionPolicy; +import me.toolify.backbone.util.FileHelper; +import me.toolify.backbone.util.MimeTypeHelper; +import me.toolify.backbone.util.StorageHelper; + +public class PropertiesModeCallback implements ActionMode.Callback { + + private MenuItem mActionCreateLinkGlobal; + private MenuItem mActionOpen; + private MenuItem mActionOpenWith; + private MenuItem mActionDelete; + private MenuItem mActionRename; + private MenuItem mActionCompress; + private MenuItem mActionExtract; + private MenuItem mActionCreateLink; + private MenuItem mActionExecute; + private MenuItem mActionSend; + private MenuItem mActionAddBookmark; + private MenuItem mActionAddShortcut; + private MenuItem mActionChecksum; + private ShareActionProvider mShareActionProvider; + + private boolean pasteReady = false; + + boolean mClosedByUser = true; + private Activity mActivity; + private ActionMode mPropertiesMode; + private Boolean mGlobal; + private final Boolean mChRooted; + + /** + * @hide + */ + OnRequestRefreshListener mOnRequestRefreshListener; + /** + * @hide + */ + OnSelectionListener mOnSelectionListener; + /** + * @hide + */ + OnCopyMoveListener onCopyMoveListener; + + private FileSystemObject mFso; + + /** + * Constructor for SelectionModeCallback. + * + * @param activity The current Activity context + * @param fso The FileSystemObject to present options for + */ + public PropertiesModeCallback(Activity activity, FileSystemObject fso) { + this.mActivity = activity; + this.mFso = fso; + this.mChRooted = FileManagerApplication.getAccessMode().compareTo(AccessMode.SAFE) == 0; + } + + /** + * Method that sets the listener for communicate a refresh request. + * + * @param onRequestRefreshListener The request refresh listener + */ + public void setOnRequestRefreshListener(OnRequestRefreshListener onRequestRefreshListener) { + this.mOnRequestRefreshListener = onRequestRefreshListener; + } + + /** + * Method that sets the listener for requesting selection data + * + * @param onSelectionListener The request selection data listener + */ + public void setOnSelectionListener(OnSelectionListener onSelectionListener) { + this.mOnSelectionListener = onSelectionListener; + } + + /** + * Method that sets the listener for marking file selections for paste + * + * @param onCopyMoveListener The request selection data listener + */ + public void setOnCopyMoveListener(OnCopyMoveListener onCopyMoveListener) { + this.onCopyMoveListener = onCopyMoveListener; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + mPropertiesMode = mode; + + MenuInflater inflater = mActivity.getMenuInflater(); + inflater.inflate(R.menu.properties_actionmode, menu); + + mActionCreateLinkGlobal = menu.findItem(R.id.mnu_actions_create_link_global); + mActionOpen = menu.findItem(R.id.mnu_actions_open); + mActionOpenWith = menu.findItem(R.id.mnu_actions_open_with); + mActionDelete = menu.findItem(R.id.mnu_actions_delete); + mActionRename = menu.findItem(R.id.mnu_actions_rename); + mActionCompress = menu.findItem(R.id.mnu_actions_compress); + mActionExtract = menu.findItem(R.id.mnu_actions_extract); + mActionCreateLink = menu.findItem(R.id.mnu_actions_create_link); + mActionExecute = menu.findItem(R.id.mnu_actions_execute); + mActionSend = menu.findItem(R.id.mnu_actions_send); + mActionAddBookmark = menu.findItem(R.id.mnu_actions_add_to_bookmarks); + mActionAddShortcut = menu.findItem(R.id.mnu_actions_add_shortcut); + mActionChecksum = menu.findItem(R.id.mnu_actions_compute_checksum); + + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + + //TODO: Figure out how to change/fix the mActionCreateLinkGlobal flag + this.mGlobal = false; + + // Reset action item visibility + mActionCreateLinkGlobal.setVisible(true); + mActionOpen.setVisible(true); + mActionOpenWith.setVisible(true); + mActionDelete.setVisible(true); + mActionRename.setVisible(true); + mActionCompress.setVisible(true); + mActionExtract.setVisible(true); + mActionCreateLink.setVisible(true); + mActionExecute.setVisible(true); + mActionSend.setVisible(true); + mActionAddBookmark.setVisible(true); + mActionAddShortcut.setVisible(true); + mActionChecksum.setVisible(true); + + /* + * Single file mode + * + * Remove the following actions if we are dealing with a single file + */ + + // Check actions that needs a valid reference + if (this.mFso != null) { + + // Hide Send/Open/Open With actions if the fso is folder or a system file + if (FileHelper.isDirectory(this.mFso) || FileHelper.isSystemFile(this.mFso)) { + mActionOpen.setVisible(false); + mActionOpenWith.setVisible(false); + mActionSend.setVisible(false); + } + + // Create link (not allow in storage volume) + if (StorageHelper.isPathInStorageVolume(this.mFso.getFullPath())) { + mActionCreateLink.setVisible(false); + } + + //Hide Execute action unless the mime/type category is EXEC + MimeTypeHelper.MimeTypeCategory category = MimeTypeHelper.getCategory(this.mActivity, this.mFso); + if (category.compareTo(MimeTypeHelper.MimeTypeCategory.EXEC) != 0) { + mActionExecute.setVisible(false); + } + } + + // Hide "Add Bookmark" if the fso is the root directory (Already has a bookmark!) + if (this.mFso != null && FileHelper.isRootDirectory(this.mFso)) { + mActionAddBookmark.setVisible(false); + } + + /* + * Multi-file mode + * + * Remove the following actions if we are dealing with a multi-fso selection + */ + + if (!this.mGlobal) { + // Create link (not allow in storage volume) + if (StorageHelper.isPathInStorageVolume(this.mFso.getFullPath())) { + mActionCreateLink.setVisible(false); + } + } + + // Hide extract (uncompress/unzip) action for non-supported files + if (!FileHelper.isSupportedUncompressedFile(this.mFso)) { + mActionExtract.setVisible(false); + } + + // Hide actions that can't be present when running in unprivileged mode) + if (this.mChRooted) { + mActionCreateLink.setVisible(false); + mActionCreateLinkGlobal.setVisible(false); + mActionExecute.setVisible(false); + + // NOTE: This actions are not implemented in chrooted environments. The reason is + // that the main target of this application is CyanogenMod (a rooted environment). + // Adding this actions requires the use of commons-compress, an external Apache + // library that will add more size to the ending apk. + // For now, will maintain without implementation. Maybe, in the future. + mActionCompress.setVisible(false); + mActionExtract.setVisible(false); + } + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem menuItem) { + + switch (menuItem.getItemId()) { + + //- Rename + case R.id.mnu_actions_rename: + showFsoInputNameDialog(menuItem, this.mFso, false); + finish(); + return true; + + //- Create link + case R.id.mnu_actions_create_link: + showFsoInputNameDialog(menuItem, this.mFso, true); + finish(); + return true; + + case R.id.mnu_actions_create_link_global: + showFsoInputNameDialog(menuItem, this.mFso, true); + finish(); + return true; + + //- Delete + case R.id.mnu_actions_delete: + DeleteActionPolicy.removeFileSystemObject( + this.mActivity, + this.mFso, + this.mOnSelectionListener, + this.mOnRequestRefreshListener, + null); + finish(); + break; + + //- Open + case R.id.mnu_actions_open: + IntentsActionPolicy.openFileSystemObject( + this.mActivity, this.mFso, false, null, null); + finish(); + break; + //- Open with + case R.id.mnu_actions_open_with: + IntentsActionPolicy.openFileSystemObject( + this.mActivity, this.mFso, true, null, null); + finish(); + break; + + //- Execute + case R.id.mnu_actions_execute: + ExecutionActionPolicy.execute(this.mActivity, this.mFso); + finish(); + break; + + //- Send + case R.id.mnu_actions_send: + IntentsActionPolicy.sendFileSystemObject( + this.mActivity, this.mFso, null, null); + finish(); + break; + + //- Create copy + case R.id.mnu_actions_copy: + // Create a copy of the fso + if (this.mOnSelectionListener != null) { + List selection = + this.mOnSelectionListener.onRequestSelectedFiles(); + CopyMoveActionPolicy.createCopyFileSystemObject( + selection, + this.onCopyMoveListener); + finish(); + } + break; + + // Move selection + case R.id.mnu_actions_move: + if (this.mOnSelectionListener != null) { + List selection = + this.mOnSelectionListener.onRequestSelectedFiles(); + CopyMoveActionPolicy.createMoveFileSystemObject( + selection, + this.onCopyMoveListener); + finish(); + } + break; + + //- Uncompress + case R.id.mnu_actions_extract: + CompressActionPolicy.uncompress( + this.mActivity, + this.mFso, + this.mOnRequestRefreshListener); + break; + //- Checksum + case R.id.mnu_actions_compute_checksum: + InfoActionPolicy.showComputeChecksumDialog( + this.mActivity, + this.mFso); + break; + //- Compress + case R.id.mnu_actions_compress: + if (this.mOnSelectionListener != null) { + CompressActionPolicy.compress( + this.mActivity, + this.mFso, + this.mOnSelectionListener, + this.mOnRequestRefreshListener); + finish(); + } + break; + + //- Add to bookmarks + case R.id.mnu_actions_add_to_bookmarks: + BookmarksActionPolicy.addToBookmarks(this.mActivity, this.mFso); + EventBus.getDefault().post(new BookmarkRefreshEvent()); + break; + + //- Add shortcut + case R.id.mnu_actions_add_shortcut: + IntentsActionPolicy.createShortcut(this.mActivity, this.mFso); + break; + + //- Navigate to parent + case R.id.mnu_actions_open_parent_folder: + NavigationActionPolicy.openParentFolder( + this.mActivity, this.mFso, this.mOnRequestRefreshListener); + break; + + default: + break; + } + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public void onDestroyActionMode(ActionMode mode) { + mPropertiesMode = null; + EventBus.getDefault().post(new ClosePropertiesDrawerEvent()); + } + + public void finish() { + mPropertiesMode.finish(); + } + + public void setClosedByUser(boolean closedByUser) { + this.mClosedByUser = closedByUser; + } + + public boolean inPropertiesActionMode() { + return mPropertiesMode != null; + } + + public void refresh() { + mPropertiesMode.invalidate(); + } + + /** + * Method that show a new dialog for input a name for an existing fso. + * + * @param menuItem The item menu associated + * @param fso The file system object + * @param allowFsoName If allow that the name of the fso will be returned + */ + private void showFsoInputNameDialog( + final MenuItem menuItem, final FileSystemObject fso, final boolean allowFsoName) { + + //Show the input name dialog + final InputNameDialog inputNameDialog = + new InputNameDialog( + this.mActivity, + this.mOnSelectionListener.onRequestCurrentItems(), + fso, + allowFsoName, + menuItem.getTitle().toString()); + inputNameDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + //Retrieve the name an execute the action + try { + String name = inputNameDialog.getName(); + switch (menuItem.getItemId()) { + case R.id.mnu_actions_rename: + // Rename the fso + if (PropertiesModeCallback.this.mOnSelectionListener != null) { + CopyMoveActionPolicy.renameFileSystemObject( + PropertiesModeCallback.this.mActivity, + inputNameDialog.mFso, + name, + PropertiesModeCallback.this.mOnSelectionListener, + PropertiesModeCallback.this.mOnRequestRefreshListener); + } + break; + + case R.id.mnu_actions_create_link: + case R.id.mnu_actions_create_link_global: + // Create a link to the fso + if (PropertiesModeCallback.this.mOnSelectionListener != null) { + NewActionPolicy.createSymlink( + PropertiesModeCallback.this.mActivity, + inputNameDialog.mFso, + name, + PropertiesModeCallback.this.mOnSelectionListener, + PropertiesModeCallback.this.mOnRequestRefreshListener); + } + break; + + default: + break; + } + + } catch (InflateException e) { + //TODO: Catch this exception properly + } + } + }); + inputNameDialog.show(); + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/actionmode/SelectionModeCallback.java b/Backbone/src/main/java/me/toolify/backbone/actionmode/SelectionModeCallback.java new file mode 100644 index 000000000..770bd4521 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/actionmode/SelectionModeCallback.java @@ -0,0 +1,648 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * Copyright (C) 2013 BrandroidTools + * + * 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 me.toolify.backbone.actionmode; + +import android.app.Activity; +import android.content.DialogInterface; +import android.view.ActionMode; +import android.view.InflateException; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.ShareActionProvider; +import android.widget.TextView; + +import java.util.List; + +import de.greenrobot.event.EventBus; +import me.toolify.backbone.FileManagerApplication; +import me.toolify.backbone.R; +import me.toolify.backbone.bus.events.BookmarkRefreshEvent; +import me.toolify.backbone.bus.events.OpenPropertiesDrawerEvent; +import me.toolify.backbone.listeners.OnCopyMoveListener; +import me.toolify.backbone.listeners.OnRequestRefreshListener; +import me.toolify.backbone.listeners.OnSelectionListener; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.preferences.AccessMode; +import me.toolify.backbone.ui.dialogs.InputNameDialog; +import me.toolify.backbone.ui.policy.BookmarksActionPolicy; +import me.toolify.backbone.ui.policy.CompressActionPolicy; +import me.toolify.backbone.ui.policy.CopyMoveActionPolicy; +import me.toolify.backbone.ui.policy.DeleteActionPolicy; +import me.toolify.backbone.ui.policy.ExecutionActionPolicy; +import me.toolify.backbone.ui.policy.InfoActionPolicy; +import me.toolify.backbone.ui.policy.IntentsActionPolicy; +import me.toolify.backbone.ui.policy.NavigationActionPolicy; +import me.toolify.backbone.ui.policy.NewActionPolicy; +import me.toolify.backbone.util.FileHelper; +import me.toolify.backbone.util.MimeTypeHelper; +import me.toolify.backbone.util.StorageHelper; + +public class SelectionModeCallback implements ActionMode.Callback { + private MenuItem mActionMoveSelection; + private MenuItem mActionDeleteSelection; + private MenuItem mActionCompressSelection; + private MenuItem mActionCreateLinkGlobal; + private MenuItem mActionSendSelection; + private MenuItem mActionProperties; + private MenuItem mActionOpen; + private MenuItem mActionOpenWith; + private MenuItem mActionDelete; + private MenuItem mActionRename; + private MenuItem mActionCompress; + private MenuItem mActionExtract; + private MenuItem mActionCreateCopy; + private MenuItem mActionCreateLink; + private MenuItem mActionExecute; + private MenuItem mActionSend; + private MenuItem mActionAddBookmark; + private MenuItem mActionAddShortcut; + private MenuItem mActionChecksum; + + private TextView mFileCount; + private TextView mFolderCount; + + boolean mClosedByUser = true; + private Activity mActivity; + private ActionMode mSelectionMode; + private Boolean mGlobal; + private Boolean mMultiSelection; + private final Boolean mSearch; + private final Boolean mChRooted; + + /** + * @hide + */ + OnRequestRefreshListener mOnRequestRefreshListener; + /** + * @hide + */ + OnSelectionListener mOnSelectionListener; + /** + * @hide + */ + OnCopyMoveListener onCopyMoveListener; + + private FileSystemObject mFso; + + /** + * Constructor for SelectionModeCallback. + * + * @param activity The current Activity context + * @param search If the call is from search activity + */ + public SelectionModeCallback (Activity activity, Boolean search) { + this.mActivity = activity; + this.mSearch = search; + this.mChRooted = FileManagerApplication.getAccessMode().compareTo(AccessMode.SAFE) == 0; + } + + /** + * Method that sets the listener for communicate a refresh request. + * + * @param onRequestRefreshListener The request refresh listener + */ + public void setOnRequestRefreshListener(OnRequestRefreshListener onRequestRefreshListener) { + this.mOnRequestRefreshListener = onRequestRefreshListener; + } + + /** + * Method that sets the listener for requesting selection data + * + * @param onSelectionListener The request selection data listener + */ + public void setOnSelectionListener(OnSelectionListener onSelectionListener) { + this.mOnSelectionListener = onSelectionListener; + } + + /** + * Method that sets the listener for marking file selections for paste + * + * @param onCopyMoveListener The request selection data listener + */ + public void setOnCopyMoveListener(OnCopyMoveListener onCopyMoveListener) { + this.onCopyMoveListener = onCopyMoveListener; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + mSelectionMode = mode; + + MenuInflater inflater = mActivity.getMenuInflater(); + inflater.inflate(R.menu.actionmode, menu); + + View customTitle = mActivity.getLayoutInflater().inflate(R.layout.navigation_action_mode, null, false); + mFileCount = (TextView)customTitle.findViewById(R.id.file_count); + mFolderCount = (TextView)customTitle.findViewById(R.id.folder_count); + mode.setCustomView(customTitle); + + mActionCreateCopy = menu.findItem(R.id.mnu_actions_copy); + mActionMoveSelection = menu.findItem(R.id.mnu_actions_move); + mActionDeleteSelection = menu.findItem(R.id.mnu_actions_delete_selection); + mActionCompressSelection = menu.findItem(R.id.mnu_actions_compress_selection); + mActionCreateLinkGlobal = menu.findItem(R.id.mnu_actions_create_link_global); + mActionSendSelection = menu.findItem(R.id.mnu_actions_send_selection); + mActionProperties = menu.findItem(R.id.mnu_actions_properties); + mActionOpen = menu.findItem(R.id.mnu_actions_open); + mActionOpenWith = menu.findItem(R.id.mnu_actions_open_with); + mActionDelete = menu.findItem(R.id.mnu_actions_delete); + mActionRename = menu.findItem(R.id.mnu_actions_rename); + mActionCompress = menu.findItem(R.id.mnu_actions_compress); + mActionExtract = menu.findItem(R.id.mnu_actions_extract); + mActionCreateLink = menu.findItem(R.id.mnu_actions_create_link); + mActionExecute = menu.findItem(R.id.mnu_actions_execute); + mActionSend = menu.findItem(R.id.mnu_actions_send); + mActionAddBookmark = menu.findItem(R.id.mnu_actions_add_to_bookmarks); + mActionAddShortcut = menu.findItem(R.id.mnu_actions_add_shortcut); + mActionChecksum = menu.findItem(R.id.mnu_actions_compute_checksum); + + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + int folders = 0; + int files = 0; + String folderCount; + String fileCount; + + // Get selection + List selection = null; + if (this.mOnSelectionListener != null) { + selection = this.mOnSelectionListener.onRequestSelectedFiles(); + } + + // Count selection + for (FileSystemObject fso : selection) { + if (FileHelper.isDirectory(fso)) { + folders++; + } else { + files++; + } + } + + // Display selection counts in action mode + folderCount = Integer.toString(folders); + fileCount = Integer.toString(files); + mFolderCount.setText(folderCount); + mFileCount.setText(fileCount); + + //TODO: Figure out how to change/fix the mActionCreateLinkGlobal flag + this.mGlobal = false; + + // Reset action item visibility + mActionMoveSelection.setVisible(true); + mActionDeleteSelection.setVisible(true); + mActionCompressSelection.setVisible(true); + mActionCreateLinkGlobal.setVisible(true); + mActionSendSelection.setVisible(true); + mActionProperties.setVisible(true); + mActionOpen.setVisible(true); + mActionOpenWith.setVisible(true); + mActionDelete.setVisible(true); + mActionRename.setVisible(true); + mActionCompress.setVisible(true); + mActionExtract.setVisible(true); + mActionCreateCopy.setVisible(true); + mActionCreateLink.setVisible(true); + mActionExecute.setVisible(true); + mActionSend.setVisible(true); + mActionAddBookmark.setVisible(true); + mActionAddShortcut.setVisible(true); + + // Determine the need for single file (not global) and multiple selection (global) operations + if (selection.size() == 1) { + this.mFso = selection.get(0); + this.mMultiSelection = false; + + // Hide multi target actions when only one item is selected + mActionDeleteSelection.setVisible(false); + mActionCompressSelection.setVisible(false); + mActionSendSelection.setVisible(false); + } else { + this.mMultiSelection = true; + + // Hide single target actions when multiple items are selected + mActionProperties.setVisible(false); + mActionOpen.setVisible(false); + mActionOpenWith.setVisible(false); + mActionDelete.setVisible(false); + mActionRename.setVisible(false); + mActionCompress.setVisible(false); + mActionExtract.setVisible(false); + mActionCreateLink.setVisible(false); + mActionExecute.setVisible(false); + mActionSend.setVisible(false); + mActionAddBookmark.setVisible(false); + mActionAddShortcut.setVisible(false); + mActionCreateLinkGlobal.setVisible(false); + mActionChecksum.setVisible(false); + } + + /* + * Single file mode + * + * Remove the following actions if we are dealing with a single file + */ + + // Check actions that needs a valid reference + if (this.mFso != null) { + + // Hide Send/Open/Open With actions if the fso is folder or a system file + if (FileHelper.isDirectory(this.mFso) || FileHelper.isSystemFile(this.mFso)) { + mActionOpen.setVisible(false); + mActionOpenWith.setVisible(false); + mActionSend.setVisible(false); + } + + // Create link (not allow in storage volume) + if (StorageHelper.isPathInStorageVolume(this.mFso.getFullPath())) { + mActionCreateLink.setVisible(false); + } + + //Hide Execute action unless the mime/type category is EXEC + MimeTypeHelper.MimeTypeCategory category = MimeTypeHelper.getCategory(this.mActivity, this.mFso); + if (category.compareTo(MimeTypeHelper.MimeTypeCategory.EXEC) != 0) { + mActionExecute.setVisible(false); + } + } + + // Hide "Add Bookmark" if the fso is the root directory (Already has a bookmark!) + if (this.mFso != null && FileHelper.isRootDirectory(this.mFso)) { + mActionAddBookmark.setVisible(false); + } + + /* + * Multi-file mode + * + * Remove the following actions if we are dealing with a multi-fso selection + */ + + //- Create link + if (this.mGlobal && selection != null) { + // Create link (not allow in storage volume) + FileSystemObject fso = selection.get(0); + if (StorageHelper.isPathInStorageVolume(fso.getFullPath())) { + mActionCreateLink.setVisible(false); + } + } else if (!this.mGlobal) { + // Create link (not allow in storage volume) + if (StorageHelper.isPathInStorageVolume(this.mFso.getFullPath())) { + mActionCreateLink.setVisible(false); + } + } + + // Hide extract (uncompress/unzip) action for non-supported files + if (!this.mMultiSelection && !FileHelper.isSupportedUncompressedFile(this.mFso)) { + mActionExtract.setVisible(false); + } + + // Send multiple (only regular files) + boolean areAllFiles = true; + for (FileSystemObject fso : selection) { + if (FileHelper.isDirectory(fso)) { + areAllFiles = false; + break; + } + } + if (!areAllFiles) { + mActionSendSelection.setVisible(false); + } + + // Hide actions that can't be present when running in unprivileged mode) + if (this.mChRooted) { + mActionCreateLink.setVisible(false); + mActionCreateLinkGlobal.setVisible(false); + mActionExecute.setVisible(false); + + // NOTE: This actions are not implemented in chrooted environments. The reason is + // that the main target of this application is CyanogenMod (a rooted environment). + // Adding this actions requires the use of commons-compress, an external Apache + // library that will add more size to the ending apk. + // For now, will maintain without implementation. Maybe, in the future. + mActionCompress.setVisible(false); + mActionCompressSelection.setVisible(false); + mActionExtract.setVisible(false); + } + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem menuItem) { + + switch (menuItem.getItemId()) { + + case R.id.mnu_select_all: + // Select all items in the visible navigation fragment + this.mOnSelectionListener.onSelectAllVisibleItems(); + break; + + //- Rename + case R.id.mnu_actions_rename: + if (this.mOnSelectionListener != null) { + showFsoInputNameDialog(menuItem, this.mFso, false); + finish(); + return true; + } + break; + + //- Create link + case R.id.mnu_actions_create_link: + if (this.mOnSelectionListener != null) { + showFsoInputNameDialog(menuItem, this.mFso, true); + finish(); + return true; + } + break; + case R.id.mnu_actions_create_link_global: + if (this.mOnSelectionListener != null) { + // The selection must be only 1 item + List selection = + this.mOnSelectionListener.onRequestSelectedFiles(); + if (selection != null && selection.size() == 1) { + showFsoInputNameDialog(menuItem, selection.get(0), true); + } + finish(); + return true; + } + break; + + //- Delete + case R.id.mnu_actions_delete: + DeleteActionPolicy.removeFileSystemObject( + this.mActivity, + this.mFso, + this.mOnSelectionListener, + this.mOnRequestRefreshListener, + null); + finish(); + break; + + //- Refresh + case R.id.mnu_actions_refresh: + if (this.mOnRequestRefreshListener != null) { + this.mOnRequestRefreshListener.onRequestRefresh(null, false); //Refresh all + } + break; + + //- Open + case R.id.mnu_actions_open: + IntentsActionPolicy.openFileSystemObject( + this.mActivity, this.mFso, false, null, null); + finish(); + break; + //- Open with + case R.id.mnu_actions_open_with: + IntentsActionPolicy.openFileSystemObject( + this.mActivity, this.mFso, true, null, null); + finish(); + break; + + //- Execute + case R.id.mnu_actions_execute: + ExecutionActionPolicy.execute(this.mActivity, this.mFso); + finish(); + break; + + //- Send + case R.id.mnu_actions_send: + IntentsActionPolicy.sendFileSystemObject( + this.mActivity, this.mFso, null, null); + finish(); + break; + case R.id.mnu_actions_send_selection: + if (this.mOnSelectionListener != null) { + List selection = + this.mOnSelectionListener.onRequestSelectedFiles(); + if (selection.size() == 1) { + IntentsActionPolicy.sendFileSystemObject( + this.mActivity, selection.get(0), null, null); + } else { + IntentsActionPolicy.sendMultipleFileSystemObject( + this.mActivity, selection, null, null); + } + finish(); + } + break; + + //- Create copy + case R.id.mnu_actions_copy: + // Create a copy of the fso + if (this.mOnSelectionListener != null) { + List selection = + this.mOnSelectionListener.onRequestSelectedFiles(); + CopyMoveActionPolicy.createCopyFileSystemObject( + selection, + this.onCopyMoveListener); + // Make sure that the main menu updates and shows the paste button. + mActivity.invalidateOptionsMenu(); + finish(); + } + break; + + // Move selection + case R.id.mnu_actions_move: + if (this.mOnSelectionListener != null) { + List selection = + this.mOnSelectionListener.onRequestSelectedFiles(); + CopyMoveActionPolicy.createMoveFileSystemObject( + selection, + this.onCopyMoveListener); + // Make sure that the main menu updates and shows the paste button. + mActivity.invalidateOptionsMenu(); + finish(); + } + break; + + // Delete selection + case R.id.mnu_actions_delete_selection: + if (this.mOnSelectionListener != null) { + List selection = + this.mOnSelectionListener.onRequestSelectedFiles(); + DeleteActionPolicy.removeFileSystemObjects( + this.mActivity, + selection, + this.mOnSelectionListener, + this.mOnRequestRefreshListener, + null); + finish(); + } + break; + + //- Uncompress + case R.id.mnu_actions_extract: + CompressActionPolicy.uncompress( + this.mActivity, + this.mFso, + this.mOnRequestRefreshListener); + break; + //- Checksum + case R.id.mnu_actions_compute_checksum: + InfoActionPolicy.showComputeChecksumDialog( + this.mActivity, + this.mFso); + break; + //- Compress + case R.id.mnu_actions_compress: + if (this.mOnSelectionListener != null) { + CompressActionPolicy.compress( + this.mActivity, + this.mFso, + this.mOnSelectionListener, + this.mOnRequestRefreshListener); + finish(); + } + break; + case R.id.mnu_actions_compress_selection: + if (this.mOnSelectionListener != null) { + CompressActionPolicy.compress( + this.mActivity, + this.mOnSelectionListener, + this.mOnRequestRefreshListener); + finish(); + } + break; + + //- Add to bookmarks + case R.id.mnu_actions_add_to_bookmarks: + BookmarksActionPolicy.addToBookmarks(this.mActivity, this.mFso); + EventBus.getDefault().post(new BookmarkRefreshEvent()); + break; + + //- Add shortcut + case R.id.mnu_actions_add_shortcut: + IntentsActionPolicy.createShortcut(this.mActivity, this.mFso); + break; + + //- Properties + case R.id.mnu_actions_properties: + case R.id.mnu_actions_properties_current_folder: + EventBus.getDefault().post(new OpenPropertiesDrawerEvent(this.mFso)); + break; + + //- Navigate to parent + case R.id.mnu_actions_open_parent_folder: + NavigationActionPolicy.openParentFolder( + this.mActivity, this.mFso, this.mOnRequestRefreshListener); + break; + + default: + break; + } + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public void onDestroyActionMode(ActionMode mode) { + // Clear this before onDeselectAll() to prevent onDeselectAll() from + // trying to close the contextual mode again. + mSelectionMode = null; + mOnSelectionListener.onDeselectAll(); + } + + public void finish() { + mSelectionMode.finish(); + } + + public void setClosedByUser(boolean closedByUser) { + this.mClosedByUser = closedByUser; + } + + public boolean inSelectionMode() { + return mSelectionMode != null; + } + + public void refresh() { + mSelectionMode.invalidate(); + } + + /** + * Method that show a new dialog for input a name for an existing fso. + * + * @param menuItem The item menu associated + * @param fso The file system object + * @param allowFsoName If allow that the name of the fso will be returned + */ + private void showFsoInputNameDialog( + final MenuItem menuItem, final FileSystemObject fso, final boolean allowFsoName) { + + //Show the input name dialog + final InputNameDialog inputNameDialog = + new InputNameDialog( + this.mActivity, + this.mOnSelectionListener.onRequestCurrentItems(), + fso, + allowFsoName, + menuItem.getTitle().toString()); + inputNameDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + //Retrieve the name an execute the action + try { + String name = inputNameDialog.getName(); + switch (menuItem.getItemId()) { + case R.id.mnu_actions_rename: + // Rename the fso + if (SelectionModeCallback.this.mOnSelectionListener != null) { + CopyMoveActionPolicy.renameFileSystemObject( + SelectionModeCallback.this.mActivity, + inputNameDialog.mFso, + name, + SelectionModeCallback.this.mOnSelectionListener, + SelectionModeCallback.this.mOnRequestRefreshListener); + } + break; + + case R.id.mnu_actions_create_link: + case R.id.mnu_actions_create_link_global: + // Create a link to the fso + if (SelectionModeCallback.this.mOnSelectionListener != null) { + NewActionPolicy.createSymlink( + SelectionModeCallback.this.mActivity, + inputNameDialog.mFso, + name, + SelectionModeCallback.this.mOnSelectionListener, + SelectionModeCallback.this.mOnRequestRefreshListener); + } + break; + + default: + break; + } + + } catch (InflateException e) { + //TODO: Catch this exception properly + } + } + }); + inputNameDialog.show(); + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/activities/AbstractNavigationActivity.java b/Backbone/src/main/java/me/toolify/backbone/activities/AbstractNavigationActivity.java new file mode 100644 index 000000000..9994151b0 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/activities/AbstractNavigationActivity.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2013 BrandroidTools + * + * 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 me.toolify.backbone.activities; + +import android.app.Activity; + +import me.toolify.backbone.FileManagerApplication; +import me.toolify.backbone.console.ConsoleBuilder; +import me.toolify.backbone.fragments.NavigationFragment; +import me.toolify.backbone.ui.widgets.BreadcrumbListener; + +public abstract class AbstractNavigationActivity extends Activity + implements BreadcrumbListener { + + // The flag indicating whether or not the application is just starting up. + // Used in initializing the action bar's breadcrumb once the fragments have + // finished loading. + private boolean mFirstRun = true; + + /** + * Determine whether the "just started up" flag is true or not. Used to perform + * actions on/based on the first (and only visible) navigationFragment loaded. + */ + public boolean isFirstRun() { + return mFirstRun; + } + + /** + * Change the app's "just started up" flag + */ + public void setFirstRun(boolean firstRun) { + this.mFirstRun = firstRun; + } + + /** + * Method that updates the titlebar of the activity or dialog + */ + public abstract void updateTitleActionBar(); + + /** + * Method called when a controlled exit is required + * @hide + */ + public void exit() { + try { + FileManagerApplication.destroyBackgroundConsole(); + } catch (Throwable ex) { + /**NON BLOCK**/ + } + try { + ConsoleBuilder.destroyConsole(); + } catch (Throwable ex) { + /**NON BLOCK**/ + } + finish(); + } + + /** + * Method that associates a breadcrumb with a {@link me.toolify.backbone.fragments.NavigationFragment} + * based on their shared position. This method does not create breadcrumbs, so it should throw + * an exception if a breadcrumb with the specified position does not exist. + * + * @param mPosition the position of the fragment + * @param fragment the fragment recieving the breadcrumb association + */ + public abstract void pairBreadcrumb(int mPosition, NavigationFragment fragment); +} diff --git a/src/com/cyanogenmod/filemanager/activities/ChangeLogActivity.java b/Backbone/src/main/java/me/toolify/backbone/activities/ChangeLogActivity.java similarity index 93% rename from src/com/cyanogenmod/filemanager/activities/ChangeLogActivity.java rename to Backbone/src/main/java/me/toolify/backbone/activities/ChangeLogActivity.java index ae3a7c8f1..9f9d44210 100644 --- a/src/com/cyanogenmod/filemanager/activities/ChangeLogActivity.java +++ b/Backbone/src/main/java/me/toolify/backbone/activities/ChangeLogActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.activities; +package me.toolify.backbone.activities; import android.app.Activity; import android.app.AlertDialog; @@ -28,11 +28,11 @@ import android.os.Bundle; import android.util.Log; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.preferences.FileManagerSettings; -import com.cyanogenmod.filemanager.ui.ThemeManager; -import com.cyanogenmod.filemanager.ui.ThemeManager.Theme; -import com.cyanogenmod.filemanager.util.DialogHelper; +import me.toolify.backbone.R; +import me.toolify.backbone.preferences.FileManagerSettings; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.ui.ThemeManager.Theme; +import me.toolify.backbone.util.DialogHelper; import java.io.InputStream; diff --git a/Backbone/src/main/java/me/toolify/backbone/activities/EditorActivity.java b/Backbone/src/main/java/me/toolify/backbone/activities/EditorActivity.java new file mode 100644 index 000000000..c7199b637 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/activities/EditorActivity.java @@ -0,0 +1,1431 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.activities; + +import android.app.ActionBar; +import android.app.Activity; +import android.app.AlertDialog; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Configuration; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Handler; +import android.preference.PreferenceActivity; +import android.text.Editable; +import android.text.InputType; +import android.text.SpannableStringBuilder; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.util.Log; +import android.view.*; +import android.widget.EditText; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.TextView.BufferType; +import android.widget.Toast; + +import me.toolify.backbone.util.HexDump; +import me.toolify.backbone.R; +import me.toolify.backbone.activities.preferences.EditorPreferenceFragment; +import me.toolify.backbone.activities.preferences.EditorSHColorSchemePreferenceFragment; +import me.toolify.backbone.activities.preferences.SettingsPreferences; +import com.ash.syntaxhighlight.HighlightColors; +import com.ash.syntaxhighlight.ISyntaxHighlightResourcesResolver; +import com.ash.syntaxhighlight.SyntaxHighlightFactory; +import com.ash.syntaxhighlight.SyntaxHighlightProcessor; +import me.toolify.backbone.commands.AsyncResultListener; +import me.toolify.backbone.commands.WriteExecutable; +import me.toolify.backbone.console.ConsoleBuilder; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.preferences.FileManagerSettings; +import me.toolify.backbone.preferences.Preferences; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.ui.ThemeManager.Theme; +import me.toolify.backbone.ui.widgets.ButtonItem; +import me.toolify.backbone.util.CommandHelper; +import me.toolify.backbone.util.DialogHelper; +import me.toolify.backbone.util.ExceptionUtil; +import me.toolify.backbone.util.ExceptionUtil.OnRelaunchCommandResult; +import me.toolify.backbone.util.FileHelper; +import me.toolify.backbone.util.ResourcesHelper; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.UUID; + +/** + * An internal activity for view and edit files. + */ +public class EditorActivity extends Activity implements TextWatcher { + + private static final String TAG = "EditorActivity"; //$NON-NLS-1$ + + private static boolean DEBUG = false; + + private static final int WRITE_RETRIES = 3; + + private final BroadcastReceiver mNotificationReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent != null) { + if (intent.getAction().compareTo(FileManagerSettings.INTENT_THEME_CHANGED) == 0) { + applyTheme(); + return; + } + if (intent.getAction().compareTo(FileManagerSettings.INTENT_SETTING_CHANGED) == 0) { + // The settings has changed + String key = intent.getStringExtra(FileManagerSettings.EXTRA_SETTING_CHANGED_KEY); + if (key != null) { + final EditorActivity activity = EditorActivity.this; + + // No suggestions + if (key.compareTo(FileManagerSettings.SETTINGS_EDITOR_NO_SUGGESTIONS.getId()) == 0) { + // Ignore in binary files + if (activity.mBinary) return; + + // Do we have a different setting? + boolean noSuggestionsSetting = + Preferences.getSharedPreferences().getBoolean( + FileManagerSettings.SETTINGS_EDITOR_NO_SUGGESTIONS.getId(), + ((Boolean)FileManagerSettings.SETTINGS_EDITOR_NO_SUGGESTIONS. + getDefaultValue()).booleanValue()); + if (noSuggestionsSetting != activity.mNoSuggestions) { + activity.mHandler.post(new Runnable() { + @Override + public void run() { + toggleNoSuggestions(); + } + }); + } + + // Word wrap + } else if (key.compareTo(FileManagerSettings.SETTINGS_EDITOR_WORD_WRAP.getId()) == 0) { + // Ignore in binary files + if (activity.mBinary) return; + + // Do we have a different setting? + boolean wordWrapSetting = Preferences.getSharedPreferences().getBoolean( + FileManagerSettings.SETTINGS_EDITOR_WORD_WRAP.getId(), + ((Boolean)FileManagerSettings.SETTINGS_EDITOR_WORD_WRAP. + getDefaultValue()).booleanValue()); + if (wordWrapSetting != activity.mWordWrap) { + activity.mHandler.post(new Runnable() { + @Override + public void run() { + toggleWordWrap(); + } + }); + } + + // Syntax highlight + // Default theme color scheme + // Color scheme + } else if (key.compareTo(FileManagerSettings.SETTINGS_EDITOR_SYNTAX_HIGHLIGHT.getId()) == 0) { + // Ignore in binary files + if (activity.mBinary) return; + + // Do we have a different setting? + boolean syntaxHighlightSetting = + Preferences.getSharedPreferences().getBoolean( + FileManagerSettings.SETTINGS_EDITOR_SYNTAX_HIGHLIGHT.getId(), + ((Boolean)FileManagerSettings.SETTINGS_EDITOR_SYNTAX_HIGHLIGHT. + getDefaultValue()).booleanValue()); + if (syntaxHighlightSetting != activity.mSyntaxHighlight) { + activity.mHandler.post(new Runnable() { + @Override + public void run() { + toggleSyntaxHighlight(); + } + }); + } + + } else if (key.compareTo(FileManagerSettings.SETTINGS_EDITOR_SH_USE_THEME_DEFAULT.getId()) == 0 || + key.compareTo(FileManagerSettings.SETTINGS_EDITOR_SH_COLOR_SCHEME.getId()) == 0 ) { + // Ignore in binary files + if (activity.mBinary) return; + + // Reload the syntax highlight + activity.mHandler.post(new Runnable() { + @Override + public void run() { + reloadSyntaxHighlight(); + } + }); + } + } + return; + } + } + } + }; + + /** + * Internal interface to notify progress update + */ + private interface OnProgressListener { + void onProgress(int progress); + } + + /** + * An internal listener for read a file + */ + private class AsyncReader implements AsyncResultListener { + + final Object mSync = new Object(); + ByteArrayOutputStream mByteBuffer = null; + SpannableStringBuilder mBuffer = null; + Exception mCause; + long mSize; + FileSystemObject mReadFso; + OnProgressListener mListener; + + /** + * Constructor of AsyncReader. For enclosing access. + */ + public AsyncReader() { + super(); + } + + /** + * {@inheritDoc} + */ + @Override + public void onAsyncStart() { + this.mByteBuffer = new ByteArrayOutputStream((int)this.mReadFso.getSize()); + this.mSize = 0; + } + + /** + * {@inheritDoc} + */ + @Override + public void onAsyncEnd(boolean cancelled) {/**NON BLOCK**/} + + /** + * {@inheritDoc} + */ + @Override + public void onAsyncExitCode(int exitCode) { + synchronized (this.mSync) { + this.mSync.notify(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onPartialResult(Object result) { + try { + if (result == null) return; + byte[] partial = (byte[])result; + + // Check if the file is a binary file. In this case the editor + // is read-only + if (!EditorActivity.this.mReadOnly) { + for (int i = 0; i < partial.length-1; i++) { + if (!isPrintableCharacter((char)partial[i])) { + EditorActivity.this.mBinary = true; + EditorActivity.this.mReadOnly = true; + break; + } + } + } + + this.mByteBuffer.write(partial, 0, partial.length); + this.mSize += partial.length; + if (this.mListener != null && this.mReadFso != null) { + int progress = 0; + if (this.mReadFso.getSize() != 0) { + progress = (int)((this.mSize*100) / this.mReadFso.getSize()); + } + this.mListener.onProgress(progress); + } + } catch (Exception e) { + this.mCause = e; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onException(Exception cause) { + this.mCause = cause; + } + } + + /** + * An internal listener for write a file + */ + private class AsyncWriter implements AsyncResultListener { + + Exception mCause; + + /** + * Constructor of AsyncWriter. For enclosing access. + */ + public AsyncWriter() { + super(); + } + + /** + * {@inheritDoc} + */ + @Override + public void onAsyncStart() {/**NON BLOCK**/} + + /** + * {@inheritDoc} + */ + @Override + public void onAsyncEnd(boolean cancelled) {/**NON BLOCK**/} + + /** + * {@inheritDoc} + */ + @Override + public void onAsyncExitCode(int exitCode) {/**NON BLOCK**/} + + /** + * {@inheritDoc} + */ + @Override + public void onPartialResult(Object result) {/**NON BLOCK**/} + + /** + * {@inheritDoc} + */ + @Override + public void onException(Exception cause) { + this.mCause = cause; + } + } + + /** + * An internal class to resolve resources for the syntax highlight library. + * @hide + */ + class ResourcesResolver implements ISyntaxHighlightResourcesResolver { + @Override + public CharSequence getString(String id, String resid) { + return EditorActivity.this.getString( + ResourcesHelper.getIdentifier( + EditorActivity.this.getResources(), "string", resid)); //$NON-NLS-1$ + } + + @Override + public int getInteger(String id, String resid, int def) { + return EditorActivity.this.getResources(). + getInteger( + ResourcesHelper.getIdentifier( + EditorActivity.this.getResources(), "integer", resid)); //$NON-NLS-1$ + } + + @Override + public int getColor(String id, String resid, int def) { + final Context ctx = EditorActivity.this; + try { + // Is default theme color scheme enabled? + if (isDefaultThemeColorScheme()) { + return ThemeManager.getCurrentTheme(ctx).getColor(ctx, resid); + } + + // Use the user-defined settings + int[] colors = getUserColorScheme(); + HighlightColors[] schemeColors = HighlightColors.values(); + int cc = schemeColors.length; + int cc2 = colors.length; + for (int i = 0; i < cc; i++) { + if (schemeColors[i].getId().compareTo(id) == 0) { + if (cc2 >= i) { + // User-defined + return colors[i]; + } + + // Theme default + return ThemeManager.getCurrentTheme(ctx).getColor(ctx, resid); + } + + } + + } catch (Exception ex) { + // Resource not found + } + return def; + } + + /** + * Method that returns if we should return the default theme color scheme or not + * + * @return boolean Whether return the default theme color scheme or not + */ + private boolean isDefaultThemeColorScheme() { + Boolean defaultValue = + (Boolean)FileManagerSettings. + SETTINGS_EDITOR_SH_USE_THEME_DEFAULT.getDefaultValue(); + return Preferences.getSharedPreferences().getBoolean( + FileManagerSettings.SETTINGS_EDITOR_SH_USE_THEME_DEFAULT.getId(), + defaultValue.booleanValue()); + } + + /** + * Method that returns the user-defined color scheme + * + * @return int[] The user-defined color scheme + */ + private int[] getUserColorScheme() { + String defaultValue = + (String)FileManagerSettings. + SETTINGS_EDITOR_SH_COLOR_SCHEME.getDefaultValue(); + String value = Preferences.getSharedPreferences().getString( + FileManagerSettings.SETTINGS_EDITOR_SH_COLOR_SCHEME.getId(), + defaultValue); + return EditorSHColorSchemePreferenceFragment.toColorShemeArray(value); + } + } + + /** + * @hide + */ + FileSystemObject mFso; + + private int mBufferSize; + private int mMaxFileSize; + + /** + * @hide + */ + boolean mDirty; + /** + * @hide + */ + boolean mReadOnly; + /** + * @hide + */ + boolean mBinary; + + /** + * @hide + */ + TextView mTitle; + /** + * @hide + */ + EditText mEditor; + /** + * @hide + */ + View mProgress; + /** + * @hide + */ + ProgressBar mProgressBar; + /** + * @hide + */ + TextView mProgressBarMsg; + /** + * @hide + */ + ButtonItem mSave; + MenuItem mSaveAction; + MenuItem mWordWrapAction; + MenuItem mNoSuggestionsAction; + MenuItem mSyntaxHighlightAction; + + // No suggestions status + /** + * @hide + */ + boolean mNoSuggestions; + + // Word wrap status + private ViewGroup mWordWrapView; + private ViewGroup mNoWordWrapView; + /** + * @hide + */ + boolean mWordWrap; + + // Syntax highlight status + /** + * @hide + */ + boolean mSyntaxHighlight; + /** + * @hide + */ + SyntaxHighlightProcessor mSyntaxHighlightProcessor; + private int mEditStart; + private int mEditEnd; + + private View mOptionsAnchorView; + + private final Object mExecSync = new Object(); + + /** + * @hide + */ + Handler mHandler; + + private static final char[] VALID_NON_PRINTABLE_CHARS = {' ', '\t', '\r', '\n'}; + + /** + * @hide + */ + String mHexLineSeparator; + + /** + * Intent extra parameter for the path of the file to open. + */ + public static final String EXTRA_OPEN_FILE = "extra_open_file"; //$NON-NLS-1$ + + /** + * {@inheritDoc} + */ + @Override + protected void onCreate(Bundle state) { + if (DEBUG) { + Log.d(TAG, "EditorActivity.onCreate"); //$NON-NLS-1$ + } + + this.mHandler = new Handler(); + + // Register the broadcast receiver + IntentFilter filter = new IntentFilter(); + filter.addAction(FileManagerSettings.INTENT_THEME_CHANGED); + filter.addAction(FileManagerSettings.INTENT_SETTING_CHANGED); + registerReceiver(this.mNotificationReceiver, filter); + + // Generate a random separator + this.mHexLineSeparator = UUID.randomUUID().toString(); + + //Set the main layout of the activity + setContentView(R.layout.editor); + + // Get the limit vars + this.mBufferSize = + getApplicationContext().getResources().getInteger(R.integer.buffer_size); + this.mMaxFileSize = + getApplicationContext().getResources().getInteger(R.integer.editor_max_file_size); + + //Initialize + initTitleActionBar(); + initLayout(); + + // Apply the theme + applyTheme(); + + // Initialize the console + initializeConsole(); + + // Read the file + readFile(); + + //Save state + super.onCreate(state); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onDestroy() { + if (DEBUG) { + Log.d(TAG, "EditorActivity.onDestroy"); //$NON-NLS-1$ + } + + // Unregister the receiver + try { + unregisterReceiver(this.mNotificationReceiver); + } catch (Throwable ex) { + /**NON BLOCK**/ + } + + //All destroy. Continue + super.onDestroy(); + } + + /** + * {@inheritDoc} + */ + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + } + + /** + * Method that initializes the titlebar of the activity. + */ + private void initTitleActionBar() { + //Configure the action bar options + getActionBar().setDisplayHomeAsUpEnabled(true); + getActionBar().setTitle(R.string.editor); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater menuInflater = getMenuInflater(); + menuInflater.inflate(R.menu.editor, menu); + mSaveAction = menu.findItem(R.id.mnu_save); + mWordWrapAction = menu.findItem(R.id.mnu_word_wrap); + mNoSuggestionsAction = menu.findItem(R.id.mnu_no_suggestions); + mSyntaxHighlightAction = menu.findItem(R.id.mnu_syntax_highlight); + + return super.onCreateOptionsMenu(menu); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + mWordWrapAction.setChecked(this.mWordWrap); + mNoSuggestionsAction.setChecked(mNoSuggestions); + mSyntaxHighlightAction.setChecked(mSyntaxHighlight); + mSaveAction.setVisible(this.mDirty); + + return super.onPrepareOptionsMenu(menu); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + if ((getActionBar().getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) + == ActionBar.DISPLAY_HOME_AS_UP) { + checkDirtyState(); + } + break; + case R.id.mnu_save: + // Save the file + checkAndWrite(); + break; + case R.id.mnu_no_suggestions: + toggleNoSuggestions(); + break; + case R.id.mnu_word_wrap: + toggleWordWrap(); + break; + case R.id.mnu_syntax_highlight: + toggleSyntaxHighlight(); + break; + case R.id.mnu_settings: + //Settings + Intent settings = new Intent(EditorActivity.this, SettingsPreferences.class); + settings.putExtra( + PreferenceActivity.EXTRA_SHOW_FRAGMENT, + EditorPreferenceFragment.class.getName()); + startActivity(settings); + break; + } + return super.onOptionsItemSelected(item); + } + + /** + * Method that initializes the layout and components of the activity. + */ + private void initLayout() { + this.mEditor = (EditText)findViewById(R.id.editor); + this.mEditor.setText(null); + this.mEditor.addTextChangedListener(this); + this.mEditor.setEnabled(false); + this.mWordWrapView = (ViewGroup)findViewById(R.id.editor_word_wrap_view); + this.mNoWordWrapView = (ViewGroup)findViewById(R.id.editor_no_word_wrap_view); + this.mWordWrapView.setVisibility(View.VISIBLE); + this.mNoWordWrapView.setVisibility(View.GONE); + + this.mNoSuggestions = false; + this.mWordWrap = true; + this.mSyntaxHighlight = true; + + // Load the no suggestions setting + boolean noSuggestionsSetting = Preferences.getSharedPreferences().getBoolean( + FileManagerSettings.SETTINGS_EDITOR_NO_SUGGESTIONS.getId(), + ((Boolean)FileManagerSettings.SETTINGS_EDITOR_NO_SUGGESTIONS. + getDefaultValue()).booleanValue()); + if (noSuggestionsSetting != this.mNoSuggestions) { + toggleNoSuggestions(); + } + + // Load the word wrap setting + boolean wordWrapSetting = Preferences.getSharedPreferences().getBoolean( + FileManagerSettings.SETTINGS_EDITOR_WORD_WRAP.getId(), + ((Boolean)FileManagerSettings.SETTINGS_EDITOR_WORD_WRAP. + getDefaultValue()).booleanValue()); + if (wordWrapSetting != this.mWordWrap) { + toggleWordWrap(); + } + + // Load the syntax highlight setting + boolean syntaxHighlighSetting = Preferences.getSharedPreferences().getBoolean( + FileManagerSettings.SETTINGS_EDITOR_SYNTAX_HIGHLIGHT.getId(), + ((Boolean)FileManagerSettings.SETTINGS_EDITOR_SYNTAX_HIGHLIGHT. + getDefaultValue()).booleanValue()); + if (syntaxHighlighSetting != this.mSyntaxHighlight) { + toggleSyntaxHighlight(); + } + + this.mProgress = findViewById(R.id.editor_progress); + this.mProgressBar = (ProgressBar)findViewById(R.id.editor_progress_bar); + this.mProgressBarMsg = (TextView)findViewById(R.id.editor_progress_msg); + } + + /** + * Method that toggle the no suggestions property of the editor + * @hide + */ + /**package**/ void toggleNoSuggestions() { + synchronized (this.mExecSync) { + int type = InputType.TYPE_CLASS_TEXT | + InputType.TYPE_TEXT_FLAG_MULTI_LINE | + InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE; + if (!this.mNoSuggestions) { + type |= InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS; + } + this.mEditor.setInputType(type); + this.mNoSuggestions = !this.mNoSuggestions; + } + } + + /** + * Method that toggle the word wrap property of the editor + * @hide + */ + /**package**/ void toggleWordWrap() { + synchronized (this.mExecSync) { + ViewGroup vSrc = this.mWordWrap ? this.mWordWrapView : this.mNoWordWrapView; + ViewGroup vDst = this.mWordWrap ? this.mNoWordWrapView : this.mWordWrapView; + ViewGroup vSrcParent = this.mWordWrap + ? this.mWordWrapView + : (ViewGroup)this.mNoWordWrapView.getChildAt(0); + ViewGroup vDstParent = this.mWordWrap + ? (ViewGroup)this.mNoWordWrapView.getChildAt(0) + : this.mWordWrapView; + vSrc.setVisibility(View.GONE); + vSrcParent.removeView(this.mEditor); + vDstParent.addView(this.mEditor); + vDst.setVisibility(View.VISIBLE); + vDst.scrollTo(0, 0); + this.mWordWrap = !this.mWordWrap; + } + } + + /** + * Method that toggles the syntax highlight property of the editor + * @hide + */ + /**package**/ void toggleSyntaxHighlight() { + synchronized (this.mExecSync) { + if (this.mSyntaxHighlightProcessor != null) { + try { + if (this.mSyntaxHighlight) { + this.mSyntaxHighlightProcessor.clear(this.mEditor.getText()); + } else { + this.mSyntaxHighlightProcessor.process(this.mEditor.getText()); + } + } catch (Exception ex) { + // An error in a syntax library, should not break down app. + Log.e(TAG, "Syntax highlight failed.", ex); //$NON-NLS-1$ + } + } + + this.mSyntaxHighlight = !this.mSyntaxHighlight; + } + } + + /** + * Method that reloads the syntax highlight of the current file + * @hide + */ + /**package**/ void reloadSyntaxHighlight() { + synchronized (this.mExecSync) { + if (this.mSyntaxHighlightProcessor != null) { + try { + this.mSyntaxHighlightProcessor.initialize(); + this.mSyntaxHighlightProcessor.process(this.mEditor.getText()); + } catch (Exception ex) { + // An error in a syntax library, should not break down app. + Log.e(TAG, "Syntax highlight failed.", ex); //$NON-NLS-1$ + } + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_BACK: + checkDirtyState(); + return true; + default: + return super.onKeyUp(keyCode, event); + } + } + + /** + * Method that initializes a console + */ + private boolean initializeConsole() { + try { + ConsoleBuilder.getConsole(this); + // There is a console allocated. Use it. + return true; + } catch (Throwable _throw) { + // Capture the exception + ExceptionUtil.translateException(this, _throw, false, true); + } + return false; + } + + /** + * Method that reads the requested file + */ + private void readFile() { + // For now editor is not dirty and editable. + setDirty(false); + this.mBinary = false; + + // Check for a valid action + String action = getIntent().getAction(); + if (action == null || + (action.compareTo(Intent.ACTION_VIEW) != 0) && + (action.compareTo(Intent.ACTION_EDIT) != 0)) { + DialogHelper.showToast( + this, R.string.editor_invalid_file_msg, Toast.LENGTH_SHORT); + return; + } + // This var should be set depending on ACTION_VIEW or ACTION_EDIT action, but for + // better compatibility, IntentsActionPolicy use always ACTION_VIEW, so we have + // to ignore this check here + this.mReadOnly = false; + + // Read the intent and check that is has a valid request + String path = getIntent().getData().getPath(); + if (path == null || path.length() == 0) { + DialogHelper.showToast( + this, R.string.editor_invalid_file_msg, Toast.LENGTH_SHORT); + return; + } + + // Set the title of the dialog + File f = new File(path); + getActionBar().setTitle(f.getName()); + + // Check that the file exists (the real file, not the symlink) + try { + this.mFso = CommandHelper.getFileInfo(this, path, true, null); + if (this.mFso == null) { + DialogHelper.showToast( + this, R.string.editor_file_not_found_msg, Toast.LENGTH_SHORT); + return; + } + } catch (Exception e) { + Log.e(TAG, "Failed to get file reference", e); //$NON-NLS-1$ + DialogHelper.showToast( + this, R.string.editor_file_not_found_msg, Toast.LENGTH_SHORT); + return; + } + + // Check that we can handle the length of the file (by device) + if (this.mMaxFileSize < this.mFso.getSize()) { + DialogHelper.showToast( + this, R.string.editor_file_exceed_size_msg, Toast.LENGTH_SHORT); + return; + } + + // Get the syntax highlight processor + SyntaxHighlightFactory shpFactory = + SyntaxHighlightFactory.getDefaultFactory(new ResourcesResolver()); + this.mSyntaxHighlightProcessor = shpFactory.getSyntaxHighlightProcessor(f); + if (this.mSyntaxHighlightProcessor != null) { + this.mSyntaxHighlightProcessor.initialize(); + } + + // Check that we have read access + try { + FileHelper.ensureReadAccess( + ConsoleBuilder.getConsole(this), + this.mFso, + null); + + // Read the file in background + asyncRead(); + + } catch (Exception ex) { + ExceptionUtil.translateException( + this, ex, false, true, new OnRelaunchCommandResult() { + @Override + public void onSuccess() { + // Read the file in background + asyncRead(); + } + + @Override + public void onFailed(Throwable cause) { + finish(); + } + + @Override + public void onCancelled() { + finish(); + } + }); + } + } + + /** + * Method that does the read of the file in background + * @hide + */ + void asyncRead() { + // Do the load of the file + AsyncTask mReadTask = + new AsyncTask() { + + private Exception mCause; + private AsyncReader mReader; + private boolean changeToBinaryMode; + private boolean changeToDisplaying; + + @Override + protected void onPreExecute() { + // Show the progress + this.changeToBinaryMode = false; + this.changeToDisplaying = false; + doProgress(true, 0); + } + + @Override + protected Boolean doInBackground(FileSystemObject... params) { + final EditorActivity activity = EditorActivity.this; + + // Only one argument (the file to open) + FileSystemObject fso = params[0]; + this.mCause = null; + + // Read the file in an async listener + try { + while (true) { + // Configure the reader + this.mReader = new AsyncReader(); + this.mReader.mReadFso = fso; + this.mReader.mListener = new OnProgressListener() { + @Override + @SuppressWarnings("synthetic-access") + public void onProgress(int progress) { + publishProgress(Integer.valueOf(progress)); + } + }; + + // Execute the command (read the file) + CommandHelper.read(activity, fso.getFullPath(), this.mReader, null); + + // Wait for + synchronized (this.mReader.mSync) { + this.mReader.mSync.wait(); + } + + // 100% + publishProgress(new Integer(100)); + + // Check if the read was successfully + if (this.mReader.mCause != null) { + this.mCause = this.mReader.mCause; + return Boolean.FALSE; + } + break; + } + + // Now we have the byte array with all the data. is a binary file? + // Then dump them byte array to hex dump string (only if users settings + // to dump file) + boolean hexDump = + Preferences.getSharedPreferences().getBoolean( + FileManagerSettings.SETTINGS_EDITOR_HEXDUMP.getId(), + ((Boolean)FileManagerSettings.SETTINGS_EDITOR_HEXDUMP. + getDefaultValue()).booleanValue()); + if (activity.mBinary && hexDump) { + // we do not use the Hexdump helper class, because we need to show the + // progress of the dump process + final String data = toHexPrintableString( + toHexDump( + this.mReader.mByteBuffer.toByteArray())); + this.mReader.mBuffer = new SpannableStringBuilder(data); + Log.i(TAG, "Bytes read: " + data.getBytes().length); //$NON-NLS-1$ + } else { + final String data = new String(this.mReader.mByteBuffer.toByteArray()); + this.mReader.mBuffer = new SpannableStringBuilder(data); + Log.i(TAG, "Bytes read: " + data.getBytes().length); //$NON-NLS-1$ + } + this.mReader.mByteBuffer = null; + + // 100% + this.changeToDisplaying = true; + publishProgress(new Integer(0)); + + } catch (Exception e) { + this.mCause = e; + return Boolean.FALSE; + } + + return Boolean.TRUE; + } + + @Override + protected void onProgressUpdate(Integer... values) { + // Do progress + doProgress(true, values[0].intValue()); + } + + @Override + protected void onPostExecute(Boolean result) { + final EditorActivity activity = EditorActivity.this; + + // Is error? + if (!result.booleanValue()) { + if (this.mCause != null) { + ExceptionUtil.translateException(activity, this.mCause); + activity.mEditor.setEnabled(false); + } + } else { + // Now we have the buffer, set the text of the editor + if (activity.mBinary) { + activity.mEditor.setText( + this.mReader.mBuffer, BufferType.NORMAL); + } else { + activity.mEditor.setText( + this.mReader.mBuffer, BufferType.EDITABLE); + + // Highlight editor text syntax + if (activity.mSyntaxHighlight && + activity.mSyntaxHighlightProcessor != null) { + try { + activity.mSyntaxHighlightProcessor.process( + activity.mEditor.getText()); + } catch (Exception ex) { + // An error in a syntax library, should not break down app. + Log.e(TAG, "Syntax highlight failed.", ex); //$NON-NLS-1$ + } + } + } + this.mReader.mBuffer = null; //Cleanup + setDirty(false); + activity.mEditor.setEnabled(!activity.mReadOnly); + + // Notify read-only mode + if (activity.mReadOnly) { + DialogHelper.showToast( + activity, + R.string.editor_read_only_mode, + Toast.LENGTH_SHORT); + } + } + + doProgress(false, 0); + } + + @Override + protected void onCancelled() { + // Hide the progress + doProgress(false, 0); + } + + /** + * Method that update the progress status + * + * @param visible If the progress bar need to be hidden + * @param progress The progress + */ + private void doProgress(boolean visible, int progress) { + final EditorActivity activity = EditorActivity.this; + + // Show the progress bar + activity.mProgressBar.setProgress(progress); + activity.mProgress.setVisibility(visible ? View.VISIBLE : View.GONE); + + if (this.changeToBinaryMode) { + // Hexdump always in nowrap mode + if (activity.mWordWrap) { + activity.toggleWordWrap(); + } + // Hexdump always has no syntax highlight + if (activity.mSyntaxHighlight) { + activity.toggleSyntaxHighlight(); + } + + // Show hex dumping text + activity.mProgressBarMsg.setText(R.string.dumping_message); + applyHexViewerTheme(); + this.changeToBinaryMode = false; + } + else if (this.changeToDisplaying) { + activity.mProgressBarMsg.setText(R.string.displaying_message); + this.changeToDisplaying = false; + } + } + + /** + * Create a hex dump of the data while show progress to user + * + * @param data The data to hex dump + * @return StringBuilder The hex dump buffer + */ + private String toHexDump(byte[] data) { + //Change to binary mode + this.changeToBinaryMode = true; + + // Start progress + publishProgress(Integer.valueOf(0)); + + // Calculate max dir size + int length = data.length; + + final int DISPLAY_SIZE = 16; // Bytes per line + ByteArrayInputStream bais = new ByteArrayInputStream(data); + byte[] line = new byte[DISPLAY_SIZE]; + int read = 0; + int offset = 0; + StringBuilder sb = new StringBuilder(); + while ((read = bais.read(line, 0, DISPLAY_SIZE)) != -1) { + //offset dump(16) data\n + String linedata = new String(line, 0, read); + sb.append(HexDump.toHexString(offset)); + sb.append(" "); //$NON-NLS-1$ + String hexDump = HexDump.toHexString(line, 0, read); + if (hexDump.length() != (DISPLAY_SIZE * 2)) { + char[] array = new char[(DISPLAY_SIZE * 2) - hexDump.length()]; + Arrays.fill(array, ' '); + hexDump += new String(array); + } + sb.append(hexDump); + sb.append(" "); //$NON-NLS-1$ + sb.append(linedata); + sb.append(EditorActivity.this.mHexLineSeparator); + offset += DISPLAY_SIZE; + if (offset % 5 == 0) { + publishProgress(Integer.valueOf((offset * 100) / length)); + } + } + + // End of the dump process + publishProgress(Integer.valueOf(100)); + + return sb.toString(); + } + + /** + * Method that converts to a visual printable hex string + * + * @param string The string to check + */ + private String toHexPrintableString(String string) { + // Remove characters without visual representation + final String REPLACED_SYMBOL = "."; //$NON-NLS-1$ + final String NEWLINE = System.getProperty("line.separator"); //$NON-NLS-1$ + String printable = string.replaceAll("\\p{Cntrl}", REPLACED_SYMBOL); //$NON-NLS-1$ + printable = printable.replaceAll("[^\\p{Print}]", REPLACED_SYMBOL); //$NON-NLS-1$ + printable = printable.replaceAll("\\p{C}", REPLACED_SYMBOL); //$NON-NLS-1$ + printable = printable.replaceAll(EditorActivity.this.mHexLineSeparator, NEWLINE); + return printable; + } + }; + mReadTask.execute(this.mFso); + } + + private void checkAndWrite() { + // Check that we have write access + try { + FileHelper.ensureWriteAccess( + ConsoleBuilder.getConsole(this), + this.mFso, + null); + + // Write the file + ensureSyncWrite(); + + } catch (Exception ex) { + ExceptionUtil.translateException( + this, ex, false, true, new OnRelaunchCommandResult() { + @Override + public void onSuccess() { + // Write the file + ensureSyncWrite(); + } + + @Override + public void onFailed(Throwable cause) {/**NON BLOCK**/} + + @Override + public void onCancelled() {/**NON BLOCK**/} + }); + } + } + + /** + * Method that checks that the write to disk operation was successfully and the + * expected bytes are written to disk. + * @hide + */ + void ensureSyncWrite() { + try { + for (int i = 0; i < WRITE_RETRIES; i++) { + // Configure the writer + AsyncWriter writer = new AsyncWriter(); + + // Write to disk + final byte[] data = this.mEditor.getText().toString().getBytes(); + long expected = data.length; + syncWrite(writer, data); + + // Sleep a bit + Thread.sleep(150L); + + // Is error? + if (writer.mCause != null) { + Log.e(TAG, "Write operation failed. Retries: " + i, writer.mCause); + if (i == (WRITE_RETRIES-1)) { + // Something was wrong. The file probably is corrupted + DialogHelper.showToast( + this, R.string.msgs_operation_failure, Toast.LENGTH_SHORT); + break; + } + + // Retry + continue; + } + + // Check that all the bytes were written + FileSystemObject fso = + CommandHelper.getFileInfo(this, this.mFso.getFullPath(), true, null); + if (fso == null || fso.getSize() != expected) { + Log.e(TAG, String.format( + "Size is not the same. Expected: %d, Written: %d. Retries: %d", + expected, fso == null ? -1 : fso.getSize(), i)); + if (i == (WRITE_RETRIES-1)) { + // Something was wrong. The destination data is not the same + // as the source data + DialogHelper.showToast( + this, R.string.msgs_operation_failure, Toast.LENGTH_SHORT); + break; + } + + // Retry + continue; + } + + // Success. The file was saved + DialogHelper.showToast( + this, R.string.editor_successfully_saved, Toast.LENGTH_SHORT); + setDirty(false); + + // Send a message that allow other activities to update his data + Intent intent = new Intent(FileManagerSettings.INTENT_FILE_CHANGED); + intent.putExtra( + FileManagerSettings.EXTRA_FILE_CHANGED_KEY, this.mFso.getFullPath()); + sendBroadcast(intent); + + // Done + break; + + } + } catch (Exception ex) { + // Something was wrong, but the file was NOT written + Log.e(TAG, "The file wasn't written.", ex); + DialogHelper.showToast( + this, R.string.msgs_operation_failure, Toast.LENGTH_SHORT); + } + } + + /** + * Method that write the file. + * + * @param writer The command listener + * @param bytes The bytes to write + * @throws Exception If something was wrong + */ + private void syncWrite(AsyncWriter writer, byte[] bytes) throws Exception { + // Create the writable command + WriteExecutable cmd = + CommandHelper.write(this, this.mFso.getFullPath(), writer, null); + + // Obtain access to the buffer (IMP! don't close the buffer here, it's manage + // by the command) + OutputStream os = cmd.createOutputStream(); + try { + // Retrieve the text from the editor + ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + try { + // Buffered write + byte[] data = new byte[this.mBufferSize]; + int read = 0, written = 0; + while ((read = bais.read(data, 0, this.mBufferSize)) != -1) { + os.write(data, 0, read); + written += read; + } + Log.i(TAG, "Bytes written: " + written); //$NON-NLS-1$ + } finally { + try { + bais.close(); + } catch (Exception e) {/**NON BLOCK**/} + } + + } finally { + // Ok. Data is written or ensure buffer close + cmd.end(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void beforeTextChanged( + CharSequence s, int start, int count, int after) {/**NON BLOCK**/} + + /** + * {@inheritDoc} + */ + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + this.mEditStart = start; + this.mEditEnd = start + count; + } + + /** + * {@inheritDoc} + */ + @Override + public void afterTextChanged(Editable s) { + setDirty(true); + if (this.mSyntaxHighlightProcessor != null) { + this.mSyntaxHighlightProcessor.process(s, this.mEditStart, this.mEditEnd); + } + } + + /** + * Method that sets if the editor is dirty (has changed) + * + * @param dirty If the editor is dirty + * @hide + */ + void setDirty(boolean dirty) { + this.mDirty = dirty; + this.invalidateOptionsMenu(); + } + + /** + * Check the dirty state of the editor, and ask the user to save the changes + * prior to exit. + */ + public void checkDirtyState() { + if (this.mDirty) { + AlertDialog dlg = DialogHelper.createYesNoDialog( + this, + R.string.editor_dirty_ask_title, + R.string.editor_dirty_ask_msg, + new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (which == DialogInterface.BUTTON_POSITIVE) { + dialog.dismiss(); + setResult(Activity.RESULT_OK); + finish(); + } + } + }); + DialogHelper.delegateDialogShow(this, dlg); + return; + } + setResult(Activity.RESULT_OK); + finish(); + } + + /** + * Method that check if a character is valid printable character + * + * @param c The character to check + * @return boolean If the character is printable + * @hide + */ + static boolean isPrintableCharacter(char c) { + int cc = VALID_NON_PRINTABLE_CHARS.length; + for (int i = 0; i < cc; i++) { + if (c == VALID_NON_PRINTABLE_CHARS[i]) { + return true; + } + } + return TextUtils.isGraphic(c); + } + + /** + * Method that applies the current theme to the activity + * @hide + */ + void applyTheme() { + Theme theme = ThemeManager.getCurrentTheme(this); + theme.setBaseTheme(this, false); + + theme.setBackgroundDrawable(this, getWindow().getDecorView(), "background_drawable"); //$NON-NLS-1$ + View v = findViewById(R.id.editor); + theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$ + //- ProgressBar + Drawable dw = theme.getDrawable(this, "horizontal_progress_bar"); //$NON-NLS-1$ + this.mProgressBar.setProgressDrawable(dw); + v = findViewById(R.id.editor_progress_msg); + theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$ + + // Need a full process of syntax highlight + if (!this.mBinary && this.mSyntaxHighlight && this.mSyntaxHighlightProcessor != null) { + reloadSyntaxHighlight(); + } + } + + /** + * Method that applies the current theme to the hex viewer editor + * @hide + */ + void applyHexViewerTheme() { + Theme theme = ThemeManager.getCurrentTheme(this); + TextView editor = (TextView)findViewById(R.id.editor); + editor.setTextAppearance(this, R.style.hexeditor_text_appearance); + editor.setTypeface(Typeface.MONOSPACE); + theme.setTextColor(this, editor, "text_color"); //$NON-NLS-1$ + } + +} diff --git a/Backbone/src/main/java/me/toolify/backbone/activities/HistoryActivity.java b/Backbone/src/main/java/me/toolify/backbone/activities/HistoryActivity.java new file mode 100644 index 000000000..ae534107c --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/activities/HistoryActivity.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * Copyright (C) 2013 BrandroidTools + * + * 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 me.toolify.backbone.activities; + +import android.app.Activity; +import android.app.FragmentManager; +import android.app.FragmentTransaction; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.util.Log; +import android.view.*; + +import me.toolify.backbone.R; +import me.toolify.backbone.fragments.HistoryFragment; +import me.toolify.backbone.model.History; +import me.toolify.backbone.preferences.FileManagerSettings; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.ui.ThemeManager.Theme; + +/** + * An activity for show navigation history. + */ +public class HistoryActivity extends Activity { + + private static final String TAG = "HistoryActivity"; //$NON-NLS-1$ + + private static boolean DEBUG = false; + + HistoryFragment mHistoryFragment; + + private final BroadcastReceiver mNotificationReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent != null) { + if (intent.getAction().compareTo(FileManagerSettings.INTENT_THEME_CHANGED) == 0) { + applyTheme(); + } + } + } + }; + + /** + * {@inheritDoc} + */ + @Override + protected void onCreate(Bundle state) { + if (DEBUG) { + Log.d(TAG, "HistoryActivity.onCreate"); //$NON-NLS-1$ + } + + // Register the broadcast receiver + IntentFilter filter = new IntentFilter(); + filter.addAction(FileManagerSettings.INTENT_THEME_CHANGED); + registerReceiver(this.mNotificationReceiver, filter); + + //Set the main layout of the activity + setContentView(R.layout.history); + + // Load the BoookmarksFragment + FragmentManager fm = getFragmentManager(); + FragmentTransaction ft = fm.beginTransaction(); + mHistoryFragment = new HistoryFragment(); + ft.add(R.id.fragment_content, mHistoryFragment); + ft.commit(); + + //Set in transition + overridePendingTransition(R.anim.translate_to_right_in, R.anim.hold_out); + + //Initialize action bar + initTitleActionBar(); + + //Save state + super.onCreate(state); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onDestroy() { + if (DEBUG) { + Log.d(TAG, "HistoryActivity.onDestroy"); //$NON-NLS-1$ + } + + // Unregister the receiver + try { + unregisterReceiver(this.mNotificationReceiver); + } catch (Throwable ex) { + /**NON BLOCK**/ + } + + //All destroy. Continue + super.onDestroy(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onPause() { + //Set out transition + overridePendingTransition(R.anim.hold_in, R.anim.translate_to_left_out); + super.onPause(); + } + + /** + * Method that initializes the titlebar of the activity. + */ + private void initTitleActionBar() { + //Configure the action bar options + getActionBar().setDisplayHomeAsUpEnabled(true); + getActionBar().setTitle(R.string.history); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater menuInflater = getMenuInflater(); + menuInflater.inflate(R.menu.history, menu); + return super.onCreateOptionsMenu(menu); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + return super.onPrepareOptionsMenu(menu); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + back(true, null); + break; + case R.id.mnu_clear_history: + mHistoryFragment.clearHistory(); + break; + } + return super.onOptionsItemSelected(item); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_BACK: + back(true, null); + return true; + default: + return super.onKeyUp(keyCode, event); + } + } + + /** + * Method that returns to previous activity and. + * + * @param cancelled Indicates if the activity was cancelled + * @param history The selected history + */ + public void back(final boolean cancelled, final History history) { + Intent intent = new Intent(); + if (cancelled) { + if (mHistoryFragment.mIsClearHistory) { + intent.putExtra(NavigationActivity.EXTRA_HISTORY_CLEAR, true); + } + setResult(RESULT_CANCELED, intent); + } else { + intent.putExtra(NavigationActivity.EXTRA_HISTORY_ENTRY_SELECTION, history); + setResult(RESULT_OK, intent); + } + finish(); + } + + /** + * Method that applies the current theme to the activity + * @hide + */ + void applyTheme() { + Theme theme = ThemeManager.getCurrentTheme(this); + theme.setBaseTheme(this, false); + + // -View + theme.setBackgroundDrawable(this, getWindow().getDecorView(), "background_drawable"); //$NON-NLS-1$ + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/activities/NavigationActivity.java b/Backbone/src/main/java/me/toolify/backbone/activities/NavigationActivity.java new file mode 100644 index 000000000..04d36662c --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/activities/NavigationActivity.java @@ -0,0 +1,1809 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * Copyright (C) 2013 BrandroidTools + * + * 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 me.toolify.backbone.activities; + +import android.annotation.TargetApi; +import android.app.ActionBar; +import android.app.AlertDialog; +import android.app.SearchManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Configuration; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.nfc.NfcAdapter; +import android.nfc.NfcEvent; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Parcelable; +import android.support.v4.app.ActionBarDrawerToggle; +import android.support.v4.view.ViewPager; +import android.support.v4.view.ViewPager.OnPageChangeListener; +import android.support.v4.widget.DrawerLayout; +import android.util.Log; +import android.view.InflateException; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; +import android.widget.Toast; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import de.greenrobot.event.EventBus; +import me.toolify.backbone.BuildConfig; +import me.toolify.backbone.FileManagerApplication; +import me.toolify.backbone.R; +import me.toolify.backbone.actionmode.PropertiesModeCallback; +import me.toolify.backbone.activities.preferences.SettingsPreferences; +import me.toolify.backbone.adapters.NavigationFragmentPagerAdapter; +import me.toolify.backbone.bus.events.BookmarkDeleteEvent; +import me.toolify.backbone.bus.events.BookmarkOpenEvent; +import me.toolify.backbone.bus.events.BookmarkRefreshEvent; +import me.toolify.backbone.bus.events.ClosePropertiesDrawerEvent; +import me.toolify.backbone.bus.events.FilesystemStatusUpdateEvent; +import me.toolify.backbone.bus.events.OpenPropertiesDrawerEvent; +import me.toolify.backbone.console.Console; +import me.toolify.backbone.console.ConsoleAllocException; +import me.toolify.backbone.console.ConsoleBuilder; +import me.toolify.backbone.console.NoSuchFileOrDirectory; +import me.toolify.backbone.fragments.HistoryFragment; +import me.toolify.backbone.fragments.NavigationFragment; +import me.toolify.backbone.fragments.NavigationFragment.OnNavigationRequestMenuListener; +import me.toolify.backbone.listeners.OnCopyMoveListener; +import me.toolify.backbone.listeners.OnRequestRefreshListener; +import me.toolify.backbone.model.Bookmark; +import me.toolify.backbone.model.DiskUsage; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.model.History; +import me.toolify.backbone.model.MountPoint; +import me.toolify.backbone.parcelables.NavigationViewInfoParcelable; +import me.toolify.backbone.parcelables.SearchInfoParcelable; +import me.toolify.backbone.preferences.AccessMode; +import me.toolify.backbone.preferences.Bookmarks; +import me.toolify.backbone.preferences.FileManagerSettings; +import me.toolify.backbone.preferences.NavigationLayoutMode; +import me.toolify.backbone.preferences.NavigationSortMode; +import me.toolify.backbone.preferences.ObjectIdentifier; +import me.toolify.backbone.preferences.Preferences; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.ui.ThemeManager.Theme; +import me.toolify.backbone.ui.dialogs.FilesystemInfoDialog; +import me.toolify.backbone.ui.dialogs.FilesystemInfoDialog.OnMountListener; +import me.toolify.backbone.ui.dialogs.InputNameDialog; +import me.toolify.backbone.ui.policy.BookmarksActionPolicy; +import me.toolify.backbone.ui.policy.CopyMoveActionPolicy; +import me.toolify.backbone.ui.policy.CopyMoveActionPolicy.COPY_MOVE_OPERATION; +import me.toolify.backbone.ui.policy.NewActionPolicy; +import me.toolify.backbone.ui.widgets.BookmarksListView; +import me.toolify.backbone.ui.widgets.Breadcrumb; +import me.toolify.backbone.ui.widgets.BreadcrumbItem; +import me.toolify.backbone.ui.widgets.BreadcrumbPager; +import me.toolify.backbone.ui.widgets.BreadcrumbSpinner; +import me.toolify.backbone.ui.widgets.FsoPropertiesView; +import me.toolify.backbone.ui.widgets.NavigationCustomTitleView; +import me.toolify.backbone.util.CommandHelper; +import me.toolify.backbone.util.DialogHelper; +import me.toolify.backbone.util.ExceptionUtil; +import me.toolify.backbone.util.FileHelper; +import me.toolify.backbone.util.StorageHelper; + +/** + * The main navigation activity. This activity is the center of the application. + * From this the user can navigate, search, make actions.
+ * This activity is singleTop, so when it is displayed no other activities exists in + * the stack.
+ * This cause an issue with the saved instance of this class, because if another activity + * is displayed, and the process is killed, NavigationActivity is started and the saved + * instance gets corrupted.
+ * For this reason the methods {link {@link android.app.Activity#onSaveInstanceState(android.os.Bundle)} and + * {@link android.app.Activity#onRestoreInstanceState(android.os.Bundle)} are not implemented, and every time + * the app is killed, is restarted from his initial state. + */ +public class NavigationActivity extends AbstractNavigationActivity + implements OnRequestRefreshListener, OnCopyMoveListener, + OnNavigationRequestMenuListener, OnPageChangeListener { + + private static final String TAG = "NavigationActivity"; //$NON-NLS-1$ + + private static boolean DEBUG = false; + + /** + * Intent code for request a bookmark selection. + */ + public static final int INTENT_REQUEST_BOOKMARK = 10001; + + /** + * Intent code for request a history selection. + */ + public static final int INTENT_REQUEST_HISTORY = 20001; + + /** + * Intent code for request a search. + */ + public static final int INTENT_REQUEST_SEARCH = 30001; + + + /** + * Constant for extra information about selected bookmark. + */ + public static final String EXTRA_BOOKMARK_SELECTION = + "extra_bookmark_selection"; //$NON-NLS-1$ + + /** + * Constant for extra information about selected history entry. + */ + public static final String EXTRA_HISTORY_ENTRY_SELECTION = + "extra_history_entry_selection"; //$NON-NLS-1$ + + /** + * Constant for extra information about clear selection action. + */ + public static final String EXTRA_HISTORY_CLEAR = + "extra_history_clear_history"; //$NON-NLS-1$ + + /** + * Constant for extra information about selected search entry. + */ + public static final String EXTRA_SEARCH_ENTRY_SELECTION = + "extra_search_entry_selection"; //$NON-NLS-1$ + + /** + * Constant for extra information about last search data. + */ + public static final String EXTRA_SEARCH_LAST_SEARCH_DATA = + "extra_search_last_search_data"; //$NON-NLS-1$ + + /** + * Constant for extra information for request a navigation to the passed path. + */ + public static final String EXTRA_NAVIGATE_TO = + "extra_navigate_to"; //$NON-NLS-1$ + + // The timeout needed to reset the exit status for back button + // After this time user need to tap 2 times the back button to + // exit, and the toast is shown again after the first tap. + private static final int RELEASE_EXIT_CHECK_TIMEOUT = 3500; + + private final BroadcastReceiver mNotificationReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent != null) { + if (intent.getAction().compareTo(FileManagerSettings.INTENT_SETTING_CHANGED) == 0) { + // The settings has changed + String key = intent.getStringExtra(FileManagerSettings.EXTRA_SETTING_CHANGED_KEY); + if (key != null) { + // Disk usage warning level + if (key.compareTo(FileManagerSettings. + SETTINGS_DISK_USAGE_WARNING_LEVEL.getId()) == 0) { + + // Set the free disk space warning level of the breadcrumb widget + Breadcrumb breadcrumb = getCurrentNavigationFragment().getBreadcrumb(); + String fds = Preferences.getSharedPreferences().getString( + FileManagerSettings.SETTINGS_DISK_USAGE_WARNING_LEVEL.getId(), + (String)FileManagerSettings. + SETTINGS_DISK_USAGE_WARNING_LEVEL.getDefaultValue()); + breadcrumb.setFreeDiskSpaceWarningLevel(Integer.parseInt(fds)); + breadcrumb.updateMountPointInfo(); + return; + } + + // Case sensitive sort + if (key.compareTo(FileManagerSettings. + SETTINGS_CASE_SENSITIVE_SORT.getId()) == 0) { + getCurrentNavigationFragment().refresh(); + return; + } + + // Display thumbs + if (key.compareTo(FileManagerSettings. + SETTINGS_DISPLAY_THUMBS.getId()) == 0) { + // Clean the icon cache applying the current theme + applyTheme(); + return; + } + + // Use flinger + if (key.compareTo(FileManagerSettings. + SETTINGS_USE_FLINGER.getId()) == 0) { + boolean useFlinger = + Preferences.getSharedPreferences().getBoolean( + FileManagerSettings.SETTINGS_USE_FLINGER.getId(), + ((Boolean)FileManagerSettings. + SETTINGS_USE_FLINGER. + getDefaultValue()).booleanValue()); + getCurrentNavigationFragment().setUseFlinger(useFlinger); + return; + } + + // Access mode + if (key.compareTo(FileManagerSettings. + SETTINGS_ACCESS_MODE.getId()) == 0) { + // Is it necessary to create or exit of the ChRooted? + boolean chRooted = + FileManagerApplication. + getAccessMode().compareTo(AccessMode.SAFE) == 0; + if (chRooted != NavigationActivity.this.mChRooted) { + if (chRooted) { + createChRooted(); + } else { + exitChRooted(); + } + } + // Update bookmarks to reflect access mode change + EventBus.getDefault().post(new BookmarkRefreshEvent()); + } + + // Filetime format mode + if (key.compareTo(FileManagerSettings. + SETTINGS_FILETIME_FORMAT_MODE.getId()) == 0) { + // Refresh the data + synchronized (FileHelper.DATETIME_SYNC) { + FileHelper.sReloadDateTimeFormats = true; + NavigationActivity.this.getCurrentNavigationFragment().refresh(); + } + } + } + + } else if (intent.getAction().compareTo( + FileManagerSettings.INTENT_FILE_CHANGED) == 0) { + // Retrieve the file that was changed + String file = + intent.getStringExtra(FileManagerSettings.EXTRA_FILE_CHANGED_KEY); + try { + FileSystemObject fso = CommandHelper.getFileInfo(context, file, null); + if (fso != null) { + getCurrentNavigationFragment().refresh(fso); + } + } catch (Exception e) { + ExceptionUtil.translateException(context, e, true, false); + } + + } else if (intent.getAction().compareTo( + FileManagerSettings.INTENT_THEME_CHANGED) == 0) { + applyTheme(); + + } else if (intent.getAction().compareTo(Intent.ACTION_TIME_CHANGED) == 0 || + intent.getAction().compareTo(Intent.ACTION_DATE_CHANGED) == 0 || + intent.getAction().compareTo(Intent.ACTION_TIMEZONE_CHANGED) == 0) { + // Refresh the data + synchronized (FileHelper.DATETIME_SYNC) { + FileHelper.sReloadDateTimeFormats = true; + NavigationActivity.this.getCurrentNavigationFragment().refresh(); + } + } + } + } + }; + + /** + * @hide + */ + private ActionBar mActionBar; + private Menu mOptionsMenu; + private MenuItem mFilesystemInfo; + private int mFilesystemStatus; + private DrawerLayout mDrawerLayout; + private ActionBarDrawerToggle mDrawerToggle; + private BookmarksListView mBookmarkDrawer; + private FsoPropertiesView mInfoDrawer; + private PropertiesModeCallback mPropertiesModeCallback; + private View mTitleLayout; + private NavigationCustomTitleView mTitle; + private Breadcrumb mBreadcrumb; + + /** + * @hide + */ + private BreadcrumbPager mBreadcrumbPager; + public NavigationFragmentPagerAdapter mPagerAdapter; + public ViewPager mViewPager; + + private boolean mExitFlag = false; + private long mExitBackTimeout = -1; + + public boolean mChRooted; + + /** + * @hide + */ + Handler mHandler; + + /** + * @hide + */ + private List mFilesForPaste; + /** + * @hide + */ + private COPY_MOVE_OPERATION mPasteOperationType; + + /** + * {@inheritDoc} + */ + @TargetApi(16) + @Override + protected void onCreate(Bundle state) { + + if (DEBUG) { + Log.d(TAG, "NavigationActivity.onCreate"); //$NON-NLS-1$ + } + + // Register the broadcast receiver + IntentFilter filter = new IntentFilter(); + filter.addAction(FileManagerSettings.INTENT_SETTING_CHANGED); + filter.addAction(FileManagerSettings.INTENT_FILE_CHANGED); + filter.addAction(FileManagerSettings.INTENT_THEME_CHANGED); + filter.addAction(Intent.ACTION_DATE_CHANGED); + filter.addAction(Intent.ACTION_TIME_CHANGED); + filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); + registerReceiver(this.mNotificationReceiver, filter); + + // Initialize NFC adapter + NfcAdapter mNfcAdapter = NfcAdapter.getDefaultAdapter(this); + if (mNfcAdapter != null && Build.VERSION.SDK_INT > 16) { + mNfcAdapter.setBeamPushUrisCallback(new NfcAdapter.CreateBeamUrisCallback() { + @Override + public Uri[] createBeamUris(NfcEvent event) { + List selectedFiles = + getCurrentNavigationFragment().getSelectedFiles(); + if (selectedFiles.size() > 0) { + List fileUri = new ArrayList(); + for (FileSystemObject f : selectedFiles) { + //Beam ignores folders and system files + if (!FileHelper.isDirectory(f) && !FileHelper.isSystemFile(f)) { + fileUri.add(Uri.fromFile(new File(f.getFullPath()))); + } + } + if (fileUri.size() > 0) { + return fileUri.toArray(new Uri[fileUri.size()]); + } + } + return null; + } + }, this); + } + + setContentView(R.layout.navigation); + + //Initialize activity console + init(); + + //Initialize viewPager after the action bar + initViewPager(); + + //Initialize action bar + mActionBar = getActionBar(); + initTitleActionBar(); + + // Create ActionBar drawer toggle drawable + mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); + mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, + R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close) { + + /** Called when a drawer has settled in a completely closed state. */ + public void onDrawerClosed(View view) { + // Reload the fragments current dir into the breadcrumb + if (getCurrentBreadcrumb() != null) { + getCurrentBreadcrumb().changeBreadcrumbPath(getCurrentNavigationFragment().getCurrentDir(), mChRooted); + } + invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu() + + // If the properties drawer was just closed, we should lock it closed and make sure + // that the properties action mode is closed too. + if (view instanceof FsoPropertiesView) { + EventBus.getDefault().post(new ClosePropertiesDrawerEvent()); + } + } + + /** Called when a drawer has settled in a completely open state. */ + public void onDrawerOpened(View drawerView) { + getActionBar().setTitle(R.string.bookmarks); + invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu() + } + }; + + // Set the drawer toggle as the DrawerListener + mDrawerLayout.setDrawerListener(mDrawerToggle); + + getActionBar().setDisplayHomeAsUpEnabled(true); + getActionBar().setHomeButtonEnabled(true); + + mBookmarkDrawer = (BookmarksListView) findViewById(R.id.left_drawer); + mInfoDrawer = (FsoPropertiesView) findViewById(R.id.right_drawer); + mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, mInfoDrawer); + + // Apply the theme + applyTheme(); + + // Show welcome message + showWelcomeMsg(); + + //Save state + super.onCreate(state); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onNewIntent(Intent intent) { + //Initialize navigation + NavigationActivity.this.getCurrentNavigationFragment().initNavigation(true, intent); + + //Check the intent action + checkIntent(intent); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onPause() { + super.onPause(); + + // Always unregister when an object no longer should be on the bus. + EventBus.getDefault().unregister(this); + EventBus.getDefault().unregister(mBookmarkDrawer); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onResume() { + super.onResume(); + mBreadcrumbPager.setViewPager(mViewPager); + //mBreadcrumbPager.setOnPageChangeListener(this); + +// mSlidingStrip.setViewPager(mViewPager); +// mSlidingStrip.setOnPageChangeListener(this); + + // Register ourselves so that we can provide the initial value. + EventBus.getDefault().register(this); + EventBus.getDefault().register(mBookmarkDrawer); + + // Tell the bookmarks fragment to refresh itself + EventBus.getDefault().post(new BookmarkRefreshEvent()); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + // Sync the toggle state after onRestoreInstanceState has occurred. + mDrawerToggle.syncState(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onDestroy() { + if (DEBUG) { + Log.d(TAG, "NavigationActivity.onDestroy"); //$NON-NLS-1$ + } + + // Unregister the receiver + try { + unregisterReceiver(this.mNotificationReceiver); + } catch (Throwable ex) { + /**NON BLOCK**/ + } + + //All destroy. Continue + super.onDestroy(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + mDrawerToggle.onConfigurationChanged(newConfig); + mBreadcrumbPager.refreshLayout(); + } + + /** + * Method that returns the current navigation view. + * + * @return NavigationFragment The current navigation view + */ + public NavigationFragment getCurrentNavigationFragment() { + return mPagerAdapter.getFragment(mViewPager, mViewPager.getCurrentItem()); + } + + + /** + * Method that returns the current breadcrumb view. + * + * @return BreadcrumbSpinner The current breadcrumb view + */ + public BreadcrumbSpinner getCurrentBreadcrumb() { + return mBreadcrumbPager.getBreadcrumb(mViewPager.getCurrentItem()); + } + + /** + * {@inheritDoc} + */ + public void pairBreadcrumb(int position, NavigationFragment fragment) { + try { + Breadcrumb b = mBreadcrumbPager.getBreadcrumb(position); + if (b != null) { + // Breadcrumb has already been loaded into the breadcrumb pager + fragment.setBreadcrumb(mBreadcrumbPager.getBreadcrumb(position)); + } else { + // Otherwise it hasn't yet, so lets queue it up + mBreadcrumbPager.queuePairFragment(position, fragment); + } + } catch (Throwable ex) { + Log.e(TAG, + String.format("Failed to pair breadcrumb %d", //$NON-NLS-1$ + position, ex)); + } + } + + /** + * Method that initializes the activity. + */ + private void init() { + this.mChRooted = FileManagerApplication.getAccessMode().compareTo(AccessMode.SAFE) == 0; + + this.mHandler = new Handler(); + this.mHandler.post(new Runnable() { + @Override + public void run() { + //Create the default console (from the preferences) + try { + Console console = ConsoleBuilder.getConsole(NavigationActivity.this); + if (console == null) { + throw new ConsoleAllocException("console == null"); //$NON-NLS-1$ + } + } catch (Throwable ex) { + if (!NavigationActivity.this.isChRooted()) { + //Show exception and exists + Log.e(TAG, getString(R.string.msgs_cant_create_console), ex); + // We don't have any console + // Show exception and exists + DialogHelper.showToast( + NavigationActivity.this, + R.string.msgs_cant_create_console, Toast.LENGTH_LONG); + NavigationActivity.this.exit(); + return; + } + + // We are in a trouble (something is not allowing creating the console) + // Ask the user to return to prompt or root access mode mode with a + // non-privileged console, prior to make crash the application + NavigationActivity.this.askOrExit(); + return; + } + } + }); + } + + /** + * Method that displays a welcome message the first time the user + * access the application + */ + private void showWelcomeMsg() { + boolean firstUse = Preferences.getSharedPreferences().getBoolean( + FileManagerSettings.SETTINGS_FIRST_USE.getId(), + ((Boolean)FileManagerSettings.SETTINGS_FIRST_USE.getDefaultValue()).booleanValue()); + + //Display the welcome message? + if (firstUse) { + AlertDialog dialog = DialogHelper.createAlertDialog( + this, R.drawable.ic_launcher, + R.string.welcome_title, getString(R.string.welcome_msg), false); + DialogHelper.delegateDialogShow(this, dialog); + + // Don't display again this dialog + try { + Preferences.savePreference( + FileManagerSettings.SETTINGS_FIRST_USE, Boolean.FALSE, true); + } catch (Exception e) {/**NON BLOCK**/} + } + } + + /** + * Method that initializes the ViewPager and NavigationPagerAdapter + */ + private void initViewPager(){ + mViewPager = (ViewPager)findViewById(R.id.navigation_pager); + + // Plug the ViewPager into the Pager Adapter and set the number of pages + mPagerAdapter = new NavigationFragmentPagerAdapter(this, getFragmentManager()); + mViewPager.setAdapter(mPagerAdapter); + mViewPager.setHorizontalScrollBarEnabled(true); + + // TODO this is a pretty much a hack job for now, and proper state + // retaining needs to be implemented for tablets + mViewPager.setOffscreenPageLimit(mPagerAdapter.getCount()); + } + + /** + * Method that initializes the titlebar of the activity. + */ + private void initTitleActionBar() { + //Inflate the view and associate breadcrumb + mTitleLayout = getLayoutInflater().inflate( + R.layout.navigation_view_customtitle, null, false); + mTitle = (NavigationCustomTitleView)mTitleLayout.findViewById(R.id.navigation_title_flipper); + mBreadcrumb = (Breadcrumb)mTitle.findViewById(R.id.breadcrumb_view); + //mSlidingStrip = (PagerSlidingTabStrip)mTitleLayout.findViewById(R.id.breadcrumb_sliding_strip); + mBreadcrumbPager = (BreadcrumbPager)mTitleLayout.findViewById(R.id.breadcrumb_pager); + + // Set the free disk space warning level of the breadcrumb widget + String fds = Preferences.getSharedPreferences().getString( + FileManagerSettings.SETTINGS_DISK_USAGE_WARNING_LEVEL.getId(), + (String)FileManagerSettings.SETTINGS_DISK_USAGE_WARNING_LEVEL.getDefaultValue()); + mBreadcrumb.setFreeDiskSpaceWarningLevel(Integer.parseInt(fds)); + //Configure the action bar options + mActionBar.setDisplayOptions( + ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_SHOW_HOME); + mActionBar.setCustomView(mTitleLayout); + } + + /** + * {@inheritDoc} + */ + public void updateTitleActionBar() { + + NavigationFragment navigationFragment = getCurrentNavigationFragment(); + mTitle.setOnHistoryListener(navigationFragment); + navigationFragment.setOnHistoryListener(navigationFragment); + navigationFragment.setOnNavigationOnRequestMenuListener(this); + } + + /** + * Method that verifies the intent passed to the activity, and checks + * if a request is made like Search. + * + * @param intent The intent to check + * @hide + */ + void checkIntent(Intent intent) { + //Search action + if (Intent.ACTION_SEARCH.equals(intent.getAction())) { + Intent searchIntent = new Intent(this, SearchActivity.class); + searchIntent.setAction(Intent.ACTION_SEARCH); + //- SearchActivity.EXTRA_SEARCH_DIRECTORY + searchIntent.putExtra( + SearchActivity.EXTRA_SEARCH_DIRECTORY, + getCurrentNavigationFragment().getCurrentDir()); + //- SearchManager.APP_DATA + if (intent.getBundleExtra(SearchManager.APP_DATA) != null) { + Bundle bundle = new Bundle(); + bundle.putAll(intent.getBundleExtra(SearchManager.APP_DATA)); + searchIntent.putExtra(SearchManager.APP_DATA, bundle); + } + //-- SearchManager.QUERY + String query = intent.getStringExtra(SearchManager.QUERY); + if (query != null) { + searchIntent.putExtra(SearchManager.QUERY, query); + } + //- android.speech.RecognizerIntent.EXTRA_RESULTS + ArrayList extraResults = + intent.getStringArrayListExtra(android.speech.RecognizerIntent.EXTRA_RESULTS); + if (extraResults != null) { + searchIntent.putStringArrayListExtra( + android.speech.RecognizerIntent.EXTRA_RESULTS, extraResults); + } + startActivityForResult(searchIntent, INTENT_REQUEST_SEARCH); + return; + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + if (checkBackAction()) { + return true; + } + + // An exit event has occurred, force the destroy the consoles + exit(); + } + return super.onKeyUp(keyCode, event); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onCreateOptionsMenu(Menu menu) { + this.mOptionsMenu = menu; + MenuInflater menuInflater = getMenuInflater(); + menuInflater.inflate(R.menu.navigation, menu); + return super.onCreateOptionsMenu(menu); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + + // Ensure that the appropriate sort mode menuItem radio is selected + NavigationSortMode sortMode = NavigationSortMode.fromId(Preferences.getSharedPreferences() + .getInt(FileManagerSettings.SETTINGS_SORT_MODE.getId(), + ((ObjectIdentifier)FileManagerSettings.SETTINGS_SORT_MODE + .getDefaultValue()).getId())); + switch(sortMode) { + case DATE_ASC: + menu.findItem(R.id.mnu_actions_sort_by_date_asc).setChecked(true); + break; + case DATE_DESC: + menu.findItem(R.id.mnu_actions_sort_by_date_desc).setChecked(true); + break; + case NAME_ASC: + menu.findItem(R.id.mnu_actions_sort_by_name_asc).setChecked(true); + break; + case NAME_DESC: + menu.findItem(R.id.mnu_actions_sort_by_name_desc).setChecked(true); + break; + } + + // Ensure that the appropriate layout mode menuItem radio is selected + NavigationLayoutMode layoutMode = NavigationLayoutMode.fromId(Preferences. + getSharedPreferences().getInt(FileManagerSettings.SETTINGS_LAYOUT_MODE.getId(), + ((ObjectIdentifier)FileManagerSettings.SETTINGS_LAYOUT_MODE + .getDefaultValue()).getId())); + switch(layoutMode) { + case ICONS: + menu.findItem(R.id.mnu_actions_layout_icons).setChecked(true); + break; + case DETAILS: + menu.findItem(R.id.mnu_actions_layout_details).setChecked(true); + break; + case SIMPLE: + menu.findItem(R.id.mnu_actions_layout_simple).setChecked(true); + break; + } + + // Ensure that the other view mode options are correctly selected + int[] menuSettingIDs = new int[] { + R.id.mnu_actions_show_dirs_first, + R.id.mnu_actions_show_hidden, + R.id.mnu_actions_show_system, + R.id.mnu_actions_show_symlinks + }; + FileManagerSettings[] settingShows = new FileManagerSettings[] { + FileManagerSettings.SETTINGS_SHOW_DIRS_FIRST, + FileManagerSettings.SETTINGS_SHOW_HIDDEN, + FileManagerSettings.SETTINGS_SHOW_SYSTEM, + FileManagerSettings.SETTINGS_SHOW_SYMLINKS + }; + for(int i = 0; i < menuSettingIDs.length; i++) { + MenuItem item = menu.findItem(menuSettingIDs[i]); + if(item != null) { + item.setChecked(Preferences.getSharedPreferences().getBoolean( + settingShows[i].getId(), + Boolean.parseBoolean(settingShows[i].getDefaultValue().toString()))); + } + } + + // Make paste action visible if there are files available for pasting + menu.findItem(R.id.mnu_actions_paste_selection).setVisible(this.onAreFilesMarkedForPaste()); + // Toggle layout mode visibility so that it is only visible in development mode + menu.findItem(R.id.mnu_actions_layout_mode).setVisible(BuildConfig.DEBUG); + mFilesystemInfo = menu.findItem(R.id.mnu_actions_show_filesystem_info); + setFilesystemStatusDrawable(mFilesystemStatus); + return super.onPrepareOptionsMenu(menu); + } + + + /** + * This method is called by various pieces of code responsible for updating file listing or + * breadcrumb data. The method receives all {@link me.toolify.backbone.bus.events.FilesystemStatusUpdateEvent} + * events generated by the various asyncTasks responsible for gathering and sending file info. + * + * @param event an event containing the filesystem status code + */ + public void onEvent(FilesystemStatusUpdateEvent event) { + mFilesystemStatus = event.status; + setFilesystemStatusDrawable(event.status); + } + + private void setFilesystemStatusDrawable(int fileSystemstatus){ + + TypedArray a = getTheme().obtainStyledAttributes(R.styleable.FileManager); + + switch (fileSystemstatus) { + + case FilesystemStatusUpdateEvent.INDICATOR_UNLOCKED: + if (mFilesystemInfo != null) { + mFilesystemInfo.setIcon(getResources(). + getDrawable(a.getResourceId(R.styleable.FileManager_actionIconLockOpen, + R.drawable.ic_action_holo_dark_lock_open))); + setFilesystemInfoProgressState(false); + } + break; + + case FilesystemStatusUpdateEvent.INDICATOR_LOCKED: + if (mFilesystemInfo != null) { + mFilesystemInfo.setIcon(getResources(). + getDrawable(a.getResourceId(R.styleable.FileManager_actionIconLockClosed, + R.drawable.ic_action_holo_dark_lock_closed))); + setFilesystemInfoProgressState(false); + } + break; + + case FilesystemStatusUpdateEvent.INDICATOR_WARNING: + if (mFilesystemInfo != null) { + mFilesystemInfo.setIcon(getResources(). + getDrawable(a.getResourceId(R.styleable.FileManager_actionIconWarning, + R.drawable.ic_action_holo_dark_warning))); + setFilesystemInfoProgressState(false); + } + break; + + case FilesystemStatusUpdateEvent.INDICATOR_REFRESHING: + if (mFilesystemInfo != null) { + setFilesystemInfoProgressState(true); + } + break; + + case FilesystemStatusUpdateEvent.INDICATOR_STOP_REFRESHING: + if (mFilesystemInfo != null) { + setFilesystemInfoProgressState(false); + } + break; + } + + a.recycle(); + } + + /** + * This function switches an action item between its normal icon and an indeterminate progress + * circle + * @param refreshing value is true if the action item should show the progress bar + */ + public void setFilesystemInfoProgressState(final boolean refreshing) { + if (mFilesystemInfo != null) { + final MenuItem refreshItem = mOptionsMenu + .findItem(R.id.mnu_actions_show_filesystem_info); + if (refreshItem != null) { + if (refreshing) { + refreshItem.setActionView(R.layout.actionbar_indeterminate_progress); + } else { + refreshItem.setActionView(null); + } + } + } + } + + /** + * This method is called by the bookmarks list when a bookmark is selected for opening. The + * method receives all {@link me.toolify.backbone.bus.events.BookmarkOpenEvent} + * events and opens the referenced directory in the currently visible + * {@link me.toolify.backbone.fragments.NavigationFragment}. + * + * @param event an event containing a bookmarked directory that should be opened + */ + public void onEvent(BookmarkOpenEvent event) { + String path = event.path; + Bookmark b = Bookmarks.getBookmark(getContentResolver(), path); + // Check that the bookmark exists + try { + FileSystemObject fso = CommandHelper.getFileInfo(this, path, null); + if (fso != null) { + getCurrentNavigationFragment().open(fso); + mDrawerLayout.closeDrawer(mBookmarkDrawer); + } else { + // The bookmark not exists, delete the user-defined bookmark + try { + if(b.getType() == Bookmark.BOOKMARK_TYPE.USER_DEFINED) + { + EventBus.getDefault().post(new BookmarkDeleteEvent(path)); + Bookmarks.removeBookmark(this, b); + EventBus.getDefault().post(new BookmarkRefreshEvent()); + } + } catch (Exception ex) {/**NON BLOCK**/} + } + } catch (Exception e) { + // Capture the exception + ExceptionUtil.translateException(this, e); + if (e instanceof NoSuchFileOrDirectory || e instanceof FileNotFoundException) { + // The bookmark not exists, delete the user-defined bookmark + try { + if(b.getType() == Bookmark.BOOKMARK_TYPE.USER_DEFINED) + EventBus.getDefault().post(new BookmarkDeleteEvent(path)); + } catch (Exception ex) {/**NON BLOCK**/} + } + return; + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onOptionsItemSelected(MenuItem item) { + + // Drawer Toggle pass-off + if (mDrawerToggle.onOptionsItemSelected(item)) { + return true; + } + + // Action Items + switch (item.getItemId()) { + case R.id.mnu_actions_sort_by_date_asc: + FileManagerSettings.setSorting(NavigationSortMode.DATE_ASC); + getCurrentNavigationFragment().refresh(); + break; + + case R.id.mnu_actions_sort_by_date_desc: + FileManagerSettings.setSorting(NavigationSortMode.DATE_DESC); + getCurrentNavigationFragment().refresh(); + break; + + case R.id.mnu_actions_sort_by_name_asc: + FileManagerSettings.setSorting(NavigationSortMode.NAME_ASC); + getCurrentNavigationFragment().refresh(); + break; + + case R.id.mnu_actions_sort_by_name_desc: + FileManagerSettings.setSorting(NavigationSortMode.NAME_DESC); + getCurrentNavigationFragment().refresh(); + break; + + case R.id.mnu_actions_show_dirs_first: + FileManagerSettings.toggleViewPreference(FileManagerSettings.SETTINGS_SHOW_DIRS_FIRST); + getCurrentNavigationFragment().refresh(); + break; + + case R.id.mnu_actions_show_hidden: + FileManagerSettings.toggleViewPreference(FileManagerSettings.SETTINGS_SHOW_HIDDEN); + getCurrentNavigationFragment().refresh(); + break; + + case R.id.mnu_actions_show_system: + FileManagerSettings.toggleViewPreference(FileManagerSettings.SETTINGS_SHOW_SYSTEM); + getCurrentNavigationFragment().refresh(); + break; + + case R.id.mnu_actions_show_symlinks: + FileManagerSettings.toggleViewPreference(FileManagerSettings.SETTINGS_SHOW_SYMLINKS); + getCurrentNavigationFragment().refresh(); + break; + + case R.id.mnu_actions_layout_simple: + FileManagerSettings.setLayout(NavigationLayoutMode.SIMPLE); + getCurrentNavigationFragment().refresh(); + break; + + case R.id.mnu_actions_layout_details: + FileManagerSettings.setLayout(NavigationLayoutMode.DETAILS); + getCurrentNavigationFragment().refresh(); + break; + + case R.id.mnu_actions_layout_icons: + FileManagerSettings.setLayout(NavigationLayoutMode.ICONS); + getCurrentNavigationFragment().refresh(); + break; + + // Show information of the filesystem + case R.id.mnu_actions_show_filesystem_info: + MountPoint mp = getCurrentNavigationFragment().getBreadcrumb().getMountPointInfo(); + DiskUsage du = getCurrentNavigationFragment().getBreadcrumb().getDiskUsageInfo(); + showMountPointInfo(mp, du); + break; + + case R.id.mnu_actions_history: + openHistory(); + break; + + case R.id.mnu_actions_search: + openSearch(); + break; + + // Refresh the current navigation fragment + case R.id.mnu_actions_refresh: + getCurrentNavigationFragment().refresh(); + break; + + // Create new object + case R.id.mnu_actions_new_file: + showFileTypeDialog(new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (which >= 0) + showInputNameDialog(which); + else dialog.dismiss(); + } + }); + break; + + // Paste selection + case R.id.mnu_actions_paste_selection: + if (true) { + CopyMoveActionPolicy.triggerCopyMoveFileSystemObjects( + NavigationActivity.this, + this.onRequestFilesMarkedForPaste(), + this.onRequestPasteOperationType(), + getCurrentNavigationFragment(), + getCurrentNavigationFragment(), + NavigationActivity.this); + /* Clear files as soon as the user decides to paste, thus permitting more copy + selections while this paste task finishes. */ + onClearFilesMarkedForPaste(); + } + break; + + // Open a properties drawer on the current directory + case R.id.mnu_actions_properties_current_folder: + EventBus.getDefault().post(new OpenPropertiesDrawerEvent( + getCurrentNavigationFragment().getCurrentDir())); + break; + + // Add current directory to bookmarks + case R.id.mnu_actions_add_to_bookmarks: + try { + FileSystemObject bookmarkFso = CommandHelper.getFileInfo(this, + getCurrentNavigationFragment().getCurrentDir(), + null); + BookmarksActionPolicy.addToBookmarks(this, bookmarkFso); + EventBus.getDefault().post(new BookmarkRefreshEvent()); + } catch (Exception e) { + ExceptionUtil.translateException(this, e, true, false); + } + break; + // Open settings + case R.id.mnu_settings: + Intent settings = new Intent( + NavigationActivity.this, SettingsPreferences.class); + startActivity(settings); + break; + + default: + return super.onOptionsItemSelected(item); + } + return super.onOptionsItemSelected(item); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (data != null) { + switch (requestCode) { + case INTENT_REQUEST_HISTORY: + if (resultCode == RESULT_OK) { + //Change current directory + History history = + (History)data.getSerializableExtra(EXTRA_HISTORY_ENTRY_SELECTION); + navigateToHistory(history); + } else if (resultCode == RESULT_CANCELED) { + boolean clear = data.getBooleanExtra(EXTRA_HISTORY_CLEAR, false); + if (clear) { + clearHistory(); + } + } + break; + + case INTENT_REQUEST_SEARCH: + if (resultCode == RESULT_OK) { + //Change directory? + FileSystemObject fso = + (FileSystemObject)data. + getSerializableExtra(EXTRA_SEARCH_ENTRY_SELECTION); + SearchInfoParcelable searchInfo = + data.getParcelableExtra(EXTRA_SEARCH_LAST_SEARCH_DATA); + if (fso != null) { + //Goto to new directory + getCurrentNavigationFragment().open(fso, searchInfo); + } + } else if (resultCode == RESULT_CANCELED) { + SearchInfoParcelable searchInfo = + data.getParcelableExtra(EXTRA_SEARCH_LAST_SEARCH_DATA); + if (searchInfo != null && searchInfo.isSuccessNavigation()) { + //Navigate to previous history + back(); + } else { + // I don't know is the search view was changed, so try to do a refresh + // of the navigation view + getCurrentNavigationFragment().refresh(true); + } + } + break; + + default: + break; + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onRequestRefresh(Object o, boolean clearSelection) { + if (o instanceof FileSystemObject) { + // Refresh only the item + this.getCurrentNavigationFragment().refresh((FileSystemObject) o); + } else if (o == null) { + // Refresh all + getCurrentNavigationFragment().refresh(); + } + if (clearSelection) { + this.getCurrentNavigationFragment().onDeselectAll(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onRequestRemove(Object o, boolean clearSelection) { + if (o instanceof FileSystemObject) { + // Remove from view + this.getCurrentNavigationFragment().removeItem((FileSystemObject) o); + + //Remove from history + getCurrentNavigationFragment().removeFromHistory((FileSystemObject) o); + } else { + onRequestRefresh(null, clearSelection); + } + if (clearSelection) { + this.getCurrentNavigationFragment().onDeselectAll(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onBreadcrumbItemClick(File item) { + getCurrentNavigationFragment().changeCurrentDir(item.getAbsolutePath()); + } + + /** + * {@inheritDoc} + */ + @Override + public void onNavigateTo(Object o) { + // Ignored + } + + /** + * {@inheritDoc} + */ + @Override + public void onRequestMenu(NavigationFragment navFragment, FileSystemObject item) { + + } + + /** + * Show/hide the "selection" action mode, according to the number of + * selected messages and the visibility of the fragment. Also update the + * content (title and menus) if necessary. + */ + public void startPropertiesActionMode(FileSystemObject fso) { + mPropertiesModeCallback = new PropertiesModeCallback(this, fso); + mPropertiesModeCallback.setOnRequestRefreshListener(this); + mPropertiesModeCallback.setOnCopyMoveListener(this); + startActionMode(mPropertiesModeCallback); + } + + /** + * Finish the "properties" action mode. + * + */ + private void finishPropertiesActionMode() { + // Close the action mode + if (isInPropertiesActionMode()) { + mPropertiesModeCallback.setClosedByUser(false); + mPropertiesModeCallback.finish(); + } + // Close and lock the info drawer + if (mDrawerLayout.isDrawerOpen(mInfoDrawer)) { + mDrawerLayout.closeDrawer(mInfoDrawer); + } + // The properties drawer has been closed, so lock it shut and unlock the bookmarks bar. + mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, mInfoDrawer); + mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, mBookmarkDrawer); + } + + /** + * @return true if the list is in the "selection" mode. + */ + private boolean isInPropertiesActionMode() { + if (mPropertiesModeCallback == null) { + return false; + } else if (mPropertiesModeCallback.inPropertiesActionMode()) { + return true; + } else { + return false; + } + } + + /** + * Method that show the information of a filesystem mount point. + * + * @param mp The mount point info + * @param du The disk usage of the mount point + */ + private void showMountPointInfo(MountPoint mp, DiskUsage du) { + //Has mount point info? + if (mp == null) { + //There is no information + AlertDialog alert = + DialogHelper.createWarningDialog( + this, + R.string.filesystem_info_warning_title, + R.string.filesystem_info_warning_msg); + DialogHelper.delegateDialogShow(this, alert); + return; + } + + //Show a the filesystem info dialog + FilesystemInfoDialog dialog = new FilesystemInfoDialog(this, mp, du); + dialog.setOnMountListener(new OnMountListener() { + @Override + public void onRemount(MountPoint mountPoint) { + //Update the statistics of breadcrumb, only if mount point is the same + Breadcrumb breadcrumb = getCurrentNavigationFragment().getBreadcrumb(); + if (breadcrumb.getMountPointInfo().compareTo(mountPoint) == 0) { + breadcrumb.updateMountPointInfo(); + } + } + }); + dialog.show(); + } + + /** + * Method that checks the action that must be realized when the + * back button is pushed. + * + * @return boolean Indicates if the action must be intercepted + */ + private boolean checkBackAction() { + // We need a basic structure to check this + if (getCurrentNavigationFragment() == null) return false; + + //Do back operation over the navigation history + boolean flag = this.mExitFlag; + + this.mExitFlag = !back(); + + // Retrieve if the exit status timeout has expired + long now = System.currentTimeMillis(); + boolean timeout = (this.mExitBackTimeout == -1 || + (now - this.mExitBackTimeout) > RELEASE_EXIT_CHECK_TIMEOUT); + + //Check if there no history and if the user was advised in the last back action + if (this.mExitFlag && (this.mExitFlag != flag || timeout)) { + //Communicate the user that the next time the application will be closed + this.mExitBackTimeout = System.currentTimeMillis(); + DialogHelper.showToast(this, R.string.msgs_push_again_to_exit, Toast.LENGTH_SHORT); + return true; + } + + //Back action not applied + return !this.mExitFlag; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onSearchRequested() { + Bundle bundle = new Bundle(); + bundle.putString( + SearchActivity.EXTRA_SEARCH_DIRECTORY, + getCurrentNavigationFragment().getCurrentDir()); + startSearch(Preferences.getLastSearch(), true, bundle, false); + return true; + } + + /** + * Method that clears the fragment history. + */ + private void clearHistory() { + getCurrentNavigationFragment().mHistory.clear(); + getCurrentNavigationFragment().onCheckHistory(); + } + + /** + * Method that navigates to the passed history reference. + * + * @param history The history reference + * @return boolean A problem occurs while navigate + */ + public synchronized boolean navigateToHistory(History history) { + try { + NavigationFragment currentNavFragment = getCurrentNavigationFragment(); + //Gets the history + History realHistory = currentNavFragment.mHistory.get(history.getPosition()); + + //Navigate to item. Check what kind of history is + if (realHistory.getItem() instanceof NavigationViewInfoParcelable) { + //Navigation + NavigationViewInfoParcelable info = + (NavigationViewInfoParcelable)realHistory.getItem(); + // Selected items must not be restored from on history navigation + info.setSelectedFiles(currentNavFragment.getSelectedFiles()); + if (!currentNavFragment.onRestoreState(info)) { + return true; + } + + } else if (realHistory.getItem() instanceof SearchInfoParcelable) { + //Search (open search with the search results) + SearchInfoParcelable info = (SearchInfoParcelable)realHistory.getItem(); + Intent searchIntent = new Intent(this, SearchActivity.class); + searchIntent.setAction(SearchActivity.ACTION_RESTORE); + searchIntent.putExtra(SearchActivity.EXTRA_SEARCH_RESTORE, (Parcelable)info); + startActivityForResult(searchIntent, INTENT_REQUEST_SEARCH); + } else { + //The type is unknown + throw new IllegalArgumentException("Unknown history type"); //$NON-NLS-1$ + } + + //Remove the old history + int cc = realHistory.getPosition(); + for (int i = currentNavFragment.mHistory.size() - 1; i >= cc; i--) { + currentNavFragment.mHistory.remove(i); + } + + //Navigate + return true; + + } catch (Throwable ex) { + if (history != null) { + Log.e(TAG, + String.format("Failed to navigate to history %d: %s", //$NON-NLS-1$ + Integer.valueOf(history.getPosition()), + history.getItem().getTitle()), ex); + } else { + Log.e(TAG, + String.format("Failed to navigate to history: null", ex)); //$NON-NLS-1$ + } + this.mHandler.post(new Runnable() { + @Override + public void run() { + DialogHelper.showToast( + NavigationActivity.this, + R.string.msgs_history_unknown, Toast.LENGTH_LONG); + } + }); + + //Not change directory + return false; + } + } + + /** + * Method that request a back action over the navigation history. + * + * @return boolean If a back action was applied + */ + public boolean back() { + NavigationFragment currentNavFragment = getCurrentNavigationFragment(); + // Check that has valid history + while (currentNavFragment.mHistory.size() > 0) { + History h = currentNavFragment.mHistory.get(currentNavFragment.mHistory.size() - 1); + if (h.getItem() instanceof NavigationViewInfoParcelable) { + // Verify that the path exists + String path = ((NavigationViewInfoParcelable)h.getItem()).getCurrentDir(); + + try { + FileSystemObject info = CommandHelper.getFileInfo(this, path, null); + if (info != null) { + break; + } + currentNavFragment.mHistory.remove(currentNavFragment.mHistory.size() - 1); + } catch (Exception e) { + ExceptionUtil.translateException(this, e, true, false); + currentNavFragment.mHistory.remove(currentNavFragment.mHistory.size() - 1); + } + } else { + break; + } + } + + //Extract a history from the + if (currentNavFragment.mHistory.size() > 0) { + //Navigate to history + return navigateToHistory(currentNavFragment.mHistory.get(currentNavFragment.mHistory.size() - 1)); + } + + //Nothing to apply + return false; + } + + /** + * Method that show a new dialog for input a name. + * + * @param fileTypeIndex The file_type array index associated + */ + private void showInputNameDialog(final int fileTypeIndex) { + String[] fileTypes = getResources().getStringArray(R.array.file_types); + String title = fileTypes[0]; + if(fileTypeIndex < fileTypes.length) + title = fileTypes[fileTypeIndex]; + + //Show the input name dialog + final InputNameDialog inputNameDialog = + new InputNameDialog( + this, + getCurrentNavigationFragment().onRequestCurrentItems(), + title); + inputNameDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + //Retrieve the name an execute the action + try { + String name = inputNameDialog.getName(); + createNewFileSystemObject(fileTypeIndex, name); + } catch (InflateException e) { + Log.e(TAG, "Could not create the input name dialog"); + } + } + }); + inputNameDialog.show(); + } + + private static int getFileTypeIcon(Context context, int position) + { + TypedArray ar = context.getResources().obtainTypedArray(R.array.file_type_icons); + if(position < 0 || position > ar.length()) return 0; + return ar.getResourceId(position, 0); + } + + private void showFileTypeDialog(final DialogInterface.OnClickListener onclick) + { + ArrayAdapter adapter = new ArrayAdapter(this, + android.R.layout.simple_list_item_1, + getResources().getStringArray(R.array.file_types)){ + public View getView(int position, View convertView, ViewGroup parent) { + View ret = super.getView(position, convertView, parent); + if(ret instanceof TextView) + { + int res = getFileTypeIcon(getContext(), position); + if(res > 0) + { + Drawable d = getResources().getDrawable(res); + ((TextView)ret).setCompoundDrawablePadding(8); + ((TextView)ret).setCompoundDrawablesRelativeWithIntrinsicBounds( + d, null, null, null); + } + return ret; + } + return ret; + } + }; + new AlertDialog.Builder(this) + .setTitle(R.string.pick_file_type) + .setAdapter(adapter, onclick) + .setNegativeButton(R.string.cancel, onclick) + .create() + .show(); + } + + /** + * Method that create the a new file system object. + * + * @param fileTypeIndex The file_type array index + * @param name The name of the file system object + * @hide + */ + void createNewFileSystemObject(final int fileTypeIndex, final String name) { + switch (fileTypeIndex) { + case 0: + NewActionPolicy.createNewDirectory(this, name, getCurrentNavigationFragment(), this); + break; + case 1: + NewActionPolicy.createNewFile(this, name, getCurrentNavigationFragment(), this); + break; + default: + break; + } + } + + /** + * This method takes a file, opens a right-hand drawerLayout, populates the drawer with the + * file's information, and starts an associated {@link me.toolify.backbone.actionmode.PropertiesModeCallback} + * + * @param event an event referencing the file to provide information for. + */ + public void onEvent(OpenPropertiesDrawerEvent event) { + + + // Resolve the full path + String path = String.valueOf(event.item); + if (event.item instanceof FileSystemObject) { + path = ((FileSystemObject)event.item).getFullPath(); + } + + // Prior to show the dialog, refresh the item reference + FileSystemObject fso = null; + try { + fso = CommandHelper.getFileInfo(this, path, false, null); + if (fso == null) { + throw new NoSuchFileOrDirectory(path); + } + + } catch (Exception e) { + // Notify the user + ExceptionUtil.translateException(this, e); + + // Remove the object + if (e instanceof FileNotFoundException || e instanceof NoSuchFileOrDirectory) { + // If have a FileSystemObject reference then there is no need to search + // the path (less resources used) + if (event.item instanceof FileSystemObject) { + getCurrentNavigationFragment().removeItem((FileSystemObject) event.item); + } else { + getCurrentNavigationFragment().removeItem((String) event.item); + } + } + return; + } + + if (mDrawerLayout.isDrawerOpen(mInfoDrawer)) { + mDrawerLayout.closeDrawer(mInfoDrawer); + mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, mInfoDrawer); + finishPropertiesActionMode(); + } else { + startPropertiesActionMode(fso); + mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, mInfoDrawer); + mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, mBookmarkDrawer); + mDrawerLayout.openDrawer(mInfoDrawer); + mInfoDrawer.loadFso(fso); + } + } + + /** + * This method closes an open properties drawer and calls finish() on the associated + * {@link me.toolify.backbone.actionmode.PropertiesModeCallback} action mode. + * + * @param event a trigger event that contains no additional information. + */ + public void onEvent(ClosePropertiesDrawerEvent event) { + finishPropertiesActionMode(); + } + + /** + * Method that opens the history activity. + * @hide + */ + void openHistory() { + Intent historyIntent = new Intent(this, HistoryActivity.class); + historyIntent.putExtra(HistoryFragment.EXTRA_HISTORY_LIST, (Serializable)this.getCurrentNavigationFragment().mHistory); + historyIntent.addFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME); + startActivityForResult(historyIntent, INTENT_REQUEST_HISTORY); + } + + /** + * Method that opens the search activity. + * @hide + */ + void openSearch() { + onSearchRequested(); + } + + /** + * Method that ask the user to change the access mode prior to crash. + * @hide + */ + public void askOrExit() { + //Show a dialog asking the user + AlertDialog dialog = + DialogHelper.createYesNoDialog( + this, + R.string.msgs_change_to_prompt_access_mode_title, + R.string.msgs_change_to_prompt_access_mode_msg, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface alertDialog, int which) { + if (which == DialogInterface.BUTTON_NEGATIVE) { + // We don't have any console + // Show exception and exit + DialogHelper.showToast( + NavigationActivity.this, + R.string.msgs_cant_create_console, Toast.LENGTH_LONG); + exit(); + return; + } + + // Ok. Now try to change to prompt mode. Any crash + // here is a fatal error. We won't have any console to operate. + try { + // Change console + ConsoleBuilder.changeToNonPrivilegedConsole(NavigationActivity.this); + + // Save preferences + Preferences.savePreference( + FileManagerSettings.SETTINGS_ACCESS_MODE, + AccessMode.PROMPT, true); + + } catch (Exception e) { + // Displays an exception and exit + Log.e(TAG, getString(R.string.msgs_cant_create_console), e); + DialogHelper.showToast( + NavigationActivity.this, + R.string.msgs_cant_create_console, Toast.LENGTH_LONG); + exit(); + } + } + }); + DialogHelper.delegateDialogShow(this, dialog); + } + + /** + * Method that creates a ChRooted environment, protecting the user to break anything in + * the device + * @hide + */ + void createChRooted() { + // If we are in a ChRooted mode, then do nothing + if (this.mChRooted) return; + this.mChRooted = true; + + //Change to first storage volume + Object[] volumes = + StorageHelper.getStorageVolumes(this); + if (volumes != null && volumes.length > 0) { + for (int x = 0; x < mPagerAdapter.getCount();x++) { + mPagerAdapter.getFragment(mViewPager, x).enterChRooted( + StorageHelper.getStoragePath(volumes[0])); + } + } + + // Remove the history (don't allow to access to previous data) + clearHistory(); + } + + /** + * Method that exits from a ChRooted + * @hide + */ + void exitChRooted() { + // If we aren't in a ChRooted mode, then do nothing + if (!this.mChRooted) return; + this.mChRooted = false; + + for (int x = 0; x < mPagerAdapter.getCount();x++) { + NavigationFragment navigationFragment = (NavigationFragment) mPagerAdapter.getItem(x); + navigationFragment.exitChRooted(); + + } + } + + /** + * Method that returns whether the activity is ChRooted + */ + public boolean isChRooted(){ + return this.mChRooted; + } + + /** + * {@inheritDoc} + */ + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + //To change body of implemented methods use File | Settings | File Templates. + } + + /** + * A part of the PageChangeListener interface. Since there are numerous UI + * elements that are contextually based on the currently selected page, we + * need to trigger UI updates after the user swipes to a new page. The pager + * is contained within the main activity, but the relevant data must be + * pushed from the currently selected list fragment. + * + * @param position the integer index of the currently selected page + * @return nothing + */ + @Override + public void onPageSelected(int position) { + + // Tell the breadcrumb that the new fragment will now be the one sending dir changes + NavigationFragment navigationFragment = getCurrentNavigationFragment(); + navigationFragment.setOnHistoryListener(navigationFragment); + navigationFragment.setOnNavigationOnRequestMenuListener(this); + } + + /** + * {@inheritDoc} + */ + @Override + public void onPageScrollStateChanged(int state) { + //To change body of implemented methods use File | Settings | File Templates. + } + + /** + * {@inheritDoc} + */ + @Override + public void onMarkFilesForPaste(List filesForPaste, COPY_MOVE_OPERATION pasteOperationType) { + this.mFilesForPaste = filesForPaste; + this.mPasteOperationType = pasteOperationType; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onAreFilesMarkedForPaste() { + return this.mFilesForPaste != null; + } + + /** + * {@inheritDoc} + */ + @Override + public void onClearFilesMarkedForPaste() { + this.mFilesForPaste = null; + this.mPasteOperationType = null; + // Make sure that the paste action item is removed + invalidateOptionsMenu(); + } + + /** + * {@inheritDoc} + */ + @Override + public List onRequestFilesMarkedForPaste() { + return this.mFilesForPaste; + } + + /** + * {@inheritDoc} + */ + @Override + public COPY_MOVE_OPERATION onRequestPasteOperationType() { + return this.mPasteOperationType; + } + + /** + * {@inheritDoc} + */ + @Override + public String onRequestDestinationDir() { + return getCurrentNavigationFragment().getCurrentDir(); + } + + /** + * Method that applies the current theme to the activity + * @hide + */ + void applyTheme() { + Theme theme = ThemeManager.getCurrentTheme(this); + theme.setBaseTheme(this, false); + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/activities/PickerActivity.java b/Backbone/src/main/java/me/toolify/backbone/activities/PickerActivity.java new file mode 100644 index 000000000..b84de5577 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/activities/PickerActivity.java @@ -0,0 +1,682 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.activities; + +import android.app.AlertDialog; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnCancelListener; +import android.content.DialogInterface.OnDismissListener; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Configuration; +import android.content.res.TypedArray; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.support.v4.app.FragmentActivity; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup.LayoutParams; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.FrameLayout; +import android.widget.ListPopupWindow; +import android.widget.ProgressBar; +import android.widget.Toast; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import de.greenrobot.event.EventBus; +import me.toolify.backbone.R; +import me.toolify.backbone.adapters.CheckableListAdapter; +import me.toolify.backbone.adapters.CheckableListAdapter.CheckableItem; +import me.toolify.backbone.bus.events.FilesystemStatusUpdateEvent; +import me.toolify.backbone.console.ConsoleBuilder; +import me.toolify.backbone.fragments.NavigationFragment; +import me.toolify.backbone.fragments.NavigationFragment.OnDirectoryChangedListener; +import me.toolify.backbone.fragments.NavigationFragment.OnFilePickedListener; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.preferences.DisplayRestrictions; +import me.toolify.backbone.preferences.FileManagerSettings; +import me.toolify.backbone.preferences.Preferences; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.ui.ThemeManager.Theme; +import me.toolify.backbone.ui.widgets.Breadcrumb; +import me.toolify.backbone.ui.widgets.BreadcrumbItem; +import me.toolify.backbone.ui.widgets.ButtonItem; +import me.toolify.backbone.util.DialogHelper; +import me.toolify.backbone.util.ExceptionUtil; +import me.toolify.backbone.util.FileHelper; +import me.toolify.backbone.util.MimeTypeHelper; +import me.toolify.backbone.util.StorageHelper; + +/** + * The activity for allow to use a {@link FragmentActivity} like, to pick a file from other + * application. + */ +public class PickerActivity extends AbstractNavigationActivity + implements OnCancelListener, OnDismissListener, + OnFilePickedListener, OnDirectoryChangedListener { + + private static final String TAG = "PickerActivity"; //$NON-NLS-1$ + + private static boolean DEBUG = false; + + private final BroadcastReceiver mNotificationReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent != null) { + if (intent.getAction().compareTo(FileManagerSettings.INTENT_THEME_CHANGED) == 0) { + applyTheme(); + } + } + } + }; + + // The result code + private static final int RESULT_CROP_IMAGE = 1; + + // The component that holds the crop operation. We use Gallery3d because we are confidence + // of his input parameters + private static final ComponentName CROP_COMPONENT = + new ComponentName( + "com.android.gallery3d", //$NON-NLS-1$ + "com.android.gallery3d.app.CropImage"); //$NON-NLS-1$ + + // Gallery crop editor action + private static final String ACTION_CROP = "com.android.camera.action.CROP"; //$NON-NLS-1$ + + // Extra data for Gallery CROP action + private static final String EXTRA_CROP = "crop"; //$NON-NLS-1$ + + // Scheme for file and directory picking + private static final String FILE_URI_SCHEME = "file"; //$NON-NLS-1$ + private static final String FOLDER_URI_SCHEME = "folder"; //$NON-NLS-1$ + private static final String DIRECTORY_URI_SCHEME = "directory"; //$NON-NLS-1$ + + FileSystemObject mFso; // The picked item + FileSystemObject mCurrentDirectory; + private AlertDialog mDialog; + /** + * @hide + */ + NavigationFragment mNavigationFragment; + Breadcrumb mBreadcrumb; + private View mRootView; + private ButtonItem mFilesystemInfo; + private ProgressBar mFilesystemInfoRefreshing; + + /** + * {@inheritDoc} + */ + @Override + protected void onCreate(Bundle state) { + //Save state + super.onCreate(state); + + if (DEBUG) { + Log.d(TAG, "PickerActivity.onCreate"); //$NON-NLS-1$ + } + + // Register the broadcast receiver + IntentFilter filter = new IntentFilter(); + filter.addAction(FileManagerSettings.INTENT_THEME_CHANGED); + registerReceiver(this.mNotificationReceiver, filter); + + // Initialize the activity + init(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onDestroy() { + if (DEBUG) { + Log.d(TAG, "PickerActivity.onDestroy"); //$NON-NLS-1$ + } + + // Unregister the receiver + try { + unregisterReceiver(this.mNotificationReceiver); + } catch (Throwable ex) { + /**NON BLOCK**/ + } + + //All destroy. Continue + super.onDestroy(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onPause() { + super.onPause(); + // Always unregister when an object no longer should be on the bus. + EventBus.getDefault().unregister(this); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onResume() { + super.onResume(); + EventBus.getDefault().register(this); + } + + /** + * {@inheritDoc} + */ + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + measureHeight(); + } + + /** + * Method that displays a dialog with a {@link NavigationFragment} to select the + * proposed file + */ + private void init() { + final boolean pickingDirectory; + final Intent intent = getIntent(); + + if (isFilePickIntent(intent)) { + // ok + Log.d(TAG, "PickerActivity: got file pick intent: " + String.valueOf(intent)); //$NON-NLS-1$ + pickingDirectory = false; + } else if (isDirectoryPickIntent(getIntent())) { + // ok + Log.d(TAG, "PickerActivity: got folder pick intent: " + String.valueOf(intent)); //$NON-NLS-1$ + pickingDirectory = true; + } else { + Log.d(TAG, "PickerActivity got unrecognized intent: " + String.valueOf(intent)); //$NON-NLS-1$ + setResult(FragmentActivity.RESULT_CANCELED); + finish(); + return; + } + + // Display restrictions + Map restrictions = new HashMap(); + //- Mime/Type restriction + String mimeType = getIntent().getType(); + if (mimeType != null) { + if (!MimeTypeHelper.isMimeTypeKnown(this, mimeType)) { + Log.i(TAG, + String.format( + "Mime type %s unknown, falling back to wildcard.", //$NON-NLS-1$ + mimeType)); + mimeType = MimeTypeHelper.ALL_MIME_TYPES; + } + restrictions.put(DisplayRestrictions.MIME_TYPE_RESTRICTION, mimeType); + } + // Other restrictions + Bundle extras = getIntent().getExtras(); + Log.d(TAG, "PickerActivity. extras: " + String.valueOf(extras)); //$NON-NLS-1$ + if (extras != null) { + //-- File size + if (extras.containsKey(android.provider.MediaStore.Audio.Media.EXTRA_MAX_BYTES)) { + long size = + extras.getLong(android.provider.MediaStore.Audio.Media.EXTRA_MAX_BYTES); + restrictions.put(DisplayRestrictions.SIZE_RESTRICTION, Long.valueOf(size)); + } + //-- Local filesystems only + if (extras.containsKey(Intent.EXTRA_LOCAL_ONLY)) { + boolean localOnly = extras.getBoolean(Intent.EXTRA_LOCAL_ONLY); + restrictions.put( + DisplayRestrictions.LOCAL_FILESYSTEM_ONLY_RESTRICTION, + Boolean.valueOf(localOnly)); + } + } + if (pickingDirectory) { + restrictions.put(DisplayRestrictions.DIRECTORY_ONLY_RESTRICTION, Boolean.TRUE); + } + + // Create or use the console + if (!initializeConsole()) { + // Something when wrong. Display a message and exit + DialogHelper.showToast(this, R.string.msgs_cant_create_console, Toast.LENGTH_SHORT); + cancel(); + return; + } + + // Create the root file + this.mRootView = getLayoutInflater().inflate(R.layout.picker, null, false); + this.mRootView.post(new Runnable() { + @Override + public void run() { + measureHeight(); + } + }); + + // Initialize the (pseudo) action bar + updateTitleActionBar(); + + // Navigation view + this.mNavigationFragment = + (NavigationFragment)getFragmentManager().findFragmentById (R.id.navigation_fragment); + this.mNavigationFragment.setRestrictions(restrictions); + this.mNavigationFragment.setOnFilePickedListener(this); + this.mNavigationFragment.setOnDirectoryChangedListener(this); + + // Apply the current theme + applyTheme(); + + // Create the dialog + this.mDialog = DialogHelper.createDialog( + this, R.drawable.ic_launcher, + pickingDirectory ? R.string.directory_picker_title : R.string.picker_title, + this.mRootView); + + this.mDialog.setButton( + DialogInterface.BUTTON_NEGATIVE, + getString(R.string.cancel), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dlg, int which) { + dlg.cancel(); + } + }); + if (pickingDirectory) { + this.mDialog.setButton( + DialogInterface.BUTTON_POSITIVE, + getString(R.string.select), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dlg, int which) { + PickerActivity.this.mFso = PickerActivity.this.mCurrentDirectory; + dlg.dismiss(); + } + }); + } + this.mDialog.setCancelable(true); + this.mDialog.setOnCancelListener(this); + this.mDialog.setOnDismissListener(this); + DialogHelper.delegateDialogShow(this, this.mDialog); + + // Set content description of storage volume button + mFilesystemInfo = (ButtonItem)this.mRootView.findViewById(R.id.button_filesystem_info); + mFilesystemInfo.setContentDescription(getString(R.string.actionbar_button_storage_cd)); + mFilesystemInfoRefreshing = (ProgressBar)this.mRootView.findViewById( + R.id.button_filesystem_info_refreshing); + + } + + /** + * Method that measure the height needed to avoid resizing when + * change to a new directory. This method fixed the height of the window + * @hide + */ + void measureHeight() { + // Calculate the dialog size based on the window height + DisplayMetrics displaymetrics = new DisplayMetrics(); + getWindowManager().getDefaultDisplay().getMetrics(displaymetrics); + final int height = displaymetrics.heightPixels; + + Configuration config = getResources().getConfiguration(); + int percent = config.orientation == Configuration.ORIENTATION_LANDSCAPE ? 55 : 70; + + FrameLayout.LayoutParams params = + new FrameLayout.LayoutParams( + LayoutParams.WRAP_CONTENT, (height * percent) / 100); + this.mRootView.setLayoutParams(params); + } + + /** + * Method that initializes a console + */ + private boolean initializeConsole() { + try { + // Create a ChRooted console + ConsoleBuilder.createDefaultConsole(this, false, false); + // There is a console allocated. Use it. + return true; + } catch (Throwable _throw) { + // Capture the exception + ExceptionUtil.translateException(this, _throw, true, false); + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case RESULT_CROP_IMAGE: + // Return what the callee activity returns + setResult(resultCode, data); + finish(); + return; + + default: + break; + } + + // The response is not understood + Log.w(TAG, + String.format( + "Ignore response. requestCode: %s, resultCode: %s, data: %s", //$NON-NLS-1$ + Integer.valueOf(requestCode), + Integer.valueOf(resultCode), + data)); + DialogHelper.showToast(this, R.string.msgs_operation_failure, Toast.LENGTH_SHORT); + } + + /** + * {@inheritDoc} + */ + @Override + public void onDismiss(DialogInterface dialog) { + if (this.mFso != null) { + File src = new File(this.mFso.getFullPath()); + if (getIntent().getExtras() != null) { + // Some AOSP applications use the gallery to edit and crop the selected image + // with the Gallery crop editor. In this case pass the picked file to the + // CropActivity with the requested parameters + // Expected result is on onActivityResult + Bundle extras = getIntent().getExtras(); + String crop = extras.getString(EXTRA_CROP); + if (Boolean.parseBoolean(crop)) { + // We want to use the Gallery3d activity because we know about it, and his + // parameters. At least we have a compatible one. + Intent intent = new Intent(ACTION_CROP); + if (getIntent().getType() != null) { + intent.setType(getIntent().getType()); + } + intent.setData(Uri.fromFile(src)); + intent.putExtras(extras); + intent.setComponent(CROP_COMPONENT); + startActivityForResult(intent, RESULT_CROP_IMAGE); + return; + } + } + + // Return the picked file, as expected (this activity should fill the intent data + // and return RESULT_OK result) + Intent result = new Intent(); + result.setData(getResultUriForFileFromIntent(src, getIntent())); + setResult(FragmentActivity.RESULT_OK, result); + finish(); + + } else { + cancel(); + } + } + + private static boolean isFilePickIntent(Intent intent) { + final String action = intent.getAction(); + + if (Intent.ACTION_GET_CONTENT.equals(action)) { + return true; + } + if (Intent.ACTION_PICK.equals(action)) { + final Uri data = intent.getData(); + if (data != null && FILE_URI_SCHEME.equals(data.getScheme())) { + return true; + } + } + + return false; + } + + private static boolean isDirectoryPickIntent(Intent intent) { + if (Intent.ACTION_PICK.equals(intent.getAction()) && intent.getData() != null) { + String scheme = intent.getData().getScheme(); + if (FOLDER_URI_SCHEME.equals(scheme) || DIRECTORY_URI_SCHEME.equals(scheme)) { + return true; + } + } + + return false; + } + + private static Uri getResultUriForFileFromIntent(File src, Intent intent) { + Uri result = Uri.fromFile(src); + + if (Intent.ACTION_PICK.equals(intent.getAction()) && intent.getData() != null) { + String scheme = intent.getData().getScheme(); + if (scheme != null) { + result = result.buildUpon().scheme(scheme).build(); + } + } + + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public void onCancel(DialogInterface dialog) { + cancel(); + } + + /** + * {@inheritDoc} + */ + @Override + public void onFilePicked(FileSystemObject item) { + this.mFso = item; + this.mDialog.dismiss(); + } + + /** + * {@inheritDoc} + */ + @Override + public void onDirectoryChanged(FileSystemObject item) { + this.mCurrentDirectory = item; + } + + /** + * This method is called by various pieces of code responsible for updating file listing or + * breadcrumb data. The method receives all {@link me.toolify.backbone.bus.events.FilesystemStatusUpdateEvent} + * events generated by the various asyncTasks responsible for gathering and sending file info. + * + * @param event an event containing the filesystem status code + */ + public void onEvent(FilesystemStatusUpdateEvent event) { + setFilesystemStatusDrawable(event.status); + } + + private void setFilesystemStatusDrawable(int fileSystemstatus){ + + TypedArray a = getTheme().obtainStyledAttributes(R.styleable.FileManager); + + switch (fileSystemstatus) { + + case FilesystemStatusUpdateEvent.INDICATOR_UNLOCKED: + if (mFilesystemInfo != null) { + mFilesystemInfo.setImageResource( + a.getResourceId(R.styleable.FileManager_actionIconLockOpen, + R.drawable.ic_action_holo_dark_lock_open)); + setFilesystemInfoProgressState(false); + } + break; + + case FilesystemStatusUpdateEvent.INDICATOR_LOCKED: + if (mFilesystemInfo != null) { + mFilesystemInfo.setImageResource( + a.getResourceId(R.styleable.FileManager_actionIconLockClosed, + R.drawable.ic_action_holo_dark_lock_closed)); + setFilesystemInfoProgressState(false); + } + break; + + case FilesystemStatusUpdateEvent.INDICATOR_WARNING: + if (mFilesystemInfo != null) { + mFilesystemInfo.setImageResource( + a.getResourceId(R.styleable.FileManager_actionIconWarning, + R.drawable.ic_action_holo_dark_warning)); + setFilesystemInfoProgressState(false); + } + break; + + case FilesystemStatusUpdateEvent.INDICATOR_REFRESHING: + if (mFilesystemInfo != null) { + setFilesystemInfoProgressState(true); + } + break; + + case FilesystemStatusUpdateEvent.INDICATOR_STOP_REFRESHING: + if (mFilesystemInfo != null) { + setFilesystemInfoProgressState(false); + } + break; + } + + a.recycle(); + } + + /** + * This function switches an action item between its normal icon and an indeterminate progress + * circle + * @param refreshing value is true if the action item should show the progress bar + */ + public void setFilesystemInfoProgressState(final boolean refreshing) { + if (mFilesystemInfo != null) { + + if (refreshing) { + mFilesystemInfo.setVisibility(View.INVISIBLE); + mFilesystemInfoRefreshing.setVisibility(View.VISIBLE); + } else { + mFilesystemInfo.setVisibility(View.VISIBLE); + mFilesystemInfoRefreshing.setVisibility(View.INVISIBLE); + } + + } + } + + /** + * Method invoked when an action item is clicked. + * + * @param view The button pushed + */ + public void onActionBarItemClick(View view) { + switch (view.getId()) { + //###################### + //Breadcrumb Actions + //###################### + case R.id.button_filesystem_info: + //Show a popup with the storage volumes to select + showStorageVolumesPopUp(view); + break; + + default: + break; + } + } + + /** + * Method that cancels the activity + */ + private void cancel() { + setResult(FragmentActivity.RESULT_CANCELED); + finish(); + } + + /** + * Method that shows a popup with the storage volumes + * + * @param anchor The view on which anchor the popup + */ + private void showStorageVolumesPopUp(View anchor) { + // Create a list (but not checkable) + final Object[] volumes = StorageHelper.getStorageVolumes(PickerActivity.this); + List descriptions = new ArrayList(); + if (volumes != null) { + int cc = volumes.length; + for (int i = 0; i < cc; i++) { + String desc = StorageHelper.getStorageVolumeDescription(this, volumes[i]); + CheckableItem item = new CheckableItem(desc, false, false); + descriptions.add(item); + } + } + CheckableListAdapter adapter = + new CheckableListAdapter(getApplicationContext(), descriptions); + + //Create a show the popup menu + final ListPopupWindow popup = DialogHelper.createListPopupWindow(this, adapter, anchor); + popup.setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View v, int position, long id) { + popup.dismiss(); + if (volumes != null) { + PickerActivity.this. + mNavigationFragment.changeCurrentDir( + StorageHelper.getStoragePath(volumes[position])); + } + } + }); + popup.show(); + } + + /** + * {@inheritDoc} + */ + public void updateTitleActionBar() { + mBreadcrumb = (Breadcrumb)this.mRootView.findViewById(R.id.breadcrumb_view); + // Set the free disk space warning level of the breadcrumb widget + String fds = Preferences.getSharedPreferences().getString( + FileManagerSettings.SETTINGS_DISK_USAGE_WARNING_LEVEL.getId(), + (String)FileManagerSettings.SETTINGS_DISK_USAGE_WARNING_LEVEL.getDefaultValue()); + mBreadcrumb.setFreeDiskSpaceWarningLevel(Integer.parseInt(fds)); + } + + /** + * {@inheritDoc} + */ + public void pairBreadcrumb(int position, NavigationFragment fragment) { + try { + fragment.setBreadcrumb(mBreadcrumb); + } catch (Throwable ex) { + Log.e(TAG, + String.format("Failed to pair breadcrumb %d", //$NON-NLS-1$ + position, ex)); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onBreadcrumbItemClick(File item) { + this.mNavigationFragment.changeCurrentDir(item.getAbsolutePath()); + } + + /** + * Method that applies the current theme to the activity + * @hide + */ + void applyTheme() { + Theme theme = ThemeManager.getCurrentTheme(this); + theme.setBaseTheme(this, true); + this.mNavigationFragment.applyTheme(); + } +} diff --git a/src/com/cyanogenmod/filemanager/activities/SearchActivity.java b/Backbone/src/main/java/me/toolify/backbone/activities/SearchActivity.java similarity index 85% rename from src/com/cyanogenmod/filemanager/activities/SearchActivity.java rename to Backbone/src/main/java/me/toolify/backbone/activities/SearchActivity.java index 60d043c82..8d7b5d080 100644 --- a/src/com/cyanogenmod/filemanager/activities/SearchActivity.java +++ b/Backbone/src/main/java/me/toolify/backbone/activities/SearchActivity.java @@ -1,5 +1,6 @@ /* - * Copyright (C) 2012 The CyanogenMod Project + * Copyright (C) 2012-2013 The CyanogenMod Project + * Copyright (C) 2013 BrandroidTools * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,9 +15,8 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.activities; +package me.toolify.backbone.activities; -import android.app.ActionBar; import android.app.Activity; import android.app.AlertDialog; import android.app.SearchManager; @@ -34,56 +34,54 @@ import android.text.TextUtils; import android.util.Log; import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemLongClickListener; -import android.widget.ImageView; import android.widget.ListAdapter; import android.widget.ListView; import android.widget.ProgressBar; -import android.widget.TextView; import android.widget.Toast; -import com.cyanogenmod.filemanager.FileManagerApplication; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.activities.preferences.SettingsPreferences; -import com.cyanogenmod.filemanager.activities.preferences.SettingsPreferences.SearchPreferenceFragment; -import com.cyanogenmod.filemanager.adapters.SearchResultAdapter; -import com.cyanogenmod.filemanager.commands.AsyncResultExecutable; -import com.cyanogenmod.filemanager.commands.AsyncResultListener; -import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory; -import com.cyanogenmod.filemanager.console.RelaunchableException; -import com.cyanogenmod.filemanager.listeners.OnRequestRefreshListener; -import com.cyanogenmod.filemanager.model.Directory; -import com.cyanogenmod.filemanager.model.FileSystemObject; -import com.cyanogenmod.filemanager.model.ParentDirectory; -import com.cyanogenmod.filemanager.model.Query; -import com.cyanogenmod.filemanager.model.SearchResult; -import com.cyanogenmod.filemanager.model.Symlink; -import com.cyanogenmod.filemanager.parcelables.SearchInfoParcelable; -import com.cyanogenmod.filemanager.preferences.AccessMode; -import com.cyanogenmod.filemanager.preferences.FileManagerSettings; -import com.cyanogenmod.filemanager.preferences.Preferences; -import com.cyanogenmod.filemanager.providers.RecentSearchesContentProvider; -import com.cyanogenmod.filemanager.tasks.SearchResultDrawingAsyncTask; -import com.cyanogenmod.filemanager.ui.ThemeManager; -import com.cyanogenmod.filemanager.ui.ThemeManager.Theme; -import com.cyanogenmod.filemanager.ui.dialogs.ActionsDialog; -import com.cyanogenmod.filemanager.ui.dialogs.MessageProgressDialog; -import com.cyanogenmod.filemanager.ui.policy.DeleteActionPolicy; -import com.cyanogenmod.filemanager.ui.policy.IntentsActionPolicy; -import com.cyanogenmod.filemanager.ui.widgets.ButtonItem; -import com.cyanogenmod.filemanager.ui.widgets.FlingerListView; -import com.cyanogenmod.filemanager.ui.widgets.FlingerListView.OnItemFlingerListener; -import com.cyanogenmod.filemanager.ui.widgets.FlingerListView.OnItemFlingerResponder; -import com.cyanogenmod.filemanager.util.CommandHelper; -import com.cyanogenmod.filemanager.util.DialogHelper; -import com.cyanogenmod.filemanager.util.ExceptionUtil; -import com.cyanogenmod.filemanager.util.ExceptionUtil.OnRelaunchCommandResult; -import com.cyanogenmod.filemanager.util.FileHelper; -import com.cyanogenmod.filemanager.util.StorageHelper; +import me.toolify.backbone.FileManagerApplication; +import me.toolify.backbone.R; +import me.toolify.backbone.activities.preferences.SettingsPreferences; +import me.toolify.backbone.activities.preferences.SearchPreferenceFragment; +import me.toolify.backbone.adapters.SearchResultAdapter; +import me.toolify.backbone.commands.AsyncResultExecutable; +import me.toolify.backbone.commands.AsyncResultListener; +import me.toolify.backbone.console.NoSuchFileOrDirectory; +import me.toolify.backbone.console.RelaunchableException; +import me.toolify.backbone.listeners.OnRequestRefreshListener; +import me.toolify.backbone.model.Directory; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.model.ParentDirectory; +import me.toolify.backbone.model.Query; +import me.toolify.backbone.model.SearchResult; +import me.toolify.backbone.model.Symlink; +import me.toolify.backbone.parcelables.SearchInfoParcelable; +import me.toolify.backbone.preferences.AccessMode; +import me.toolify.backbone.preferences.FileManagerSettings; +import me.toolify.backbone.preferences.Preferences; +import me.toolify.backbone.providers.RecentSearchesContentProvider; +import me.toolify.backbone.tasks.SearchResultDrawingAsyncTask; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.ui.ThemeManager.Theme; +import me.toolify.backbone.ui.dialogs.MessageProgressDialog; +import me.toolify.backbone.ui.policy.DeleteActionPolicy; +import me.toolify.backbone.ui.policy.IntentsActionPolicy; +import me.toolify.backbone.ui.widgets.FlingerListView; +import me.toolify.backbone.ui.widgets.FlingerListView.OnItemFlingerListener; +import me.toolify.backbone.ui.widgets.FlingerListView.OnItemFlingerResponder; +import me.toolify.backbone.util.CommandHelper; +import me.toolify.backbone.util.DialogHelper; +import me.toolify.backbone.util.ExceptionUtil; +import me.toolify.backbone.util.ExceptionUtil.OnRelaunchCommandResult; +import me.toolify.backbone.util.FileHelper; +import me.toolify.backbone.util.StorageHelper; import java.io.FileNotFoundException; import java.util.ArrayList; @@ -104,7 +102,7 @@ public class SearchActivity extends Activity * An {@link Intent} action for restore view information. */ public static final String ACTION_RESTORE = - "com.cyanogenmod.filemanager.activities.SearchActivity#Restore"; //$NON-NLS-1$ + "me.toolify.backbone.activities.SearchActivity#Restore"; //$NON-NLS-1$ /** * Intent extra parameter for search in the selected directory on enter. @@ -168,8 +166,7 @@ public boolean onItemFlingerStart( AdapterView parent, View view, int position, long id) { try { // Response if the item can be removed - SearchResultAdapter adapter = (SearchResultAdapter)parent.getAdapter(); - SearchResult result = adapter.getItem(position); + SearchResult result = mSearchResultAdaper.getItem(position); if (result != null && result.getFso() != null) { if (result.getFso() instanceof ParentDirectory) { // This is not possible ... @@ -189,8 +186,7 @@ public void onItemFlingerEnd(OnItemFlingerResponder responder, try { // Response if the item can be removed - SearchResultAdapter adapter = (SearchResultAdapter)parent.getAdapter(); - SearchResult result = adapter.getItem(position); + SearchResult result = mSearchResultAdaper.getItem(position); if (result != null && result.getFso() != null) { DeleteActionPolicy.removeFileSystemObject( SearchActivity.this, @@ -227,15 +223,16 @@ public void onItemFlingerEnd(OnItemFlingerResponder responder, /** * @hide */ - ProgressBar mSearchWaiting; + SearchResultAdapter mSearchResultAdaper; /** * @hide */ - TextView mSearchFoundItems; + ProgressBar mSearchWaiting; /** * @hide */ - TextView mSearchTerms; + MenuItem mSearchFoundItems; + private View mEmptyListMsg; private String mSearchDirectory; @@ -410,21 +407,48 @@ private void restoreState(Bundle state) { */ private void initTitleActionBar() { //Configure the action bar options - getActionBar().setBackgroundDrawable( - getResources().getDrawable(R.drawable.bg_holo_titlebar)); - getActionBar().setDisplayOptions( - ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_SHOW_HOME); getActionBar().setDisplayHomeAsUpEnabled(true); - View customTitle = getLayoutInflater().inflate(R.layout.simple_customtitle, null, false); + getActionBar().setTitle(R.string.search); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater menuInflater = getMenuInflater(); + menuInflater.inflate(R.menu.search, menu); + mSearchFoundItems = menu.findItem(R.id.mnu_search_result_number); + return super.onCreateOptionsMenu(menu); + } - TextView title = (TextView)customTitle.findViewById(R.id.customtitle_title); - title.setText(R.string.search); - title.setContentDescription(getString(R.string.search)); - ButtonItem configuration = (ButtonItem)customTitle.findViewById(R.id.ab_button1); - configuration.setImageResource(R.drawable.ic_holo_light_config); - configuration.setVisibility(View.VISIBLE); + /** + * {@inheritDoc} + */ + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + return super.onPrepareOptionsMenu(menu); + } - getActionBar().setCustomView(customTitle); + /** + * {@inheritDoc} + */ + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + back(true, null, false); + break; + case R.id.mnu_search_settings: + //Settings + Intent settings = new Intent(this, SettingsPreferences.class); + settings.putExtra( + PreferenceActivity.EXTRA_SHOW_FRAGMENT, + SearchPreferenceFragment.class.getName()); + startActivity(settings); + break; + } + return super.onOptionsItemSelected(item); } /** @@ -452,32 +476,13 @@ private void initComponents() { //Other components this.mSearchWaiting = (ProgressBar)findViewById(R.id.search_waiting); - this.mSearchFoundItems = (TextView)findViewById(R.id.search_status_found_items); + /*TODO: Set this info to the status action icon when "in progress" searching is focused on the list view and + not the dialog * / +/* this.mSearchFoundItems = (TextView)findViewById(R.id.search_status_found_items); setFoundItems(0, ""); //$NON-NLS-1$ this.mSearchTerms = (TextView)findViewById(R.id.search_status_query_terms); this.mSearchTerms.setText( - Html.fromHtml(getString(R.string.search_terms, ""))); //$NON-NLS-1$ - } - - /** - * Method invoked when an action item is clicked. - * - * @param view The button pushed - */ - public void onActionBarItemClick(View view) { - switch (view.getId()) { - case R.id.ab_button1: - //Settings - Intent settings = new Intent(this, SettingsPreferences.class); - settings.putExtra( - PreferenceActivity.EXTRA_SHOW_FRAGMENT, - SearchPreferenceFragment.class.getName()); - startActivity(settings); - break; - - default: - break; - } + Html.fromHtml(getString(R.string.search_terms, ""))); //$NON-NLS-1$*/ } /** @@ -575,7 +580,7 @@ public void onClick(DialogInterface alertDialog, int which) { //Close search activity back(true, null, false); } - }); + }); DialogHelper.delegateDialogShow(this, dialog); } @@ -611,15 +616,17 @@ void doSearch( } //Set the listview + if (this.mSearchListView.getAdapter() != null) { + ((SearchResultAdapter)this.mSearchListView.getAdapter()).dispose(); + } this.mResultList = new ArrayList(); SearchResultAdapter adapter = new SearchResultAdapter(this, new ArrayList(), R.layout.search_item, this.mQuery); this.mSearchListView.setAdapter(adapter); - //Set terms - this.mSearchTerms.setText( - Html.fromHtml(getString(R.string.search_terms, query.getTerms()))); + // Set terms in action bar title + getActionBar().setTitle(getString(R.string.search) + ": \"" + query.getTerms() + "\""); //$NON-NLS-1$ //Now, do the search in background this.mSearchListView.post(new Runnable() { @@ -719,8 +726,7 @@ public void run() { if (terms.endsWith(" | ")) { //$NON-NLS-1$; terms = ""; //$NON-NLS-1$; } - SearchActivity.this.mSearchTerms.setText( - Html.fromHtml(getString(R.string.search_terms, terms))); + getActionBar().setTitle(getString(R.string.search) + ": \"" + query.getTerms() + "\""); //$NON-NLS-1$ try { if (SearchActivity.this.mSearchWaiting != null) { @@ -783,7 +789,6 @@ private List filterQuery(List original) { void removeAll() { SearchResultAdapter adapter = (SearchResultAdapter)this.mSearchListView.getAdapter(); adapter.clear(); - adapter.notifyDataSetChanged(); this.mSearchListView.setSelection(0); toggleResults(false, true); } @@ -808,28 +813,27 @@ void toggleResults(boolean hasResults, boolean showEmpty) { * @hide */ void setFoundItems(final int items, final String searchDirectory) { - if (this.mSearchFoundItems != null) { - this.mSearchFoundItems.post(new Runnable() { - @Override - public void run() { - String directory = searchDirectory; - if (SearchActivity.this.mChRooted && - directory != null && directory.length() > 0) { - directory = StorageHelper.getChrootedPath(directory); - } - - String foundItems = - getResources(). + this.runOnUiThread(new Runnable() { + @Override + public void run() { + String directory = searchDirectory; + if (SearchActivity.this.mChRooted && + directory != null && directory.length() > 0) { + directory = StorageHelper.getChrootedPath(directory); + } + String foundItems = + getResources(). getQuantityString( - R.plurals.search_found_items, items, Integer.valueOf(items)); - SearchActivity.this.mSearchFoundItems.setText( - getString( - R.string.search_found_items_in_directory, - foundItems, - directory)); + R.plurals.search_found_items, items, Integer.valueOf(items)); + if (mSearchFoundItems != null) { + mSearchFoundItems.setTitle( + getString( + R.string.search_found_items_in_directory, + foundItems, + directory)); } - }); - } + } + }); } /** @@ -846,20 +850,6 @@ public boolean onKeyUp(int keyCode, KeyEvent event) { } } - /** - * {@inheritDoc} - */ - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - back(true, null, false); - return true; - default: - return super.onOptionsItemSelected(item); - } - } - /** * {@inheritDoc} */ @@ -930,10 +920,6 @@ public void onRequestMenu(FileSystemObject item) { } return; } - - ActionsDialog dialog = new ActionsDialog(this, fso, false, true); - dialog.setOnRequestRefreshListener(this); - dialog.show(); } /** @@ -1003,7 +989,7 @@ public void onRequestRemove(Object o, boolean clearSelection) { @Override public void onNavigateTo(Object o) { if (o instanceof FileSystemObject) { - back(false, (FileSystemObject)o, true); + back(false, (FileSystemObject) o, true); } } @@ -1047,7 +1033,7 @@ void back(final boolean cancelled, FileSystemObject item, boolean isChecked) { @Override public void onSuccess() { if (navigateTo(fFso, intent)) { - finish(); + exit(); } } @Override @@ -1072,8 +1058,18 @@ public void onCancelled() { /** NON BLOCK**/} // End this activity if (finish) { - finish(); + exit(); + } + } + + /** + * Method invoked when the activity needs to exit + */ + private void exit() { + if (this.mSearchListView.getAdapter() != null) { + ((SearchResultAdapter)this.mSearchListView.getAdapter()).dispose(); } + finish(); } /** @@ -1244,22 +1240,9 @@ void applyTheme() { Theme theme = ThemeManager.getCurrentTheme(this); theme.setBaseTheme(this, false); - //- ActionBar - theme.setTitlebarDrawable(this, getActionBar(), "titlebar_drawable"); //$NON-NLS-1$ - View v = getActionBar().getCustomView().findViewById(R.id.customtitle_title); - theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$ - v = findViewById(R.id.ab_button1); - theme.setImageDrawable(this, (ImageView)v, "ic_config_drawable"); //$NON-NLS-1$ // ContentView theme.setBackgroundDrawable( this, getWindow().getDecorView(), "background_drawable"); //$NON-NLS-1$ - //- StatusBar - v = findViewById(R.id.search_status); - theme.setBackgroundDrawable(this, v, "statusbar_drawable"); //$NON-NLS-1$ - v = findViewById(R.id.search_status_found_items); - theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$ - v = findViewById(R.id.search_status_query_terms); - theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$ //ListView if (this.mSearchListView.getAdapter() != null) { ((SearchResultAdapter)this.mSearchListView.getAdapter()).notifyDataSetChanged(); diff --git a/src/com/cyanogenmod/filemanager/activities/ShortcutActivity.java b/Backbone/src/main/java/me/toolify/backbone/activities/ShortcutActivity.java similarity index 90% rename from src/com/cyanogenmod/filemanager/activities/ShortcutActivity.java rename to Backbone/src/main/java/me/toolify/backbone/activities/ShortcutActivity.java index cf4377066..6807d24ba 100644 --- a/src/com/cyanogenmod/filemanager/activities/ShortcutActivity.java +++ b/Backbone/src/main/java/me/toolify/backbone/activities/ShortcutActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.activities; +package me.toolify.backbone.activities; import android.app.Activity; import android.content.BroadcastReceiver; @@ -28,16 +28,16 @@ import android.util.Log; import android.widget.Toast; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.console.ConsoleBuilder; -import com.cyanogenmod.filemanager.model.FileSystemObject; -import com.cyanogenmod.filemanager.preferences.FileManagerSettings; -import com.cyanogenmod.filemanager.ui.ThemeManager; -import com.cyanogenmod.filemanager.ui.ThemeManager.Theme; -import com.cyanogenmod.filemanager.ui.policy.IntentsActionPolicy; -import com.cyanogenmod.filemanager.util.CommandHelper; -import com.cyanogenmod.filemanager.util.DialogHelper; -import com.cyanogenmod.filemanager.util.ExceptionUtil; +import me.toolify.backbone.R; +import me.toolify.backbone.console.ConsoleBuilder; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.preferences.FileManagerSettings; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.ui.ThemeManager.Theme; +import me.toolify.backbone.ui.policy.IntentsActionPolicy; +import me.toolify.backbone.util.CommandHelper; +import me.toolify.backbone.util.DialogHelper; +import me.toolify.backbone.util.ExceptionUtil; /** * The activity for handle the desktop shortcuts create by the app. @@ -200,11 +200,7 @@ private void init() { */ private boolean initializeConsole() { try { - // Is there a console allocate - if (!ConsoleBuilder.isAlloc()) { - // Create a console - ConsoleBuilder.getConsole(this); - } + ConsoleBuilder.getConsole(this); // There is a console allocated. Use it. return true; } catch (Throwable _throw) { diff --git a/Backbone/src/main/java/me/toolify/backbone/activities/preferences/AboutPreferenceFragment.java b/Backbone/src/main/java/me/toolify/backbone/activities/preferences/AboutPreferenceFragment.java new file mode 100644 index 000000000..bdf1c73d3 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/activities/preferences/AboutPreferenceFragment.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2013 BrandroidTools + * + * 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 me.toolify.backbone.activities.preferences; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.preference.Preference; + +import me.toolify.backbone.R; +import me.toolify.backbone.activities.ChangeLogActivity; +import me.toolify.backbone.preferences.Preferences; + +/** + * A class that manages the 'about' screen + */ +public class AboutPreferenceFragment extends TitlePreferenceFragment { + + private static final String TAG = "AboutPreferenceFragment"; //$NON-NLS-1$ + + private static final boolean DEBUG = false; + + private Preference mFaq; + private Preference mChangelog; + private Preference mPrivacyPolicy; + private Preference mCreditsAndLicenses; + private Preference mVersion; + + /** + * @hide + */ + boolean mLoaded = false; + + /** + * {@inheritDoc} + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Change the preference manager + getPreferenceManager().setSharedPreferencesName(Preferences.SETTINGS_FILENAME); + getPreferenceManager().setSharedPreferencesMode(Context.MODE_PRIVATE); + this.mLoaded = false; + + // Add the preferences + addPreferencesFromResource(R.xml.preferences_about); + + // Frequently Asked Questions + this.mFaq = findPreference("bb_filemanager_faq"); + this.mFaq.setEnabled(false); + + // Changelog + this.mChangelog = findPreference("bb_filemanager_changelog"); + mChangelog.setIntent(new Intent(getActivity(), ChangeLogActivity.class)); + this.mChangelog.setEnabled(false); + + // Privacy Policy + this.mPrivacyPolicy = findPreference("bb_filemanager_privacy_policy"); + this.mPrivacyPolicy.setEnabled(false); + + // Credits and Open Source Licenses + this.mCreditsAndLicenses = findPreference("bb_filemanager_credits_licenses"); + mCreditsAndLicenses.setIntent(new Intent(getActivity(), LicenseActivity.class));; + + // Build Version + this.mVersion = findPreference("bb_filemanager_version"); + // Retrieve the about header + try { + String appver = + getActivity().getPackageManager().getPackageInfo(getActivity().getPackageName(), 0).versionName; + mVersion.setSummary(getString(R.string.pref_about_version_num, appver)); + } catch (Exception e) { + mVersion.setSummary(getString(R.string.pref_about_version_num, "")); //$NON-NLS-1$ + } + + // Loaded + this.mLoaded = true; + } + + /** + * {@inheritDoc} + */ + @Override + public CharSequence getTitle() { + return getString(R.string.pref_about); + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/activities/preferences/EditorPreferenceFragment.java b/Backbone/src/main/java/me/toolify/backbone/activities/preferences/EditorPreferenceFragment.java new file mode 100644 index 000000000..76b956bf0 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/activities/preferences/EditorPreferenceFragment.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.activities.preferences; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceChangeListener; +import android.util.Log; + +import me.toolify.backbone.R; +import me.toolify.backbone.preferences.FileManagerSettings; +import me.toolify.backbone.preferences.Preferences; + +/** + * A class that manages the editor options + */ +public class EditorPreferenceFragment extends TitlePreferenceFragment { + + private static final String TAG = "EditorPreferenceFragment"; //$NON-NLS-1$ + + private static final boolean DEBUG = false; + + private CheckBoxPreference mNoSuggestions; + private CheckBoxPreference mWordWrap; + private CheckBoxPreference mHexdump; + + private CheckBoxPreference mSyntaxHighlight; + + + /** + * @hide + */ + boolean mLoaded = false; + + private final OnPreferenceChangeListener mOnChangeListener = + new OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(final Preference preference, Object newValue) { + boolean ret = true; + + String key = preference.getKey(); + if (DEBUG) { + Log.d(TAG, + String.format("New value for %s: %s", //$NON-NLS-1$ + key, + String.valueOf(newValue))); + } + + // Notify the change (only if fragment is loaded. Default values are loaded + // while not in loaded mode) + if (EditorPreferenceFragment.this.mLoaded && ret) { + Intent intent = new Intent(FileManagerSettings.INTENT_SETTING_CHANGED); + intent.putExtra( + FileManagerSettings.EXTRA_SETTING_CHANGED_KEY, preference.getKey()); + getActivity().sendBroadcast(intent); + } + + return ret; + } + }; + + /** + * {@inheritDoc} + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Change the preference manager + getPreferenceManager().setSharedPreferencesName(Preferences.SETTINGS_FILENAME); + getPreferenceManager().setSharedPreferencesMode(Context.MODE_PRIVATE); + this.mLoaded = false; + + // Add the preferences + addPreferencesFromResource(R.xml.preferences_editor); + + // No suggestions + this.mNoSuggestions = + (CheckBoxPreference)findPreference( + FileManagerSettings.SETTINGS_EDITOR_NO_SUGGESTIONS.getId()); + this.mNoSuggestions.setOnPreferenceChangeListener(this.mOnChangeListener); + + // WordWrap + this.mWordWrap = + (CheckBoxPreference)findPreference( + FileManagerSettings.SETTINGS_EDITOR_WORD_WRAP.getId()); + this.mWordWrap.setOnPreferenceChangeListener(this.mOnChangeListener); + + // Hexdump + this.mHexdump = + (CheckBoxPreference)findPreference( + FileManagerSettings.SETTINGS_EDITOR_HEXDUMP.getId()); + this.mHexdump.setOnPreferenceChangeListener(this.mOnChangeListener); + + // Syntax highlight + this.mSyntaxHighlight = + (CheckBoxPreference)findPreference( + FileManagerSettings.SETTINGS_EDITOR_SYNTAX_HIGHLIGHT.getId()); + this.mSyntaxHighlight.setOnPreferenceChangeListener(this.mOnChangeListener); + + // Loaded + this.mLoaded = true; + } + + /** + * {@inheritDoc} + */ + @Override + public CharSequence getTitle() { + return getString(R.string.pref_editor); + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/activities/preferences/EditorSHColorSchemePreferenceFragment.java b/Backbone/src/main/java/me/toolify/backbone/activities/preferences/EditorSHColorSchemePreferenceFragment.java new file mode 100644 index 000000000..eba18b2db --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/activities/preferences/EditorSHColorSchemePreferenceFragment.java @@ -0,0 +1,308 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.activities.preferences; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceChangeListener; +import android.preference.Preference.OnPreferenceClickListener; +import android.text.TextUtils; +import android.util.Log; + +import me.toolify.backbone.R; +import com.ash.syntaxhighlight.HighlightColors; +import me.toolify.backbone.preferences.FileManagerSettings; +import me.toolify.backbone.preferences.Preferences; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.ui.preferences.ColorPickerPreference; +import me.toolify.backbone.util.ExceptionUtil; + +/** + * A class that manages the color scheme of the syntax highlight processor. + */ +public class EditorSHColorSchemePreferenceFragment extends TitlePreferenceFragment { + + private static final String TAG = "EditorSHColorSchemePreferenceFragment"; //$NON-NLS-1$ + + private static final boolean DEBUG = false; + + private static final String KEY_RESET_COLOR_SCHEME = "ash_reset_color_scheme"; //$NON-NLS-1$ + + private CheckBoxPreference mUseThemeDefault; + private Preference mResetColorScheme; + private ColorPickerPreference[] mColorScheme; + + /** + * @hide + */ + boolean mLoaded = false; + + private final OnPreferenceChangeListener mOnChangeListener = + new OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(final Preference preference, final Object newValue) { + boolean ret = true; + + String key = preference.getKey(); + if (DEBUG) { + Log.d(TAG, + String.format("New value for %s: %s", //$NON-NLS-1$ + key, + String.valueOf(newValue))); + } + + // Use theme default + if (key.compareTo( + FileManagerSettings.SETTINGS_EDITOR_SH_USE_THEME_DEFAULT.getId()) == 0) { + boolean enabled = ((Boolean)newValue).booleanValue(); + setColorSchemeEnabled(!enabled); + + } else if (isColorSchemePreference(preference)) { + // Unify the color schemes property. Save the property here + int color = ((Integer)newValue).intValue(); + try { + String colorScheme = toColorSchemeSet(preference, color); + Preferences.savePreference( + FileManagerSettings.SETTINGS_EDITOR_SH_COLOR_SCHEME, + colorScheme, + true); + } catch (Exception e) { + ExceptionUtil.translateException(getActivity(), e); + } + ((ColorPickerPreference)preference).setColor(color); + + // Change the key to get notifications of color scheme + key = FileManagerSettings.SETTINGS_EDITOR_SH_COLOR_SCHEME.getId(); + } + + // Notify the change (only if fragment is loaded. Default values are loaded + // while not in loaded mode) + if (EditorSHColorSchemePreferenceFragment.this.mLoaded && ret) { + Intent intent = new Intent(FileManagerSettings.INTENT_SETTING_CHANGED); + intent.putExtra( + FileManagerSettings.EXTRA_SETTING_CHANGED_KEY, key); + getActivity().sendBroadcast(intent); + } + + return ret; + } + }; + + private final OnPreferenceClickListener mOnClickListener = + new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + String key = preference.getKey(); + if (KEY_RESET_COLOR_SCHEME.compareTo(key) == 0) { + loadDefaultColorScheme(true); + } + return false; + } + }; + + /** + * {@inheritDoc} + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Change the preference manager + getPreferenceManager().setSharedPreferencesName(Preferences.SETTINGS_FILENAME); + getPreferenceManager().setSharedPreferencesMode(Context.MODE_PRIVATE); + + // Add the preferences + addPreferencesFromResource(R.xml.preferences_editor_color_scheme); + + // Color scheme (need to resolver color scheme prior to use theme default) + loadDefaultColorScheme(false); + + // Use Theme default + this.mUseThemeDefault = + (CheckBoxPreference)findPreference( + FileManagerSettings.SETTINGS_EDITOR_SH_USE_THEME_DEFAULT.getId()); + Boolean defaultValue = ((Boolean)FileManagerSettings. + SETTINGS_EDITOR_SH_USE_THEME_DEFAULT.getDefaultValue()); + Boolean value = + Boolean.valueOf( + Preferences.getSharedPreferences().getBoolean( + FileManagerSettings.SETTINGS_EDITOR_SH_USE_THEME_DEFAULT.getId(), + defaultValue.booleanValue())); + + // Reset to default theme color scheme + this.mResetColorScheme = findPreference(KEY_RESET_COLOR_SCHEME); + + // Now the listeners + this.mOnChangeListener.onPreferenceChange(this.mUseThemeDefault, value); + this.mUseThemeDefault.setOnPreferenceChangeListener(this.mOnChangeListener); + this.mResetColorScheme.setOnPreferenceClickListener(this.mOnClickListener); + + // Loaded + this.mLoaded = true; + } + + /** + * {@inheritDoc} + */ + @Override + public CharSequence getTitle() { + return getString(R.string.pref_syntax_highlight_color_scheme); + } + + /** + * Method that loads the default color scheme + * + * @param reset Whether the color scheme should be reseted + * @hide + */ + void loadDefaultColorScheme(boolean reset) { + try { + String defaultValue = + (String)FileManagerSettings.SETTINGS_EDITOR_SH_COLOR_SCHEME.getDefaultValue(); + if (!reset) { + defaultValue = + Preferences.getSharedPreferences().getString( + FileManagerSettings.SETTINGS_EDITOR_SH_COLOR_SCHEME.getId(), + defaultValue); + } else { + Preferences.savePreference( + FileManagerSettings.SETTINGS_EDITOR_SH_COLOR_SCHEME, + defaultValue, + true); + } + int[] colorScheme = toColorShemeArray(defaultValue); + HighlightColors[] colors = HighlightColors.values(); + int cc = colors.length; + this.mColorScheme = new ColorPickerPreference[cc]; + for (int i = 0; i < cc; i++) { + this.mColorScheme[i] = (ColorPickerPreference)findPreference(colors[i].getId()); + setColorScheme(colors[i], colorScheme, i); + this.mColorScheme[i].setOnPreferenceChangeListener(this.mOnChangeListener); + } + } catch (Exception e) { + ExceptionUtil.translateException(getActivity(), e); + } + } + + /** + * Method that set the enabled status of the color schemes preferences + * + * @param enable If the color scheme preferences should be enabled or not. + * @hide + */ + void setColorSchemeEnabled(final boolean enable) { + int cc = this.mColorScheme.length; + for (int i = 0; i < cc; i++) { + this.mColorScheme[i].setEnabled(enable); + } + this.mResetColorScheme.setEnabled(enable); + } + + /** + * Method that set a color scheme (use setting or theme default) + * + * @param color The color reference + * @param colorScheme The array of colors + * @param pos The position of the color + * @hide + */ + void setColorScheme(HighlightColors color, int[] colorScheme, int pos) { + try { + this.mColorScheme[pos].setColor(colorScheme[pos]); + } catch (Exception e) { + this.mColorScheme[pos].setColor( + ThemeManager.getCurrentTheme( + getActivity()).getColor(getActivity(), color.getResId())); + Log.w(TAG, + String.format( + "Color scheme value not found for \"%s\"", //$NON-NLS-1$ + color.getId())); + } + } + + /** + * Method that returns if the preference is part of the color scheme preferences + * + * @return boolean Whether preference is part of the color scheme preferences + * @hide + */ + static boolean isColorSchemePreference(final Preference preference) { + String key = preference.getKey(); + if (key == null) { + return false; + } + HighlightColors[] colors = HighlightColors.values(); + int cc = colors.length; + for (int i = 0; i < cc; i++) { + if (colors[i].getId().compareTo(key) == 0) { + return true; + } + } + return false; + } + + /** + * Method that converts the string set of color schemes to an array of colors + * + * @param value The string set of color schemes to parse + * @return int[] Array of colors + */ + public static int[] toColorShemeArray(String value) { + if (value == null || value.length() == 0) { + return new int[]{}; + } + String[] values = value.split("\\|"); //$NON-NLS-1$ + int[] colors = new int[values.length]; + int cc = colors.length; + for (int i = 0; i < cc; i++) { + try { + colors[i] = Integer.parseInt(values[i]); + } catch (Exception e) { + Log.w(TAG, + String.format( + "Problem parsing color value \"%s\" on position %d", //$NON-NLS-1$ + values[i], Integer.valueOf(i))); + colors[i] = 0; + } + } + return colors; + } + + /** + * Method that converts all the color scheme preference to one unified preference set + * + * @param preference The color scheme preference that was changed + * @param newValue The new value of the color scheme + * @return colorScheme The actual color schemes + * @hide + */ + String toColorSchemeSet(final Preference preference, final int newValue) { + int cc = this.mColorScheme.length; + String[] colorSchemes = new String[cc]; + for (int i = 0; i < cc; i++) { + String prop = String.valueOf(this.mColorScheme[i].getColor()); + if (this.mColorScheme[i].getKey().compareTo(preference.getKey()) == 0) { + prop = String.valueOf(newValue); + } + colorSchemes[i] = prop; + } + return TextUtils.join("|", colorSchemes); //$NON-NLS-1$ + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/activities/preferences/GeneralPreferenceFragment.java b/Backbone/src/main/java/me/toolify/backbone/activities/preferences/GeneralPreferenceFragment.java new file mode 100644 index 000000000..78dc52b24 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/activities/preferences/GeneralPreferenceFragment.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.activities.preferences; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceChangeListener; +import android.util.Log; + +import me.toolify.backbone.FileManagerApplication; +import me.toolify.backbone.R; +import me.toolify.backbone.console.ConsoleBuilder; +import me.toolify.backbone.preferences.AccessMode; +import me.toolify.backbone.preferences.FileManagerSettings; +import me.toolify.backbone.preferences.ObjectStringIdentifier; +import me.toolify.backbone.preferences.Preferences; + +/** + * A class that manages the commons options of the application + */ +public class GeneralPreferenceFragment extends TitlePreferenceFragment { + + private static final String TAG = "GeneralPreferenceFragment"; //$NON-NLS-1$ + + private static final boolean DEBUG = false; + + private CheckBoxPreference mCaseSensitiveSort; + private ListPreference mFiletimeFormatMode; + private ListPreference mFreeDiskSpaceWarningLevel; + private CheckBoxPreference mComputeFolderStatistics; + private CheckBoxPreference mDisplayThumbs; +// private CheckBoxPreference mUseFlinger; + private ListPreference mAccessMode; + private CheckBoxPreference mDebugTraces; + + /** + * @hide + */ + boolean mLoaded = false; + + private final OnPreferenceChangeListener mOnChangeListener = + new OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(final Preference preference, Object newValue) { + boolean ret = true; + + String key = preference.getKey(); + if (DEBUG) { + Log.d(TAG, + String.format("New value for %s: %s", //$NON-NLS-1$ + key, + String.valueOf(newValue))); + } + + // Filetime format mode + if (FileManagerSettings.SETTINGS_FILETIME_FORMAT_MODE. + getId().compareTo(key) == 0) { + String value = (String)newValue; + int valueId = Integer.valueOf(value).intValue(); + String[] labels = getResources().getStringArray( + R.array.filetime_format_mode_labels); + preference.setSummary(labels[valueId]); + } + + // Disk usage warning level + else if (FileManagerSettings.SETTINGS_DISK_USAGE_WARNING_LEVEL. + getId().compareTo(key) == 0) { + String value = (String)newValue; + preference.setSummary( + getResources().getString( + R.string.pref_disk_usage_warning_level_summary, value)); + } + + // Access mode + else if (FileManagerSettings.SETTINGS_ACCESS_MODE.getId().compareTo(key) == 0) { + Activity activity = GeneralPreferenceFragment.this.getActivity(); + + String value = (String)newValue; + AccessMode oldMode = FileManagerApplication.getAccessMode(); + AccessMode newMode = AccessMode.fromId(value); + if (oldMode.compareTo(newMode) != 0) { + // The mode was changes. Change the console + if (newMode.compareTo(AccessMode.ROOT) == 0) { + if (!ConsoleBuilder.changeToPrivilegedConsole( + activity.getApplicationContext())) { + value = String.valueOf(oldMode.ordinal()); + ret = false; + } + } else { + if (!ConsoleBuilder.changeToNonPrivilegedConsole( + activity.getApplicationContext())) { + value = String.valueOf(oldMode.ordinal()); + ret = false; + } + } + } + + int valueId = Integer.valueOf(value).intValue(); + String[] summary = getResources().getStringArray( + R.array.access_mode_summaries); + preference.setSummary(summary[valueId]); + } + + // Notify the change (only if fragment is loaded. Default values are loaded + // while not in loaded mode) + if (GeneralPreferenceFragment.this.mLoaded && ret) { + Intent intent = new Intent(FileManagerSettings.INTENT_SETTING_CHANGED); + intent.putExtra( + FileManagerSettings.EXTRA_SETTING_CHANGED_KEY, preference.getKey()); + getActivity().sendBroadcast(intent); + } + + return ret; + } + }; + + /** + * {@inheritDoc} + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Change the preference manager + getPreferenceManager().setSharedPreferencesName(Preferences.SETTINGS_FILENAME); + getPreferenceManager().setSharedPreferencesMode(Context.MODE_PRIVATE); + + // Add the preferences + addPreferencesFromResource(R.xml.preferences_general); + + // Case sensitive sort + this.mCaseSensitiveSort = + (CheckBoxPreference)findPreference( + FileManagerSettings.SETTINGS_CASE_SENSITIVE_SORT.getId()); + this.mCaseSensitiveSort.setOnPreferenceChangeListener(this.mOnChangeListener); + + // Filetime format mode + this.mFiletimeFormatMode = + (ListPreference)findPreference( + FileManagerSettings.SETTINGS_FILETIME_FORMAT_MODE.getId()); + String defaultValue = ((ObjectStringIdentifier)FileManagerSettings. + SETTINGS_FILETIME_FORMAT_MODE.getDefaultValue()).getId(); + String value = Preferences.getSharedPreferences().getString( + FileManagerSettings.SETTINGS_FILETIME_FORMAT_MODE.getId(), + defaultValue); + this.mOnChangeListener.onPreferenceChange(this.mFiletimeFormatMode, value); + this.mFiletimeFormatMode.setOnPreferenceChangeListener(this.mOnChangeListener); + + // Disk usage warning level + this.mFreeDiskSpaceWarningLevel = + (ListPreference)findPreference( + FileManagerSettings.SETTINGS_DISK_USAGE_WARNING_LEVEL.getId()); + defaultValue = ((String)FileManagerSettings. + SETTINGS_DISK_USAGE_WARNING_LEVEL.getDefaultValue()); + value = Preferences.getSharedPreferences().getString( + FileManagerSettings.SETTINGS_DISK_USAGE_WARNING_LEVEL.getId(), + defaultValue); + this.mOnChangeListener.onPreferenceChange(this.mFreeDiskSpaceWarningLevel, value); + this.mFreeDiskSpaceWarningLevel.setOnPreferenceChangeListener(this.mOnChangeListener); + + // Compute folder statistics + this.mComputeFolderStatistics = + (CheckBoxPreference)findPreference( + FileManagerSettings.SETTINGS_COMPUTE_FOLDER_STATISTICS.getId()); + this.mComputeFolderStatistics.setOnPreferenceChangeListener(this.mOnChangeListener); + + // Display thumbs + this.mDisplayThumbs = + (CheckBoxPreference)findPreference( + FileManagerSettings.SETTINGS_DISPLAY_THUMBS.getId()); + Boolean defaultBooleanValue = ((Boolean)FileManagerSettings. + SETTINGS_DISPLAY_THUMBS.getDefaultValue()); + Boolean booleanValue = Preferences.getSharedPreferences().getBoolean( + FileManagerSettings.SETTINGS_DISPLAY_THUMBS.getId(), + defaultBooleanValue); + this.mOnChangeListener.onPreferenceChange(this.mDisplayThumbs, booleanValue); + this.mDisplayThumbs.setOnPreferenceChangeListener(this.mOnChangeListener); + + // Use flinger +// this.mUseFlinger = +// (CheckBoxPreference)findPreference( +// FileManagerSettings.SETTINGS_USE_FLINGER.getId()); +// this.mUseFlinger.setOnPreferenceChangeListener(this.mOnChangeListener); + + // Access mode + this.mAccessMode = + (ListPreference)findPreference( + FileManagerSettings.SETTINGS_ACCESS_MODE.getId()); + this.mAccessMode.setOnPreferenceChangeListener(this.mOnChangeListener); + defaultValue = ((ObjectStringIdentifier)FileManagerSettings. + SETTINGS_ACCESS_MODE.getDefaultValue()).getId(); + value = Preferences.getSharedPreferences().getString( + FileManagerSettings.SETTINGS_ACCESS_MODE.getId(), + defaultValue); + this.mOnChangeListener.onPreferenceChange(this.mAccessMode, value); + // If device is not rooted, this setting cannot be changed + this.mAccessMode.setEnabled(FileManagerApplication.isDeviceRooted()); + + // Capture Debug traces + this.mDebugTraces = + (CheckBoxPreference)findPreference( + FileManagerSettings.SETTINGS_SHOW_TRACES.getId()); + this.mDebugTraces.setOnPreferenceChangeListener(this.mOnChangeListener); + + // Loaded + this.mLoaded = true; + } + + /** + * {@inheritDoc} + */ + @Override + public CharSequence getTitle() { + return getString(R.string.pref_general); + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/activities/preferences/LicenseActivity.java b/Backbone/src/main/java/me/toolify/backbone/activities/preferences/LicenseActivity.java new file mode 100644 index 000000000..d72c15f14 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/activities/preferences/LicenseActivity.java @@ -0,0 +1,247 @@ +package me.toolify.backbone.activities.preferences; + +import android.app.Activity; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.AsyncTask; +import android.os.Bundle; +import android.util.Log; +import android.view.*; +import android.widget.ListAdapter; +import android.widget.ListView; +import me.toolify.backbone.R; +import me.toolify.backbone.adapters.LicenseAdapter; +import me.toolify.backbone.model.License; +import me.toolify.backbone.model.License.LICENSE_TYPE; +import me.toolify.backbone.preferences.FileManagerSettings; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.util.ExceptionUtil; + + +import java.util.ArrayList; +import java.util.List; + +public class LicenseActivity extends Activity { + + private static final String TAG = "LicenseActivity"; //$NON-NLS-1$ + + private static boolean DEBUG = false; + + /** + * @hide + */ + private List mLicenses; + /** + * @hide + */ + private ListView mListView; + /** + * @hide + */ + private ListAdapter mAdapter; + boolean mIsEmpty; + + /** + * {@inheritDoc} + */ + @Override + protected void onCreate(Bundle state) { + if (DEBUG) { + Log.d(TAG, "HistoryActivity.onCreate"); //$NON-NLS-1$ + } + + // Register the broadcast receiver + IntentFilter filter = new IntentFilter(); + filter.addAction(FileManagerSettings.INTENT_THEME_CHANGED); + + //Set in transition + overridePendingTransition(R.anim.translate_to_right_in, R.anim.hold_out); + + //Set the main layout of the activity + setContentView(R.layout.licenses); + + //Initialize action bars and data + initTitleActionBar(); + initLicenses(); + + // Apply the theme + applyTheme(); + + //Save state + super.onCreate(state); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onDestroy() { + if (DEBUG) { + Log.d(TAG, "HistoryActivity.onDestroy"); //$NON-NLS-1$ + } + + //All destroy. Continue + super.onDestroy(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onPause() { + //Set out transition + overridePendingTransition(R.anim.hold_in, R.anim.translate_to_left_out); + super.onPause(); + } + + /** + * Method that initializes the titlebar of the activity. + */ + private void initTitleActionBar() { + //Configure the action bar options + getActionBar().setDisplayHomeAsUpEnabled(true); + getActionBar().setTitle(R.string.licenses); + } + + /** + * Method that initializes the licenses listview of the activity. + */ + private void initLicenses() { + this.mListView = (ListView)findViewById(R.id.licenses_listview); + mLicenses = new ArrayList(); + final LicenseAdapter mAdapter = new LicenseAdapter(this, mLicenses); + this.mListView.setAdapter(mAdapter); + + // Retrieve the loading view + final View waiting = findViewById(R.id.licenses_waiting); + + this.mListView = (ListView)findViewById(R.id.licenses_listview); + + // Load the history in background + AsyncTask task = new AsyncTask() { + Exception mCause; + List mLicenses; + + @Override + protected Boolean doInBackground(Void... params) { + try { + this.mLicenses = loadLicenses(); + if (this.mLicenses.isEmpty()) { + View msg = findViewById(R.id.licenses_empty_msg); + msg.setVisibility(View.VISIBLE); + return Boolean.TRUE; + } + LicenseActivity.this.mIsEmpty = this.mLicenses.isEmpty(); + + //Show inverted history + final List adapterList = new ArrayList(this.mLicenses); + LicenseActivity.this.mAdapter = + new LicenseAdapter(LicenseActivity.this, adapterList); + + return Boolean.TRUE; + + } catch (Exception e) { + this.mCause = e; + return Boolean.FALSE; + } + } + + @Override + protected void onPreExecute() { + waiting.setVisibility(View.VISIBLE); + } + + @Override + protected void onPostExecute(Boolean result) { + waiting.setVisibility(View.GONE); + if (result.booleanValue()) { + if (LicenseActivity.this.mListView != null && + LicenseActivity.this.mAdapter != null) { + + LicenseActivity.this.mListView. + setAdapter(LicenseActivity.this.mAdapter); + } + + } else { + if (this.mCause != null) { + ExceptionUtil.translateException(LicenseActivity.this, this.mCause); + } + } + } + + @Override + protected void onCancelled() { + waiting.setVisibility(View.GONE); + } + }; + task.execute(); + } + + private List loadLicenses() { + String[] mHeaderArray = getResources().getStringArray(R.array.credits_headers); + String[] mDetailArray = getResources().getStringArray(R.array.credits_details); + List list = new ArrayList(); + + int len = mHeaderArray.length; + for (int i = 0; i < len; i++) { + License l = new License(LICENSE_TYPE.LICENSE, mHeaderArray[i], mDetailArray[i]); + list.add(l); + + } + return list; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + back(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_BACK: + back(); + return true; + default: + return super.onKeyUp(keyCode, event); + } + } + + + /** + * Method that returns to previous activity and. + */ + private void back() { + Intent intent = new Intent(); + setResult(RESULT_CANCELED, intent); + finish(); + } + + + /** + * Method that applies the current theme to the activity + * @hide + */ + void applyTheme() { + ThemeManager.Theme theme = ThemeManager.getCurrentTheme(this); + theme.setBaseTheme(this, false); + + // -View + theme.setBackgroundDrawable(this, getWindow().getDecorView(), "background_drawable"); //$NON-NLS-1$ + this.mListView.setDivider( + theme.getDrawable(this, "horizontal_divider_drawable")); //$NON-NLS-1$ + this.mListView.invalidate(); + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/activities/preferences/SearchPreferenceFragment.java b/Backbone/src/main/java/me/toolify/backbone/activities/preferences/SearchPreferenceFragment.java new file mode 100644 index 000000000..85f1f8ba7 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/activities/preferences/SearchPreferenceFragment.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.activities.preferences; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceChangeListener; +import android.preference.Preference.OnPreferenceClickListener; +import android.provider.SearchRecentSuggestions; +import android.util.Log; +import android.widget.Toast; + +import me.toolify.backbone.R; +import me.toolify.backbone.preferences.FileManagerSettings; +import me.toolify.backbone.preferences.ObjectStringIdentifier; +import me.toolify.backbone.preferences.Preferences; +import me.toolify.backbone.providers.RecentSearchesContentProvider; +import me.toolify.backbone.util.DialogHelper; + +/** + * A class that manages the search options + */ +public class SearchPreferenceFragment extends TitlePreferenceFragment { + + private static final String TAG = "SearchPreferenceFragment"; //$NON-NLS-1$ + + private static final boolean DEBUG = false; + + // Internal keys + private static final String REMOVE_SEARCH_TERMS_KEY = + "cm_filemanager_remove_saved_search_terms"; //$NON-NLS-1$ + + private CheckBoxPreference mHighlightTerms; + private CheckBoxPreference mShowRelevanceWidget; + private ListPreference mSortSearchResultMode; + private CheckBoxPreference mSaveSearchTerms; + private Preference mRemoveSearchTerms; + + /** + * @hide + */ + boolean mLoaded = false; + + private final OnPreferenceChangeListener mOnChangeListener = + new OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + String key = preference.getKey(); + if (DEBUG) { + Log.d(TAG, + String.format("New value for %s: %s", //$NON-NLS-1$ + key, + String.valueOf(newValue))); + } + + // Saved search terms + if (preference.getKey().compareTo( + FileManagerSettings.SETTINGS_SAVE_SEARCH_TERMS.getId()) == 0) { + if (!((Boolean)newValue).booleanValue()) { + // Remove search terms if saved search terms + // is not active by the user + clearRecentSearchTerms(); + } + + // Sort search result mode + } else if (FileManagerSettings.SETTINGS_SORT_SEARCH_RESULTS_MODE. + getId().compareTo(key) == 0) { + int value = Integer.valueOf((String)newValue).intValue(); + String[] summary = getResources().getStringArray( + R.array.sort_search_results_mode_labels); + preference.setSummary(summary[value]); + } + + // Notify the change (only if fragment is loaded. Default values are loaded + // while not in loaded mode) + if (SearchPreferenceFragment.this.mLoaded) { + Intent intent = new Intent(FileManagerSettings.INTENT_SETTING_CHANGED); + intent.putExtra( + FileManagerSettings.EXTRA_SETTING_CHANGED_KEY, preference.getKey()); + getActivity().sendBroadcast(intent); + } + + return true; + } + }; + + private final OnPreferenceClickListener mOnClickListener = + new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + if (preference.getKey().compareTo(REMOVE_SEARCH_TERMS_KEY) == 0) { + // Remove search terms + clearRecentSearchTerms(); + + // Advise the user + DialogHelper.showToast( + getActivity(), + getActivity().getString(R.string.pref_remove_saved_search_terms_msg), + Toast.LENGTH_SHORT); + } + return false; + } + }; + + /** + * {@inheritDoc} + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Change the preference manager + getPreferenceManager().setSharedPreferencesName(Preferences.SETTINGS_FILENAME); + getPreferenceManager().setSharedPreferencesMode(Context.MODE_PRIVATE); + this.mLoaded = false; + + // Add the preferences + addPreferencesFromResource(R.xml.preferences_search); + + // Highlight terms + this.mHighlightTerms = + (CheckBoxPreference)findPreference( + FileManagerSettings.SETTINGS_HIGHLIGHT_TERMS.getId()); + this.mHighlightTerms.setOnPreferenceChangeListener(this.mOnChangeListener); + + // Relevance widget + this.mShowRelevanceWidget = + (CheckBoxPreference)findPreference( + FileManagerSettings.SETTINGS_SHOW_RELEVANCE_WIDGET.getId()); + this.mShowRelevanceWidget.setOnPreferenceChangeListener(this.mOnChangeListener); + + // Sort search result mode + this.mSortSearchResultMode = + (ListPreference)findPreference( + FileManagerSettings.SETTINGS_SORT_SEARCH_RESULTS_MODE.getId()); + this.mSortSearchResultMode.setOnPreferenceChangeListener(this.mOnChangeListener); + String defaultValue = ((ObjectStringIdentifier)FileManagerSettings. + SETTINGS_SORT_SEARCH_RESULTS_MODE.getDefaultValue()).getId(); + String value = Preferences.getSharedPreferences().getString( + FileManagerSettings.SETTINGS_SORT_SEARCH_RESULTS_MODE.getId(), + defaultValue); + this.mOnChangeListener.onPreferenceChange(this.mSortSearchResultMode, value); + + // Saved search terms + this.mSaveSearchTerms = + (CheckBoxPreference)findPreference( + FileManagerSettings.SETTINGS_SAVE_SEARCH_TERMS.getId()); + this.mSaveSearchTerms.setOnPreferenceChangeListener(this.mOnChangeListener); + + // Remove search terms + this.mRemoveSearchTerms = findPreference(REMOVE_SEARCH_TERMS_KEY); + this.mRemoveSearchTerms.setOnPreferenceClickListener(this.mOnClickListener); + + // Loaded + this.mLoaded = true; + } + + /** + * Method that removes the recent suggestions on search activity + * @hide + */ + void clearRecentSearchTerms() { + SearchRecentSuggestions suggestions = + new SearchRecentSuggestions(getActivity(), + RecentSearchesContentProvider.AUTHORITY, + RecentSearchesContentProvider.MODE); + suggestions.clearHistory(); + Preferences.setLastSearch(null); + } + + /** + * {@inheritDoc} + */ + @Override + public CharSequence getTitle() { + return getString(R.string.pref_search); + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/activities/preferences/SettingsPreferences.java b/Backbone/src/main/java/me/toolify/backbone/activities/preferences/SettingsPreferences.java new file mode 100644 index 000000000..528799e0e --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/activities/preferences/SettingsPreferences.java @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * Copyright (C) 2013 BrandroidTools + * + * 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 me.toolify.backbone.activities.preferences; + +import android.app.Fragment; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.TypedArray; +import android.os.Bundle; +import android.preference.PreferenceActivity; +import android.util.Log; +import android.view.MenuItem; + +import java.util.List; + +import me.toolify.backbone.R; +import me.toolify.backbone.dashclock.DashExtension; +import me.toolify.backbone.dashclock.DashSettings; +import me.toolify.backbone.preferences.FileManagerSettings; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.ui.ThemeManager.Theme; +import me.toolify.backbone.util.AndroidHelper; + +/** + * The {@link SettingsPreferences} preferences + */ +public class SettingsPreferences extends PreferenceActivity { + + private static final String TAG = "SettingsPreferences"; //$NON-NLS-1$ + + private static final boolean DEBUG = false; + + private final BroadcastReceiver mNotificationReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent != null) { + if (intent.getAction().compareTo(FileManagerSettings.INTENT_THEME_CHANGED) == 0) { + finish(); + } + } + } + }; + + /** + * {@inheritDoc} + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + if (DEBUG) { + Log.d(TAG, "SettingsPreferences.onCreate"); //$NON-NLS-1$ + } + + // Register the broadcast receiver + IntentFilter filter = new IntentFilter(); + filter.addAction(FileManagerSettings.INTENT_THEME_CHANGED); + registerReceiver(this.mNotificationReceiver, filter); + + //Initialize action bars + initTitleActionBar(); + + // Apply the theme + applyTheme(); + + super.onCreate(savedInstanceState); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onDestroy() { + if (DEBUG) { + Log.d(TAG, "SettingsPreferences.onDestroy"); //$NON-NLS-1$ + } + + // Unregister the receiver + try { + unregisterReceiver(this.mNotificationReceiver); + } catch (Throwable ex) { + /**NON BLOCK**/ + } + + //All destroy. Continue + super.onDestroy(); + } + + /** + * Method that initializes the titlebar of the activity. + */ + private void initTitleActionBar() { + //Configure the action bar options + getActionBar().setDisplayHomeAsUpEnabled(true); + getActionBar().setTitle(R.string.pref); + } + + /** + * {@inheritDoc} + */ + @Override + public void onBuildHeaders(List
target) { + loadHeadersFromResource(R.xml.preferences_headers, target); + + /* Manually load the header icons, since Android chokes on icons referenced via '?attr/xxx' + which must be done for theming */ + final TypedArray a = getTheme().obtainStyledAttributes(R.styleable.FileManagerPrefs); + + for (Header header : target) { + switch (header.titleRes) { + case R.string.pref_general: + header.iconRes = a.getResourceId(R.styleable.FileManagerPrefs_preferenceIconGeneral, + R.drawable.ic_preference_black_general); + break; + case R.string.pref_search: + header.iconRes = a.getResourceId(R.styleable.FileManagerPrefs_preferenceIconSearch, + R.drawable.ic_preference_black_search); + break; + case R.string.pref_editor: + header.iconRes = a.getResourceId(R.styleable.FileManagerPrefs_preferenceIconEditor, + R.drawable.ic_preference_black_editor); + break; + case R.string.pref_about: + header.iconRes = a.getResourceId(R.styleable.FileManagerPrefs_preferenceIconAbout, + R.drawable.ic_preference_black_about); + break; + } + } + + // Create Dashclock preference if the user has enabled our Dashclock extension + if(DashExtension.isEnabled(this)) { + Header dashHeader = new Header(); + dashHeader.titleRes = R.string.pref_dashclock; + Intent i = new Intent(this, DashSettings.class); + i.putExtra("showLauncherIcon", true); + dashHeader.intent = i; + dashHeader.iconRes = a.getResourceId(R.styleable.FileManagerPrefs_preferenceIconDashclock, + R.drawable.ic_preference_black_dashclock); + target.add(dashHeader); + } + + a.recycle(); + } + + @Override + protected void onResume() { + super.onResume(); + invalidateHeaders(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + finish(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onAttachFragment(Fragment fragment) { + super.onAttachFragment(fragment); + if (!AndroidHelper.isTablet(this) && fragment instanceof TitlePreferenceFragment) { + getActionBar().setTitle(((TitlePreferenceFragment)fragment).getTitle()); + } else { + getActionBar().setTitle(R.string.pref); + } + } + + /** + * Method that applies the current theme to the activity + * @hide + */ + void applyTheme() { + Theme theme = ThemeManager.getCurrentTheme(this); + theme.setBaseTheme(this, false); + + // -View + theme.setBackgroundDrawable( + this, + this.getWindow().getDecorView(), + "background_drawable"); //$NON-NLS-1$ + } + +} diff --git a/Backbone/src/main/java/me/toolify/backbone/activities/preferences/ThemesPreferenceFragment.java b/Backbone/src/main/java/me/toolify/backbone/activities/preferences/ThemesPreferenceFragment.java new file mode 100644 index 000000000..0aeec6f21 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/activities/preferences/ThemesPreferenceFragment.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.activities.preferences; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceChangeListener; +import android.util.Log; + +import me.toolify.backbone.R; +import me.toolify.backbone.preferences.FileManagerSettings; +import me.toolify.backbone.preferences.Preferences; +import me.toolify.backbone.ui.preferences.ThemeSelectorPreference; + +/** + * A class that manages the theme selection + */ +public class ThemesPreferenceFragment extends TitlePreferenceFragment { + + private static final String TAG = "ThemesPreferenceFragment"; //$NON-NLS-1$ + + private static final boolean DEBUG = false; + + private ThemeSelectorPreference mThemeSelector; + + private final OnPreferenceChangeListener mOnChangeListener = + new OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + String key = preference.getKey(); + if (DEBUG) { + Log.d(TAG, + String.format("New value for %s: %s", //$NON-NLS-1$ + key, + String.valueOf(newValue))); + } + + // Notify to all activities that the theme has changed + Intent intent = new Intent(FileManagerSettings.INTENT_THEME_CHANGED); + intent.putExtra(FileManagerSettings.EXTRA_THEME_ID, (String)newValue); + getActivity().sendBroadcast(intent); + + //Wait for allow activities to apply the theme, prior to finish settings + try { + Thread.sleep(250L); + } catch (Throwable e) {/**NON BLOCK**/} + getActivity().finish(); + return true; + } + }; + + /** + * {@inheritDoc} + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Change the preference manager + getPreferenceManager().setSharedPreferencesName(Preferences.SETTINGS_FILENAME); + getPreferenceManager().setSharedPreferencesMode(Context.MODE_PRIVATE); + + // Add the preferences + addPreferencesFromResource(R.xml.preferences_themes); + + // Theme selector + this.mThemeSelector = + (ThemeSelectorPreference)findPreference( + FileManagerSettings.SETTINGS_THEME.getId()); + this.mThemeSelector.setOnPreferenceChangeListener(this.mOnChangeListener); + } + + /** + * {@inheritDoc} + */ + @Override + public CharSequence getTitle() { + return getString(R.string.pref_themes); + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/activities/preferences/TitlePreferenceFragment.java b/Backbone/src/main/java/me/toolify/backbone/activities/preferences/TitlePreferenceFragment.java new file mode 100644 index 000000000..f49589e8a --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/activities/preferences/TitlePreferenceFragment.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.activities.preferences; + +import android.preference.PreferenceFragment; + +/** + * The base class of all preference fragments of the backbone app + */ +public abstract class TitlePreferenceFragment extends PreferenceFragment { + /** + * Method that returns the title of the preference fragment + * + * @return CharSequence The title of the fragment + */ + public abstract CharSequence getTitle(); +} diff --git a/src/com/cyanogenmod/filemanager/adapters/AssociationsAdapter.java b/Backbone/src/main/java/me/toolify/backbone/adapters/AssociationsAdapter.java similarity index 91% rename from src/com/cyanogenmod/filemanager/adapters/AssociationsAdapter.java rename to Backbone/src/main/java/me/toolify/backbone/adapters/AssociationsAdapter.java index 22f6c3c95..9d156396c 100644 --- a/src/com/cyanogenmod/filemanager/adapters/AssociationsAdapter.java +++ b/Backbone/src/main/java/me/toolify/backbone/adapters/AssociationsAdapter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.adapters; +package me.toolify.backbone.adapters; import android.content.Context; import android.content.pm.ResolveInfo; @@ -22,14 +22,15 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.TextView; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.ui.ThemeManager; -import com.cyanogenmod.filemanager.ui.ThemeManager.Theme; +import me.toolify.backbone.R; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.ui.ThemeManager.Theme; import java.util.List; @@ -72,6 +73,7 @@ public DataHolder() { private DataHolder[] mData; + private AdapterView mParent; private final OnItemClickListener mOnItemClickListener; //The resource item layout @@ -86,13 +88,16 @@ public DataHolder() { * Constructor of AssociationsAdapter. * * @param context The current context + * @param parent The adapter view * @param intents The intents info * @param onItemClickListener The listener for listen action clicks */ public AssociationsAdapter( - Context context, List intents, OnItemClickListener onItemClickListener) { + Context context, AdapterView parent, + List intents, OnItemClickListener onItemClickListener) { super(context, RESOURCE_ITEM_NAME, intents); this.mOnItemClickListener = onItemClickListener; + this.mParent = parent; //Do cache of the data for better performance processData(intents); @@ -182,7 +187,7 @@ public View getView(int position, View convertView, ViewGroup parent) { @Override public void onClick(View v) { ViewHolder viewHolder = (ViewHolder)v.getTag(); - this.mOnItemClickListener.onItemClick(null, v, viewHolder.mPosition, v.getId()); + this.mOnItemClickListener.onItemClick(this.mParent, v, viewHolder.mPosition, v.getId()); } } diff --git a/Backbone/src/main/java/me/toolify/backbone/adapters/BookmarksAdapter.java b/Backbone/src/main/java/me/toolify/backbone/adapters/BookmarksAdapter.java new file mode 100644 index 000000000..1268ec3df --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/adapters/BookmarksAdapter.java @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.adapters; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.TextView; + +import me.toolify.backbone.R; +import me.toolify.backbone.model.Bookmark; +import me.toolify.backbone.model.Bookmark.BOOKMARK_TYPE; +import me.toolify.backbone.ui.IconHolder; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.ui.ThemeManager.Theme; +import me.toolify.backbone.util.BookmarksHelper; + +import java.util.List; + +/** + * An implementation of {@link ArrayAdapter} for display bookmarks. + */ +public class BookmarksAdapter extends ArrayAdapter { + + /** + * A class that conforms with the ViewHolder pattern to performance + * the list view rendering. + */ + private static class ViewHolder { + /** + * @hide + */ + public ViewHolder() { + super(); + } + TextView mTvSeparator; + ImageView mTvIcon; + TextView mTvName; + TextView mTvPath; + ImageButton mBtAction; + } + + /** + * A class that holds the full data information. + */ + private static class DataHolder { + /** + * @hide + */ + public DataHolder() { + super(); + } + Drawable mDwIcon; + String mName; + String mPath; + String mCategoryTitle; + Drawable mDwAction; + String mActionCd; + } + + + + private DataHolder[] mData; + private int[] mCellStates; + private IconHolder mIconHolder; + private final OnClickListener mOnActionClickListener; + + // The resource item layout + private static final int RESOURCE_LAYOUT = R.layout.bookmarks_item; + + // The resource of the item separator + private static final int RESOURCE_ITEM_SEPARATOR = R.id.bookmarks_item_separator; + // The resource of the item icon + private static final int RESOURCE_ITEM_ICON = R.id.bookmarks_item_icon; + // The resource of the item name + private static final int RESOURCE_ITEM_NAME = R.id.bookmarks_item_name; + // The resource of the item directory + private static final int RESOURCE_ITEM_PATH = R.id.bookmarks_item_path; + // The resource of the item button action + private static final int RESOURCE_ITEM_ACTION = R.id.bookmarks_item_action; + + // State of ListView item that has never been determined. + private static final int STATE_UNKNOWN = 0; + //State of a ListView item that is sectioned. A sectioned item must display the separator. + private static final int STATE_SECTIONED_CELL = 1; + //State of a ListView item that is not sectioned and therefore does not display the separator. + private static final int STATE_REGULAR_CELL = 2; + + /** + * Constructor of BookmarksAdapter. + * + * @param context The current context + * @param bookmarks The bookmarks + * @param onActionClickListener The listener for listen action clicks + */ + public BookmarksAdapter( + Context context, List bookmarks, OnClickListener onActionClickListener) { + super(context, RESOURCE_ITEM_NAME, bookmarks); + this.mIconHolder = new IconHolder(context); + this.mOnActionClickListener = onActionClickListener; + + //Do cache of the data for better performance + processData(bookmarks); + } + + /** + * {@inheritDoc} + */ + @Override + public void notifyDataSetChanged() { + processData(null); + super.notifyDataSetChanged(); + } + + /** + * Method that dispose the elements of the adapter. + */ + public void dispose() { + clear(); + this.mData = null; + if (mIconHolder != null) { + mIconHolder.cleanup(); + mIconHolder = null; + } + } + + /** + * Method that process the data before use {@link #getView} method. + * + * @param bookmarks The list of bookmarks (to better performance) or null. + */ + private void processData(List bookmarks) { + this.mData = new DataHolder[getCount()]; + this.mCellStates = new int[getCount()]; + int cc = (bookmarks == null) ? getCount() : bookmarks.size(); + for (int i = 0; i < cc; i++) { + //Bookmark info + Bookmark bookmark = (bookmarks == null) ? getItem(i) : bookmarks.get(i); + + //Build the data holder + this.mData[i] = new BookmarksAdapter.DataHolder(); + this.mData[i].mDwIcon = + this.mIconHolder.getDrawable(BookmarksHelper.getIcon(bookmark)); + this.mData[i].mName = bookmark.mName; + this.mData[i].mPath = bookmark.mPath; + switch (bookmark.mCategory) { + case LOCATIONS: + this.mData[i].mCategoryTitle = getContext().getString(R.string.bookmarks_header_locations); + break; + case USER_BOOKMARKS: + this.mData[i].mCategoryTitle = getContext().getString(R.string.bookmarks_header_user_bookmarks); + break; + case CLOUD: + this.mData[i].mCategoryTitle = getContext().getString(R.string.bookmarks_header_cloud); + break; + } + this.mData[i].mDwAction = null; + this.mData[i].mActionCd = null; + if (bookmark.mType.compareTo(BOOKMARK_TYPE.HOME) == 0) { + this.mData[i].mDwAction = + this.mIconHolder.getDrawable("ic_config_drawable"); //$NON-NLS-1$ + this.mData[i].mActionCd = + getContext().getString(R.string.bookmarks_button_config_cd); + } else if (bookmark.mType.compareTo(BOOKMARK_TYPE.USER_DEFINED) == 0) { + this.mData[i].mDwAction = + this.mIconHolder.getDrawable("ic_close_drawable"); //$NON-NLS-1$ + this.mData[i].mActionCd = + getContext().getString(R.string.bookmarks_button_remove_bookmark_cd); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public View getView(int position, View convertView, ViewGroup parent) { + + // Separator List View Header + boolean needSeparator = false; + switch (mCellStates[position]) { + case STATE_SECTIONED_CELL: + needSeparator = true; + break; + + case STATE_REGULAR_CELL: + needSeparator = false; + break; + + case STATE_UNKNOWN: + default: + // A separator is needed if it's the first itemview of the + // ListView or if the group of the current cell is different + // from the previous itemview. + if (position == 0) { + needSeparator = true; + } else { + // Test to see if the category has changed since the last bookmark + if (getItem(position).mCategory != null && getItem(position-1).mCategory != null && + getItem(position).mCategory != getItem(position-1).mCategory) { + needSeparator = true; + } + } + + // Cache the result + mCellStates[position] = needSeparator ? STATE_SECTIONED_CELL : STATE_REGULAR_CELL; + break; + } + + //Check to reuse view + View v = convertView; + if (v == null) { + //Create the view holder + LayoutInflater li = + (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + v = li.inflate(RESOURCE_LAYOUT, parent, false); + ViewHolder viewHolder = new BookmarksAdapter.ViewHolder(); + viewHolder.mTvSeparator = (TextView)v.findViewById(RESOURCE_ITEM_SEPARATOR); + viewHolder.mTvIcon = (ImageView)v.findViewById(RESOURCE_ITEM_ICON); + viewHolder.mTvName = (TextView)v.findViewById(RESOURCE_ITEM_NAME); + viewHolder.mTvPath = (TextView)v.findViewById(RESOURCE_ITEM_PATH); + viewHolder.mBtAction = (ImageButton)v.findViewById(RESOURCE_ITEM_ACTION); + viewHolder.mBtAction.setTag(Integer.valueOf(position)); + v.setTag(viewHolder); + + // Apply the current theme + Theme theme = ThemeManager.getCurrentTheme(getContext()); + theme.setBackgroundDrawable( + getContext(), v, "selectors_deselected_drawable"); //$NON-NLS-1$ +/* Legacy CM theme code. We should be getting this stuff exclusively + from the layout, because this is a nightmare to keep orderly. + theme.setTextColor( + getContext(), viewHolder.mTvName, "nav_drawer_text_color"); //$NON-NLS-1$ + theme.setTextColor( + getContext(), viewHolder.mTvPath, "nav_drawer_text_color"); //$NON-NLS-1$*/ + } + + //Retrieve data holder + final DataHolder dataHolder = this.mData[position]; + + //Retrieve the view holder + ViewHolder viewHolder = (ViewHolder)v.getTag(); + + //Set the data + if (needSeparator) { + viewHolder.mTvSeparator.setText(dataHolder.mCategoryTitle); + viewHolder.mTvSeparator.setVisibility(View.VISIBLE); + } else { + viewHolder.mTvSeparator.setVisibility(View.GONE); + } + viewHolder.mTvIcon.setImageDrawable(dataHolder.mDwIcon); + viewHolder.mTvName.setText(dataHolder.mName); + viewHolder.mTvPath.setText(dataHolder.mPath); + boolean hasAction = dataHolder.mDwAction != null; + viewHolder.mBtAction.setImageDrawable(hasAction ? dataHolder.mDwAction : null); + viewHolder.mBtAction.setVisibility(hasAction ? View.VISIBLE : View.GONE); + viewHolder.mBtAction.setOnClickListener(this.mOnActionClickListener); + viewHolder.mBtAction.setContentDescription(dataHolder.mActionCd); + + //Return the view + return v; + } + + /** + * Method that should be invoked when the theme of the app was changed + */ + public void notifyThemeChanged() { + if (mIconHolder != null) { + mIconHolder.cleanup(); + } + // Empty icon holder + this.mIconHolder = new IconHolder(getContext()); + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/adapters/BreadcrumbSpinnerAdapter.java b/Backbone/src/main/java/me/toolify/backbone/adapters/BreadcrumbSpinnerAdapter.java new file mode 100644 index 000000000..fc8f82a2d --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/adapters/BreadcrumbSpinnerAdapter.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2013 BrandroidTools + * + * 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 me.toolify.backbone.adapters; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.RelativeLayout; +import android.widget.SpinnerAdapter; +import android.widget.TextView; + +import java.io.File; +import java.util.ArrayList; + +import me.toolify.backbone.R; +import me.toolify.backbone.util.FileHelper; + +public class BreadcrumbSpinnerAdapter extends BaseAdapter implements SpinnerAdapter{ + + private Context mContext; + private ArrayList fileList; + + public BreadcrumbSpinnerAdapter(Context context, ArrayList fileList) { + this.mContext = context; + this.fileList = fileList; + } + + /** + * {@inheritDoc} + */ + @Override + public int getCount() { + return fileList.size(); + } + + /** + * {@inheritDoc} + */ + @Override + public Object getItem(int position) { + return fileList.get(position); + } + + /** + * {@inheritDoc} + */ + @Override + public long getItemId(int position) { + return position; + } + + /** + * {@inheritDoc} + */ + @Override + public View getView(int position, View convertView, ViewGroup parent) { + RelativeLayout row = (RelativeLayout) View.inflate(mContext, R.layout.breadcrumb_spinner_selected_item, null); + TextView title = (TextView)row.findViewById(R.id.breadcrumb_spinner_item_title); + TextView subtitle = (TextView)row.findViewById(R.id.breadcrumb_spinner_item_subtitle); + title.setText(buildTitleString(fileList.get(position))); + subtitle.setText(buildSubtitleString(fileList.get(position))); + return row; + } + + /** + * {@inheritDoc} + */ + @Override + public View getDropDownView(int position, View convertView, ViewGroup parent) { + TextView textView = (TextView) View.inflate(mContext, R.layout.breadcrumb_spinner_dropdown_item, null); + textView.setText(buildTitleString(fileList.get(position))); + return textView; + } + + private String buildTitleString(File file) { + String fileString; + if (file.compareTo(new File(FileHelper.ROOT_DIRECTORY)) == 0){ + fileString = "/"; + } else { + fileString = file.getName(); + } + return fileString; + } + + private String buildSubtitleString(File file) { + String fileString; + if (file.compareTo(new File(FileHelper.ROOT_DIRECTORY)) == 0){ + fileString = "Filesystem Root"; + } else { + fileString = file.getParent(); + } + return fileString; + } +} diff --git a/src/com/cyanogenmod/filemanager/adapters/CheckableListAdapter.java b/Backbone/src/main/java/me/toolify/backbone/adapters/CheckableListAdapter.java similarity index 96% rename from src/com/cyanogenmod/filemanager/adapters/CheckableListAdapter.java rename to Backbone/src/main/java/me/toolify/backbone/adapters/CheckableListAdapter.java index be28711f2..5c3ea0d87 100644 --- a/src/com/cyanogenmod/filemanager/adapters/CheckableListAdapter.java +++ b/Backbone/src/main/java/me/toolify/backbone/adapters/CheckableListAdapter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.adapters; +package me.toolify.backbone.adapters; import android.content.Context; import android.view.LayoutInflater; @@ -24,9 +24,9 @@ import android.widget.ImageView; import android.widget.TextView; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.ui.ThemeManager; -import com.cyanogenmod.filemanager.ui.ThemeManager.Theme; +import me.toolify.backbone.R; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.ui.ThemeManager.Theme; import java.util.List; diff --git a/Backbone/src/main/java/me/toolify/backbone/adapters/FileSystemObjectAdapter.java b/Backbone/src/main/java/me/toolify/backbone/adapters/FileSystemObjectAdapter.java new file mode 100644 index 000000000..3b2ac60ba --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/adapters/FileSystemObjectAdapter.java @@ -0,0 +1,574 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.adapters; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +import de.greenrobot.event.EventBus; +import me.toolify.backbone.R; +import me.toolify.backbone.bus.events.OpenPropertiesDrawerEvent; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.model.ParentDirectory; +import me.toolify.backbone.preferences.FileManagerSettings; +import me.toolify.backbone.preferences.Preferences; +import me.toolify.backbone.ui.IconHolder; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.ui.ThemeManager.Theme; +import me.toolify.backbone.ui.image.ImageFetcher; +import me.toolify.backbone.util.FileHelper; +import me.toolify.backbone.util.MimeTypeHelper; +import me.toolify.backbone.util.MimeTypeHelper.MimeTypeCategory; + +/** + * An implementation of {@link ArrayAdapter} for display file system objects. + */ +public class FileSystemObjectAdapter + extends ArrayAdapter implements OnClickListener { + + /** + * An interface to communicate selection changes events. + */ + public interface OnSelectionChangedListener { + /** + * Method invoked when the selection changed. + * + * @param selectedItems The new selected items + */ + void onSelectionChanged(List selectedItems); + } + + /** + * A class that conforms with the ViewHolder pattern to performance + * the list view rendering. + */ + private static class ViewHolder { + /** + * @hide + */ + public ViewHolder() { + super(); + } + ImageButton mBtInfo; + ImageView mBtIcon; + TextView mTvName; + TextView mTvSummary; + TextView mTvSize; + Boolean mHasSelectedBg; + } + + /** + * A class that holds the full data information. + */ + private static class DataHolder { + /** + * @hide + */ + public DataHolder() { + super(); + } + boolean mSelected; + Drawable mDwCheck; + Drawable mDwIcon; + String mName; + String mSummary; + String mSize; + String mImagePath; + boolean mDynamic; + } + + + private DataHolder[] mData; + private IconHolder mIconHolder; + private final int mItemViewResourceId; + private List mSelectedItems; + private final boolean mPickable; + private ImageFetcher mImageFetcher; + private boolean displayThumbs; + + private OnSelectionChangedListener mOnSelectionChangedListener; + + private boolean mDisposed; + + //The resource of the item info button + private static final int RESOURCE_ITEM_INFO = R.id.navigation_view_item_info; + //The resource of the item icon + private static final int RESOURCE_ITEM_ICON = R.id.navigation_view_item_icon; + //The resource of the item name + private static final int RESOURCE_ITEM_NAME = R.id.navigation_view_item_name; + //The resource of the item summary information + private static final int RESOURCE_ITEM_SUMMARY = R.id.navigation_view_item_summary; + //The resource of the item size information + private static final int RESOURCE_ITEM_SIZE = R.id.navigation_view_item_size; + + /** + * Constructor of FileSystemObjectAdapter. + * + * @param context The current context + * @param files The list of file system objects + * @param itemViewResourceId The identifier of the layout that represents an item + * of the list adapter + * @param pickable If the adapter should act as a pickable browser. + */ + public FileSystemObjectAdapter( + Context context, List files, + int itemViewResourceId, boolean pickable, ImageFetcher imageFetcher) { + super(context, RESOURCE_ITEM_NAME, files); + this.mDisposed = false; + this.mItemViewResourceId = itemViewResourceId; + this.mSelectedItems = new ArrayList(); + this.mPickable = pickable; + this.mImageFetcher = imageFetcher; + notifyThemeChanged(); // Reload icons + + processData(); + } + + /** + * Method that sets the listener which communicates selection changes. + * + * @param onSelectionChangedListener The listener reference + */ + public void setOnSelectionChangedListener( + OnSelectionChangedListener onSelectionChangedListener) { + this.mOnSelectionChangedListener = onSelectionChangedListener; + } + + /** + * Method that loads the default icons (known icons and more common icons). + */ + private void loadDefaultIcons() { + this.mIconHolder.getDrawable("ic_fso_folder_drawable"); //$NON-NLS-1$ + this.mIconHolder.getDrawable("ic_fso_default_drawable"); //$NON-NLS-1$ + } + + /** + * {@inheritDoc} + */ + @Override + public void notifyDataSetChanged() { + if (this.mDisposed) { + return; + } + processData(); + super.notifyDataSetChanged(); + } + + /** + * Method that dispose the elements of the adapter. + */ + public void dispose() { + this.mDisposed = true; + clear(); + this.mData = null; + if (mIconHolder != null) { + mIconHolder.cleanup(); + mIconHolder = null; + } + this.mSelectedItems.clear(); + } + + /** + * Method that returns the {@link FileSystemObject} reference from his path. + * + * @param path The path of the file system object + * @return FileSystemObject The file system object reference + */ + public FileSystemObject getItem(String path) { + int cc = getCount(); + for (int i = 0; i < cc; i++) { + //File system object info + FileSystemObject fso = getItem(i); + if (fso.getFullPath().compareTo(path) == 0) { + return fso; + } + } + return null; + } + + /** + * Method that process the data before use {@link #getView} method. + */ + private void processData() { + Theme theme = ThemeManager.getCurrentTheme(getContext()); + Resources res = getContext().getResources(); + int cc = getCount(); + + this.mData = new DataHolder[cc]; + + for (int i = 0; i < cc; i++) { + //File system object info + FileSystemObject fso = getItem(i); + + //Parse the last modification time and permissions + StringBuilder sbSummary = new StringBuilder(); + if (fso instanceof ParentDirectory) { + sbSummary.append(res.getString(R.string.parent_dir)); + } else { + sbSummary.append( + FileHelper.formatFileTime( + getContext(), fso.getLastModifiedTime())); + sbSummary.append(" "); //$NON-NLS-1$ + sbSummary.append(fso.toRawPermissionString()); + } + + //Build the data holder + this.mData[i] = new FileSystemObjectAdapter.DataHolder(); + this.mData[i].mSelected = this.mSelectedItems.contains(fso); + this.mData[i].mDynamic = MimeTypeHelper.getIsDynamic(getContext(), fso); + this.mData[i].mImagePath = null; + if (this.mData[i].mDynamic) { + // Produce specific icon for file (e.g. apk or image thumbnail) and store it + if (FileHelper.getExtension(fso).equals("apk")) { + this.mData[i].mImagePath = fso.getFullPath(); + } else if (MimeTypeHelper.getCategory(getContext(), fso) == MimeTypeCategory.IMAGE) { + // Gather image file path for lazy loading + this.mData[i].mImagePath = fso.getFullPath(); + } else { + // Icon is marked as dynamic in mimetypes.properties but wasn't handled above + this.mData[i].mDwIcon = this.mIconHolder.getDrawable("ic_holo_dark_fs_warning"); + } + } else { + // Display icon according to mimetype + this.mData[i].mDwIcon = this.mIconHolder.getDrawable( + MimeTypeHelper.getIcon(getContext(), fso)); + } + this.mData[i].mName = fso.getName(); + this.mData[i].mSummary = sbSummary.toString(); + this.mData[i].mSize = FileHelper.getHumanReadableSize(fso); + } + } + + /** + * {@inheritDoc} + */ + @Override + public View getView(int position, View convertView, ViewGroup parent) { + //Check to reuse view + View v = convertView; + Theme theme = ThemeManager.getCurrentTheme(getContext()); + + if (v == null) { + //Create the view holder + LayoutInflater li = + (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + v = li.inflate(this.mItemViewResourceId, parent, false); + ViewHolder viewHolder = new FileSystemObjectAdapter.ViewHolder(); + viewHolder.mBtIcon = (ImageButton)v.findViewById(RESOURCE_ITEM_ICON); + viewHolder.mTvName = (TextView)v.findViewById(RESOURCE_ITEM_NAME); + viewHolder.mTvSummary = (TextView)v.findViewById(RESOURCE_ITEM_SUMMARY); + viewHolder.mTvSize = (TextView)v.findViewById(RESOURCE_ITEM_SIZE); + if (!this.mPickable) { + viewHolder.mBtInfo = (ImageButton)v.findViewById(RESOURCE_ITEM_INFO); + viewHolder.mBtInfo.setOnClickListener(this); + } else { + viewHolder.mBtInfo = (ImageButton)v.findViewById(RESOURCE_ITEM_INFO); + viewHolder.mBtInfo.setVisibility(View.GONE); + } + v.setTag(viewHolder); + } + + //Retrieve data holder + final DataHolder dataHolder = this.mData[position]; + + //Retrieve the view holder + ViewHolder viewHolder = (ViewHolder)v.getTag(); + + //Set the data + //Gather image thumbnail or generate apk icon if it hasn't been generated yet + if (displayThumbs && this.mData[position].mImagePath != null && !this.mData[position].mImagePath.isEmpty()) { + viewHolder.mBtIcon.setScaleType(ImageView.ScaleType.CENTER_CROP); + RelativeLayout.LayoutParams lp = ((RelativeLayout.LayoutParams)viewHolder.mBtIcon.getLayoutParams()); + if(lp != null) + { + lp.setMargins(0, 0, 0, 0); + viewHolder.mBtIcon.setLayoutParams(lp); + } + mImageFetcher.loadImage(this.mData[position].mImagePath, viewHolder.mBtIcon); + } else { + viewHolder.mBtIcon.setImageDrawable(dataHolder.mDwIcon); + } + viewHolder.mTvName.setText(dataHolder.mName); + if (viewHolder.mTvSummary != null) { + viewHolder.mTvSummary.setText(dataHolder.mSummary); + } + if (viewHolder.mTvSize != null) { + viewHolder.mTvSize.setText(dataHolder.mSize); + } + if (!this.mPickable) { + viewHolder.mBtInfo.setVisibility( + dataHolder.mName.compareTo( + FileHelper.PARENT_DIRECTORY) == 0 ? View.INVISIBLE : View.VISIBLE); + viewHolder.mBtIcon.setOnClickListener(this); + viewHolder.mBtIcon.setTag(Integer.valueOf(position)); + viewHolder.mBtInfo.setTag(Integer.valueOf(position)); + + if (viewHolder.mHasSelectedBg == null + || viewHolder.mHasSelectedBg != dataHolder.mSelected) { + String drawableId = dataHolder.mSelected + ? "selectors_selected_drawable" //$NON-NLS-1$ + : "selectors_deselected_drawable"; //$NON-NLS-1$ + + theme.setBackgroundDrawable(getContext(), v, drawableId); + viewHolder.mHasSelectedBg = dataHolder.mSelected; + } + } + + //Return the view + return v; + } + + /** + * Method that returns if the item of the passed position is selected. + * + * @param position The position of the item + * @return boolean If the item of the passed position is selected + */ + public boolean isSelected(int position) { + return this.mData[position].mSelected; + } + + /** + * Method that selects in the {@link ArrayAdapter} the passed item. + * + * @param fso The file system object to select + */ + public void toggleSelection(FileSystemObject fso) { + toggleSelection(null, fso); + } + + /** + * Method that selects in the {@link ArrayAdapter} the passed item. + * + * @param v The check view object (can be null) + * @param fso The file system object to select + */ + private void toggleSelection(View v, FileSystemObject fso) { + if (this.mData != null) { + Theme theme = ThemeManager.getCurrentTheme(getContext()); + int cc = this.mData.length; + for (int i = 0; i < cc; i++) { + DataHolder data = this.mData[i]; + if (data.mName.compareTo(fso.getName()) == 0) { + //Select/Deselect the item + data.mSelected = !data.mSelected; + if (v != null) { + View viewParent = (View)v.getParent().getParent(); + + viewParent.setSelected(data.mSelected); + + if (data.mSelected) { + theme.setBackgroundDrawable( + getContext(), + viewParent, + "selectors_selected_drawable"); //$NON-NLS-1$ + } else { + theme.setBackgroundDrawable( + getContext(), + viewParent, + "selectors_deselected_drawable"); //$NON-NLS-1$ + } + } + + //Add or remove from the global selected items + final List selectedItems = + FileSystemObjectAdapter.this.mSelectedItems; + if (data.mSelected) { + if (!selectedItems.contains(fso)) { + selectedItems.add(fso); + } + } else { + if (selectedItems.contains(fso)) { + selectedItems.remove(fso); + } + } + + //Communicate event + if (this.mOnSelectionChangedListener != null) { + List selection = + new ArrayList(selectedItems); + this.mOnSelectionChangedListener.onSelectionChanged(selection); + } + + // The internal structure was update, only super adapter need to be notified + super.notifyDataSetChanged(); + + //Found + return; + } + } + } + } + + /** + * Method that deselect all items. + */ + public void deselectedAll() { + this.mSelectedItems.clear(); + doSelectDeselectAllVisibleItems(false); + } + + /** + * Method that select all visible items. + */ + public void selectedAllVisibleItems() { + doSelectDeselectAllVisibleItems(true); + } + + /** + * Method that deselect all visible items. + */ + public void deselectedAllVisibleItems() { + doSelectDeselectAllVisibleItems(false); + } + + /** + * Method that select/deselect all items. + * + * @param select Indicates if select (true) or deselect (false) all items. + */ + private void doSelectDeselectAllVisibleItems(boolean select) { + if (this.mData != null && this.mData.length > 0) { + // Clear mSelectedItems. Both deselect all and select all require a blank slate. + FileSystemObjectAdapter.this.mSelectedItems.clear(); + + Theme theme = ThemeManager.getCurrentTheme(getContext()); + int cc = this.mData.length; + for (int i = 0; i < cc; i++) { + DataHolder data = this.mData[i]; + if (data.mName.compareTo(FileHelper.PARENT_DIRECTORY) == 0) { + // No select the parent directory + continue; + } + data.mSelected = select; + if (data.mSelected) { + data.mDwCheck = + theme.getDrawable( + getContext(), "checkbox_selected_drawable"); //$NON-NLS-1$ + } else { + data.mDwCheck = + theme.getDrawable( + getContext(), "checkbox_deselected_drawable"); //$NON-NLS-1$ + } + + //Add or remove from the global selected items + FileSystemObject fso = getItem(i); + final List selectedItems = + FileSystemObjectAdapter.this.mSelectedItems; + if (data.mSelected) { + if (!selectedItems.contains(fso)) { + selectedItems.add(fso); + } + } else { + if (selectedItems.contains(fso)) { + selectedItems.remove(fso); + } + } + } + + //Communicate event + if (this.mOnSelectionChangedListener != null) { + List selection = + new ArrayList( + FileSystemObjectAdapter.this.mSelectedItems); + this.mOnSelectionChangedListener.onSelectionChanged(selection); + } + + // The internal structure was update, only super adapter need to be notified + super.notifyDataSetChanged(); + } + } + + /** + * Method that returns the selected items. + * + * @return List The selected items + */ + public List getSelectedItems() { + return new ArrayList(this.mSelectedItems); + } + + /** + * Method that sets the selected items. + * + * @param selectedItems The selected items + */ + public void setSelectedItems(List selectedItems) { + this.mSelectedItems = selectedItems; + } + + public int getSelectedItemsCount() { + return mSelectedItems.size(); + } + + /** + * {@inheritDoc} + */ + @Override + public void onClick(View v) { + + //Select or deselect the item + int pos = ((Integer)v.getTag()).intValue(); + + //Retrieve data holder + final FileSystemObject fso = getItem(pos); + + //What button was pressed? + switch (v.getId()) { + case RESOURCE_ITEM_ICON: + // If we're not dealing with a parent folder item, toggle it + if (fso.getName().compareTo(FileHelper.PARENT_DIRECTORY) != 0){ + toggleSelection(v, fso); + } + break; + case RESOURCE_ITEM_INFO: + EventBus.getDefault().post(new OpenPropertiesDrawerEvent(fso)); + break; + default: + break; + } + } + + /** + * Method that should be invoked when the theme of the app was changed + */ + public void notifyThemeChanged() { + // Empty icon holder + if (this.mIconHolder != null) { + this.mIconHolder.cleanup(); + } + displayThumbs = Preferences.getSharedPreferences().getBoolean( + FileManagerSettings.SETTINGS_DISPLAY_THUMBS.getId(), + ((Boolean)FileManagerSettings.SETTINGS_DISPLAY_THUMBS.getDefaultValue()).booleanValue()); + this.mIconHolder = new IconHolder(getContext()); + loadDefaultIcons(); + } + +} diff --git a/src/com/cyanogenmod/filemanager/adapters/HistoryAdapter.java b/Backbone/src/main/java/me/toolify/backbone/adapters/HistoryAdapter.java similarity index 87% rename from src/com/cyanogenmod/filemanager/adapters/HistoryAdapter.java rename to Backbone/src/main/java/me/toolify/backbone/adapters/HistoryAdapter.java index 4582410f6..4b1fcda91 100644 --- a/src/com/cyanogenmod/filemanager/adapters/HistoryAdapter.java +++ b/Backbone/src/main/java/me/toolify/backbone/adapters/HistoryAdapter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.adapters; +package me.toolify.backbone.adapters; import android.content.Context; import android.graphics.drawable.Drawable; @@ -25,13 +25,13 @@ import android.widget.ImageView; import android.widget.TextView; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.model.History; -import com.cyanogenmod.filemanager.parcelables.NavigationViewInfoParcelable; -import com.cyanogenmod.filemanager.parcelables.SearchInfoParcelable; -import com.cyanogenmod.filemanager.ui.IconHolder; -import com.cyanogenmod.filemanager.ui.ThemeManager; -import com.cyanogenmod.filemanager.ui.ThemeManager.Theme; +import me.toolify.backbone.R; +import me.toolify.backbone.model.History; +import me.toolify.backbone.parcelables.NavigationViewInfoParcelable; +import me.toolify.backbone.parcelables.SearchInfoParcelable; +import me.toolify.backbone.ui.IconHolder; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.ui.ThemeManager.Theme; import java.util.List; @@ -98,7 +98,7 @@ public DataHolder() { */ public HistoryAdapter(Context context, List history) { super(context, RESOURCE_ITEM_NAME, history); - this.mIconHolder = new IconHolder(); + notifyThemeChanged(); // Reload icons //Do cache of the data for better performance processData(history); @@ -119,7 +119,10 @@ public void notifyDataSetChanged() { public void dispose() { clear(); this.mData = null; - this.mIconHolder = null; + if (mIconHolder != null) { + mIconHolder.cleanup(); + mIconHolder = null; + } } /** @@ -138,12 +141,10 @@ private void processData(List historyData) { this.mData[i] = new HistoryAdapter.DataHolder(); if (history.getItem() instanceof NavigationViewInfoParcelable) { this.mData[i].mDwIcon = - this.mIconHolder.getDrawable( - getContext(), "ic_fso_folder_drawable"); //$NON-NLS-1$ + this.mIconHolder.getDrawable("ic_fso_folder_drawable"); //$NON-NLS-1$ } else if (history.getItem() instanceof SearchInfoParcelable) { this.mData[i].mDwIcon = - this.mIconHolder.getDrawable( - getContext(), "ic_history_search_drawable"); //$NON-NLS-1$ + this.mIconHolder.getDrawable("ic_history_search_drawable"); //$NON-NLS-1$ } this.mData[i].mName = history.getItem().getTitle(); if (this.mData[i].mName == null || this.mData[i].mName.trim().length() == 0) { @@ -207,8 +208,11 @@ public View getView(int position, View convertView, ViewGroup parent) { * Method that should be invoked when the theme of the app was changed */ public void notifyThemeChanged() { - // Empty icon holder - this.mIconHolder = new IconHolder(); + if (mIconHolder != null) { + mIconHolder.cleanup(); + } + // Empty icon holder (only have folders and search icons) + this.mIconHolder = new IconHolder(getContext()); } } diff --git a/Backbone/src/main/java/me/toolify/backbone/adapters/LicenseAdapter.java b/Backbone/src/main/java/me/toolify/backbone/adapters/LicenseAdapter.java new file mode 100644 index 000000000..ad1263050 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/adapters/LicenseAdapter.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.adapters; + +import android.content.Context; +import android.text.Html; +import android.text.method.LinkMovementMethod; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; +import me.toolify.backbone.R; +import me.toolify.backbone.model.License; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.ui.ThemeManager.Theme; + +import java.util.List; + +/** + * An implementation of {@link android.widget.ArrayAdapter} for display history. + */ +public class LicenseAdapter extends ArrayAdapter { + + /** + * A class that conforms with the ViewHolder pattern to performance + * the list view rendering. + */ + private static class ViewHolder { + /** + * @hide + */ + public ViewHolder() { + super(); + } + TextView mTvHeader; + TextView mTvDetail; + } + + /** + * A class that holds the full data information. + */ + private static class DataHolder { + /** + * @hide + */ + public DataHolder() { + super(); + } + String mHeader; + String mDetail; + } + + private DataHolder[] mData; + + //The resource item layout + private static final int RESOURCE_LAYOUT = R.layout.license_item; + + //The resource of the item name + private static final int RESOURCE_ITEM_HEADER = R.id.license_header; + //The resource of the item directory + private static final int RESOURCE_ITEM_DETAIL = R.id.license_details; + + /** + * Constructor of HistoryAdapter. + * + * @param context The current context + * @param licenses The license list reference + */ + public LicenseAdapter(Context context, List licenses) { + super(context, RESOURCE_ITEM_HEADER, licenses); + + //Do cache of the data for better performance + processData(licenses); + } + + /** + * {@inheritDoc} + */ + @Override + public void notifyDataSetChanged() { + processData(null); + super.notifyDataSetChanged(); + } + + /** + * Method that dispose the elements of the adapter. + */ + public void dispose() { + clear(); + this.mData = null; + } + + /** + * Method that process the data before use {@link #getView} method. + * + * @param licenseData The list of licenses (to better performance) or null. + */ + private void processData(List licenseData) { + this.mData = new DataHolder[getCount()]; + int cc = (licenseData == null) ? getCount() : licenseData.size(); + for (int i = 0; i < cc; i++) { + //History info + License license = (licenseData == null) ? getItem(i) : licenseData.get(i); + + //Build the data holder + this.mData[i] = new LicenseAdapter.DataHolder(); + this.mData[i].mHeader = license.mHeader; + this.mData[i].mDetail = license.mDetail; + } + } + + /** + * {@inheritDoc} + */ + @Override + public View getView(int position, View convertView, ViewGroup parent) { + + //Check to reuse view + View v = convertView; + if (v == null) { + //Create the view holder + LayoutInflater li = + (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + v = li.inflate(RESOURCE_LAYOUT, parent, false); + ViewHolder viewHolder = new LicenseAdapter.ViewHolder(); + viewHolder.mTvHeader = (TextView)v.findViewById(RESOURCE_ITEM_HEADER); + viewHolder.mTvDetail = (TextView)v.findViewById(RESOURCE_ITEM_DETAIL); + v.setTag(viewHolder); + + // Apply the current theme + Theme theme = ThemeManager.getCurrentTheme(getContext()); + theme.setBackgroundDrawable( + getContext(), v, "selectors_deselected_drawable"); //$NON-NLS-1$ + theme.setTextColor( + getContext(), viewHolder.mTvHeader, "text_color"); //$NON-NLS-1$ + theme.setTextColor( + getContext(), viewHolder.mTvDetail, "text_color"); //$NON-NLS-1$ + } + + //Retrieve data holder + final DataHolder dataHolder = this.mData[position]; + + //Retrieve the view holder + ViewHolder viewHolder = (ViewHolder)v.getTag(); + + //Set the data + viewHolder.mTvHeader.setText(Html.fromHtml(dataHolder.mHeader)); + viewHolder.mTvHeader.setMovementMethod(LinkMovementMethod.getInstance()); + viewHolder.mTvDetail.setText(Html.fromHtml(dataHolder.mDetail)); + viewHolder.mTvDetail.setMovementMethod(LinkMovementMethod.getInstance()); + + //Return the view + return v; + } + + /** + * Method that should be invoked when the theme of the app was changed + */ + public void notifyThemeChanged() { + + } + +} diff --git a/src/com/cyanogenmod/filemanager/adapters/MenuSettingsAdapter.java b/Backbone/src/main/java/me/toolify/backbone/adapters/MenuSettingsAdapter.java similarity index 95% rename from src/com/cyanogenmod/filemanager/adapters/MenuSettingsAdapter.java rename to Backbone/src/main/java/me/toolify/backbone/adapters/MenuSettingsAdapter.java index b9bc1b6ae..6fdd496c6 100644 --- a/src/com/cyanogenmod/filemanager/adapters/MenuSettingsAdapter.java +++ b/Backbone/src/main/java/me/toolify/backbone/adapters/MenuSettingsAdapter.java @@ -14,16 +14,16 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.adapters; +package me.toolify.backbone.adapters; import android.content.Context; import android.content.res.Resources; -import com.cyanogenmod.filemanager.preferences.FileManagerSettings; -import com.cyanogenmod.filemanager.preferences.ObjectIdentifier; -import com.cyanogenmod.filemanager.preferences.ObjectStringIdentifier; -import com.cyanogenmod.filemanager.preferences.Preferences; -import com.cyanogenmod.filemanager.util.ResourcesHelper; +import me.toolify.backbone.preferences.FileManagerSettings; +import me.toolify.backbone.preferences.ObjectIdentifier; +import me.toolify.backbone.preferences.ObjectStringIdentifier; +import me.toolify.backbone.preferences.Preferences; +import me.toolify.backbone.util.ResourcesHelper; import java.lang.reflect.Method; import java.util.ArrayList; diff --git a/Backbone/src/main/java/me/toolify/backbone/adapters/NavigationFragmentPagerAdapter.java b/Backbone/src/main/java/me/toolify/backbone/adapters/NavigationFragmentPagerAdapter.java new file mode 100644 index 000000000..a13274fad --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/adapters/NavigationFragmentPagerAdapter.java @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * Copyright (C) 2013 BrandroidTools + * + * 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 me.toolify.backbone.adapters; + +import android.content.Context; +import android.os.Parcelable; +import android.annotation.TargetApi; +import android.app.Fragment; +import android.app.FragmentManager; +import android.app.FragmentTransaction; +import android.support.v4.view.PagerAdapter; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; + +import me.toolify.backbone.FileManagerApplication; +import me.toolify.backbone.fragments.NavigationFragment; + +/** + * Implementation of {@link android.support.v4.view.PagerAdapter} that + * represents each page as a {@link NavigationFragment} that is persistently kept in the + * fragment manager as long as the user can return to the page. + * + * + */ +public class NavigationFragmentPagerAdapter extends PagerAdapter { + private static final String TAG = "FragmentPagerAdapter"; + private static final boolean DEBUG = false; + + private final FragmentManager mFragmentManager; + private FragmentTransaction mCurTransaction = null; + private Fragment mCurrentPrimaryItem = null; + + private int mNumPages; + + public NavigationFragmentPagerAdapter(Context context, FragmentManager fm) { + mFragmentManager = fm; + this.mNumPages = FileManagerApplication.NUM_PAGES; + } + + /** + * Return the Fragment associated with a specified position. + * + * @param position the integer position of the requested fragment within the Pager Adapter + */ + public Fragment getItem(int position) { + NavigationFragment myFragment = NavigationFragment.newInstance(position); + return myFragment; + } + + /** + * This function is the reason why this class is a full copy of + * FragmentPagerAdapter and not an implementation. The app required the + * ability to call functions on each FileListFragment from the main class. + * This function needed to be able to reference mFragmentManger. + * + * @param container + * @param position + * @return FileListFragment + */ + public NavigationFragment getFragment(ViewGroup container, int position) { + String name = makeFragmentName(container.getId(), position); + NavigationFragment fragment = (NavigationFragment) mFragmentManager.findFragmentByTag(name); + return fragment; + } + + /** + * {@inheritDoc} + */ + @Override + public int getCount() { + return mNumPages; + } + + /** + * {@inheritDoc} + */ + @Override + public void startUpdate(ViewGroup container) { + } + + /** + * {@inheritDoc} + */ + @TargetApi(15) + @Override + public Object instantiateItem(ViewGroup container, int position) { + if (mCurTransaction == null) { + mCurTransaction = mFragmentManager.beginTransaction(); + } + + // Do we already have this fragment? + String name = makeFragmentName(container.getId(), position); + Fragment fragment = mFragmentManager.findFragmentByTag(name); + if (fragment != null) { + if (DEBUG) + Log.v(TAG, "Attaching item #" + position + ": f=" + fragment); + mCurTransaction.attach(fragment); + } else { + fragment = getItem(position); + if (DEBUG) + Log.v(TAG, "Adding item #" + position + ": f=" + fragment); + mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), position)); + } + if (fragment != mCurrentPrimaryItem) { + fragment.setMenuVisibility(false); + if (android.os.Build.VERSION.SDK_INT >= 15) { + fragment.setUserVisibleHint(false); + } + } + + return fragment; + } + + /** + * {@inheritDoc} + */ + @Override + public void destroyItem(ViewGroup container, int position, Object object) { + if (mCurTransaction == null) { + mCurTransaction = mFragmentManager.beginTransaction(); + } + if (DEBUG) + Log.v(TAG, "Detaching item #" + position + ": f=" + object + " v=" + ((Fragment) object).getView()); + mCurTransaction.detach((Fragment) object); + } + + /** + * {@inheritDoc} + */ + @TargetApi(15) + @Override + public void setPrimaryItem(ViewGroup container, int position, Object object) { + Fragment fragment = (Fragment) object; + if (fragment != mCurrentPrimaryItem) { + if (mCurrentPrimaryItem != null) { + mCurrentPrimaryItem.setMenuVisibility(false); + if (android.os.Build.VERSION.SDK_INT >= 15) { + mCurrentPrimaryItem.setUserVisibleHint(false); + } + } + if (fragment != null) { + fragment.setMenuVisibility(true); + if (android.os.Build.VERSION.SDK_INT >= 15) { + fragment.setUserVisibleHint(true); + } + } + mCurrentPrimaryItem = fragment; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void finishUpdate(ViewGroup container) { + if (mCurTransaction != null) { + mCurTransaction.commitAllowingStateLoss(); + mCurTransaction = null; + mFragmentManager.executePendingTransactions(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isViewFromObject(View view, Object object) { + return ((Fragment) object).getView() == view; + } + + /** + * {@inheritDoc} + */ + @Override + public Parcelable saveState() { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public void restoreState(Parcelable state, ClassLoader loader) { + } + + private static String makeFragmentName(int viewId, int index) { + return "android:switcher:" + viewId + ":" + index; + } + + /** + * {@inheritDoc} + */ + @Override + public CharSequence getPageTitle(int position) { + return "Fragment" + position; + } +} diff --git a/src/com/cyanogenmod/filemanager/adapters/SearchResultAdapter.java b/Backbone/src/main/java/me/toolify/backbone/adapters/SearchResultAdapter.java similarity index 83% rename from src/com/cyanogenmod/filemanager/adapters/SearchResultAdapter.java rename to Backbone/src/main/java/me/toolify/backbone/adapters/SearchResultAdapter.java index 0561a6473..2441832f9 100644 --- a/src/com/cyanogenmod/filemanager/adapters/SearchResultAdapter.java +++ b/Backbone/src/main/java/me/toolify/backbone/adapters/SearchResultAdapter.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2012 The CyanogenMod Project + * Copyright (C) 2013 BrandroidTools * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +15,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.adapters; +package me.toolify.backbone.adapters; import android.content.Context; import android.graphics.drawable.Drawable; @@ -25,18 +26,18 @@ import android.widget.ImageView; import android.widget.TextView; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.model.FileSystemObject; -import com.cyanogenmod.filemanager.model.Query; -import com.cyanogenmod.filemanager.model.SearchResult; -import com.cyanogenmod.filemanager.preferences.FileManagerSettings; -import com.cyanogenmod.filemanager.preferences.Preferences; -import com.cyanogenmod.filemanager.ui.IconHolder; -import com.cyanogenmod.filemanager.ui.ThemeManager; -import com.cyanogenmod.filemanager.ui.ThemeManager.Theme; -import com.cyanogenmod.filemanager.ui.widgets.RelevanceView; -import com.cyanogenmod.filemanager.util.MimeTypeHelper; -import com.cyanogenmod.filemanager.util.SearchHelper; +import me.toolify.backbone.R; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.model.Query; +import me.toolify.backbone.model.SearchResult; +import me.toolify.backbone.preferences.FileManagerSettings; +import me.toolify.backbone.preferences.Preferences; +import me.toolify.backbone.ui.IconHolder; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.ui.ThemeManager.Theme; +import me.toolify.backbone.ui.widgets.RelevanceView; +import me.toolify.backbone.util.MimeTypeHelper; +import me.toolify.backbone.util.SearchHelper; import java.io.File; import java.util.ArrayList; @@ -80,6 +81,7 @@ public DataHolder() { Float mRelevance; } + private static final int MESSAGE_REDRAW = 1; private DataHolder[] mData; private IconHolder mIconHolder; @@ -90,6 +92,8 @@ public DataHolder() { private final List mQueries; + private boolean mDisposed; + //The resource of the item icon private static final int RESOURCE_ITEM_ICON = R.id.search_item_icon; //The resource of the item name @@ -111,7 +115,11 @@ public DataHolder() { public SearchResultAdapter( Context context, List files, int itemViewResourceId, Query queries) { super(context, RESOURCE_ITEM_NAME, files); - this.mIconHolder = new IconHolder(); + this.mDisposed = false; + final boolean displayThumbs = Preferences.getSharedPreferences().getBoolean( + FileManagerSettings.SETTINGS_DISPLAY_THUMBS.getId(), + ((Boolean)FileManagerSettings.SETTINGS_DISPLAY_THUMBS.getDefaultValue()).booleanValue()); + this.mIconHolder = new IconHolder(context); this.mItemViewResourceId = itemViewResourceId; this.mQueries = queries.getQueries(); @@ -127,15 +135,15 @@ public SearchResultAdapter( //Do cache of the data for better performance loadDefaultIcons(); - processData(files); + processData(); } /** * Method that loads the default icons (known icons and more common icons). */ private void loadDefaultIcons() { - this.mIconHolder.getDrawable(getContext(), "ic_fso_folder_drawable"); //$NON-NLS-1$ - this.mIconHolder.getDrawable(getContext(), "ic_fso_default_drawable"); //$NON-NLS-1$ + this.mIconHolder.getDrawable("ic_fso_folder_drawable"); //$NON-NLS-1$ + this.mIconHolder.getDrawable("ic_fso_default_drawable"); //$NON-NLS-1$ } /** @@ -143,7 +151,10 @@ private void loadDefaultIcons() { */ @Override public void notifyDataSetChanged() { - processData(null); + if (this.mDisposed) { + return; + } + processData(); super.notifyDataSetChanged(); } @@ -151,6 +162,10 @@ public void notifyDataSetChanged() { * Method that dispose the elements of the adapter. */ public void dispose() { + if (this.mIconHolder != null) { + this.mIconHolder.cleanup(); + } + this.mDisposed = true; clear(); this.mData = null; this.mIconHolder = null; @@ -158,25 +173,23 @@ public void dispose() { /** * Method that process the data before use {@link #getView} method. - * - * @param files The list of files (to better performance) or null. */ - private void processData(List files) { + private void processData() { Theme theme = ThemeManager.getCurrentTheme(getContext()); int highlightedColor = theme.getColor(getContext(), "search_highlight_color"); //$NON-NLS-1$ this.mData = new DataHolder[getCount()]; - int cc = (files == null) ? getCount() : files.size(); + int cc = getCount(); for (int i = 0; i < cc; i++) { //File system object info - SearchResult result = (files == null) ? getItem(i) : files.get(i); + SearchResult result = getItem(i); //Build the data holder + final FileSystemObject fso = result.getFso(); this.mData[i] = new SearchResultAdapter.DataHolder(); - this.mData[i].mDwIcon = - this.mIconHolder.getDrawable( - getContext(), MimeTypeHelper.getIcon(getContext(), result.getFso())); + this.mData[i].mDwIcon = this.mIconHolder.getDrawable( + MimeTypeHelper.getIcon(getContext(), fso)); if (this.mHighlightTerms) { this.mData[i].mName = SearchHelper.getHighlightedName(result, this.mQueries, highlightedColor); diff --git a/Backbone/src/main/java/me/toolify/backbone/bus/events/BookmarkDeleteEvent.java b/Backbone/src/main/java/me/toolify/backbone/bus/events/BookmarkDeleteEvent.java new file mode 100644 index 000000000..4e040eed7 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/bus/events/BookmarkDeleteEvent.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2013 BrandroidTools + * + * 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 me.toolify.backbone.bus.events; + +public class BookmarkDeleteEvent extends BusEvent { + public final String path; + + public BookmarkDeleteEvent(String path) { + this.path = path; + } + + @Override + public String toString() { + return new StringBuilder("(") + .append("bus event: delete bookmark ") + .append(path) + .append(")") + .toString(); + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/bus/events/BookmarkOpenEvent.java b/Backbone/src/main/java/me/toolify/backbone/bus/events/BookmarkOpenEvent.java new file mode 100644 index 000000000..76697e89a --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/bus/events/BookmarkOpenEvent.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) BrandroidTools + * + * 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 me.toolify.backbone.bus.events; + +public class BookmarkOpenEvent extends BusEvent { + public final String path; + + public BookmarkOpenEvent(String path) { + this.path = path; + } + + @Override + public String toString() { + return new StringBuilder("(") + .append("bus event: open bookmark ") + .append(path) + .append(")") + .toString(); + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/bus/events/BookmarkRefreshEvent.java b/Backbone/src/main/java/me/toolify/backbone/bus/events/BookmarkRefreshEvent.java new file mode 100644 index 000000000..586ad1706 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/bus/events/BookmarkRefreshEvent.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) BrandroidTools + * + * 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 me.toolify.backbone.bus.events; + +public class BookmarkRefreshEvent extends BusEvent { + + public void BookmarkRefreshEvent() { + + } +} \ No newline at end of file diff --git a/Backbone/src/main/java/me/toolify/backbone/bus/events/BusEvent.java b/Backbone/src/main/java/me/toolify/backbone/bus/events/BusEvent.java new file mode 100644 index 000000000..d6ab61b49 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/bus/events/BusEvent.java @@ -0,0 +1,13 @@ +package me.toolify.backbone.bus.events; + +/** + * Created by Brandon on 9/25/13. + */ +public abstract class BusEvent { + @Override + public String toString() { + return new StringBuilder("(bus event: ") + .append(this.getClass().toString()) + .append(")").toString(); + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/bus/events/ClosePropertiesDrawerEvent.java b/Backbone/src/main/java/me/toolify/backbone/bus/events/ClosePropertiesDrawerEvent.java new file mode 100644 index 000000000..9679f9af6 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/bus/events/ClosePropertiesDrawerEvent.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) BrandroidTools + * + * 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 me.toolify.backbone.bus.events; + +public class ClosePropertiesDrawerEvent extends BusEvent { + + public ClosePropertiesDrawerEvent() { + + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/bus/events/FilesystemStatusUpdateEvent.java b/Backbone/src/main/java/me/toolify/backbone/bus/events/FilesystemStatusUpdateEvent.java new file mode 100644 index 000000000..2dad0faa3 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/bus/events/FilesystemStatusUpdateEvent.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) BrandroidTools + * + * 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 me.toolify.backbone.bus.events; + +public class FilesystemStatusUpdateEvent extends BusEvent { + public final int status; + + public static final int INDICATOR_LOCKED = 0; + public static final int INDICATOR_UNLOCKED = 1; + public static final int INDICATOR_WARNING = 2; + public static final int INDICATOR_REFRESHING = 3; + public static final int INDICATOR_STOP_REFRESHING = 4; + + public FilesystemStatusUpdateEvent(int status) { + this.status = status; + } + + @Override + public String toString() { + return new StringBuilder("(") + .append("bus event: filesystem info status update") + .append(" - status code: ") + .append(Integer.toString(status)) + .append(")") + .toString(); + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/bus/events/OpenPropertiesDrawerEvent.java b/Backbone/src/main/java/me/toolify/backbone/bus/events/OpenPropertiesDrawerEvent.java new file mode 100644 index 000000000..599380f24 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/bus/events/OpenPropertiesDrawerEvent.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) BrandroidTools + * + * 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 me.toolify.backbone.bus.events; + +import me.toolify.backbone.model.FileSystemObject; + +public class OpenPropertiesDrawerEvent extends BusEvent { + public final Object item; + + public OpenPropertiesDrawerEvent(Object item) { + this.item = item; + } + + @Override + public String toString() { + String path = String.valueOf(item); + if (item instanceof FileSystemObject) { + path = ((FileSystemObject)item).getFullPath(); + } + return new StringBuilder("(") + .append("bus event: open properties on file \"") + .append(path) + .append("\")") + .toString(); + } +} diff --git a/src/com/cyanogenmod/filemanager/commands/AsyncResultExecutable.java b/Backbone/src/main/java/me/toolify/backbone/commands/AsyncResultExecutable.java similarity index 98% rename from src/com/cyanogenmod/filemanager/commands/AsyncResultExecutable.java rename to Backbone/src/main/java/me/toolify/backbone/commands/AsyncResultExecutable.java index c53aa1920..9a3684d34 100644 --- a/src/com/cyanogenmod/filemanager/commands/AsyncResultExecutable.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/AsyncResultExecutable.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; +package me.toolify.backbone.commands; /** * An interface that defines a class as executable in an asynchronous way. diff --git a/src/com/cyanogenmod/filemanager/commands/AsyncResultListener.java b/Backbone/src/main/java/me/toolify/backbone/commands/AsyncResultListener.java similarity index 97% rename from src/com/cyanogenmod/filemanager/commands/AsyncResultListener.java rename to Backbone/src/main/java/me/toolify/backbone/commands/AsyncResultListener.java index 847bd1a1d..781e4c814 100644 --- a/src/com/cyanogenmod/filemanager/commands/AsyncResultListener.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/AsyncResultListener.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; +package me.toolify.backbone.commands; /** diff --git a/src/com/cyanogenmod/filemanager/commands/ChangeOwnerExecutable.java b/Backbone/src/main/java/me/toolify/backbone/commands/ChangeOwnerExecutable.java similarity index 94% rename from src/com/cyanogenmod/filemanager/commands/ChangeOwnerExecutable.java rename to Backbone/src/main/java/me/toolify/backbone/commands/ChangeOwnerExecutable.java index 9d0b9c359..05294e6a3 100644 --- a/src/com/cyanogenmod/filemanager/commands/ChangeOwnerExecutable.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/ChangeOwnerExecutable.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; +package me.toolify.backbone.commands; /** * An interface that represents an executable for change the owner of diff --git a/src/com/cyanogenmod/filemanager/commands/ChangePermissionsExecutable.java b/Backbone/src/main/java/me/toolify/backbone/commands/ChangePermissionsExecutable.java similarity index 95% rename from src/com/cyanogenmod/filemanager/commands/ChangePermissionsExecutable.java rename to Backbone/src/main/java/me/toolify/backbone/commands/ChangePermissionsExecutable.java index 0cb2c8447..8146f25cf 100644 --- a/src/com/cyanogenmod/filemanager/commands/ChangePermissionsExecutable.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/ChangePermissionsExecutable.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; +package me.toolify.backbone.commands; /** * An interface that represents an executable for change the permissions of diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/ChecksumExecutable.java b/Backbone/src/main/java/me/toolify/backbone/commands/ChecksumExecutable.java new file mode 100644 index 000000000..afe6b8c10 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/ChecksumExecutable.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands; + +/** + * An interface that represents an executable for calculate checksum of file system objects. + */ +public interface ChecksumExecutable extends AsyncResultExecutable { + + /** + * Checksum enumerations + */ + public enum CHECKSUMS { + /** + * MD5 digest algorithm + */ + MD5, + /** + * SHA-1 digest algorithm + */ + SHA1 + } + + /** + * Method that returns the calculated MD5 [0] and SHA-1 [1] digests + * + * @return String[] The calculated MD5 [0] and SHA-1 [1] digests + */ + String[] getResult(); + + /** + * Method that returns a calculated digest checksum + * + * @param checksum The checksum to return + * @return String The calculated digest to return + */ + String getChecksum(CHECKSUMS checksum); +} diff --git a/src/com/cyanogenmod/filemanager/commands/CompressExecutable.java b/Backbone/src/main/java/me/toolify/backbone/commands/CompressExecutable.java similarity index 95% rename from src/com/cyanogenmod/filemanager/commands/CompressExecutable.java rename to Backbone/src/main/java/me/toolify/backbone/commands/CompressExecutable.java index af8ce5433..8b6ce49fd 100644 --- a/src/com/cyanogenmod/filemanager/commands/CompressExecutable.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/CompressExecutable.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; +package me.toolify.backbone.commands; /** * An interface that represents an executable for compress file system objects. diff --git a/src/com/cyanogenmod/filemanager/commands/CopyExecutable.java b/Backbone/src/main/java/me/toolify/backbone/commands/CopyExecutable.java similarity index 94% rename from src/com/cyanogenmod/filemanager/commands/CopyExecutable.java rename to Backbone/src/main/java/me/toolify/backbone/commands/CopyExecutable.java index 983d84446..e8f9a78df 100644 --- a/src/com/cyanogenmod/filemanager/commands/CopyExecutable.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/CopyExecutable.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; +package me.toolify.backbone.commands; /** * An interface that represents an executable for copy a file system object to diff --git a/src/com/cyanogenmod/filemanager/commands/CreateDirExecutable.java b/Backbone/src/main/java/me/toolify/backbone/commands/CreateDirExecutable.java similarity index 94% rename from src/com/cyanogenmod/filemanager/commands/CreateDirExecutable.java rename to Backbone/src/main/java/me/toolify/backbone/commands/CreateDirExecutable.java index 40d490e06..a05dcf448 100644 --- a/src/com/cyanogenmod/filemanager/commands/CreateDirExecutable.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/CreateDirExecutable.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; +package me.toolify.backbone.commands; /** * An interface that represents an executable for create a new directory. diff --git a/src/com/cyanogenmod/filemanager/commands/CreateFileExecutable.java b/Backbone/src/main/java/me/toolify/backbone/commands/CreateFileExecutable.java similarity index 94% rename from src/com/cyanogenmod/filemanager/commands/CreateFileExecutable.java rename to Backbone/src/main/java/me/toolify/backbone/commands/CreateFileExecutable.java index 011aaf518..365d23cfe 100644 --- a/src/com/cyanogenmod/filemanager/commands/CreateFileExecutable.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/CreateFileExecutable.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; +package me.toolify.backbone.commands; /** * An interface that represents an executable for create a new file. diff --git a/src/com/cyanogenmod/filemanager/commands/DeleteDirExecutable.java b/Backbone/src/main/java/me/toolify/backbone/commands/DeleteDirExecutable.java similarity index 94% rename from src/com/cyanogenmod/filemanager/commands/DeleteDirExecutable.java rename to Backbone/src/main/java/me/toolify/backbone/commands/DeleteDirExecutable.java index 0f2ce04b8..e909fc481 100644 --- a/src/com/cyanogenmod/filemanager/commands/DeleteDirExecutable.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/DeleteDirExecutable.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; +package me.toolify.backbone.commands; /** * An interface that represents an executable for delete a new directory. diff --git a/src/com/cyanogenmod/filemanager/commands/DeleteFileExecutable.java b/Backbone/src/main/java/me/toolify/backbone/commands/DeleteFileExecutable.java similarity index 94% rename from src/com/cyanogenmod/filemanager/commands/DeleteFileExecutable.java rename to Backbone/src/main/java/me/toolify/backbone/commands/DeleteFileExecutable.java index 33a9f235b..a60e47f43 100644 --- a/src/com/cyanogenmod/filemanager/commands/DeleteFileExecutable.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/DeleteFileExecutable.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; +package me.toolify.backbone.commands; /** * An interface that represents an executable for delete a new file. diff --git a/src/com/cyanogenmod/filemanager/commands/DiskUsageExecutable.java b/Backbone/src/main/java/me/toolify/backbone/commands/DiskUsageExecutable.java similarity index 90% rename from src/com/cyanogenmod/filemanager/commands/DiskUsageExecutable.java rename to Backbone/src/main/java/me/toolify/backbone/commands/DiskUsageExecutable.java index 31beb761c..b35933fd9 100644 --- a/src/com/cyanogenmod/filemanager/commands/DiskUsageExecutable.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/DiskUsageExecutable.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; +package me.toolify.backbone.commands; -import com.cyanogenmod.filemanager.model.DiskUsage; +import me.toolify.backbone.model.DiskUsage; import java.util.List; diff --git a/src/com/cyanogenmod/filemanager/commands/EchoExecutable.java b/Backbone/src/main/java/me/toolify/backbone/commands/EchoExecutable.java similarity index 94% rename from src/com/cyanogenmod/filemanager/commands/EchoExecutable.java rename to Backbone/src/main/java/me/toolify/backbone/commands/EchoExecutable.java index a1ff41b11..5d32b954b 100644 --- a/src/com/cyanogenmod/filemanager/commands/EchoExecutable.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/EchoExecutable.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; +package me.toolify.backbone.commands; /** * An interface that represents an executable for expanding environment variables diff --git a/src/com/cyanogenmod/filemanager/commands/ExecExecutable.java b/Backbone/src/main/java/me/toolify/backbone/commands/ExecExecutable.java similarity index 95% rename from src/com/cyanogenmod/filemanager/commands/ExecExecutable.java rename to Backbone/src/main/java/me/toolify/backbone/commands/ExecExecutable.java index 5cf676282..3436b09c4 100644 --- a/src/com/cyanogenmod/filemanager/commands/ExecExecutable.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/ExecExecutable.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; +package me.toolify.backbone.commands; /** * An interface that represents an executable for execute a command diff --git a/src/com/cyanogenmod/filemanager/commands/Executable.java b/Backbone/src/main/java/me/toolify/backbone/commands/Executable.java similarity index 94% rename from src/com/cyanogenmod/filemanager/commands/Executable.java rename to Backbone/src/main/java/me/toolify/backbone/commands/Executable.java index 874235d98..c495d41fb 100644 --- a/src/com/cyanogenmod/filemanager/commands/Executable.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/Executable.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; +package me.toolify.backbone.commands; /** * An interface that defines a class as executable. diff --git a/src/com/cyanogenmod/filemanager/commands/ExecutableCreator.java b/Backbone/src/main/java/me/toolify/backbone/commands/ExecutableCreator.java similarity index 94% rename from src/com/cyanogenmod/filemanager/commands/ExecutableCreator.java rename to Backbone/src/main/java/me/toolify/backbone/commands/ExecutableCreator.java index a0fd6f94b..3c414431e 100644 --- a/src/com/cyanogenmod/filemanager/commands/ExecutableCreator.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/ExecutableCreator.java @@ -14,38 +14,24 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; - -import com.cyanogenmod.filemanager.commands.ListExecutable.LIST_MODE; -import com.cyanogenmod.filemanager.console.CommandNotFoundException; -import com.cyanogenmod.filemanager.console.InsufficientPermissionsException; -import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory; -import com.cyanogenmod.filemanager.model.Group; -import com.cyanogenmod.filemanager.model.MountPoint; -import com.cyanogenmod.filemanager.model.Permissions; -import com.cyanogenmod.filemanager.model.Query; -import com.cyanogenmod.filemanager.model.User; -import com.cyanogenmod.filemanager.preferences.CompressionMode; +package me.toolify.backbone.commands; + +import me.toolify.backbone.commands.ListExecutable.LIST_MODE; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.console.NoSuchFileOrDirectory; +import me.toolify.backbone.model.Group; +import me.toolify.backbone.model.MountPoint; +import me.toolify.backbone.model.Permissions; +import me.toolify.backbone.model.Query; +import me.toolify.backbone.model.User; +import me.toolify.backbone.preferences.CompressionMode; /** * A interface that defines methods for create {@link Executable} objects. */ public interface ExecutableCreator { - /** - * Method that creates an executable for change the current directory. - * - * @param dir The absolute path of the new directory to establish as current directory - * @return ChangeCurrentDirExecutable A {@link ChangeCurrentDirExecutable} executable - * implementation reference - * @throws CommandNotFoundException If the executable can't be created - * @throws NoSuchFileOrDirectory If the file or directory was not found - * @throws InsufficientPermissionsException If an operation requires elevated permissions - */ - ChangeCurrentDirExecutable createChangeCurrentDirExecutable( - String dir) throws CommandNotFoundException, - NoSuchFileOrDirectory, InsufficientPermissionsException; - /** * Method that creates an executable for change the owner of a file system object. * @@ -118,18 +104,6 @@ CreateDirExecutable createCreateDirectoryExecutable(String dir) CreateFileExecutable createCreateFileExecutable(String file) throws CommandNotFoundException, NoSuchFileOrDirectory, InsufficientPermissionsException; - /** - * Method that creates an executable for retrieve the current directory. - * - * @return CurrentDirExecutable A {@link CurrentDirExecutable} executable - * implementation reference - * @throws CommandNotFoundException If the executable can't be created - * @throws NoSuchFileOrDirectory If the file or directory was not found - * @throws InsufficientPermissionsException If an operation requires elevated permissions - */ - CurrentDirExecutable createCurrentDirExecutable() throws CommandNotFoundException, - NoSuchFileOrDirectory, InsufficientPermissionsException; - /** * Method that creates an executable for delete a directory. * @@ -251,7 +225,7 @@ FolderUsageExecutable createFolderUsageExecutable( * @throws InsufficientPermissionsException If an operation requires elevated permissions */ GroupsExecutable createGroupsExecutable() - throws com.cyanogenmod.filemanager.console.CommandNotFoundException, + throws me.toolify.backbone.console.CommandNotFoundException, NoSuchFileOrDirectory, InsufficientPermissionsException; /** @@ -375,6 +349,21 @@ ParentDirExecutable createParentDirExecutable(String fso) throws CommandNotFound ProcessIdExecutable createShellProcessIdExecutable() throws CommandNotFoundException, NoSuchFileOrDirectory, InsufficientPermissionsException; + /** + * Method that creates an executable for retrieve operating system process identifiers of a + * shell. + * + * @param pid The shell process id where the process is running + * @param processName The process name + * @return ProcessIdExecutable A {@link ProcessIdExecutable} executable implementation + * reference + * @throws CommandNotFoundException If the executable can't be created + * @throws NoSuchFileOrDirectory If the file or directory was not found + * @throws InsufficientPermissionsException If an operation requires elevated permissions + */ + ProcessIdExecutable createProcessIdExecutable(int pid) throws CommandNotFoundException, + NoSuchFileOrDirectory, InsufficientPermissionsException; + /** * Method that creates an executable for retrieve operating system process identifier of a * process. @@ -529,4 +518,19 @@ UncompressExecutable createUncompressExecutable( throws CommandNotFoundException, NoSuchFileOrDirectory, InsufficientPermissionsException; + /** + * Method that creates an executable for calculate checksums of file system objects. + * + * @param src The compressed file + * @param asyncResultListener The listener where to return partial results + * @return ChecksumExecutable A {@link ChecksumExecutable} executable implementation reference + * @throws CommandNotFoundException If the executable can't be created + * @throws NoSuchFileOrDirectory If the file or directory was not found + * @throws InsufficientPermissionsException If an operation requires elevated permissions + */ + ChecksumExecutable createChecksumExecutable( + String src, AsyncResultListener asyncResultListener) + throws CommandNotFoundException, + NoSuchFileOrDirectory, InsufficientPermissionsException; + } diff --git a/src/com/cyanogenmod/filemanager/commands/ExecutableFactory.java b/Backbone/src/main/java/me/toolify/backbone/commands/ExecutableFactory.java similarity index 95% rename from src/com/cyanogenmod/filemanager/commands/ExecutableFactory.java rename to Backbone/src/main/java/me/toolify/backbone/commands/ExecutableFactory.java index 32def5cb1..97bf8e5fb 100644 --- a/src/com/cyanogenmod/filemanager/commands/ExecutableFactory.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/ExecutableFactory.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; +package me.toolify.backbone.commands; /** * A class that represents a factory for creating {@link Executable} objects. diff --git a/src/com/cyanogenmod/filemanager/commands/FindExecutable.java b/Backbone/src/main/java/me/toolify/backbone/commands/FindExecutable.java similarity index 94% rename from src/com/cyanogenmod/filemanager/commands/FindExecutable.java rename to Backbone/src/main/java/me/toolify/backbone/commands/FindExecutable.java index 7bcee56fa..ed7d61ba7 100644 --- a/src/com/cyanogenmod/filemanager/commands/FindExecutable.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/FindExecutable.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; +package me.toolify.backbone.commands; /** * An interface that represents an executable for make a search over diff --git a/src/com/cyanogenmod/filemanager/commands/FolderUsageExecutable.java b/Backbone/src/main/java/me/toolify/backbone/commands/FolderUsageExecutable.java similarity index 90% rename from src/com/cyanogenmod/filemanager/commands/FolderUsageExecutable.java rename to Backbone/src/main/java/me/toolify/backbone/commands/FolderUsageExecutable.java index d9e1253a6..cf8b0e0b3 100644 --- a/src/com/cyanogenmod/filemanager/commands/FolderUsageExecutable.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/FolderUsageExecutable.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; +package me.toolify.backbone.commands; -import com.cyanogenmod.filemanager.model.FolderUsage; +import me.toolify.backbone.model.FolderUsage; /** * An interface that represents an executable for retrieve a folder usage diff --git a/src/com/cyanogenmod/filemanager/commands/GroupsExecutable.java b/Backbone/src/main/java/me/toolify/backbone/commands/GroupsExecutable.java similarity index 90% rename from src/com/cyanogenmod/filemanager/commands/GroupsExecutable.java rename to Backbone/src/main/java/me/toolify/backbone/commands/GroupsExecutable.java index 30615d7c5..59d4cb280 100644 --- a/src/com/cyanogenmod/filemanager/commands/GroupsExecutable.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/GroupsExecutable.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; +package me.toolify.backbone.commands; -import com.cyanogenmod.filemanager.model.Group; +import me.toolify.backbone.model.Group; import java.util.List; diff --git a/src/com/cyanogenmod/filemanager/commands/IdentityExecutable.java b/Backbone/src/main/java/me/toolify/backbone/commands/IdentityExecutable.java similarity index 90% rename from src/com/cyanogenmod/filemanager/commands/IdentityExecutable.java rename to Backbone/src/main/java/me/toolify/backbone/commands/IdentityExecutable.java index 8508ef7b9..e14397e72 100644 --- a/src/com/cyanogenmod/filemanager/commands/IdentityExecutable.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/IdentityExecutable.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; +package me.toolify.backbone.commands; -import com.cyanogenmod.filemanager.model.Identity; +import me.toolify.backbone.model.Identity; /** * An interface that represents an executable for retrieve information of diff --git a/src/com/cyanogenmod/filemanager/commands/LinkExecutable.java b/Backbone/src/main/java/me/toolify/backbone/commands/LinkExecutable.java similarity index 94% rename from src/com/cyanogenmod/filemanager/commands/LinkExecutable.java rename to Backbone/src/main/java/me/toolify/backbone/commands/LinkExecutable.java index 53833ee91..405c517cd 100644 --- a/src/com/cyanogenmod/filemanager/commands/LinkExecutable.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/LinkExecutable.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; +package me.toolify.backbone.commands; /** * An interface that represents an executable for create symlinks to other file system objects. diff --git a/src/com/cyanogenmod/filemanager/commands/ListExecutable.java b/Backbone/src/main/java/me/toolify/backbone/commands/ListExecutable.java similarity index 92% rename from src/com/cyanogenmod/filemanager/commands/ListExecutable.java rename to Backbone/src/main/java/me/toolify/backbone/commands/ListExecutable.java index 071177904..9138d975e 100644 --- a/src/com/cyanogenmod/filemanager/commands/ListExecutable.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/ListExecutable.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; +package me.toolify.backbone.commands; -import com.cyanogenmod.filemanager.model.FileSystemObject; +import me.toolify.backbone.model.FileSystemObject; import java.util.List; diff --git a/src/com/cyanogenmod/filemanager/commands/MountExecutable.java b/Backbone/src/main/java/me/toolify/backbone/commands/MountExecutable.java similarity index 95% rename from src/com/cyanogenmod/filemanager/commands/MountExecutable.java rename to Backbone/src/main/java/me/toolify/backbone/commands/MountExecutable.java index 1e6b2ca17..4d5c41ad6 100644 --- a/src/com/cyanogenmod/filemanager/commands/MountExecutable.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/MountExecutable.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; +package me.toolify.backbone.commands; /** * An interface that represents an executable for mount filesystems. diff --git a/src/com/cyanogenmod/filemanager/commands/MountPointInfoExecutable.java b/Backbone/src/main/java/me/toolify/backbone/commands/MountPointInfoExecutable.java similarity index 90% rename from src/com/cyanogenmod/filemanager/commands/MountPointInfoExecutable.java rename to Backbone/src/main/java/me/toolify/backbone/commands/MountPointInfoExecutable.java index af0ce5e58..8806ab047 100644 --- a/src/com/cyanogenmod/filemanager/commands/MountPointInfoExecutable.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/MountPointInfoExecutable.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; +package me.toolify.backbone.commands; -import com.cyanogenmod.filemanager.model.MountPoint; +import me.toolify.backbone.model.MountPoint; import java.util.List; diff --git a/src/com/cyanogenmod/filemanager/commands/MoveExecutable.java b/Backbone/src/main/java/me/toolify/backbone/commands/MoveExecutable.java similarity index 94% rename from src/com/cyanogenmod/filemanager/commands/MoveExecutable.java rename to Backbone/src/main/java/me/toolify/backbone/commands/MoveExecutable.java index 4d9bb2a15..12ea61d88 100644 --- a/src/com/cyanogenmod/filemanager/commands/MoveExecutable.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/MoveExecutable.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; +package me.toolify.backbone.commands; /** * An interface that represents an executable for move a file system object to diff --git a/src/com/cyanogenmod/filemanager/commands/ParentDirExecutable.java b/Backbone/src/main/java/me/toolify/backbone/commands/ParentDirExecutable.java similarity index 95% rename from src/com/cyanogenmod/filemanager/commands/ParentDirExecutable.java rename to Backbone/src/main/java/me/toolify/backbone/commands/ParentDirExecutable.java index 71f1ea7bb..c65ccbd2d 100644 --- a/src/com/cyanogenmod/filemanager/commands/ParentDirExecutable.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/ParentDirExecutable.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; +package me.toolify.backbone.commands; /** * An interface that represents an executable for retrieve the parent directory of diff --git a/src/com/cyanogenmod/filemanager/commands/ProcessIdExecutable.java b/Backbone/src/main/java/me/toolify/backbone/commands/ProcessIdExecutable.java similarity index 90% rename from src/com/cyanogenmod/filemanager/commands/ProcessIdExecutable.java rename to Backbone/src/main/java/me/toolify/backbone/commands/ProcessIdExecutable.java index cbcbce3c7..bdc1e1524 100644 --- a/src/com/cyanogenmod/filemanager/commands/ProcessIdExecutable.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/ProcessIdExecutable.java @@ -14,7 +14,9 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; +package me.toolify.backbone.commands; + +import java.util.List; /** * An interface that represents an executable for retrieve the process identifier @@ -26,5 +28,5 @@ public interface ProcessIdExecutable extends SyncResultExecutable { * {@inheritDoc} */ @Override - Integer getResult(); + List getResult(); } diff --git a/src/com/cyanogenmod/filemanager/commands/QuickFolderSearchExecutable.java b/Backbone/src/main/java/me/toolify/backbone/commands/QuickFolderSearchExecutable.java similarity index 95% rename from src/com/cyanogenmod/filemanager/commands/QuickFolderSearchExecutable.java rename to Backbone/src/main/java/me/toolify/backbone/commands/QuickFolderSearchExecutable.java index 54b0bda2d..8b3058e03 100644 --- a/src/com/cyanogenmod/filemanager/commands/QuickFolderSearchExecutable.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/QuickFolderSearchExecutable.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; +package me.toolify.backbone.commands; import java.util.List; diff --git a/src/com/cyanogenmod/filemanager/commands/ReadExecutable.java b/Backbone/src/main/java/me/toolify/backbone/commands/ReadExecutable.java similarity index 94% rename from src/com/cyanogenmod/filemanager/commands/ReadExecutable.java rename to Backbone/src/main/java/me/toolify/backbone/commands/ReadExecutable.java index 475eb5c1f..3f1ab6b1e 100644 --- a/src/com/cyanogenmod/filemanager/commands/ReadExecutable.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/ReadExecutable.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; +package me.toolify.backbone.commands; /** * An interface that represents an executable for read files. diff --git a/src/com/cyanogenmod/filemanager/commands/ResolveLinkExecutable.java b/Backbone/src/main/java/me/toolify/backbone/commands/ResolveLinkExecutable.java similarity index 89% rename from src/com/cyanogenmod/filemanager/commands/ResolveLinkExecutable.java rename to Backbone/src/main/java/me/toolify/backbone/commands/ResolveLinkExecutable.java index a5a2e6385..48bf851c7 100644 --- a/src/com/cyanogenmod/filemanager/commands/ResolveLinkExecutable.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/ResolveLinkExecutable.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; +package me.toolify.backbone.commands; -import com.cyanogenmod.filemanager.model.FileSystemObject; +import me.toolify.backbone.model.FileSystemObject; /** * An interface that represents an executable for resolves the real diff --git a/src/com/cyanogenmod/filemanager/commands/SIGNAL.java b/Backbone/src/main/java/me/toolify/backbone/commands/SIGNAL.java similarity index 97% rename from src/com/cyanogenmod/filemanager/commands/SIGNAL.java rename to Backbone/src/main/java/me/toolify/backbone/commands/SIGNAL.java index 26bcaa03d..cb8b7498e 100644 --- a/src/com/cyanogenmod/filemanager/commands/SIGNAL.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/SIGNAL.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; +package me.toolify.backbone.commands; /** * An enumeration of allow signals that can send to programs. diff --git a/src/com/cyanogenmod/filemanager/commands/SendSignalExecutable.java b/Backbone/src/main/java/me/toolify/backbone/commands/SendSignalExecutable.java similarity index 94% rename from src/com/cyanogenmod/filemanager/commands/SendSignalExecutable.java rename to Backbone/src/main/java/me/toolify/backbone/commands/SendSignalExecutable.java index 076f70987..b280dfcd8 100644 --- a/src/com/cyanogenmod/filemanager/commands/SendSignalExecutable.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/SendSignalExecutable.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; +package me.toolify.backbone.commands; /** * An interface that represents an executable for send signal to processes. diff --git a/src/com/cyanogenmod/filemanager/commands/SyncResultExecutable.java b/Backbone/src/main/java/me/toolify/backbone/commands/SyncResultExecutable.java similarity index 95% rename from src/com/cyanogenmod/filemanager/commands/SyncResultExecutable.java rename to Backbone/src/main/java/me/toolify/backbone/commands/SyncResultExecutable.java index bfe51f6c0..a4371bde4 100644 --- a/src/com/cyanogenmod/filemanager/commands/SyncResultExecutable.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/SyncResultExecutable.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; +package me.toolify.backbone.commands; /** * An interface that defines a class as executable in a synchronous way. diff --git a/src/com/cyanogenmod/filemanager/commands/UncompressExecutable.java b/Backbone/src/main/java/me/toolify/backbone/commands/UncompressExecutable.java similarity index 96% rename from src/com/cyanogenmod/filemanager/commands/UncompressExecutable.java rename to Backbone/src/main/java/me/toolify/backbone/commands/UncompressExecutable.java index 3a2b21cc7..22587bc3e 100644 --- a/src/com/cyanogenmod/filemanager/commands/UncompressExecutable.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/UncompressExecutable.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; +package me.toolify.backbone.commands; /** * An interface that represents an executable for uncompress file system objects. diff --git a/src/com/cyanogenmod/filemanager/commands/WritableExecutable.java b/Backbone/src/main/java/me/toolify/backbone/commands/WritableExecutable.java similarity index 92% rename from src/com/cyanogenmod/filemanager/commands/WritableExecutable.java rename to Backbone/src/main/java/me/toolify/backbone/commands/WritableExecutable.java index f00702a65..920020799 100644 --- a/src/com/cyanogenmod/filemanager/commands/WritableExecutable.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/WritableExecutable.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; +package me.toolify.backbone.commands; -import com.cyanogenmod.filemanager.model.MountPoint; +import me.toolify.backbone.model.MountPoint; /** * An interface that represents an executable that writes in a filesystem.
diff --git a/src/com/cyanogenmod/filemanager/commands/WriteExecutable.java b/Backbone/src/main/java/me/toolify/backbone/commands/WriteExecutable.java similarity index 96% rename from src/com/cyanogenmod/filemanager/commands/WriteExecutable.java rename to Backbone/src/main/java/me/toolify/backbone/commands/WriteExecutable.java index 1b4bc4b5f..b4621bc61 100644 --- a/src/com/cyanogenmod/filemanager/commands/WriteExecutable.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/WriteExecutable.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands; +package me.toolify.backbone.commands; import java.io.IOException; import java.io.OutputStream; diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/java/ChecksumCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/java/ChecksumCommand.java new file mode 100644 index 000000000..68b237adc --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/java/ChecksumCommand.java @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.java; + +import android.util.Log; + +import me.toolify.backbone.commands.AsyncResultListener; +import me.toolify.backbone.commands.ChecksumExecutable; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.console.NoSuchFileOrDirectory; +import me.toolify.backbone.util.HexDump; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.security.MessageDigest; +import java.util.Locale; + +/** + * A class for calculate MD5 and SHA-1 checksums of a file system object.
+ *
+ * Partial results are returned in order (MD5 -> SHA1) + */ +public class ChecksumCommand extends Program implements ChecksumExecutable { + + private static final String TAG = "ChecksumCommand"; //$NON-NLS-1$ + + private final File mSrc; + private final String[] mChecksums; + private final AsyncResultListener mAsyncResultListener; + + private boolean mCancelled; + private final Object mSync = new Object(); + + /** + * Constructor of ChecksumCommand. + * + * @param src The source file + * @param asyncResultListener The partial result listener + */ + public ChecksumCommand( + String src, AsyncResultListener asyncResultListener) { + super(); + this.mAsyncResultListener = asyncResultListener; + this.mChecksums = new String[]{null, null}; + this.mSrc = new File(src); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isAsynchronous() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public void execute() throws InsufficientPermissionsException, + NoSuchFileOrDirectory, ExecutionException { + + if (isTrace()) { + Log.v(TAG, + String.format("Calculating checksums of file %s", this.mSrc)); //$NON-NLS-1$ + } + + // Check that the file exists + if (!this.mSrc.exists()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$ + } + throw new NoSuchFileOrDirectory(this.mSrc.getAbsolutePath()); + } + + CHECKSUMS checksum = CHECKSUMS.MD5; + try { + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onAsyncStart(); + } + + // Calculate digests + calculateDigest(checksum); + checksum = CHECKSUMS.SHA1; + calculateDigest(checksum); + + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onAsyncEnd(false); + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onAsyncExitCode(0); + } + + if (isTrace()) { + Log.v(TAG, "Result: OK"); //$NON-NLS-1$ + } + + } catch (InterruptedException ie) { + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onAsyncEnd(true); + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onAsyncExitCode(143); + } + + if (isTrace()) { + Log.v(TAG, "Result: CANCELLED"); //$NON-NLS-1$ + } + + } catch (Exception e) { + Log.e(TAG, + String.format( + "Fail to calculate %s checksum of file %s", //$NON-NLS-1$ + checksum.name(), + this.mSrc.getAbsolutePath()), + e); + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onException(e); + } + if (isTrace()) { + Log.v(TAG, "Result: FAIL"); //$NON-NLS-1$ + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isCancelled() { + synchronized (this.mSync) { + return this.mCancelled; + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean cancel() { + try { + synchronized (this.mSync) { + this.mCancelled = true; + } + } catch (Throwable _throw) {/**NON BLOCK**/} + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean end() { + return cancel(); + } + + /** + * {@inheritDoc} + */ + @Override + public void setOnEndListener(OnEndListener onEndListener) { + //Ignore. Java console don't use this + } + + /** + * {@inheritDoc} + */ + @Override + public void setOnCancelListener(OnCancelListener onCancelListener) { + //Ignore. Java console don't use this + } + + /** + * {@inheritDoc} + */ + @Override + public String[] getResult() { + return this.mChecksums; + } + + /** + * {@inheritDoc} + */ + @Override + public String getChecksum(CHECKSUMS checksum) { + return getResult()[checksum.ordinal()]; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isCancellable() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public AsyncResultListener getAsyncResultListener() { + return this.mAsyncResultListener; + } + + /** + * Method that calculate a digest of the file for the source file + * + * @param type The type of digest to obtain + * @throws InterruptedException If the operation was cancelled + * @throws Exception If an error occurs + */ + private void calculateDigest(CHECKSUMS type) throws InterruptedException, Exception { + + InputStream is = null; + try { + MessageDigest md = MessageDigest.getInstance(type.name()); + is = new FileInputStream(this.mSrc); + + // Start digesting + byte[] data = new byte[getBufferSize()]; + int read = 0; + while ((read = is.read(data, 0, getBufferSize())) != -1) { + checkCancelled(); + md.update(data, 0, read); + } + checkCancelled(); + + // Finally digest + this.mChecksums[type.ordinal()] = + HexDump.toHexString(md.digest()).toLowerCase(Locale.ROOT); + checkCancelled(); + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onPartialResult(this.mChecksums[type.ordinal()]); + } + + } finally { + try { + if (is != null) { + is.close(); + } + } catch (Exception e) {/**NON BLOCK**/} + } + } + + /** + * Checks if the operation was cancelled + * + * @throws InterruptedException If the operation was cancelled + */ + private void checkCancelled() throws InterruptedException { + synchronized (this.mSync) { + if (this.mCancelled) { + throw new InterruptedException(); + } + } + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/java/CopyCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/java/CopyCommand.java new file mode 100644 index 000000000..7b272ca5b --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/java/CopyCommand.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.java; + +import android.util.Log; + +import me.toolify.backbone.commands.CopyExecutable; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.console.NoSuchFileOrDirectory; +import me.toolify.backbone.model.MountPoint; +import me.toolify.backbone.util.FileHelper; +import me.toolify.backbone.util.MountPointHelper; + +import java.io.File; + + +/** + * A class for copy a file or directory. + */ +public class CopyCommand extends Program implements CopyExecutable { + + private static final String TAG = "CopyCommand"; //$NON-NLS-1$ + + private final String mSrc; + private final String mDst; + + /** + * Constructor of CopyCommand. + * + * @param src The name of the file or directory to be copied + * @param dst The name of the file or directory in which copy the source file or directory + */ + public CopyCommand(String src, String dst) { + super(); + this.mSrc = src; + this.mDst = dst; + } + + /** + * {@inheritDoc} + */ + @Override + public Boolean getResult() { + return Boolean.TRUE; + } + + /** + * {@inheritDoc} + */ + @Override + public void execute() + throws InsufficientPermissionsException, NoSuchFileOrDirectory, ExecutionException { + if (isTrace()) { + Log.v(TAG, + String.format("Moving from %s to %s", //$NON-NLS-1$ + this.mSrc, this.mDst)); + } + + File s = new File(this.mSrc); + File d = new File(this.mDst); + if (!s.exists()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$ + } + throw new NoSuchFileOrDirectory(this.mSrc); + } + + //Copy recursively + if (!FileHelper.copyRecursive(s, d, getBufferSize())) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. InsufficientPermissionsException"); //$NON-NLS-1$ + } + throw new InsufficientPermissionsException(); + } + + if (isTrace()) { + Log.v(TAG, "Result: OK"); //$NON-NLS-1$ + } + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getSrcWritableMountPoint() { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getDstWritableMountPoint() { + return MountPointHelper.getMountPointFromDirectory(this.mDst); + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/java/CreateDirCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/java/CreateDirCommand.java new file mode 100644 index 000000000..6f6f3d695 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/java/CreateDirCommand.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.java; + +import android.util.Log; + +import me.toolify.backbone.commands.CreateDirExecutable; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.console.NoSuchFileOrDirectory; +import me.toolify.backbone.model.MountPoint; +import me.toolify.backbone.util.MountPointHelper; + +import java.io.File; + + +/** + * A class for create a directory. + */ +public class CreateDirCommand extends Program implements CreateDirExecutable { + + private static final String TAG = "CreateDirCommand"; //$NON-NLS-1$ + + private final String mPath; + + /** + * Constructor of CreateDirCommand. + * + * @param path The name of the new directory + */ + public CreateDirCommand(String path) { + super(); + this.mPath = path; + } + + /** + * {@inheritDoc} + */ + @Override + public Boolean getResult() { + return Boolean.TRUE; + } + + /** + * {@inheritDoc} + */ + @Override + public void execute() + throws InsufficientPermissionsException, NoSuchFileOrDirectory, ExecutionException { + if (isTrace()) { + Log.v(TAG, + String.format("Creating directory: %s", this.mPath)); //$NON-NLS-1$ + } + + File f = new File(this.mPath); + // Check that if the path exist, it need to be a directory. Otherwise something is + // wrong + if (f.exists() && !f.isDirectory()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. ExecutionException"); //$NON-NLS-1$ + } + throw new ExecutionException("the path exists but is not a folder"); //$NON-NLS-1$ + } + + // Only create the directory if the folder not exists. Otherwise mkdir will return false + if (!f.exists()) { + if (!f.mkdir()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. InsufficientPermissionsException"); //$NON-NLS-1$ + } + throw new InsufficientPermissionsException(); + } + } + + if (isTrace()) { + Log.v(TAG, "Result: OK"); //$NON-NLS-1$ + } + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getSrcWritableMountPoint() { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getDstWritableMountPoint() { + return MountPointHelper.getMountPointFromDirectory(this.mPath); + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/java/CreateFileCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/java/CreateFileCommand.java new file mode 100644 index 000000000..bb825f91f --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/java/CreateFileCommand.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.java; + +import android.util.Log; + +import me.toolify.backbone.commands.CreateFileExecutable; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.console.NoSuchFileOrDirectory; +import me.toolify.backbone.model.MountPoint; +import me.toolify.backbone.util.MountPointHelper; + +import java.io.File; +import java.io.IOException; + + +/** + * A class for create a file. + */ +public class CreateFileCommand extends Program implements CreateFileExecutable { + + private static final String TAG = "CreateFileCommand"; //$NON-NLS-1$ + + + private final String mPath; + + /** + * Constructor of CreateFileCommand. + * + * @param path The name of the new file + */ + public CreateFileCommand(String path) { + super(); + this.mPath = path; + } + + /** + * {@inheritDoc} + */ + @Override + public Boolean getResult() { + return Boolean.TRUE; + } + + /** + * {@inheritDoc} + */ + @Override + public void execute() + throws InsufficientPermissionsException, NoSuchFileOrDirectory, ExecutionException { + if (isTrace()) { + Log.v(TAG, + String.format("Creating file: %s", this.mPath)); //$NON-NLS-1$ + } + + File f = new File(this.mPath); + try { + // Check that if the path exist, it need to be a file. Otherwise something is + // wrong + if (f.exists() && !f.isFile()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. ExecutionException"); //$NON-NLS-1$ + } + throw new ExecutionException("the path exists but is not a file"); //$NON-NLS-1$ + } + + // Only create the file if the file not exists. Otherwise createNewFile + // will return false + if (!f.exists()) { + if (!f.createNewFile()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. InsufficientPermissionsException"); //$NON-NLS-1$ + } + throw new InsufficientPermissionsException(); + } + } + } catch (IOException ioe) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. InsufficientPermissionsException"); //$NON-NLS-1$ + } + throw new InsufficientPermissionsException(); + } + + if (isTrace()) { + Log.v(TAG, "Result: OK"); //$NON-NLS-1$ + } + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getSrcWritableMountPoint() { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getDstWritableMountPoint() { + return MountPointHelper.getMountPointFromDirectory(this.mPath); + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/java/DeleteDirCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/java/DeleteDirCommand.java new file mode 100644 index 000000000..b7283f5b7 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/java/DeleteDirCommand.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.java; + +import android.util.Log; + +import me.toolify.backbone.commands.DeleteDirExecutable; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.console.NoSuchFileOrDirectory; +import me.toolify.backbone.model.MountPoint; +import me.toolify.backbone.util.FileHelper; +import me.toolify.backbone.util.MountPointHelper; + +import java.io.File; + + +/** + * A class for delete a folder. + */ +public class DeleteDirCommand extends Program implements DeleteDirExecutable { + + private static final String TAG = "DeleteDirCommand"; //$NON-NLS-1$ + + private final String mPath; + + /** + * Constructor of DeleteDirCommand. + * + * @param path The name of the new folder + */ + public DeleteDirCommand(String path) { + super(); + this.mPath = path; + } + + /** + * {@inheritDoc} + */ + @Override + public Boolean getResult() { + return Boolean.TRUE; + } + + /** + * {@inheritDoc} + */ + @Override + public void execute() + throws InsufficientPermissionsException, NoSuchFileOrDirectory, ExecutionException { + if (isTrace()) { + Log.v(TAG, + String.format("Deleting directory: %s", this.mPath)); //$NON-NLS-1$ + } + + File f = new File(this.mPath); + if (!f.exists()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$ + } + throw new NoSuchFileOrDirectory(this.mPath); + } + + // Check that if the path exist, it need to be a folder. Otherwise something is + // wrong + if (f.exists() && !f.isDirectory()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. ExecutionException"); //$NON-NLS-1$ + } + throw new ExecutionException("the path exists but is not a folder"); //$NON-NLS-1$ + } + + // Delete the file + if (!FileHelper.deleteFolder(f)) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. InsufficientPermissionsException"); //$NON-NLS-1$ + } + throw new InsufficientPermissionsException(); + } + + if (isTrace()) { + Log.v(TAG, "Result: OK"); //$NON-NLS-1$ + } + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getSrcWritableMountPoint() { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getDstWritableMountPoint() { + return MountPointHelper.getMountPointFromDirectory(this.mPath); + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/java/DeleteFileCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/java/DeleteFileCommand.java new file mode 100644 index 000000000..ccd978924 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/java/DeleteFileCommand.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.java; + +import android.util.Log; + +import me.toolify.backbone.commands.DeleteFileExecutable; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.console.NoSuchFileOrDirectory; +import me.toolify.backbone.model.MountPoint; +import me.toolify.backbone.util.MountPointHelper; + +import java.io.File; + + +/** + * A class for delete a file. + */ +public class DeleteFileCommand extends Program implements DeleteFileExecutable { + + private static final String TAG = "DeleteFileCommand"; //$NON-NLS-1$ + + private final String mPath; + + /** + * Constructor of DeleteFileCommand. + * + * @param path The name of the new file + */ + public DeleteFileCommand(String path) { + super(); + this.mPath = path; + } + + /** + * {@inheritDoc} + */ + @Override + public Boolean getResult() { + return Boolean.TRUE; + } + + /** + * {@inheritDoc} + */ + @Override + public void execute() + throws InsufficientPermissionsException, NoSuchFileOrDirectory, ExecutionException { + if (isTrace()) { + Log.v(TAG, + String.format("Deleting file: %s", this.mPath)); //$NON-NLS-1$ + } + + File f = new File(this.mPath); + if (!f.exists()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$ + } + throw new NoSuchFileOrDirectory(this.mPath); + } + + // Check that if the path exist, it need to be a file. Otherwise something is + // wrong + if (f.exists() && !f.isFile()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. ExecutionException"); //$NON-NLS-1$ + } + throw new ExecutionException("the path exists but is not a file"); //$NON-NLS-1$ + } + + // Delete the file + if (!f.delete()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. InsufficientPermissionsException"); //$NON-NLS-1$ + } + throw new InsufficientPermissionsException(); + } + + if (isTrace()) { + Log.v(TAG, "Result: OK"); //$NON-NLS-1$ + } + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getSrcWritableMountPoint() { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getDstWritableMountPoint() { + return MountPointHelper.getMountPointFromDirectory(this.mPath); + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/java/DiskUsageCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/java/DiskUsageCommand.java new file mode 100644 index 000000000..cad2730ea --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/java/DiskUsageCommand.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.java; + +import android.util.Log; + +import me.toolify.backbone.BuildConfig; +import me.toolify.backbone.commands.DiskUsageExecutable; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.console.NoSuchFileOrDirectory; +import me.toolify.backbone.model.DiskUsage; +import me.toolify.backbone.model.MountPoint; + +import java.io.File; +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.List; + + +/** + * A class for get information about disk usage. + */ +public class DiskUsageCommand extends Program implements DiskUsageExecutable { + + private static final String TAG = "DiskUsageCommand"; //$NON-NLS-1$ + + private final String mMountsFile; + private final String mSrc; + private final Hashtable mDisksUsage = new Hashtable(); + + /** + * Constructor of DiskUsageCommand. + * + * @param mountsFile The system mounts file + */ + public DiskUsageCommand(String mountsFile) { + this(mountsFile, null); + } + + /** + * Constructor of DiskUsageCommand. + * + * @param mountsFile The system mounts file + * @param dir The directory of which obtain its disk usage + */ + public DiskUsageCommand(String mountsFile, String dir) { + super(); + this.mMountsFile = mountsFile; + this.mSrc = null; + } + + /** + * {@inheritDoc} + */ + @Override + public List getResult() { + List ret = new ArrayList(); + for(DiskUsage du : mDisksUsage.values()) + ret.add(du); + return ret; + } + + /** + * {@inheritDoc} + */ + @Override + public void execute() + throws InsufficientPermissionsException, NoSuchFileOrDirectory, ExecutionException { + + if (isTrace()) { + Log.v(TAG, + String.format("Getting usage for: %s", //$NON-NLS-1$ + this.mSrc == null ? "all" : this.mSrc)); //$NON-NLS-1$ + } + + if (this.mSrc == null) { + // Retrieve the mount points + MountPointInfoCommand cmd = new MountPointInfoCommand(this.mMountsFile); + cmd.setBufferSize(getBufferSize()); + cmd.setTrace(isTrace()); + cmd.execute(); + List mp = cmd.getResult(); + + // Get every disk usage + for (int i = 0; i < mp.size(); i++) { + String mpp = mp.get(i).getMountPoint(); + File root = new File(mpp); + mDisksUsage.put(mpp, createDiskUsage(root)); + } + } else { + mDisksUsage.put(mSrc, createDiskUsage(new File(this.mSrc))); + } + + if (isTrace()) { + Log.v(TAG, "Result: OK"); //$NON-NLS-1$ + } + } + + /** + * Method that create a reference of the disk usage from the root file + * + * @param root The root file + * @return DiskUsage The disk usage + */ + private DiskUsage createDiskUsage(File root) { + long total = root.getTotalSpace(); + long free = root.getFreeSpace(); + DiskUsage du = new DiskUsage( + root.getAbsolutePath(), + total, + total - free, + free); + if (isTrace()) { + Log.v(TAG, du.toString()); + } + return du; + } + +} diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/java/FindCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/java/FindCommand.java new file mode 100644 index 000000000..f253c3197 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/java/FindCommand.java @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.java; + +import android.util.Log; + +import me.toolify.backbone.commands.AsyncResultListener; +import me.toolify.backbone.commands.FindExecutable; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.console.NoSuchFileOrDirectory; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.model.Query; +import me.toolify.backbone.util.FileHelper; +import me.toolify.backbone.util.SearchHelper; + +import java.io.File; +import java.util.Arrays; + +/** + * A class for search files. + */ +public class FindCommand extends Program implements FindExecutable { + + private static final String TAG = "FindCommand"; //$NON-NLS-1$ + + private final String mDirectory; + private final String[] mQueryRegExp; + private final AsyncResultListener mAsyncResultListener; + + private boolean mCancelled; + private boolean mEnded; + private final Object mSync = new Object(); + + /** + * Constructor of FindCommand. + * + * @param directory The absolute directory where start the search + * @param query The terms to be searched + * @param asyncResultListener The partial result listener + */ + public FindCommand(String directory, Query query, AsyncResultListener asyncResultListener) { + super(); + this.mDirectory = directory; + this.mQueryRegExp = createRegexp(directory, query); + this.mAsyncResultListener = asyncResultListener; + this.mCancelled = false; + this.mEnded = false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isAsynchronous() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public void execute() + throws InsufficientPermissionsException, NoSuchFileOrDirectory, ExecutionException { + if (isTrace()) { + Log.v(TAG, + String.format("Finding in %s the query %s", //$NON-NLS-1$ + this.mDirectory, Arrays.toString(this.mQueryRegExp))); + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onAsyncStart(); + } + + File f = new File(this.mDirectory); + if (!f.exists()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$ + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onException(new NoSuchFileOrDirectory(this.mDirectory)); + } + } + if (!f.isDirectory()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$ + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onException( + new ExecutionException("path exists but it's not a folder")); //$NON-NLS-1$ + } + } + + // Find the data + findRecursive(f); + + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onAsyncEnd(this.mCancelled); + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onAsyncExitCode(0); + } + + if (isTrace()) { + Log.v(TAG, "Result: OK"); //$NON-NLS-1$ + } + } + + /** + * Method that search files recursively + * + * @param folder The folder where to start the search + */ + private void findRecursive(File folder) { + // Obtains the files and folders of the folders + File[] files = folder.listFiles(); + if (files != null) { + int cc = files.length; + for (int i = 0; i < cc; i++) { + if (files[i].isDirectory()) { + findRecursive(files[i]); + } + + // Check if the file or folder matches the regexp + try { + int ccc = this.mQueryRegExp.length; + for (int j = 0; j < ccc; j++) { + if (files[i].getName().matches(this.mQueryRegExp[j])) { + FileSystemObject fso = + FileHelper.createFileSystemObject(files[i]); + if (fso != null) { + if (isTrace()) { + Log.v(TAG, String.valueOf(fso)); + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onPartialResult(fso); + } + } + } + } + } catch (Exception e) {/**NON-BLOCK**/} + + // Check if the process was cancelled + try { + synchronized (this.mSync) { + if (this.mCancelled || this.mEnded) { + this.mSync.notify(); + break; + } + } + } catch (Exception e) {/**NON BLOCK**/} + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isCancelled() { + synchronized (this.mSync) { + return this.mCancelled; + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean cancel() { + try { + synchronized (this.mSync) { + this.mCancelled = true; + this.mSync.wait(5000L); + } + } catch (Exception e) {/**NON BLOCK**/} + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean end() { + try { + synchronized (this.mSync) { + this.mEnded = true; + this.mSync.wait(5000L); + } + } catch (Exception e) {/**NON BLOCK**/} + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public void setOnEndListener(OnEndListener onEndListener) { + //Ignore. Java console don't use this + } + + /** + * {@inheritDoc} + */ + @Override + public void setOnCancelListener(OnCancelListener onCancelListener) { + //Ignore. Java console don't use this + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isCancellable() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public AsyncResultListener getAsyncResultListener() { + return this.mAsyncResultListener; + } + + /** + * Method that create the regexp of this command, using the directory and + * arguments and creating the regular expressions of the search. + * + * @param directory The directory where to search + * @param query The query make for user + * @return String[] The regexp for filtering files + */ + private static String[] createRegexp(String directory, Query query) { + String[] args = new String[query.getSlotsCount()]; + int cc = query.getSlotsCount(); + for (int i = 0; i < cc; i++) { + args[i] = SearchHelper.toIgnoreCaseRegExp(query.getSlot(i), true); + } + return args; + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/java/FolderUsageCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/java/FolderUsageCommand.java new file mode 100644 index 000000000..74ffd4384 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/java/FolderUsageCommand.java @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.java; + +import android.util.Log; + +import me.toolify.backbone.commands.AsyncResultListener; +import me.toolify.backbone.commands.FolderUsageExecutable; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.console.NoSuchFileOrDirectory; +import me.toolify.backbone.model.FolderUsage; +import me.toolify.backbone.util.MimeTypeHelper; +import me.toolify.backbone.util.MimeTypeHelper.MimeTypeCategory; + +import java.io.File; + +/** + * A class for retrieve the disk usage of a folder. + */ +public class FolderUsageCommand extends Program implements FolderUsageExecutable { + + private static final String TAG = "FolderUsage"; //$NON-NLS-1$ + + private final String mDirectory; + private final AsyncResultListener mAsyncResultListener; + private final FolderUsage mFolderUsage; + + private boolean mCancelled; + private boolean mEnded; + private final Object mSync = new Object(); + + /** + * Constructor of FolderUsageCommand. + * + * @param directory The absolute directory to compute + * @param asyncResultListener The partial result listener + */ + public FolderUsageCommand( + String directory, AsyncResultListener asyncResultListener) { + super(); + this.mDirectory = directory; + this.mAsyncResultListener = asyncResultListener; + this.mFolderUsage = new FolderUsage(directory); + this.mCancelled = false; + this.mEnded = false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isAsynchronous() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public FolderUsage getFolderUsage() { + return this.mFolderUsage; + } + + /** + * {@inheritDoc} + */ + @Override + public void execute() + throws InsufficientPermissionsException, NoSuchFileOrDirectory, ExecutionException { + if (isTrace()) { + Log.v(TAG, + String.format("Computing folder usage for folder %s", //$NON-NLS-1$ + this.mDirectory)); + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onAsyncStart(); + } + + File f = new File(this.mDirectory); + if (!f.exists()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$ + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onException(new NoSuchFileOrDirectory(this.mDirectory)); + } + } + if (!f.isDirectory()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$ + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onException( + new ExecutionException("path exists but it's not a folder")); //$NON-NLS-1$ + } + } + + // Compute data recursively + computeRecursive(f); + + synchronized (this.mSync) { + this.mEnded = true; + this.mSync.notify(); + } + + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onAsyncEnd(this.mCancelled); + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onAsyncExitCode(0); + } + + if (isTrace()) { + Log.v(TAG, "Result: OK"); //$NON-NLS-1$ + } + } + + /** + * Method that computes the folder usage recursively + * + * @param folder The folder where to start the computation + */ + private void computeRecursive(File folder) { + // Obtains the files and folders of the folders + try { + File[] files = folder.listFiles(); + int c = 0; + if (files != null) { + int cc = files.length; + for (int i = 0; i < cc; i++) { + if (files[i].isDirectory()) { + this.mFolderUsage.addFolder(); + computeRecursive(files[i]); + } else { + this.mFolderUsage.addFile(); + // Compute statistics and size + MimeTypeCategory category = + MimeTypeHelper.getCategory(null, files[i]); + this.mFolderUsage.addFileToCategory(category); + this.mFolderUsage.addSize(files[i].length()); + } + + // Partial notification + if (c % 5 == 0) { + //If a listener is defined, then send the partial result + if (getAsyncResultListener() != null) { + getAsyncResultListener().onPartialResult(this.mFolderUsage); + } + } + + // Check if the process was cancelled + try { + synchronized (this.mSync) { + if (this.mCancelled || this.mEnded) { + this.mSync.notify(); + break; + } + } + } catch (Exception e) {/**NON BLOCK**/} + } + } + } finally { + //If a listener is defined, then send the partial result + if (getAsyncResultListener() != null) { + getAsyncResultListener().onPartialResult(this.mFolderUsage); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isCancelled() { + synchronized (this.mSync) { + return this.mCancelled; + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean cancel() { + try { + synchronized (this.mSync) { + if (this.mEnded || this.mCancelled) { + this.mCancelled = true; + return true; + } + this.mCancelled = true; + this.mSync.wait(5000L); + } + } catch (Exception e) {/**NON BLOCK**/} + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean end() { + try { + synchronized (this.mSync) { + this.mEnded = true; + this.mSync.wait(5000L); + } + } catch (Exception e) {/**NON BLOCK**/} + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public void setOnEndListener(OnEndListener onEndListener) { + //Ignore. Java console don't use this + } + + /** + * {@inheritDoc} + */ + @Override + public void setOnCancelListener(OnCancelListener onCancelListener) { + //Ignore. Java console don't use this + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isCancellable() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public AsyncResultListener getAsyncResultListener() { + return this.mAsyncResultListener; + } +} diff --git a/src/com/cyanogenmod/filemanager/commands/java/JavaExecutableCreator.java b/Backbone/src/main/java/me/toolify/backbone/commands/java/JavaExecutableCreator.java similarity index 76% rename from src/com/cyanogenmod/filemanager/commands/java/JavaExecutableCreator.java rename to Backbone/src/main/java/me/toolify/backbone/commands/java/JavaExecutableCreator.java index 875544efe..9a5e5efda 100644 --- a/src/com/cyanogenmod/filemanager/commands/java/JavaExecutableCreator.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/java/JavaExecutableCreator.java @@ -14,51 +14,50 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands.java; - -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.commands.AsyncResultListener; -import com.cyanogenmod.filemanager.commands.ChangeCurrentDirExecutable; -import com.cyanogenmod.filemanager.commands.ChangeOwnerExecutable; -import com.cyanogenmod.filemanager.commands.ChangePermissionsExecutable; -import com.cyanogenmod.filemanager.commands.CompressExecutable; -import com.cyanogenmod.filemanager.commands.CopyExecutable; -import com.cyanogenmod.filemanager.commands.CreateDirExecutable; -import com.cyanogenmod.filemanager.commands.CreateFileExecutable; -import com.cyanogenmod.filemanager.commands.CurrentDirExecutable; -import com.cyanogenmod.filemanager.commands.DeleteDirExecutable; -import com.cyanogenmod.filemanager.commands.DeleteFileExecutable; -import com.cyanogenmod.filemanager.commands.DiskUsageExecutable; -import com.cyanogenmod.filemanager.commands.EchoExecutable; -import com.cyanogenmod.filemanager.commands.ExecExecutable; -import com.cyanogenmod.filemanager.commands.ExecutableCreator; -import com.cyanogenmod.filemanager.commands.FindExecutable; -import com.cyanogenmod.filemanager.commands.FolderUsageExecutable; -import com.cyanogenmod.filemanager.commands.GroupsExecutable; -import com.cyanogenmod.filemanager.commands.IdentityExecutable; -import com.cyanogenmod.filemanager.commands.LinkExecutable; -import com.cyanogenmod.filemanager.commands.ListExecutable; -import com.cyanogenmod.filemanager.commands.ListExecutable.LIST_MODE; -import com.cyanogenmod.filemanager.commands.MountExecutable; -import com.cyanogenmod.filemanager.commands.MountPointInfoExecutable; -import com.cyanogenmod.filemanager.commands.MoveExecutable; -import com.cyanogenmod.filemanager.commands.ParentDirExecutable; -import com.cyanogenmod.filemanager.commands.ProcessIdExecutable; -import com.cyanogenmod.filemanager.commands.QuickFolderSearchExecutable; -import com.cyanogenmod.filemanager.commands.ReadExecutable; -import com.cyanogenmod.filemanager.commands.ResolveLinkExecutable; -import com.cyanogenmod.filemanager.commands.SIGNAL; -import com.cyanogenmod.filemanager.commands.SendSignalExecutable; -import com.cyanogenmod.filemanager.commands.UncompressExecutable; -import com.cyanogenmod.filemanager.commands.WriteExecutable; -import com.cyanogenmod.filemanager.console.CommandNotFoundException; -import com.cyanogenmod.filemanager.console.java.JavaConsole; -import com.cyanogenmod.filemanager.model.Group; -import com.cyanogenmod.filemanager.model.MountPoint; -import com.cyanogenmod.filemanager.model.Permissions; -import com.cyanogenmod.filemanager.model.Query; -import com.cyanogenmod.filemanager.model.User; -import com.cyanogenmod.filemanager.preferences.CompressionMode; +package me.toolify.backbone.commands.java; + +import me.toolify.backbone.R; +import me.toolify.backbone.commands.AsyncResultListener; +import me.toolify.backbone.commands.ChangeOwnerExecutable; +import me.toolify.backbone.commands.ChangePermissionsExecutable; +import me.toolify.backbone.commands.ChecksumExecutable; +import me.toolify.backbone.commands.CompressExecutable; +import me.toolify.backbone.commands.CopyExecutable; +import me.toolify.backbone.commands.CreateDirExecutable; +import me.toolify.backbone.commands.CreateFileExecutable; +import me.toolify.backbone.commands.DeleteDirExecutable; +import me.toolify.backbone.commands.DeleteFileExecutable; +import me.toolify.backbone.commands.DiskUsageExecutable; +import me.toolify.backbone.commands.EchoExecutable; +import me.toolify.backbone.commands.ExecExecutable; +import me.toolify.backbone.commands.ExecutableCreator; +import me.toolify.backbone.commands.FindExecutable; +import me.toolify.backbone.commands.FolderUsageExecutable; +import me.toolify.backbone.commands.GroupsExecutable; +import me.toolify.backbone.commands.IdentityExecutable; +import me.toolify.backbone.commands.LinkExecutable; +import me.toolify.backbone.commands.ListExecutable; +import me.toolify.backbone.commands.ListExecutable.LIST_MODE; +import me.toolify.backbone.commands.MountExecutable; +import me.toolify.backbone.commands.MountPointInfoExecutable; +import me.toolify.backbone.commands.MoveExecutable; +import me.toolify.backbone.commands.ParentDirExecutable; +import me.toolify.backbone.commands.ProcessIdExecutable; +import me.toolify.backbone.commands.QuickFolderSearchExecutable; +import me.toolify.backbone.commands.ReadExecutable; +import me.toolify.backbone.commands.ResolveLinkExecutable; +import me.toolify.backbone.commands.SIGNAL; +import me.toolify.backbone.commands.SendSignalExecutable; +import me.toolify.backbone.commands.UncompressExecutable; +import me.toolify.backbone.commands.WriteExecutable; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.java.JavaConsole; +import me.toolify.backbone.model.Group; +import me.toolify.backbone.model.MountPoint; +import me.toolify.backbone.model.Permissions; +import me.toolify.backbone.model.Query; +import me.toolify.backbone.model.User; +import me.toolify.backbone.preferences.CompressionMode; /** * A class for create shell {@link "Executable"} objects. @@ -77,15 +76,6 @@ public class JavaExecutableCreator implements ExecutableCreator { this.mConsole = console; } - /** - * {@inheritDoc} - */ - @Override - public ChangeCurrentDirExecutable createChangeCurrentDirExecutable(String dir) - throws CommandNotFoundException { - return new ChangeCurrentDirCommand(this.mConsole, dir); - } - /** * {@inheritDoc} */ @@ -131,14 +121,6 @@ public CreateFileExecutable createCreateFileExecutable(String file) return new CreateFileCommand(file); } - /** - * {@inheritDoc} - */ - @Override - public CurrentDirExecutable createCurrentDirExecutable() throws CommandNotFoundException { - return new CurrentDirCommand(this.mConsole); - } - /** * {@inheritDoc} */ @@ -302,6 +284,15 @@ public ProcessIdExecutable createShellProcessIdExecutable() throws CommandNotFou throw new CommandNotFoundException("Not implemented"); //$NON-NLS-1$ } + /** + * {@inheritDoc} + */ + @Override + public ProcessIdExecutable createProcessIdExecutable(int pid) + throws CommandNotFoundException { + throw new CommandNotFoundException("Not implemented"); //$NON-NLS-1$ + } + /** * {@inheritDoc} */ @@ -400,4 +391,14 @@ public UncompressExecutable createUncompressExecutable( throw new CommandNotFoundException("Not implemented"); //$NON-NLS-1$ } + /** + * {@inheritDoc} + */ + @Override + public ChecksumExecutable createChecksumExecutable( + String src, AsyncResultListener asyncResultListener) + throws CommandNotFoundException { + return new ChecksumCommand(src, asyncResultListener); + } + } diff --git a/src/com/cyanogenmod/filemanager/commands/java/JavaExecutableFactory.java b/Backbone/src/main/java/me/toolify/backbone/commands/java/JavaExecutableFactory.java similarity index 83% rename from src/com/cyanogenmod/filemanager/commands/java/JavaExecutableFactory.java rename to Backbone/src/main/java/me/toolify/backbone/commands/java/JavaExecutableFactory.java index 0220e0ff7..a474ba00f 100644 --- a/src/com/cyanogenmod/filemanager/commands/java/JavaExecutableFactory.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/java/JavaExecutableFactory.java @@ -14,11 +14,11 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands.java; +package me.toolify.backbone.commands.java; -import com.cyanogenmod.filemanager.commands.ExecutableCreator; -import com.cyanogenmod.filemanager.commands.ExecutableFactory; -import com.cyanogenmod.filemanager.console.java.JavaConsole; +import me.toolify.backbone.commands.ExecutableCreator; +import me.toolify.backbone.commands.ExecutableFactory; +import me.toolify.backbone.console.java.JavaConsole; /** * A class that represents a factory for creating java {@link "Executable"} objects. */ diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/java/ListCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/java/ListCommand.java new file mode 100644 index 000000000..f58b3c572 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/java/ListCommand.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.java; + +import android.util.Log; + +import me.toolify.backbone.commands.ListExecutable; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.console.NoSuchFileOrDirectory; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.model.ParentDirectory; +import me.toolify.backbone.util.FileHelper; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + + +/** + * A class for list information about files and directories. + */ +public class ListCommand extends Program implements ListExecutable { + + private static final String TAG = "ListCommand"; //$NON-NLS-1$ + + private final String mSrc; + private final LIST_MODE mMode; + private final List mFiles; + + /** + * Constructor of ListCommand. List mode. + * + * @param src The file system object to be listed + * @param mode The mode of listing + */ + public ListCommand(String src, LIST_MODE mode) { + super(); + this.mSrc = src; + this.mMode = mode; + this.mFiles = new ArrayList(); + } + + /** + * {@inheritDoc} + */ + @Override + public List getResult() { + return this.mFiles; + } + + /** + * Method that returns a single result of the program invocation. + * Only must be called within a FILEINFO mode listing. + * + * @return FileSystemObject The file system object reference + */ + public FileSystemObject getSingleResult() { + return this.mFiles.get(0); + } + + /** + * {@inheritDoc} + */ + @Override + public void execute() + throws InsufficientPermissionsException, NoSuchFileOrDirectory, ExecutionException { + if (isTrace()) { + Log.v(TAG, + String.format("Listing %s. Mode: %s", //$NON-NLS-1$ + this.mSrc, this.mMode)); + } + + File f = new File(this.mSrc); + if (!f.exists()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$ + } + throw new NoSuchFileOrDirectory(this.mSrc); + } + if (this.mMode.compareTo(LIST_MODE.DIRECTORY) == 0) { + File[] files = f.listFiles(); + if (files != null) { + for (int i = 0; i < files.length; i++) { + FileSystemObject fso = FileHelper.createFileSystemObject(files[i]); + if (fso != null) { + if (isTrace()) { + Log.v(TAG, String.valueOf(fso)); + } + this.mFiles.add(fso); + } + } + } + + //Now if not is the root directory + if (this.mSrc != null && + this.mSrc.compareTo(FileHelper.ROOT_DIRECTORY) != 0 && + this.mMode.compareTo(LIST_MODE.DIRECTORY) == 0) { + this.mFiles.add(0, new ParentDirectory(new File(this.mSrc).getParent())); + } + + } else { + // Build the parent information + FileSystemObject fso = FileHelper.createFileSystemObject(f); + if (fso != null) { + if (isTrace()) { + Log.v(TAG, String.valueOf(fso)); + } + this.mFiles.add(fso); + } + } + + if (isTrace()) { + Log.v(TAG, "Result: OK"); //$NON-NLS-1$ + } + } + +} diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/java/MountPointInfoCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/java/MountPointInfoCommand.java new file mode 100644 index 000000000..c0b4fcfd9 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/java/MountPointInfoCommand.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.java; + +import android.util.Log; + +import me.toolify.backbone.commands.MountPointInfoExecutable; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.console.NoSuchFileOrDirectory; +import me.toolify.backbone.model.MountPoint; +import me.toolify.backbone.util.ParseHelper; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.util.ArrayList; +import java.util.List; + + +/** + * A class for get information about disk usage. + */ +public class MountPointInfoCommand extends Program implements MountPointInfoExecutable { + + private static final String TAG = "MountPointInfoCommand"; //$NON-NLS-1$ + + private final String mMountsFile; + private final List mMountPoints; + + /** + * Constructor of MountPointInfoCommand. + * + * @param mountsFile The system mounts file + */ + public MountPointInfoCommand(String mountsFile) { + super(); + this.mMountPoints = new ArrayList(); + this.mMountsFile = mountsFile; + } + + /** + * {@inheritDoc} + */ + @Override + public List getResult() { + return this.mMountPoints; + } + + /** + * {@inheritDoc} + */ + @Override + public void execute() + throws InsufficientPermissionsException, NoSuchFileOrDirectory, ExecutionException { + + if (isTrace()) { + Log.v(TAG, + String.format("Getting usage from %s", //$NON-NLS-1$ + this.mMountsFile)); + } + + // Read the file with the mount information + BufferedReader br = null; + try { + br = new BufferedReader(new FileReader(new File(this.mMountsFile)), getBufferSize()); + StringBuilder sb = new StringBuilder(); + int read = 0; + char[] data = new char[getBufferSize()]; + while ((read = br.read(data, 0, getBufferSize())) != -1) { + sb.append(data, 0, read); + } + + // Send to parse + String[] lines = sb.toString().split("\n"); //$NON-NLS-1$ + for (int i = 0; i < lines.length; i++) { + MountPoint mp = ParseHelper.toMountPoint(lines[i]); + if (isTrace()) { + Log.v(TAG, String.valueOf(mp)); + } + this.mMountPoints.add(mp); + } + + } catch (Exception e) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. InsufficientPermissionsException"); //$NON-NLS-1$ + } + throw new InsufficientPermissionsException(); + + } finally { + try { + if (br != null) { + br.close(); + } + } catch (Throwable _throw) {/**NON BLOCK**/} + } + + if (isTrace()) { + Log.v(TAG, "Result: OK"); //$NON-NLS-1$ + } + } + +} diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/java/MoveCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/java/MoveCommand.java new file mode 100644 index 000000000..e7f1ecf12 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/java/MoveCommand.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.java; + +import android.util.Log; + +import me.toolify.backbone.commands.MoveExecutable; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.console.NoSuchFileOrDirectory; +import me.toolify.backbone.model.MountPoint; +import me.toolify.backbone.util.FileHelper; +import me.toolify.backbone.util.MountPointHelper; + +import java.io.File; + + +/** + * A class for move a file or directory. + */ +public class MoveCommand extends Program implements MoveExecutable { + + private static final String TAG = "MoveCommand"; //$NON-NLS-1$ + + private final String mSrc; + private final String mDst; + + /** + * Constructor of MoveCommand. + * + * @param src The name of the file or directory to be moved + * @param dst The name of the file or directory in which move the source file or directory + */ + public MoveCommand(String src, String dst) { + super(); + this.mSrc = src; + this.mDst = dst; + } + + /** + * {@inheritDoc} + */ + @Override + public Boolean getResult() { + return Boolean.TRUE; + } + + /** + * {@inheritDoc} + */ + @Override + public void execute() + throws InsufficientPermissionsException, NoSuchFileOrDirectory, ExecutionException { + if (isTrace()) { + Log.v(TAG, + String.format("Creating from %s to %s", this.mSrc, this.mDst)); //$NON-NLS-1$ + } + + File s = new File(this.mSrc); + File d = new File(this.mDst); + if (!s.exists()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$ + } + throw new NoSuchFileOrDirectory(this.mSrc); + } + + //Move or copy recursively + if (d.exists()) { + if (!FileHelper.copyRecursive(s, d, getBufferSize())) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. InsufficientPermissionsException"); //$NON-NLS-1$ + } + throw new InsufficientPermissionsException(); + } + if (!FileHelper.deleteFolder(s)) { + if (isTrace()) { + Log.v(TAG, "Result: OK. WARNING. Source not deleted."); //$NON-NLS-1$ + } + } + } else { + // Move between filesystem is not allow. If rename fails then use copy operation + if (!s.renameTo(d)) { + if (!FileHelper.copyRecursive(s, d, getBufferSize())) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. InsufficientPermissionsException"); //$NON-NLS-1$ + } + throw new InsufficientPermissionsException(); + } + if (!FileHelper.deleteFolder(s)) { + if (isTrace()) { + Log.v(TAG, "Result: OK. WARNING. Source not deleted."); //$NON-NLS-1$ + } + } + } + } + + if (isTrace()) { + Log.v(TAG, "Result: OK"); //$NON-NLS-1$ + } + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getSrcWritableMountPoint() { + return MountPointHelper.getMountPointFromDirectory(this.mSrc); + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getDstWritableMountPoint() { + return MountPointHelper.getMountPointFromDirectory(this.mDst); + } + +} diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/java/ParentDirCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/java/ParentDirCommand.java new file mode 100644 index 000000000..ea9fec57d --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/java/ParentDirCommand.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.java; + +import android.util.Log; + +import me.toolify.backbone.commands.ParentDirExecutable; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.console.NoSuchFileOrDirectory; + +import java.io.File; + + +/** + * A class for returns the parent directory. + */ +public class ParentDirCommand extends Program implements ParentDirExecutable { + + private static final String TAG = "ParentDirCommand"; //$NON-NLS-1$ + + private final String mSrc; + private String mParentDir; + + /** + * Constructor of ParentDirCommand. + * + * @param src The source file + */ + public ParentDirCommand(String src) { + super(); + this.mSrc = src; + } + + /** + * {@inheritDoc} + */ + @Override + public String getResult() { + return this.mParentDir; + } + + /** + * {@inheritDoc} + */ + @Override + public void execute() + throws InsufficientPermissionsException, NoSuchFileOrDirectory, ExecutionException { + if (isTrace()) { + Log.v(TAG, + String.format("Getting parent directory of %s", //$NON-NLS-1$ + this.mSrc)); + } + + File f = new File(this.mSrc); + this.mParentDir = f.getParent(); + + if (isTrace()) { + Log.v(TAG, + String.format("Parent directory: %S", //$NON-NLS-1$ + this.mParentDir)); + } + + if (isTrace()) { + Log.v(TAG, "Result: OK"); //$NON-NLS-1$ + } + } + +} diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/java/Program.java b/Backbone/src/main/java/me/toolify/backbone/commands/java/Program.java new file mode 100644 index 000000000..b017d8bc8 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/java/Program.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.java; + +import me.toolify.backbone.commands.Executable; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.console.NoSuchFileOrDirectory; + + +/** + * An abstract base class for all java executables. + */ +public abstract class Program implements Executable { + + private boolean mTrace; + private int mBufferSize; + + /** + * Constructor of Program + */ + public Program() { + super(); + } + + /** + * Method that return if the command has to trace his operations + * + * @return boolean If the command has to trace + */ + public boolean isTrace() { + return this.mTrace; + } + + /** + * Method that sets if the command has to trace his operations + * + * @param trace If the command has to trace + */ + public void setTrace(boolean trace) { + this.mTrace = trace; + } + + /** + * Method that return the buffer size of the program + * + * @return int The buffer size of the program + */ + public int getBufferSize() { + return this.mBufferSize; + } + + /** + * Method that sets the buffer size of the program + * + * @param bufferSize The buffer size of the program + */ + public void setBufferSize(int bufferSize) { + this.mBufferSize = bufferSize; + } + + /** + * Method that returns if this program uses an asynchronous model. false + * by default. + * + * @return boolean If this program uses an asynchronous model + */ + @SuppressWarnings("static-method") + public boolean isAsynchronous() { + return false; + } + + /** + * Method that executes the program + * + * @throws NoSuchFileOrDirectory If the file or directory was not found + * @throws InsufficientPermissionsException If an operation requires elevated permissions + * @throws ExecutionException If the operation returns a invalid exit code + */ + public abstract void execute() + throws InsufficientPermissionsException, NoSuchFileOrDirectory, ExecutionException; + +} diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/java/ReadCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/java/ReadCommand.java new file mode 100644 index 000000000..464930d3e --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/java/ReadCommand.java @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.java; + +import android.util.Log; + +import me.toolify.backbone.commands.AsyncResultListener; +import me.toolify.backbone.commands.ReadExecutable; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.console.NoSuchFileOrDirectory; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; + +/** + * A class for read a file. + */ +public class ReadCommand extends Program implements ReadExecutable { + + private static final String TAG = "ReadCommand"; //$NON-NLS-1$ + + private final String mFile; + private final AsyncResultListener mAsyncResultListener; + + private boolean mCancelled; + private boolean mEnded; + private final Object mSync = new Object(); + + /** + * Constructor of ExecCommand. + * + * @param file The file to read + * @param asyncResultListener The partial result listener + */ + public ReadCommand( + String file, AsyncResultListener asyncResultListener) { + super(); + this.mFile = file; + this.mAsyncResultListener = asyncResultListener; + this.mCancelled = false; + this.mEnded = false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isAsynchronous() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public void execute() + throws InsufficientPermissionsException, NoSuchFileOrDirectory, ExecutionException { + if (isTrace()) { + Log.v(TAG, + String.format("Reading file %s", this.mFile)); //$NON-NLS-1$ + + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onAsyncStart(); + } + + File f = new File(this.mFile); + if (!f.exists()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$ + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onException(new NoSuchFileOrDirectory(this.mFile)); + } + } + if (!f.isFile()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. NoSuchFileOrDirectory"); //$NON-NLS-1$ + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onException( + new ExecutionException("path exists but it's not a file")); //$NON-NLS-1$ + } + } + + // Read the file + read(f); + + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onAsyncEnd(this.mCancelled); + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onAsyncExitCode(0); + } + + if (isTrace()) { + Log.v(TAG, "Result: OK"); //$NON-NLS-1$ + } + } + + /** + * Method that read the file + * + * @param file The file to read + */ + private void read(File file) { + // Read the file + BufferedInputStream bis = null; + try { + bis = new BufferedInputStream(new FileInputStream(file), getBufferSize()); + int read = 0; + byte[] data = new byte[getBufferSize()]; + while ((read = bis.read(data, 0, getBufferSize())) != -1) { + if (this.mAsyncResultListener != null) { + byte[] readData = new byte[read]; + System.arraycopy(data, 0, readData, 0, read); + this.mAsyncResultListener.onPartialResult(readData); + + // Check if the process was cancelled + try { + synchronized (this.mSync) { + if (this.mCancelled || this.mEnded) { + this.mSync.notify(); + break; + } + } + } catch (Exception e) {/**NON BLOCK**/} + } + } + + } catch (Exception e) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. InsufficientPermissionsException"); //$NON-NLS-1$ + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onException(new InsufficientPermissionsException()); + } + + } finally { + try { + if (bis != null) { + bis.close(); + } + } catch (Throwable _throw) {/**NON BLOCK**/} + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isCancelled() { + synchronized (this.mSync) { + return this.mCancelled; + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean cancel() { + try { + synchronized (this.mSync) { + this.mCancelled = true; + this.mSync.wait(5000L); + } + } catch (Exception e) {/**NON BLOCK**/} + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean end() { + try { + synchronized (this.mSync) { + this.mEnded = true; + this.mSync.wait(5000L); + } + } catch (Exception e) {/**NON BLOCK**/} + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public void setOnEndListener(OnEndListener onEndListener) { + //Ignore. Java console don't use this + } + + /** + * {@inheritDoc} + */ + @Override + public void setOnCancelListener(OnCancelListener onCancelListener) { + //Ignore. Java console don't use this + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isCancellable() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public AsyncResultListener getAsyncResultListener() { + return this.mAsyncResultListener; + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/java/ResolveLinkCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/java/ResolveLinkCommand.java new file mode 100644 index 000000000..ad32c335c --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/java/ResolveLinkCommand.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.java; + +import android.util.Log; + +import me.toolify.backbone.commands.ListExecutable.LIST_MODE; +import me.toolify.backbone.commands.ResolveLinkExecutable; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.console.NoSuchFileOrDirectory; +import me.toolify.backbone.model.FileSystemObject; + +import java.io.File; + + +/** + * A class for retrieve the real file name of a symlink. This command + * can be used too for retrieve the absolute path of a file or directory + */ +public class ResolveLinkCommand extends Program implements ResolveLinkExecutable { + + private static final String TAG = "ResolveLinkCommand"; //$NON-NLS-1$ + + private final String mSrc; + private FileSystemObject mFso; + + /** + * Constructor of ResolveLinkCommand. + * + * @param src The file system object to read + */ + public ResolveLinkCommand(String src) { + super(); + this.mSrc = src; + } + + /** + * {@inheritDoc} + */ + @Override + public FileSystemObject getResult() { + return this.mFso; + } + + /** + * {@inheritDoc} + */ + @Override + public void execute() + throws InsufficientPermissionsException, NoSuchFileOrDirectory, ExecutionException { + if (isTrace()) { + Log.v(TAG, + String.format("Resolving link of %s", //$NON-NLS-1$ + this.mSrc)); + } + + File f = new File(this.mSrc); + if (!f.exists()) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. InsufficientPermissionsException"); //$NON-NLS-1$ + } + throw new InsufficientPermissionsException(); + } + try { + String absPath = f.getCanonicalPath(); + ListCommand cmd = new ListCommand(absPath, LIST_MODE.FILEINFO); + cmd.execute(); + this.mFso = cmd.getSingleResult(); + } catch (Exception e) { + if (isTrace()) { + Log.v(TAG, "Result: FAIL. ExecutionException"); //$NON-NLS-1$ + } + throw new ExecutionException("can't resolve link"); //$NON-NLS-1$ + } + + if (isTrace()) { + Log.v(TAG, + String.format("Link: %s", //$NON-NLS-1$ + this.mFso)); + } + + if (isTrace()) { + Log.v(TAG, "Result: OK"); //$NON-NLS-1$ + } + } + +} diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/java/WriteCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/java/WriteCommand.java new file mode 100644 index 000000000..d46b3fe08 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/java/WriteCommand.java @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.java; + +import android.util.Log; + +import me.toolify.backbone.commands.AsyncResultListener; +import me.toolify.backbone.commands.WriteExecutable; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.console.NoSuchFileOrDirectory; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * A class for write data to disk.
+ *
+ * User MUST call the {@link #createOutputStream()} to get the output stream where + * write the data.
. When no more exist then user MUST call the onEnd method + * of the asynchronous listener.
+ */ +public class WriteCommand extends Program implements WriteExecutable { + + private static final String TAG = "WriteCommand"; //$NON-NLS-1$ + + private final String mFile; + private BufferedOutputStream mBuffer; + private final AsyncResultListener mAsyncResultListener; + + private boolean mCancelled; + private final Object mSync = new Object(); + + private static final long TIMEOUT = 1000L; + + private final Object mWriteSync = new Object(); + private boolean mReady; + + /** + * Constructor of WriteCommand. + * + * @param file The file where to write the data + * @param asyncResultListener The partial result listener + */ + public WriteCommand( + String file, AsyncResultListener asyncResultListener) { + super(); + this.mFile = file; + this.mAsyncResultListener = asyncResultListener; + this.mCancelled = false; + this.mReady = false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isAsynchronous() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public OutputStream createOutputStream() throws IOException { + try { + // Wait until command is ready + synchronized (this.mWriteSync) { + if (!this.mReady) { + try { + this.mWriteSync.wait(TIMEOUT); + } catch (Exception e) {/**NON BLOCK**/} + } + } + this.mBuffer = new BufferedOutputStream( + new FileOutputStream( + new File(this.mFile)), getBufferSize()); + return this.mBuffer; + } catch (IOException ioEx) { + if (isTrace()) { + Log.e(TAG, "Result: FAILED. IOException", ioEx); //$NON-NLS-1$ + } + throw ioEx; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void execute() + throws InsufficientPermissionsException, NoSuchFileOrDirectory, ExecutionException { + synchronized (this.mSync) { + this.mReady = true; + this.mSync.notify(); + } + + if (isTrace()) { + Log.v(TAG, + String.format("Writing file %s", this.mFile)); //$NON-NLS-1$ + + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onAsyncStart(); + } + + // Wait the finalization + try { + synchronized (this.mSync) { + this.mSync.wait(); + } + } catch (Throwable _throw) {/**NON BLOCK**/} + + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onAsyncEnd(this.mCancelled); + } + if (this.mAsyncResultListener != null) { + this.mAsyncResultListener.onAsyncExitCode(0); + } + + if (isTrace()) { + Log.v(TAG, "Result: OK"); //$NON-NLS-1$ + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isCancelled() { + synchronized (this.mSync) { + return this.mCancelled; + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean cancel() { + closeBuffer(); + this.mCancelled = true; + try { + synchronized (this.mSync) { + this.mSync.notify(); + } + } catch (Throwable _throw) {/**NON BLOCK**/} + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean end() { + closeBuffer(); + try { + synchronized (this.mSync) { + this.mSync.notify(); + } + } catch (Throwable _throw) {/**NON BLOCK**/} + return true; + } + + /** + * Method that close the buffer + */ + private void closeBuffer() { + try { + if (this.mBuffer != null) { + this.mBuffer.close(); + } + } catch (Exception ex) {/**NON BLOCK**/} + try { + Thread.yield(); + } catch (Exception ex) {/**NON BLOCK**/} + } + + /** + * {@inheritDoc} + */ + @Override + public void setOnEndListener(OnEndListener onEndListener) { + //Ignore. Java console don't use this + } + + /** + * {@inheritDoc} + */ + @Override + public void setOnCancelListener(OnCancelListener onCancelListener) { + //Ignore. Java console don't use this + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isCancellable() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public AsyncResultListener getAsyncResultListener() { + return this.mAsyncResultListener; + } +} diff --git a/src/com/cyanogenmod/filemanager/commands/shell/AsyncResultProgram.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/AsyncResultProgram.java similarity index 91% rename from src/com/cyanogenmod/filemanager/commands/shell/AsyncResultProgram.java rename to Backbone/src/main/java/me/toolify/backbone/commands/shell/AsyncResultProgram.java index 64dc0e53f..ca3908abf 100644 --- a/src/com/cyanogenmod/filemanager/commands/shell/AsyncResultProgram.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/AsyncResultProgram.java @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands.shell; +package me.toolify.backbone.commands.shell; -import com.cyanogenmod.filemanager.commands.AsyncResultExecutable; -import com.cyanogenmod.filemanager.commands.AsyncResultListener; -import com.cyanogenmod.filemanager.commands.SIGNAL; -import com.cyanogenmod.filemanager.util.FileHelper; +import me.toolify.backbone.commands.AsyncResultExecutable; +import me.toolify.backbone.commands.AsyncResultListener; +import me.toolify.backbone.commands.SIGNAL; +import me.toolify.backbone.util.FileHelper; import java.util.ArrayList; import java.util.Collections; @@ -51,7 +51,7 @@ public abstract class AsyncResultProgram * @hide */ final List mPartialDataType; - private final Object mSync = new Object(); + final Object mSync = new Object(); /** * @hide */ @@ -106,7 +106,7 @@ public AsyncResultProgram( * @hide */ public final void onRequestStartParsePartialResult() { - this.mWorkerThread = new AsyncResultProgramThread(this.mSync); + this.mWorkerThread = new AsyncResultProgramThread(); this.mWorkerThread.start(); //Notify start to command class @@ -131,17 +131,12 @@ public final void onRequestEndParsePartialResult(boolean cancelled) { this.mSync.notify(); } synchronized (this.mTerminateSync) { - try { - this.mSync.wait(); - } catch (Exception e) { - /**NON BLOCK**/ - } - try { - if (this.mWorkerThread.isAlive()) { - this.mWorkerThread.interrupt(); + if (this.mWorkerThread.isAlive()) { + try { + this.mTerminateSync.wait(); + } catch (Exception e) { + /**NON BLOCK**/ } - } catch (Exception e) { - /**NON BLOCK**/ } } @@ -327,6 +322,15 @@ public final void setOnEndListener(OnEndListener onEndListener) { this.mOnEndListener = onEndListener; } + /** + * {@inheritDoc} + */ + @Override + public final boolean isIndefinitelyWait() { + // Asynchronous programs should wait indefinitely for its nature + return true; + } + /** * {@inheritDoc} */ @@ -353,16 +357,12 @@ public boolean isExpectEnd() { */ private class AsyncResultProgramThread extends Thread { boolean mAlive = true; - private final Object mSyncObj; /** * Constructor of AsyncResultProgramThread. - * - * @param sync The synchronized object */ - AsyncResultProgramThread(Object sync) { + AsyncResultProgramThread() { super(); - this.mSyncObj = sync; } /** @@ -373,12 +373,9 @@ public void run() { try { this.mAlive = true; while (this.mAlive) { - synchronized (this.mSyncObj) { - this.mSyncObj.wait(); + synchronized (AsyncResultProgram.this.mSync) { + AsyncResultProgram.this.mSync.wait(); while (AsyncResultProgram.this.mPartialData.size() > 0) { - if (!this.mAlive) { - return; - } Byte type = AsyncResultProgram.this.mPartialDataType.remove(0); String data = AsyncResultProgram.this.mPartialData.remove(0); try { diff --git a/src/com/cyanogenmod/filemanager/commands/shell/AsyncResultProgramListener.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/AsyncResultProgramListener.java similarity index 94% rename from src/com/cyanogenmod/filemanager/commands/shell/AsyncResultProgramListener.java rename to Backbone/src/main/java/me/toolify/backbone/commands/shell/AsyncResultProgramListener.java index 158023c30..fd32ca8ba 100644 --- a/src/com/cyanogenmod/filemanager/commands/shell/AsyncResultProgramListener.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/AsyncResultProgramListener.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands.shell; +package me.toolify.backbone.commands.shell; -import com.cyanogenmod.filemanager.commands.SIGNAL; +import me.toolify.backbone.commands.SIGNAL; /** * An interface for communicate shell results in a asynchronous way. diff --git a/src/com/cyanogenmod/filemanager/commands/shell/BashShell.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/BashShell.java similarity index 95% rename from src/com/cyanogenmod/filemanager/commands/shell/BashShell.java rename to Backbone/src/main/java/me/toolify/backbone/commands/shell/BashShell.java index a357447ad..b9f116e09 100644 --- a/src/com/cyanogenmod/filemanager/commands/shell/BashShell.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/BashShell.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands.shell; +package me.toolify.backbone.commands.shell; /** diff --git a/src/com/cyanogenmod/filemanager/commands/shell/ChangeOwnerCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/ChangeOwnerCommand.java similarity index 82% rename from src/com/cyanogenmod/filemanager/commands/shell/ChangeOwnerCommand.java rename to Backbone/src/main/java/me/toolify/backbone/commands/shell/ChangeOwnerCommand.java index 999ac5400..d5234b9a9 100644 --- a/src/com/cyanogenmod/filemanager/commands/shell/ChangeOwnerCommand.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/ChangeOwnerCommand.java @@ -14,16 +14,16 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands.shell; +package me.toolify.backbone.commands.shell; -import com.cyanogenmod.filemanager.commands.ChangeOwnerExecutable; -import com.cyanogenmod.filemanager.console.CommandNotFoundException; -import com.cyanogenmod.filemanager.console.ExecutionException; -import com.cyanogenmod.filemanager.console.InsufficientPermissionsException; -import com.cyanogenmod.filemanager.model.Group; -import com.cyanogenmod.filemanager.model.MountPoint; -import com.cyanogenmod.filemanager.model.User; -import com.cyanogenmod.filemanager.util.MountPointHelper; +import me.toolify.backbone.commands.ChangeOwnerExecutable; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.model.Group; +import me.toolify.backbone.model.MountPoint; +import me.toolify.backbone.model.User; +import me.toolify.backbone.util.MountPointHelper; import java.text.ParseException; diff --git a/src/com/cyanogenmod/filemanager/commands/shell/ChangePermissionsCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/ChangePermissionsCommand.java similarity index 83% rename from src/com/cyanogenmod/filemanager/commands/shell/ChangePermissionsCommand.java rename to Backbone/src/main/java/me/toolify/backbone/commands/shell/ChangePermissionsCommand.java index 4c2bf60a4..70ef29e73 100644 --- a/src/com/cyanogenmod/filemanager/commands/shell/ChangePermissionsCommand.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/ChangePermissionsCommand.java @@ -14,15 +14,15 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands.shell; +package me.toolify.backbone.commands.shell; -import com.cyanogenmod.filemanager.commands.ChangePermissionsExecutable; -import com.cyanogenmod.filemanager.console.CommandNotFoundException; -import com.cyanogenmod.filemanager.console.ExecutionException; -import com.cyanogenmod.filemanager.console.InsufficientPermissionsException; -import com.cyanogenmod.filemanager.model.MountPoint; -import com.cyanogenmod.filemanager.model.Permissions; -import com.cyanogenmod.filemanager.util.MountPointHelper; +import me.toolify.backbone.commands.ChangePermissionsExecutable; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.model.MountPoint; +import me.toolify.backbone.model.Permissions; +import me.toolify.backbone.util.MountPointHelper; import java.text.ParseException; diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/shell/ChecksumCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/ChecksumCommand.java new file mode 100644 index 000000000..fc4ad567a --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/ChecksumCommand.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.shell; + +import me.toolify.backbone.commands.AsyncResultListener; +import me.toolify.backbone.commands.ChecksumExecutable; +import me.toolify.backbone.commands.SIGNAL; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; + +import java.io.File; + +/** + * A class for calculate MD5 and SHA-1 checksums of a file system object.
+ *
+ * Partial results are returned in order (MD5 -> SHA1) + * + * {@link "http://unixhelp.ed.ac.uk/CGI/man-cgi?md5sum"} + * {@link "http://unixhelp.ed.ac.uk/CGI/man-cgi?sha1sum"} + * @see me.toolify.backbone.commands.ChecksumExecutable.CHECKSUMS + */ +public class ChecksumCommand extends AsyncResultProgram implements ChecksumExecutable { + + private static final String ID = "checksum"; //$NON-NLS-1$ + + private final String mName; + private final String[] mChecksums; + private int mChecksumsCounter; + private String mPartial; + + /** + * Constructor of ChecksumCommand. + * + * @param src The source file + * @param asyncResultListener The partial result listener + * @throws InvalidCommandDefinitionException If the command has an invalid definition + */ + public ChecksumCommand(String src, AsyncResultListener asyncResultListener) + throws InvalidCommandDefinitionException { + super(ID, asyncResultListener, src); + this.mChecksums = new String[]{null, null}; + this.mName = new File(src).getName(); + } + + /** + * {@inheritDoc} + */ + @Override + public void onStartParsePartialResult() { + this.mChecksums[0] = null; + this.mChecksums[1] = null; + this.mChecksumsCounter = 0; + this.mPartial = ""; //$NON-NLS-1$ + } + + /** + * {@inheritDoc} + */ + @Override + public void onEndParsePartialResult(boolean cancelled) { + // Send the last partial data + if (this.mPartial != null && this.mPartial.length() > 0) { + if (getAsyncResultListener() != null) { + String data = processPartialResult(this.mPartial); + if (data != null) { + getAsyncResultListener().onPartialResult(data); + } + } + } + this.mPartial = ""; //$NON-NLS-1$ + } + + /** + * {@inheritDoc} + */ + @Override + public void onParsePartialResult(final String partialIn) { + if (partialIn == null || partialIn.length() ==0) return; + boolean endsWithNewLine = partialIn.endsWith("\n"); //$NON-NLS-1$ + String[] lines = partialIn.split("\n"); //$NON-NLS-1$ + + // Append the pending data to the first line + lines[0] = this.mPartial + lines[0]; + + // Return all the lines, except the last + for (int i = 0; i < lines.length-1; i++) { + if (getAsyncResultListener() != null) { + String data = processPartialResult(lines[i]); + if (data != null) { + getAsyncResultListener().onPartialResult(data); + } + } + } + + // Return the last line? + if (endsWithNewLine) { + if (getAsyncResultListener() != null) { + String data = processPartialResult(lines[lines.length-1]); + if (data != null) { + getAsyncResultListener().onPartialResult(data); + } + } + this.mPartial = ""; //$NON-NLS-1$ + } else { + // Save the partial for next calls + this.mPartial = lines[lines.length-1]; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onParseErrorPartialResult(String partialErr) {/**NON BLOCK**/} + + /** + * {@inheritDoc} + */ + @Override + public SIGNAL onRequestEnd() { + try { + if (this.getProgramListener().getOutputStream() != null) { + this.getProgramListener().getOutputStream().flush(); + } + } catch (Exception ex) {/**NON BLOCK**/} + try { + Thread.yield(); + } catch (Exception ex) {/**NON BLOCK**/} + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public String[] getResult() { + return this.mChecksums; + } + + /** + * {@inheritDoc} + */ + @Override + public String getChecksum(CHECKSUMS checksum) { + return getResult()[checksum.ordinal()]; + } + + /** + * Method that processes a line to determine if it's a valid partial result + * + * @param line The line to process + * @return String The processed line + */ + private String processPartialResult(String line) { + // MD5 and SHA-1 return both the digest and the name of the file + // 4c044b884cf2ff3839713da0e81dced19f099b09 boot.zip + int pos = line.indexOf(" "); //$NON-NLS-1$ + if (line.endsWith(this.mName) && pos != -1) { + String digest = line.substring(0, pos).trim(); + if (this.mChecksumsCounter < this.mChecksums.length) { + this.mChecksums[this.mChecksumsCounter] = digest; + } + this.mChecksumsCounter++; + return digest; + } + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public void checkExitCode(int exitCode) + throws InsufficientPermissionsException, CommandNotFoundException, ExecutionException { + //Ignore exit code 143 (cancelled) + //Ignore exit code 137 (kill -9) + if (exitCode != 0 && exitCode != 143 && exitCode != 137) { + throw new ExecutionException( + "exitcode != 0 && != 143 && != 137"); //$NON-NLS-1$ + } + } + +} diff --git a/src/com/cyanogenmod/filemanager/commands/shell/Command.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/Command.java similarity index 95% rename from src/com/cyanogenmod/filemanager/commands/shell/Command.java rename to Backbone/src/main/java/me/toolify/backbone/commands/shell/Command.java index cf9a9f5ce..b349ca425 100644 --- a/src/com/cyanogenmod/filemanager/commands/shell/Command.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/Command.java @@ -14,21 +14,19 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands.shell; +package me.toolify.backbone.commands.shell; import android.content.res.Resources; import android.content.res.XmlResourceParser; - -import com.android.internal.util.XmlUtils; -import com.cyanogenmod.filemanager.FileManagerApplication; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.console.CommandNotFoundException; -import com.cyanogenmod.filemanager.console.ExecutionException; -import com.cyanogenmod.filemanager.console.InsufficientPermissionsException; -import com.cyanogenmod.filemanager.preferences.FileManagerSettings; -import com.cyanogenmod.filemanager.preferences.Preferences; -import com.cyanogenmod.filemanager.util.ShellHelper; - +import me.toolify.backbone.FileManagerApplication; +import me.toolify.backbone.R; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.preferences.FileManagerSettings; +import me.toolify.backbone.preferences.Preferences; +import me.toolify.backbone.util.ShellHelper; +import me.toolify.backbone.util.XmlUtils; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/shell/CompressCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/CompressCommand.java new file mode 100644 index 000000000..560fe6106 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/CompressCommand.java @@ -0,0 +1,373 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.shell; + +import me.toolify.backbone.commands.AsyncResultListener; +import me.toolify.backbone.commands.CompressExecutable; +import me.toolify.backbone.commands.SIGNAL; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.preferences.CompressionMode; +import me.toolify.backbone.util.FileHelper; + +import java.io.File; + +/** + * A class for compress file system objects + * + * {@link "http://unixhelp.ed.ac.uk/CGI/man-cgi?tar"} + * {@link "http://unixhelp.ed.ac.uk/CGI/man-cgi?gzip"} + * {@link "http://unixhelp.ed.ac.uk/CGI/man-cgi?bzip2"} + */ +public class CompressCommand extends AsyncResultProgram implements CompressExecutable { + + /** + * An enumeration of implemented compression modes. + */ + private enum Mode { + /** + * Archive using Tar algorithm + */ + A_TAR(TAR_ID, "", CompressionMode.A_TAR), //$NON-NLS-1$ + /** + * Archive and compress using Gzip algorithm + */ + AC_GZIP(TAR_ID, "z", CompressionMode.AC_GZIP), //$NON-NLS-1$ + /** + * Archive and compress using Gzip algorithm + */ + AC_GZIP2(TAR_ID, "z", CompressionMode.AC_GZIP2), //$NON-NLS-1$ + /** + * Archive and compress using Bzip algorithm + */ + AC_BZIP(TAR_ID, "j", CompressionMode.AC_BZIP), //$NON-NLS-1$ + /** + * Compress using Gzip algorithm + */ + C_GZIP(GZIP_ID, "z", CompressionMode.C_GZIP), //$NON-NLS-1$ + /** + * Compress using Bzip algorithm + */ + C_BZIP(BZIP_ID, "j", CompressionMode.C_BZIP), //$NON-NLS-1$ + /** + * Archive using Zip algorithm + */ + A_ZIP(ZIP_ID, "", CompressionMode.A_ZIP); //$NON-NLS-1$ + + final String mId; + final String mFlag; + final CompressionMode mMode; + + /** + * Constructor of Mode + * + * @param id The command identifier + * @param flag The tar compression flag + * @param mode The compression mode + */ + private Mode(String id, String flag, CompressionMode mode) { + this.mId = id; + this.mFlag = flag; + this.mMode = mode; + } + + /** + * Method that return the mode from his compression mode + * + * @param mode The compression mode + * @return Mode The mode + */ + public static Mode fromCompressionMode(CompressionMode mode) { + Mode[] modes = Mode.values(); + int cc = modes.length; + for (int i = 0; i < cc; i++) { + if (modes[i].mMode.compareTo(mode) == 0) { + return modes[i]; + } + } + return null; + } + } + + private static final String TAR_ID = "tar"; //$NON-NLS-1$ + private static final String GZIP_ID = "gzip"; //$NON-NLS-1$ + private static final String BZIP_ID = "bzip"; //$NON-NLS-1$ + private static final String ZIP_ID = "zip"; //$NON-NLS-1$ + + private Boolean mResult; + private String mPartial; + + private final Mode mMode; + private final String mOutFile; + + /** + * Constructor of CompressCommand. This method creates an archive-compressed + * file from one or various file system objects. + * + * @param mode The compression mode + * @param dst The absolute path of the new compress file + * @param src An array of file system objects to compress + * @param asyncResultListener The partial result listener + * @throws InvalidCommandDefinitionException If the command has an invalid definition + */ + public CompressCommand( + CompressionMode mode, String dst, String[] src, AsyncResultListener asyncResultListener) + throws InvalidCommandDefinitionException { + super(Mode.fromCompressionMode(mode).mId, + asyncResultListener, + resolveArchiveArgs(Mode.fromCompressionMode(mode), dst)); + this.mMode = Mode.fromCompressionMode(mode); + + if (!this.mMode.mMode.mArchive) { + throw new InvalidCommandDefinitionException( + "Unsupported archive mode"); //$NON-NLS-1$ + } + + //Convert the arguments from absolute to relative + addExpandedArguments( + convertAbsolutePathsToRelativePaths(dst, src), true); + + // Create the output file + this.mOutFile = dst; + } + + /** + * Constructor of CompressCommand. This method creates a compressed + * file from one file. + * + * @param mode The compression mode + * @param src The file to compress + * @param asyncResultListener The partial result listener + * @throws InvalidCommandDefinitionException If the command has an invalid definition + */ + public CompressCommand( + CompressionMode mode, String src, AsyncResultListener asyncResultListener) + throws InvalidCommandDefinitionException { + super(Mode.fromCompressionMode(mode).mId, + asyncResultListener, + resolveCompressArgs(mode, src)); + this.mMode = Mode.fromCompressionMode(mode); + + if (this.mMode.mMode.mArchive) { + throw new InvalidCommandDefinitionException( + "Unsupported compression mode"); //$NON-NLS-1$ + } + + // Create the output file + this.mOutFile = resolveOutputFile(mode, src); + } + + /** + * {@inheritDoc} + */ + @Override + public void onStartParsePartialResult() { + this.mResult = Boolean.FALSE; + this.mPartial = ""; //$NON-NLS-1$ + } + + /** + * {@inheritDoc} + */ + @Override + public void onEndParsePartialResult(boolean cancelled) { + // Send the last partial data + if (this.mPartial != null && this.mPartial.length() > 0) { + if (getAsyncResultListener() != null) { + String data = processPartialResult(this.mPartial); + if (data != null) { + getAsyncResultListener().onPartialResult(data); + } + } + } + this.mPartial = ""; //$NON-NLS-1$ + } + + /** + * {@inheritDoc} + */ + @Override + public void onParsePartialResult(final String partialIn) { + if (partialIn == null || partialIn.length() ==0) return; + boolean endsWithNewLine = partialIn.endsWith("\n"); //$NON-NLS-1$ + String[] lines = partialIn.split("\n"); //$NON-NLS-1$ + + // Append the pending data to the first line + lines[0] = this.mPartial + lines[0]; + + // Return all the lines, except the last + int cc = lines.length; + for (int i = 0; i < cc-1; i++) { + if (getAsyncResultListener() != null) { + String data = processPartialResult(lines[i]); + if (data != null) { + getAsyncResultListener().onPartialResult(data); + } + } + } + + // Return the last line? + if (endsWithNewLine) { + if (getAsyncResultListener() != null) { + String data = processPartialResult(lines[lines.length-1]); + if (data != null) { + getAsyncResultListener().onPartialResult(data); + } + } + this.mPartial = ""; //$NON-NLS-1$ + } else { + // Save the partial for next calls + this.mPartial = lines[lines.length-1]; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onParseErrorPartialResult(String partialErr) {/**NON BLOCK**/} + + /** + * {@inheritDoc} + */ + @Override + public SIGNAL onRequestEnd() { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public Boolean getResult() { + return this.mResult; + } + + /** + * {@inheritDoc} + */ + @Override + public void checkExitCode(int exitCode) + throws InsufficientPermissionsException, CommandNotFoundException, ExecutionException { + + //Ignore exit code 143 (cancelled) + //Ignore exit code 137 (kill -9) + if (exitCode != 0 && exitCode != 1 && exitCode != 143 && exitCode != 137) { + throw new ExecutionException( + "exitcode != 0 && != 143 && != 137"); //$NON-NLS-1$ + } + + // Correct + this.mResult = Boolean.TRUE; + } + + /** + * {@inheritDoc} + */ + @Override + public String getOutCompressedFile() { + return this.mOutFile; + } + + /** + * Method that resolves the arguments for the archive mode + * + * @return String[] The arguments + */ + private final static String[] resolveArchiveArgs(Mode mode, String dst) { + if (mode.compareTo(Mode.A_ZIP) == 0) { + return new String[]{ + FileHelper.getParentDir(dst), + dst + }; + } + return new String[]{ + FileHelper.getParentDir(dst), + mode.mFlag, + dst + }; + } + + /** + * Method that resolves the arguments for the compression mode + * + * @return String[] The arguments + */ + private static String[] resolveCompressArgs(CompressionMode mode, String src) { + switch (mode) { + case C_GZIP: + case C_BZIP: + return new String[]{src}; + default: + return new String[]{}; + } + } + + /** + * Method that processes a line to determine if it's a valid partial result + * + * @param line The line to process + * @return String The processed line + */ + private String processPartialResult(String line) { + if (this.mMode.compareTo(Mode.A_ZIP) == 0) { + if (line.startsWith(" adding: ")) { //$NON-NLS-1$ + int pos = line.lastIndexOf('('); + if (pos != -1) { + // Remove progress + return line.substring(10, pos).trim(); + } + return line.substring(10).trim(); + } + return null; + } + return line; + } + + /** + * Method that resolves the output path of the compressed file + * + * @return String The output path of the compressed file + */ + private static String resolveOutputFile(CompressionMode mode, String src) { + return String.format("%s.%s", src, mode.mExtension); //$NON-NLS-1$ + } + + /** + * Method that converts the absolute paths of the source files to relative paths + * + * @param dst The destination compressed file + * @param src The source uncompressed files + * @return String[] The array of relative paths + */ + private static String[] convertAbsolutePathsToRelativePaths(String dst, String[] src) { + File parent = new File(dst).getParentFile(); + String p = File.separator; + if (parent != null) { + p = parent.getAbsolutePath(); + } + + // Converts every path + String[] out = new String[src.length]; + int cc = src.length; + for (int i = 0; i < cc; i++) { + out[i] = FileHelper.toRelativePath(src[i], p); + } + return out; + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/shell/CopyCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/CopyCommand.java new file mode 100644 index 000000000..452a0103f --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/CopyCommand.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.shell; + +import me.toolify.backbone.commands.CopyExecutable; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.model.MountPoint; +import me.toolify.backbone.util.MountPointHelper; + +import java.text.ParseException; + + +/** + * A class for copy a file or directory. + * + * {@link "http://unixhelp.ed.ac.uk/CGI/man-cgi?cp"} + */ +public class CopyCommand extends SyncResultProgram implements CopyExecutable { + + private static final String ID = "cp"; //$NON-NLS-1$ + private Boolean mRet; + private final String mDst; + + /** + * Constructor of CopyCommand. + * + * @param src The name of the file or directory to be copied + * @param dst The name of the file or directory in which copy the source file or directory + * @throws InvalidCommandDefinitionException If the command has an invalid definition + */ + public CopyCommand(String src, String dst) throws InvalidCommandDefinitionException { + super(ID, src, dst); + this.mDst = dst; + } + + /** + * {@inheritDoc} + */ + @Override + public void parse(String in, String err) throws ParseException { + //Release the return object + this.mRet = Boolean.TRUE; + } + + /** + * {@inheritDoc} + */ + @Override + public Boolean getResult() { + return this.mRet; + } + + /** + * {@inheritDoc} + */ + @Override + public void checkExitCode(int exitCode) + throws InsufficientPermissionsException, CommandNotFoundException, ExecutionException { + if (exitCode != 0) { + throw new ExecutionException("exitcode != 0"); //$NON-NLS-1$ + } + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getSrcWritableMountPoint() { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getDstWritableMountPoint() { + return MountPointHelper.getMountPointFromDirectory(this.mDst); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isIndefinitelyWait() { + return true; + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/shell/CreateDirCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/CreateDirCommand.java new file mode 100644 index 000000000..4a48cbf99 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/CreateDirCommand.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.shell; + +import me.toolify.backbone.commands.CreateDirExecutable; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.model.MountPoint; +import me.toolify.backbone.util.MountPointHelper; + +import java.text.ParseException; + + +/** + * A class for create a directory. + * + * {@link "http://unixhelp.ed.ac.uk/CGI/man-cgi?mkdir"} + */ +public class CreateDirCommand extends SyncResultProgram implements CreateDirExecutable { + + private static final String ID = "mkdir"; //$NON-NLS-1$ + private Boolean mRet; + private final String mFileName; + + /** + * Constructor of CreateDirCommand. + * + * @param fileName The name of the new directory + * @throws InvalidCommandDefinitionException If the command has an invalid definition + */ + public CreateDirCommand(String fileName) throws InvalidCommandDefinitionException { + super(ID, fileName); + this.mFileName = fileName; + } + + /** + * {@inheritDoc} + */ + @Override + public void parse(String in, String err) throws ParseException { + //Release the return object + this.mRet = Boolean.TRUE; + } + + /** + * {@inheritDoc} + */ + @Override + public Boolean getResult() { + return this.mRet; + } + + /** + * {@inheritDoc} + */ + @Override + public void checkExitCode(int exitCode) + throws InsufficientPermissionsException, CommandNotFoundException, ExecutionException { + if (exitCode != 0) { + throw new ExecutionException("exitcode != 0"); //$NON-NLS-1$ + } + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getSrcWritableMountPoint() { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getDstWritableMountPoint() { + return MountPointHelper.getMountPointFromDirectory(this.mFileName); + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/shell/CreateFileCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/CreateFileCommand.java new file mode 100644 index 000000000..62f607de9 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/CreateFileCommand.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.shell; + +import me.toolify.backbone.commands.CreateFileExecutable; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.model.MountPoint; +import me.toolify.backbone.util.MountPointHelper; + +import java.text.ParseException; + + +/** + * A class for create a file (regular file). + * + * {@link "http://unixhelp.ed.ac.uk/CGI/man-cgi?echo"}. This class use echo instead of touch + * because echo returns better exit codes (permission denied, read-only, ..) + */ +public class CreateFileCommand extends SyncResultProgram implements CreateFileExecutable { + + private static final String ID = "touch"; //$NON-NLS-1$ + private Boolean mRet; + private final String mFileName; + + /** + * Constructor of CreateFileCommand. + * + * @param fileName The name of the new file + * @throws InvalidCommandDefinitionException If the command has an invalid definition + */ + public CreateFileCommand(String fileName) throws InvalidCommandDefinitionException { + super(ID, fileName); + this.mFileName = fileName; + } + + /** + * {@inheritDoc} + */ + @Override + public void parse(String in, String err) throws ParseException { + //Release the return object + this.mRet = Boolean.TRUE; + } + + /** + * {@inheritDoc} + */ + @Override + public Boolean getResult() { + return this.mRet; + } + + /** + * {@inheritDoc} + */ + @Override + public void checkExitCode(int exitCode) + throws InsufficientPermissionsException, CommandNotFoundException, ExecutionException { + if (exitCode != 0) { + throw new ExecutionException("exitcode != 0"); //$NON-NLS-1$ + } + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getSrcWritableMountPoint() { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getDstWritableMountPoint() { + return MountPointHelper.getMountPointFromDirectory(this.mFileName); + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/shell/DeleteDirCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/DeleteDirCommand.java new file mode 100644 index 000000000..3e11fbaaa --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/DeleteDirCommand.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.shell; + +import me.toolify.backbone.commands.DeleteDirExecutable; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.model.MountPoint; +import me.toolify.backbone.util.MountPointHelper; + +import java.text.ParseException; + + +/** + * A class for delete a file (regular file). + * + * {@link "http://unixhelp.ed.ac.uk/CGI/man-cgi?rm"} + */ +public class DeleteDirCommand extends SyncResultProgram implements DeleteDirExecutable { + + private static final String ID = "rmdir"; //$NON-NLS-1$ + private Boolean mRet; + private final String mFileName; + + /** + * Constructor of DeleteDirCommand. + * + * @param fileName The name of the directory to be deleted + * @throws InvalidCommandDefinitionException If the command has an invalid definition + */ + public DeleteDirCommand(String fileName) throws InvalidCommandDefinitionException { + super(ID, fileName); + this.mFileName = fileName; + } + + /** + * {@inheritDoc} + */ + @Override + public void parse(String in, String err) throws ParseException { + //Release the return object + this.mRet = Boolean.TRUE; + } + + /** + * {@inheritDoc} + */ + @Override + public Boolean getResult() { + return this.mRet; + } + + /** + * {@inheritDoc} + */ + @Override + public void checkExitCode(int exitCode) + throws InsufficientPermissionsException, CommandNotFoundException, ExecutionException { + if (exitCode != 0) { + throw new ExecutionException("exitcode != 0"); //$NON-NLS-1$ + } + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getSrcWritableMountPoint() { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getDstWritableMountPoint() { + return MountPointHelper.getMountPointFromDirectory(this.mFileName); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isIndefinitelyWait() { + return true; + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/shell/DeleteFileCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/DeleteFileCommand.java new file mode 100644 index 000000000..9e7a26e6f --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/DeleteFileCommand.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.shell; + +import me.toolify.backbone.commands.DeleteFileExecutable; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.model.MountPoint; +import me.toolify.backbone.util.MountPointHelper; + +import java.text.ParseException; + + +/** + * A class for delete a file (regular file). + * + * {@link "http://unixhelp.ed.ac.uk/CGI/man-cgi?rm"} + */ +public class DeleteFileCommand extends SyncResultProgram implements DeleteFileExecutable { + + private static final String ID = "rm"; //$NON-NLS-1$ + private Boolean mRet; + private final String mFileName; + + /** + * Constructor of DeleteFileCommand. + * + * @param fileName The name of the file to be deleted + * @throws InvalidCommandDefinitionException If the command has an invalid definition + */ + public DeleteFileCommand(String fileName) throws InvalidCommandDefinitionException { + super(ID, fileName); + this.mFileName = fileName; + } + + /** + * {@inheritDoc} + */ + @Override + public void parse(String in, String err) throws ParseException { + //Release the return object + this.mRet = Boolean.TRUE; + } + + /** + * {@inheritDoc} + */ + @Override + public Boolean getResult() { + return this.mRet; + } + + /** + * {@inheritDoc} + */ + @Override + public void checkExitCode(int exitCode) + throws InsufficientPermissionsException, CommandNotFoundException, ExecutionException { + if (exitCode != 0) { + throw new ExecutionException("exitcode != 0"); //$NON-NLS-1$ + } + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getSrcWritableMountPoint() { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getDstWritableMountPoint() { + return MountPointHelper.getMountPointFromDirectory(this.mFileName); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isIndefinitelyWait() { + return true; + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/shell/DiskUsageCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/DiskUsageCommand.java new file mode 100644 index 000000000..81c557f1f --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/DiskUsageCommand.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.shell; + +import me.toolify.backbone.commands.DiskUsageExecutable; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.model.DiskUsage; +import me.toolify.backbone.util.ParseHelper; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; + + +/** + * A class for get information about disk usage. + * + * {@link "http://unixhelp.ed.ac.uk/CGI/man-cgi?df"} + */ +public class DiskUsageCommand extends SyncResultProgram implements DiskUsageExecutable { + + private static final String ID = "diskusage"; //$NON-NLS-1$ + private static final String ID_ALL = "diskusageall"; //$NON-NLS-1$ + + private final List mDisksUsage; + + /** + * Constructor of DiskUsageCommand. + * + * @throws InvalidCommandDefinitionException If the command has an invalid definition + */ + public DiskUsageCommand() throws InvalidCommandDefinitionException { + super(ID_ALL, new String[]{}); + this.mDisksUsage = new ArrayList(); + } + + /** + * Constructor of DiskUsageCommand. + * + * @param dir The directory of which obtain its disk usage + * @throws InvalidCommandDefinitionException If the command has an invalid definition + */ + public DiskUsageCommand(String dir) throws InvalidCommandDefinitionException { + super(ID, new String[]{dir}); + this.mDisksUsage = new ArrayList(); + } + + /** + * {@inheritDoc} + */ + @Override + public void parse(String in, String err) throws ParseException { + //Release the array + this.mDisksUsage.clear(); + + // Check the in buffer to extract information + BufferedReader br = null; + int line = 0; + try { + br = new BufferedReader(new StringReader(in)); + String szLine = br.readLine(); //The first line must be ignored + while ((szLine = br.readLine()) != null) { + //Checks that there is some text in the line. Otherwise ignore it + if (szLine.trim().length() == 0) { + break; + } + + //Parse the line into a DiskUsage reference + try { + this.mDisksUsage.add(ParseHelper.toDiskUsage(szLine)); + } catch (ParseException pEx) { + //Ignore + } + + line++; + } + + } catch (IOException ioEx) { + throw new ParseException(ioEx.getMessage(), line); + + } catch (Exception ex) { + throw new ParseException(ex.getMessage(), line); + + } finally { + try { + if (br != null) { + br.close(); + } + } catch (Throwable ex) { + /**NON BLOCK**/ + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public List getResult() { + return this.mDisksUsage; + } + + /** + * {@inheritDoc} + */ + @Override + public void checkExitCode(int exitCode) + throws InsufficientPermissionsException, CommandNotFoundException, ExecutionException { + if (exitCode != 0 && exitCode != 1) { //Permission denied + throw new ExecutionException("exitcode != 0 && != 1"); //$NON-NLS-1$ + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isIgnoreShellStdErrCheck() { + return true; + } + +} diff --git a/src/com/cyanogenmod/filemanager/commands/shell/EchoCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/EchoCommand.java similarity index 89% rename from src/com/cyanogenmod/filemanager/commands/shell/EchoCommand.java rename to Backbone/src/main/java/me/toolify/backbone/commands/shell/EchoCommand.java index 8488343f4..86fad6bb0 100644 --- a/src/com/cyanogenmod/filemanager/commands/shell/EchoCommand.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/EchoCommand.java @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands.shell; +package me.toolify.backbone.commands.shell; -import com.cyanogenmod.filemanager.commands.EchoExecutable; -import com.cyanogenmod.filemanager.console.CommandNotFoundException; -import com.cyanogenmod.filemanager.console.ExecutionException; -import com.cyanogenmod.filemanager.console.InsufficientPermissionsException; +import me.toolify.backbone.commands.EchoExecutable; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; import java.io.BufferedReader; import java.io.IOException; diff --git a/src/com/cyanogenmod/filemanager/commands/shell/ExecCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/ExecCommand.java similarity index 87% rename from src/com/cyanogenmod/filemanager/commands/shell/ExecCommand.java rename to Backbone/src/main/java/me/toolify/backbone/commands/shell/ExecCommand.java index c66cef9f9..ff59732ed 100644 --- a/src/com/cyanogenmod/filemanager/commands/shell/ExecCommand.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/ExecCommand.java @@ -14,14 +14,14 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands.shell; +package me.toolify.backbone.commands.shell; -import com.cyanogenmod.filemanager.commands.AsyncResultListener; -import com.cyanogenmod.filemanager.commands.ExecExecutable; -import com.cyanogenmod.filemanager.commands.SIGNAL; -import com.cyanogenmod.filemanager.console.CommandNotFoundException; -import com.cyanogenmod.filemanager.console.ExecutionException; -import com.cyanogenmod.filemanager.console.InsufficientPermissionsException; +import me.toolify.backbone.commands.AsyncResultListener; +import me.toolify.backbone.commands.ExecExecutable; +import me.toolify.backbone.commands.SIGNAL; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; /** * A class for execute a command diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/shell/FindCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/FindCommand.java new file mode 100644 index 000000000..ec380836f --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/FindCommand.java @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.shell; + +import android.util.Log; + +import me.toolify.backbone.commands.AsyncResultListener; +import me.toolify.backbone.commands.FindExecutable; +import me.toolify.backbone.commands.SIGNAL; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.model.Query; +import me.toolify.backbone.util.FileHelper; +import me.toolify.backbone.util.ParseHelper; +import me.toolify.backbone.util.SearchHelper; + +import java.io.BufferedReader; +import java.io.File; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.List; + +/** + * A class for search files. + * + * {@link "http://unixhelp.ed.ac.uk/CGI/man-cgi?find"} + */ +public class FindCommand extends AsyncResultProgram implements FindExecutable { + + private static final String TAG = "FindCommand"; //$NON-NLS-1$ + + private static final String ID = "find"; //$NON-NLS-1$ + + private final File mDirectory; + + /** + * Constructor of FindCommand. + * + * @param directory The absolute path of the directory where do the search + * @param query The terms to be searched + * @param asyncResultListener The partial result listener + * @throws InvalidCommandDefinitionException If the command has an invalid definition + */ + public FindCommand( + String directory, Query query, AsyncResultListener asyncResultListener) + throws InvalidCommandDefinitionException { + super(ID, asyncResultListener, createArgs(FileHelper.addTrailingSlash(directory), query)); + this.mDirectory = new File(directory); + } + + /** + * {@inheritDoc} + */ + @Override + public void onStartParsePartialResult() { + //$NON-NLS-1$ + } + + /** + * {@inheritDoc} + */ + @Override + public void onEndParsePartialResult(boolean cancelled) { + //$NON-NLS-1$ + } + + /** + * {@inheritDoc} + */ + @Override + public void onParsePartialResult(final String partialIn) { + + // Check the in buffer to extract information + final List partialFiles = new ArrayList(); + BufferedReader br = null; + try { + //Read the partial + previous partial and clean partial + br = new BufferedReader(new StringReader(partialIn)); + + //Add all lines to an array + String line = null; + while ((line = br.readLine()) != null) { + //Checks that there is some text in the line. Otherwise ignore it + if (line.trim().length() == 0) { + break; + } + + // Add to the list + try { + FileSystemObject fso = ParseHelper.parseStatOutput(line); + + // Search directory is not part of the search + if (fso.getFullPath().compareTo(this.mDirectory.getAbsolutePath()) != 0) { + partialFiles.add(fso); + } + + } catch (Exception e) { + // Log the parsing error + if (isTrace()) { + Log.w(TAG, + String.format( + "Failed to parse output: %s", //$NON-NLS-1$ + String.valueOf(line))); + } + } + } + + //If a listener is defined, then send the partial result + if (getAsyncResultListener() != null) { + getAsyncResultListener().onPartialResult(partialFiles); + } + + } catch (Exception ex) { + Log.w(TAG, "Partial result fails", ex); //$NON-NLS-1$ + + } finally { + try { + if (br != null) { + br.close(); + } + } catch (Throwable ex) { + /**NON BLOCK**/ + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onParseErrorPartialResult(String partialErr) {/**NON BLOCK**/} + + /** + * {@inheritDoc} + */ + @Override + public SIGNAL onRequestEnd() { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isIgnoreShellStdErrCheck() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public void checkExitCode(int exitCode) + throws InsufficientPermissionsException, CommandNotFoundException, ExecutionException { + + //Search in a subdirectory without permissions returns 1, but this + //not must be treated as an error + //Ignore exit code 143 (cancelled) + //Ignore exit code 137 (kill -9) + if (exitCode != 0 && exitCode != 1 && exitCode != 143 && exitCode != 137) { + throw new ExecutionException( + "exitcode != 0 && != 1 && != 143 && != 137"); //$NON-NLS-1$ + } + } + + /** + * Method that create the arguments of this command, using the directory and + * arguments and creating the regular expressions of the search. + * + * @param directory The directory where to search + * @param query The query make for user + * @return String[] The arguments of the command + */ + private static String[] createArgs(String directory, Query query) { + String[] args = new String[query.getSlotsCount() + 1]; + args[0] = directory; + int cc = query.getSlotsCount(); + for (int i = 0; i < cc; i++) { + args[i + 1] = SearchHelper.toIgnoreCaseRegExp(query.getSlot(i), false); + } + return args; + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/shell/FolderUsageCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/FolderUsageCommand.java new file mode 100644 index 000000000..5151f6347 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/FolderUsageCommand.java @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.shell; + +import android.util.Log; + +import me.toolify.backbone.commands.AsyncResultListener; +import me.toolify.backbone.commands.FolderUsageExecutable; +import me.toolify.backbone.commands.SIGNAL; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.model.BlockDevice; +import me.toolify.backbone.model.CharacterDevice; +import me.toolify.backbone.model.Directory; +import me.toolify.backbone.model.DomainSocket; +import me.toolify.backbone.model.FolderUsage; +import me.toolify.backbone.model.NamedPipe; +import me.toolify.backbone.model.Symlink; +import me.toolify.backbone.util.FileHelper; +import me.toolify.backbone.util.MimeTypeHelper; +import me.toolify.backbone.util.MimeTypeHelper.MimeTypeCategory; + +import java.io.BufferedReader; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.List; + +/** + * A class for retrieve the disk usage of a folder + * + * {@link "http://unixhelp.ed.ac.uk/CGI/man-cgi?ls"} + */ +public class FolderUsageCommand extends AsyncResultProgram implements FolderUsageExecutable { + + private static final String TAG = "FolderUsageCommand"; //$NON-NLS-1$ + + private static final String ID = "folderusage"; //$NON-NLS-1$ + + private final String mDirectory; + private FolderUsage mFolderUsage; + + /** + * Constructor of FolderUsageCommand. + * + * @param directory The absolute directory to compute + * @param asyncResultListener The partial result listener + * @throws InvalidCommandDefinitionException If the command has an invalid definition + */ + public FolderUsageCommand( + String directory, AsyncResultListener asyncResultListener) + throws InvalidCommandDefinitionException { + super(ID, asyncResultListener, new String[]{directory}); + this.mFolderUsage = new FolderUsage(directory); + this.mDirectory = directory; + } + + /** + * {@inheritDoc} + */ + @Override + public void onStartParsePartialResult() { + this.mFolderUsage = new FolderUsage(this.mDirectory); + } + + /** + * {@inheritDoc} + */ + @Override + public void onEndParsePartialResult(boolean cancelled) { + //$NON-NLS-1$ + } + + /** + * {@inheritDoc} + */ + @Override + public void onParsePartialResult(final String partialIn) { + + // Check the in buffer to extract information + BufferedReader br = null; + try { + // Parse the line. We expect a ls -l output line + // -rw-r--r-- root root 7 2012-12-30 00:49 test.txt + // + // (1) permissions + // (2) number of links and directories + // (3) owner + // (4) group + // (5) size + // (6) date + // (7) name + + //Partial contains full lines + br = new BufferedReader(new StringReader(partialIn)); + + //Add all lines to an array + List lines = new ArrayList(); + String line = null; + while ((line = br.readLine()) != null) { + // Discard empty, paths, and folder links + if (line.length() == 0 || + line.startsWith(FileHelper.ROOT_DIRECTORY) || + line.startsWith(FileHelper.CURRENT_DIRECTORY) || + line.startsWith(FileHelper.PARENT_DIRECTORY)) { + continue; + } + lines.add(line); + } + + int c = 0; + try { + while (lines.size() > 0) { + // Retrieve the info + String szLine = lines.remove(0).trim(); + try { + // Clean the line (we don't care about names, only need the extension) + // so remove spaces is safe here + while (szLine.indexOf(" ") != -1) { //$NON-NLS-1$ + szLine = szLine.replaceAll(" ", " "); //$NON-NLS-1$ //$NON-NLS-2$ + } + + // Don't compute . and .. + // This is not secure, but we don't need a exact precission on this + // method + if (szLine.length() == 0 || + szLine.endsWith(" " + FileHelper.CURRENT_DIRECTORY) || //$NON-NLS-1$ + szLine.endsWith(" " + FileHelper.PARENT_DIRECTORY)) { //$NON-NLS-1$ + c++; + continue; + } + + char type = szLine.charAt(0); + if (type == Symlink.UNIX_ID || + type == BlockDevice.UNIX_ID || + type == CharacterDevice.UNIX_ID || + type == DomainSocket.UNIX_ID || + type == NamedPipe.UNIX_ID) { + // File + Category + this.mFolderUsage.addFile(); + if (type == Symlink.UNIX_ID) { + this.mFolderUsage.addFileToCategory(MimeTypeCategory.NONE); + } else { + this.mFolderUsage.addFileToCategory(MimeTypeCategory.SYSTEM); + } + + } else if (type == Directory.UNIX_ID) { + // Folder + this.mFolderUsage.addFolder(); + + } else { + // File + Category + Size + try { + // we need a valid line + String[] fields = szLine.split(" "); //$NON-NLS-1$ + if (fields.length < 8) { + continue; + } + + long size = Long.parseLong(fields[4]); + String name = fields[fields.length-1];// We only need the extension + String ext = FileHelper.getExtension(name); + MimeTypeCategory category = + MimeTypeHelper.getCategoryFromExt(null, ext); + this.mFolderUsage.addFile(); + this.mFolderUsage.addFileToCategory(category); + this.mFolderUsage.addSize(size); + } catch (Exception e) {/**NON BLOCK**/} + } + c++; + + } catch (Exception e) { + // Ignore. + } + + // Partial notification + if (c % 5 == 0) { + //If a listener is defined, then send the partial result + if (getAsyncResultListener() != null) { + getAsyncResultListener().onPartialResult(this.mFolderUsage); + } + } + } + } catch (Exception ex) { /**NON BLOCK **/ } + + //If a listener is defined, then send the partial result + if (getAsyncResultListener() != null) { + getAsyncResultListener().onPartialResult(this.mFolderUsage); + } + + } catch (Exception ex) { + Log.w(TAG, "Partial result fails", ex); //$NON-NLS-1$ + + } finally { + try { + if (br != null) { + br.close(); + } + } catch (Throwable ex) { + /**NON BLOCK**/ + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onParseErrorPartialResult(String partialErr) {/**NON BLOCK**/} + + /** + * {@inheritDoc} + */ + @Override + public SIGNAL onRequestEnd() { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public FolderUsage getFolderUsage() { + return this.mFolderUsage; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isIgnoreShellStdErrCheck() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public void checkExitCode(int exitCode) + throws InsufficientPermissionsException, CommandNotFoundException, ExecutionException { + + //Access a subdirectory without permissions returns 1, but this + //not must be treated as an error + //Ignore exit code 143 (cancelled) + //Ignore exit code 137 (kill -9) + if (exitCode != 0 && exitCode != 1 && exitCode != 143 && exitCode != 137) { + throw new ExecutionException( + "exitcode != 0 && != 1 && != 143 && != 137"); //$NON-NLS-1$ + } + } +} diff --git a/src/com/cyanogenmod/filemanager/commands/shell/GroupsCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/GroupsCommand.java similarity index 87% rename from src/com/cyanogenmod/filemanager/commands/shell/GroupsCommand.java rename to Backbone/src/main/java/me/toolify/backbone/commands/shell/GroupsCommand.java index e6eb99d0f..494d59df3 100644 --- a/src/com/cyanogenmod/filemanager/commands/shell/GroupsCommand.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/GroupsCommand.java @@ -14,14 +14,14 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands.shell; +package me.toolify.backbone.commands.shell; -import com.cyanogenmod.filemanager.commands.GroupsExecutable; -import com.cyanogenmod.filemanager.console.CommandNotFoundException; -import com.cyanogenmod.filemanager.console.ExecutionException; -import com.cyanogenmod.filemanager.console.InsufficientPermissionsException; -import com.cyanogenmod.filemanager.model.Group; -import com.cyanogenmod.filemanager.util.FileHelper; +import me.toolify.backbone.commands.GroupsExecutable; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.model.Group; +import me.toolify.backbone.util.FileHelper; import java.io.BufferedReader; import java.io.IOException; diff --git a/src/com/cyanogenmod/filemanager/commands/shell/IdentityCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/IdentityCommand.java similarity index 91% rename from src/com/cyanogenmod/filemanager/commands/shell/IdentityCommand.java rename to Backbone/src/main/java/me/toolify/backbone/commands/shell/IdentityCommand.java index e87a90eca..10d73115a 100644 --- a/src/com/cyanogenmod/filemanager/commands/shell/IdentityCommand.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/IdentityCommand.java @@ -14,17 +14,17 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands.shell; - -import com.cyanogenmod.filemanager.commands.IdentityExecutable; -import com.cyanogenmod.filemanager.console.CommandNotFoundException; -import com.cyanogenmod.filemanager.console.ExecutionException; -import com.cyanogenmod.filemanager.console.InsufficientPermissionsException; -import com.cyanogenmod.filemanager.model.AID; -import com.cyanogenmod.filemanager.model.Group; -import com.cyanogenmod.filemanager.model.Identity; -import com.cyanogenmod.filemanager.model.User; -import com.cyanogenmod.filemanager.util.FileHelper; +package me.toolify.backbone.commands.shell; + +import me.toolify.backbone.commands.IdentityExecutable; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.model.AID; +import me.toolify.backbone.model.Group; +import me.toolify.backbone.model.Identity; +import me.toolify.backbone.model.User; +import me.toolify.backbone.util.FileHelper; import java.io.BufferedReader; import java.io.IOException; diff --git a/src/com/cyanogenmod/filemanager/commands/shell/InvalidCommandDefinitionException.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/InvalidCommandDefinitionException.java similarity index 96% rename from src/com/cyanogenmod/filemanager/commands/shell/InvalidCommandDefinitionException.java rename to Backbone/src/main/java/me/toolify/backbone/commands/shell/InvalidCommandDefinitionException.java index 93ea24981..8bd53f447 100644 --- a/src/com/cyanogenmod/filemanager/commands/shell/InvalidCommandDefinitionException.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/InvalidCommandDefinitionException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands.shell; +package me.toolify.backbone.commands.shell; /** * An exception thrown when the information of the command is diff --git a/src/com/cyanogenmod/filemanager/commands/shell/LinkCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/LinkCommand.java similarity index 84% rename from src/com/cyanogenmod/filemanager/commands/shell/LinkCommand.java rename to Backbone/src/main/java/me/toolify/backbone/commands/shell/LinkCommand.java index 9dc0ac7ca..34dcb537d 100644 --- a/src/com/cyanogenmod/filemanager/commands/shell/LinkCommand.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/LinkCommand.java @@ -14,14 +14,14 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands.shell; +package me.toolify.backbone.commands.shell; -import com.cyanogenmod.filemanager.commands.LinkExecutable; -import com.cyanogenmod.filemanager.console.CommandNotFoundException; -import com.cyanogenmod.filemanager.console.ExecutionException; -import com.cyanogenmod.filemanager.console.InsufficientPermissionsException; -import com.cyanogenmod.filemanager.model.MountPoint; -import com.cyanogenmod.filemanager.util.MountPointHelper; +import me.toolify.backbone.commands.LinkExecutable; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.model.MountPoint; +import me.toolify.backbone.util.MountPointHelper; import java.text.ParseException; diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/shell/ListCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/ListCommand.java new file mode 100644 index 000000000..7f89e5a67 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/ListCommand.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.shell; + +import android.util.Log; + +import me.toolify.backbone.commands.ListExecutable; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.console.shell.ShellConsole; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.model.ParentDirectory; +import me.toolify.backbone.util.FileHelper; +import me.toolify.backbone.util.ParseHelper; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.StringReader; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; + + +/** + * A class for list information about files and directories. + * + * {@link "http://unixhelp.ed.ac.uk/CGI/man-cgi?stat"} + */ +public class ListCommand extends SyncResultProgram implements ListExecutable { + + private static final String TAG = "ListCommand"; //$NON-NLS-1$ + + private static final String ID_LS = "ls"; //$NON-NLS-1$ + private static final String ID_FILEINFO = "fileinfo"; //$NON-NLS-1$ + + private final LIST_MODE mMode; + private final List mFiles; + private String mParentDir; + + /** + * Constructor of ListCommand. List mode. + * + * @param src The file system object to be listed + * @param console The console in which retrieve the parent directory information. + * null to attach to the default console + * @throws InvalidCommandDefinitionException If the command has an invalid definition + */ + public ListCommand(String src, ShellConsole console) + throws InvalidCommandDefinitionException { + // Always add backslash for list the files of the directory, instead of + // the directory. + super(ID_LS, new String[]{ FileHelper.addTrailingSlash(src) }); + + //Initialize files to something distinct of null + this.mFiles = new ArrayList(); + this.mMode = LIST_MODE.DIRECTORY; + + //Retrieve parent directory information + if (src.compareTo(FileHelper.ROOT_DIRECTORY) == 0) { + this.mParentDir = null; + } else { + this.mParentDir = new File(src).getAbsolutePath(); + } + } + + /** + * Constructor of ListCommand. FileInfo mode + * + * @param src The file system object to be listed + * @param followSymlinks If follow the symlink + * @param console The console in which retrieve the parent directory information. + * null to attach to the default console + * @throws InvalidCommandDefinitionException If the command has an invalid definition + * @throws FileNotFoundException If the initial directory not exists + * @throws IOException If initial directory couldn't be checked + */ + public ListCommand(String src, boolean followSymlinks, ShellConsole console) + throws InvalidCommandDefinitionException, FileNotFoundException, IOException { + // Always remove backslash for avoid listing the files of the directory, instead of + // the directory. + super(ID_FILEINFO, + new String[]{ + FileHelper.removeTrailingSlash( + followSymlinks ? + new File(src).getCanonicalPath() : + new File(src).getAbsolutePath())}); + + //Initialize files to something distinct of null + this.mFiles = new ArrayList(); + this.mMode = LIST_MODE.FILEINFO; + + //Get the absolute path + if (followSymlinks) { + this.mParentDir = + FileHelper.removeTrailingSlash( + new File(src).getCanonicalFile().getParent()); + } else { + this.mParentDir = + FileHelper.removeTrailingSlash( + new File(src).getAbsoluteFile().getParent()); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void parse(String in, String err) throws ParseException { + //Release the array + this.mFiles.clear(); + + // Read every line and parse it + BufferedReader br = null; + try { + br = new BufferedReader(new StringReader(in)); + String line = null; + while ((line = br.readLine()) != null) { + //Checks that there is some text in the line. Otherwise ignore it + if (line.trim().length() == 0) { + break; + } + + // Parse and add to result files + try { + this.mFiles.add(ParseHelper.parseStatOutput(line)); + } catch (Exception e) { + // Log the parsing error + if (isTrace()) { + Log.w(TAG, + String.format( + "Failed to parse output: %s", //$NON-NLS-1$ + String.valueOf(line))); + } + } + } + + // Add the parent directory + if (this.mParentDir != null && + this.mParentDir.compareTo(FileHelper.ROOT_DIRECTORY) != 0 && + this.mMode.compareTo(LIST_MODE.DIRECTORY) == 0) { + this.mFiles.add(0, new ParentDirectory(new File(this.mParentDir).getParent())); + } + + } catch (IOException ioEx) { + throw new ParseException(ioEx.getMessage(), 0); + + } catch (Exception ex) { + throw new ParseException(ex.getMessage(), 0); + + } finally { + try { + if (br != null) { + br.close(); + } + } catch (Throwable ex) { + /**NON BLOCK**/ + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public List getResult() { + return this.mFiles; + } + + /** + * Method that returns a single result of the program invocation. + * Only must be called within a FILEINFO mode listing. + * + * @return FileSystemObject The file system object reference + */ + public FileSystemObject getSingleResult() { + return this.mFiles.get(0); + } + + /** + * {@inheritDoc} + */ + @Override + public void checkExitCode(int exitCode) + throws InsufficientPermissionsException, CommandNotFoundException, ExecutionException { + // 123: stat failed ... Function not implemented (for broken symlinks) + if (exitCode != 0 && exitCode != 1 && exitCode != 123) { + throw new ExecutionException("exitcode != 0 && != 1 && != 123"); //$NON-NLS-1$ + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isWaitOnNewDataReceipt() { + return true; + } +} diff --git a/src/com/cyanogenmod/filemanager/commands/shell/MountCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/MountCommand.java similarity index 84% rename from src/com/cyanogenmod/filemanager/commands/shell/MountCommand.java rename to Backbone/src/main/java/me/toolify/backbone/commands/shell/MountCommand.java index eae5e8310..62e30d33d 100644 --- a/src/com/cyanogenmod/filemanager/commands/shell/MountCommand.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/MountCommand.java @@ -14,13 +14,13 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands.shell; +package me.toolify.backbone.commands.shell; -import com.cyanogenmod.filemanager.commands.MountExecutable; -import com.cyanogenmod.filemanager.console.CommandNotFoundException; -import com.cyanogenmod.filemanager.console.ExecutionException; -import com.cyanogenmod.filemanager.console.InsufficientPermissionsException; -import com.cyanogenmod.filemanager.model.MountPoint; +import me.toolify.backbone.commands.MountExecutable; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.model.MountPoint; import java.text.ParseException; diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/shell/MountPointInfoCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/MountPointInfoCommand.java new file mode 100644 index 000000000..6c10a11ed --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/MountPointInfoCommand.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.shell; + +import me.toolify.backbone.commands.MountPointInfoExecutable; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.model.MountPoint; +import me.toolify.backbone.util.ParseHelper; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; + + +/** + * A class for get information about disk usage. + * + * {@link "http://unixhelp.ed.ac.uk/CGI/man-cgi?cat"} + * {@link + * "http://linux.web.cern.ch/linux/scientific5/docs/rhel/Deployment_Guide/s2-proc-mounts.html"} + */ +public class MountPointInfoCommand extends SyncResultProgram implements MountPointInfoExecutable { + + private static final String ID = "mountpointinfo"; //$NON-NLS-1$ + + private final List mMountPoints; + + /** + * Constructor of MountPointInfoCommand. + * + * @throws InvalidCommandDefinitionException If the command has an invalid definition + */ + public MountPointInfoCommand() throws InvalidCommandDefinitionException { + super(ID, new String[]{}); + this.mMountPoints = new ArrayList(); + } + + /** + * {@inheritDoc} + */ + @Override + public void parse(String in, String err) throws ParseException { + //Release the array + this.mMountPoints.clear(); + + // Check the in buffer to extract information + BufferedReader br = null; + int line = 0; + try { + br = new BufferedReader(new StringReader(in)); + String szLine = null; + while ((szLine = br.readLine()) != null) { + //Checks that there is some text in the line. Otherwise ignore it + if (szLine.trim().length() == 0) { + break; + } + + //Parse the line into a MountPoint reference + try { + this.mMountPoints.add(ParseHelper.toMountPoint(szLine)); + } catch (ParseException pEx) { + //Ignore + } + + line++; + } + + } catch (IOException ioEx) { + throw new ParseException(ioEx.getMessage(), line); + + } catch (Exception ex) { + throw new ParseException(ex.getMessage(), line); + + } finally { + try { + if (br != null) { + br.close(); + } + } catch (Throwable ex) { + /**NON BLOCK**/ + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public List getResult() { + return this.mMountPoints; + } + + /** + * {@inheritDoc} + */ + @Override + public void checkExitCode(int exitCode) + throws InsufficientPermissionsException, CommandNotFoundException, ExecutionException { + if (exitCode != 0 && exitCode != 1) { //Permission denied + throw new ExecutionException("exitcode != 0 && != 1"); //$NON-NLS-1$ + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isIgnoreShellStdErrCheck() { + return true; + } + +} diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/shell/MoveCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/MoveCommand.java new file mode 100644 index 000000000..a132ca0af --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/MoveCommand.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.shell; + +import me.toolify.backbone.commands.MoveExecutable; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.model.MountPoint; +import me.toolify.backbone.util.MountPointHelper; + +import java.text.ParseException; + + +/** + * A class for move a file or directory. + * + * {@link "http://unixhelp.ed.ac.uk/CGI/man-cgi?mv"} + */ +public class MoveCommand extends SyncResultProgram implements MoveExecutable { + + private static final String ID = "mv"; //$NON-NLS-1$ + private Boolean mRet; + private final String mSrc; + private final String mDst; + + /** + * Constructor of MoveCommand. + * + * @param src The name of the file or directory to be moved + * @param dst The name of the file or directory in which move the source file or directory + * @throws InvalidCommandDefinitionException If the command has an invalid definition + */ + public MoveCommand(String src, String dst) throws InvalidCommandDefinitionException { + super(ID, src, dst); + this.mSrc = src; + this.mDst = dst; + } + + /** + * {@inheritDoc} + */ + @Override + public void parse(String in, String err) throws ParseException { + //Release the return object + this.mRet = Boolean.TRUE; + } + + /** + * {@inheritDoc} + */ + @Override + public Boolean getResult() { + return this.mRet; + } + + /** + * {@inheritDoc} + */ + @Override + public void checkExitCode(int exitCode) + throws InsufficientPermissionsException, CommandNotFoundException, ExecutionException { + if (exitCode != 0) { + throw new ExecutionException("exitcode != 0"); //$NON-NLS-1$ + } + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getSrcWritableMountPoint() { + return MountPointHelper.getMountPointFromDirectory(this.mSrc); + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getDstWritableMountPoint() { + return MountPointHelper.getMountPointFromDirectory(this.mDst); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isIndefinitelyWait() { + return true; + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/shell/ParentDirCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/ParentDirCommand.java new file mode 100644 index 000000000..6775e055f --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/ParentDirCommand.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.shell; + +import me.toolify.backbone.commands.ParentDirExecutable; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; +import java.text.ParseException; + + +/** + * A class for retrieve the parent directory of a file system object. + * + * {@link "http://unixhelp.ed.ac.uk/CGI/man-cgi?dirname"} + */ +public class ParentDirCommand extends SyncResultProgram implements ParentDirExecutable { + + private static final String ID = "dirname"; //$NON-NLS-1$ + private String mParentDir; + + /** + * Constructor of ParentDirCommand. + * + * @param src The file system object to read + * @throws InvalidCommandDefinitionException If the command has an invalid definition + */ + public ParentDirCommand(String src) throws InvalidCommandDefinitionException { + super(ID, src); + } + + /** + * {@inheritDoc} + */ + @Override + public void parse(String in, String err) throws ParseException { + //Release the return object + this.mParentDir = ""; //$NON-NLS-1$ + + // Check the in buffer to extract information + BufferedReader br = null; + try { + br = new BufferedReader(new StringReader(in)); + String szLine = br.readLine(); + if (szLine != null) { + this.mParentDir = szLine; + } + + } catch (IOException ioEx) { + throw new ParseException(ioEx.getMessage(), 0); + + } catch (Exception ex) { + throw new ParseException(ex.getMessage(), 0); + + } finally { + try { + if (br != null) { + br.close(); + } + } catch (Throwable ex) { + /**NON BLOCK**/ + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public String getResult() { + return this.mParentDir; + } + + /** + * {@inheritDoc} + */ + @Override + public void checkExitCode(int exitCode) + throws InsufficientPermissionsException, CommandNotFoundException, ExecutionException { + if (exitCode != 0) { + throw new ExecutionException("exitcode != 0"); //$NON-NLS-1$ + } + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/shell/ProcessIdCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/ProcessIdCommand.java new file mode 100644 index 000000000..7b795ee36 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/ProcessIdCommand.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.shell; + +import me.toolify.backbone.commands.ProcessIdExecutable; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; + + +/** + * A class for retrieve the process identifier of a program. + * + * {@link "http://unixhelp.ed.ac.uk/CGI/man-cgi?ps"} + */ +public class ProcessIdCommand extends SyncResultProgram implements ProcessIdExecutable { + + private static final String ID_SHELL = "pid_shell"; //$NON-NLS-1$ + private static final String ID_SHELL_CMDS = "pid_shell_cmds"; //$NON-NLS-1$ + private static final String ID_CMD = "pid_cmd"; //$NON-NLS-1$ + private List mPIDs; + + /** + * Constructor of ProcessIdCommand.
+ * Use this to retrieve the PID of a shell. + * + * @throws InvalidCommandDefinitionException If the command has an invalid definition + */ + public ProcessIdCommand() throws InvalidCommandDefinitionException { + super(ID_SHELL); + } + + /** + * Constructor of ProcessIdCommand.
+ * Use this to retrieve all PIDs running on a shell. + * + * @param pid The process identifier of the shell when the process is running + * @throws InvalidCommandDefinitionException If the command has an invalid definition + */ + public ProcessIdCommand(int pid) throws InvalidCommandDefinitionException { + super(ID_SHELL_CMDS, new String[]{String.valueOf(pid)}); + } + + /** + * Constructor of ProcessIdCommand.
+ * Use this to retrieve the PID of a command running on a shell. + * + * @param pid The process identifier of the shell when the process is running + * @param processName The process name + * @throws InvalidCommandDefinitionException If the command has an invalid definition + */ + public ProcessIdCommand(int pid, String processName) throws InvalidCommandDefinitionException { + super(ID_CMD, new String[]{processName, String.valueOf(pid)}); + } + + /** + * {@inheritDoc} + */ + @Override + public void parse(String in, String err) throws ParseException { + //Release the return object + this.mPIDs = new ArrayList(); + + // Check the in buffer to extract information + BufferedReader br = null; + try { + br = new BufferedReader(new StringReader(in)); + String szLine = br.readLine(); + if (szLine == null) { + throw new ParseException("no information", 0); //$NON-NLS-1$ + } + do { + // Add every PID + this.mPIDs.add(Integer.valueOf(szLine.trim())); + + // Next line + szLine = br.readLine(); + } while (szLine != null); + + } catch (IOException ioEx) { + throw new ParseException(ioEx.getMessage(), 0); + + } catch (ParseException pEx) { + throw pEx; + + } catch (Exception ex) { + throw new ParseException(ex.getMessage(), 0); + + } finally { + try { + if (br != null) { + br.close(); + } + } catch (Throwable ex) { + /**NON BLOCK**/ + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public List getResult() { + return this.mPIDs; + } + + /** + * {@inheritDoc} + */ + @Override + public void checkExitCode(int exitCode) + throws InsufficientPermissionsException, CommandNotFoundException, + ExecutionException { + /**NON BLOCK**/ + } + +} diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/shell/Program.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/Program.java new file mode 100644 index 000000000..094ee8ebc --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/Program.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.shell; + +import me.toolify.backbone.commands.Executable; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.console.NoSuchFileOrDirectory; +import me.toolify.backbone.console.OperationTimeoutException; + +import java.io.OutputStream; + + +/** + * An abstract class that represents a command that need a shell + * to be executed. + */ +public abstract class Program extends Command implements Executable { + + /** + * An interface for transmitting data to the program + */ + public interface ProgramListener { + /** + * Invoked when a request the program need to write in the shell program. + * + * @param data The data to write to the shell console + * @param offset The initial position in buffer to store the bytes read from this stream + * @param byteCount The maximum number of bytes to store in b + * @return boolean If the write was transmitted successfully + * @throws ExecutionException If the console is not ready + */ + boolean onRequestWrite(byte[] data, int offset, int byteCount) throws ExecutionException; + + /** + * Method that returns the output stream of the console. + * + * @return OutputStream The output stream of the console + */ + OutputStream getOutputStream(); + } + + // The listener for the program + private ProgramListener mProgramListener; + + // Indicate if the program expect some output to stderr. If something is received + // in the stderr the program should be killed + private boolean mExitOnStdErrOutput; + + /** + * @Constructor of Program + * + * @param id The resource identifier of the command + * @param args Arguments of the command (will be formatted with the arguments from + * the command definition) + * @throws InvalidCommandDefinitionException If the command has an invalid definition + */ + public Program(String id, String... args) throws InvalidCommandDefinitionException { + super(id, args); + this.mExitOnStdErrOutput = false; + } + + /** + * @Constructor of Program + * + * @param id The resource identifier of the command + * @param prepare Indicates if the argument must be prepared + * @param args Arguments of the command (will be formatted with the arguments from + * the command definition) + * @throws InvalidCommandDefinitionException If the command has an invalid definition + */ + public Program(String id, boolean prepare, String... args) + throws InvalidCommandDefinitionException { + super(id, prepare, args); + this.mExitOnStdErrOutput = false; + } + + /** + * Method that returns the program listener + * + * @return ProgramListener The program listener + */ + protected ProgramListener getProgramListener() { + return this.mProgramListener; + } + + /** + * Method that sets the program listener + * + * @param programListener The program listener + */ + public void setProgramListener(ProgramListener programListener) { + this.mProgramListener = programListener; + } + + /** + * Method that returns if the program should be killed if some output is received in + * the standard error buffer. + * + * @return boolean If the program should be killed + */ + public boolean isExitOnStdErrOutput() { + return this.mExitOnStdErrOutput; + } + + /** + * Method that sets if the program should be killed if some output is received in + * the standard error buffer. + * + * @param exitOnStdErrOutput If the program should be killed + */ + public void setExitOnStdErrOutput(boolean exitOnStdErrOutput) { + this.mExitOnStdErrOutput = exitOnStdErrOutput; + } + + /** + * Returns whether the shell should wait indefinitely for the end of the command. + * + * @return boolean If shell should wait indefinitely for the end of the command + * @hide + */ + @SuppressWarnings("static-method") + public boolean isIndefinitelyWait() { + return false; + } + + /** + * Returns whether the shell shouldn't raise a {@link OperationTimeoutException} when + * the program didn't exited but new data was received. + * + * @return boolean If shell shouldn't raise a {@link OperationTimeoutException} if new + * data was received + * @hide + */ + @SuppressWarnings("static-method") + public boolean isWaitOnNewDataReceipt() { + return false; + } + + /** + * Method that returns if the standard error must be + * ignored safely by the shell, and don't check for errors + * like NoSuchFileOrDirectory or + * Permission denied by the shell. + * + * @return boolean If the standard error must be ignored + * @hide + */ + @SuppressWarnings("static-method") + public boolean isIgnoreShellStdErrCheck() { + return false; + } + + /** + * Method that checks if the standard errors has exceptions. + * + * @param exitCode Program exit code + * @param err Standard Error buffer + * @throws InsufficientPermissionsException If an operation requires elevated permissions + * @throws NoSuchFileOrDirectory If the file or directory was not found + * @throws CommandNotFoundException If the command was not found + * @throws ExecutionException If the another exception is detected in the standard error + * @hide + */ + @SuppressWarnings("unused") + public void checkStdErr(int exitCode, String err) + throws InsufficientPermissionsException, NoSuchFileOrDirectory, + CommandNotFoundException, ExecutionException { + /**NON BLOCK**/ + } + +} diff --git a/src/com/cyanogenmod/filemanager/commands/shell/QuickFolderSearchCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/QuickFolderSearchCommand.java similarity index 89% rename from src/com/cyanogenmod/filemanager/commands/shell/QuickFolderSearchCommand.java rename to Backbone/src/main/java/me/toolify/backbone/commands/shell/QuickFolderSearchCommand.java index b060381be..b54f9efd8 100644 --- a/src/com/cyanogenmod/filemanager/commands/shell/QuickFolderSearchCommand.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/QuickFolderSearchCommand.java @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands.shell; +package me.toolify.backbone.commands.shell; -import com.cyanogenmod.filemanager.commands.QuickFolderSearchExecutable; -import com.cyanogenmod.filemanager.console.CommandNotFoundException; -import com.cyanogenmod.filemanager.console.ExecutionException; -import com.cyanogenmod.filemanager.console.InsufficientPermissionsException; +import me.toolify.backbone.commands.QuickFolderSearchExecutable; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; import java.io.BufferedReader; import java.io.File; @@ -126,4 +126,12 @@ public boolean isIgnoreShellStdErrCheck() { return true; } + /** + * {@inheritDoc} + */ + @Override + public boolean isWaitOnNewDataReceipt() { + return true; + } + } diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/shell/ReadCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/ReadCommand.java new file mode 100644 index 000000000..d46d92cd7 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/ReadCommand.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.shell; + +import me.toolify.backbone.commands.AsyncResultListener; +import me.toolify.backbone.commands.ReadExecutable; +import me.toolify.backbone.commands.SIGNAL; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; + +/** + * A class for read a file + * + * {@link "http://unixhelp.ed.ac.uk/CGI/man-cgi?cat"} + */ +public class ReadCommand extends AsyncResultProgram implements ReadExecutable { + + private static final String ID = "read"; //$NON-NLS-1$ + + /** + * Constructor of ExecCommand. + * + * @param file The file to read + * @param asyncResultListener The partial result listener + * @throws InvalidCommandDefinitionException If the command has an invalid definition + */ + public ReadCommand( + String file, AsyncResultListener asyncResultListener) + throws InvalidCommandDefinitionException { + super(ID, asyncResultListener, new String[]{file}); + } + + /** + * {@inheritDoc} + */ + @Override + public void onStartParsePartialResult() {/**NON BLOCK**/} + + /** + * {@inheritDoc} + */ + @Override + public void onEndParsePartialResult(boolean cancelled) {/**NON BLOCK**/} + + /** + * {@inheritDoc} + */ + @Override + public void onParsePartialResult(final String partialIn) { + //If a listener is defined, then send the partial result + if (partialIn != null && partialIn.length() > 0) { + if (getAsyncResultListener() != null) { + getAsyncResultListener().onPartialResult(partialIn.getBytes()); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onParseErrorPartialResult(String partialErr) {/**NON BLOCK**/} + + /** + * {@inheritDoc} + */ + @Override + public SIGNAL onRequestEnd() { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isIgnoreShellStdErrCheck() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean parseOnlyCompleteLines() { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public void checkExitCode(int exitCode) + throws InsufficientPermissionsException, CommandNotFoundException, ExecutionException { + // We have not privileges to read the file + if (exitCode == 1) { + throw new InsufficientPermissionsException(); + } + + //Ignore exit code 143 (cancelled) + //Ignore exit code 137 (kill -9) + if (exitCode != 0 && exitCode != 143 && exitCode != 137) { + throw new ExecutionException( + "exitcode != 0 && && exitCode != 1 && != 143 && != 137"); //$NON-NLS-1$ + } + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/shell/ResolveLinkCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/ResolveLinkCommand.java new file mode 100644 index 000000000..376b0d54b --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/ResolveLinkCommand.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.shell; + +import me.toolify.backbone.commands.ResolveLinkExecutable; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.util.FileHelper; +import me.toolify.backbone.util.ParseHelper; + +import java.io.BufferedReader; +import java.io.File; +import java.io.StringReader; +import java.text.ParseException; + + +/** + * A class for retrieve the real file name of a symlink. This command + * can be used too for retrieve the absolute path of a file or directory + * + * {@link "http://unixhelp.ed.ac.uk/CGI/man-cgi?readlink"} + */ +public class ResolveLinkCommand extends SyncResultProgram implements ResolveLinkExecutable { + + private static final String ID = "readlink"; //$NON-NLS-1$ + private FileSystemObject mFso; + + /** + * Constructor of ResolveLinkCommand. + * + * @param src The file system object to read + * @throws InvalidCommandDefinitionException If the command has an invalid definition + */ + public ResolveLinkCommand(String src) throws InvalidCommandDefinitionException { + super(ID, src, + (src.compareTo(FileHelper.ROOT_DIRECTORY) == 0) ? + FileHelper.ROOT_DIRECTORY : + new File(src).getParentFile().getAbsolutePath()); + } + + /** + * {@inheritDoc} + */ + @Override + public void parse(String in, String err) throws ParseException { + // Check the in buffer to extract information + BufferedReader br = null; + try { + br = new BufferedReader(new StringReader(in)); + + // Extract and parse the stat output + String line = br.readLine(); + this.mFso = ParseHelper.parseStatOutput(line); + + } catch (Exception ex) { + throw new ParseException(ex.getMessage(), 0); + + } finally { + try { + if (br != null) { + br.close(); + } + } catch (Throwable ex) { + /**NON BLOCK**/ + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public FileSystemObject getResult() { + return this.mFso; + } + + /** + * {@inheritDoc} + */ + @Override + public void checkExitCode(int exitCode) + throws InsufficientPermissionsException, CommandNotFoundException, ExecutionException { + /**NON BLOCK**/ + } +} diff --git a/src/com/cyanogenmod/filemanager/commands/shell/SendSignalCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/SendSignalCommand.java similarity index 87% rename from src/com/cyanogenmod/filemanager/commands/shell/SendSignalCommand.java rename to Backbone/src/main/java/me/toolify/backbone/commands/shell/SendSignalCommand.java index a8e9aac77..f626eeaf2 100644 --- a/src/com/cyanogenmod/filemanager/commands/shell/SendSignalCommand.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/SendSignalCommand.java @@ -14,13 +14,13 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands.shell; +package me.toolify.backbone.commands.shell; -import com.cyanogenmod.filemanager.commands.SIGNAL; -import com.cyanogenmod.filemanager.commands.SendSignalExecutable; -import com.cyanogenmod.filemanager.console.CommandNotFoundException; -import com.cyanogenmod.filemanager.console.ExecutionException; -import com.cyanogenmod.filemanager.console.InsufficientPermissionsException; +import me.toolify.backbone.commands.SIGNAL; +import me.toolify.backbone.commands.SendSignalExecutable; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; import java.text.ParseException; diff --git a/src/com/cyanogenmod/filemanager/commands/shell/Shell.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/Shell.java similarity index 90% rename from src/com/cyanogenmod/filemanager/commands/shell/Shell.java rename to Backbone/src/main/java/me/toolify/backbone/commands/shell/Shell.java index 36b89fddc..9a65162ca 100644 --- a/src/com/cyanogenmod/filemanager/commands/shell/Shell.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/Shell.java @@ -14,17 +14,17 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands.shell; +package me.toolify.backbone.commands.shell; import android.util.Log; -import com.cyanogenmod.filemanager.commands.SyncResultExecutable; -import com.cyanogenmod.filemanager.commands.WritableExecutable; -import com.cyanogenmod.filemanager.console.CommandNotFoundException; -import com.cyanogenmod.filemanager.console.ExecutionException; -import com.cyanogenmod.filemanager.console.InsufficientPermissionsException; -import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory; -import com.cyanogenmod.filemanager.console.ReadOnlyFilesystemException; +import me.toolify.backbone.commands.SyncResultExecutable; +import me.toolify.backbone.commands.WritableExecutable; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.console.NoSuchFileOrDirectory; +import me.toolify.backbone.console.ReadOnlyFilesystemException; /** * An abstract class that represents a command to wrap others commands, diff --git a/src/com/cyanogenmod/filemanager/commands/shell/ShellExecutableCreator.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/ShellExecutableCreator.java similarity index 83% rename from src/com/cyanogenmod/filemanager/commands/shell/ShellExecutableCreator.java rename to Backbone/src/main/java/me/toolify/backbone/commands/shell/ShellExecutableCreator.java index adc2ea68b..2b4232b88 100644 --- a/src/com/cyanogenmod/filemanager/commands/shell/ShellExecutableCreator.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/ShellExecutableCreator.java @@ -14,49 +14,50 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands.shell; - -import com.cyanogenmod.filemanager.commands.AsyncResultListener; -import com.cyanogenmod.filemanager.commands.ChangeCurrentDirExecutable; -import com.cyanogenmod.filemanager.commands.ChangeOwnerExecutable; -import com.cyanogenmod.filemanager.commands.ChangePermissionsExecutable; -import com.cyanogenmod.filemanager.commands.CompressExecutable; -import com.cyanogenmod.filemanager.commands.CopyExecutable; -import com.cyanogenmod.filemanager.commands.CreateDirExecutable; -import com.cyanogenmod.filemanager.commands.CreateFileExecutable; -import com.cyanogenmod.filemanager.commands.CurrentDirExecutable; -import com.cyanogenmod.filemanager.commands.DeleteDirExecutable; -import com.cyanogenmod.filemanager.commands.DeleteFileExecutable; -import com.cyanogenmod.filemanager.commands.DiskUsageExecutable; -import com.cyanogenmod.filemanager.commands.EchoExecutable; -import com.cyanogenmod.filemanager.commands.ExecExecutable; -import com.cyanogenmod.filemanager.commands.ExecutableCreator; -import com.cyanogenmod.filemanager.commands.FindExecutable; -import com.cyanogenmod.filemanager.commands.FolderUsageExecutable; -import com.cyanogenmod.filemanager.commands.GroupsExecutable; -import com.cyanogenmod.filemanager.commands.IdentityExecutable; -import com.cyanogenmod.filemanager.commands.LinkExecutable; -import com.cyanogenmod.filemanager.commands.ListExecutable; -import com.cyanogenmod.filemanager.commands.MountExecutable; -import com.cyanogenmod.filemanager.commands.MountPointInfoExecutable; -import com.cyanogenmod.filemanager.commands.MoveExecutable; -import com.cyanogenmod.filemanager.commands.ParentDirExecutable; -import com.cyanogenmod.filemanager.commands.ProcessIdExecutable; -import com.cyanogenmod.filemanager.commands.QuickFolderSearchExecutable; -import com.cyanogenmod.filemanager.commands.ReadExecutable; -import com.cyanogenmod.filemanager.commands.ResolveLinkExecutable; -import com.cyanogenmod.filemanager.commands.SIGNAL; -import com.cyanogenmod.filemanager.commands.SendSignalExecutable; -import com.cyanogenmod.filemanager.commands.UncompressExecutable; -import com.cyanogenmod.filemanager.commands.WriteExecutable; -import com.cyanogenmod.filemanager.console.CommandNotFoundException; -import com.cyanogenmod.filemanager.console.shell.ShellConsole; -import com.cyanogenmod.filemanager.model.Group; -import com.cyanogenmod.filemanager.model.MountPoint; -import com.cyanogenmod.filemanager.model.Permissions; -import com.cyanogenmod.filemanager.model.Query; -import com.cyanogenmod.filemanager.model.User; -import com.cyanogenmod.filemanager.preferences.CompressionMode; +package me.toolify.backbone.commands.shell; + +import me.toolify.backbone.commands.AsyncResultListener; +import me.toolify.backbone.commands.ChangeOwnerExecutable; +import me.toolify.backbone.commands.ChangePermissionsExecutable; +import me.toolify.backbone.commands.ChecksumExecutable; +import me.toolify.backbone.commands.CompressExecutable; +import me.toolify.backbone.commands.CopyExecutable; +import me.toolify.backbone.commands.CreateDirExecutable; +import me.toolify.backbone.commands.CreateFileExecutable; +import me.toolify.backbone.commands.DeleteDirExecutable; +import me.toolify.backbone.commands.DeleteFileExecutable; +import me.toolify.backbone.commands.DiskUsageExecutable; +import me.toolify.backbone.commands.EchoExecutable; +import me.toolify.backbone.commands.ExecExecutable; +import me.toolify.backbone.commands.ExecutableCreator; +import me.toolify.backbone.commands.FindExecutable; +import me.toolify.backbone.commands.FolderUsageExecutable; +import me.toolify.backbone.commands.GroupsExecutable; +import me.toolify.backbone.commands.IdentityExecutable; +import me.toolify.backbone.commands.LinkExecutable; +import me.toolify.backbone.commands.ListExecutable; +import me.toolify.backbone.commands.MountExecutable; +import me.toolify.backbone.commands.MountPointInfoExecutable; +import me.toolify.backbone.commands.MoveExecutable; +import me.toolify.backbone.commands.ParentDirExecutable; +import me.toolify.backbone.commands.ProcessIdExecutable; +import me.toolify.backbone.commands.QuickFolderSearchExecutable; +import me.toolify.backbone.commands.ReadExecutable; +import me.toolify.backbone.commands.ResolveLinkExecutable; +import me.toolify.backbone.commands.SIGNAL; +import me.toolify.backbone.commands.SendSignalExecutable; +import me.toolify.backbone.commands.UncompressExecutable; +import me.toolify.backbone.commands.WriteExecutable; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.console.NoSuchFileOrDirectory; +import me.toolify.backbone.console.shell.ShellConsole; +import me.toolify.backbone.model.Group; +import me.toolify.backbone.model.MountPoint; +import me.toolify.backbone.model.Permissions; +import me.toolify.backbone.model.Query; +import me.toolify.backbone.model.User; +import me.toolify.backbone.preferences.CompressionMode; /** * A class for create shell {@link "Executable"} objects. @@ -75,19 +76,6 @@ public class ShellExecutableCreator implements ExecutableCreator { this.mConsole = console; } - /** - * {@inheritDoc} - */ - @Override - public ChangeCurrentDirExecutable createChangeCurrentDirExecutable(String dir) - throws CommandNotFoundException { - try { - return new ChangeCurrentDirCommand(dir); - } catch (InvalidCommandDefinitionException icdEx) { - throw new CommandNotFoundException("ChangeCurrentDirCommand", icdEx); //$NON-NLS-1$ - } - } - /** * {@inheritDoc} */ @@ -153,18 +141,6 @@ public CreateFileExecutable createCreateFileExecutable(String file) } } - /** - * {@inheritDoc} - */ - @Override - public CurrentDirExecutable createCurrentDirExecutable() throws CommandNotFoundException { - try { - return new CurrentDirCommand(); - } catch (InvalidCommandDefinitionException icdEx) { - throw new CommandNotFoundException("CurrentDirCommand", icdEx); //$NON-NLS-1$ - } - } - /** * {@inheritDoc} */ @@ -396,6 +372,19 @@ public ProcessIdExecutable createShellProcessIdExecutable() throws CommandNotFou } } + /** + * {@inheritDoc} + */ + @Override + public ProcessIdExecutable createProcessIdExecutable(int pid) + throws CommandNotFoundException { + try { + return new ProcessIdCommand(pid); + } catch (InvalidCommandDefinitionException icdEx) { + throw new CommandNotFoundException("ProcessIdCommand", icdEx); //$NON-NLS-1$ + } + } + /** * {@inheritDoc} */ @@ -534,4 +523,19 @@ public UncompressExecutable createUncompressExecutable( } } + /** + * {@inheritDoc} + */ + @Override + public ChecksumExecutable createChecksumExecutable( + String src, AsyncResultListener asyncResultListener) + throws CommandNotFoundException, NoSuchFileOrDirectory, + InsufficientPermissionsException { + try { + return new ChecksumCommand(src, asyncResultListener); + } catch (InvalidCommandDefinitionException icdEx) { + throw new CommandNotFoundException("ChecksumCommand", icdEx); //$NON-NLS-1$ + } + } + } diff --git a/src/com/cyanogenmod/filemanager/commands/shell/ShellExecutableFactory.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/ShellExecutableFactory.java similarity index 83% rename from src/com/cyanogenmod/filemanager/commands/shell/ShellExecutableFactory.java rename to Backbone/src/main/java/me/toolify/backbone/commands/shell/ShellExecutableFactory.java index 52fe170b3..18d384b50 100644 --- a/src/com/cyanogenmod/filemanager/commands/shell/ShellExecutableFactory.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/ShellExecutableFactory.java @@ -14,11 +14,11 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands.shell; +package me.toolify.backbone.commands.shell; -import com.cyanogenmod.filemanager.commands.ExecutableCreator; -import com.cyanogenmod.filemanager.commands.ExecutableFactory; -import com.cyanogenmod.filemanager.console.shell.ShellConsole; +import me.toolify.backbone.commands.ExecutableCreator; +import me.toolify.backbone.commands.ExecutableFactory; +import me.toolify.backbone.console.shell.ShellConsole; /** * A class that represents a factory for creating shell {@link "Executable"} objects. diff --git a/src/com/cyanogenmod/filemanager/commands/shell/SuperuserShell.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/SuperuserShell.java similarity index 82% rename from src/com/cyanogenmod/filemanager/commands/shell/SuperuserShell.java rename to Backbone/src/main/java/me/toolify/backbone/commands/shell/SuperuserShell.java index d693137d6..eb970d93f 100644 --- a/src/com/cyanogenmod/filemanager/commands/shell/SuperuserShell.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/SuperuserShell.java @@ -14,14 +14,14 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands.shell; +package me.toolify.backbone.commands.shell; -import com.cyanogenmod.filemanager.console.CommandNotFoundException; -import com.cyanogenmod.filemanager.console.ExecutionException; -import com.cyanogenmod.filemanager.console.InsufficientPermissionsException; -import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory; -import com.cyanogenmod.filemanager.console.ReadOnlyFilesystemException; -import com.cyanogenmod.filemanager.util.ShellHelper; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.console.NoSuchFileOrDirectory; +import me.toolify.backbone.console.ReadOnlyFilesystemException; +import me.toolify.backbone.util.ShellHelper; diff --git a/src/com/cyanogenmod/filemanager/commands/shell/SyncResultProgram.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/SyncResultProgram.java similarity index 97% rename from src/com/cyanogenmod/filemanager/commands/shell/SyncResultProgram.java rename to Backbone/src/main/java/me/toolify/backbone/commands/shell/SyncResultProgram.java index 2d73e58c4..1d2b6c4fe 100644 --- a/src/com/cyanogenmod/filemanager/commands/shell/SyncResultProgram.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/SyncResultProgram.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands.shell; +package me.toolify.backbone.commands.shell; /** diff --git a/src/com/cyanogenmod/filemanager/commands/shell/SyncResultProgramListener.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/SyncResultProgramListener.java similarity index 95% rename from src/com/cyanogenmod/filemanager/commands/shell/SyncResultProgramListener.java rename to Backbone/src/main/java/me/toolify/backbone/commands/shell/SyncResultProgramListener.java index 56585b005..a3da068bf 100644 --- a/src/com/cyanogenmod/filemanager/commands/shell/SyncResultProgramListener.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/SyncResultProgramListener.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands.shell; +package me.toolify.backbone.commands.shell; import java.text.ParseException; diff --git a/src/com/cyanogenmod/filemanager/commands/shell/UncompressCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/UncompressCommand.java similarity index 81% rename from src/com/cyanogenmod/filemanager/commands/shell/UncompressCommand.java rename to Backbone/src/main/java/me/toolify/backbone/commands/shell/UncompressCommand.java index 0bade8aca..34f74b5a9 100644 --- a/src/com/cyanogenmod/filemanager/commands/shell/UncompressCommand.java +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/UncompressCommand.java @@ -14,16 +14,16 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.commands.shell; +package me.toolify.backbone.commands.shell; -import com.cyanogenmod.filemanager.commands.AsyncResultListener; -import com.cyanogenmod.filemanager.commands.SIGNAL; -import com.cyanogenmod.filemanager.commands.UncompressExecutable; -import com.cyanogenmod.filemanager.console.CommandNotFoundException; -import com.cyanogenmod.filemanager.console.ExecutionException; -import com.cyanogenmod.filemanager.console.InsufficientPermissionsException; -import com.cyanogenmod.filemanager.preferences.UncompressionMode; -import com.cyanogenmod.filemanager.util.FileHelper; +import me.toolify.backbone.commands.AsyncResultListener; +import me.toolify.backbone.commands.SIGNAL; +import me.toolify.backbone.commands.UncompressExecutable; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.preferences.UncompressionMode; +import me.toolify.backbone.util.FileHelper; import java.io.File; @@ -85,7 +85,11 @@ private enum Mode { /** * Uncompress using Unix compress algorithm */ - C_UNXZ(UNXZ_ID, "", UncompressionMode.C_UNXZ); //$NON-NLS-1$ + C_UNXZ(UNXZ_ID, "", UncompressionMode.C_UNXZ), //$NON-NLS-1$ + /** + * Uncompress using Rar algorithm + */ + A_UNRAR(UNRAR_ID, "", UncompressionMode.C_UNRAR); //$NON-NLS-1$ final String mId; final String mFlag; @@ -112,12 +116,13 @@ private Mode(String id, String flag, UncompressionMode mode) { private static final String UNLZMA_ID = "unlzma"; //$NON-NLS-1$ private static final String UNCOMPRESS_ID = "uncompress"; //$NON-NLS-1$ private static final String UNXZ_ID = "unxz"; //$NON-NLS-1$ + private static final String UNRAR_ID = "unrar"; //$NON-NLS-1$ private Boolean mResult; private String mPartial; private final String mOutFile; - private final boolean mIsArchive; + private final Mode mMode; /** * Constructor of UncompressCommand.
@@ -146,6 +151,7 @@ public UncompressCommand( throw new InvalidCommandDefinitionException( "Unsupported uncompress mode"); //$NON-NLS-1$ } + this.mMode = mode; // Retrieve information about the uncompress process if (dst != null) { @@ -153,7 +159,6 @@ public UncompressCommand( } else { this.mOutFile = resolveOutputFile(src); } - this.mIsArchive = mode.mMode.mArchive; } /** @@ -173,7 +178,10 @@ public void onEndParsePartialResult(boolean cancelled) { // Send the last partial data if (this.mPartial != null && this.mPartial.length() > 0) { if (getAsyncResultListener() != null) { - getAsyncResultListener().onPartialResult(this.mPartial); + String data = processPartialResult(this.mPartial); + if (data != null) { + getAsyncResultListener().onPartialResult(data); + } } } this.mPartial = ""; //$NON-NLS-1$ @@ -194,14 +202,20 @@ public void onParsePartialResult(final String partialIn) { // Return all the lines, except the last for (int i = 0; i < lines.length-1; i++) { if (getAsyncResultListener() != null) { - getAsyncResultListener().onPartialResult(lines[i]); + String data = processPartialResult(lines[i]); + if (data != null) { + getAsyncResultListener().onPartialResult(data); + } } } // Return the last line? if (endsWithNewLine) { if (getAsyncResultListener() != null) { - getAsyncResultListener().onPartialResult(lines[lines.length-1]); + String data = processPartialResult(lines[lines.length-1]); + if (data != null) { + getAsyncResultListener().onPartialResult(data); + } } this.mPartial = ""; //$NON-NLS-1$ } else { @@ -263,7 +277,36 @@ public String getOutUncompressedFile() { */ @Override public boolean IsArchive() { - return this.mIsArchive; + return this.mMode.mMode.mArchive; + } + + /** + * Method that processes a line to determine if it's a valid partial result + * + * @param line The line to process + * @return String The processed line + */ + private String processPartialResult(String line) { + if (this.mMode.compareTo(Mode.A_UNRAR) == 0) { + if (line.startsWith("Extracting ")) { //$NON-NLS-1$ + int pos = line.indexOf((char)8); + if (pos != -1) { + // Remove progress + return line.substring(12, pos).trim(); + } + return line.substring(12).trim(); + } + return null; + } + + if (this.mMode.compareTo(Mode.A_UNZIP) == 0) { + if (line.startsWith(" inflating: ")) { //$NON-NLS-1$ + return line.substring(13).trim(); + } + return null; + } + + return line; } /** @@ -303,6 +346,7 @@ private static String[] resolveArguments(String src, String dst) { return new String[]{mode.mFlag, out, src}; case A_UNZIP: + case A_UNRAR: return new String[]{out, src}; case C_GUNZIP: diff --git a/Backbone/src/main/java/me/toolify/backbone/commands/shell/WriteCommand.java b/Backbone/src/main/java/me/toolify/backbone/commands/shell/WriteCommand.java new file mode 100644 index 000000000..62160cb95 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/commands/shell/WriteCommand.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.commands.shell; + +import me.toolify.backbone.commands.AsyncResultListener; +import me.toolify.backbone.commands.SIGNAL; +import me.toolify.backbone.commands.WriteExecutable; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * A class for write data to disk.
+ *
+ * User MUST call the {@link #createOutputStream()} to get the output stream where + * write the data.
. When no more exist then user MUST call the onEnd method + * of the asynchronous listener.
+ * + * {@link "http://unixhelp.ed.ac.uk/CGI/man-cgi?dd"} + */ +public class WriteCommand extends AsyncResultProgram implements WriteExecutable { + + private static final String ID = "write"; //$NON-NLS-1$ + + private static final long TIMEOUT = 1000L; + + /** + * @hide + */ + final Object mWriteSync = new Object(); + private boolean mReady; + /** + * @hide + */ + boolean mError; + + /** + * Constructor of WriteCommand. + * + * @param file The file where to write the data + * @param asyncResultListener The partial result listener + * @throws InvalidCommandDefinitionException If the command has an invalid definition + */ + public WriteCommand( + String file, AsyncResultListener asyncResultListener) + throws InvalidCommandDefinitionException { + super(ID, asyncResultListener, file); + this.mReady = false; + this.mError = false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isExpectEnd() { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public OutputStream createOutputStream() throws IOException { + + // Wait until command is ready + synchronized (this.mWriteSync) { + if (!this.mReady) { + try { + this.mWriteSync.wait(TIMEOUT); + } catch (Exception e) {/**NON BLOCK**/} + } + } + return getProgramListener().getOutputStream(); + } + + /** + * {@inheritDoc} + */ + @Override + public void onStartParsePartialResult() { + synchronized (this.mWriteSync) { + this.mReady = true; + this.mWriteSync.notify(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onEndParsePartialResult(boolean cancelled) {/**NON BLOCK**/} + + /** + * {@inheritDoc} + */ + @Override + public SIGNAL onRequestEnd() { + try { + if (this.getProgramListener().getOutputStream() != null) { + this.getProgramListener().getOutputStream().flush(); + } + } catch (Exception ex) {/**NON BLOCK**/} + try { + Thread.yield(); + } catch (Exception ex) {/**NON BLOCK**/} + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public void onParsePartialResult(final String partialIn) {/**NON BLOCK**/} + + /** + * {@inheritDoc} + */ + @Override + public void onParseErrorPartialResult(String partialErr) {/**NON BLOCK**/} + + /** + * {@inheritDoc} + */ + @Override + public boolean isIgnoreShellStdErrCheck() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public void checkExitCode(int exitCode) + throws InsufficientPermissionsException, CommandNotFoundException, ExecutionException { + //Ignore exit code 143 (cancelled) + //Ignore exit code 137 (kill -9) + if (exitCode != 0 && exitCode != 143 && exitCode != 137) { + throw new ExecutionException( + "exitcode != 0 && != 143 && != 137"); //$NON-NLS-1$ + } + } + +} diff --git a/src/com/cyanogenmod/filemanager/console/CommandNotFoundException.java b/Backbone/src/main/java/me/toolify/backbone/console/CommandNotFoundException.java similarity index 96% rename from src/com/cyanogenmod/filemanager/console/CommandNotFoundException.java rename to Backbone/src/main/java/me/toolify/backbone/console/CommandNotFoundException.java index cca7db2a3..b8d725aee 100644 --- a/src/com/cyanogenmod/filemanager/console/CommandNotFoundException.java +++ b/Backbone/src/main/java/me/toolify/backbone/console/CommandNotFoundException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.console; +package me.toolify.backbone.console; /** * An exception thrown when the command was not found diff --git a/src/com/cyanogenmod/filemanager/console/Console.java b/Backbone/src/main/java/me/toolify/backbone/console/Console.java similarity index 90% rename from src/com/cyanogenmod/filemanager/console/Console.java rename to Backbone/src/main/java/me/toolify/backbone/console/Console.java index ba28db55a..1232a9a04 100644 --- a/src/com/cyanogenmod/filemanager/console/Console.java +++ b/Backbone/src/main/java/me/toolify/backbone/console/Console.java @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.cyanogenmod.filemanager.console; - -import com.cyanogenmod.filemanager.commands.AsyncResultExecutable; -import com.cyanogenmod.filemanager.commands.Executable; -import com.cyanogenmod.filemanager.commands.ExecutableFactory; -import com.cyanogenmod.filemanager.model.Identity; -import com.cyanogenmod.filemanager.preferences.FileManagerSettings; -import com.cyanogenmod.filemanager.preferences.Preferences; +package me.toolify.backbone.console; + +import me.toolify.backbone.commands.AsyncResultExecutable; +import me.toolify.backbone.commands.Executable; +import me.toolify.backbone.commands.ExecutableFactory; +import me.toolify.backbone.model.Identity; +import me.toolify.backbone.preferences.FileManagerSettings; +import me.toolify.backbone.preferences.Preferences; /** * This class represents a class for executing commands in the operating system layer, diff --git a/src/com/cyanogenmod/filemanager/console/ConsoleAllocException.java b/Backbone/src/main/java/me/toolify/backbone/console/ConsoleAllocException.java similarity index 96% rename from src/com/cyanogenmod/filemanager/console/ConsoleAllocException.java rename to Backbone/src/main/java/me/toolify/backbone/console/ConsoleAllocException.java index ade454a63..7ad3d474e 100644 --- a/src/com/cyanogenmod/filemanager/console/ConsoleAllocException.java +++ b/Backbone/src/main/java/me/toolify/backbone/console/ConsoleAllocException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.console; +package me.toolify.backbone.console; /** * An exception thrown when an exception occurs allocating a console. diff --git a/src/com/cyanogenmod/filemanager/console/ConsoleBuilder.java b/Backbone/src/main/java/me/toolify/backbone/console/ConsoleBuilder.java similarity index 85% rename from src/com/cyanogenmod/filemanager/console/ConsoleBuilder.java rename to Backbone/src/main/java/me/toolify/backbone/console/ConsoleBuilder.java index 1552abd95..560123a30 100644 --- a/src/com/cyanogenmod/filemanager/console/ConsoleBuilder.java +++ b/Backbone/src/main/java/me/toolify/backbone/console/ConsoleBuilder.java @@ -14,23 +14,22 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.console; +package me.toolify.backbone.console; import android.content.Context; import android.util.Log; import android.widget.Toast; -import com.cyanogenmod.filemanager.FileManagerApplication; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.commands.shell.InvalidCommandDefinitionException; -import com.cyanogenmod.filemanager.console.java.JavaConsole; -import com.cyanogenmod.filemanager.console.shell.NonPriviledgeConsole; -import com.cyanogenmod.filemanager.console.shell.PrivilegedConsole; -import com.cyanogenmod.filemanager.preferences.AccessMode; -import com.cyanogenmod.filemanager.preferences.FileManagerSettings; -import com.cyanogenmod.filemanager.preferences.Preferences; -import com.cyanogenmod.filemanager.util.DialogHelper; -import com.cyanogenmod.filemanager.util.FileHelper; +import me.toolify.backbone.FileManagerApplication; +import me.toolify.backbone.R; +import me.toolify.backbone.commands.shell.InvalidCommandDefinitionException; +import me.toolify.backbone.console.java.JavaConsole; +import me.toolify.backbone.console.shell.NonPriviledgeConsole; +import me.toolify.backbone.console.shell.PrivilegedConsole; +import me.toolify.backbone.preferences.AccessMode; +import me.toolify.backbone.preferences.FileManagerSettings; +import me.toolify.backbone.preferences.Preferences; +import me.toolify.backbone.util.DialogHelper; import java.io.FileNotFoundException; import java.io.IOException; @@ -95,13 +94,6 @@ public static Console getConsole(Context context, boolean createIfNotExists) return null; } createDefaultConsole(context); - } else { - // Need to change the console? Is the appropriate console for the current mode? - if (FileManagerApplication.getAccessMode(). - compareTo(AccessMode.ROOT) == 0 && !isPrivileged()) { - // Force to change the console - createDefaultConsole(context); - } } return sHolder.getConsole(); } @@ -125,7 +117,7 @@ public static boolean changeToNonPrivilegedConsole(Context context) { try { //Create the console, destroy the current console, and marks as current holder = new ConsoleHolder( - createNonPrivilegedConsole(context, FileHelper.ROOT_DIRECTORY)); + createNonPrivilegedConsole(context)); destroyConsole(); sHolder = holder; return true; @@ -157,7 +149,7 @@ public static boolean changeToPrivilegedConsole(Context context) { try { //Create the console, destroy the current console, and marks as current holder = new ConsoleHolder( - createAndCheckPrivilegedConsole(context, FileHelper.ROOT_DIRECTORY)); + createAndCheckPrivilegedConsole(context)); destroyConsole(); sHolder = holder; @@ -243,11 +235,8 @@ public static Console createDefaultConsole(Context context, //Is there a console allocated if (sHolder == null) { sHolder = (superuserMode) - ? new ConsoleHolder( - createAndCheckPrivilegedConsole( - context, FileHelper.ROOT_DIRECTORY)) - : new ConsoleHolder( - createNonPrivilegedConsole(context, FileHelper.ROOT_DIRECTORY)); + ? new ConsoleHolder(createAndCheckPrivilegedConsole(context)) + : new ConsoleHolder(createNonPrivilegedConsole(context)); if (superuserMode) { // Change also the background console to privileged FileManagerApplication.changeBackgroundConsoleToPriviligedConsole(); @@ -275,7 +264,6 @@ public static void destroyConsole() { * Method that creates a new non privileged console. * * @param context The current context - * @param initialDirectory The initial directory of the console * @return Console The non privileged console * @throws FileNotFoundException If the initial directory not exists * @throws IOException If initial directory couldn't be checked @@ -283,7 +271,7 @@ public static void destroyConsole() { * @throws ConsoleAllocException If the console can't be allocated * @see NonPriviledgeConsole */ - public static Console createNonPrivilegedConsole(Context context, String initialDirectory) + public static Console createNonPrivilegedConsole(Context context) throws FileNotFoundException, IOException, InvalidCommandDefinitionException, ConsoleAllocException { @@ -291,14 +279,14 @@ public static Console createNonPrivilegedConsole(Context context, String initial // Is rooted? Then create a shell console if (FileManagerApplication.isDeviceRooted()) { - NonPriviledgeConsole console = new NonPriviledgeConsole(initialDirectory); + NonPriviledgeConsole console = new NonPriviledgeConsole(); console.setBufferSize(bufferSize); console.alloc(); return console; } // No rooted. Then create a java console - JavaConsole console = new JavaConsole(context, initialDirectory, bufferSize); + JavaConsole console = new JavaConsole(context, bufferSize); console.alloc(); return console; } @@ -308,7 +296,6 @@ public static Console createNonPrivilegedConsole(Context context, String initial * privileged console fails, the a non privileged console * * @param context The current context - * @param initialDirectory The initial directory of the console * @return Console The privileged console * @throws FileNotFoundException If the initial directory not exists * @throws IOException If initial directory couldn't be checked @@ -317,10 +304,10 @@ public static Console createNonPrivilegedConsole(Context context, String initial * @throws InsufficientPermissionsException If the console created is not a privileged console * @see PrivilegedConsole */ - public static Console createPrivilegedConsole(Context context, String initialDirectory) + public static Console createPrivilegedConsole(Context context) throws FileNotFoundException, IOException, InvalidCommandDefinitionException, ConsoleAllocException, InsufficientPermissionsException { - PrivilegedConsole console = new PrivilegedConsole(initialDirectory); + PrivilegedConsole console = new PrivilegedConsole(); console.setBufferSize(context.getResources().getInteger(R.integer.buffer_size)); console.alloc(); if (console.getIdentity().getUser().getId() != ROOT_UID) { @@ -340,7 +327,6 @@ public static Console createPrivilegedConsole(Context context, String initialDir * privileged console fails, the a non privileged console * * @param context The current context - * @param initialDirectory The initial directory of the console * @return Console The privileged console * @throws FileNotFoundException If the initial directory not exists * @throws IOException If initial directory couldn't be checked @@ -349,10 +335,10 @@ public static Console createPrivilegedConsole(Context context, String initialDir * @throws InsufficientPermissionsException If the console created is not a privileged console * @see PrivilegedConsole */ - public static Console createAndCheckPrivilegedConsole(Context context, String initialDirectory) + public static Console createAndCheckPrivilegedConsole(Context context) throws FileNotFoundException, IOException, InvalidCommandDefinitionException, ConsoleAllocException, InsufficientPermissionsException { - return createAndCheckPrivilegedConsole(context, initialDirectory, true); + return createAndCheckPrivilegedConsole(context, true); } /** @@ -360,7 +346,6 @@ public static Console createAndCheckPrivilegedConsole(Context context, String in * privileged console fails, the a non privileged console * * @param context The current context - * @param initialDirectory The initial directory of the console * @param silent Indicates that no message have to be displayed * @return Console The privileged console * @throws FileNotFoundException If the initial directory not exists @@ -371,12 +356,12 @@ public static Console createAndCheckPrivilegedConsole(Context context, String in * @see PrivilegedConsole */ public static Console createAndCheckPrivilegedConsole( - Context context, String initialDirectory, boolean silent) + Context context, boolean silent) throws FileNotFoundException, IOException, InvalidCommandDefinitionException, ConsoleAllocException, InsufficientPermissionsException { try { // Create the privileged console - return createPrivilegedConsole(context, initialDirectory); + return createPrivilegedConsole(context); } catch (ConsoleAllocException caEx) { //Show a message with the problem? @@ -404,7 +389,7 @@ public static Console createAndCheckPrivilegedConsole( } //Create the non-privileged console - return createNonPrivilegedConsole(context, initialDirectory); + return createNonPrivilegedConsole(context); } // Rethrow the exception @@ -412,18 +397,6 @@ public static Console createAndCheckPrivilegedConsole( } } - /** - * Method that returns if the current console is a privileged console - * - * @return boolean If the current console is a privileged console - */ - public static boolean isAlloc() { - if (sHolder != null && sHolder.getConsole() != null) { - return true; - } - return false; - } - /** * Method that returns if the current console is a privileged console * diff --git a/src/com/cyanogenmod/filemanager/console/ConsoleHolder.java b/Backbone/src/main/java/me/toolify/backbone/console/ConsoleHolder.java similarity index 97% rename from src/com/cyanogenmod/filemanager/console/ConsoleHolder.java rename to Backbone/src/main/java/me/toolify/backbone/console/ConsoleHolder.java index cb3e86867..06a3227d3 100644 --- a/src/com/cyanogenmod/filemanager/console/ConsoleHolder.java +++ b/Backbone/src/main/java/me/toolify/backbone/console/ConsoleHolder.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.console; +package me.toolify.backbone.console; /** * A class that holds a console. diff --git a/src/com/cyanogenmod/filemanager/console/ExecutionException.java b/Backbone/src/main/java/me/toolify/backbone/console/ExecutionException.java similarity index 96% rename from src/com/cyanogenmod/filemanager/console/ExecutionException.java rename to Backbone/src/main/java/me/toolify/backbone/console/ExecutionException.java index e05d35e28..d5bbb8acb 100644 --- a/src/com/cyanogenmod/filemanager/console/ExecutionException.java +++ b/Backbone/src/main/java/me/toolify/backbone/console/ExecutionException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.console; +package me.toolify.backbone.console; /** * An exception thrown when an operation invocation fails. diff --git a/src/com/cyanogenmod/filemanager/console/InsufficientPermissionsException.java b/Backbone/src/main/java/me/toolify/backbone/console/InsufficientPermissionsException.java similarity index 90% rename from src/com/cyanogenmod/filemanager/console/InsufficientPermissionsException.java rename to Backbone/src/main/java/me/toolify/backbone/console/InsufficientPermissionsException.java index 614e5609e..26e7bd126 100644 --- a/src/com/cyanogenmod/filemanager/console/InsufficientPermissionsException.java +++ b/Backbone/src/main/java/me/toolify/backbone/console/InsufficientPermissionsException.java @@ -14,10 +14,10 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.console; +package me.toolify.backbone.console; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.commands.SyncResultExecutable; +import me.toolify.backbone.R; +import me.toolify.backbone.commands.SyncResultExecutable; /** * An exception thrown when an operation required elevated permissions. diff --git a/src/com/cyanogenmod/filemanager/console/NoSuchFileOrDirectory.java b/Backbone/src/main/java/me/toolify/backbone/console/NoSuchFileOrDirectory.java similarity index 96% rename from src/com/cyanogenmod/filemanager/console/NoSuchFileOrDirectory.java rename to Backbone/src/main/java/me/toolify/backbone/console/NoSuchFileOrDirectory.java index a2e87c7b8..0a334d680 100644 --- a/src/com/cyanogenmod/filemanager/console/NoSuchFileOrDirectory.java +++ b/Backbone/src/main/java/me/toolify/backbone/console/NoSuchFileOrDirectory.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.console; +package me.toolify.backbone.console; /** * An exception thrown when the file or directory is not found. diff --git a/src/com/cyanogenmod/filemanager/console/OperationTimeoutException.java b/Backbone/src/main/java/me/toolify/backbone/console/OperationTimeoutException.java similarity index 96% rename from src/com/cyanogenmod/filemanager/console/OperationTimeoutException.java rename to Backbone/src/main/java/me/toolify/backbone/console/OperationTimeoutException.java index c549fda00..fa8b9c949 100644 --- a/src/com/cyanogenmod/filemanager/console/OperationTimeoutException.java +++ b/Backbone/src/main/java/me/toolify/backbone/console/OperationTimeoutException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.console; +package me.toolify.backbone.console; /** * An exception thrown when the operation exceeds the timeout. diff --git a/src/com/cyanogenmod/filemanager/console/ReadOnlyFilesystemException.java b/Backbone/src/main/java/me/toolify/backbone/console/ReadOnlyFilesystemException.java similarity index 93% rename from src/com/cyanogenmod/filemanager/console/ReadOnlyFilesystemException.java rename to Backbone/src/main/java/me/toolify/backbone/console/ReadOnlyFilesystemException.java index 6e26146bd..fec09de16 100644 --- a/src/com/cyanogenmod/filemanager/console/ReadOnlyFilesystemException.java +++ b/Backbone/src/main/java/me/toolify/backbone/console/ReadOnlyFilesystemException.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.console; +package me.toolify.backbone.console; -import com.cyanogenmod.filemanager.model.MountPoint; +import me.toolify.backbone.model.MountPoint; /** * An exception thrown when an operation is writing in a read-only filesystem. diff --git a/src/com/cyanogenmod/filemanager/console/RelaunchableException.java b/Backbone/src/main/java/me/toolify/backbone/console/RelaunchableException.java similarity index 88% rename from src/com/cyanogenmod/filemanager/console/RelaunchableException.java rename to Backbone/src/main/java/me/toolify/backbone/console/RelaunchableException.java index db5ece7bb..3d72a74c0 100644 --- a/src/com/cyanogenmod/filemanager/console/RelaunchableException.java +++ b/Backbone/src/main/java/me/toolify/backbone/console/RelaunchableException.java @@ -14,11 +14,11 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.console; +package me.toolify.backbone.console; import android.os.AsyncTask; -import com.cyanogenmod.filemanager.commands.SyncResultExecutable; +import me.toolify.backbone.commands.SyncResultExecutable; import java.util.ArrayList; import java.util.List; @@ -41,7 +41,9 @@ public abstract class RelaunchableException extends Exception { public RelaunchableException(SyncResultExecutable executable) { super(); this.mExecutables = new ArrayList(); - addExecutable(executable); + if (executable != null) { + addExecutable(executable); + } } /** @@ -53,7 +55,9 @@ public RelaunchableException(SyncResultExecutable executable) { public RelaunchableException(String detailMessage, SyncResultExecutable executable) { super(detailMessage); this.mExecutables = new ArrayList(); - addExecutable(executable); + if (executable != null) { + addExecutable(executable); + } } /** @@ -67,7 +71,9 @@ public RelaunchableException( String detailMessage, Throwable throwable, SyncResultExecutable executable) { super(detailMessage, throwable); this.mExecutables = new ArrayList(); - addExecutable(executable); + if (executable != null) { + addExecutable(executable); + } } /** @@ -91,7 +97,7 @@ public void addExecutable(SyncResultExecutable executable) { /** * Method that returns the task to execute when the re-execution ends. * - * @return AsyncTask The task to execute when the re-execution ends + * @return ImageAsyncTask The task to execute when the re-execution ends */ public AsyncTask getTask() { return this.mTask; diff --git a/Backbone/src/main/java/me/toolify/backbone/console/java/JavaConsole.java b/Backbone/src/main/java/me/toolify/backbone/console/java/JavaConsole.java new file mode 100644 index 000000000..0fe4bf72e --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/console/java/JavaConsole.java @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.console.java; + +import android.content.Context; +import android.os.Process; +import android.util.Log; + +import me.toolify.backbone.commands.Executable; +import me.toolify.backbone.commands.ExecutableFactory; +import me.toolify.backbone.commands.SIGNAL; +import me.toolify.backbone.commands.java.JavaExecutableFactory; +import me.toolify.backbone.commands.java.Program; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.Console; +import me.toolify.backbone.console.ConsoleAllocException; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.console.NoSuchFileOrDirectory; +import me.toolify.backbone.console.OperationTimeoutException; +import me.toolify.backbone.console.ReadOnlyFilesystemException; +import me.toolify.backbone.model.AID; +import me.toolify.backbone.model.Group; +import me.toolify.backbone.model.Identity; +import me.toolify.backbone.model.User; +import me.toolify.backbone.util.AIDHelper; + +import java.util.ArrayList; + +/** + * An implementation of a {@link Console} based on a java implementation.
+ *
+ * This console is a non-privileged console an many of the functionality is not implemented + * because can't be obtain from java api. + */ +public final class JavaConsole extends Console { + + private static final String TAG = "JavaConsole"; //$NON-NLS-1$ + + private boolean mActive; + + private final Context mCtx; + private final int mBufferSize; + + /** + * Constructor of JavaConsole + * + * @param ctx The current context + * @param bufferSize The buffer size + */ + public JavaConsole(Context ctx, int bufferSize) { + super(); + this.mCtx = ctx; + this.mBufferSize = bufferSize; + } + + /** + * {@inheritDoc} + */ + @Override + public void alloc() throws ConsoleAllocException { + try { + if (isTrace()) { + Log.v(TAG, "Allocating Java console"); //$NON-NLS-1$ + } + this.mActive = true; + } catch (Exception e) { + Log.e(TAG, "Failed to allocate Java console", e); //$NON-NLS-1$ + throw new ConsoleAllocException("failed to build console", e); //$NON-NLS-1$ + } + } + + /** + * {@inheritDoc} + */ + @Override + public void dealloc() { + if (isTrace()) { + Log.v(TAG, "Deallocating Java console"); //$NON-NLS-1$ + } + this.mActive = true; + } + + /** + * {@inheritDoc} + */ + @Override + public void realloc() throws ConsoleAllocException { + dealloc(); + alloc(); + } + + /** + * {@inheritDoc} + */ + @Override + public ExecutableFactory getExecutableFactory() { + return new JavaExecutableFactory(this); + } + + /** + * {@inheritDoc} + */ + @Override + public Identity getIdentity() { + AID aid = AIDHelper.getAID(Process.myUid()); + if (aid == null) return null; + return new Identity( + new User(aid.getId(), aid.getName()), + new Group(aid.getId(), aid.getName()), + new ArrayList()); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isPrivileged() { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isActive() { + return this.mActive; + } + + /** + * Method that returns the current context + * + * @return Context The current context + */ + public Context getCtx() { + return this.mCtx; + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void execute(Executable executable) throws ConsoleAllocException, + InsufficientPermissionsException, NoSuchFileOrDirectory, + OperationTimeoutException, ExecutionException, + CommandNotFoundException, ReadOnlyFilesystemException { + // Check that the program is a java program + try { + Program p = (Program)executable; + p.isTrace(); + } catch (Throwable e) { + Log.e(TAG, String.format("Failed to resolve program: %s", //$NON-NLS-1$ + executable.getClass().toString()), e); + throw new CommandNotFoundException("executable is not a program", e); //$NON-NLS-1$ + } + + //Auditing program execution + if (isTrace()) { + Log.v(TAG, String.format("Executing program: %s", //$NON-NLS-1$ + executable.getClass().toString())); + } + + // Execute the program + final Program program = (Program)executable; + program.setTrace(isTrace()); + program.setBufferSize(this.mBufferSize); + if (program.isAsynchronous()) { + // Execute in a thread + Thread t = new Thread() { + @Override + public void run() { + try { + program.execute(); + } catch (Exception e) { + // Program must use onException to communicate exceptions + Log.v(TAG, + String.format("Async execute failed program: %s", //$NON-NLS-1$ + program.getClass().toString())); + } + } + }; + t.start(); + + } else { + // Synchronous execution + program.execute(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onCancel() { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onSendSignal(SIGNAL signal) { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onEnd() { + return false; + } + +} \ No newline at end of file diff --git a/Backbone/src/main/java/me/toolify/backbone/console/shell/NonPriviledgeConsole.java b/Backbone/src/main/java/me/toolify/backbone/console/shell/NonPriviledgeConsole.java new file mode 100644 index 000000000..65f46b97a --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/console/shell/NonPriviledgeConsole.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.console.shell; + +import me.toolify.backbone.commands.shell.BashShell; +import me.toolify.backbone.commands.shell.InvalidCommandDefinitionException; + +import java.io.FileNotFoundException; +import java.io.IOException; + +/** + * A class that represents a non privileged shell console. + * + * @see ShellConsole + * @see BashShell + */ +public class NonPriviledgeConsole extends ShellConsole { + + /** + * Constructor of NonPriviledgeConsole. + * + * @throws FileNotFoundException If the default initial directory not exists + * @throws IOException If initial directory couldn't be checked + * @throws InvalidCommandDefinitionException If the command has an invalid definition + */ + public NonPriviledgeConsole() + throws FileNotFoundException, IOException, InvalidCommandDefinitionException { + super(new BashShell()); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isPrivileged() { + return false; + } + +} diff --git a/Backbone/src/main/java/me/toolify/backbone/console/shell/PrivilegedConsole.java b/Backbone/src/main/java/me/toolify/backbone/console/shell/PrivilegedConsole.java new file mode 100644 index 000000000..684a7071c --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/console/shell/PrivilegedConsole.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.console.shell; + +import me.toolify.backbone.commands.shell.InvalidCommandDefinitionException; +import me.toolify.backbone.commands.shell.SuperuserShell; + +import java.io.FileNotFoundException; +import java.io.IOException; + + +/** + * A class that represents a privileged shell console. + * + * @see ShellConsole + * @see SuperuserShell + */ +public class PrivilegedConsole extends ShellConsole { + + /** + * Constructor of PrivilegedConsole. + * + * @throws FileNotFoundException If the default initial directory not exists + * @throws IOException If initial directory couldn't be checked + * @throws InvalidCommandDefinitionException If the command has an invalid definition + */ + public PrivilegedConsole() + throws FileNotFoundException, IOException, InvalidCommandDefinitionException { + super(new SuperuserShell()); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isPrivileged() { + return true; + } + +} diff --git a/Backbone/src/main/java/me/toolify/backbone/console/shell/ShellConsole.java b/Backbone/src/main/java/me/toolify/backbone/console/shell/ShellConsole.java new file mode 100644 index 000000000..2a4c61691 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/console/shell/ShellConsole.java @@ -0,0 +1,1367 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.console.shell; + +import android.util.Log; + +import me.toolify.backbone.FileManagerApplication; +import me.toolify.backbone.commands.AsyncResultExecutable; +import me.toolify.backbone.commands.Executable; +import me.toolify.backbone.commands.ExecutableFactory; +import me.toolify.backbone.commands.GroupsExecutable; +import me.toolify.backbone.commands.IdentityExecutable; +import me.toolify.backbone.commands.ProcessIdExecutable; +import me.toolify.backbone.commands.SIGNAL; +import me.toolify.backbone.commands.shell.AsyncResultProgram; +import me.toolify.backbone.commands.shell.Command; +import me.toolify.backbone.commands.shell.InvalidCommandDefinitionException; +import me.toolify.backbone.commands.shell.Program; +import me.toolify.backbone.commands.shell.Shell; +import me.toolify.backbone.commands.shell.ShellExecutableFactory; +import me.toolify.backbone.commands.shell.SyncResultProgram; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.Console; +import me.toolify.backbone.console.ConsoleAllocException; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.console.NoSuchFileOrDirectory; +import me.toolify.backbone.console.OperationTimeoutException; +import me.toolify.backbone.console.ReadOnlyFilesystemException; +import me.toolify.backbone.model.Identity; +import me.toolify.backbone.util.CommandHelper; +import me.toolify.backbone.util.FileHelper; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.SecureRandom; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * An implementation of a {@link Console} based in the execution of shell commands.
+ *
+ * This class holds a shell bash program associated with the application, being + * a wrapper to execute all other programs (like shell does in linux), capturing the + * output (stdin and stderr) and the exit code of the program executed. + */ +public abstract class ShellConsole extends Console implements Program.ProgramListener { + + private static final String TAG = "ShellConsole"; //$NON-NLS-1$ + + // A timeout of 3 seconds should be enough for no-debugging environments + private static final long DEFAULT_TIMEOUT = + FileManagerApplication.isDebuggable() ? 20000L : 3000L; + + // A maximum operation timeout independently of the isWaitOnNewDataReceipt + // of the program. A synchronous operation must not be more longer than + // MAX_OPERATION_TIMEOUT + DEFAULT_TIMEOUT + private static final long MAX_OPERATION_TIMEOUT = 30000L; + + private static final int DEFAULT_BUFFER = 512; + + //Shell References + private final Shell mShell; + private Identity mIdentity; + + //Process References + private final Object mSync = new Object(); + /** + * @hide + */ + final Object mPartialSync = new Object(); + /** + * @hide + */ + boolean mActive = false; + private boolean mFinished = true; + private boolean mNewData = false; + private Process mProc = null; + /** + * @hide + */ + Program mActiveCommand = null; + /** + * @hide + */ + boolean mCancelled; + /** + * @hide + */ + boolean mStarted; + + //Buffers + private InputStream mIn = null; + private InputStream mErr = null; + private OutputStream mOut = null; + /** + * @hide + */ + StringBuffer mSbIn = null; + /** + * @hide + */ + StringBuffer mSbErr = null; + + private final SecureRandom mRandom; + private String mStartControlPattern; + private String mEndControlPattern; + + /** + * @hide + */ + int mBufferSize; + + private final ShellExecutableFactory mExecutableFactory; + + /** + * Constructor of ShellConsole. + * + * @param shell The shell used to execute commands + * @throws FileNotFoundException If the initial directory not exists + * @throws IOException If initial directory couldn't be resolved + */ + public ShellConsole(Shell shell) + throws FileNotFoundException, IOException { + super(); + this.mShell = shell; + this.mExecutableFactory = new ShellExecutableFactory(this); + + this.mBufferSize = DEFAULT_BUFFER; + + //Restart the buffers + this.mSbIn = new StringBuffer(); + this.mSbErr = new StringBuffer(); + + //Generate an aleatory secure random generator + try { + this.mRandom = SecureRandom.getInstance("SHA1PRNG"); //$NON-NLS-1$ + } catch (Exception ex) { + throw new IOException(ex); + } + } + + /** + * {@inheritDoc} + */ + @Override + public ExecutableFactory getExecutableFactory() { + return this.mExecutableFactory; + } + + /** + * {@inheritDoc} + */ + @Override + public Identity getIdentity() { + return this.mIdentity; + } + + /** + * Method that returns the buffer size + * + * @return int The buffer size + */ + public int getBufferSize() { + return this.mBufferSize; + } + + /** + * Method that sets the buffer size + * + * @param bufferSize the The buffer size + */ + public void setBufferSize(int bufferSize) { + this.mBufferSize = bufferSize; + } + + /** + * {@inheritDoc} + */ + @Override + public final boolean isActive() { + return this.mActive; + } + + /** + * {@inheritDoc} + */ + @Override + public final void alloc() throws ConsoleAllocException { + try { + //Create command string + List cmd = new ArrayList(); + cmd.add(this.mShell.getCommand()); + if (this.mShell.getArguments() != null && this.mShell.getArguments().length() > 0) { + cmd.add(this.mShell.getArguments()); + } + + //Create the process + Runtime rt = Runtime.getRuntime(); + this.mProc = + rt.exec( + cmd.toArray(new String[cmd.size()]), + null, + new File(FileHelper.ROOT_DIRECTORY).getCanonicalFile()); + synchronized (this.mSync) { + this.mActive = true; + } + if (isTrace()) { + Log.v(TAG, + String.format("Create console %s, command: %s, args: %s", //$NON-NLS-1$ + this.mShell.getId(), + this.mShell.getCommand(), + this.mShell.getArguments())); + } + + //Allocate buffers + this.mIn = this.mProc.getInputStream(); + this.mErr = this.mProc.getErrorStream(); + this.mOut = this.mProc.getOutputStream(); + if (this.mIn == null || this.mErr == null || this.mOut == null) { + try { + dealloc(); + } catch (Throwable ex) { + /**NON BLOCK**/ + } + throw new ConsoleAllocException("Console buffer allocation error."); //$NON-NLS-1$ + } + + //Starts a thread for extract output, and check timeout + createStdInThread(this.mIn); + createStdErrThread(this.mErr); + + //Wait for thread start + Thread.sleep(50L); + + //Check if process its active + checkIfProcessExits(); + synchronized (this.mSync) { + if (!this.mActive) { + throw new ConsoleAllocException("Shell not started."); //$NON-NLS-1$ + } + } + + // Retrieve the PID of the shell + ProcessIdExecutable processIdCmd = + getExecutableFactory(). + newCreator().createShellProcessIdExecutable(); + // Wait indefinitely if the console is allocating a su command. We need to + // wait to user response to SuperUser or SuperSu prompt (or whatever it is) + // The rest of sync operations will run with a timeout. + execute(processIdCmd, this.isPrivileged()); + Integer pid = null; + try { + pid = processIdCmd.getResult().get(0); + } catch (Exception e) { + // Ignore + } + if (pid == null) { + throw new ConsoleAllocException( + "can't retrieve the PID of the shell."); //$NON-NLS-1$ + } + this.mShell.setPid(pid.intValue()); + + //Retrieve identity + IdentityExecutable identityCmd = + getExecutableFactory().newCreator().createIdentityExecutable(); + execute(identityCmd); + this.mIdentity = identityCmd.getResult(); + // Identity command is required for root console detection, + // but Groups command is not used for now. Also, this command is causing + // problems on some implementations (maybe toolbox?) which don't + // recognize the root AID and returns an error. Safely ignore on error. + try { + if (this.mIdentity.getGroups().size() == 0) { + //Try with groups + GroupsExecutable groupsCmd = + getExecutableFactory().newCreator().createGroupsExecutable(); + execute(groupsCmd); + this.mIdentity.setGroups(groupsCmd.getResult()); + } + } catch (Exception ex) { + Log.w(TAG, "Groups command failed. Ignored.", ex); //$NON-NLS-1$ + } + + } catch (Exception ex) { + try { + dealloc(); + } catch (Throwable ex2) { + /**NON BLOCK**/ + } + throw new ConsoleAllocException("Console allocation error.", ex); //$NON-NLS-1$ + } + + } + + /** + * {@inheritDoc} + */ + @Override + public final void dealloc() { + synchronized (this.mSync) { + if (this.mActive) { + this.mActive = false; + this.mFinished = true; + + //Close buffers + try { + if (this.mIn != null) { + this.mIn.close(); + } + } catch (Throwable ex) { + /**NON BLOCK**/ + } + try { + if (this.mErr != null) { + this.mErr.close(); + } + } catch (Throwable ex) { + /**NON BLOCK**/ + } + try { + if (this.mOut != null) { + this.mOut.close(); + } + } catch (Throwable ex) { + /**NON BLOCK**/ + } + try { + this.mProc.destroy(); + } catch (Throwable e) {/**NON BLOCK**/} + this.mIn = null; + this.mErr = null; + this.mOut = null; + this.mSbIn = null; + this.mSbErr = null; + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public final void realloc() throws ConsoleAllocException { + dealloc(); + alloc(); + } + + /** + * {@inheritDoc} + */ + @Override + public final synchronized void execute(final Executable executable) + throws ConsoleAllocException, InsufficientPermissionsException, + CommandNotFoundException, NoSuchFileOrDirectory, + OperationTimeoutException, ExecutionException, ReadOnlyFilesystemException { + execute(executable, false); + } + + /** + * Method for execute a command in the operating system layer. + * + * @param executable The executable command to be executed + * @param waitForSu Wait for su (do not used timeout) + * @throws ConsoleAllocException If the console is not allocated + * @throws InsufficientPermissionsException If an operation requires elevated permissions + * @throws NoSuchFileOrDirectory If the file or directory was not found + * @throws OperationTimeoutException If the operation exceeded the maximum time of wait + * @throws CommandNotFoundException If the executable program was not found + * @throws ExecutionException If the operation returns a invalid exit code + * @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem + */ + private synchronized void execute(final Executable executable, final boolean waitForSu) + throws ConsoleAllocException, InsufficientPermissionsException, + CommandNotFoundException, NoSuchFileOrDirectory, + OperationTimeoutException, ExecutionException, ReadOnlyFilesystemException { + + //Is a program? + if (!(executable instanceof Program)) { + throw new CommandNotFoundException("executable not instanceof Program"); //$NON-NLS-1$ + } + + //Asynchronous or synchronous execution? + final Program program = (Program)executable; + if (executable instanceof AsyncResultExecutable) { + Thread asyncThread = new Thread(new Runnable() { + @Override + public void run() { + //Synchronous execution (but asynchronous running in a thread) + //This way syncExecute is locked until this thread ends + try { + //Synchronous execution (2 tries with 1 reallocation) + final ShellConsole shell = ShellConsole.this; + if (shell.syncExecute(program, true, false)) { + shell.syncExecute(program, false, false); + } + } catch (Exception ex) { + if (((AsyncResultExecutable)executable).getAsyncResultListener() != null) { + ((AsyncResultExecutable)executable). + getAsyncResultListener().onException(ex); + } else { + //Capture exception + Log.e(TAG, "Fail asynchronous execution", ex); //$NON-NLS-1$ + } + } + } + }); + asyncThread.start(); + } else { + //Synchronous execution (2 tries with 1 reallocation) + program.setExitOnStdErrOutput(waitForSu); + if (syncExecute(program, true, waitForSu) && !waitForSu) { + syncExecute(program, false, false); + } + } + } + + /** + * Method for execute a program command in the operating system layer in a synchronous way. + * + * @param program The program to execute + * @param reallocate If the console must be reallocated on i/o error + * @param waitForSu Wait for su (do not used timeout) + * @return boolean If the console was reallocated + * @throws ConsoleAllocException If the console is not allocated + * @throws InsufficientPermissionsException If an operation requires elevated permissions + * @throws CommandNotFoundException If the command was not found + * @throws NoSuchFileOrDirectory If the file or directory was not found + * @throws OperationTimeoutException If the operation exceeded the maximum time of wait + * @throws ExecutionException If the operation returns a invalid exit code + * @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem + * @hide + */ + synchronized boolean syncExecute( + final Program program, boolean reallocate, boolean waitForSu) + throws ConsoleAllocException, InsufficientPermissionsException, + CommandNotFoundException, NoSuchFileOrDirectory, + OperationTimeoutException, ExecutionException, ReadOnlyFilesystemException { + + try { + //Check the console status before send command + checkConsole(); + + synchronized (this.mSync) { + if (!this.mActive) { + throw new ConsoleAllocException("No console allocated"); //$NON-NLS-1$ + } + } + + //Saves the active command reference + this.mActiveCommand = program; + + //Reset the buffers + this.mStarted = false; + this.mCancelled = false; + this.mSbIn = new StringBuffer(); + this.mSbErr = new StringBuffer(); + + //Random start/end identifiers + String startId1 = + String.format("/#%d#/", Long.valueOf(this.mRandom.nextLong())); //$NON-NLS-1$ + String startId2 = + String.format("/#%d#/", Long.valueOf(this.mRandom.nextLong())); //$NON-NLS-1$ + String endId1 = + String.format("/#%d#/", Long.valueOf(this.mRandom.nextLong())); //$NON-NLS-1$ + String endId2 = + String.format("/#%d#/", Long.valueOf(this.mRandom.nextLong())); //$NON-NLS-1$ + + //Create command string + String cmd = program.getCommand(); + String args = program.getArguments(); + + //Audit command + if (isTrace()) { + Log.v(TAG, + String.format("%s-%s, command: %s, args: %s", //$NON-NLS-1$ + this.mShell.getId(), + program.getId(), + cmd, + args)); + } + + //Is asynchronous program? Then set asynchronous + program.setProgramListener(this); + if (program instanceof AsyncResultProgram) { + ((AsyncResultProgram)program).setOnCancelListener(this); + ((AsyncResultProgram)program).setOnEndListener(this); + } + + //Send the command + a control code with exit code + //The process has finished where control control code is present. + //This control code is unique in every invocation and is secure random + //generated (control code 1 + exit code + control code 2) + try { + boolean hasEndControl = (!(program instanceof AsyncResultProgram) || + (program instanceof AsyncResultProgram && + ((AsyncResultProgram)program).isExpectEnd())); + + this.mStartControlPattern = startId1 + "\\d{1,3}" + startId2; //$NON-NLS-1$ + this.mEndControlPattern = endId1 + "\\d{1,3}" + endId2; //$NON-NLS-1$ + String startCmd = + Command.getStartCodeCommandInfo( + FileManagerApplication.getInstance().getResources()); + startCmd = String.format( + startCmd, "'" + startId1 +//$NON-NLS-1$ + "'", "'" + startId2 + "'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + String endCmd = + Command.getExitCodeCommandInfo( + FileManagerApplication.getInstance().getResources()); + endCmd = String.format( + endCmd, "'" + endId1 + //$NON-NLS-1$ + "'", "'" + endId2 + "'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + StringBuilder sb = new StringBuilder() + .append(startCmd) + .append(" ") //$NON-NLS-1$ + .append(cmd) + .append(" ") //$NON-NLS-1$ + .append(args); + if (hasEndControl) { + sb = sb.append(" ") //$NON-NLS-1$ + .append(endCmd); + } + sb.append(FileHelper.NEWLINE); + synchronized (this.mSync) { + this.mFinished = false; + this.mNewData = false; + this.mOut.write(sb.toString().getBytes()); + } + } catch (InvalidCommandDefinitionException icdEx) { + throw new CommandNotFoundException( + "ExitCodeCommandInfo not found", icdEx); //$NON-NLS-1$ + } + + //Now, wait for buffers to be filled + synchronized (this.mSync) { + if (!this.mFinished) { + if (waitForSu || program.isIndefinitelyWait()) { + this.mSync.wait(); + } else { + final long start = System.currentTimeMillis(); + while (true) { + this.mSync.wait(DEFAULT_TIMEOUT); + if (!this.mFinished) { + final long end = System.currentTimeMillis(); + if (!program.isWaitOnNewDataReceipt() || + !this.mNewData || + (end - start >= MAX_OPERATION_TIMEOUT)) { + throw new OperationTimeoutException(end - start, cmd); + } + + // Still waiting for program ending + this.mNewData = false; + continue; + } + break; + } + } + } + } + + //End partial results? + if (program instanceof AsyncResultProgram) { + synchronized (this.mPartialSync) { + ((AsyncResultProgram)program).onRequestEndParsePartialResult(this.mCancelled); + } + } + + //Retrieve exit code + int exitCode = getExitCode(this.mSbIn); + if (program instanceof AsyncResultProgram) { + synchronized (this.mPartialSync) { + ((AsyncResultProgram)program).onRequestExitCode(exitCode); + } + } + if (isTrace()) { + Log.v(TAG, + String.format("%s-%s, command: %s, exitCode: %s", //$NON-NLS-1$ + this.mShell.getId(), + program.getId(), + cmd, + String.valueOf(exitCode))); + } + + //Check if invocation was successfully or not + if (!program.isIgnoreShellStdErrCheck()) { + //Wait for stderr buffer to be filled + if (exitCode != 0) { + try { + Thread.sleep(100L); + } catch (Throwable ex) {/**NON BLOCK**/} + } + this.mShell.checkStdErr(this.mActiveCommand, exitCode, this.mSbErr.toString()); + } + this.mShell.checkExitCode(exitCode); + program.checkExitCode(exitCode); + program.checkStdErr(exitCode, this.mSbErr.toString()); + + //Parse the result? Only if not partial results + if (program instanceof SyncResultProgram) { + try { + ((SyncResultProgram)program).parse( + this.mSbIn.toString(), this.mSbErr.toString()); + } catch (ParseException pEx) { + throw new ExecutionException( + "SyncResultProgram parse failed", pEx); //$NON-NLS-1$ + } + } + + //Invocation finished. Now program.getResult() has the result of + //the operation, if any exists + + } catch (OperationTimeoutException otEx) { + try { + killCurrentCommand(); + } catch (Exception e) { /**NON BLOCK **/} + throw otEx; + + } catch (IOException ioEx) { + if (reallocate) { + realloc(); + return true; + } + throw new ExecutionException("Console allocation error.", ioEx); //$NON-NLS-1$ + + } catch (InterruptedException ioEx) { + if (reallocate) { + realloc(); + return true; + } + throw new ExecutionException("Console allocation error.", ioEx); //$NON-NLS-1$ + + } finally { + //Dereference the active command + this.mActiveCommand = null; + } + + //Operation complete + return false; + } + + /** + * Method that creates the standard input thread for read program response. + * + * @param in The standard input buffer + * @return Thread The standard input thread + */ + private Thread createStdInThread(final InputStream in) { + Thread t = new Thread(new Runnable() { + @SuppressWarnings("synthetic-access") + @Override + public void run() { + final ShellConsole shell = ShellConsole.this; + int read = 0; + StringBuffer sb = null; + try { + while (shell.mActive) { + //Read only one byte with active wait + final int r = in.read(); + if (r == -1) { + break; + } + + // Type of command + boolean async = + shell.mActiveCommand != null && + shell.mActiveCommand instanceof AsyncResultProgram; + if (!async || sb == null) { + sb = new StringBuffer(); + } + + if (!shell.mCancelled) { + shell.mSbIn.append((char)r); + if (!shell.mStarted) { + shell.mStarted = isCommandStarted(shell.mSbIn); + if (shell.mStarted) { + sb = new StringBuffer(shell.mSbIn.toString()); + if (async) { + synchronized (shell.mPartialSync) { + ((AsyncResultProgram) + shell.mActiveCommand). + onRequestStartParsePartialResult(); + } + } + } else { + sb.append(shell.mSbIn.toString()); + } + } else { + sb.append((char)r); + } + + // New data received + onNewData(); + + //Check if the command has finished (and extract the control) + boolean finished = isCommandFinished(shell.mSbIn, sb); + + //Notify asynchronous partial data + if (shell.mStarted && async) { + AsyncResultProgram program = + ((AsyncResultProgram)shell.mActiveCommand); + String partial = sb.toString(); + int cc = shell.mEndControlPattern.length(); + if (partial.length() >= cc) { + program.onRequestParsePartialResult(partial); + shell.toStdIn(partial); + + // Reset the temp buffer + sb = new StringBuffer(); + } + } + + if (finished) { + if (!async) { + shell.toStdIn(String.valueOf((char)r)); + } else { + AsyncResultProgram program = + ((AsyncResultProgram)shell.mActiveCommand); + String partial = sb.toString(); + if (program != null) { + program.onRequestParsePartialResult(partial); + } + shell.toStdIn(partial); + } + + //Notify the end + notifyProcessFinished(); + break; + } + if (!async && !finished) { + shell.toStdIn(String.valueOf((char)r)); + } + } + + //Has more data? Read with available as more as exists + //or maximum loop count is rebased + int count = 0; + while (in.available() > 0 && count < 10) { + count++; + int available = + Math.min(in.available(), shell.mBufferSize); + byte[] data = new byte[available]; + read = in.read(data); + + // Type of command + async = + shell.mActiveCommand != null && + shell.mActiveCommand instanceof AsyncResultProgram; + + // Exit if active command is cancelled + if (shell.mCancelled) continue; + + final String s = new String(data, 0, read); + shell.mSbIn.append(s); + if (!shell.mStarted) { + shell.mStarted = isCommandStarted(shell.mSbIn); + if (shell.mStarted) { + sb = new StringBuffer(shell.mSbIn.toString()); + if (async) { + synchronized (shell.mPartialSync) { + AsyncResultProgram p = + ((AsyncResultProgram)shell.mActiveCommand); + if (p != null) { + p.onRequestStartParsePartialResult(); + } + } + } + } else { + sb.append(shell.mSbIn.toString()); + } + } else { + sb.append(s); + } + + // New data received + onNewData(); + + //Check if the command has finished (and extract the control) + boolean finished = isCommandFinished(shell.mSbIn, sb); + + //Notify asynchronous partial data + if (async) { + AsyncResultProgram program = ((AsyncResultProgram)shell.mActiveCommand); + String partial = sb.toString(); + int cc = shell.mEndControlPattern.length(); + if (partial.length() >= cc) { + if (program != null) { + program.onRequestParsePartialResult(partial); + } + shell.toStdIn(partial); + + // Reset the temp buffer + sb = new StringBuffer(); + } + } + + if (finished) { + if (!async) { + shell.toStdIn(s); + } else { + AsyncResultProgram program = + ((AsyncResultProgram)shell.mActiveCommand); + String partial = sb.toString(); + if (program != null) { + program.onRequestParsePartialResult(partial); + } + shell.toStdIn(partial); + } + + //Notify the end + notifyProcessFinished(); + break; + } + if (!async && !finished) { + shell.toStdIn(s); + } + + //Wait for buffer to be filled + try { + Thread.sleep(1L); + } catch (Throwable ex) {/**NON BLOCK**/} + } + + //Asynchronous programs can cause a lot of output, control buffers + //for a low memory footprint + if (async) { + trimBuffer(shell.mSbIn); + trimBuffer(shell.mSbErr); + } + + //Check if process has exited + checkIfProcessExits(); + } + } catch (Exception ioEx) { + notifyProcessExit(ioEx); + } + } + }); + t.setName(String.format("%s", "stdin")); //$NON-NLS-1$//$NON-NLS-2$ + t.start(); + return t; + } + + /** + * Method that echoes the stdin + * + * @param stdin The buffer of the stdin + * @hide + */ + void toStdIn(String stdin) { + //Audit (if not cancelled) + if (!this.mCancelled && isTrace() && stdin.length() > 0) { + Log.v(TAG, + String.format( + "stdin: %s", stdin)); //$NON-NLS-1$ + } + } + + /** + * Method that creates the standard error thread for read program response. + * + * @param err The standard error buffer + * @return Thread The standard error thread + */ + private Thread createStdErrThread(final InputStream err) { + Thread t = new Thread(new Runnable() { + @Override + public void run() { + final ShellConsole shell = ShellConsole.this; + int read = 0; + try { + while (shell.mActive) { + //Read only one byte with active wait + int r = err.read(); + if (r == -1) { + break; + } + + // Has the process received something that we dont expect? + if (shell.mActiveCommand != null && + shell.mActiveCommand.isExitOnStdErrOutput()) { + notifyProcessFinished(); + continue; + } + + // Type of command + boolean async = + shell.mActiveCommand != null && + shell.mActiveCommand instanceof AsyncResultProgram; + + StringBuffer sb = new StringBuffer(); + if (!shell.mCancelled) { + shell.mSbErr.append((char)r); + sb.append((char)r); + + //Notify asynchronous partial data + if (shell.mStarted && async) { + AsyncResultProgram program = + ((AsyncResultProgram)shell.mActiveCommand); + if (program != null) { + program.parsePartialErrResult(new String(new char[]{(char)r})); + } + } + + toStdErr(sb.toString()); + } + + // New data received + onNewData(); + + //Has more data? Read with available as more as exists + //or maximum loop count is rebased + int count = 0; + while (err.available() > 0 && count < 10) { + count++; + int available = Math.min(err.available(), shell.mBufferSize); + byte[] data = new byte[available]; + read = err.read(data); + + // Type of command + async = + shell.mActiveCommand != null && + shell.mActiveCommand instanceof AsyncResultProgram; + + // Add to stderr + String s = new String(data, 0, read); + shell.mSbErr.append(s); + sb.append(s); + + //Notify asynchronous partial data + if (async) { + AsyncResultProgram program = + ((AsyncResultProgram)shell.mActiveCommand); + if (program != null) { + program.parsePartialErrResult(s); + } + } + toStdErr(s); + + // Has the process received something that we dont expect? + if (shell.mActiveCommand != null && + shell.mActiveCommand.isExitOnStdErrOutput()) { + notifyProcessFinished(); + break; + } + + // New data received + onNewData(); + + //Wait for buffer to be filled + try { + Thread.sleep(1L); + } catch (Throwable ex) { + /**NON BLOCK**/ + } + } + + //Asynchronous programs can cause a lot of output, control buffers + //for a low memory footprint + if (shell.mActiveCommand != null && + shell.mActiveCommand instanceof AsyncResultProgram) { + trimBuffer(shell.mSbIn); + trimBuffer(shell.mSbErr); + } + } + } catch (Exception ioEx) { + notifyProcessExit(ioEx); + } + } + }); + t.setName(String.format("%s", "stderr")); //$NON-NLS-1$//$NON-NLS-2$ + t.start(); + return t; + } + + /** + * Method that echoes the stderr + * + * @param stdin The buffer of the stderr + * @hide + */ + void toStdErr(String stderr) { + //Audit (if not cancelled) + if (!this.mCancelled && isTrace()) { + Log.v(TAG, + String.format( + "stderr: %s", stderr)); //$NON-NLS-1$ + } + } + + /** + * Method that checks the console status and restart the console + * if this is unusable. + * + * @throws ConsoleAllocException If the console can't be reallocated + */ + private void checkConsole() throws ConsoleAllocException { + try { + //Test write something to the buffer + this.mOut.write(FileHelper.NEWLINE.getBytes()); + this.mOut.write(FileHelper.NEWLINE.getBytes()); + } catch (IOException ioex) { + //Something is wrong with the buffers. Reallocate console. + Log.w(TAG, + "Something is wrong with the console buffers. Reallocate console.", //$NON-NLS-1$ + ioex); + + //Reallocate the damage console + realloc(); + } + } + + /** + * Method that verifies if the process had exited. + * @hide + */ + void checkIfProcessExits() { + try { + if (this.mProc != null) { + synchronized (this.mSync) { + this.mProc.exitValue(); + } + this.mActive = false; //Exited + } + } catch (IllegalThreadStateException itsEx) { + //Not exited + } + } + + /** + * Method that notifies the ending of the process. + * + * @param ex The exception, only if the process exit with a exception. + * Otherwise null + * @hide + */ + void notifyProcessExit(Exception ex) { + synchronized (this.mSync) { + if (this.mActive) { + this.mActive = false; + this.mFinished = true; + this.mSync.notify(); + if (ex != null) { + Log.w(TAG, "Exit with exception", ex); //$NON-NLS-1$ + } + } + } + } + + /** + * Method that notifies the ending of the command execution. + * @hide + */ + void notifyProcessFinished() { + synchronized (this.mSync) { + if (this.mActive) { + this.mFinished = true; + this.mSync.notify(); + } + } + } + + /** + * Method that returns if the command has started by checking the + * standard input buffer. This method also removes the control start command + * from the buffer, if it's present, leaving in the buffer the new data bytes. + * + * @param stdin The standard in buffer + * @return boolean If the command has started + * @hide + */ + boolean isCommandStarted(StringBuffer stdin) { + if (stdin == null) return false; + Pattern pattern = Pattern.compile(this.mStartControlPattern); + Matcher matcher = pattern.matcher(stdin.toString()); + if (matcher.find()) { + stdin.replace(0, matcher.end(), ""); //$NON-NLS-1$ + return true; + } + return false; + } + + /** + * Method that returns if the command has finished by checking the + * standard input buffer. + * + * @param stdin The standard in buffer + * @return boolean If the command has finished + * @hide + */ + boolean isCommandFinished(StringBuffer stdin, StringBuffer partial) { + Pattern pattern = Pattern.compile(this.mEndControlPattern); + if (stdin == null) return false; + Matcher matcher = pattern.matcher(stdin.toString()); + boolean ret = matcher.find(); + // Remove partial + if (ret && partial != null) { + matcher = pattern.matcher(partial.toString()); + if (matcher.find()) { + partial.replace(matcher.start(), matcher.end(), ""); //$NON-NLS-1$ + } + } + return ret; + } + + /** + * New data was received + * @hide + */ + void onNewData() { + synchronized (this.mSync) { + this.mNewData = true; + } + } + + /** + * Method that returns the exit code of the last executed command. + * + * @param stdin The standard in buffer + * @return int The exit code of the last executed command + */ + private int getExitCode(StringBuffer stdin) { + // If process was cancelled, don't expect a exit code. + // Returns always 143 code + if (this.mCancelled) { + return 143; + } + + // Parse the stdin seeking exit code pattern + String txt = stdin.toString(); + Pattern pattern = Pattern.compile(this.mEndControlPattern); + Matcher matcher = pattern.matcher(txt); + if (matcher.find()) { + this.mSbIn = new StringBuffer(txt.substring(0, matcher.start())); + String exitTxt = matcher.group(); + return Integer.parseInt( + exitTxt.substring( + exitTxt.indexOf("#/") + 2, //$NON-NLS-1$ + exitTxt.indexOf("/#", 2))); //$NON-NLS-1$ + } + return 255; + } + + /** + * Method that trim a buffer, let in the buffer some + * text to ensure that the exit code is in there. + * + * @param sb The buffer to trim + * @hide + */ + @SuppressWarnings("static-method") void trimBuffer(StringBuffer sb) { + final int bufferSize = 200; + if (sb.length() > bufferSize) { + sb.delete(0, sb.length() - bufferSize); + } + } + + /** + * Method that kill the current command. + * + * @return boolean If the program was killed + * @hide + */ + private boolean killCurrentCommand() { + synchronized (this.mSync) { + // Check background console + try { + FileManagerApplication.getBackgroundConsole(); + } catch (Exception e) { + Log.w(TAG, "There is not background console. Not allowed.", e); //$NON-NLS-1$ + return false; + } + + if (this.mActiveCommand.getCommand() != null) { + try { + boolean isCancellable = true; + if (this.mActiveCommand instanceof AsyncResultProgram) { + final AsyncResultProgram asyncCmd = + (AsyncResultProgram)this.mActiveCommand; + isCancellable = asyncCmd.isCancellable(); + } + + if (isCancellable) { + try { + //Get the PIDs in background + List pids = + CommandHelper.getProcessesIds( + null, + this.mShell.getPid(), + FileManagerApplication.getBackgroundConsole()); + for (Integer pid: pids) { + if (pid != null) { + CommandHelper.sendSignal( + null, + pid.intValue(), + FileManagerApplication.getBackgroundConsole()); + try { + //Wait for process to be killed + Thread.sleep(100L); + } catch (Throwable ex) { + /**NON BLOCK**/ + } + } + } + return true; + } finally { + // It's finished + this.mCancelled = true; + notifyProcessFinished(); + this.mSync.notify(); + } + } + } catch (Throwable ex) { + Log.w(TAG, + String.format("Unable to kill current program: %s", //$NON-NLS-1$ + this.mActiveCommand.getCommand()), ex); + } + } + } + return false; + } + + /** + * Method that send a signal to the current command. + * + * @param SIGNAL The signal to send + * @return boolean If the signal was sent + * @hide + */ + private boolean sendSignalToCurrentCommand(SIGNAL signal) { + synchronized (this.mSync) { + // Check background console + try { + FileManagerApplication.getBackgroundConsole(); + } catch (Exception e) { + Log.w(TAG, "There is not background console. Not allowed.", e); //$NON-NLS-1$ + return false; + } + + if (this.mActiveCommand.getCommand() != null) { + try { + boolean isCancellable = true; + if (this.mActiveCommand instanceof AsyncResultProgram) { + final AsyncResultProgram asyncCmd = + (AsyncResultProgram)this.mActiveCommand; + isCancellable = asyncCmd.isCancellable(); + } + + if (isCancellable) { + try { + //Get the PIDs in background + List pids = + CommandHelper.getProcessesIds( + null, + this.mShell.getPid(), + FileManagerApplication.getBackgroundConsole()); + for (Integer pid: pids) { + if (pid != null) { + CommandHelper.sendSignal( + null, + pid.intValue(), + signal, + FileManagerApplication.getBackgroundConsole()); + try { + //Wait for process to be signaled + Thread.sleep(100L); + } catch (Throwable ex) { + /**NON BLOCK**/ + } + } + } + return true; + } finally { + // It's finished + this.mCancelled = true; + notifyProcessFinished(); + this.mSync.notify(); + } + } + } catch (Throwable ex) { + Log.w(TAG, + String.format("Unable to send signal to current program: %s", //$NON-NLS-1$ + this.mActiveCommand.getCommand()), ex); + } + } + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onEnd() { + //Kill the current command on end request + return killCurrentCommand(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onSendSignal(SIGNAL signal) { + //Send a signal to the current command on end request + return sendSignalToCurrentCommand(signal); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onCancel() { + //Kill the current command on cancel request + return killCurrentCommand(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onRequestWrite( + byte[] data, int offset, int byteCount) throws ExecutionException { + try { + // Method that write to the stdin the data requested by the program + if (this.mOut != null) { + this.mOut.write(data, offset, byteCount); + this.mOut.flush(); + Thread.yield(); + return true; + } + } catch (Exception ex) { + String msg = String.format("Unable to write data to program: %s", //$NON-NLS-1$ + this.mActiveCommand.getCommand()); + Log.e(TAG, msg, ex); + throw new ExecutionException(msg, ex); + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public OutputStream getOutputStream() { + return this.mOut; + } + +} diff --git a/Backbone/src/main/java/me/toolify/backbone/dashclock/DashExtension.java b/Backbone/src/main/java/me/toolify/backbone/dashclock/DashExtension.java new file mode 100644 index 000000000..820d33d25 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/dashclock/DashExtension.java @@ -0,0 +1,131 @@ +package me.toolify.backbone.dashclock; + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.util.Log; + +import com.google.android.apps.dashclock.api.DashClockExtension; +import com.google.android.apps.dashclock.api.ExtensionData; + +import java.util.Locale; + +import me.toolify.backbone.R; +import me.toolify.backbone.activities.NavigationActivity; +import me.toolify.backbone.model.DiskUsage; +import me.toolify.backbone.util.CommandHelper; +import me.toolify.backbone.util.FileHelper; +import me.toolify.backbone.util.StorageHelper; + +/** + * Created by Brandon on 7/26/13. + */ +public class DashExtension extends DashClockExtension { + private final static String TAG = "DashExtension"; + + public static int getIcon(String volumeDescription) + { + String desc = volumeDescription.toLowerCase(Locale.US); + int ret = R.drawable.dashclock_database; + if(desc.indexOf("ext") > -1 || (desc.indexOf("sd") > -1 && desc.indexOf("internal") == -1)) + ret = R.drawable.dashclock_sd; + else if(desc.indexOf("usb") > -1) + ret = R.drawable.dashclock_usb; + return ret; + } + + private static SharedPreferences getPreferences(Context context) + { + return context.getSharedPreferences("dashclock", MODE_PRIVATE); + } + + public static boolean isEnabled(Context context) + { + return getPreferences(context).getBoolean("enabled", false); + } + + private void setEnabled(boolean enabled) + { + SharedPreferences prefs = getPreferences(this); + if(prefs.getBoolean("enabled", !enabled) == enabled) return; + prefs.edit().putBoolean("enabled", enabled).apply(); + } + + @Override + protected void onInitialize(boolean isReconnect) { + super.onInitialize(isReconnect); + setEnabled(true); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + setEnabled(true); + return super.onStartCommand(intent, flags, startId); + } + + @Override + public boolean onUnbind(Intent intent) { + setEnabled(false); + return super.onUnbind(intent); + } + + @Override + public void onDestroy() { + setEnabled(false); + super.onDestroy(); + } + + @Override + protected void onUpdateData(int reason) { + final SharedPreferences prefs = getPreferences(this); + final boolean showFree = prefs.getBoolean("usage", true); + int icon = 0; + + Intent intent = new Intent(this, NavigationActivity.class); + try { + StringBuilder sbBody = new StringBuilder(); + long free = 0, total = 0; + Object lastVolume = null; + for(Object storage : StorageHelper.getStorageVolumes(this)) + { + String desc = StorageHelper.getStorageVolumeDescription(this, storage); + String path = StorageHelper.getStoragePath(storage); + boolean useMe = StorageHelper.isValidMount(path); + if(!prefs.getBoolean("storage_" + path, useMe)) continue; + lastVolume = storage; + DiskUsage du = CommandHelper.getDiskUsage(this, path, null); + long usage = showFree ? du.getFree() : du.getUsed(); + free += usage; + total += du.getTotal(); + sbBody.append(desc + ": " + + FileHelper.getHumanReadableSize(usage) + "/" + + FileHelper.getHumanReadableSize(du.getTotal()) + "\n"); + } + if(sbBody.length() == 0) { + publishUpdate(new ExtensionData().visible(false)); + return; + } + if(lastVolume != null) + { + String path = StorageHelper.getStoragePath(lastVolume); + icon = getIcon(StorageHelper.getStorageVolumeDescription(this, lastVolume)); + intent.putExtra(NavigationActivity.EXTRA_NAVIGATE_TO, FileHelper.getAbsPath(path)); + } + sbBody.setLength(sbBody.length() - 1); + publishUpdate(new ExtensionData() + .icon(icon) + .visible(true) + .status(FileHelper.getHumanReadableSize(free)) + .expandedTitle(getString(showFree ? + R.string.filesystem_info_dialog_free_disk_usage : + R.string.filesystem_info_dialog_used_disk_usage) + + " " + FileHelper.getHumanReadableSize(free) + + "/" + FileHelper.getHumanReadableSize(total)) + .expandedBody(sbBody.toString()) + .clickIntent(intent) + ); + } catch(Exception e) { + Log.e(TAG, "Unable to get disk usage", e); + } + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/dashclock/DashSettings.java b/Backbone/src/main/java/me/toolify/backbone/dashclock/DashSettings.java new file mode 100644 index 000000000..5f4e6e616 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/dashclock/DashSettings.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2013 BrandroidTools + * + * 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 me.toolify.backbone.dashclock; + +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.PreferenceCategory; +import android.view.MenuItem; + +import me.toolify.backbone.R; +import me.toolify.backbone.model.MountPoint; +import me.toolify.backbone.util.MountPointHelper; +import me.toolify.backbone.util.StorageHelper; + +public class DashSettings extends PreferenceActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getActionBar().setDisplayHomeAsUpEnabled(true); + /* This lets the activity default to a single color white icon when + opened from Dashclock, therefore adhering to Dashclock extension standards */ + if (getIntent().getBooleanExtra("showLauncherIcon", false)) { + getActionBar().setIcon(R.drawable.ic_launcher); + } + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + setupPreferences(); + } + + private String getUsageType(boolean free) + { + String ret = getString(free ? + R.string.filesystem_info_dialog_free_disk_usage : + R.string.filesystem_info_dialog_used_disk_usage); + ret = ret.substring(0, ret.length() - 1); // remove colon + return ret; + } + + private void setupPreferences() + { + getPreferenceManager().setSharedPreferencesName("dashclock"); + addPreferencesFromResource(R.xml.preferences_dashclock); + final SharedPreferences prefs = getSharedPreferences("dashclock", MODE_PRIVATE); + //final PreferenceScreen mIcon = (PreferenceScreen)findPreference("pref_icon"); + final Preference mUsage = findPreference("pref_usage"); + //mIcon.setIcon(prefs.getInt("icon", R.drawable.dashclock_sd)); + boolean showFree = prefs.getBoolean("usage", true); + mUsage.setSummary(getUsageType(showFree)); + mUsage.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + boolean free = !prefs.getBoolean("usage", true); + prefs.edit().putBoolean("usage", free).apply(); + mUsage.setSummary(getUsageType(free)); + return false; + } + }); + PreferenceCategory mStores = (PreferenceCategory)findPreference("pref_storage"); + if(mStores == null) + return; + for(Object storage : StorageHelper.getStorageVolumes(this)) + { + final String path = StorageHelper.getStoragePath(storage); + String desc = StorageHelper.getStorageVolumeDescription(this, storage); + final CheckBoxPreference p = new CheckBoxPreference(this); + int storeIcon = DashExtension.getIcon(desc); + if(desc.equals(getString(R.string.external_storage))|| + desc.equals(getString(R.string.internal_storage))) + storeIcon = R.drawable.dashclock_sd; + p.setIcon(storeIcon); + boolean useMe = true; + MountPoint mp = MountPointHelper.getMountPointFromDirectory(path); + if(mp.getOptions().indexOf("mode=0")>-1) useMe = false; // Cyanogenmod USB workaround + if(!MountPointHelper.isReadWrite(mp)) useMe = false; + if(desc.equals(getString(R.string.bookmarks_system_folder))) + desc = path; + p.setTitle(desc); + p.setChecked(prefs.getBoolean("storage_" + path, useMe)); + p.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + prefs.edit() + .putBoolean("storage_" + path, p.isChecked()) + .apply(); + return false; + } + }); + mStores.addPreference(p); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if(item.getItemId() == android.R.id.home) + { + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/fragments/HistoryFragment.java b/Backbone/src/main/java/me/toolify/backbone/fragments/HistoryFragment.java new file mode 100644 index 000000000..fb9d625a7 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/fragments/HistoryFragment.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * Copyright (C) 2013 BrandroidTools + * + * 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 me.toolify.backbone.fragments; + +import android.app.Activity; +import android.app.Fragment; +import android.os.AsyncTask; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ListView; +import me.toolify.backbone.FileManagerApplication; +import me.toolify.backbone.R; +import me.toolify.backbone.activities.HistoryActivity; +import me.toolify.backbone.adapters.HistoryAdapter; +import me.toolify.backbone.model.History; +import me.toolify.backbone.preferences.AccessMode; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.util.ExceptionUtil; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class HistoryFragment extends Fragment implements OnItemClickListener { + + private static final String TAG = "HistoryFragment"; //$NON-NLS-1$ + + private static boolean DEBUG = false; + + /** + * @hide + */ + ListView mListView; + /** + * @hide + */ + HistoryAdapter mAdapter; + /** + * @hide + */ + private Activity mActivity; + + private boolean mChRooted; + + boolean mIsEmpty; + public boolean mIsClearHistory; + + /** + * Intent extra parameter for the history data. + */ + public static final String EXTRA_HISTORY_LIST = "extra_history_list"; //$NON-NLS-1$ + + /** + * {@inheritDoc} + */ + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.history_fragment, container, false); + } + + /** + * {@inheritDoc} + */ + @Override + public void onActivityCreated(Bundle state) { + if (DEBUG) { + Log.d(TAG, "HistoryFragment.onActivityCreated"); //$NON-NLS-1$ + } + + mActivity = getActivity(); + + + this.mIsEmpty = false; + this.mIsClearHistory = false; + + // Is ChRooted? + this.mChRooted = FileManagerApplication.getAccessMode().compareTo(AccessMode.SAFE) == 0; + + //Initialize action bars and data + initHistory(); + + // Apply the theme + applyTheme(); + + //Save state + super.onCreate(state); + } + + /** + * Method that initializes the history listview of the activity. + */ + @SuppressWarnings("unchecked") + private void initHistory() { + // Retrieve the loading view + final View waiting = mActivity.findViewById(R.id.history_waiting); + + this.mListView = (ListView)mActivity.findViewById(R.id.history_listview); + + // Load the history in background + AsyncTask task = new AsyncTask() { + Exception mCause; + List mHistory; + + @Override + protected Boolean doInBackground(Void... params) { + try { + this.mHistory = + (List)mActivity.getIntent().getSerializableExtra(EXTRA_HISTORY_LIST); + if (this.mHistory.isEmpty()) { + View msg = mActivity.findViewById(R.id.history_empty_msg); + msg.setVisibility(View.VISIBLE); + return Boolean.TRUE; + } + HistoryFragment.this.mIsEmpty = this.mHistory.isEmpty(); + + //Show inverted history + final List adapterList = new ArrayList(this.mHistory); + Collections.reverse(adapterList); + HistoryFragment.this.mAdapter = + new HistoryAdapter(mActivity, adapterList); + + return Boolean.TRUE; + + } catch (Exception e) { + this.mCause = e; + return Boolean.FALSE; + } + } + + @Override + protected void onPreExecute() { + waiting.setVisibility(View.VISIBLE); + } + + @Override + protected void onPostExecute(Boolean result) { + waiting.setVisibility(View.GONE); + if (result.booleanValue()) { + if (HistoryFragment.this.mListView != null && + HistoryFragment.this.mAdapter != null) { + + HistoryFragment.this.mListView. + setAdapter(HistoryFragment.this.mAdapter); + HistoryFragment.this.mListView. + setOnItemClickListener(HistoryFragment.this); + } + + } else { + if (this.mCause != null) { + ExceptionUtil.translateException(mActivity, this.mCause); + } + } + } + + @Override + protected void onCancelled() { + waiting.setVisibility(View.GONE); + } + }; + task.execute(); + } + + /** + * {@inheritDoc} + */ + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + History history = ((HistoryAdapter)parent.getAdapter()).getItem(position); + back(false, history); + } + + /** + * Method that clean the history and return back to navigation view + * @hide + */ + public void clearHistory() { + if (this.mAdapter != null) { + this.mAdapter.clear(); + this.mAdapter.notifyDataSetChanged(); + View msg = mActivity.findViewById(R.id.history_empty_msg); + msg.setVisibility(View.VISIBLE); + this.mIsClearHistory = true; + } + } + + /** + * Method that returns to previous activity and. + * + * @param cancelled Indicates if the activity was cancelled + * @param history The selected history + */ + private void back(final boolean cancelled, final History history) { + if (mActivity instanceof HistoryActivity) { + ((HistoryActivity) mActivity).back(cancelled, history); + } + } + + /** + * Method that applies the current theme to the activity + * @hide + */ + void applyTheme() { + ThemeManager.Theme theme = ThemeManager.getCurrentTheme(mActivity); + theme.setBaseTheme(mActivity, false); + + if (this.mAdapter != null) { + this.mAdapter.notifyThemeChanged(); + this.mAdapter.notifyDataSetChanged(); + } + this.mListView.setDivider( + theme.getDrawable(mActivity, "horizontal_divider_drawable")); //$NON-NLS-1$ + this.mListView.invalidate(); + } + +} diff --git a/Backbone/src/main/java/me/toolify/backbone/fragments/NavigationFragment.java b/Backbone/src/main/java/me/toolify/backbone/fragments/NavigationFragment.java new file mode 100644 index 000000000..8e095a4b8 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/fragments/NavigationFragment.java @@ -0,0 +1,1775 @@ +/* + * Copyright (C) 2013 BrandroidTools + * + * 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 me.toolify.backbone.fragments; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import android.app.Fragment; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.TypedArray; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Handler; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.widget.AbsListView; +import android.widget.AdapterView; +import android.widget.LinearLayout; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.Toast; + +import me.toolify.backbone.FileManagerApplication; +import me.toolify.backbone.R; +import me.toolify.backbone.activities.AbstractNavigationActivity; +import me.toolify.backbone.activities.NavigationActivity; +import me.toolify.backbone.adapters.FileSystemObjectAdapter; +import me.toolify.backbone.adapters.FileSystemObjectAdapter.OnSelectionChangedListener; +import me.toolify.backbone.console.ConsoleAllocException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.listeners.OnHistoryListener; +import me.toolify.backbone.listeners.OnRequestRefreshListener; +import me.toolify.backbone.listeners.OnSelectionListener; +import me.toolify.backbone.model.*; +import me.toolify.backbone.parcelables.HistoryNavigable; +import me.toolify.backbone.parcelables.NavigationViewInfoParcelable; +import me.toolify.backbone.parcelables.SearchInfoParcelable; +import me.toolify.backbone.preferences.*; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.actionmode.SelectionModeCallback; +import me.toolify.backbone.ui.image.ImageCache.ImageCacheParams; +import me.toolify.backbone.ui.image.ImageFetcher; +import me.toolify.backbone.ui.policy.*; +import me.toolify.backbone.ui.widgets.*; +import me.toolify.backbone.ui.widgets.FlingerListView.OnItemFlingerListener; +import me.toolify.backbone.ui.widgets.FlingerListView.OnItemFlingerResponder; +import me.toolify.backbone.util.*; + +public class NavigationFragment extends Fragment implements + AdapterView.OnItemClickListener, AdapterView.OnItemLongClickListener, + OnHistoryListener, OnSelectionChangedListener, OnSelectionListener, OnRequestRefreshListener { + + private static final String TAG = "NavigationFragment"; //$NON-NLS-1$ + private static final String IMAGE_CACHE_DIR = "thumbs"; + + /** + * An interface to communicate a request for show the menu associated + * with an item. + */ + public interface OnNavigationRequestMenuListener { + /** + * Method invoked when a request to show the menu associated + * with an item is started. + * + * @param navFragment The navigation fragment that generates the event + * @param item The item for which the request was started + */ + void onRequestMenu(NavigationFragment navFragment, FileSystemObject item); + } + + /** + * An interface to communicate a request when the user choose a file. + */ + public interface OnFilePickedListener { + /** + * Method invoked when a request when the user choose a file. + * + * @param item The item choose + */ + void onFilePicked(FileSystemObject item); + } + + /** + * An interface to communicate a change of the current directory + */ + public interface OnDirectoryChangedListener { + /** + * Method invoked when the current directory changes + * + * @param item The newly active directory + */ + void onDirectoryChanged(FileSystemObject item); + } + + /** + * The navigation view mode + * @hide + */ + public enum NAVIGATION_MODE { + /** + * The navigation view acts as a browser, and allow open files itself. + */ + BROWSABLE, + /** + * The navigation view acts as a picker of files + */ + PICKABLE, + } + + /** + * A listener for flinging events from {@link FlingerListView} + */ + private final OnItemFlingerListener mOnItemFlingerListener = new OnItemFlingerListener() { + + @Override + public boolean onItemFlingerStart( + AdapterView parent, View view, int position, long id) { + try { + // Response if the item can be removed + FileSystemObjectAdapter adapter = (FileSystemObjectAdapter)parent.getAdapter(); + FileSystemObject fso = adapter.getItem(position); + if (fso != null) { + if (fso instanceof ParentDirectory) { + return false; + } + return true; + } + } catch (Exception e) { + ExceptionUtil.translateException(mActivity, e, true, false); + } + return false; + } + + @Override + public void onItemFlingerEnd(OnItemFlingerResponder responder, + AdapterView parent, View view, int position, long id) { + + try { + // Response if the item can be removed + FileSystemObjectAdapter adapter = (FileSystemObjectAdapter)parent.getAdapter(); + FileSystemObject fso = adapter.getItem(position); + if (fso != null) { + DeleteActionPolicy.removeFileSystemObject( + mActivity, + fso, + NavigationFragment.this, + NavigationFragment.this, + responder); + return; + } + + // Cancels the flinger operation + responder.cancel(); + + } catch (Exception e) { + ExceptionUtil.translateException(mActivity, e, true, false); + responder.cancel(); + } + } + }; + + private int mId; + private int mPosition; + private String mCurrentDir; + private NavigationLayoutMode mCurrentMode; + /** + * @hide + */ + List mFiles; + private FileSystemObjectAdapter mAdapter; + + public List mHistory; + + private int mImageThumbSize; + private int mImageThumbSpacing; + private ImageFetcher mImageFetcher; + + private boolean mChangingDir; + private final Object mSync = new Object(); + + private OnHistoryListener mOnHistoryListener; + private OnNavigationRequestMenuListener mOnNavigationRequestMenuListener; + private OnFilePickedListener mOnFilePickedListener; + private OnDirectoryChangedListener mOnDirectoryChangedListener; + + public boolean mChRooted; + + private NAVIGATION_MODE mNavigationMode; + + // Restrictions + private Map mRestrictions; + + /** + * @hide + */ + LinearLayout mNavigationViewHolder; + /** + * @hide + */ + Breadcrumb mBreadcrumb; + /** + * @hide + */ + NavigationCustomTitleView mTitle; + /** + * @hide + */ + AbsListView mAdapterView; + /** + * @hide + */ + private SelectionModeCallback mSelectionModeCallback; + + //The layout for icons mode + private static final int RESOURCE_MODE_ICONS_LAYOUT = R.layout.navigation_view_icons; + private static final int RESOURCE_MODE_ICONS_ITEM = R.layout.navigation_view_icons_item; + //The layout for simple mode + private static final int RESOURCE_MODE_SIMPLE_LAYOUT = R.layout.navigation_view_simple; + private static final int RESOURCE_MODE_SIMPLE_ITEM = R.layout.navigation_view_simple_item; + //The layout for details mode + private static final int RESOURCE_MODE_DETAILS_LAYOUT = R.layout.navigation_view_details; + private static final int RESOURCE_MODE_DETAILS_ITEM = R.layout.navigation_view_details_item; + + //The current layout identifier (is shared for all the mode layout) + private static final int RESOURCE_CURRENT_LAYOUT = R.id.navigation_view_layout; + + private AbstractNavigationActivity mActivity; + + /** + * @hide + */ + Handler mHandler; + + /** + * Create a new instance of FileListFragment, providing "num" as an + * argument. + */ + public static NavigationFragment newInstance(int num) { + NavigationFragment f = new NavigationFragment(); + + // Supply num input as an argument. + Bundle args = new Bundle(); + args.putInt("position", num); + f.setArguments(args); + return f; + } + + /** + * {@inheritDoc} + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mActivity = (AbstractNavigationActivity)getActivity(); + Bundle b = getArguments(); + if (b != null) { + mPosition = getArguments().getInt("position", 0); + } else { + mPosition = 0; + } + + mImageThumbSize = getResources().getDimensionPixelSize(R.dimen.navigation_row_height); + + //TODO: This should also be set every time the view mode (list, grid) is changed + ImageCacheParams cacheParams = new ImageCacheParams(getActivity(), IMAGE_CACHE_DIR); + + //TODO: Make a way for mImageFetchers to share the same % of cache + cacheParams.setMemCacheSizePercent(0.25f); // Set memory cache to 25% of app memory + + // The ImageFetcher takes care of loading images into our ImageView children asynchronously + mImageFetcher = new ImageFetcher(getActivity(), mImageThumbSize); + mImageFetcher.setLoadingImage(R.drawable.fso_type_image_drawable); + mImageFetcher.addImageCache(getActivity().getFragmentManager(), cacheParams); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.navigation_fragment, container, false); + } + + /** + * {@inheritDoc} + */ + @Override + public void onActivityCreated(Bundle state) { + super.onActivityCreated(state); + + //Navigation views + initNavigationViewContainer(); + + // Apply the theme + applyTheme(); + + this.mHandler = new Handler(); + this.mHandler.post(new Runnable() { + + @Override + public void run() { + //Initialize navigation + if (mActivity.isFirstRun() == true) { + initNavigation(false, getActivity().getIntent()); + mActivity.updateTitleActionBar(); + mActivity.setFirstRun(false); + } else { + initNavigation(false, null); + } + }; + }); + } + + /** + * {@inheritDoc} + */ + @Override + public void onResume() { + super.onResume(); + mImageFetcher.setExitTasksEarly(false); + mAdapter.notifyDataSetChanged(); + } + + /** + * {@inheritDoc} + */ + @Override + public void onPause() { + super.onPause(); + mImageFetcher.setPauseWork(false); + mImageFetcher.setExitTasksEarly(true); + mImageFetcher.flushCache(); + } + + /** + * {@inheritDoc} + */ + @Override + public void onDestroy() { + super.onDestroy(); + mImageFetcher.closeCache(); + } + + /** + * Invoked when the instance need to be saved. + * + * @return NavigationViewInfoParcelable The serialized info + */ + public NavigationViewInfoParcelable onSaveState() { + int top; + + //Return the persistent the data + NavigationViewInfoParcelable parcel = new NavigationViewInfoParcelable(); + parcel.setId(this.mId); + parcel.setCurrentDir(this.mCurrentDir); + parcel.setChRooted(this.mChRooted); + parcel.setSelectedFiles(this.mAdapter.getSelectedItems()); + parcel.setFiles(this.mFiles); + parcel.setScrollIndex(this.mAdapterView.getFirstVisiblePosition()); + if (this.mAdapterView instanceof ListView) { + View topView = this.mAdapterView.getChildAt(0); + top = (topView == null) ? 0 : topView.getTop(); + } else { + top = 0; + } + parcel.setScrollIndexOffset(top); + return parcel; + } + + /** + * Invoked when the instance need to be restored. + * + * @param info The serialized info + * @return boolean If can restore + */ + public boolean onRestoreState(NavigationViewInfoParcelable info) { + synchronized (mSync) { + if (mChangingDir) { + return false; + } + } + + //Restore the data + this.mId = info.getId(); + this.mCurrentDir = info.getCurrentDir(); + this.mChRooted = info.getChRooted(); + this.mFiles = info.getFiles(); + this.mAdapter.setSelectedItems(info.getSelectedFiles()); + + //Update the views + refresh(info.getScrollIndex(), info.getScrollIndexOffset()); + return true; + } + + /** + * Method that initializes the navigation views of the activity + */ + private void initNavigationViewContainer() { + // Since navigation_fragment.xml only contains a single LinearLayout, we can assume + // that getView() will safely return the appropriate view. + mNavigationViewHolder = (LinearLayout)getView(); + TypedArray a = mActivity.obtainStyledAttributes(R.styleable.Navigable); + try { + init(a); + } finally { + a.recycle(); + } + } + + /** + * Method that initializes the view. This method loads all the necessary + * information and create an appropriate layout for the view. + * + * @param tarray The type array + */ + private void init(TypedArray tarray) { + // Retrieve the mode + this.mNavigationMode = NAVIGATION_MODE.BROWSABLE; + int mode = tarray.getInteger( + R.styleable.Navigable_navigation, + NAVIGATION_MODE.BROWSABLE.ordinal()); + if (mode >= 0 && mode < NAVIGATION_MODE.values().length) { + this.mNavigationMode = NAVIGATION_MODE.values()[mode]; + } + + // Initialize default restrictions (no restrictions) + this.mRestrictions = new HashMap(); + + //Initialize variables + this.mFiles = new ArrayList(); + + + // Associate the related breadcrumb + mActivity.pairBreadcrumb(mPosition, this); + + // Is ChRooted environment? + if (this.mNavigationMode.compareTo(NAVIGATION_MODE.PICKABLE) == 0) { + // Pick mode is always ChRooted + this.mChRooted = true; + } else { + this.mChRooted = + FileManagerApplication.getAccessMode().compareTo(AccessMode.SAFE) == 0; + } + + //Retrieve the default configuration + if (this.mNavigationMode.compareTo(NAVIGATION_MODE.BROWSABLE) == 0) { + SharedPreferences preferences = Preferences.getSharedPreferences(); + int viewMode = preferences.getInt( + FileManagerSettings.SETTINGS_LAYOUT_MODE.getId(), + ((ObjectIdentifier)FileManagerSettings. + SETTINGS_LAYOUT_MODE.getDefaultValue()).getId()); + changeViewMode(NavigationLayoutMode.fromId(viewMode)); + } else { + // Pick mode has always a details layout + changeViewMode(NavigationLayoutMode.DETAILS); + } + + mAdapterView.setOnScrollListener(new AbsListView.OnScrollListener() { + @Override + public void onScrollStateChanged(AbsListView absListView, int scrollState) { + // Pause fetcher to ensure smoother scrolling when flinging + if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) { + mImageFetcher.setPauseWork(true); + } else { + mImageFetcher.setPauseWork(false); + } + } + + @Override + public void onScroll(AbsListView absListView, int firstVisibleItem, + int visibleItemCount, int totalItemCount) { + } + }); + + } + + /** + * Method that initializes the navigation. + * + * @param restore Initialize from a restore info + * @param intent The current intent + * @hide + */ + public void initNavigation(final boolean restore, final Intent intent) { + this.mHistory = new ArrayList(); + this.mHandler.post(new Runnable() { + @Override + public void run() { + //Is necessary navigate? + if (!restore) { + applyInitialDir(intent); + } + } + }); + } + + /** + * Method that applies the user-defined initial directory + * + * @param intent The current intent + * @hide + */ + void applyInitialDir(final Intent intent) { + //Load the user-defined initial directory + String initialDir = + Preferences.getSharedPreferences().getString( + FileManagerSettings.SETTINGS_INITIAL_DIR.getId(), + (String)FileManagerSettings. + SETTINGS_INITIAL_DIR.getDefaultValue()); + + // Override the user-defined initial directory if an intent with an EXTRA_NAVIGATE_TO from + // a ShortcutActivity or an ACTION_PICK from a PickerActivity is present. Ensure proper + // hanlding of chroot and absolute path. + if (intent != null) { + // EXTRA_NAVIGATE_TO from ShortcutActivity + String navigateTo = intent.getStringExtra(NavigationActivity.EXTRA_NAVIGATE_TO); + if (navigateTo != null && navigateTo.length() > 0) { + initialDir = navigateTo; + } + // ACTION_PICK passed to PickerActivity from an external file select intent + if (Intent.ACTION_PICK.equals(intent.getAction())) { + + final Uri data = intent.getData(); + if (data != null) { + final String path = data.getPath(); + if(path != null) { + final File file = new File(path); + if (file.exists() || file.isAbsolute()) { + if (file.isDirectory()) { + initialDir = file.getAbsolutePath(); + } else { + initialDir = file.getParentFile().getAbsolutePath(); + } + } + } + } + } + } + + if (this.mChRooted) { + // Initial directory is the first external sdcard (sdcard, emmc, usb, ...) + if (!StorageHelper.isPathInStorageVolume(initialDir)) { + Object[] volumes = + StorageHelper.getStorageVolumes(mActivity); + if (volumes != null && volumes.length > 0) { + initialDir = StorageHelper.getStoragePath(volumes[0]); + //Ensure that initial directory is an absolute directory + initialDir = FileHelper.getAbsPath(initialDir); + } else { + // Show exception and exit + DialogHelper.showToast( + mActivity, + R.string.msgs_cant_create_console, Toast.LENGTH_LONG); + mActivity.exit(); + return; + } + } + } else { + //Ensure that initial directory is an absolute directory + final String userInitialDir = initialDir; + initialDir = FileHelper.getAbsPath(initialDir); + final String absInitialDir = initialDir; + File f = new File(initialDir); + boolean exists = f.exists(); + if (!exists) { + // Fix for /data/media/0. Libcore doesn't detect it correctly. + try { + exists = CommandHelper.getFileInfo(mActivity, initialDir, false, null) != null; + } catch (InsufficientPermissionsException ipex) { + ExceptionUtil.translateException( + mActivity, ipex, false, true, new ExceptionUtil.OnRelaunchCommandResult() { + @Override + public void onSuccess() { + changeCurrentDir(absInitialDir); + } + @Override + public void onFailed(Throwable cause) { + showInitialInvalidDirectoryMsg(userInitialDir); + changeCurrentDir(FileHelper.ROOT_DIRECTORY); + } + @Override + public void onCancelled() { + showInitialInvalidDirectoryMsg(userInitialDir); + changeCurrentDir(FileHelper.ROOT_DIRECTORY); + } + }); + + // Asynchronous mode + return; + } catch (Exception ex) { + // We are not interested in other exceptions + ExceptionUtil.translateException(mActivity, ex, true, false); + } + + // Check again the initial directory + if (!exists) { + showInitialInvalidDirectoryMsg(userInitialDir); + initialDir = FileHelper.ROOT_DIRECTORY; + } + + // Weird, but we have a valid initial directory + } + } + + // Change the current directory to the user-defined initial directory + changeCurrentDir(initialDir); + } + + /** + * Displays a message reporting invalid directory + * + * @param initialDir The initial directory + * @hide + */ + void showInitialInvalidDirectoryMsg(String initialDir) { + // Change to root directory + DialogHelper.showToast( + mActivity, + getString( + R.string.msgs_settings_invalid_initial_directory, + initialDir), + Toast.LENGTH_SHORT); + } + + /** + * {@inheritDoc} + */ + @Override + public void onNewHistory(HistoryNavigable navigable) { + //Recollect information about current status + History history = new History(this.mHistory.size(), navigable); + this.mHistory.add(history); + } + + /** + * {@inheritDoc} + */ + @Override + public void onCheckHistory() { + //Need to show HomeUp Button + boolean enabled = this.mHistory != null && this.mHistory.size() > 0; +// mActivity.getActionBar().setDisplayHomeAsUpEnabled(enabled); +// mActivity.getActionBar().setHomeButtonEnabled(enabled); + } + + /** + * Method that remove the {@link me.toolify.backbone.model.FileSystemObject} from the history + */ + public void removeFromHistory(FileSystemObject fso) { + if (this.mHistory != null) { + int cc = this.mHistory.size(); + for (int i = cc-1; i >= 0 ; i--) { + History history = this.mHistory.get(i); + if (history.getItem() instanceof NavigationViewInfoParcelable) { + String p0 = fso.getFullPath(); + String p1 = + ((NavigationViewInfoParcelable)history.getItem()).getCurrentDir(); + if (p0.compareTo(p1) == 0) { + this.mHistory.remove(i); + } + } + } + } + } + + /** + * Method that returns the display restrictions to apply to this view. + * + * @return Map The restrictions to apply + */ + public Map getRestrictions() { + return this.mRestrictions; + } + + /** + * Method that sets the display restrictions to apply to this view. + * + * @param mRestrictions The restrictions to apply + */ + public void setRestrictions(Map mRestrictions) { + this.mRestrictions = mRestrictions; + } + + /** + * Method that returns the current file list of the navigation view. + * + * @return List The current file list of the navigation view + */ + public List getFiles() { + if (this.mFiles == null) { + return null; + } + return new ArrayList(this.mFiles); + } + + /** + * Method that returns the current file list of the navigation fragment. + * + * @return List The current file list of the navigation view + */ + public List getSelectedFiles() { + if (this.mAdapter != null && this.mAdapter.getSelectedItems() != null) { + return new ArrayList(this.mAdapter.getSelectedItems()); + } + return null; + } + + /** + * Method that returns the custom title view associated with this navigation view. + * + * @return NavigationCustomTitleView The custom title view fragment + */ + public NavigationCustomTitleView getCustomTitle() { + return this.mTitle; + } + + /** + * Method that associates the custom title fragment with this navigation view. + * + * @param title The custom title view fragment + */ + public void setCustomTitle(NavigationCustomTitleView title) { + this.mTitle = title; + } + + /** + * Method that returns the breadcrumb associated with this navigation view. + * + * @return Breadcrumb The breadcrumb view fragment + */ + public Breadcrumb getBreadcrumb() { + return this.mBreadcrumb; + } + + /** + * Method that associates the breadcrumb with this navigation view. + * + * @param breadcrumb The breadcrumb view fragment + */ + public void setBreadcrumb(Breadcrumb breadcrumb) { + this.mBreadcrumb = breadcrumb; + this.mBreadcrumb.setNavigationFragment(this); + this.mBreadcrumb.addBreadcrumbListener(mActivity); + } + + /** + * Method that sets the listener for communicate history changes. + * + * @param onHistoryListener The listener for communicate history changes + */ + public void setOnHistoryListener(OnHistoryListener onHistoryListener) { + this.mOnHistoryListener = onHistoryListener; + } + + /** + * Method that sets the listener for menu item requests. + * + * @param onNavigationRequestMenuListener The listener reference + */ + public void setOnNavigationOnRequestMenuListener( + OnNavigationRequestMenuListener onNavigationRequestMenuListener) { + this.mOnNavigationRequestMenuListener = onNavigationRequestMenuListener; + } + + /** + * @return the mOnFilePickedListener + */ + public OnFilePickedListener getOnFilePickedListener() { + return this.mOnFilePickedListener; + } + + /** + * Method that sets the listener for picked items + * + * @param onFilePickedListener The listener reference + */ + public void setOnFilePickedListener(OnFilePickedListener onFilePickedListener) { + this.mOnFilePickedListener = onFilePickedListener; + } + + /** + * Method that sets the listener for directory changes + * + * @param onDirectoryChangedListener The listener reference + */ + public void setOnDirectoryChangedListener( + OnDirectoryChangedListener onDirectoryChangedListener) { + this.mOnDirectoryChangedListener = onDirectoryChangedListener; + } + + /** + * Method that sets if the view should use flinger gesture detection. + * + * @param useFlinger If the view should use flinger gesture detection + */ + public void setUseFlinger(boolean useFlinger) { + if (this.mCurrentMode.compareTo(NavigationLayoutMode.ICONS) == 0) { + // Not supported + return; + } + // Set the flinger listener (only when navigate) + if (this.mNavigationMode.compareTo(NAVIGATION_MODE.BROWSABLE) == 0) { + if (this.mAdapterView instanceof FlingerListView) { + if (useFlinger) { + ((FlingerListView)this.mAdapterView). + setOnItemFlingerListener(this.mOnItemFlingerListener); + } else { + ((FlingerListView)this.mAdapterView).setOnItemFlingerListener(null); + } + } + } + } + + /** + * Method that forces the view to scroll to the file system object passed. + * + * @param fso The file system object + */ + public void scrollTo(FileSystemObject fso) { + if (fso != null) { + try { + int position = this.mAdapter.getPosition(fso); + this.mAdapterView.setSelection(position); + } catch (Exception e) { + this.mAdapterView.setSelection(0); + } + } else { + this.mAdapterView.setSelection(0); + } + } + + /** + * Method that refresh the view data. + */ + public void refresh() { + refresh(false); + } + + /** + * Method that refresh the view data. + * + * @param restore Restore previous position + */ + public void refresh(boolean restore) { + int scrollIndex = 0; + int scrollIndexOffset = 0; + // Try to restore the previous scroll position + if (restore) { + try { + if (this.mAdapterView != null && this.mAdapter != null) { + scrollIndex = this.mAdapterView.getFirstVisiblePosition(); + View topView = this.mAdapterView.getChildAt(0); + scrollIndexOffset = (topView == null) ? 0 : topView.getTop(); + } + } catch (Throwable _throw) {/**NON BLOCK**/} + } + refresh(scrollIndex, scrollIndexOffset); + } + + /** + * Method that refresh the view data. + * + * @param scrollIndex The index of the item at the top of the file list. + * @param scrollIndexOffset The exact scroll offset within the item at the top of the file list. + */ + public void refresh(Integer scrollIndex, Integer scrollIndexOffset) { + //Check that current directory was set + if (this.mCurrentDir == null || this.mFiles == null) { + return; + } + + //Reload data + changeCurrentDir(this.mCurrentDir, false, true, false, null, null, scrollIndex, scrollIndexOffset); + } + + /** + * Method that refresh the view data. + * + * @param scrollTo Scroll to object + */ + public void refresh(FileSystemObject scrollTo) { + //Check that current directory was set + if (this.mCurrentDir == null || this.mFiles == null) { + return; + } + + //Reload data + changeCurrentDir(this.mCurrentDir, false, true, false, null, scrollTo, null, null); + } + + /** + * Method that recycles this object + */ + public void recycle() { + if (this.mAdapter != null) { + this.mAdapter.dispose(); + } + } + + /** + * Method that change the view mode. + * + * @param newMode The new mode + */ + @SuppressWarnings("unchecked") + public void changeViewMode(final NavigationLayoutMode newMode) { + synchronized (this.mSync) { + //Check that it is really necessary change the mode + if (this.mCurrentMode != null && this.mCurrentMode.compareTo(newMode) == 0) { + return; + } + + // If we should set the listview to response to flinger gesture detection + boolean useFlinger = + Preferences.getSharedPreferences().getBoolean( + FileManagerSettings.SETTINGS_USE_FLINGER.getId(), + ((Boolean)FileManagerSettings. + SETTINGS_USE_FLINGER. + getDefaultValue()).booleanValue()); + + //Creates the new layout + AbsListView newView = null; + int itemResourceId = -1; + if (newMode.compareTo(NavigationLayoutMode.ICONS) == 0) { + newView = (AbsListView)mNavigationViewHolder.inflate( + mActivity, RESOURCE_MODE_ICONS_LAYOUT, null); + itemResourceId = RESOURCE_MODE_ICONS_ITEM; + + } else if (newMode.compareTo(NavigationLayoutMode.SIMPLE) == 0) { + newView = (AbsListView)mNavigationViewHolder.inflate( + mActivity, RESOURCE_MODE_SIMPLE_LAYOUT, null); + itemResourceId = RESOURCE_MODE_SIMPLE_ITEM; + + // Set the flinger listener (only when navigate) + if (this.mNavigationMode.compareTo(NAVIGATION_MODE.BROWSABLE) == 0) { + if (useFlinger && newView instanceof FlingerListView) { + ((FlingerListView)newView). + setOnItemFlingerListener(this.mOnItemFlingerListener); + } + } + + } else if (newMode.compareTo(NavigationLayoutMode.DETAILS) == 0) { + newView = (AbsListView)mNavigationViewHolder.inflate( + mActivity, RESOURCE_MODE_DETAILS_LAYOUT, null); + itemResourceId = RESOURCE_MODE_DETAILS_ITEM; + + // Set the flinger listener (only when navigate) + if (this.mNavigationMode.compareTo(NAVIGATION_MODE.BROWSABLE) == 0) { + if (useFlinger && newView instanceof FlingerListView) { + ((FlingerListView)newView). + setOnItemFlingerListener(this.mOnItemFlingerListener); + } + } + } + + //Get the current adapter and its adapter list + List files = new ArrayList(this.mFiles); + final AdapterView current = + (AdapterView)getView().findViewById(RESOURCE_CURRENT_LAYOUT); + FileSystemObjectAdapter adapter = + new FileSystemObjectAdapter( + mActivity, + new ArrayList(), + itemResourceId, + this.mNavigationMode.compareTo(NAVIGATION_MODE.PICKABLE) == 0, + mImageFetcher); + adapter.setOnSelectionChangedListener(this); + + //Remove current layout + if (current != null) { + if (current.getAdapter() != null) { + //Save selected items before dispose adapter + FileSystemObjectAdapter currentAdapter = + ((FileSystemObjectAdapter)current.getAdapter()); + adapter.setSelectedItems(currentAdapter.getSelectedItems()); + currentAdapter.dispose(); + } + mNavigationViewHolder.removeView(current); + } + this.mFiles = files; + adapter.addAll(files); + + //Set the adapter + this.mAdapter = adapter; + newView.setAdapter(this.mAdapter); + newView.setOnItemClickListener(NavigationFragment.this); + + //Add the new layout + this.mAdapterView = newView; + mNavigationViewHolder.addView(newView, 0); + this.mCurrentMode = newMode; + + // Pick mode doesn't implements the onlongclick + if (this.mNavigationMode.compareTo(NAVIGATION_MODE.BROWSABLE) == 0) { + this.mAdapterView.setOnItemLongClickListener(this); + } else { + this.mAdapterView.setOnItemLongClickListener(null); + } + + //Save the preference (only in navigation browse mode) + if (this.mNavigationMode.compareTo(NAVIGATION_MODE.BROWSABLE) == 0) { + try { + Preferences.savePreference( + FileManagerSettings.SETTINGS_LAYOUT_MODE, newMode, true); + } catch (Exception ex) { + Log.e(TAG, "Save of view mode preference fails", ex); //$NON-NLS-1$ + } + } + } + } + + /** + * Method that removes a {@link FileSystemObject} from the view + * + * @param fso The file system object + */ + public void removeItem(FileSystemObject fso) { + // Delete also from internal list + if (fso != null) { + int cc = this.mFiles.size()-1; + for (int i = cc; i >= 0; i--) { + FileSystemObject f = this.mFiles.get(i); + if (f != null && f.compareTo(fso) == 0) { + this.mFiles.remove(i); + break; + } + } + } + this.mAdapter.remove(fso); + } + + /** + * Method that removes a file system object from his path from the view + * + * @param path The file system object path + */ + public void removeItem(String path) { + FileSystemObject fso = this.mAdapter.getItem(path); + if (fso != null) { + this.mAdapter.remove(fso); + } + } + + /** + * Method that returns the current directory. + * + * @return String The current directory + */ + public String getCurrentDir() { + return this.mCurrentDir; + } + + /** + * Method that changes the current directory of the view. + * + * @param newDir The new directory location + */ + public void changeCurrentDir(final String newDir) { + changeCurrentDir(newDir, true, false, false, null, null, null, null); + } + + /** + * Method that changes the current directory of the view. + * + * @param newDir The new directory location + * @param searchInfo The search information (if calling activity is {@link "SearchActivity"}) + */ + public void changeCurrentDir(final String newDir, SearchInfoParcelable searchInfo) { + changeCurrentDir(newDir, true, false, false, searchInfo, null, null, null); + } + + /** + * Method that changes the current directory of the view. + * + * @param newDir The new directory location + * @param addToHistory Add the directory to history + * @param reload Force the reload of the data + * @param useCurrent If this method must use the actual data (for back actions) + * @param searchInfo The search information (if calling activity is {@link "SearchActivity"}) + * @param scrollTo The new FileSystemObject to scroll to, since it isn't an existing view yet + * @param scrollIndex If not null, then listview must scroll to this item + * @param scrollIndexOffset If not null, then listview must scroll to this offset within the item + */ + private void changeCurrentDir( + final String newDir, final boolean addToHistory, + final boolean reload, final boolean useCurrent, + final SearchInfoParcelable searchInfo, final FileSystemObject scrollTo, + final Integer scrollIndex, final Integer scrollIndexOffset) { + + // Check navigation security (don't allow to go outside the ChRooted environment if one + // is created) + final String fNewDir = checkChRootedNavigation(newDir); + + // Wait to finalization + synchronized (this.mSync) { + if (mChangingDir) { + try { + mSync.wait(); + } catch (InterruptedException iex) { + // Ignore + } + } + mChangingDir = true; + } + + //Check that it is really necessary change the directory + if (!reload && this.mCurrentDir != null && this.mCurrentDir.compareTo(fNewDir) == 0) { + return; + } + + final boolean hasChanged = + !(this.mCurrentDir != null && this.mCurrentDir.compareTo(fNewDir) == 0); + final boolean isNewHistory = (this.mCurrentDir != null); + + //Execute the listing in a background process + AsyncTask> task = + new AsyncTask>() { + /** + * {@inheritDoc} + */ + @Override + protected List doInBackground(String... params) { + try { + //Reset the custom title view and returns to breadcrumb + if (NavigationFragment.this.mTitle != null) { + NavigationFragment.this.mTitle.post(new Runnable() { + @Override + public void run() { + try { + NavigationFragment.this.mTitle.restoreView(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + + //Start of loading data + if (NavigationFragment.this.mBreadcrumb != null) { + try { + NavigationFragment.this.mBreadcrumb.startLoading(); + } catch (Throwable ex) { + /**NON BLOCK**/ + } + } + + //Get the files, resolve links and apply configuration + //(sort, hidden, ...) + List files = NavigationFragment.this.mFiles; + if (!useCurrent) { + files = CommandHelper.listFiles(mActivity, fNewDir, null); + } + return files; + + } catch (final ConsoleAllocException e) { + //Show exception and exists + mNavigationViewHolder.post(new Runnable() { + @Override + public void run() { + Log.e(TAG, mActivity.getString( + R.string.msgs_cant_create_console), e); + DialogHelper.showToast(mActivity, + R.string.msgs_cant_create_console, + Toast.LENGTH_LONG); + mActivity.finish(); + } + }); + return null; + + } catch (Exception ex) { + //End of loading data + if (NavigationFragment.this.mBreadcrumb != null) { + try { + NavigationFragment.this.mBreadcrumb.endLoading(); + } catch (Throwable ex2) { + /**NON BLOCK**/ + } + } + + //Capture exception (attach task, and use listener to do the anim) + ExceptionUtil.attachAsyncTask( + ex, + new AsyncTask() { + private List mTaskFiles = null; + @Override + @SuppressWarnings({ + "unchecked", "unqualified-field-access" + }) + protected Boolean doInBackground(Object... taskParams) { + mTaskFiles = (List)taskParams[0]; + return Boolean.TRUE; + } + + @Override + @SuppressWarnings("unqualified-field-access") + protected void onPostExecute(Boolean result) { + if (!result.booleanValue()){ + return; + } + onPostExecuteTask( + mTaskFiles, addToHistory, + isNewHistory, hasChanged, + searchInfo, fNewDir, scrollTo, + scrollIndex, scrollIndexOffset); + } + }); + final ExceptionUtil.OnRelaunchCommandResult exListener = + new ExceptionUtil.OnRelaunchCommandResult() { + @Override + public void onSuccess() { + // Do animation + fadeEfect(false); + } + @Override + public void onFailed(Throwable cause) { + // Do animation + fadeEfect(false); + } + @Override + public void onCancelled() { + // Do animation + fadeEfect(false); + } + private void done() { + // Do animation + fadeEfect(false); + synchronized (mSync) { + mChangingDir = false; + mSync.notify(); + } + } + }; + ExceptionUtil.translateException( + mActivity, ex, false, true, exListener); + } + return null; + } + + /** + * {@inheritDoc} + */ + @Override + protected void onPreExecute() { + // Do animation + fadeEfect(true); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onPostExecute(List files) { + if (files != null) { + onPostExecuteTask( + files, addToHistory, isNewHistory, + hasChanged, searchInfo, fNewDir, + scrollTo, scrollIndex, scrollIndexOffset); + + // Do animation + fadeEfect(false); + + synchronized (mSync) { + mChangingDir = false; + mSync.notify(); + } + } + } + + /** + * Method that performs a fade animation. + * + * @param out Fade out (true); Fade in (false) + */ + void fadeEfect(final boolean out) { + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + Animation fadeAnim = out ? + new AlphaAnimation(1, 0) : + new AlphaAnimation(0, 1); + fadeAnim.setDuration(50L); + fadeAnim.setFillAfter(true); + fadeAnim.setInterpolator(new AccelerateInterpolator()); + mNavigationViewHolder.startAnimation(fadeAnim); + } + }); + } + }; + task.execute(fNewDir); + } + + + /** + * Method invoked when a execution ends. + * + * @param files The files obtains from the list + * @param addToHistory If add path to history + * @param isNewHistory If is new history + * @param hasChanged If current directory was changed + * @param searchInfo The search information (if calling activity is {@link "SearchActivity"}) + * @param newDir The new directory + * @param scrollTo The new FileSystemObject to scroll to, since it isn't an existing view yet + * @param scrollIndex If not null, then listview must scroll to this item + * @param scrollIndexOffset If not null, then listview must scroll to this offset within the item + * @hide + */ + void onPostExecuteTask( + List files, boolean addToHistory, boolean isNewHistory, + boolean hasChanged, SearchInfoParcelable searchInfo, + String newDir, final FileSystemObject scrollTo, + final Integer scrollIndex, final Integer scrollIndexOffset) { + try { + //Check that there is not errors and have some data + if (files == null) { + return; + } + + //Apply user preferences + List sortedFiles = + FileHelper.applyUserPreferences(files, this.mRestrictions, this.mChRooted); + + //Remove parent directory if we are in the root of a chrooted environment + if (this.mChRooted && StorageHelper.isStorageVolume(newDir)) { + if (files.size() > 0 && files.get(0) instanceof ParentDirectory) { + files.remove(0); + } + } + + //Load the data + loadData(sortedFiles); + this.mFiles = sortedFiles; + if (searchInfo != null) { + searchInfo.setSuccessNavigation(true); + } + + //Add to history? + if (addToHistory && hasChanged && isNewHistory) { + if (this.mOnHistoryListener != null) { + //Communicate the need of a history change + this.mOnHistoryListener.onNewHistory(onSaveState()); + } + } + + //Change the breadcrumb + if (this.mBreadcrumb != null) { + this.mBreadcrumb.changeBreadcrumbPath(newDir, this.mChRooted); + } + + //Scroll to stored scroll location? + if (scrollTo != null) { + scrollTo(scrollTo); + } else if (scrollIndex != null && scrollIndexOffset != null) { + if(this.mAdapterView instanceof FlingerListView) { + this.mAdapterView.post(new Runnable() { + @Override + public void run() { + ((FlingerListView)mAdapterView).setSelectionFromTop(scrollIndex, scrollIndexOffset); + } + }); + + } else { + this.mAdapterView.post(new Runnable() { + @Override + public void run() { + mAdapterView.setSelection(scrollIndex); + } + }); + } + } + + //The current directory is now the "newDir" + this.mCurrentDir = newDir; + if (this.mOnDirectoryChangedListener != null) { + FileSystemObject dir = FileHelper.createFileSystemObject(new File(newDir)); + this.mOnDirectoryChangedListener.onDirectoryChanged(dir); + } + } finally { + //If calling activity is search, then save the search history + if (searchInfo != null) { + this.mOnHistoryListener.onNewHistory(searchInfo); + } + + //End of loading data + try { + NavigationFragment.this.mBreadcrumb.endLoading(); + } catch (Throwable ex) { + /**NON BLOCK**/ + } + } + } + + /** + * Method that loads the files in the adapter. + * + * @param files The files to load in the adapter + * @hide + */ + @SuppressWarnings("unchecked") + private void loadData(final List files) { + //Notify data to adapter view + final AdapterView view = + (AdapterView)getView().findViewById(RESOURCE_CURRENT_LAYOUT); + FileSystemObjectAdapter adapter = (FileSystemObjectAdapter)view.getAdapter(); + adapter.setNotifyOnChange(false); + adapter.clear(); + adapter.addAll(files); + adapter.notifyDataSetChanged(); + view.setSelection(0); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { + // Different actions depending on user preference + + // Get the adapter and the fso + FileSystemObjectAdapter adapter = ((FileSystemObjectAdapter)parent.getAdapter()); + FileSystemObject fso = adapter.getItem(position); + + // Parent directory hasn't actions + if (fso instanceof ParentDirectory) { + return false; + } + + // Pick mode doesn't implements the onlongclick + if (this.mNavigationMode.compareTo(NAVIGATION_MODE.PICKABLE) == 0) { + return false; + } + + onToggleSelection(fso); + return true; //Always consume the event + } + + /** + * Method that opens or navigates to the {@link FileSystemObject} + * + * @param fso The file system object + */ + public void open(FileSystemObject fso) { + open(fso, null); + } + + /** + * Method that opens or navigates to the {@link FileSystemObject} + * + * @param fso The file system object + * @param searchInfo The search info + */ + public void open(FileSystemObject fso, SearchInfoParcelable searchInfo) { + // If is a folder, then navigate to + if (FileHelper.isDirectory(fso)) { + changeCurrentDir(fso.getFullPath(), searchInfo); + } else { + // Open the file with the preferred registered app + IntentsActionPolicy.openFileSystemObject(mActivity, fso, false, null, null); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + try { + FileSystemObject fso = ((FileSystemObjectAdapter)parent.getAdapter()).getItem(position); + if (fso instanceof ParentDirectory) { + changeCurrentDir(fso.getParent(), true, false, false, null, null, null, null); + return; + } else if (fso instanceof Directory) { + changeCurrentDir(fso.getFullPath(), true, false, false, null, null, null, null); + return; + } else if (fso instanceof Symlink) { + Symlink symlink = (Symlink)fso; + if (symlink.getLinkRef() != null && symlink.getLinkRef() instanceof Directory) { + changeCurrentDir( + symlink.getLinkRef().getFullPath(), true, false, false, null, null, null, null); + return; + } + + // Open the link ref + fso = symlink.getLinkRef(); + } + + // Open the file (edit or pick) + if (this.mNavigationMode.compareTo(NAVIGATION_MODE.BROWSABLE) == 0) { + // Open the file with the preferred registered app + IntentsActionPolicy.openFileSystemObject(mActivity, fso, false, null, null); + } else { + // Request a file pick selection + if (this.mOnFilePickedListener != null) { + this.mOnFilePickedListener.onFilePicked(fso); + } + } + } catch (Throwable ex) { + ExceptionUtil.translateException(mActivity, ex); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onRequestRefresh(Object o, boolean clearSelection) { + if (o instanceof FileSystemObject) { + refresh((FileSystemObject)o); + } else if (o == null) { + refresh(); + } + if (clearSelection) { + onDeselectAll(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onRequestRemove(Object o, boolean clearSelection) { + if (o != null && o instanceof FileSystemObject) { + removeItem((FileSystemObject)o); + } else { + onRequestRefresh(null, clearSelection); + } + if (clearSelection) { + onDeselectAll(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onNavigateTo(Object o) { + // Ignored + } + + /** + * {@inheritDoc} + */ + @Override + public void onSelectionChanged(final List selectedItems) { + updateSelectionMode(); + } + + /** + * Method invoked when a request to show the menu associated + * with an item is started. + * + * @param item The item for which the request was started + */ + public void onRequestMenu(final FileSystemObject item) { + if (this.mOnNavigationRequestMenuListener != null) { + this.mOnNavigationRequestMenuListener.onRequestMenu(this, item); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onToggleSelection(FileSystemObject fso) { + if (this.mAdapter != null) { + this.mAdapter.toggleSelection(fso); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onDeselectAll() { + if (this.mAdapter != null) { + this.mAdapter.deselectedAll(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onSelectAllVisibleItems() { + if (this.mAdapter != null) { + this.mAdapter.selectedAllVisibleItems(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onDeselectAllVisibleItems() { + if (this.mAdapter != null) { + this.mAdapter.deselectedAllVisibleItems(); + } + } + + public int onRequestSelectionCount() { + return mAdapter.getSelectedItemsCount(); + } + + /** + * Show/hide the "selection" action mode, according to the number of + * selected messages and the visibility of the fragment. Also update the + * content (title and menus) if necessary. + */ + public void updateSelectionMode() { + final int numSelected = onRequestSelectionCount(); + if (numSelected == 0) { + finishSelectionMode(); + return; + } + if (isInSelectionMode()) { + updateSelectionModeView(); + } else { + mSelectionModeCallback = new SelectionModeCallback(mActivity, false); + mSelectionModeCallback.setOnSelectionListener(this); + if(mActivity instanceof NavigationActivity) { + mSelectionModeCallback.setOnRequestRefreshListener((NavigationActivity)mActivity); + mSelectionModeCallback.setOnCopyMoveListener((NavigationActivity)mActivity); + } + mActivity.startActionMode(mSelectionModeCallback); + } + } + + /** + * Finish the "selection" action mode. + * + * Note this method finishes the contextual mode, but does *not* clear the + * selection. If you want to do so use {@link #onDeselectAll()} instead. + */ + private void finishSelectionMode() { + if (isInSelectionMode()) { + mSelectionModeCallback.setClosedByUser(false); + mSelectionModeCallback.finish(); + } + } + + /** + * @return true if the list is in the "selection" mode. + */ + private boolean isInSelectionMode() { + if (mSelectionModeCallback == null) { + return false; + } else if (mSelectionModeCallback.inSelectionMode()) { + return true; + } else { + return false; + } + } + + /** Update the "selection" action mode bar */ + private void updateSelectionModeView() { + mSelectionModeCallback.refresh(); + } + + /** + * {@inheritDoc} + */ + @Override + public List onRequestSelectedFiles() { + return this.getSelectedFiles(); + } + + /** + * {@inheritDoc} + */ + @Override + public List onRequestCurrentItems() { + return this.getFiles(); + } + + /** + * {@inheritDoc} + */ + @Override + public String onRequestCurrentDir() { + return this.mCurrentDir; + } + + /** + * Method that creates a ChRooted environment, protecting the user to break anything + * in the device + * @hide + */ + public void enterChRooted(String chRootedVolumePath) { + // If we are in a ChRooted environment, then do nothing + if (this.mChRooted) return; + this.mChRooted = true; + + if (FileHelper.isSubDirectoryOf(this.mCurrentDir, chRootedVolumePath)) { + // current directory is already inside the ChRooted environment, so + // no directory change or selection removal is necessary + } else { + changeCurrentDir(chRootedVolumePath, false, true, false, null, null, null, null); + onDeselectAll(); + } + + } + + /** + * Method that exits from a ChRooted environment + * @hide + */ + public void exitChRooted() { + // If we aren't in a ChRooted environment, then do nothing + if (!this.mChRooted) return; + this.mChRooted = false; + + // Refresh + refresh(); + } + + /** + * Method that ensures that the user don't go outside the ChRooted environment + * + * @param newDir The new directory to navigate to + * @return String + */ + private String checkChRootedNavigation(String newDir) { + // If we aren't in ChRooted environment, then there is nothing to check + if (!this.mChRooted) return newDir; + + // Check if the path is owned by one of the storage volumes + if (!StorageHelper.isPathInStorageVolume(newDir)) { + Object[] volumes = StorageHelper.getStorageVolumes(mActivity); + if (volumes != null && volumes.length > 0) { + return StorageHelper.getStoragePath(volumes[0]); + } + } + return newDir; + } + + /** + * Method that applies the current theme to the activity + */ + public void applyTheme() { + + //- Redraw the adapter view + ThemeManager.Theme theme = ThemeManager.getCurrentTheme(mActivity); + //theme.setBackgroundDrawable(mActivity, mNavigationViewHolder, "background_drawable"); //$NON-NLS-1$ + if (this.mAdapter != null) { + this.mAdapter.notifyThemeChanged(); + } + if (this.mAdapterView instanceof ListView) { + ((ListView)this.mAdapterView).setDivider( + theme.getDrawable(mActivity, "horizontal_divider_drawable")); //$NON-NLS-1$ + } + refresh(); + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/listeners/OnCopyMoveListener.java b/Backbone/src/main/java/me/toolify/backbone/listeners/OnCopyMoveListener.java new file mode 100644 index 000000000..ecc06a655 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/listeners/OnCopyMoveListener.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * Copyright (C) 2013 BrandroidTools + * + * 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 me.toolify.backbone.listeners; + +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.ui.policy.CopyMoveActionPolicy.COPY_MOVE_OPERATION; + +import java.util.List; + +/** + * A listener for handling file selections ready for paste operations. + */ +public interface OnCopyMoveListener { + + /** + * Invoked when a selection of files must be marked for paste. item must be created. + * + * @param filesForPaste The data to marked + * @param pasteOperationType The type of paste operation (Copy/Move) + */ + void onMarkFilesForPaste(List filesForPaste, COPY_MOVE_OPERATION pasteOperationType); + + /** + * Invoked when the availability of files marked for paste must be checked. + */ + boolean onAreFilesMarkedForPaste(); + + /** + * Invoked when files marked for paste must be unmarked. + */ + void onClearFilesMarkedForPaste(); + + /** + * Invoked when files marked for paste are requested. + */ + List onRequestFilesMarkedForPaste(); + + /** + * Invoked when files marked for paste are requested. + */ + COPY_MOVE_OPERATION onRequestPasteOperationType(); + + /** + * Invoked when destination directory is requested. + */ + String onRequestDestinationDir(); +} diff --git a/src/com/cyanogenmod/filemanager/listeners/OnHistoryListener.java b/Backbone/src/main/java/me/toolify/backbone/listeners/OnHistoryListener.java similarity index 89% rename from src/com/cyanogenmod/filemanager/listeners/OnHistoryListener.java rename to Backbone/src/main/java/me/toolify/backbone/listeners/OnHistoryListener.java index a0ba51604..44dd28606 100644 --- a/src/com/cyanogenmod/filemanager/listeners/OnHistoryListener.java +++ b/Backbone/src/main/java/me/toolify/backbone/listeners/OnHistoryListener.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.listeners; +package me.toolify.backbone.listeners; -import com.cyanogenmod.filemanager.parcelables.HistoryNavigable; +import me.toolify.backbone.parcelables.HistoryNavigable; /** * A listener for communicate history changes. diff --git a/src/com/cyanogenmod/filemanager/listeners/OnRequestRefreshListener.java b/Backbone/src/main/java/me/toolify/backbone/listeners/OnRequestRefreshListener.java similarity index 96% rename from src/com/cyanogenmod/filemanager/listeners/OnRequestRefreshListener.java rename to Backbone/src/main/java/me/toolify/backbone/listeners/OnRequestRefreshListener.java index 0de97198e..0ff15993e 100644 --- a/src/com/cyanogenmod/filemanager/listeners/OnRequestRefreshListener.java +++ b/Backbone/src/main/java/me/toolify/backbone/listeners/OnRequestRefreshListener.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.listeners; +package me.toolify.backbone.listeners; /** * A listener for requesting an object refresh. diff --git a/src/com/cyanogenmod/filemanager/listeners/OnSelectionListener.java b/Backbone/src/main/java/me/toolify/backbone/listeners/OnSelectionListener.java similarity index 86% rename from src/com/cyanogenmod/filemanager/listeners/OnSelectionListener.java rename to Backbone/src/main/java/me/toolify/backbone/listeners/OnSelectionListener.java index 11ee1124a..b19195029 100644 --- a/src/com/cyanogenmod/filemanager/listeners/OnSelectionListener.java +++ b/Backbone/src/main/java/me/toolify/backbone/listeners/OnSelectionListener.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2012 The CyanogenMod Project + * Copyright (C) 2013 BrandroidTools * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,9 +15,9 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.listeners; +package me.toolify.backbone.listeners; -import com.cyanogenmod.filemanager.model.FileSystemObject; +import me.toolify.backbone.model.FileSystemObject; import java.util.List; @@ -67,4 +68,11 @@ public interface OnSelectionListener { * @return String The current directory. */ String onRequestCurrentDir(); + + /** + * Method that request the number of selected items. + * + * @return String The current directory. + */ + int onRequestSelectionCount(); } diff --git a/src/com/cyanogenmod/filemanager/model/AID.java b/Backbone/src/main/java/me/toolify/backbone/model/AID.java similarity index 98% rename from src/com/cyanogenmod/filemanager/model/AID.java rename to Backbone/src/main/java/me/toolify/backbone/model/AID.java index c6f288ba8..c068522f1 100644 --- a/src/com/cyanogenmod/filemanager/model/AID.java +++ b/Backbone/src/main/java/me/toolify/backbone/model/AID.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.model; +package me.toolify.backbone.model; import java.io.Serializable; diff --git a/src/com/cyanogenmod/filemanager/model/BlockDevice.java b/Backbone/src/main/java/me/toolify/backbone/model/BlockDevice.java similarity index 98% rename from src/com/cyanogenmod/filemanager/model/BlockDevice.java rename to Backbone/src/main/java/me/toolify/backbone/model/BlockDevice.java index 31873b36c..4a7e22dd0 100644 --- a/src/com/cyanogenmod/filemanager/model/BlockDevice.java +++ b/Backbone/src/main/java/me/toolify/backbone/model/BlockDevice.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.model; +package me.toolify.backbone.model; import java.util.Date; diff --git a/src/com/cyanogenmod/filemanager/model/Bookmark.java b/Backbone/src/main/java/me/toolify/backbone/model/Bookmark.java similarity index 82% rename from src/com/cyanogenmod/filemanager/model/Bookmark.java rename to Backbone/src/main/java/me/toolify/backbone/model/Bookmark.java index 192f2d9c5..ab2217c8c 100644 --- a/src/com/cyanogenmod/filemanager/model/Bookmark.java +++ b/Backbone/src/main/java/me/toolify/backbone/model/Bookmark.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.model; +package me.toolify.backbone.model; import android.database.Cursor; import android.net.Uri; @@ -22,11 +22,11 @@ import android.os.Parcelable; import android.provider.BaseColumns; -import com.cyanogenmod.filemanager.providers.BookmarksContentProvider; - import java.io.File; import java.io.Serializable; +import me.toolify.backbone.providers.BookmarksContentProvider; + /** * A class that represent a bookmark. */ @@ -58,6 +58,24 @@ public enum BOOKMARK_TYPE { USER_DEFINED } + /** + * Enumeration for displayed bookmark categories. + */ + public enum BOOKMARK_CATEGORY { + /** + * Filesystem locations including filesystems, mounted sd cards and mounted usb drives. + */ + LOCATIONS, + /** + * User bookmarks. + */ + USER_BOOKMARKS, + /** + * Cloud drives. + */ + CLOUD + } + private static final long serialVersionUID = -7524744999056506867L; /** @@ -111,6 +129,8 @@ public static class Columns implements BaseColumns { /** @hide **/ public BOOKMARK_TYPE mType; /** @hide **/ + public BOOKMARK_CATEGORY mCategory; + /** @hide **/ public String mName; /** @hide **/ public String mPath; @@ -119,13 +139,15 @@ public static class Columns implements BaseColumns { * Constructor of Bookmark. * * @param type The type of the bookmark + * @param category The display category of the bookmark * @param name The name of the bookmark * @param path The path that the bookmark points to * @hide */ - public Bookmark(BOOKMARK_TYPE type, String name, String path) { + public Bookmark(BOOKMARK_TYPE type, BOOKMARK_CATEGORY category, String name, String path) { super(); this.mType = type; + this.mCategory = category; this.mName = name; this.mPath = path; } @@ -139,6 +161,7 @@ public Bookmark(Cursor c) { super(); this.mId = c.getInt(Columns.BOOKMARK_ID_INDEX); this.mType = BOOKMARK_TYPE.USER_DEFINED; + this.mCategory = BOOKMARK_CATEGORY.USER_BOOKMARKS; this.mPath = c.getString(Columns.BOOKMARK_PATH_INDEX); this.mName = new File(this.mPath).getName(); } @@ -167,7 +190,7 @@ public boolean equals(Object obj) { if (obj == null) { return false; } - if (getClass() != obj.getClass()) { + if (!(obj instanceof Bookmark)) { return false; } Bookmark other = (Bookmark) obj; @@ -206,6 +229,7 @@ public int describeContents() { public void writeToParcel(Parcel dest, int flags) { dest.writeInt(this.mId); dest.writeString(this.mType.toString()); + dest.writeString(this.mCategory.toString()); dest.writeString(this.mName); dest.writeString(this.mPath); } @@ -218,9 +242,10 @@ public void writeToParcel(Parcel dest, int flags) { public Bookmark createFromParcel(Parcel in) { int id = in.readInt(); BOOKMARK_TYPE type = BOOKMARK_TYPE.valueOf(in.readString()); + BOOKMARK_CATEGORY category = BOOKMARK_CATEGORY.valueOf(in.readString()); String name = in.readString(); String path = in.readString(); - Bookmark b = new Bookmark(type, name, path); + Bookmark b = new Bookmark(type, category, name, path); b.mId = id; return b; } @@ -243,14 +268,19 @@ public int compareTo(Bookmark another) { return this.mPath.compareTo(another.mPath); } + public BOOKMARK_TYPE getType() { + return mType; + } + /** * {@inheritDoc} */ @Override public String toString() { return "Bookmark [id=" + this.mId + ", type=" + //$NON-NLS-1$//$NON-NLS-2$ - this.mType + ", name=" + this.mName + //$NON-NLS-1$ - ", path=" + this.mPath + "]"; //$NON-NLS-1$//$NON-NLS-2$ + this.mType + ", category=" + this.mCategory + //$NON-NLS-1$ + ", name=" + this.mName + ", path=" //$NON-NLS-1$//$NON-NLS-2$ + + this.mPath + "]"; //$NON-NLS-1$ } } diff --git a/src/com/cyanogenmod/filemanager/model/CharacterDevice.java b/Backbone/src/main/java/me/toolify/backbone/model/CharacterDevice.java similarity index 98% rename from src/com/cyanogenmod/filemanager/model/CharacterDevice.java rename to Backbone/src/main/java/me/toolify/backbone/model/CharacterDevice.java index 489e05ad2..443f03de3 100644 --- a/src/com/cyanogenmod/filemanager/model/CharacterDevice.java +++ b/Backbone/src/main/java/me/toolify/backbone/model/CharacterDevice.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.model; +package me.toolify.backbone.model; import java.util.Date; diff --git a/src/com/cyanogenmod/filemanager/model/Directory.java b/Backbone/src/main/java/me/toolify/backbone/model/Directory.java similarity index 96% rename from src/com/cyanogenmod/filemanager/model/Directory.java rename to Backbone/src/main/java/me/toolify/backbone/model/Directory.java index 77b120815..291f50b04 100644 --- a/src/com/cyanogenmod/filemanager/model/Directory.java +++ b/Backbone/src/main/java/me/toolify/backbone/model/Directory.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.model; +package me.toolify.backbone.model; -import com.cyanogenmod.filemanager.R; +import me.toolify.backbone.R; import java.util.Date; diff --git a/src/com/cyanogenmod/filemanager/model/DiskUsage.java b/Backbone/src/main/java/me/toolify/backbone/model/DiskUsage.java similarity index 98% rename from src/com/cyanogenmod/filemanager/model/DiskUsage.java rename to Backbone/src/main/java/me/toolify/backbone/model/DiskUsage.java index 693e39b00..05d817990 100644 --- a/src/com/cyanogenmod/filemanager/model/DiskUsage.java +++ b/Backbone/src/main/java/me/toolify/backbone/model/DiskUsage.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.model; +package me.toolify.backbone.model; import java.io.Serializable; diff --git a/src/com/cyanogenmod/filemanager/model/DomainSocket.java b/Backbone/src/main/java/me/toolify/backbone/model/DomainSocket.java similarity index 98% rename from src/com/cyanogenmod/filemanager/model/DomainSocket.java rename to Backbone/src/main/java/me/toolify/backbone/model/DomainSocket.java index c7d9af1ff..68a134dd4 100644 --- a/src/com/cyanogenmod/filemanager/model/DomainSocket.java +++ b/Backbone/src/main/java/me/toolify/backbone/model/DomainSocket.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.model; +package me.toolify.backbone.model; import java.util.Date; diff --git a/src/com/cyanogenmod/filemanager/model/FileSystemObject.java b/Backbone/src/main/java/me/toolify/backbone/model/FileSystemObject.java similarity index 97% rename from src/com/cyanogenmod/filemanager/model/FileSystemObject.java rename to Backbone/src/main/java/me/toolify/backbone/model/FileSystemObject.java index 397b0f11e..545605906 100644 --- a/src/com/cyanogenmod/filemanager/model/FileSystemObject.java +++ b/Backbone/src/main/java/me/toolify/backbone/model/FileSystemObject.java @@ -14,10 +14,10 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.model; +package me.toolify.backbone.model; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.util.FileHelper; +import me.toolify.backbone.R; +import me.toolify.backbone.util.FileHelper; import java.io.File; import java.io.Serializable; @@ -348,9 +348,8 @@ public boolean equals(Object obj) { * @return String The string representation */ public String toRawPermissionString() { - return String.format("%s%s", //$NON-NLS-1$ - String.valueOf(getUnixIdentifier()), - getPermissions().toRawString()); + return Character.toString(getUnixIdentifier()) + + getPermissions().toRawString(); } /** diff --git a/src/com/cyanogenmod/filemanager/model/FolderUsage.java b/Backbone/src/main/java/me/toolify/backbone/model/FolderUsage.java similarity index 98% rename from src/com/cyanogenmod/filemanager/model/FolderUsage.java rename to Backbone/src/main/java/me/toolify/backbone/model/FolderUsage.java index 341f72a15..abe1fdd37 100644 --- a/src/com/cyanogenmod/filemanager/model/FolderUsage.java +++ b/Backbone/src/main/java/me/toolify/backbone/model/FolderUsage.java @@ -14,11 +14,11 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.model; +package me.toolify.backbone.model; import android.util.SparseArray; -import com.cyanogenmod.filemanager.util.MimeTypeHelper.MimeTypeCategory; +import me.toolify.backbone.util.MimeTypeHelper.MimeTypeCategory; import java.io.Serializable; diff --git a/src/com/cyanogenmod/filemanager/model/Group.java b/Backbone/src/main/java/me/toolify/backbone/model/Group.java similarity index 96% rename from src/com/cyanogenmod/filemanager/model/Group.java rename to Backbone/src/main/java/me/toolify/backbone/model/Group.java index 553bd998e..4335febe3 100644 --- a/src/com/cyanogenmod/filemanager/model/Group.java +++ b/Backbone/src/main/java/me/toolify/backbone/model/Group.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.model; +package me.toolify.backbone.model; /** * A class that represents a group of the operating system. diff --git a/src/com/cyanogenmod/filemanager/model/GroupPermission.java b/Backbone/src/main/java/me/toolify/backbone/model/GroupPermission.java similarity index 97% rename from src/com/cyanogenmod/filemanager/model/GroupPermission.java rename to Backbone/src/main/java/me/toolify/backbone/model/GroupPermission.java index 3a32c5860..ce66a7fe6 100644 --- a/src/com/cyanogenmod/filemanager/model/GroupPermission.java +++ b/Backbone/src/main/java/me/toolify/backbone/model/GroupPermission.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.model; +package me.toolify.backbone.model; /** * A class for represents a group permissions. @@ -77,6 +77,7 @@ public boolean isSetGID() { */ public void setSetGID(boolean setgid) { this.mSetGid = setgid; + invalidateRawString(); } /** @@ -124,7 +125,7 @@ public String toString() { * {@inheritDoc} */ @Override - public String toRawString() { + protected String getRawString() { StringBuilder p = new StringBuilder(); p.append(isRead() ? READ : UNASIGNED); p.append(isWrite() ? WRITE : UNASIGNED); diff --git a/src/com/cyanogenmod/filemanager/model/History.java b/Backbone/src/main/java/me/toolify/backbone/model/History.java similarity index 96% rename from src/com/cyanogenmod/filemanager/model/History.java rename to Backbone/src/main/java/me/toolify/backbone/model/History.java index 327878363..e0ad9620f 100644 --- a/src/com/cyanogenmod/filemanager/model/History.java +++ b/Backbone/src/main/java/me/toolify/backbone/model/History.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.model; +package me.toolify.backbone.model; -import com.cyanogenmod.filemanager.parcelables.HistoryNavigable; +import me.toolify.backbone.parcelables.HistoryNavigable; import java.io.Serializable; diff --git a/src/com/cyanogenmod/filemanager/model/Identity.java b/Backbone/src/main/java/me/toolify/backbone/model/Identity.java similarity index 99% rename from src/com/cyanogenmod/filemanager/model/Identity.java rename to Backbone/src/main/java/me/toolify/backbone/model/Identity.java index e0aff5b1a..b1b2eb0ef 100644 --- a/src/com/cyanogenmod/filemanager/model/Identity.java +++ b/Backbone/src/main/java/me/toolify/backbone/model/Identity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.model; +package me.toolify.backbone.model; import java.io.Serializable; import java.util.ArrayList; diff --git a/Backbone/src/main/java/me/toolify/backbone/model/License.java b/Backbone/src/main/java/me/toolify/backbone/model/License.java new file mode 100644 index 000000000..46a4676c1 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/model/License.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.model; + +/** + * A class that represent a bookmark. + */ +public class License { + + /** + * Enumeration for types of bookmarks. + */ + public enum LICENSE_TYPE { + /** + * An USB mount point. + */ + LICENSE, + /** + * A bookmark added by the user. + */ + CREDIT + } + + /** @hide **/ + public int mId; + /** @hide **/ + public LICENSE_TYPE mType; + /** @hide **/ + public String mHeader; + /** @hide **/ + public String mDetail; + + /** + * Constructor of License. + * + * @param type The type of the bookmark + * @param header The header text of the license + * @param detail The text of the license details + * @hide + */ + public License(LICENSE_TYPE type, String header, String detail) { + super(); + this.mType = type; + this.mHeader = header; + this.mDetail = detail; + } +} diff --git a/src/com/cyanogenmod/filemanager/model/MountPoint.java b/Backbone/src/main/java/me/toolify/backbone/model/MountPoint.java similarity index 98% rename from src/com/cyanogenmod/filemanager/model/MountPoint.java rename to Backbone/src/main/java/me/toolify/backbone/model/MountPoint.java index 92576a877..a3da94ba0 100644 --- a/src/com/cyanogenmod/filemanager/model/MountPoint.java +++ b/Backbone/src/main/java/me/toolify/backbone/model/MountPoint.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.model; +package me.toolify.backbone.model; import java.io.Serializable; @@ -135,7 +135,7 @@ public boolean equals(Object obj) { if (obj == null) { return false; } - if (getClass() != obj.getClass()) { + if (!(obj instanceof MountPoint)) { return false; } MountPoint other = (MountPoint) obj; diff --git a/src/com/cyanogenmod/filemanager/model/NamedPipe.java b/Backbone/src/main/java/me/toolify/backbone/model/NamedPipe.java similarity index 98% rename from src/com/cyanogenmod/filemanager/model/NamedPipe.java rename to Backbone/src/main/java/me/toolify/backbone/model/NamedPipe.java index 5a15f8505..723ebd1f3 100644 --- a/src/com/cyanogenmod/filemanager/model/NamedPipe.java +++ b/Backbone/src/main/java/me/toolify/backbone/model/NamedPipe.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.model; +package me.toolify.backbone.model; import java.util.Date; diff --git a/src/com/cyanogenmod/filemanager/model/OthersPermission.java b/Backbone/src/main/java/me/toolify/backbone/model/OthersPermission.java similarity index 97% rename from src/com/cyanogenmod/filemanager/model/OthersPermission.java rename to Backbone/src/main/java/me/toolify/backbone/model/OthersPermission.java index 09c5b62ef..83e96ad9b 100644 --- a/src/com/cyanogenmod/filemanager/model/OthersPermission.java +++ b/Backbone/src/main/java/me/toolify/backbone/model/OthersPermission.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.model; +package me.toolify.backbone.model; /** * A class for represents a others permissions. @@ -77,6 +77,7 @@ public boolean isStickybit() { */ public void setStickybit(boolean stickybit) { this.mStickybit = stickybit; + invalidateRawString(); } /** @@ -124,7 +125,7 @@ public String toString() { * {@inheritDoc} */ @Override - public String toRawString() { + protected String getRawString() { StringBuilder p = new StringBuilder(); p.append(isRead() ? READ : UNASIGNED); p.append(isWrite() ? WRITE : UNASIGNED); diff --git a/src/com/cyanogenmod/filemanager/model/ParentDirectory.java b/Backbone/src/main/java/me/toolify/backbone/model/ParentDirectory.java similarity index 92% rename from src/com/cyanogenmod/filemanager/model/ParentDirectory.java rename to Backbone/src/main/java/me/toolify/backbone/model/ParentDirectory.java index 8a713791c..958d85658 100644 --- a/src/com/cyanogenmod/filemanager/model/ParentDirectory.java +++ b/Backbone/src/main/java/me/toolify/backbone/model/ParentDirectory.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.model; +package me.toolify.backbone.model; -import com.cyanogenmod.filemanager.util.FileHelper; +import me.toolify.backbone.util.FileHelper; /** diff --git a/src/com/cyanogenmod/filemanager/model/Permission.java b/Backbone/src/main/java/me/toolify/backbone/model/Permission.java similarity index 90% rename from src/com/cyanogenmod/filemanager/model/Permission.java rename to Backbone/src/main/java/me/toolify/backbone/model/Permission.java index 61cd7b55f..2b21bb97f 100644 --- a/src/com/cyanogenmod/filemanager/model/Permission.java +++ b/Backbone/src/main/java/me/toolify/backbone/model/Permission.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.model; +package me.toolify.backbone.model; import java.io.Serializable; @@ -53,6 +53,8 @@ public abstract class Permission implements Serializable { private boolean mWrite; private boolean mExecute; + private String mRawString; + /** * Constructor of Permission. * @@ -83,6 +85,7 @@ public boolean isRead() { */ public void setRead(boolean read) { this.mRead = read; + invalidateRawString(); } /** @@ -101,6 +104,7 @@ public boolean isWrite() { */ public void setWrite(boolean write) { this.mWrite = write; + invalidateRawString(); } /** @@ -119,6 +123,7 @@ public boolean isExecute() { */ public void setExecute(boolean execute) { this.mExecute = execute; + invalidateRawString(); } /** @@ -176,6 +181,15 @@ public String toString() { * * @return String The string representation of the permissions */ - public abstract String toRawString(); + public String toRawString() { + if (mRawString == null) { + mRawString = getRawString(); + } + return mRawString; + } + protected void invalidateRawString() { + mRawString = null; + } + protected abstract String getRawString(); } diff --git a/src/com/cyanogenmod/filemanager/model/Permissions.java b/Backbone/src/main/java/me/toolify/backbone/model/Permissions.java similarity index 96% rename from src/com/cyanogenmod/filemanager/model/Permissions.java rename to Backbone/src/main/java/me/toolify/backbone/model/Permissions.java index cad6a054f..cfeab049f 100644 --- a/src/com/cyanogenmod/filemanager/model/Permissions.java +++ b/Backbone/src/main/java/me/toolify/backbone/model/Permissions.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.model; +package me.toolify.backbone.model; -import com.cyanogenmod.filemanager.util.ParseHelper; +import me.toolify.backbone.util.ParseHelper; import java.io.Serializable; import java.text.ParseException; @@ -182,10 +182,9 @@ public String toString() { * @return String The string representation of the permissions */ public String toRawString() { - return String.format("%s%s%s", //$NON-NLS-1$ - this.mUser.toRawString(), - this.mGroup.toRawString(), - this.mOthers.toRawString()); + return this.mUser.toRawString() + + this.mGroup.toRawString() + + this.mOthers.toRawString(); } /** diff --git a/src/com/cyanogenmod/filemanager/model/Query.java b/Backbone/src/main/java/me/toolify/backbone/model/Query.java similarity index 98% rename from src/com/cyanogenmod/filemanager/model/Query.java rename to Backbone/src/main/java/me/toolify/backbone/model/Query.java index abda13273..9bdcaa331 100644 --- a/src/com/cyanogenmod/filemanager/model/Query.java +++ b/Backbone/src/main/java/me/toolify/backbone/model/Query.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.model; +package me.toolify.backbone.model; import android.text.TextUtils; diff --git a/src/com/cyanogenmod/filemanager/model/RegularFile.java b/Backbone/src/main/java/me/toolify/backbone/model/RegularFile.java similarity index 98% rename from src/com/cyanogenmod/filemanager/model/RegularFile.java rename to Backbone/src/main/java/me/toolify/backbone/model/RegularFile.java index 49c9fd770..775cd746a 100644 --- a/src/com/cyanogenmod/filemanager/model/RegularFile.java +++ b/Backbone/src/main/java/me/toolify/backbone/model/RegularFile.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.model; +package me.toolify.backbone.model; import java.util.Date; diff --git a/src/com/cyanogenmod/filemanager/model/SearchResult.java b/Backbone/src/main/java/me/toolify/backbone/model/SearchResult.java similarity index 98% rename from src/com/cyanogenmod/filemanager/model/SearchResult.java rename to Backbone/src/main/java/me/toolify/backbone/model/SearchResult.java index 6f1c7d0ab..6b6e0b295 100644 --- a/src/com/cyanogenmod/filemanager/model/SearchResult.java +++ b/Backbone/src/main/java/me/toolify/backbone/model/SearchResult.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.model; +package me.toolify.backbone.model; import java.io.Serializable; diff --git a/Backbone/src/main/java/me/toolify/backbone/model/StorageVolume.java b/Backbone/src/main/java/me/toolify/backbone/model/StorageVolume.java new file mode 100644 index 000000000..4b6c9683c --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/model/StorageVolume.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * 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 me.toolify.backbone.model; + +import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.UserHandle; + +import java.io.File; + +/** + * Description of a storage volume and its capabilities, including the + * filesystem path where it may be mounted. + * + * @hide + */ +public class StorageVolume implements Parcelable { + + // TODO: switch to more durable token + private int mStorageId; + + private final File mPath; + private final int mDescriptionId; + private final boolean mPrimary; + private final boolean mRemovable; + private final boolean mEmulated; + private final int mMtpReserveSpace; + private final boolean mAllowMassStorage; + /** Maximum file size for the storage, or zero for no limit */ + private final long mMaxFileSize; + /** When set, indicates exclusive ownership of this volume */ + private final UserHandle mOwner; + + // StorageVolume extra for ACTION_MEDIA_REMOVED, ACTION_MEDIA_UNMOUNTED, ACTION_MEDIA_CHECKING, + // ACTION_MEDIA_NOFS, ACTION_MEDIA_MOUNTED, ACTION_MEDIA_SHARED, ACTION_MEDIA_UNSHARED, + // ACTION_MEDIA_BAD_REMOVAL, ACTION_MEDIA_UNMOUNTABLE and ACTION_MEDIA_EJECT broadcasts. + public static final String EXTRA_STORAGE_VOLUME = "storage_volume"; + + public StorageVolume(File path, int descriptionId, boolean primary, boolean removable, + boolean emulated, int mtpReserveSpace, boolean allowMassStorage, long maxFileSize, + UserHandle owner) { + mPath = path; + mDescriptionId = descriptionId; + mPrimary = primary; + mRemovable = removable; + mEmulated = emulated; + mMtpReserveSpace = mtpReserveSpace; + mAllowMassStorage = allowMassStorage; + mMaxFileSize = maxFileSize; + mOwner = owner; + } + + private StorageVolume(Parcel in) { + mStorageId = in.readInt(); + mPath = new File(in.readString()); + mDescriptionId = in.readInt(); + mPrimary = in.readInt() != 0; + mRemovable = in.readInt() != 0; + mEmulated = in.readInt() != 0; + mMtpReserveSpace = in.readInt(); + mAllowMassStorage = in.readInt() != 0; + mMaxFileSize = in.readLong(); + mOwner = in.readParcelable(null); + } + + public static StorageVolume fromTemplate(StorageVolume template, File path, UserHandle owner) { + return new StorageVolume(path, template.mDescriptionId, template.mPrimary, + template.mRemovable, template.mEmulated, template.mMtpReserveSpace, + template.mAllowMassStorage, template.mMaxFileSize, owner); + } + + /** + * Returns the mount path for the volume. + * + * @return the mount path + */ + public String getPath() { + return mPath.toString(); + } + + public File getPathFile() { + return mPath; + } + + /** + * Returns a user visible description of the volume. + * + * @return the volume description + */ + public String getDescription(Context context) { + return context.getResources().getString(mDescriptionId); + } + + public int getDescriptionId() { + return mDescriptionId; + } + + public boolean isPrimary() { + return mPrimary; + } + + /** + * Returns true if the volume is removable. + * + * @return is removable + */ + public boolean isRemovable() { + return mRemovable; + } + + /** + * Returns true if the volume is emulated. + * + * @return is removable + */ + public boolean isEmulated() { + return mEmulated; + } + + /** + * Returns the MTP storage ID for the volume. + * this is also used for the storage_id column in the media provider. + * + * @return MTP storage ID + */ + public int getStorageId() { + return mStorageId; + } + + /** + * Do not call this unless you are MountService + */ + public void setStorageId(int index) { + // storage ID is 0x00010001 for primary storage, + // then 0x00020001, 0x00030001, etc. for secondary storages + mStorageId = ((index + 1) << 16) + 1; + } + + /** + * Number of megabytes of space to leave unallocated by MTP. + * MTP will subtract this value from the free space it reports back + * to the host via GetStorageInfo, and will not allow new files to + * be added via MTP if there is less than this amount left free in the storage. + * If MTP has dedicated storage this value should be zero, but if MTP is + * sharing storage with the rest of the system, set this to a positive value + * to ensure that MTP activity does not result in the storage being + * too close to full. + * + * @return MTP reserve space + */ + public int getMtpReserveSpace() { + return mMtpReserveSpace; + } + + /** + * Returns true if this volume can be shared via USB mass storage. + * + * @return whether mass storage is allowed + */ + public boolean allowMassStorage() { + return mAllowMassStorage; + } + + /** + * Returns maximum file size for the volume, or zero if it is unbounded. + * + * @return maximum file size + */ + public long getMaxFileSize() { + return mMaxFileSize; + } + + public UserHandle getOwner() { + return mOwner; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof StorageVolume && mPath != null) { + StorageVolume volume = (StorageVolume)obj; + return (mPath.equals(volume.mPath)); + } + return false; + } + + @Override + public int hashCode() { + return mPath.hashCode(); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder("StorageVolume ["); + builder.append("mStorageId=").append(mStorageId); + builder.append(" mPath=").append(mPath); + builder.append(" mDescriptionId=").append(mDescriptionId); + builder.append(" mPrimary=").append(mPrimary); + builder.append(" mRemovable=").append(mRemovable); + builder.append(" mEmulated=").append(mEmulated); + builder.append(" mMtpReserveSpace=").append(mMtpReserveSpace); + builder.append(" mAllowMassStorage=").append(mAllowMassStorage); + builder.append(" mMaxFileSize=").append(mMaxFileSize); + builder.append(" mOwner=").append(mOwner); + builder.append("]"); + return builder.toString(); + } + + public static final Creator CREATOR = new Creator() { + @Override + public StorageVolume createFromParcel(Parcel in) { + return new StorageVolume(in); + } + + @Override + public StorageVolume[] newArray(int size) { + return new StorageVolume[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(mStorageId); + parcel.writeString(mPath.toString()); + parcel.writeInt(mDescriptionId); + parcel.writeInt(mPrimary ? 1 : 0); + parcel.writeInt(mRemovable ? 1 : 0); + parcel.writeInt(mEmulated ? 1 : 0); + parcel.writeInt(mMtpReserveSpace); + parcel.writeInt(mAllowMassStorage ? 1 : 0); + parcel.writeLong(mMaxFileSize); + parcel.writeParcelable(mOwner, flags); + } +} diff --git a/src/com/cyanogenmod/filemanager/model/Symlink.java b/Backbone/src/main/java/me/toolify/backbone/model/Symlink.java similarity index 98% rename from src/com/cyanogenmod/filemanager/model/Symlink.java rename to Backbone/src/main/java/me/toolify/backbone/model/Symlink.java index dc0018104..13672f0ee 100644 --- a/src/com/cyanogenmod/filemanager/model/Symlink.java +++ b/Backbone/src/main/java/me/toolify/backbone/model/Symlink.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.model; +package me.toolify.backbone.model; import java.util.Date; diff --git a/src/com/cyanogenmod/filemanager/model/SystemFile.java b/Backbone/src/main/java/me/toolify/backbone/model/SystemFile.java similarity index 97% rename from src/com/cyanogenmod/filemanager/model/SystemFile.java rename to Backbone/src/main/java/me/toolify/backbone/model/SystemFile.java index 7767452d3..1ab62a473 100644 --- a/src/com/cyanogenmod/filemanager/model/SystemFile.java +++ b/Backbone/src/main/java/me/toolify/backbone/model/SystemFile.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.model; +package me.toolify.backbone.model; import java.util.Date; diff --git a/src/com/cyanogenmod/filemanager/model/User.java b/Backbone/src/main/java/me/toolify/backbone/model/User.java similarity index 96% rename from src/com/cyanogenmod/filemanager/model/User.java rename to Backbone/src/main/java/me/toolify/backbone/model/User.java index 963bf27f1..890fcc39c 100644 --- a/src/com/cyanogenmod/filemanager/model/User.java +++ b/Backbone/src/main/java/me/toolify/backbone/model/User.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.model; +package me.toolify.backbone.model; /** * A class that represents a user of the operating system. diff --git a/src/com/cyanogenmod/filemanager/model/UserPermission.java b/Backbone/src/main/java/me/toolify/backbone/model/UserPermission.java similarity index 97% rename from src/com/cyanogenmod/filemanager/model/UserPermission.java rename to Backbone/src/main/java/me/toolify/backbone/model/UserPermission.java index ced812c9b..beb51e237 100644 --- a/src/com/cyanogenmod/filemanager/model/UserPermission.java +++ b/Backbone/src/main/java/me/toolify/backbone/model/UserPermission.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.model; +package me.toolify.backbone.model; /** * A class for represents a user permissions. @@ -77,6 +77,7 @@ public boolean isSetUID() { */ public void setSetUID(boolean setuid) { this.mSetuid = setuid; + invalidateRawString(); } /** @@ -124,7 +125,7 @@ public String toString() { * {@inheritDoc} */ @Override - public String toRawString() { + protected String getRawString() { StringBuilder p = new StringBuilder(); p.append(isRead() ? READ : UNASIGNED); p.append(isWrite() ? WRITE : UNASIGNED); diff --git a/src/com/cyanogenmod/filemanager/parcelables/HistoryNavigable.java b/Backbone/src/main/java/me/toolify/backbone/parcelables/HistoryNavigable.java similarity index 96% rename from src/com/cyanogenmod/filemanager/parcelables/HistoryNavigable.java rename to Backbone/src/main/java/me/toolify/backbone/parcelables/HistoryNavigable.java index 475e30d21..4607fd27e 100644 --- a/src/com/cyanogenmod/filemanager/parcelables/HistoryNavigable.java +++ b/Backbone/src/main/java/me/toolify/backbone/parcelables/HistoryNavigable.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.parcelables; +package me.toolify.backbone.parcelables; import android.os.Parcelable; diff --git a/src/com/cyanogenmod/filemanager/parcelables/NavigationInfoParcelable.java b/Backbone/src/main/java/me/toolify/backbone/parcelables/NavigationInfoParcelable.java similarity index 97% rename from src/com/cyanogenmod/filemanager/parcelables/NavigationInfoParcelable.java rename to Backbone/src/main/java/me/toolify/backbone/parcelables/NavigationInfoParcelable.java index af6ecd8f2..89f79adf0 100644 --- a/src/com/cyanogenmod/filemanager/parcelables/NavigationInfoParcelable.java +++ b/Backbone/src/main/java/me/toolify/backbone/parcelables/NavigationInfoParcelable.java @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.parcelables; +package me.toolify.backbone.parcelables; import android.os.Parcel; import android.os.Parcelable; -import com.cyanogenmod.filemanager.model.History; +import me.toolify.backbone.model.History; import java.util.ArrayList; import java.util.List; diff --git a/src/com/cyanogenmod/filemanager/parcelables/NavigationViewInfoParcelable.java b/Backbone/src/main/java/me/toolify/backbone/parcelables/NavigationViewInfoParcelable.java similarity index 79% rename from src/com/cyanogenmod/filemanager/parcelables/NavigationViewInfoParcelable.java rename to Backbone/src/main/java/me/toolify/backbone/parcelables/NavigationViewInfoParcelable.java index 21b721830..29b01e5aa 100644 --- a/src/com/cyanogenmod/filemanager/parcelables/NavigationViewInfoParcelable.java +++ b/Backbone/src/main/java/me/toolify/backbone/parcelables/NavigationViewInfoParcelable.java @@ -14,22 +14,22 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.parcelables; +package me.toolify.backbone.parcelables; import android.os.Parcel; import android.os.Parcelable; -import com.cyanogenmod.filemanager.FileManagerApplication; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.model.FileSystemObject; -import com.cyanogenmod.filemanager.util.FileHelper; +import me.toolify.backbone.FileManagerApplication; +import me.toolify.backbone.R; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.util.FileHelper; import java.io.File; import java.util.ArrayList; import java.util.List; /** - * A serializer/deserializer class for {@link "NavigationView"}. + * A serializer/deserializer class for {@link "NavigationFragment"}. */ public class NavigationViewInfoParcelable extends HistoryNavigable { @@ -40,6 +40,9 @@ public class NavigationViewInfoParcelable extends HistoryNavigable { private boolean mChRooted; private List mFiles; private List mSelectedFiles; + private Integer mScrollIndex; + private Integer mScrollIndexOffset; + /** * Constructor of NavigationViewInfoParcelable. @@ -167,6 +170,42 @@ public void setSelectedFiles(List selectedFiles) { this.mSelectedFiles = selectedFiles; } + /** + * Method that returns the index of the item at the top of the file list. + * + * @return Integer The current selected file list + */ + public Integer getScrollIndex() { + return this.mScrollIndex; + } + + /** + * Method that sets the index of the item at the top of the file list. + * + * @param mHistoryScroll The current selected file list + */ + public void setScrollIndex(Integer mHistoryScroll) { + this.mScrollIndex = mHistoryScroll; + } + + /** + * Method that returns the exact scroll offset within the item at the top of the file list. + * + * @return Integer The current selected file list + */ + public Integer getScrollIndexOffset() { + return this.mScrollIndexOffset; + } + + /** + * Method that sets the exact scroll offset within the item at the top of the file list. + * + * @param scrollIndexOffset The current selected file list + */ + public void setScrollIndexOffset(Integer scrollIndexOffset) { + this.mScrollIndexOffset = scrollIndexOffset; + } + /** * {@inheritDoc} */ @@ -199,6 +238,10 @@ public void writeToParcel(Parcel dest, int flags) { if (this.mFiles != null) { dest.writeList(this.mFiles); } + //- 5 + dest.writeInt(this.mScrollIndex == null? 0 : this.mScrollIndex); + //- 6 + dest.writeInt(this.mScrollIndexOffset == null? 0 : this.mScrollIndexOffset); } /** @@ -230,6 +273,10 @@ private void readFromParcel(Parcel in) { in.readList(files, NavigationViewInfoParcelable.class.getClassLoader()); this.mFiles = new ArrayList(files); } + //- 5 + this.mScrollIndex = in.readInt(); + //- 6 + this.mScrollIndexOffset = in.readInt(); } /** diff --git a/src/com/cyanogenmod/filemanager/parcelables/SearchInfoParcelable.java b/Backbone/src/main/java/me/toolify/backbone/parcelables/SearchInfoParcelable.java similarity index 96% rename from src/com/cyanogenmod/filemanager/parcelables/SearchInfoParcelable.java rename to Backbone/src/main/java/me/toolify/backbone/parcelables/SearchInfoParcelable.java index f9e264ba4..beb0ca5aa 100644 --- a/src/com/cyanogenmod/filemanager/parcelables/SearchInfoParcelable.java +++ b/Backbone/src/main/java/me/toolify/backbone/parcelables/SearchInfoParcelable.java @@ -14,15 +14,15 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.parcelables; +package me.toolify.backbone.parcelables; import android.os.Parcel; import android.os.Parcelable; -import com.cyanogenmod.filemanager.FileManagerApplication; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.model.Query; -import com.cyanogenmod.filemanager.model.SearchResult; +import me.toolify.backbone.FileManagerApplication; +import me.toolify.backbone.R; +import me.toolify.backbone.model.Query; +import me.toolify.backbone.model.SearchResult; import java.util.ArrayList; import java.util.List; diff --git a/src/com/cyanogenmod/filemanager/preferences/AccessMode.java b/Backbone/src/main/java/me/toolify/backbone/preferences/AccessMode.java similarity index 97% rename from src/com/cyanogenmod/filemanager/preferences/AccessMode.java rename to Backbone/src/main/java/me/toolify/backbone/preferences/AccessMode.java index 9f240f50f..d6c9cc934 100644 --- a/src/com/cyanogenmod/filemanager/preferences/AccessMode.java +++ b/Backbone/src/main/java/me/toolify/backbone/preferences/AccessMode.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.preferences; +package me.toolify.backbone.preferences; /** * An enumeration of the access modes. diff --git a/src/com/cyanogenmod/filemanager/preferences/Bookmarks.java b/Backbone/src/main/java/me/toolify/backbone/preferences/Bookmarks.java similarity index 98% rename from src/com/cyanogenmod/filemanager/preferences/Bookmarks.java rename to Backbone/src/main/java/me/toolify/backbone/preferences/Bookmarks.java index 998b71531..a6a91de1f 100644 --- a/src/com/cyanogenmod/filemanager/preferences/Bookmarks.java +++ b/Backbone/src/main/java/me/toolify/backbone/preferences/Bookmarks.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.preferences; +package me.toolify.backbone.preferences; import android.content.ContentResolver; import android.content.ContentUris; @@ -23,7 +23,7 @@ import android.database.Cursor; import android.net.Uri; -import com.cyanogenmod.filemanager.model.Bookmark; +import me.toolify.backbone.model.Bookmark; /** * A class for deal with user-defined bookmarks diff --git a/src/com/cyanogenmod/filemanager/preferences/BookmarksDatabaseHelper.java b/Backbone/src/main/java/me/toolify/backbone/preferences/BookmarksDatabaseHelper.java similarity index 97% rename from src/com/cyanogenmod/filemanager/preferences/BookmarksDatabaseHelper.java rename to Backbone/src/main/java/me/toolify/backbone/preferences/BookmarksDatabaseHelper.java index f5b3e465c..6500fa7cb 100644 --- a/src/com/cyanogenmod/filemanager/preferences/BookmarksDatabaseHelper.java +++ b/Backbone/src/main/java/me/toolify/backbone/preferences/BookmarksDatabaseHelper.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.preferences; +package me.toolify.backbone.preferences; import android.content.Context; import android.database.sqlite.SQLiteDatabase; diff --git a/Backbone/src/main/java/me/toolify/backbone/preferences/CompressionMode.java b/Backbone/src/main/java/me/toolify/backbone/preferences/CompressionMode.java new file mode 100644 index 000000000..e9e8b6166 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/preferences/CompressionMode.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.preferences; + +/** + * An enumeration of all implemented compression modes. + */ +public enum CompressionMode { + /** + * Archive using Tar algorithm + */ + A_TAR("tar", true, null), //$NON-NLS-1$ + /** + * Archive and compress using Gzip algorithm + */ + AC_GZIP("tar.gz", true, null), //$NON-NLS-1$ + /** + * Archive and compress using Gzip algorithm + */ + AC_GZIP2("tgz", true, null), //$NON-NLS-1$ + /** + * Archive and compress using Bzip algorithm + */ + AC_BZIP("tar.bz2", true, null), //$NON-NLS-1$ + /** + * Compress using Gzip algorithm + */ + C_GZIP("gz", false, null), //$NON-NLS-1$ + /** + * Compress using Bzip algorithm + */ + C_BZIP("bz2", false, null), //$NON-NLS-1$ + /** + * Archive using Zip algorithm + */ + A_ZIP("zip", true, "zip"); //$NON-NLS-1$ //$NON-NLS-2$ + + /** + * The file extension + */ + public final String mExtension; + /** + * If the file is an archive or archive-compressed (true) or a compressed file (false) + */ + public final boolean mArchive; + /** + * If the compress mode requires the present of an optional file (null == required) + */ + public final String mCommandId; + + /** + * Constructor of CompressionMode + * + * @param extension The output extension + * @param archive If the output is an archive or archive-compressed + */ + private CompressionMode(String extension, boolean archive, String commandId) { + this.mExtension = extension; + this.mArchive = archive; + this.mCommandId = commandId; + } +} diff --git a/src/com/cyanogenmod/filemanager/preferences/ConfigurationListener.java b/Backbone/src/main/java/me/toolify/backbone/preferences/ConfigurationListener.java similarity index 95% rename from src/com/cyanogenmod/filemanager/preferences/ConfigurationListener.java rename to Backbone/src/main/java/me/toolify/backbone/preferences/ConfigurationListener.java index a2e220897..c965f1a69 100644 --- a/src/com/cyanogenmod/filemanager/preferences/ConfigurationListener.java +++ b/Backbone/src/main/java/me/toolify/backbone/preferences/ConfigurationListener.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.preferences; +package me.toolify.backbone.preferences; /** * An interface that defines method to observe configuration changes. diff --git a/src/com/cyanogenmod/filemanager/preferences/DisplayRestrictions.java b/Backbone/src/main/java/me/toolify/backbone/preferences/DisplayRestrictions.java similarity index 89% rename from src/com/cyanogenmod/filemanager/preferences/DisplayRestrictions.java rename to Backbone/src/main/java/me/toolify/backbone/preferences/DisplayRestrictions.java index 2525ade4e..674230956 100644 --- a/src/com/cyanogenmod/filemanager/preferences/DisplayRestrictions.java +++ b/Backbone/src/main/java/me/toolify/backbone/preferences/DisplayRestrictions.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.preferences; +package me.toolify.backbone.preferences; /** * An enumeration of the restrictions that can be applied when displaying list of files. @@ -32,6 +32,10 @@ public enum DisplayRestrictions { * Restriction for display only files with a size lower than the specified */ SIZE_RESTRICTION, + /** + * Restriction for display only directories + */ + DIRECTORY_ONLY_RESTRICTION, /** * Restriction for display only files from the local file system. Avoid remote files. */ diff --git a/Backbone/src/main/java/me/toolify/backbone/preferences/FileManagerSettings.java b/Backbone/src/main/java/me/toolify/backbone/preferences/FileManagerSettings.java new file mode 100644 index 000000000..e2d53564c --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/preferences/FileManagerSettings.java @@ -0,0 +1,306 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.preferences; + +import me.toolify.backbone.util.FileHelper; + + +/** + * The enumeration of settings of FileManager application. + */ +public enum FileManagerSettings { + /** + * Whether is the first use of the application + * @hide + */ + SETTINGS_FIRST_USE("cm_filemanager_first_use", Boolean.TRUE), //$NON-NLS-1$ + + /** + * The access mode to use + * @hide + */ + SETTINGS_ACCESS_MODE("cm_filemanager_access_mode", AccessMode.SAFE), //$NON-NLS-1$ + + /** + * The initial directory to be used. + * @hide + */ + SETTINGS_INITIAL_DIR("cm_filemanager_initial_dir", FileHelper.ROOT_DIRECTORY), //$NON-NLS-1$ + + /** + * The view mode to use (simple, details, or icons). + * @hide + */ + SETTINGS_LAYOUT_MODE("cm_filemanager_layout_mode", NavigationLayoutMode.DETAILS), //$NON-NLS-1$ + /** + * The sort mode to use (name or data, ascending or descending). + * @hide + */ + SETTINGS_SORT_MODE("cm_filemanager_sort_mode", NavigationSortMode.NAME_ASC), //$NON-NLS-1$ + + /** + * When to sort the directories before the files. + * @hide + */ + SETTINGS_SHOW_DIRS_FIRST("cm_filemanager_show_dirs_first", Boolean.TRUE), //$NON-NLS-1$ + /** + * When to show the hidden files. + * @hide + */ + SETTINGS_SHOW_HIDDEN("cm_filemanager_show_hidden", Boolean.TRUE), //$NON-NLS-1$ + /** + * When to show the system files. + * @hide + */ + SETTINGS_SHOW_SYSTEM("cm_filemanager_show_system", Boolean.TRUE), //$NON-NLS-1$ + /** + * When to show the symlinks files. + * @hide + */ + SETTINGS_SHOW_SYMLINKS("cm_filemanager_show_symlinks", Boolean.TRUE), //$NON-NLS-1$ + + /** + * When to use case sensitive comparison in sorting of files + * @hide + */ + SETTINGS_CASE_SENSITIVE_SORT("cm_filemanager_case_sensitive_sort", Boolean.FALSE), //$NON-NLS-1$ + /** + * Defines the filetime format mode to use + * @hide + */ + SETTINGS_FILETIME_FORMAT_MODE( + "cm_filemanager_filetime_format_mode", FileTimeFormatMode.LOCALE), //$NON-NLS-1$ + /** + * When display a warning in free disk widget + * @hide + */ + SETTINGS_DISK_USAGE_WARNING_LEVEL( + "cm_filemanager_disk_usage_warning_level", //$NON-NLS-1$ + new String("95")), //$NON-NLS-1$ + /** + * When to compute folder statistics in folder properties dialog + * @hide + */ + SETTINGS_COMPUTE_FOLDER_STATISTICS( + "cm_filemanager_compute_folder_statistics", Boolean.FALSE), //$NON-NLS-1$ + /** + * When to display thumbs of pictures, videos, ... + * @hide + */ + SETTINGS_DISPLAY_THUMBS( + "cm_filemanager_show_thumbs", Boolean.TRUE), //$NON-NLS-1$ + /** + * Whether use flinger to remove items + * @hide + */ + SETTINGS_USE_FLINGER("cm_filemanager_use_flinger", Boolean.FALSE), //$NON-NLS-1$ + + + /** + * When to highlight the terms of the search in the search results + * @hide + */ + SETTINGS_HIGHLIGHT_TERMS("cm_filemanager_highlight_terms", Boolean.TRUE), //$NON-NLS-1$ + /** + * When to show the relevance widget on searches + * @hide + */ + SETTINGS_SHOW_RELEVANCE_WIDGET( + "cm_filemanager_show_relevance_widget", //$NON-NLS-1$ + Boolean.TRUE), + /** + * How to sort the search results + * @hide + */ + SETTINGS_SORT_SEARCH_RESULTS_MODE( + "cm_filemanager_sort_search_results_mode", //$NON-NLS-1$ + SearchSortResultMode.RELEVANCE), + /** + * When to save the search terms + * @hide + */ + SETTINGS_SAVE_SEARCH_TERMS("cm_filemanager_save_search_terms", Boolean.TRUE), //$NON-NLS-1$ + + /** + * When to show debug traces + * @hide + */ + SETTINGS_SHOW_TRACES("cm_filemanager_show_debug_traces", Boolean.FALSE), //$NON-NLS-1$ + + /** + * When to editor should display suggestions + * @hide + */ + SETTINGS_EDITOR_NO_SUGGESTIONS( + "cm_filemanager_editor_no_suggestions", Boolean.FALSE), //$NON-NLS-1$ + + /** + * When to editor should use word wrap + * @hide + */ + SETTINGS_EDITOR_WORD_WRAP("cm_filemanager_editor_word_wrap", Boolean.TRUE), //$NON-NLS-1$ + + /** + * When to editor should open a binary file in a hex viewer + * @hide + */ + SETTINGS_EDITOR_HEXDUMP("cm_filemanager_editor_hexdump", Boolean.TRUE), //$NON-NLS-1$ + + /** + * When to editor should use the syntax highlight + * @hide + */ + SETTINGS_EDITOR_SYNTAX_HIGHLIGHT( + "cm_filemanager_editor_syntax_highlight", Boolean.TRUE), //$NON-NLS-1$ + + /** + * When to editor should use the default color scheme of the theme for syntax highlight + * @hide + */ + SETTINGS_EDITOR_SH_USE_THEME_DEFAULT( + "cm_filemanager_editor_sh_use_theme_default", Boolean.TRUE), //$NON-NLS-1$ + + /** + * When to editor should use the default color scheme of the theme for syntax highlight + * @hide + */ + SETTINGS_EDITOR_SH_COLOR_SCHEME( + "cm_filemanager_editor_sh_color_scheme", ""), //$NON-NLS-1$ //$NON-NLS-2$ + + /** + * The current theme to use in the app + * @hide + */ + SETTINGS_THEME("cm_filemanager_theme", //$NON-NLS-1$ + "me.toolify.backbone:light"); //$NON-NLS-1$ + + /** + * A broadcast intent that is sent when a setting was changed + */ + public final static String INTENT_SETTING_CHANGED = + "me.toolify.backbone.INTENT_SETTING_CHANGED"; //$NON-NLS-1$ + + /** + * A broadcast intent that is sent when a theme was changed + */ + public final static String INTENT_THEME_CHANGED = + "me.toolify.backbone.INTENT_THEME_CHANGED"; //$NON-NLS-1$ + + /** + * A broadcast intent that is sent when a file was changed + */ + public final static String INTENT_FILE_CHANGED = + "me.toolify.backbone.INTENT_FILE_CHANGED"; //$NON-NLS-1$ + + /** + * The extra key with the preference key that was changed + */ + public final static String EXTRA_SETTING_CHANGED_KEY = "preference"; //$NON-NLS-1$ + + /** + * The extra key with the file key that was changed + */ + public final static String EXTRA_FILE_CHANGED_KEY = "file"; //$NON-NLS-1$ + + /** + * The extra key with the file key that was changed + */ + public final static String EXTRA_THEME_PACKAGE = "package"; //$NON-NLS-1$ + + /** + * The extra key with the identifier of theme that was changed + */ + public final static String EXTRA_THEME_ID = "id"; //$NON-NLS-1$ + + + + + private final String mId; + private final Object mDefaultValue; + + /** + * Constructor of FileManagerSettings. + * + * @param id The unique identifier of the setting + * @param defaultValue The default value of the setting + */ + private FileManagerSettings(String id, Object defaultValue) { + this.mId = id; + this.mDefaultValue = defaultValue; + } + + /** + * Method that returns the unique identifier of the setting. + * @return the mId + */ + public String getId() { + return this.mId; + } + + /** + * Method that returns the default value of the setting. + * + * @return Object The default value of the setting + */ + public Object getDefaultValue() { + return this.mDefaultValue; + } + + /** + * Method that returns an instance of {@link FileManagerSettings} from its. + * unique identifier + * + * @param id The unique identifier + * @return FileManagerSettings The navigation sort mode + */ + public static FileManagerSettings fromId(String id) { + FileManagerSettings[] values = values(); + int cc = values.length; + for (int i = 0; i < cc; i++) { + if (values[i].mId == id) { + return values[i]; + } + } + return null; + } + + /** + * Convenience method for changing sorting mode + * @param sort Setting to use for sorting + */ + public static void setSorting(NavigationSortMode sort) { + Preferences.getSharedPreferences().edit().putInt( + FileManagerSettings.SETTINGS_SORT_MODE.getId(), + sort.getId()).apply(); + } + + public static void setLayout(NavigationLayoutMode layout) { + Preferences.getSharedPreferences().edit().putInt( + FileManagerSettings.SETTINGS_LAYOUT_MODE.getId(), + layout.getId()).apply(); + } + + /** + * Convenience method for toggling a checkable view preference + * @param key The setting to toggle + */ + public static void toggleViewPreference(FileManagerSettings key) { + Preferences.getSharedPreferences().edit().putBoolean(key.getId(), + !Preferences.getSharedPreferences().getBoolean(key.getId(), + (Boolean.parseBoolean(key.getDefaultValue().toString())))).apply(); + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/preferences/FileTimeFormatMode.java b/Backbone/src/main/java/me/toolify/backbone/preferences/FileTimeFormatMode.java new file mode 100644 index 000000000..ce57c9f6e --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/preferences/FileTimeFormatMode.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.preferences; + +/** + * An enumeration of the search result sort modes. + */ +public enum FileTimeFormatMode implements ObjectStringIdentifier { + + /** + * System-defined. + */ + SYSTEM("0", null), //$NON-NLS-1$ + /** + * Locale dependent + */ + LOCALE("1", null), //$NON-NLS-1$ + /** + * dd/MM/yyyy HH:mm:ss + */ + DDMMYYYY_HHMMSS("2", "dd/MM/yyyy HH:mm:ss"), //$NON-NLS-1$ //$NON-NLS-2$ + /** + * MM/dd/yyyy HH:mm:ss + */ + MMDDYYYY_HHMMSS("3", "MM/dd/yyyy HH:mm:ss"), //$NON-NLS-1$ //$NON-NLS-2$ + /** + * yyyy-MM-dd HH:mm:ss + */ + YYYYMMDD_HHMMSS("4", "yyyy-MM-dd HH:mm:ss"); //$NON-NLS-1$ //$NON-NLS-2$ + + private String mId; + private String mFormat; + + /** + * Constructor of FileTimeFormatMode. + * + * @param id The unique identifier of the enumeration + * @param format The format (if apply) + */ + private FileTimeFormatMode(String id, String format) { + this.mId = id; + this.mFormat = format; + } + + /** + * {@inheritDoc} + */ + @Override + public String getId() { + return this.mId; + } + + /** + * Method that returns the format of the filetime. + * + * @return String The format of the filetime. + */ + public String getFormat() { + return this.mFormat; + } + + /** + * Method that returns an instance of {@link FileTimeFormatMode} from its + * unique identifier. + * + * @param id The unique identifier + * @return FileTimeFormatMode The filetime format mode + */ + public static FileTimeFormatMode fromId(String id) { + FileTimeFormatMode[] values = values(); + int cc = values.length; + for (int i = 0; i < cc; i++) { + if (values[i].mId.compareTo(id) == 0) { + return values[i]; + } + } + return null; + } + +} diff --git a/src/com/cyanogenmod/filemanager/preferences/NavigationLayoutMode.java b/Backbone/src/main/java/me/toolify/backbone/preferences/NavigationLayoutMode.java similarity index 97% rename from src/com/cyanogenmod/filemanager/preferences/NavigationLayoutMode.java rename to Backbone/src/main/java/me/toolify/backbone/preferences/NavigationLayoutMode.java index eb572db79..bef882297 100644 --- a/src/com/cyanogenmod/filemanager/preferences/NavigationLayoutMode.java +++ b/Backbone/src/main/java/me/toolify/backbone/preferences/NavigationLayoutMode.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.preferences; +package me.toolify.backbone.preferences; /** * An enumeration of the view layout modes. diff --git a/src/com/cyanogenmod/filemanager/preferences/NavigationSortMode.java b/Backbone/src/main/java/me/toolify/backbone/preferences/NavigationSortMode.java similarity index 97% rename from src/com/cyanogenmod/filemanager/preferences/NavigationSortMode.java rename to Backbone/src/main/java/me/toolify/backbone/preferences/NavigationSortMode.java index 41e977914..6ffda01de 100644 --- a/src/com/cyanogenmod/filemanager/preferences/NavigationSortMode.java +++ b/Backbone/src/main/java/me/toolify/backbone/preferences/NavigationSortMode.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.preferences; +package me.toolify.backbone.preferences; /** * An enumeration of the navigation sort modes. diff --git a/src/com/cyanogenmod/filemanager/preferences/ObjectIdentifier.java b/Backbone/src/main/java/me/toolify/backbone/preferences/ObjectIdentifier.java similarity index 94% rename from src/com/cyanogenmod/filemanager/preferences/ObjectIdentifier.java rename to Backbone/src/main/java/me/toolify/backbone/preferences/ObjectIdentifier.java index c903e1227..d9ec94538 100644 --- a/src/com/cyanogenmod/filemanager/preferences/ObjectIdentifier.java +++ b/Backbone/src/main/java/me/toolify/backbone/preferences/ObjectIdentifier.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.preferences; +package me.toolify.backbone.preferences; /** * An interface that defines an object with an identifier. diff --git a/src/com/cyanogenmod/filemanager/preferences/ObjectStringIdentifier.java b/Backbone/src/main/java/me/toolify/backbone/preferences/ObjectStringIdentifier.java similarity index 94% rename from src/com/cyanogenmod/filemanager/preferences/ObjectStringIdentifier.java rename to Backbone/src/main/java/me/toolify/backbone/preferences/ObjectStringIdentifier.java index e24fb4064..468db808b 100644 --- a/src/com/cyanogenmod/filemanager/preferences/ObjectStringIdentifier.java +++ b/Backbone/src/main/java/me/toolify/backbone/preferences/ObjectStringIdentifier.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.preferences; +package me.toolify.backbone.preferences; /** * An interface that defines an object with an identifier. diff --git a/src/com/cyanogenmod/filemanager/preferences/Preferences.java b/Backbone/src/main/java/me/toolify/backbone/preferences/Preferences.java similarity index 95% rename from src/com/cyanogenmod/filemanager/preferences/Preferences.java rename to Backbone/src/main/java/me/toolify/backbone/preferences/Preferences.java index bbd218ae5..0e40e6ea7 100644 --- a/src/com/cyanogenmod/filemanager/preferences/Preferences.java +++ b/Backbone/src/main/java/me/toolify/backbone/preferences/Preferences.java @@ -14,14 +14,14 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.preferences; +package me.toolify.backbone.preferences; import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.util.Log; -import com.cyanogenmod.filemanager.FileManagerApplication; +import me.toolify.backbone.FileManagerApplication; import java.io.InvalidClassException; import java.util.ArrayList; @@ -30,6 +30,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; /** * A helper class for access and manage the preferences of the application. @@ -47,7 +48,7 @@ public final class Preferences { * The name of the file manager settings file. * @hide */ - public static final String SETTINGS_FILENAME = "com.cyanogenmod.filemanager"; //$NON-NLS-1$ + public static final String SETTINGS_FILENAME = "me.toolify.backbone"; //$NON-NLS-1$ /** * The list of configuration listeners. @@ -168,6 +169,7 @@ public static void savePreferences(Map prefs, boole * @throws InvalidClassException If the value of a preference is not of the * type of the preference */ + @SuppressWarnings("unchecked") private static void savePreferences( Map prefs, boolean noSaveIfExists, boolean applied) throws InvalidClassException { @@ -190,6 +192,8 @@ private static void savePreferences( editor.putBoolean(pref.getId(), ((Boolean)value).booleanValue()); } else if (value instanceof String && pref.getDefaultValue() instanceof String) { editor.putString(pref.getId(), (String)value); + } else if (value instanceof Set && pref.getDefaultValue() instanceof Set) { + editor.putStringSet(pref.getId(), (Set)value); } else if (value instanceof ObjectIdentifier && pref.getDefaultValue() instanceof ObjectIdentifier) { editor.putInt(pref.getId(), ((ObjectIdentifier)value).getId()); diff --git a/src/com/cyanogenmod/filemanager/preferences/SearchSortResultMode.java b/Backbone/src/main/java/me/toolify/backbone/preferences/SearchSortResultMode.java similarity index 97% rename from src/com/cyanogenmod/filemanager/preferences/SearchSortResultMode.java rename to Backbone/src/main/java/me/toolify/backbone/preferences/SearchSortResultMode.java index 0bf6b4282..43a37a9bf 100644 --- a/src/com/cyanogenmod/filemanager/preferences/SearchSortResultMode.java +++ b/Backbone/src/main/java/me/toolify/backbone/preferences/SearchSortResultMode.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.preferences; +package me.toolify.backbone.preferences; /** * An enumeration of the search result sort modes. diff --git a/src/com/cyanogenmod/filemanager/preferences/UncompressionMode.java b/Backbone/src/main/java/me/toolify/backbone/preferences/UncompressionMode.java similarity index 93% rename from src/com/cyanogenmod/filemanager/preferences/UncompressionMode.java rename to Backbone/src/main/java/me/toolify/backbone/preferences/UncompressionMode.java index 94be8c7e5..e39b51f3c 100644 --- a/src/com/cyanogenmod/filemanager/preferences/UncompressionMode.java +++ b/Backbone/src/main/java/me/toolify/backbone/preferences/UncompressionMode.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.preferences; +package me.toolify.backbone.preferences; /** * An enumeration of all implemented uncompression modes. @@ -63,7 +63,11 @@ public enum UncompressionMode { /** * Uncompress using Unix compress algorithm */ - C_UNXZ("xz", false); //$NON-NLS-1$ + C_UNXZ("xz", false), //$NON-NLS-1$ + /** + * Uncompress using Rar algorithm + */ + C_UNRAR("rar", true); //$NON-NLS-1$ /** * The file extension diff --git a/src/com/cyanogenmod/filemanager/providers/BookmarksContentProvider.java b/Backbone/src/main/java/me/toolify/backbone/providers/BookmarksContentProvider.java similarity index 96% rename from src/com/cyanogenmod/filemanager/providers/BookmarksContentProvider.java rename to Backbone/src/main/java/me/toolify/backbone/providers/BookmarksContentProvider.java index 254ebde9a..8eb12f528 100644 --- a/src/com/cyanogenmod/filemanager/providers/BookmarksContentProvider.java +++ b/Backbone/src/main/java/me/toolify/backbone/providers/BookmarksContentProvider.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.providers; +package me.toolify.backbone.providers; import android.content.ContentProvider; import android.content.ContentUris; @@ -28,8 +28,9 @@ import android.text.TextUtils; import android.util.Log; -import com.cyanogenmod.filemanager.model.Bookmark; -import com.cyanogenmod.filemanager.preferences.BookmarksDatabaseHelper; +import me.toolify.backbone.BuildConfig; +import me.toolify.backbone.model.Bookmark; +import me.toolify.backbone.preferences.BookmarksDatabaseHelper; /** * A content provider for manage the user-defined bookmarks @@ -48,8 +49,7 @@ public class BookmarksContentProvider extends ContentProvider { /** * The authority string name. */ - public static final String AUTHORITY = - "com.cyanogenmod.filemanager.providers.bookmarks"; //$NON-NLS-1$ + public static final String AUTHORITY = BuildConfig.BOOKMARKS_PROVIDER_AUTHORITY; private static final UriMatcher sURLMatcher = new UriMatcher(UriMatcher.NO_MATCH); diff --git a/src/com/cyanogenmod/filemanager/providers/RecentSearchesContentProvider.java b/Backbone/src/main/java/me/toolify/backbone/providers/RecentSearchesContentProvider.java similarity index 87% rename from src/com/cyanogenmod/filemanager/providers/RecentSearchesContentProvider.java rename to Backbone/src/main/java/me/toolify/backbone/providers/RecentSearchesContentProvider.java index 6a3e7d088..f0ca5052a 100644 --- a/src/com/cyanogenmod/filemanager/providers/RecentSearchesContentProvider.java +++ b/Backbone/src/main/java/me/toolify/backbone/providers/RecentSearchesContentProvider.java @@ -14,10 +14,12 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.providers; +package me.toolify.backbone.providers; import android.content.SearchRecentSuggestionsProvider; +import me.toolify.backbone.BuildConfig; + /** * A content provider for manage the user search history. */ @@ -26,8 +28,7 @@ public class RecentSearchesContentProvider extends SearchRecentSuggestionsProvid /** * The authority string name. */ - public static final String AUTHORITY = - "com.cyanogenmod.filemanager.providers.recentsearches"; //$NON-NLS-1$ + public static final String AUTHORITY = BuildConfig.SEARCHES_PROVIDER_AUTHORITY; /** * The provider mode. diff --git a/Backbone/src/main/java/me/toolify/backbone/tasks/FilesystemAsyncTask.java b/Backbone/src/main/java/me/toolify/backbone/tasks/FilesystemAsyncTask.java new file mode 100644 index 000000000..297bce2ab --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/tasks/FilesystemAsyncTask.java @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.tasks; + +import android.content.Context; +import android.os.AsyncTask; +import android.util.Log; + +import de.greenrobot.event.EventBus; +import me.toolify.backbone.bus.events.FilesystemStatusUpdateEvent; +import me.toolify.backbone.model.DiskUsage; +import me.toolify.backbone.model.MountPoint; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.ui.ThemeManager.Theme; +import me.toolify.backbone.ui.widgets.BreadcrumbSpinner; +import me.toolify.backbone.util.MountPointHelper; + +/** + * A class for recovery information about filesystem status (mount point, disk usage, ...). + */ +public class FilesystemAsyncTask extends AsyncTask { + + private static final String TAG = "FilesystemAsyncTask"; //$NON-NLS-1$ + + /** + * @hide + */ + final Context mContext; + /** + * @hide + */ + final BreadcrumbSpinner mBreadcrumbSpinner; + /** + * @hide + */ + final int mFreeDiskSpaceWarningLevel; + private boolean mRunning; + + /** + * @hide + */ + static int sColorFilterNormal; + + /** + * Constructor of FilesystemAsyncTask. + * + * @param context The current context + * @param breadcrumbSpinner The breadcrumbSpinner calling this task + * @param freeDiskSpaceWarningLevel The free disk space warning level + */ + public FilesystemAsyncTask( + Context context, BreadcrumbSpinner breadcrumbSpinner, int freeDiskSpaceWarningLevel) { + super(); + this.mContext = context; + this.mBreadcrumbSpinner = breadcrumbSpinner; + this.mFreeDiskSpaceWarningLevel = freeDiskSpaceWarningLevel; + this.mRunning = false; + } + + + + /** + * Method that returns if there is a task running. + * + * @return boolean If there is a task running + */ + public boolean isRunning() { + return this.mRunning; + } + + /** + * {@inheritDoc} + */ + @Override + protected Boolean doInBackground(String... params) { + //Running + this.mRunning = true; + + //Extract the directory from arguments + String dir = params[0]; + + //Extract filesystem mount point from directory + if (isCancelled()) { + return Boolean.TRUE; + } + MountPoint mp1 = MountPointHelper.getMountPointFromDirectory(dir); + if(mp1.getMountPoint().equals("/") && dir.equals("/storage/emulated/0")) // AOSP 4.3 bug + mp1 = MountPointHelper.getMountPointFromDirectory("/mnt/shell/emulated"); + final MountPoint mp = mp1; + if (mp == null) { + //There is no information about + if (isCancelled()) { + return Boolean.TRUE; + } + this.mBreadcrumbSpinner.post(new Runnable() { + @Override + public void run() { + EventBus.getDefault().post(new FilesystemStatusUpdateEvent( + FilesystemStatusUpdateEvent.INDICATOR_WARNING)); + FilesystemAsyncTask.this.mBreadcrumbSpinner.setMountPointInfo(null); + } + }); + } else { + //Set image icon an save the mount point info + if (isCancelled()) { + return Boolean.TRUE; + } + this.mBreadcrumbSpinner.post(new Runnable() { + @Override + public void run() { + int eventType = + MountPointHelper.isReadOnly(mp) + ? FilesystemStatusUpdateEvent.INDICATOR_LOCKED + : FilesystemStatusUpdateEvent.INDICATOR_UNLOCKED; + EventBus.getDefault().post(new FilesystemStatusUpdateEvent(eventType)); + FilesystemAsyncTask.this.mBreadcrumbSpinner.setMountPointInfo(mp); + } + }); + + //Load information about disk usage + if (isCancelled()) { + return Boolean.TRUE; + } + this.mBreadcrumbSpinner.post(new Runnable() { + @Override + public void run() { + DiskUsage du = null; + try { + du = MountPointHelper.getMountPointDiskUsage(mp); + } catch (Exception e) { + Log.e(TAG, "Failed to retrieve disk usage information", e); //$NON-NLS-1$ + du = new DiskUsage( + mp.getMountPoint(), 0, 0, 0); + } + int usage = 0; + if (du != null && du.getTotal() != 0) { + usage = (int) (du.getUsed() * 100 / du.getTotal()); + //FilesystemAsyncTask.this.fileSystemInfo.setProgress(usage); ** CM progress bar removed + FilesystemAsyncTask.this.mBreadcrumbSpinner.setDiskUsageInfo(du); + } else { + usage = du == null ? 0 : 100; + //FilesystemAsyncTask.this.fileSystemInfo.setProgress(usage); ** CM progress bar removed + FilesystemAsyncTask.this.mBreadcrumbSpinner.setDiskUsageInfo(null); + } + + //TODO point this at another view in the action bar, now that diskusage is gone + // Advise about diskusage (>=mFreeDiskSpaceWarningLevel) with other color + Theme theme = ThemeManager.getCurrentTheme(FilesystemAsyncTask.this.mContext); + int filter = + usage >= FilesystemAsyncTask.this.mFreeDiskSpaceWarningLevel ? + theme.getColor( + FilesystemAsyncTask.this.mContext, + "disk_usage_filter_warning_color") : //$NON-NLS-1$ + theme.getColor( + FilesystemAsyncTask.this.mContext, + "disk_usage_filter_normal_color"); //$NON-NLS-1$ +/* FilesystemAsyncTask.this.fileSystemInfo. + getProgressDrawable().setColorFilter( + new PorterDuffColorFilter(filter, Mode.MULTIPLY)); ** CM progress bar removed */ + } + }); + } + return Boolean.TRUE; + } + + /** + * {@inheritDoc} + */ + @Override + protected void onPostExecute(Boolean result) { + this.mRunning = false; + } + + /** + * {@inheritDoc} + */ + @Override + protected void onCancelled(Boolean result) { + this.mRunning = false; + super.onCancelled(result); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onCancelled() { + this.mRunning = false; + super.onCancelled(); + } + +} diff --git a/Backbone/src/main/java/me/toolify/backbone/tasks/ImageAsyncTask.java b/Backbone/src/main/java/me/toolify/backbone/tasks/ImageAsyncTask.java new file mode 100644 index 000000000..64642e67f --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/tasks/ImageAsyncTask.java @@ -0,0 +1,692 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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 me.toolify.backbone.tasks; + +import android.annotation.TargetApi; +import android.os.Handler; +import android.os.Message; +import android.os.Process; + +import java.util.ArrayDeque; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.FutureTask; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * ************************************* + * Copied from JB release framework: + * https://android.googlesource.com/platform/frameworks/base/+/jb-release/core/java/android/os/ImageAsyncTask.java + * + * so that threading behavior on all OS versions is the same and we can tweak behavior by using + * executeOnExecutor() if needed. + * + * There are 3 changes in this copy of ImageAsyncTask: + * -pre-HC a single thread executor is used for serial operation + * (Executors.newSingleThreadExecutor) and is the default + * -the default THREAD_POOL_EXECUTOR was changed to use DiscardOldestPolicy + * -a new fixed thread pool called DUAL_THREAD_EXECUTOR was added + * ************************************* + * + *

ImageAsyncTask enables proper and easy use of the UI thread. This class allows to + * perform background operations and publish results on the UI thread without + * having to manipulate threads and/or handlers.

+ * + *

ImageAsyncTask is designed to be a helper class around {@link Thread} and {@link android.os.Handler} + * and does not constitute a generic threading framework. AsyncTasks should ideally be + * used for short operations (a few seconds at the most.) If you need to keep threads + * running for long periods of time, it is highly recommended you use the various APIs + * provided by the java.util.concurrent pacakge such as {@link java.util.concurrent.Executor}, + * {@link java.util.concurrent.ThreadPoolExecutor} and {@link java.util.concurrent.FutureTask}.

+ * + *

An asynchronous task is defined by a computation that runs on a background thread and + * whose result is published on the UI thread. An asynchronous task is defined by 3 generic + * types, called Params, Progress and Result, + * and 4 steps, called onPreExecute, doInBackground, + * onProgressUpdate and onPostExecute.

+ * + *
+ *

Developer Guides

+ *

For more information about using tasks and threads, read the + * Processes and + * Threads developer guide.

+ *
+ * + *

Usage

+ *

ImageAsyncTask must be subclassed to be used. The subclass will override at least + * one method ({@link #doInBackground}), and most often will override a + * second one ({@link #onPostExecute}.)

+ * + *

Here is an example of subclassing:

+ *
+ * private class DownloadFilesTask extends ImageAsyncTask<URL, Integer, Long> {
+ *     protected Long doInBackground(URL... urls) {
+ *         int count = urls.length;
+ *         long totalSize = 0;
+ *         for (int i = 0; i < count; i++) {
+ *             totalSize += Downloader.downloadFile(urls[i]);
+ *             publishProgress((int) ((i / (float) count) * 100));
+ *             // Escape early if cancel() is called
+ *             if (isCancelled()) break;
+ *         }
+ *         return totalSize;
+ *     }
+ *
+ *     protected void onProgressUpdate(Integer... progress) {
+ *         setProgressPercent(progress[0]);
+ *     }
+ *
+ *     protected void onPostExecute(Long result) {
+ *         showDialog("Downloaded " + result + " bytes");
+ *     }
+ * }
+ * 
+ * + *

Once created, a task is executed very simply:

+ *
+ * new DownloadFilesTask().execute(url1, url2, url3);
+ * 
+ * + *

ImageAsyncTask's generic types

+ *

The three types used by an asynchronous task are the following:

+ *
    + *
  1. Params, the type of the parameters sent to the task upon + * execution.
  2. + *
  3. Progress, the type of the progress units published during + * the background computation.
  4. + *
  5. Result, the type of the result of the background + * computation.
  6. + *
+ *

Not all types are always used by an asynchronous task. To mark a type as unused, + * simply use the type {@link Void}:

+ *
+ * private class MyTask extends ImageAsyncTask<Void, Void, Void> { ... }
+ * 
+ * + *

The 4 steps

+ *

When an asynchronous task is executed, the task goes through 4 steps:

+ *
    + *
  1. {@link #onPreExecute()}, invoked on the UI thread immediately after the task + * is executed. This step is normally used to setup the task, for instance by + * showing a progress bar in the user interface.
  2. + *
  3. {@link #doInBackground}, invoked on the background thread + * immediately after {@link #onPreExecute()} finishes executing. This step is used + * to perform background computation that can take a long time. The parameters + * of the asynchronous task are passed to this step. The result of the computation must + * be returned by this step and will be passed back to the last step. This step + * can also use {@link #publishProgress} to publish one or more units + * of progress. These values are published on the UI thread, in the + * {@link #onProgressUpdate} step.
  4. + *
  5. {@link #onProgressUpdate}, invoked on the UI thread after a + * call to {@link #publishProgress}. The timing of the execution is + * undefined. This method is used to display any form of progress in the user + * interface while the background computation is still executing. For instance, + * it can be used to animate a progress bar or show logs in a text field.
  6. + *
  7. {@link #onPostExecute}, invoked on the UI thread after the background + * computation finishes. The result of the background computation is passed to + * this step as a parameter.
  8. + *
+ * + *

Cancelling a task

+ *

A task can be cancelled at any time by invoking {@link #cancel(boolean)}. Invoking + * this method will cause subsequent calls to {@link #isCancelled()} to return true. + * After invoking this method, {@link #onCancelled(Object)}, instead of + * {@link #onPostExecute(Object)} will be invoked after {@link #doInBackground(Object[])} + * returns. To ensure that a task is cancelled as quickly as possible, you should always + * check the return value of {@link #isCancelled()} periodically from + * {@link #doInBackground(Object[])}, if possible (inside a loop for instance.)

+ * + *

Threading rules

+ *

There are a few threading rules that must be followed for this class to + * work properly:

+ *
    + *
  • The ImageAsyncTask class must be loaded on the UI thread. This is done + * automatically as of {@link android.os.Build.VERSION_CODES#JELLY_BEAN}.
  • + *
  • The task instance must be created on the UI thread.
  • + *
  • {@link #execute} must be invoked on the UI thread.
  • + *
  • Do not call {@link #onPreExecute()}, {@link #onPostExecute}, + * {@link #doInBackground}, {@link #onProgressUpdate} manually.
  • + *
  • The task can be executed only once (an exception will be thrown if + * a second execution is attempted.)
  • + *
+ * + *

Memory observability

+ *

ImageAsyncTask guarantees that all callback calls are synchronized in such a way that the following + * operations are safe without explicit synchronizations.

+ *
    + *
  • Set member fields in the constructor or {@link #onPreExecute}, and refer to them + * in {@link #doInBackground}. + *
  • Set member fields in {@link #doInBackground}, and refer to them in + * {@link #onProgressUpdate} and {@link #onPostExecute}. + *
+ * + *

Order of execution

+ *

When first introduced, AsyncTasks were executed serially on a single background + * thread. Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed + * to a pool of threads allowing multiple tasks to operate in parallel. Starting with + * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are executed on a single + * thread to avoid common application errors caused by parallel execution.

+ *

If you truly want parallel execution, you can invoke + * {@link #executeOnExecutor(java.util.concurrent.Executor, Object[])} with + * {@link #THREAD_POOL_EXECUTOR}.

+ */ +public abstract class ImageAsyncTask { + private static final String LOG_TAG = "ImageAsyncTask"; + + private static final int CORE_POOL_SIZE = 5; + private static final int MAXIMUM_POOL_SIZE = 128; + private static final int KEEP_ALIVE = 1; + + private static final ThreadFactory sThreadFactory = new ThreadFactory() { + private final AtomicInteger mCount = new AtomicInteger(1); + + public Thread newThread(Runnable r) { + return new Thread(r, "ImageAsyncTask #" + mCount.getAndIncrement()); + } + }; + + private static final BlockingQueue sPoolWorkQueue = + new LinkedBlockingQueue(10); + + /** + * An {@link java.util.concurrent.Executor} that can be used to execute tasks in parallel. + */ + public static final Executor THREAD_POOL_EXECUTOR + = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, + TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory, + new ThreadPoolExecutor.DiscardOldestPolicy()); + + /** + * An {@link java.util.concurrent.Executor} that executes tasks one at a time in serial + * order. This serialization is global to a particular process. + */ + public static final Executor SERIAL_EXECUTOR = new SerialExecutor(); + + public static final Executor DUAL_THREAD_EXECUTOR = + Executors.newFixedThreadPool(2, sThreadFactory); + + private static final int MESSAGE_POST_RESULT = 0x1; + private static final int MESSAGE_POST_PROGRESS = 0x2; + + private static final InternalHandler sHandler = new InternalHandler(); + + private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR; + private final WorkerRunnable mWorker; + private final FutureTask mFuture; + + private volatile Status mStatus = Status.PENDING; + + private final AtomicBoolean mCancelled = new AtomicBoolean(); + private final AtomicBoolean mTaskInvoked = new AtomicBoolean(); + + @TargetApi(11) + private static class SerialExecutor implements Executor { + final ArrayDeque mTasks = new ArrayDeque(); + Runnable mActive; + + public synchronized void execute(final Runnable r) { + mTasks.offer(new Runnable() { + public void run() { + try { + r.run(); + } finally { + scheduleNext(); + } + } + }); + if (mActive == null) { + scheduleNext(); + } + } + + protected synchronized void scheduleNext() { + if ((mActive = mTasks.poll()) != null) { + THREAD_POOL_EXECUTOR.execute(mActive); + } + } + } + + /** + * Indicates the current status of the task. Each status will be set only once + * during the lifetime of a task. + */ + public enum Status { + /** + * Indicates that the task has not been executed yet. + */ + PENDING, + /** + * Indicates that the task is running. + */ + RUNNING, + /** + * Indicates that {@link ImageAsyncTask#onPostExecute} has finished. + */ + FINISHED, + } + + /** @hide Used to force static handler to be created. */ + public static void init() { + sHandler.getLooper(); + } + + /** @hide */ + public static void setDefaultExecutor(Executor exec) { + sDefaultExecutor = exec; + } + + /** + * Creates a new asynchronous task. This constructor must be invoked on the UI thread. + */ + public ImageAsyncTask() { + mWorker = new WorkerRunnable() { + public Result call() throws Exception { + mTaskInvoked.set(true); + + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + //noinspection unchecked + return postResult(doInBackground(mParams)); + } + }; + + mFuture = new FutureTask(mWorker) { + @Override + protected void done() { + try { + postResultIfNotInvoked(get()); + } catch (InterruptedException e) { + android.util.Log.w(LOG_TAG, e); + } catch (ExecutionException e) { + throw new RuntimeException("An error occured while executing doInBackground()", + e.getCause()); + } catch (CancellationException e) { + postResultIfNotInvoked(null); + } + } + }; + } + + private void postResultIfNotInvoked(Result result) { + final boolean wasTaskInvoked = mTaskInvoked.get(); + if (!wasTaskInvoked) { + postResult(result); + } + } + + private Result postResult(Result result) { + @SuppressWarnings("unchecked") + Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT, + new AsyncTaskResult(this, result)); + message.sendToTarget(); + return result; + } + + /** + * Returns the current status of this task. + * + * @return The current status. + */ + public final Status getStatus() { + return mStatus; + } + + /** + * Override this method to perform a computation on a background thread. The + * specified parameters are the parameters passed to {@link #execute} + * by the caller of this task. + * + * This method can call {@link #publishProgress} to publish updates + * on the UI thread. + * + * @param params The parameters of the task. + * + * @return A result, defined by the subclass of this task. + * + * @see #onPreExecute() + * @see #onPostExecute + * @see #publishProgress + */ + protected abstract Result doInBackground(Params... params); + + /** + * Runs on the UI thread before {@link #doInBackground}. + * + * @see #onPostExecute + * @see #doInBackground + */ + protected void onPreExecute() { + } + + /** + *

Runs on the UI thread after {@link #doInBackground}. The + * specified result is the value returned by {@link #doInBackground}.

+ * + *

This method won't be invoked if the task was cancelled.

+ * + * @param result The result of the operation computed by {@link #doInBackground}. + * + * @see #onPreExecute + * @see #doInBackground + * @see #onCancelled(Object) + */ + @SuppressWarnings({"UnusedDeclaration"}) + protected void onPostExecute(Result result) { + } + + /** + * Runs on the UI thread after {@link #publishProgress} is invoked. + * The specified values are the values passed to {@link #publishProgress}. + * + * @param values The values indicating progress. + * + * @see #publishProgress + * @see #doInBackground + */ + @SuppressWarnings({"UnusedDeclaration"}) + protected void onProgressUpdate(Progress... values) { + } + + /** + *

Runs on the UI thread after {@link #cancel(boolean)} is invoked and + * {@link #doInBackground(Object[])} has finished.

+ * + *

The default implementation simply invokes {@link #onCancelled()} and + * ignores the result. If you write your own implementation, do not call + * super.onCancelled(result).

+ * + * @param result The result, if any, computed in + * {@link #doInBackground(Object[])}, can be null + * + * @see #cancel(boolean) + * @see #isCancelled() + */ + @SuppressWarnings({"UnusedParameters"}) + protected void onCancelled(Result result) { + onCancelled(); + } + + /** + *

Applications should preferably override {@link #onCancelled(Object)}. + * This method is invoked by the default implementation of + * {@link #onCancelled(Object)}.

+ * + *

Runs on the UI thread after {@link #cancel(boolean)} is invoked and + * {@link #doInBackground(Object[])} has finished.

+ * + * @see #onCancelled(Object) + * @see #cancel(boolean) + * @see #isCancelled() + */ + protected void onCancelled() { + } + + /** + * Returns true if this task was cancelled before it completed + * normally. If you are calling {@link #cancel(boolean)} on the task, + * the value returned by this method should be checked periodically from + * {@link #doInBackground(Object[])} to end the task as soon as possible. + * + * @return true if task was cancelled before it completed + * + * @see #cancel(boolean) + */ + public final boolean isCancelled() { + return mCancelled.get(); + } + + /** + *

Attempts to cancel execution of this task. This attempt will + * fail if the task has already completed, already been cancelled, + * or could not be cancelled for some other reason. If successful, + * and this task has not started when cancel is called, + * this task should never run. If the task has already started, + * then the mayInterruptIfRunning parameter determines + * whether the thread executing this task should be interrupted in + * an attempt to stop the task.

+ * + *

Calling this method will result in {@link #onCancelled(Object)} being + * invoked on the UI thread after {@link #doInBackground(Object[])} + * returns. Calling this method guarantees that {@link #onPostExecute(Object)} + * is never invoked. After invoking this method, you should check the + * value returned by {@link #isCancelled()} periodically from + * {@link #doInBackground(Object[])} to finish the task as early as + * possible.

+ * + * @param mayInterruptIfRunning true if the thread executing this + * task should be interrupted; otherwise, in-progress tasks are allowed + * to complete. + * + * @return false if the task could not be cancelled, + * typically because it has already completed normally; + * true otherwise + * + * @see #isCancelled() + * @see #onCancelled(Object) + */ + public final boolean cancel(boolean mayInterruptIfRunning) { + mCancelled.set(true); + return mFuture.cancel(mayInterruptIfRunning); + } + + /** + * Waits if necessary for the computation to complete, and then + * retrieves its result. + * + * @return The computed result. + * + * @throws java.util.concurrent.CancellationException If the computation was cancelled. + * @throws java.util.concurrent.ExecutionException If the computation threw an exception. + * @throws InterruptedException If the current thread was interrupted + * while waiting. + */ + public final Result get() throws InterruptedException, ExecutionException { + return mFuture.get(); + } + + /** + * Waits if necessary for at most the given time for the computation + * to complete, and then retrieves its result. + * + * @param timeout Time to wait before cancelling the operation. + * @param unit The time unit for the timeout. + * + * @return The computed result. + * + * @throws java.util.concurrent.CancellationException If the computation was cancelled. + * @throws java.util.concurrent.ExecutionException If the computation threw an exception. + * @throws InterruptedException If the current thread was interrupted + * while waiting. + * @throws java.util.concurrent.TimeoutException If the wait timed out. + */ + public final Result get(long timeout, TimeUnit unit) throws InterruptedException, + ExecutionException, TimeoutException { + return mFuture.get(timeout, unit); + } + + /** + * Executes the task with the specified parameters. The task returns + * itself (this) so that the caller can keep a reference to it. + * + *

Note: this function schedules the task on a queue for a single background + * thread or pool of threads depending on the platform version. When first + * introduced, AsyncTasks were executed serially on a single background thread. + * Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed + * to a pool of threads allowing multiple tasks to operate in parallel. Starting + * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are back to being + * executed on a single thread to avoid common application errors caused + * by parallel execution. If you truly want parallel execution, you can use + * the {@link #executeOnExecutor} version of this method + * with {@link #THREAD_POOL_EXECUTOR}; however, see commentary there for warnings + * on its use. + * + *

This method must be invoked on the UI thread. + * + * @param params The parameters of the task. + * + * @return This instance of ImageAsyncTask. + * + * @throws IllegalStateException If {@link #getStatus()} returns either + * {@link ImageAsyncTask.Status#RUNNING} or {@link ImageAsyncTask.Status#FINISHED}. + * + * @see #executeOnExecutor(java.util.concurrent.Executor, Object[]) + * @see #execute(Runnable) + */ + public final ImageAsyncTask execute(Params... params) { + return executeOnExecutor(sDefaultExecutor, params); + } + + /** + * Executes the task with the specified parameters. The task returns + * itself (this) so that the caller can keep a reference to it. + * + *

This method is typically used with {@link #THREAD_POOL_EXECUTOR} to + * allow multiple tasks to run in parallel on a pool of threads managed by + * ImageImageAsyncTask, however you can also use your own {@link java.util.concurrent.Executor} for custom + * behavior. + * + *

Warning: Allowing multiple tasks to run in parallel from + * a thread pool is generally not what one wants, because the order + * of their operation is not defined. For example, if these tasks are used + * to modify any state in common (such as writing a file due to a button click), + * there are no guarantees on the order of the modifications. + * Without careful work it is possible in rare cases for the newer version + * of the data to be over-written by an older one, leading to obscure data + * loss and stability issues. Such changes are best + * executed in serial; to guarantee such work is serialized regardless of + * platform version you can use this function with {@link #SERIAL_EXECUTOR}. + * + *

This method must be invoked on the UI thread. + * + * @param exec The executor to use. {@link #THREAD_POOL_EXECUTOR} is available as a + * convenient process-wide thread pool for tasks that are loosely coupled. + * @param params The parameters of the task. + * + * @return This instance of ImageAsyncTask. + * + * @throws IllegalStateException If {@link #getStatus()} returns either + * {@link ImageAsyncTask.Status#RUNNING} or {@link ImageAsyncTask.Status#FINISHED}. + * + * @see #execute(Object[]) + */ + public final ImageAsyncTask executeOnExecutor(Executor exec, + Params... params) { + if (mStatus != Status.PENDING) { + switch (mStatus) { + case RUNNING: + throw new IllegalStateException("Cannot execute task:" + + " the task is already running."); + case FINISHED: + throw new IllegalStateException("Cannot execute task:" + + " the task has already been executed " + + "(a task can be executed only once)"); + } + } + + mStatus = Status.RUNNING; + + onPreExecute(); + + mWorker.mParams = params; + exec.execute(mFuture); + + return this; + } + + /** + * Convenience version of {@link #execute(Object...)} for use with + * a simple Runnable object. See {@link #execute(Object[])} for more + * information on the order of execution. + * + * @see #execute(Object[]) + * @see #executeOnExecutor(java.util.concurrent.Executor, Object[]) + */ + public static void execute(Runnable runnable) { + sDefaultExecutor.execute(runnable); + } + + /** + * This method can be invoked from {@link #doInBackground} to + * publish updates on the UI thread while the background computation is + * still running. Each call to this method will trigger the execution of + * {@link #onProgressUpdate} on the UI thread. + * + * {@link #onProgressUpdate} will note be called if the task has been + * canceled. + * + * @param values The progress values to update the UI with. + * + * @see #onProgressUpdate + * @see #doInBackground + */ + protected final void publishProgress(Progress... values) { + if (!isCancelled()) { + sHandler.obtainMessage(MESSAGE_POST_PROGRESS, + new AsyncTaskResult(this, values)).sendToTarget(); + } + } + + private void finish(Result result) { + if (isCancelled()) { + onCancelled(result); + } else { + onPostExecute(result); + } + mStatus = Status.FINISHED; + } + + private static class InternalHandler extends Handler { + @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"}) + @Override + public void handleMessage(Message msg) { + AsyncTaskResult result = (AsyncTaskResult) msg.obj; + switch (msg.what) { + case MESSAGE_POST_RESULT: + // There is only one result + result.mTask.finish(result.mData[0]); + break; + case MESSAGE_POST_PROGRESS: + result.mTask.onProgressUpdate(result.mData); + break; + } + } + } + + private static abstract class WorkerRunnable implements Callable { + Params[] mParams; + } + + @SuppressWarnings({"RawUseOfParameterizedType"}) + private static class AsyncTaskResult { + final ImageAsyncTask mTask; + final Data[] mData; + + AsyncTaskResult(ImageAsyncTask task, Data... data) { + mTask = task; + mData = data; + } + } +} \ No newline at end of file diff --git a/src/com/cyanogenmod/filemanager/tasks/SearchResultDrawingAsyncTask.java b/Backbone/src/main/java/me/toolify/backbone/tasks/SearchResultDrawingAsyncTask.java similarity index 86% rename from src/com/cyanogenmod/filemanager/tasks/SearchResultDrawingAsyncTask.java rename to Backbone/src/main/java/me/toolify/backbone/tasks/SearchResultDrawingAsyncTask.java index c53d59c61..792d6554a 100644 --- a/src/com/cyanogenmod/filemanager/tasks/SearchResultDrawingAsyncTask.java +++ b/Backbone/src/main/java/me/toolify/backbone/tasks/SearchResultDrawingAsyncTask.java @@ -14,30 +14,30 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.tasks; +package me.toolify.backbone.tasks; import android.os.AsyncTask; import android.view.View; import android.widget.ListView; import android.widget.ProgressBar; -import com.cyanogenmod.filemanager.FileManagerApplication; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.adapters.SearchResultAdapter; -import com.cyanogenmod.filemanager.model.FileSystemObject; -import com.cyanogenmod.filemanager.model.Query; -import com.cyanogenmod.filemanager.model.SearchResult; -import com.cyanogenmod.filemanager.preferences.AccessMode; -import com.cyanogenmod.filemanager.preferences.DisplayRestrictions; -import com.cyanogenmod.filemanager.preferences.FileManagerSettings; -import com.cyanogenmod.filemanager.preferences.NavigationSortMode; -import com.cyanogenmod.filemanager.preferences.ObjectStringIdentifier; -import com.cyanogenmod.filemanager.preferences.Preferences; -import com.cyanogenmod.filemanager.preferences.SearchSortResultMode; -import com.cyanogenmod.filemanager.util.ExceptionUtil; -import com.cyanogenmod.filemanager.util.FileHelper; -import com.cyanogenmod.filemanager.util.MimeTypeHelper; -import com.cyanogenmod.filemanager.util.SearchHelper; +import me.toolify.backbone.FileManagerApplication; +import me.toolify.backbone.R; +import me.toolify.backbone.adapters.SearchResultAdapter; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.model.Query; +import me.toolify.backbone.model.SearchResult; +import me.toolify.backbone.preferences.AccessMode; +import me.toolify.backbone.preferences.DisplayRestrictions; +import me.toolify.backbone.preferences.FileManagerSettings; +import me.toolify.backbone.preferences.NavigationSortMode; +import me.toolify.backbone.preferences.ObjectStringIdentifier; +import me.toolify.backbone.preferences.Preferences; +import me.toolify.backbone.preferences.SearchSortResultMode; +import me.toolify.backbone.util.ExceptionUtil; +import me.toolify.backbone.util.FileHelper; +import me.toolify.backbone.util.MimeTypeHelper; +import me.toolify.backbone.util.SearchHelper; import java.util.Collections; import java.util.Comparator; diff --git a/Backbone/src/main/java/me/toolify/backbone/ui/IconHolder.java b/Backbone/src/main/java/me/toolify/backbone/ui/IconHolder.java new file mode 100644 index 000000000..164200c96 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/ui/IconHolder.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.ui; + +import android.content.Context; +import android.graphics.drawable.Drawable; + +import me.toolify.backbone.ui.ThemeManager.Theme; + +import java.util.HashMap; +import java.util.Map; + +/** + * A class that holds icons for a more efficient access. + */ +public class IconHolder { + + private final Map mIcons; // Themes based + private final Context mContext; + + /** + * Constructor of IconHolder. + * + */ + public IconHolder(Context context) { + super(); + this.mContext = context; + this.mIcons = new HashMap(); + } + + /** + * Method that returns a drawable reference of a icon. + * + * @param resid The resource identifier + * @return Drawable The drawable icon reference + */ + public Drawable getDrawable(final String resid) { + //Check if the icon exists in the cache + if (this.mIcons.containsKey(resid)) { + return this.mIcons.get(resid); + } + + //Load the drawable, cache and returns reference + Theme theme = ThemeManager.getCurrentTheme(mContext); + Drawable dw = theme.getDrawable(mContext, resid); + this.mIcons.put(resid, dw); + return dw; + } + + /** + * Free any resources used by this instance + */ + public void cleanup() { + this.mIcons.clear(); + } +} diff --git a/src/com/cyanogenmod/filemanager/ui/ThemeManager.java b/Backbone/src/main/java/me/toolify/backbone/ui/ThemeManager.java similarity index 88% rename from src/com/cyanogenmod/filemanager/ui/ThemeManager.java rename to Backbone/src/main/java/me/toolify/backbone/ui/ThemeManager.java index d1fd30df1..ed41a81c5 100644 --- a/src/com/cyanogenmod/filemanager/ui/ThemeManager.java +++ b/Backbone/src/main/java/me/toolify/backbone/ui/ThemeManager.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.ui; +package me.toolify.backbone.ui; import android.app.ActionBar; import android.app.AlertDialog; @@ -31,8 +31,8 @@ import android.widget.ImageView; import android.widget.TextView; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.preferences.FileManagerSettings; +import me.toolify.backbone.R; +import me.toolify.backbone.preferences.FileManagerSettings; import java.util.ArrayList; import java.util.List; @@ -50,19 +50,19 @@ public final class ThemeManager { * The permission that MUST have the activity that holds the themes */ public static final String PERMISSION_READ_THEME = - "com.cyanogenmod.filemanager.permissions.READ_THEME"; //$NON-NLS-1$ + "me.toolify.backbone.permissions.READ_THEME"; //$NON-NLS-1$ /** * The action that MUST have all app that want to register as a theme for this app */ public static final String ACTION_MAIN_THEME = - "com.cyanogenmod.filemanager.actions.MAIN_THEME"; //$NON-NLS-1$ + "me.toolify.backbone.actions.MAIN_THEME"; //$NON-NLS-1$ /** * The category that MUST have all app that want to register as a theme for this app */ public static final String CATEGORY_THEME = - "com.cyanogenmod.filemanager.categories.THEME"; //$NON-NLS-1$ + "me.toolify.backbone.categories.THEME"; //$NON-NLS-1$ private static final String RESOURCE_THEMES_IDS = "themes_ids"; //$NON-NLS-1$ private static final String RESOURCE_THEMES_NAMES = "themes_names"; //$NON-NLS-1$ @@ -101,7 +101,11 @@ public static synchronized Theme getDefaultTheme(Context ctx) { mDefaultTheme = new Theme(); String themeSettings = (String)FileManagerSettings.SETTINGS_THEME.getDefaultValue(); mDefaultTheme.mPackage = - themeSettings.substring(0, themeSettings.indexOf(":")); //$NON-NLS-1$ + ctx.getPackageName(); + /* TODO: Review this hack, perhaps when this crazy theme engine gets stripped + out. Gradle package renaming was breaking the line below, so since we only use + one in-house theme now, lets just dynamically load the context package */ + //themeSettings.substring(0, themeSettings.indexOf(":")); //$NON-NLS-1$ mDefaultTheme.mId = themeSettings.substring(themeSettings.indexOf(":") + 1); //$NON-NLS-1$ mDefaultTheme.mName = ctx.getString(R.string.theme_default_name); @@ -354,11 +358,7 @@ public String getAuthor() { public Drawable getPreviewImage(Context ctx) { String resId = "theme_preview_drawable"; //$NON-NLS-1$ if (this.compareTo(ThemeManager.getDefaultTheme(ctx)) != 0) { - resId = - String.format( - "%s_%s", //$NON-NLS-1$ - this.mId, - "theme_preview_drawable"); //$NON-NLS-1$ + resId = mId + "_theme_preview_drawable"; //$NON-NLS-1$ } int id = this.mResources.getIdentifier(resId, "drawable", this.mPackage); //$NON-NLS-1$ if (id != 0) { @@ -374,11 +374,7 @@ public Drawable getPreviewImage(Context ctx) { * @return Drawable The drawable */ public Drawable getNoPreviewImage(Context ctx) { - String resId = - String.format( - "%s_%s", //$NON-NLS-1$ - this.mId, - "theme_no_preview_drawable"); //$NON-NLS-1$ + String resId = mId + "_theme_no_preview_drawable"; //$NON-NLS-1$ int id = this.mResources.getIdentifier(resId, "drawable", this.mPackage); //$NON-NLS-1$ if (id != 0) { return this.mResources.getDrawable(id); @@ -399,14 +395,13 @@ public Drawable getNoPreviewImage(Context ctx) { * @param overlay Indicates if the theme should be the overlay one */ public void setBaseTheme(Context ctx, boolean overlay) { - String resId = - String.format("%s_%s", this.mId, "base_theme"); //$NON-NLS-1$ //$NON-NLS-2$ + String resId = mId + "_base_theme"; //$NON-NLS-1$ int id = this.mResources.getIdentifier(resId, "string", this.mPackage); //$NON-NLS-1$ if (id != 0) { - String base = this.mResources.getString(id, "holo_light"); //$NON-NLS-1$ + String base = this.mResources.getString(id, "holo_light_darkactionbar"); //$NON-NLS-1$ int themeId = base.compareTo("holo") == 0 ? //$NON-NLS-1$ R.style.FileManager_Theme_Holo : - R.style.FileManager_Theme_Holo_Light; + R.style.FileManager_Theme_Holo_Light_DarkActionBar; if (overlay) { themeId = base.compareTo("holo") == 0 ? //$NON-NLS-1$ R.style.FileManager_Theme_Holo_Overlay : @@ -419,10 +414,10 @@ public void setBaseTheme(Context ctx, boolean overlay) { // Default theme id = mDefaultTheme.mResources.getIdentifier( "base_theme", "string", mDefaultTheme.mPackage); //$NON-NLS-1$ //$NON-NLS-2$ - String base = this.mResources.getString(id, "holo_light"); //$NON-NLS-1$ + String base = this.mResources.getString(id, "holo_light_darkactionbar"); //$NON-NLS-1$ int themeId = base.compareTo("holo") == 0 ? //$NON-NLS-1$ R.style.FileManager_Theme_Holo : - R.style.FileManager_Theme_Holo_Light; + R.style.FileManager_Theme_Holo_Light_DarkActionBar; if (overlay) { themeId = base.compareTo("holo") == 0 ? //$NON-NLS-1$ R.style.FileManager_Theme_Holo_Overlay : @@ -439,7 +434,7 @@ public void setBaseTheme(Context ctx, boolean overlay) { * @param resource The string resource */ public void setTitlebarDrawable(Context ctx, ActionBar actionBar, String resource) { - String resId = String.format("%s_%s", this.mId, resource); //$NON-NLS-1$ + String resId = this.mId + "_" + resource; //$NON-NLS-1$ int id = this.mResources.getIdentifier(resId, "drawable", this.mPackage); //$NON-NLS-1$ if (id != 0) { actionBar.setBackgroundDrawable(this.mResources.getDrawable(id)); @@ -452,6 +447,27 @@ public void setTitlebarDrawable(Context ctx, ActionBar actionBar, String resourc actionBar.setBackgroundDrawable(mDefaultTheme.mResources.getDrawable(id)); } + /** + * Method that sets the split drawable of an ActionBar + * + * @param ctx The current context + * @param actionBar The action bar + * @param resource The string resource + */ + public void setSplitActionBarDrawable(Context ctx, ActionBar actionBar, String resource) { + String resId = String.format("%s_%s", this.mId, resource); //$NON-NLS-1$ + int id = this.mResources.getIdentifier(resId, "drawable", this.mPackage); //$NON-NLS-1$ + if (id != 0) { + actionBar.setSplitBackgroundDrawable(this.mResources.getDrawable(id)); + return; + } + + // Default theme + id = mDefaultTheme.mResources.getIdentifier( + resource, "drawable", mDefaultTheme.mPackage); //$NON-NLS-1$ + actionBar.setSplitBackgroundDrawable(mDefaultTheme.mResources.getDrawable(id)); + } + /** * Method that sets the background drawable of a View * @@ -460,17 +476,25 @@ public void setTitlebarDrawable(Context ctx, ActionBar actionBar, String resourc * @param resource The string resource */ public void setBackgroundDrawable(Context ctx, View view, String resource) { - String resId = String.format("%s_%s", this.mId, resource); //$NON-NLS-1$ + String resId = mId + "_" + resource; //$NON-NLS-1$ int id = this.mResources.getIdentifier(resId, "drawable", this.mPackage); //$NON-NLS-1$ if (id != 0) { - view.setBackground(this.mResources.getDrawable(id)); + if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN) { + view.setBackgroundDrawable(this.mResources.getDrawable(id)); + } else { + view.setBackground(this.mResources.getDrawable(id)); + } return; } // Default theme id = mDefaultTheme.mResources.getIdentifier( resource, "drawable", mDefaultTheme.mPackage); //$NON-NLS-1$ - view.setBackground(mDefaultTheme.mResources.getDrawable(id)); + if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN) { + view.setBackgroundDrawable(mDefaultTheme.mResources.getDrawable(id)); + } else { + view.setBackground(mDefaultTheme.mResources.getDrawable(id)); + } } /** @@ -481,7 +505,7 @@ public void setBackgroundDrawable(Context ctx, View view, String resource) { * @param resource The string resource */ public void setImageDrawable(Context ctx, ImageView view, String resource) { - String resId = String.format("%s_%s", this.mId, resource); //$NON-NLS-1$ + String resId = mId + "_" + resource; //$NON-NLS-1$ int id = this.mResources.getIdentifier(resId, "drawable", this.mPackage); //$NON-NLS-1$ if (id != 0) { view.setImageDrawable(this.mResources.getDrawable(id)); @@ -502,7 +526,7 @@ public void setImageDrawable(Context ctx, ImageView view, String resource) { * @return Drawable The drawable */ public Drawable getDrawable(Context ctx, String resource) { - String resId = String.format("%s_%s", this.mId, resource); //$NON-NLS-1$ + String resId = mId + "_" + resource; //$NON-NLS-1$ int id = this.mResources.getIdentifier(resId, "drawable", this.mPackage); //$NON-NLS-1$ if (id != 0) { return this.mResources.getDrawable(id); @@ -522,7 +546,7 @@ public Drawable getDrawable(Context ctx, String resource) { * @param resource The string resource */ public void setTextColor(Context ctx, TextView view, String resource) { - String resId = String.format("%s_%s", this.mId, resource); //$NON-NLS-1$ + String resId = mId + "_" + resource; //$NON-NLS-1$ int id = this.mResources.getIdentifier(resId, "color", this.mPackage); //$NON-NLS-1$ if (id != 0) { view.setTextColor(this.mResources.getColor(id)); @@ -543,7 +567,7 @@ public void setTextColor(Context ctx, TextView view, String resource) { * @return int The color reference */ public int getColor(Context ctx, String resource) { - String resId = String.format("%s_%s", this.mId, resource); //$NON-NLS-1$ + String resId = mId + "_" + resource; //$NON-NLS-1$ int id = this.mResources.getIdentifier(resId, "color", this.mPackage); //$NON-NLS-1$ if (id != 0) { return this.mResources.getColor(id); @@ -563,7 +587,7 @@ public int getColor(Context ctx, String resource) { * @param resource The string resource */ public void setBackgroundColor(Context ctx, View view, String resource) { - String resId = String.format("%s_%s", this.mId, resource); //$NON-NLS-1$ + String resId = mId + "_" + resource; //$NON-NLS-1$ int id = this.mResources.getIdentifier(resId, "color", this.mPackage); //$NON-NLS-1$ if (id != 0) { view.setBackgroundColor(this.mResources.getColor(id)); diff --git a/src/com/cyanogenmod/filemanager/ui/dialogs/AssociationsDialog.java b/Backbone/src/main/java/me/toolify/backbone/ui/dialogs/AssociationsDialog.java similarity index 88% rename from src/com/cyanogenmod/filemanager/ui/dialogs/AssociationsDialog.java rename to Backbone/src/main/java/me/toolify/backbone/ui/dialogs/AssociationsDialog.java index 096bacdbd..2590c3a0d 100644 --- a/src/com/cyanogenmod/filemanager/ui/dialogs/AssociationsDialog.java +++ b/Backbone/src/main/java/me/toolify/backbone/ui/dialogs/AssociationsDialog.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.ui.dialogs; +package me.toolify.backbone.ui.dialogs; import android.app.AlertDialog; import android.content.ComponentName; @@ -37,17 +37,16 @@ import android.widget.AdapterView.OnItemClickListener; import android.widget.CheckBox; import android.widget.GridView; -import android.widget.ListAdapter; import android.widget.Toast; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.adapters.AssociationsAdapter; -import com.cyanogenmod.filemanager.ui.ThemeManager; -import com.cyanogenmod.filemanager.ui.ThemeManager.Theme; -import com.cyanogenmod.filemanager.ui.policy.IntentsActionPolicy; -import com.cyanogenmod.filemanager.util.AndroidHelper; -import com.cyanogenmod.filemanager.util.DialogHelper; -import com.cyanogenmod.filemanager.util.ExceptionUtil; +import me.toolify.backbone.R; +import me.toolify.backbone.adapters.AssociationsAdapter; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.ui.ThemeManager.Theme; +import me.toolify.backbone.ui.policy.IntentsActionPolicy; +import me.toolify.backbone.util.AndroidHelper; +import me.toolify.backbone.util.DialogHelper; +import me.toolify.backbone.util.ExceptionUtil; import java.util.Iterator; import java.util.List; @@ -138,7 +137,7 @@ private void init(int icon, String title, String action, isPlatformSigned && this.mAllowPreferred ? View.VISIBLE : View.GONE); this.mGrid = (GridView)v.findViewById(R.id.associations_gridview); AssociationsAdapter adapter = - new AssociationsAdapter(this.mContext, this.mIntents, this); + new AssociationsAdapter(this.mContext, this.mGrid, this.mIntents, this); this.mGrid.setAdapter(adapter); // Ensure a default title dialog @@ -208,21 +207,28 @@ public void run() { */ @Override public void onItemClick(AdapterView parent, View view, int position, long id) { - deselectAll(); - ((ViewGroup)view).setSelected(true); + // If the item is selected, then just open it like ActivityChooserView + // If there is no parent, that means an internal call. In this case ignore it. + if (parent != null && ((ViewGroup)view).isSelected()) { + this.mDialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick(); + + } else { + deselectAll(); + ((ViewGroup)view).setSelected(true); + + // Internal editors can be associated + boolean isPlatformSigned = AndroidHelper.isAppPlatformSignature(this.mContext); + if (isPlatformSigned && this.mAllowPreferred) { + ResolveInfo ri = getSelected(); + this.mRemember.setVisibility( + IntentsActionPolicy.isInternalEditor(ri) ? + View.INVISIBLE : + View.VISIBLE); + } - // Internal editors can be associated - boolean isPlatformSigned = AndroidHelper.isAppPlatformSignature(this.mContext); - if (isPlatformSigned && this.mAllowPreferred) { - ResolveInfo ri = getSelected(); - this.mRemember.setVisibility( - IntentsActionPolicy.isInternalEditor(ri) ? - View.INVISIBLE : - View.VISIBLE); + // Enable action button + this.mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(true); } - - // Enable action button - this.mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(true); } /** @@ -286,10 +292,8 @@ private boolean isPreferredSelected() { ResolveInfo info = this.mIntents.get(i); if (info.activityInfo.name.equals(this.mPreferred.activityInfo.name)) { ViewGroup item = (ViewGroup)this.mGrid.getChildAt(i); - if (item != null) { - if (item.isSelected()) { - return true; - } + if (item != null && item.isSelected()) { + return true; } } } @@ -301,27 +305,29 @@ private boolean isPreferredSelected() { * Method that deselect all the items of the grid view */ private void deselectAll() { - ListAdapter adapter = this.mGrid.getAdapter(); - int cc = adapter.getCount(); + int cc = this.mGrid.getChildCount(); for (int i = 0; i < cc; i++) { ViewGroup item = (ViewGroup)this.mGrid.getChildAt(i); - item.setSelected(false); + if (item != null) { + item.setSelected(false); + } } } /** - * Method that deselect all the items of the grid view + * Method that returns the selected item of the grid view * * @return ResolveInfo The selected item * @hide */ ResolveInfo getSelected() { AssociationsAdapter adapter = (AssociationsAdapter)this.mGrid.getAdapter(); - int count = adapter.getCount(); - for (int i = 0; i < count; i++) { + int cc = this.mGrid.getChildCount(); + int firstVisible = this.mGrid.getFirstVisiblePosition(); + for (int i = 0; i < cc; i++) { ViewGroup item = (ViewGroup)this.mGrid.getChildAt(i); - if (item.isSelected()) { - return adapter.getItem(i); + if (item != null && item.isSelected()) { + return adapter.getItem(i + firstVisible); } } return null; @@ -472,7 +478,12 @@ void onIntentSelected(ResolveInfo ri, Intent intent, boolean remember) { } if (intent != null) { - this.mContext.startActivity(intent); + // Capture security exceptions + try { + this.mContext.startActivity(intent); + } catch (Exception e) { + ExceptionUtil.translateException(this.mContext, e); + } } } } diff --git a/Backbone/src/main/java/me/toolify/backbone/ui/dialogs/ComputeChecksumDialog.java b/Backbone/src/main/java/me/toolify/backbone/ui/dialogs/ComputeChecksumDialog.java new file mode 100644 index 000000000..52bd96c8a --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/ui/dialogs/ComputeChecksumDialog.java @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.ui.dialogs; + +import android.app.AlertDialog; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Handler; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import me.toolify.backbone.R; +import me.toolify.backbone.commands.AsyncResultExecutable; +import me.toolify.backbone.commands.AsyncResultListener; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.ui.ThemeManager.Theme; +import me.toolify.backbone.util.CommandHelper; +import me.toolify.backbone.util.DialogHelper; +import me.toolify.backbone.util.ExceptionUtil; +/** + * A class that wraps a dialog for computing the checksums of a {@link FileSystemObject} + */ +public class ComputeChecksumDialog implements + DialogInterface.OnClickListener, View.OnClickListener, AsyncResultListener { + + /** + * @hide + */ + final Context mContext; + /** + * @hide + */ + final FileSystemObject mFso; + private final Handler mHandler; + /** + * @hide + */ + final AlertDialog mDialog; + + // For cancel the operation + /** + * @hide + */ + AsyncResultExecutable mCmd; + /** + * @hide + */ + boolean mFinished; + + /** + * @hide + */ + EditText[] mChecksums = new EditText[2]; + + /** + * @hide + */ + int mComputeStatus; + + private final ClipboardManager mClipboardMgr; + + /** + * Constructor of ComputeChecksumDialog. + * + * @param context The current context + * @param fso The file system object to execute + */ + public ComputeChecksumDialog(final Context context, final FileSystemObject fso) { + super(); + + // Save properties + this.mContext = context; + this.mFso = fso; + this.mHandler = new Handler(); + this.mComputeStatus = 0; + + this.mClipboardMgr = + (ClipboardManager)this.mContext.getSystemService(Context.CLIPBOARD_SERVICE); + + //Create the layout + LayoutInflater li = + (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + ViewGroup layout = (ViewGroup)li.inflate(R.layout.compute_checksum_dialog, null); + TextView tvFileName = (TextView)layout.findViewById(R.id.checksum_filename); + tvFileName.setText(fso.getFullPath()); + this.mChecksums[0] = (EditText)layout.findViewById(R.id.checksum_md5); + this.mChecksums[1] = (EditText)layout.findViewById(R.id.checksum_sha1); + View btMD5 = layout.findViewById(R.id.bt_md5_clipboard); + btMD5.setOnClickListener(this); + View btSHA1 = layout.findViewById(R.id.bt_sha1_clipboard); + btSHA1.setOnClickListener(this); + + // Apply the theme + applyTheme(context, layout); + + //Create the dialog + String title = context.getString(R.string.compute_checksum_title); + this.mDialog = DialogHelper.createDialog( + context, + 0, + title, + layout); + this.mDialog.setButton( + DialogInterface.BUTTON_NEUTRAL, context.getString(android.R.string.cancel), this); + + // Start checksum compute + try { + this.mCmd = CommandHelper.checksum(context, fso.getFullPath(), this, null); + } catch (Exception e) { + ExceptionUtil.translateException(context, e); + } + } + + /** + * Method that shows the dialog. + */ + public void show() { + DialogHelper.delegateDialogShow(this.mContext, this.mDialog); + } + + /** + * Method that dismiss the dialog. + */ + public void dismiss() { + this.mDialog.dismiss(); + } + + /** + * {@inheritDoc} + */ + @Override + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case DialogInterface.BUTTON_NEUTRAL: + // Cancel the program? + try { + if (this.mCmd != null && !this.mFinished) { + if (this.mCmd.isCancellable() && !this.mCmd.isCancelled()) { + this.mCmd.cancel(); + } + } + } catch (Exception e) {/**NON BLOCK**/} + this.mDialog.dismiss(); + break; + + default: + break; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onClick(View v) { + String digest = ""; //$NON-NLS-1$ + String label = ""; //$NON-NLS-1$ + switch (v.getId()) { + case R.id.bt_md5_clipboard: + digest = this.mChecksums[0].getText().toString(); + label = String.format("MD5 Checksum - %s", this.mFso.getFullPath()); //$NON-NLS-1$ + break; + case R.id.bt_sha1_clipboard: + digest = this.mChecksums[1].getText().toString(); + label = String.format("SHA-1 Checksum - %s", this.mFso.getFullPath()); //$NON-NLS-1$ + break; + + default: + break; + } + + // Copy text to clipboard + if (this.mClipboardMgr != null) { + ClipData clip =ClipData.newPlainText(label, digest); + this.mClipboardMgr.setPrimaryClip(clip); + DialogHelper.showToast(this.mContext, R.string.copy_text_msg, Toast.LENGTH_SHORT); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onAsyncStart() { + /** NON BLOCK **/ + } + + /** + * {@inheritDoc} + */ + @Override + public void onAsyncEnd(boolean cancelled) { + /** NON BLOCK **/ + } + + /** + * {@inheritDoc} + */ + @Override + public void onAsyncExitCode(int exitCode) { + if (exitCode != 0) { + this.mHandler.post(new Runnable() { + @Override + public void run() { + int cc = ComputeChecksumDialog.this.mChecksums.length; + for (int i = ComputeChecksumDialog.this.mComputeStatus; i < cc; i++) { + ComputeChecksumDialog.this.mChecksums[i].setText(R.string.error_message); + } + } + }); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onPartialResult(final Object result) { + this.mHandler.post(new Runnable() { + @Override + public void run() { + setChecksum(String.valueOf(result)); + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public void onException(Exception cause) { + ExceptionUtil.translateException(this.mContext, cause, false, false); + } + + /** + * Method that attach the checksum result to the view + * + * @param digest The digest value + * @hide + */ + synchronized void setChecksum(String digest) { + this.mChecksums[this.mComputeStatus].setText(digest); + this.mComputeStatus++; + } + + /** + * Method that applies the current theme to the dialog + * + * @param ctx The current context + * @param root The root view + */ + private void applyTheme(Context ctx, ViewGroup root) { + // Apply the current theme + Theme theme = ThemeManager.getCurrentTheme(ctx); + theme.setBackgroundDrawable(ctx, root, "background_drawable"); //$NON-NLS-1$ + View v = root.findViewById(R.id.checksum_filename_label); + theme.setTextColor(ctx, (TextView)v, "text_color"); //$NON-NLS-1$ + v = root.findViewById(R.id.checksum_filename); + theme.setTextColor(ctx, (TextView)v, "text_color"); //$NON-NLS-1$ + v = root.findViewById(R.id.checksum_md5_label); + theme.setTextColor(ctx, (TextView)v, "text_color"); //$NON-NLS-1$ + theme.setBackgroundColor(ctx, this.mChecksums[0], "console_bg_color"); //$NON-NLS-1$ + theme.setTextColor(ctx, this.mChecksums[0], "console_fg_color"); //$NON-NLS-1$ + v = root.findViewById(R.id.checksum_sha1_label); + theme.setTextColor(ctx, (TextView)v, "text_color"); //$NON-NLS-1$ + theme.setBackgroundColor(ctx, this.mChecksums[1], "console_bg_color"); //$NON-NLS-1$ + theme.setTextColor(ctx, this.mChecksums[1], "console_fg_color"); //$NON-NLS-1$ + v = root.findViewById(R.id.bt_md5_clipboard); + theme.setImageDrawable(ctx, (ImageView)v, "ic_copy_drawable"); //$NON-NLS-1$ + v = root.findViewById(R.id.bt_sha1_clipboard); + theme.setImageDrawable(ctx, (ImageView)v, "ic_copy_drawable"); //$NON-NLS-1$ + } +} diff --git a/src/com/cyanogenmod/filemanager/ui/dialogs/ExecutionDialog.java b/Backbone/src/main/java/me/toolify/backbone/ui/dialogs/ExecutionDialog.java similarity index 86% rename from src/com/cyanogenmod/filemanager/ui/dialogs/ExecutionDialog.java rename to Backbone/src/main/java/me/toolify/backbone/ui/dialogs/ExecutionDialog.java index 5bc8ed48e..1dd7abe3d 100644 --- a/src/com/cyanogenmod/filemanager/ui/dialogs/ExecutionDialog.java +++ b/Backbone/src/main/java/me/toolify/backbone/ui/dialogs/ExecutionDialog.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.ui.dialogs; +package me.toolify.backbone.ui.dialogs; import android.app.AlertDialog; import android.content.Context; @@ -28,15 +28,15 @@ import android.widget.Button; import android.widget.TextView; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.commands.AsyncResultExecutable; -import com.cyanogenmod.filemanager.commands.ExecExecutable; -import com.cyanogenmod.filemanager.model.FileSystemObject; -import com.cyanogenmod.filemanager.ui.ThemeManager; -import com.cyanogenmod.filemanager.ui.ThemeManager.Theme; -import com.cyanogenmod.filemanager.util.DialogHelper; -import com.cyanogenmod.filemanager.util.FixedQueue; -import com.cyanogenmod.filemanager.util.FixedQueue.EmptyQueueException; +import me.toolify.backbone.R; +import me.toolify.backbone.commands.AsyncResultExecutable; +import me.toolify.backbone.commands.ExecExecutable; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.ui.ThemeManager.Theme; +import me.toolify.backbone.util.DialogHelper; +import me.toolify.backbone.util.FixedQueue; +import me.toolify.backbone.util.FixedQueue.EmptyQueueException; import java.util.List; @@ -146,29 +146,17 @@ public ExecutionDialog(final Context context, final FileSystemObject fso) { LayoutInflater li = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); ViewGroup layout = (ViewGroup)li.inflate(R.layout.execution_dialog, null); - View tvScriptNameLabel = layout.findViewById(R.id.execution_script_name_label); TextView tvScriptName = (TextView)layout.findViewById(R.id.execution_script_name); tvScriptName.setText(fso.getFullPath()); - View tvTimeLabel = layout.findViewById(R.id.execution_time_label); this.mTvTime = (TextView)layout.findViewById(R.id.execution_time); this.mTvTime.setText("-"); //$NON-NLS-1$ - View tvExitCodeLabel = layout.findViewById(R.id.execution_exitcode_label); this.mTvExitCode = (TextView)layout.findViewById(R.id.execution_exitcode); this.mTvExitCode.setText("-"); //$NON-NLS-1$ this.mTvOutput = (TextView)layout.findViewById(R.id.execution_output); this.mTvOutput.setMovementMethod(new ScrollingMovementMethod()); - // Apply the current theme - Theme theme = ThemeManager.getCurrentTheme(context); - theme.setBackgroundDrawable(context, layout, "background_drawable"); //$NON-NLS-1$ - theme.setTextColor(context, (TextView)tvScriptNameLabel, "text_color"); //$NON-NLS-1$ - theme.setTextColor(context, tvScriptName, "text_color"); //$NON-NLS-1$ - theme.setTextColor(context, (TextView)tvTimeLabel, "text_color"); //$NON-NLS-1$ - theme.setTextColor(context, this.mTvTime, "text_color"); //$NON-NLS-1$ - theme.setTextColor(context, (TextView)tvExitCodeLabel, "text_color"); //$NON-NLS-1$ - theme.setTextColor(context, this.mTvExitCode, "text_color"); //$NON-NLS-1$ - theme.setBackgroundColor(context, this.mTvOutput, "console_bg_color"); //$NON-NLS-1$ - theme.setTextColor(context, this.mTvOutput, "console_fg_color"); //$NON-NLS-1$ + // Apply the theme + applyTheme(context, layout); //Create the dialog String title = context.getString(R.string.execution_console_title); @@ -369,4 +357,28 @@ void drawMessage(String msg, boolean scroll) { } } + /** + * Method that applies the current theme to the dialog + * + * @param ctx The current context + * @param root The root view + */ + private void applyTheme(Context ctx, ViewGroup root) { + // Apply the current theme + Theme theme = ThemeManager.getCurrentTheme(ctx); + theme.setBackgroundDrawable(ctx, root, "background_drawable"); //$NON-NLS-1$ + View v = root.findViewById(R.id.execution_time_label); + theme.setTextColor(ctx, (TextView)v, "text_color"); //$NON-NLS-1$ + v = root.findViewById(R.id.execution_script_name); + theme.setTextColor(ctx, (TextView)v, "text_color"); //$NON-NLS-1$ + v = root.findViewById(R.id.execution_time_label); + theme.setTextColor(ctx, (TextView)v, "text_color"); //$NON-NLS-1$ + theme.setTextColor(ctx, this.mTvTime, "text_color"); //$NON-NLS-1$ + v = root.findViewById(R.id.execution_exitcode_label); + theme.setTextColor(ctx, (TextView)v, "text_color"); //$NON-NLS-1$ + theme.setTextColor(ctx, this.mTvExitCode, "text_color"); //$NON-NLS-1$ + theme.setBackgroundColor(ctx, this.mTvOutput, "console_bg_color"); //$NON-NLS-1$ + theme.setTextColor(ctx, this.mTvOutput, "console_fg_color"); //$NON-NLS-1$ + } + } diff --git a/src/com/cyanogenmod/filemanager/ui/dialogs/FilesystemInfoDialog.java b/Backbone/src/main/java/me/toolify/backbone/ui/dialogs/FilesystemInfoDialog.java similarity index 95% rename from src/com/cyanogenmod/filemanager/ui/dialogs/FilesystemInfoDialog.java rename to Backbone/src/main/java/me/toolify/backbone/ui/dialogs/FilesystemInfoDialog.java index d0786d102..984b21dc6 100644 --- a/src/com/cyanogenmod/filemanager/ui/dialogs/FilesystemInfoDialog.java +++ b/Backbone/src/main/java/me/toolify/backbone/ui/dialogs/FilesystemInfoDialog.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.ui.dialogs; +package me.toolify.backbone.ui.dialogs; import android.app.AlertDialog; import android.content.Context; @@ -28,21 +28,21 @@ import android.widget.Switch; import android.widget.TextView; -import com.cyanogenmod.filemanager.FileManagerApplication; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.console.ConsoleBuilder; -import com.cyanogenmod.filemanager.model.DiskUsage; -import com.cyanogenmod.filemanager.model.MountPoint; -import com.cyanogenmod.filemanager.preferences.AccessMode; -import com.cyanogenmod.filemanager.preferences.FileManagerSettings; -import com.cyanogenmod.filemanager.preferences.Preferences; -import com.cyanogenmod.filemanager.ui.ThemeManager; -import com.cyanogenmod.filemanager.ui.ThemeManager.Theme; -import com.cyanogenmod.filemanager.ui.widgets.DiskUsageGraph; -import com.cyanogenmod.filemanager.util.CommandHelper; -import com.cyanogenmod.filemanager.util.DialogHelper; -import com.cyanogenmod.filemanager.util.FileHelper; -import com.cyanogenmod.filemanager.util.MountPointHelper; +import me.toolify.backbone.FileManagerApplication; +import me.toolify.backbone.R; +import me.toolify.backbone.console.ConsoleBuilder; +import me.toolify.backbone.model.DiskUsage; +import me.toolify.backbone.model.MountPoint; +import me.toolify.backbone.preferences.AccessMode; +import me.toolify.backbone.preferences.FileManagerSettings; +import me.toolify.backbone.preferences.Preferences; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.ui.ThemeManager.Theme; +import me.toolify.backbone.ui.widgets.DiskUsageGraph; +import me.toolify.backbone.util.CommandHelper; +import me.toolify.backbone.util.DialogHelper; +import me.toolify.backbone.util.FileHelper; +import me.toolify.backbone.util.MountPointHelper; /** * A class that wraps a dialog for showing information about a mount point.
diff --git a/src/com/cyanogenmod/filemanager/ui/dialogs/FsoPropertiesDialog.java b/Backbone/src/main/java/me/toolify/backbone/ui/dialogs/FsoPropertiesDialog.java similarity index 80% rename from src/com/cyanogenmod/filemanager/ui/dialogs/FsoPropertiesDialog.java rename to Backbone/src/main/java/me/toolify/backbone/ui/dialogs/FsoPropertiesDialog.java index 87c38c837..f5a123497 100644 --- a/src/com/cyanogenmod/filemanager/ui/dialogs/FsoPropertiesDialog.java +++ b/Backbone/src/main/java/me/toolify/backbone/ui/dialogs/FsoPropertiesDialog.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.ui.dialogs; +package me.toolify.backbone.ui.dialogs; import android.app.Activity; import android.app.AlertDialog; @@ -33,40 +33,44 @@ import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.LinearLayout; import android.widget.Spinner; import android.widget.TextView; - -import com.cyanogenmod.filemanager.FileManagerApplication; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.commands.AsyncResultListener; -import com.cyanogenmod.filemanager.commands.FolderUsageExecutable; -import com.cyanogenmod.filemanager.console.ConsoleBuilder; -import com.cyanogenmod.filemanager.model.AID; -import com.cyanogenmod.filemanager.model.FileSystemObject; -import com.cyanogenmod.filemanager.model.FolderUsage; -import com.cyanogenmod.filemanager.model.Group; -import com.cyanogenmod.filemanager.model.GroupPermission; -import com.cyanogenmod.filemanager.model.OthersPermission; -import com.cyanogenmod.filemanager.model.Permission; -import com.cyanogenmod.filemanager.model.Permissions; -import com.cyanogenmod.filemanager.model.Symlink; -import com.cyanogenmod.filemanager.model.User; -import com.cyanogenmod.filemanager.model.UserPermission; -import com.cyanogenmod.filemanager.preferences.AccessMode; -import com.cyanogenmod.filemanager.preferences.FileManagerSettings; -import com.cyanogenmod.filemanager.preferences.Preferences; -import com.cyanogenmod.filemanager.ui.ThemeManager; -import com.cyanogenmod.filemanager.ui.ThemeManager.Theme; -import com.cyanogenmod.filemanager.util.AIDHelper; -import com.cyanogenmod.filemanager.util.CommandHelper; -import com.cyanogenmod.filemanager.util.DialogHelper; -import com.cyanogenmod.filemanager.util.ExceptionUtil; -import com.cyanogenmod.filemanager.util.FileHelper; -import com.cyanogenmod.filemanager.util.MimeTypeHelper; -import com.cyanogenmod.filemanager.util.MimeTypeHelper.MimeTypeCategory; -import com.cyanogenmod.filemanager.util.ResourcesHelper; - -import java.text.DateFormat; +import android.widget.Toast; + +import me.toolify.backbone.FileManagerApplication; +import me.toolify.backbone.R; +import me.toolify.backbone.commands.AsyncResultListener; +import me.toolify.backbone.commands.FolderUsageExecutable; +import me.toolify.backbone.console.ConsoleBuilder; +import me.toolify.backbone.model.AID; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.model.FolderUsage; +import me.toolify.backbone.model.Group; +import me.toolify.backbone.model.GroupPermission; +import me.toolify.backbone.model.OthersPermission; +import me.toolify.backbone.model.Permission; +import me.toolify.backbone.model.Permissions; +import me.toolify.backbone.model.Symlink; +import me.toolify.backbone.model.User; +import me.toolify.backbone.model.UserPermission; +import me.toolify.backbone.preferences.AccessMode; +import me.toolify.backbone.preferences.FileManagerSettings; +import me.toolify.backbone.preferences.Preferences; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.ui.ThemeManager.Theme; +import me.toolify.backbone.util.AIDHelper; +import me.toolify.backbone.util.CommandHelper; +import me.toolify.backbone.util.DialogHelper; +import me.toolify.backbone.util.ExceptionUtil; +import me.toolify.backbone.util.FileHelper; +import me.toolify.backbone.util.MimeTypeHelper; +import me.toolify.backbone.util.MimeTypeHelper.MimeTypeCategory; +import me.toolify.backbone.util.ResourcesHelper; +import me.toolify.backbone.util.StorageHelper; + +import java.io.File; +import java.io.IOException; /** * A class that wraps a dialog for showing information about a {@link FileSystemObject} @@ -88,7 +92,10 @@ public class FsoPropertiesDialog * @hide */ final FileSystemObject mFso; - private boolean mHasChanged; + /** + * @hide + */ + boolean mHasChanged; /** * @hide @@ -100,6 +107,10 @@ public class FsoPropertiesDialog private View mPermissionsViewTab; private View mInfoView; private View mPermissionsView; + /** + * @hide + */ + CheckBox mChkNoMedia; /** * @hide */ @@ -124,7 +135,10 @@ public class FsoPropertiesDialog */ TextView mTvContains; - private boolean mIgnoreCheckEvents; + /** + * @hide + */ + boolean mIgnoreCheckEvents; private boolean mHasPrivileged; private final boolean mIsAdvancedMode; @@ -253,7 +267,13 @@ private void fillData(View contentView) { this.mTvSize = (TextView)contentView.findViewById(R.id.fso_properties_size); View vContatinsRow = contentView.findViewById(R.id.fso_properties_contains_row); this.mTvContains = (TextView)contentView.findViewById(R.id.fso_properties_contains); - TextView tvDate = (TextView)contentView.findViewById(R.id.fso_properties_date); + TextView tvLastAccessedTime = + (TextView)contentView.findViewById(R.id.fso_properties_last_accessed); + TextView tvLastModifiedTime = + (TextView)contentView.findViewById(R.id.fso_properties_last_modified); + TextView tvLastChangedTime = + (TextView)contentView.findViewById(R.id.fso_properties_last_changed); + this.mChkNoMedia = (CheckBox)contentView.findViewById(R.id.fso_include_in_media_scan); this.mSpnOwner = (Spinner)contentView.findViewById(R.id.fso_properties_owner); this.mSpnGroup = (Spinner)contentView.findViewById(R.id.fso_properties_group); this.mInfoMsgView = (TextView)contentView.findViewById(R.id.fso_info_msg); @@ -290,8 +310,12 @@ private void fillData(View contentView) { } this.mTvSize.setText(size); this.mTvContains.setText("-"); //$NON-NLS-1$ - DateFormat df = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT); - tvDate.setText(df.format(this.mFso.getLastModifiedTime())); + tvLastAccessedTime.setText( + FileHelper.formatFileTime(this.mContext, this.mFso.getLastAccessedTime())); + tvLastModifiedTime.setText( + FileHelper.formatFileTime(this.mContext, this.mFso.getLastModifiedTime())); + tvLastChangedTime.setText( + FileHelper.formatFileTime(this.mContext, this.mFso.getLastChangedTime())); //- Permissions String loadingMsg = this.mContext.getString(R.string.loading_message); @@ -338,6 +362,18 @@ private void fillData(View contentView) { setPermissionCheckBoxesListener(this.mChkGroupPermission); setPermissionCheckBoxesListener(this.mChkOthersPermission); + // Check if we should show "Skip media scan" toggle + if (!FileHelper.isDirectory(this.mFso) || + !StorageHelper.isPathInStorageVolume(this.mFso.getFullPath())) { + LinearLayout fsoSkipMediaScanView = + (LinearLayout)contentView.findViewById(R.id.fso_skip_media_scan_view); + fsoSkipMediaScanView.setVisibility(View.GONE); + } else { + //attach the click events + this.mChkNoMedia.setChecked(isNoMediaFilePresent()); + this.mChkNoMedia.setOnCheckedChangeListener(this); + } + //Change the tab onClick(this.mInfoViewTab); this.mIgnoreCheckEvents = false; @@ -524,8 +560,45 @@ public void onClick(View v) { */ @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - if (this.mIgnoreCheckEvents) return; + switch (buttonView.getId()) { + case R.id.fso_include_in_media_scan: + onNoMediaCheckedChanged(buttonView, isChecked); + break; + default: + onPermissionsCheckedChanged(buttonView, isChecked); + break; + } + } + + /** + * Method that manage a check changed event + * + * @param buttonView The checkbox + * @param isChecked If the checkbox is checked + */ + private void onNoMediaCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (this.mIgnoreCheckEvents) { + this.mIgnoreCheckEvents = false; + return; + } + // Checked means "skip media scan" + final File nomedia = FileHelper.getNoMediaFile(this.mFso); + if (isChecked) { + preventMediaScan(nomedia); + } else { + allowMediaScan(nomedia); + } + } + + /** + * Method that manage a check changed event + * + * @param buttonView The checkbox + * @param isChecked If the checkbox is checked + */ + private void onPermissionsCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (this.mIgnoreCheckEvents) return; try { // Cancel the folder usage command cancelFolderUsageCommand(); @@ -601,7 +674,6 @@ public void onFailed(Throwable cause) { updatePermissions(); } }); - } } @@ -1060,11 +1132,11 @@ private void printFolderUsage(final boolean computing, final boolean cancelled) // Compute folders and files string String folders = res.getQuantityString( - R.plurals.fso_properties_dialog_folders, + R.plurals.n_folders, this.mFolderUsage.getNumberOfFolders(), Integer.valueOf(this.mFolderUsage.getNumberOfFolders())); String files = res.getQuantityString( - R.plurals.fso_properties_dialog_files, + R.plurals.n_files, this.mFolderUsage.getNumberOfFiles(), Integer.valueOf(this.mFolderUsage.getNumberOfFiles())); final String contains = res.getString( @@ -1156,9 +1228,19 @@ private void applyTheme() { theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ v = this.mContentView.findViewById(R.id.fso_properties_contains); theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ - v = this.mContentView.findViewById(R.id.fso_properties_date_label); + v = this.mContentView.findViewById(R.id.fso_properties_last_accessed_label); + theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ + v = this.mContentView.findViewById(R.id.fso_properties_last_accessed); theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ - v = this.mContentView.findViewById(R.id.fso_properties_date); + v = this.mContentView.findViewById(R.id.fso_properties_last_modified_label); + theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ + v = this.mContentView.findViewById(R.id.fso_properties_last_modified); + theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ + v = this.mContentView.findViewById(R.id.fso_properties_last_changed_label); + theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ + v = this.mContentView.findViewById(R.id.fso_properties_last_changed); + theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ + v = this.mContentView.findViewById(R.id.fso_include_in_media_scan_label); theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ v = this.mContentView.findViewById(R.id.fso_properties_owner_label); @@ -1198,4 +1280,144 @@ private void applyTabTheme() { theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ } + /** + * Method that prevents media scan in the directory (creates a new .nomedia file) + * + * @param nomedia The .nomedia file + */ + private void preventMediaScan(final File nomedia) { + // Create .nomedia file. The file should not exist here + try { + if (!nomedia.createNewFile()) { + // failed to create .nomedia file + DialogHelper.showToast( + this.mContext, + this.mContext.getString( + R.string.fso_failed_to_prevent_media_scan), + Toast.LENGTH_SHORT); + this.mIgnoreCheckEvents = true; + this.mChkNoMedia.setChecked(false); + return; + } + + // Refresh the listview + this.mHasChanged = true; + + } catch (IOException ex) { + // failed to create .nomedia file + ExceptionUtil.translateException(this.mContext, ex, true, false, null); + DialogHelper.showToast( + this.mContext, + this.mContext.getString( + R.string.fso_failed_to_prevent_media_scan), + Toast.LENGTH_SHORT); + this.mIgnoreCheckEvents = true; + this.mChkNoMedia.setChecked(false); + } + } + + /** + * Method that allows media scan in the directory (removes the .nomedia file) + * + * @param nomedia The .nomedia file + */ + private void allowMediaScan(final File nomedia) { + // Delete .nomedia file. The file should exist here + + // .nomedia is a directory? Then ask the user prior to remove completely the folder + if (nomedia.isDirectory()) { + // confirm removing the dir + AlertDialog alert = DialogHelper.createYesNoDialog( + this.mContext, + R.string.fso_delete_nomedia_dir_title, + R.string.fso_delete_nomedia_dir_body, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (which == DialogInterface.BUTTON_POSITIVE) { + boolean ret = FileHelper.deleteFolder(nomedia); + if (!ret) { + DialogHelper.showToast( + FsoPropertiesDialog.this.mContext, + FsoPropertiesDialog.this.mContext.getString( + R.string.fso_failed_to_allow_media_scan), + Toast.LENGTH_SHORT); + FsoPropertiesDialog.this.mIgnoreCheckEvents = true; + FsoPropertiesDialog.this.mChkNoMedia.setChecked(true); + return; + } + + // Refresh the listview + FsoPropertiesDialog.this.mHasChanged = true; + + } else { + FsoPropertiesDialog.this.mIgnoreCheckEvents = true; + FsoPropertiesDialog.this.mChkNoMedia.setChecked(true); + } + } + }); + DialogHelper.delegateDialogShow(this.mContext, alert); + + // .nomedia file is not empty? Then ask the user prior to remove the file + } else if (nomedia.length() != 0) { + // confirm removing non empty file + AlertDialog alert = DialogHelper.createYesNoDialog( + this.mContext, + R.string.fso_delete_nomedia_non_empty_title, + R.string.fso_delete_nomedia_non_empty_body, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (which == DialogInterface.BUTTON_POSITIVE) { + if (!nomedia.delete()) { + DialogHelper.showToast( + FsoPropertiesDialog.this.mContext, + FsoPropertiesDialog.this.mContext.getString( + R.string.fso_failed_to_allow_media_scan), + Toast.LENGTH_SHORT); + FsoPropertiesDialog.this.mIgnoreCheckEvents = true; + FsoPropertiesDialog.this.mChkNoMedia.setChecked(true); + return; + } + + // Refresh the listview + FsoPropertiesDialog.this.mHasChanged = true; + + } else { + FsoPropertiesDialog.this.mIgnoreCheckEvents = true; + FsoPropertiesDialog.this.mChkNoMedia.setChecked(true); + } + } + }); + DialogHelper.delegateDialogShow(this.mContext, alert); + + // Normal .nomedia file + } else { + if (!nomedia.delete()) { + //failed to delete .nomedia file + DialogHelper.showToast( + this.mContext, + this.mContext.getString( + R.string.fso_failed_to_allow_media_scan), + Toast.LENGTH_SHORT); + FsoPropertiesDialog.this.mIgnoreCheckEvents = true; + FsoPropertiesDialog.this.mChkNoMedia.setChecked(true); + return; + } + + // Refresh the listview + FsoPropertiesDialog.this.mHasChanged = true; + } + } + + /** + * Method that checks if the .nomedia file is present + * + * @return boolean If the .nomedia file is present + */ + private boolean isNoMediaFilePresent() { + final File nomedia = FileHelper.getNoMediaFile(this.mFso); + return nomedia.exists(); + } + } diff --git a/src/com/cyanogenmod/filemanager/ui/dialogs/InitialDirectoryDialog.java b/Backbone/src/main/java/me/toolify/backbone/ui/dialogs/InitialDirectoryDialog.java similarity index 94% rename from src/com/cyanogenmod/filemanager/ui/dialogs/InitialDirectoryDialog.java rename to Backbone/src/main/java/me/toolify/backbone/ui/dialogs/InitialDirectoryDialog.java index 83a8578c3..d00954304 100644 --- a/src/com/cyanogenmod/filemanager/ui/dialogs/InitialDirectoryDialog.java +++ b/Backbone/src/main/java/me/toolify/backbone/ui/dialogs/InitialDirectoryDialog.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.ui.dialogs; +package me.toolify.backbone.ui.dialogs; import android.app.AlertDialog; import android.content.Context; @@ -26,13 +26,13 @@ import android.widget.TextView; import android.widget.Toast; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.preferences.FileManagerSettings; -import com.cyanogenmod.filemanager.preferences.Preferences; -import com.cyanogenmod.filemanager.ui.ThemeManager; -import com.cyanogenmod.filemanager.ui.ThemeManager.Theme; -import com.cyanogenmod.filemanager.ui.widgets.DirectoryInlineAutocompleteTextView; -import com.cyanogenmod.filemanager.util.DialogHelper; +import me.toolify.backbone.R; +import me.toolify.backbone.preferences.FileManagerSettings; +import me.toolify.backbone.preferences.Preferences; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.ui.ThemeManager.Theme; +import me.toolify.backbone.ui.widgets.DirectoryInlineAutocompleteTextView; +import me.toolify.backbone.util.DialogHelper; import java.io.File; diff --git a/src/com/cyanogenmod/filemanager/ui/dialogs/InputNameDialog.java b/Backbone/src/main/java/me/toolify/backbone/ui/dialogs/InputNameDialog.java similarity index 96% rename from src/com/cyanogenmod/filemanager/ui/dialogs/InputNameDialog.java rename to Backbone/src/main/java/me/toolify/backbone/ui/dialogs/InputNameDialog.java index a0aaa0a68..c8b247021 100644 --- a/src/com/cyanogenmod/filemanager/ui/dialogs/InputNameDialog.java +++ b/Backbone/src/main/java/me/toolify/backbone/ui/dialogs/InputNameDialog.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.ui.dialogs; +package me.toolify.backbone.ui.dialogs; import android.app.AlertDialog; import android.content.Context; @@ -27,12 +27,12 @@ import android.widget.EditText; import android.widget.TextView; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.model.FileSystemObject; -import com.cyanogenmod.filemanager.ui.ThemeManager; -import com.cyanogenmod.filemanager.ui.ThemeManager.Theme; -import com.cyanogenmod.filemanager.util.DialogHelper; -import com.cyanogenmod.filemanager.util.FileHelper; +import me.toolify.backbone.R; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.ui.ThemeManager.Theme; +import me.toolify.backbone.util.DialogHelper; +import me.toolify.backbone.util.FileHelper; import java.io.File; import java.util.List; @@ -57,10 +57,11 @@ public class InputNameDialog * @hide */ final List mFiles; + /** * @hide */ - final FileSystemObject mFso; + public final FileSystemObject mFso; private final boolean mAllowFsoName; private DialogInterface.OnCancelListener mOnCancelListener; diff --git a/src/com/cyanogenmod/filemanager/ui/dialogs/MessageProgressDialog.java b/Backbone/src/main/java/me/toolify/backbone/ui/dialogs/MessageProgressDialog.java similarity index 96% rename from src/com/cyanogenmod/filemanager/ui/dialogs/MessageProgressDialog.java rename to Backbone/src/main/java/me/toolify/backbone/ui/dialogs/MessageProgressDialog.java index 3b64a8978..4cacb5943 100644 --- a/src/com/cyanogenmod/filemanager/ui/dialogs/MessageProgressDialog.java +++ b/Backbone/src/main/java/me/toolify/backbone/ui/dialogs/MessageProgressDialog.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.ui.dialogs; +package me.toolify.backbone.ui.dialogs; import android.app.AlertDialog; import android.content.Context; @@ -25,10 +25,10 @@ import android.widget.TextView; import android.widget.Toast; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.ui.ThemeManager; -import com.cyanogenmod.filemanager.ui.ThemeManager.Theme; -import com.cyanogenmod.filemanager.util.DialogHelper; +import me.toolify.backbone.R; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.ui.ThemeManager.Theme; +import me.toolify.backbone.util.DialogHelper; /** * A class that wraps a dialog for showing a progress with text message (non graphical). diff --git a/Backbone/src/main/java/me/toolify/backbone/ui/image/ImageCache.java b/Backbone/src/main/java/me/toolify/backbone/ui/image/ImageCache.java new file mode 100644 index 000000000..6430d37c2 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/ui/image/ImageCache.java @@ -0,0 +1,633 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * 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 me.toolify.backbone.ui.image; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Bitmap.CompressFormat; +import android.graphics.BitmapFactory; +import android.graphics.drawable.BitmapDrawable; +import android.os.Bundle; +import android.os.Environment; +import android.app.Fragment; +import android.app.FragmentManager; +import android.support.v4.util.LruCache; +import android.util.Log; + +import me.toolify.backbone.util.DiskLruCache; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.ref.SoftReference; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.HashSet; +import java.util.Iterator; + +/** + * This class handles disk and memory caching of bitmaps in conjunction with the + * {@link ImageWorker} class and its subclasses. Use + * {@link ImageCache#getInstance(android.app.FragmentManager, ImageCacheParams)} to get an instance of this + * class, although usually a cache should be added directly to an {@link ImageWorker} by calling + * {@link ImageWorker#addImageCache(android.app.FragmentManager, ImageCacheParams)}. + */ +public class ImageCache { + private static final String TAG = "ImageCache"; + + private static boolean DEBUG = false; + + // Default memory cache size in kilobytes + private static final int DEFAULT_MEM_CACHE_SIZE = 1024 * 5; // 5MB + + // Default disk cache size in bytes + private static final int DEFAULT_DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB + + // Compression settings when writing images to disk cache + private static final CompressFormat DEFAULT_COMPRESS_FORMAT = CompressFormat.JPEG; + private static final int DEFAULT_COMPRESS_QUALITY = 70; + private static final int DISK_CACHE_INDEX = 0; + + // Constants to easily toggle various caches + private static final boolean DEFAULT_MEM_CACHE_ENABLED = true; + private static final boolean DEFAULT_DISK_CACHE_ENABLED = false; + private static final boolean DEFAULT_INIT_DISK_CACHE_ON_CREATE = false; + + private DiskLruCache mDiskLruCache; + private LruCache mMemoryCache; + private ImageCacheParams mCacheParams; + private final Object mDiskCacheLock = new Object(); + private boolean mDiskCacheStarting = true; + + private HashSet> mReusableBitmaps; + + /** + * Create a new ImageCache object using the specified parameters. This should not be + * called directly by other classes, instead use + * {@link ImageCache#getInstance(android.app.FragmentManager, ImageCacheParams)} to fetch an ImageCache + * instance. + * + * @param cacheParams The cache parameters to use to initialize the cache + */ + private ImageCache(ImageCacheParams cacheParams) { + init(cacheParams); + } + + /** + * Return an {@link ImageCache} instance. A {@link RetainFragment} is used to retain the + * ImageCache object across configuration changes such as a change in device orientation. + * + * @param fragmentManager The fragment manager to use when dealing with the retained fragment. + * @param cacheParams The cache parameters to use if the ImageCache needs instantiation. + * @return An existing retained ImageCache object or a new one if one did not exist + */ + public static ImageCache getInstance( + FragmentManager fragmentManager, ImageCacheParams cacheParams) { + + // Search for, or create an instance of the non-UI RetainFragment + final RetainFragment mRetainFragment = findOrCreateRetainFragment(fragmentManager); + + // See if we already have an ImageCache stored in RetainFragment + ImageCache imageCache = (ImageCache) mRetainFragment.getObject(); + + // No existing ImageCache, create one and store it in RetainFragment + if (imageCache == null) { + imageCache = new ImageCache(cacheParams); + mRetainFragment.setObject(imageCache); + } + + return imageCache; + } + + /** + * Initialize the cache, providing all parameters. + * + * @param cacheParams The cache parameters to initialize the cache + */ + private void init(ImageCacheParams cacheParams) { + mCacheParams = cacheParams; + + // Set up memory cache + if (mCacheParams.memoryCacheEnabled) { + if (DEBUG) { + Log.d(TAG, "Memory cache created (size = " + mCacheParams.memCacheSize + ")"); + } + + mReusableBitmaps = new HashSet>(); + + mMemoryCache = new LruCache(mCacheParams.memCacheSize) { + + /** + * Notify the removed entry that is no longer being cached + */ + @Override + protected void entryRemoved(boolean evicted, String key, + BitmapDrawable oldValue, BitmapDrawable newValue) { + + // Add the bitmap to a SoftRefrence set for possible use with inBitmap later + mReusableBitmaps.add(new SoftReference(oldValue.getBitmap())); + + } + + /** + * Measure item size in kilobytes rather than units which is more practical + * for a bitmap cache + */ + @Override + protected int sizeOf(String key, BitmapDrawable value) { + final int bitmapSize = getBitmapSize(value) / 1024; + return bitmapSize == 0 ? 1 : bitmapSize; + } + }; + } + + // By default the disk cache is not initialized here as it should be initialized + // on a separate thread due to disk access. + if (cacheParams.initDiskCacheOnCreate) { + // Set up disk cache + initDiskCache(); + } + } + + /** + * Initializes the disk cache. Note that this includes disk access so this should not be + * executed on the main/UI thread. By default an ImageCache does not initialize the disk + * cache when it is created, instead you should call initDiskCache() to initialize it on a + * background thread. + */ + public void initDiskCache() { + // Set up disk cache + synchronized (mDiskCacheLock) { + if (mDiskLruCache == null || mDiskLruCache.isClosed()) { + File diskCacheDir = mCacheParams.diskCacheDir; + if (mCacheParams.diskCacheEnabled && diskCacheDir != null) { + if (!diskCacheDir.exists()) { + diskCacheDir.mkdirs(); + } + if (getUsableSpace(diskCacheDir) > mCacheParams.diskCacheSize) { + try { + mDiskLruCache = DiskLruCache.open( + diskCacheDir, 1, 1, mCacheParams.diskCacheSize); + if (DEBUG) { + Log.d(TAG, "Disk cache initialized"); + } + } catch (final IOException e) { + mCacheParams.diskCacheDir = null; + Log.e(TAG, "initDiskCache - " + e); + } + } + } + } + mDiskCacheStarting = false; + mDiskCacheLock.notifyAll(); + } + } + + /** + * Adds a bitmap to both memory and disk cache. + * @param data Unique identifier for the bitmap to store + * @param value The bitmap drawable to store + */ + public void addBitmapToCache(String data, BitmapDrawable value) { + if (data == null || value == null) { + return; + } + + // Add to memory cache + if (mMemoryCache != null) { + mMemoryCache.put(data, value); + } + + synchronized (mDiskCacheLock) { + // Add to disk cache + if (mDiskLruCache != null) { + final String key = hashKeyForDisk(data); + OutputStream out = null; + try { + DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key); + if (snapshot == null) { + final DiskLruCache.Editor editor = mDiskLruCache.edit(key); + if (editor != null) { + out = editor.newOutputStream(DISK_CACHE_INDEX); + value.getBitmap().compress( + mCacheParams.compressFormat, mCacheParams.compressQuality, out); + editor.commit(); + out.close(); + } + } else { + snapshot.getInputStream(DISK_CACHE_INDEX).close(); + } + } catch (final IOException e) { + Log.e(TAG, "addBitmapToCache - " + e); + } catch (Exception e) { + Log.e(TAG, "addBitmapToCache - " + e); + } finally { + try { + if (out != null) { + out.close(); + } + } catch (IOException e) {} + } + } + } + } + + /** + * Get from memory cache. + * + * @param data Unique identifier for which item to get + * @return The bitmap drawable if found in cache, null otherwise + */ + public BitmapDrawable getBitmapFromMemCache(String data) { + BitmapDrawable memValue = null; + + if (mMemoryCache != null) { + memValue = mMemoryCache.get(data); + } + + if (DEBUG && memValue != null) { + Log.d(TAG, "Memory cache hit"); + } + + return memValue; + } + + /** + * Get from disk cache. + * + * @param data Unique identifier for which item to get + * @return The bitmap if found in cache, null otherwise + */ + public Bitmap getBitmapFromDiskCache(String data) { + final String key = hashKeyForDisk(data); + Bitmap bitmap = null; + + synchronized (mDiskCacheLock) { + while (mDiskCacheStarting) { + try { + mDiskCacheLock.wait(); + } catch (InterruptedException e) {} + } + if (mDiskLruCache != null) { + InputStream inputStream = null; + try { + final DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key); + if (snapshot != null) { + if (DEBUG) { + Log.d(TAG, "Disk cache hit"); + } + inputStream = snapshot.getInputStream(DISK_CACHE_INDEX); + if (inputStream != null) { + // Decode bitmap, but we don't want to sample so give + // MAX_VALUE as the target dimensions + bitmap = ImageResizer.decodeSampledBitmapFromFile( + data, Integer.MAX_VALUE, Integer.MAX_VALUE, this); + } + } + } catch (final IOException e) { + Log.e(TAG, "getBitmapFromDiskCache - " + e); + } finally { + try { + if (inputStream != null) { + inputStream.close(); + } + } catch (IOException e) {} + } + } + return bitmap; + } + } + + /** + * @param options - BitmapFactory.Options with out* options populated + * @return Bitmap that case be used for inBitmap + */ + protected Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) { + Bitmap bitmap = null; + + if (mReusableBitmaps != null && !mReusableBitmaps.isEmpty()) { + final Iterator> iterator = mReusableBitmaps.iterator(); + Bitmap item; + + while (iterator.hasNext()) { + item = iterator.next().get(); + + if (null != item && item.isMutable()) { + // Check to see it the item can be used for inBitmap + if (canUseForInBitmap(item, options)) { + bitmap = item; + + // Remove from reusable set so it can't be used again + iterator.remove(); + break; + } + } else { + // Remove from the set if the reference has been cleared. + iterator.remove(); + } + } + } + + return bitmap; + } + + /** + * Clears both the memory and disk cache associated with this ImageCache object. Note that + * this includes disk access so this should not be executed on the main/UI thread. + */ + public void clearCache() { + if (mMemoryCache != null) { + mMemoryCache.evictAll(); + if (DEBUG) { + Log.d(TAG, "Memory cache cleared"); + } + } + + synchronized (mDiskCacheLock) { + mDiskCacheStarting = true; + if (mDiskLruCache != null && !mDiskLruCache.isClosed()) { + try { + mDiskLruCache.delete(); + if (DEBUG) { + Log.d(TAG, "Disk cache cleared"); + } + } catch (IOException e) { + Log.e(TAG, "clearCache - " + e); + } + mDiskLruCache = null; + initDiskCache(); + } + } + } + + /** + * Flushes the disk cache associated with this ImageCache object. Note that this includes + * disk access so this should not be executed on the main/UI thread. + */ + public void flush() { + synchronized (mDiskCacheLock) { + if (mDiskLruCache != null) { + try { + mDiskLruCache.flush(); + if (DEBUG) { + Log.d(TAG, "Disk cache flushed"); + } + } catch (IOException e) { + Log.e(TAG, "flush - " + e); + } + } + } + } + + /** + * Closes the disk cache associated with this ImageCache object. Note that this includes + * disk access so this should not be executed on the main/UI thread. + */ + public void close() { + synchronized (mDiskCacheLock) { + if (mDiskLruCache != null) { + try { + if (!mDiskLruCache.isClosed()) { + mDiskLruCache.close(); + mDiskLruCache = null; + if (DEBUG) { + Log.d(TAG, "Disk cache closed"); + } + } + } catch (IOException e) { + Log.e(TAG, "close - " + e); + } + } + } + } + + /** + * A holder class that contains cache parameters. + */ + public static class ImageCacheParams { + public int memCacheSize = DEFAULT_MEM_CACHE_SIZE; + public int diskCacheSize = DEFAULT_DISK_CACHE_SIZE; + public File diskCacheDir; + public CompressFormat compressFormat = DEFAULT_COMPRESS_FORMAT; + public int compressQuality = DEFAULT_COMPRESS_QUALITY; + public boolean memoryCacheEnabled = DEFAULT_MEM_CACHE_ENABLED; + public boolean diskCacheEnabled = DEFAULT_DISK_CACHE_ENABLED; + public boolean initDiskCacheOnCreate = DEFAULT_INIT_DISK_CACHE_ON_CREATE; + + /** + * Create a set of image cache parameters that can be provided to + * {@link ImageCache#getInstance(android.app.FragmentManager, ImageCacheParams)} or + * {@link ImageWorker#addImageCache(android.app.FragmentManager, ImageCacheParams)}. + * @param context A context to use. + * @param diskCacheDirectoryName A unique subdirectory name that will be appended to the + * application cache directory. Usually "cache" or "images" + * is sufficient. + */ + public ImageCacheParams(Context context, String diskCacheDirectoryName) { + diskCacheDir = getDiskCacheDir(context, diskCacheDirectoryName); + } + + /** + * Sets the memory cache size based on a percentage of the max available VM memory. + * Eg. setting percent to 0.2 would set the memory cache to one fifth of the available + * memory. Throws {@link IllegalArgumentException} if percent is < 0.05 or > .8. + * memCacheSize is stored in kilobytes instead of bytes as this will eventually be passed + * to construct a LruCache which takes an int in its constructor. + * + * This value should be chosen carefully based on a number of factors + * Refer to the corresponding Android Training class for more discussion: + * http://developer.android.com/training/displaying-bitmaps/ + * + * @param percent Percent of available app memory to use to size memory cache + */ + public void setMemCacheSizePercent(float percent) { + if (percent < 0.05f || percent > 0.8f) { + throw new IllegalArgumentException("setMemCacheSizePercent - percent must be " + + "between 0.05 and 0.8 (inclusive)"); + } + memCacheSize = Math.round(percent * Runtime.getRuntime().maxMemory() / 1024); + } + } + + /** + * @param candidate - Bitmap to check + * @param targetOptions - Options that have the out* value populated + * @return true if candidate can be used for inBitmap re-use with + * targetOptions + */ + private static boolean canUseForInBitmap( + Bitmap candidate, BitmapFactory.Options targetOptions) { + int width = targetOptions.outWidth / targetOptions.inSampleSize; + int height = targetOptions.outHeight / targetOptions.inSampleSize; + + return candidate.getWidth() == width && candidate.getHeight() == height; + } + + /** + * Get a usable cache directory (external if available, internal otherwise). + * + * @param context The context to use + * @param uniqueName A unique directory name to append to the cache dir + * @return The cache dir + */ + public static File getDiskCacheDir(Context context, String uniqueName) { + // Check if media is mounted or storage is built-in, if so, try and use external cache dir + // otherwise use internal cache dir + final String cachePath = + Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || + !isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() : + context.getCacheDir().getPath(); + + return new File(cachePath + File.separator + uniqueName); + } + + /** + * A hashing method that changes a string (like a URL) into a hash suitable for using as a + * disk filename. + */ + public static String hashKeyForDisk(String key) { + String cacheKey; + try { + final MessageDigest mDigest = MessageDigest.getInstance("MD5"); + mDigest.update(key.getBytes()); + cacheKey = bytesToHexString(mDigest.digest()); + } catch (NoSuchAlgorithmException e) { + cacheKey = String.valueOf(key.hashCode()); + } + return cacheKey; + } + + private static String bytesToHexString(byte[] bytes) { + // http://stackoverflow.com/questions/332079 + StringBuilder sb = new StringBuilder(); + for (byte aByte : bytes) { + String hex = Integer.toHexString(0xFF & aByte); + if (hex.length() == 1) { + sb.append('0'); + } + sb.append(hex); + } + return sb.toString(); + } + + /** + * Get the size in bytes of a bitmap in a BitmapDrawable. + * @param value BitmapDrawable to get the file size of + * @return size in bytes + */ + @TargetApi(12) + public static int getBitmapSize(BitmapDrawable value) { + return value.getBitmap().getByteCount(); + } + + /** + * Check if external storage is built-in or removable. + * + * @return True if external storage is removable (like an SD card), false + * otherwise. + */ + @TargetApi(9) + public static boolean isExternalStorageRemovable() { + + return Environment.isExternalStorageRemovable(); + + } + + /** + * Get the external app cache directory. + * + * @param context The context to use + * @return The external cache dir + */ + @TargetApi(8) + public static File getExternalCacheDir(Context context) { + return context.getExternalCacheDir(); + } + + /** + * Check how much usable space is available at a given path. + * + * @param path The path to check + * @return The space available in bytes + */ + @TargetApi(9) + public static long getUsableSpace(File path) { + return path.getUsableSpace(); + } + + /** + * Locate an existing instance of this Fragment or if not found, create and + * add it using FragmentManager. + * + * @param fm The FragmentManager manager to use. + * @return The existing instance of the Fragment or the new instance if just + * created. + */ + private static RetainFragment findOrCreateRetainFragment(FragmentManager fm) { + // Check to see if we have retained the worker fragment. + RetainFragment mRetainFragment = (RetainFragment) fm.findFragmentByTag(TAG); + + // If not retained (or first time running), we need to create and add it. + if (mRetainFragment == null) { + mRetainFragment = new RetainFragment(); + fm.beginTransaction().add(mRetainFragment, TAG).commitAllowingStateLoss(); + } + + return mRetainFragment; + } + + /** + * A simple non-UI Fragment that stores a single Object and is retained over configuration + * changes. It will be used to retain the ImageCache object. + */ + public static class RetainFragment extends Fragment { + private Object mObject; + + /** + * Empty constructor as per the Fragment documentation + */ + public RetainFragment() {} + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Make sure this Fragment is retained over a configuration change + setRetainInstance(true); + } + + /** + * Store a single object in this Fragment. + * + * @param object The object to store + */ + public void setObject(Object object) { + mObject = object; + } + + /** + * Get the stored object. + * + * @return The stored object + */ + public Object getObject() { + return mObject; + } + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/ui/image/ImageFetcher.java b/Backbone/src/main/java/me/toolify/backbone/ui/image/ImageFetcher.java new file mode 100644 index 000000000..b9bf40c73 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/ui/image/ImageFetcher.java @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * 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 me.toolify.backbone.ui.image; + +import android.content.Context; +import android.graphics.Bitmap; +import android.util.Log; + +import me.toolify.backbone.util.DiskLruCache; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; + +/** + * A simple subclass of {@link ImageResizer} that fetches and resizes images fetched from a URL. + */ +public class ImageFetcher extends ImageResizer { + private static final String TAG = "BB.ImageFetcher"; + private static boolean DEBUG = true; + private static final int HTTP_CACHE_SIZE = 10 * 1024 * 1024; // 10MB + private static final String HTTP_CACHE_DIR = "http"; + private static final int IO_BUFFER_SIZE = 8 * 1024; + + private DiskLruCache mHttpDiskCache; + private File mHttpCacheDir; + private boolean mHttpDiskCacheStarting = true; + private final Object mHttpDiskCacheLock = new Object(); + private static final int DISK_CACHE_INDEX = 0; + + /** + * Initialize providing a target image width and height for the processing images. + * + * @param context + * @param imageWidth + * @param imageHeight + */ + public ImageFetcher(Context context, int imageWidth, int imageHeight) { + super(context, imageWidth, imageHeight); + init(context); + } + + /** + * Initialize providing a single target image size (used for both width and height); + * + * @param context + * @param imageSize + */ + public ImageFetcher(Context context, int imageSize) { + super(context, imageSize); + init(context); + } + + private void init(Context context) { + //checkConnection(context); + mHttpCacheDir = ImageCache.getDiskCacheDir(context, HTTP_CACHE_DIR); + } + + @Override + protected void initDiskCacheInternal() { + super.initDiskCacheInternal(); + initHttpDiskCache(); + } + + private void initHttpDiskCache() { + if (!mHttpCacheDir.exists()) { + mHttpCacheDir.mkdirs(); + } + synchronized (mHttpDiskCacheLock) { + if (ImageCache.getUsableSpace(mHttpCacheDir) > HTTP_CACHE_SIZE) { + try { + mHttpDiskCache = DiskLruCache.open(mHttpCacheDir, 1, 1, HTTP_CACHE_SIZE); + if (DEBUG) { + Log.d(TAG, "HTTP cache initialized"); + } + } catch (IOException e) { + mHttpDiskCache = null; + } + } + mHttpDiskCacheStarting = false; + mHttpDiskCacheLock.notifyAll(); + } + } + + @Override + protected void clearCacheInternal() { + super.clearCacheInternal(); + synchronized (mHttpDiskCacheLock) { + if (mHttpDiskCache != null && !mHttpDiskCache.isClosed()) { + try { + mHttpDiskCache.delete(); + if (DEBUG) { + Log.d(TAG, "HTTP cache cleared"); + } + } catch (IOException e) { + Log.e(TAG, "clearCacheInternal - " + e); + } + mHttpDiskCache = null; + mHttpDiskCacheStarting = true; + initHttpDiskCache(); + } + } + } + + @Override + protected void flushCacheInternal() { + super.flushCacheInternal(); + synchronized (mHttpDiskCacheLock) { + if (mHttpDiskCache != null) { + try { + mHttpDiskCache.flush(); + if (DEBUG) { + Log.d(TAG, "HTTP cache flushed"); + } + } catch (IOException e) { + Log.e(TAG, "flush - " + e); + } + } + } + } + + @Override + protected void closeCacheInternal() { + super.closeCacheInternal(); + synchronized (mHttpDiskCacheLock) { + if (mHttpDiskCache != null) { + try { + if (!mHttpDiskCache.isClosed()) { + mHttpDiskCache.close(); + mHttpDiskCache = null; + if (DEBUG) { + Log.d(TAG, "HTTP cache closed"); + } + } + } catch (IOException e) { + Log.e(TAG, "closeCacheInternal - " + e); + } + } + } + } + +/* *//** + * Simple network connection check. + * + * @param context + *//* + private void checkConnection(Context context) { + final ConnectivityManager cm = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + final NetworkInfo networkInfo = cm.getActiveNetworkInfo(); + if (networkInfo == null || !networkInfo.isConnectedOrConnecting()) { + //Toast.makeText(context, R.string.no_network_connection_toast, Toast.LENGTH_LONG).show(); + Log.e(TAG, "checkConnection - no connection found"); + } + }*/ + + /** + * The main process method, which will be called by the ImageWorker in the ImageAsyncTask background + * thread. + * + * @param data The data to load the bitmap, in this case, a regular http URL + * @return The downloaded and resized bitmap + */ + private Bitmap processBitmap(String data) { + if (DEBUG) { + Log.d(TAG, "processBitmap - " + data); + } + + Bitmap bitmap = null; + if (data != null) { + bitmap = decodeSampledBitmapFromFile(data, mImageWidth, + mImageHeight, getImageCache()); + } + + return bitmap; + } + + @Override + protected Bitmap processBitmap(Object data) { + return processBitmap(String.valueOf(data)); + } + + /** + * Download a bitmap from a URL and write the content to an output stream. + * + * @param urlString The URL to fetch + * @return true if successful, false otherwise + */ + public boolean downloadUrlToStream(String urlString, OutputStream outputStream) { + HttpURLConnection urlConnection = null; + BufferedOutputStream out = null; + BufferedInputStream in = null; + + try { + final URL url = new URL(urlString); + urlConnection = (HttpURLConnection) url.openConnection(); + in = new BufferedInputStream(urlConnection.getInputStream(), IO_BUFFER_SIZE); + out = new BufferedOutputStream(outputStream, IO_BUFFER_SIZE); + + int b; + while ((b = in.read()) != -1) { + out.write(b); + } + return true; + } catch (final IOException e) { + Log.e(TAG, "Error in downloadBitmap - " + e); + } finally { + if (urlConnection != null) { + urlConnection.disconnect(); + } + try { + if (out != null) { + out.close(); + } + if (in != null) { + in.close(); + } + } catch (final IOException e) {} + } + return false; + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/ui/image/ImageResizer.java b/Backbone/src/main/java/me/toolify/backbone/ui/image/ImageResizer.java new file mode 100644 index 000000000..7e6dd6947 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/ui/image/ImageResizer.java @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * 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 me.toolify.backbone.ui.image; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.Build; +import android.util.Log; + +import java.io.FileDescriptor; + +/** + * A simple subclass of {@link ImageWorker} that resizes images from resources given a target width + * and height. Useful for when the input images might be too large to simply load directly into + * memory. + */ +public class ImageResizer extends ImageWorker { + private static final String TAG = "ImageResizer"; + private static boolean DEBUG = false; + protected int mImageWidth; + protected int mImageHeight; + + /** + * Initialize providing a single target image size (used for both width and height); + * + * @param context + * @param imageWidth Desired image width to be stored for later use + * @param imageHeight Desired image height to be stored for later use + */ + public ImageResizer(Context context, int imageWidth, int imageHeight) { + super(context); + setImageSize(imageWidth, imageHeight); + } + + /** + * Initialize providing a single target image size (used for both width and height); + * + * @param context + * @param imageSize Desired image height/width to be stored for later use + */ + public ImageResizer(Context context, int imageSize) { + super(context); + setImageSize(imageSize); + } + + /** + * Set the target image width and height. + * + * @param width Desired image width to be stored for later use + * @param height Desired image height to be stored for later use + */ + public void setImageSize(int width, int height) { + mImageWidth = width; + mImageHeight = height; + } + + /** + * Set the target image size (width and height will be the same). + * + * @param size Desired image height/width to be stored for later use + */ + public void setImageSize(int size) { + setImageSize(size, size); + } + + /** + * The main processing method. This happens in a background task. In this case we are just + * sampling down the bitmap and returning it from a resource. + * + * @param resId Resource id of the drawable to be downsized + * @return + */ + private Bitmap processBitmap(int resId) { + if(DEBUG) { + Log.d(TAG, "processBitmap - " + resId); + } + return decodeSampledBitmapFromResource(mResources, resId, mImageWidth, + mImageHeight, getImageCache()); + } + + @Override + protected Bitmap processBitmap(Object data) { + return processBitmap(Integer.parseInt(String.valueOf(data))); + } + + /** + * Decode and sample down a bitmap from resources to the requested width and height. + * + * @param res The resources object containing the image data + * @param resId The resource id of the image data + * @param reqWidth The requested width of the resulting bitmap + * @param reqHeight The requested height of the resulting bitmap + * @param cache The ImageCache used to find candidate bitmaps for use with inBitmap + * @return A bitmap sampled down from the original with the same aspect ratio and dimensions + * that are equal to or greater than the requested width and height + */ + public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, + int reqWidth, int reqHeight, ImageCache cache) { + + // First decode with inJustDecodeBounds=true to check dimensions + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeResource(res, resId, options); + + // Calculate inSampleSize + options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); + + // Use inBitmap since we're only supporting 4.0+ + addInBitmapOptions(options, cache); + + // Decode bitmap with inSampleSize set + options.inJustDecodeBounds = false; + return BitmapFactory.decodeResource(res, resId, options); + } + + /** + * Decode and sample down a bitmap from a file to the requested width and height. + * + * @param filename The full path of the file to decode + * @param reqWidth The requested width of the resulting bitmap + * @param reqHeight The requested height of the resulting bitmap + * @param cache The ImageCache used to find candidate bitmaps for use with inBitmap + * @return A bitmap sampled down from the original with the same aspect ratio and dimensions + * that are equal to or greater than the requested width and height + */ + public static Bitmap decodeSampledBitmapFromFile(String filename, + int reqWidth, int reqHeight, ImageCache cache) { + + // First decode with inJustDecodeBounds=true to check dimensions + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(filename, options); + + // Calculate inSampleSize + options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); + + // Use inBitmap since we're only supporting 4.0+ + addInBitmapOptions(options, cache); + + // Decode bitmap with inSampleSize set + options.inJustDecodeBounds = false; + return BitmapFactory.decodeFile(filename, options); + } + + /** + * Decode and sample down a bitmap from a file input stream to the requested width and height. + * + * @param fileDescriptor The file descriptor to read from + * @param reqWidth The requested width of the resulting bitmap + * @param reqHeight The requested height of the resulting bitmap + * @param cache The ImageCache used to find candidate bitmaps for use with inBitmap + * @return A bitmap sampled down from the original with the same aspect ratio and dimensions + * that are equal to or greater than the requested width and height + */ + public static Bitmap decodeSampledBitmapFromDescriptor( + FileDescriptor fileDescriptor, int reqWidth, int reqHeight, ImageCache cache) { + + // First decode with inJustDecodeBounds=true to check dimensions + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options); + + // Calculate inSampleSize + options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); + + // Decode bitmap with inSampleSize set + options.inJustDecodeBounds = false; + + // If we're running on Honeycomb or newer, try to use inBitmap + addInBitmapOptions(options, cache); + + return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options); + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + private static void addInBitmapOptions(BitmapFactory.Options options, ImageCache cache) { + // inBitmap only works with mutable bitmaps so force the decoder to + // return mutable bitmaps. + options.inMutable = true; + //options.inPurgeable = true; + + if (cache != null) { + // Try and find a bitmap to use for inBitmap + Bitmap inBitmap = cache.getBitmapFromReusableSet(options); + + if (inBitmap != null) { + if (DEBUG) { + Log.d(TAG, "Found bitmap to use for inBitmap"); + } + options.inBitmap = inBitmap; + } + } + } + + /** + * Calculate an inSampleSize for use in a {@link android.graphics.BitmapFactory.Options} object when decoding + * bitmaps using the decode* methods from {@link android.graphics.BitmapFactory}. This implementation calculates + * the closest inSampleSize that will result in the final decoded bitmap having a width and + * height equal to or larger than the requested width and height. This implementation does not + * ensure a power of 2 is returned for inSampleSize which can be faster when decoding but + * results in a larger bitmap which isn't as useful for caching purposes. + * + * @param options An options object with out* params already populated (run through a decode* + * method with inJustDecodeBounds==true + * @param reqWidth The requested width of the resulting bitmap + * @param reqHeight The requested height of the resulting bitmap + * @return The value to be used for inSampleSize + */ + public static int calculateInSampleSize(BitmapFactory.Options options, + int reqWidth, int reqHeight) { + // Raw height and width of image + final int height = options.outHeight; + final int width = options.outWidth; + int inSampleSize = 1; + + if (height > reqHeight || width > reqWidth) { + + // Calculate ratios of height and width to requested height and width + final int heightRatio = Math.round((float) height / (float) reqHeight); + final int widthRatio = Math.round((float) width / (float) reqWidth); + + // Choose the smallest ratio as inSampleSize value, this will guarantee a final image + // with both dimensions larger than or equal to the requested height and width. + inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; + + // This offers some additional logic in case the image has a strange + // aspect ratio. For example, a panorama may have a much larger + // width than height. In these cases the total pixels might still + // end up being too large to fit comfortably in memory, so we should + // be more aggressive with sample down the image (=larger inSampleSize). + + final float totalPixels = width * height; + + // Anything more than 2x the requested pixels we'll sample down further + final float totalReqPixelsCap = reqWidth * reqHeight * 2; + + while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) { + inSampleSize++; + } + } + return inSampleSize; + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/ui/image/ImageWorker.java b/Backbone/src/main/java/me/toolify/backbone/ui/image/ImageWorker.java new file mode 100644 index 000000000..7f36fb911 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/ui/image/ImageWorker.java @@ -0,0 +1,506 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * 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 me.toolify.backbone.ui.image; + +import android.app.Activity; +import android.app.FragmentManager; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.TransitionDrawable; +import android.os.AsyncTask; +import android.os.Build; +import android.util.Log; +import android.widget.ImageView; + +import me.toolify.backbone.tasks.ImageAsyncTask; + +import java.lang.ref.WeakReference; + +/** + * This class wraps up completing some arbitrary long running work when loading a bitmap to an + * ImageView. It handles things like using a memory and disk cache, running the work in a background + * thread and setting a placeholder image. + */ +public abstract class ImageWorker { + private static final String TAG = "ImageWorker"; + private static boolean DEBUG = false; + + private static final int FADE_IN_TIME = 200; + private final Context mContext; + + private ImageCache mImageCache; + private ImageCache.ImageCacheParams mImageCacheParams; + private Bitmap mLoadingBitmap; + private boolean mFadeInBitmap = true; + private boolean mExitTasksEarly = false; + protected boolean mPauseWork = false; + private final Object mPauseWorkLock = new Object(); + + protected Resources mResources; + + private static final int MESSAGE_CLEAR = 0; + private static final int MESSAGE_INIT_DISK_CACHE = 1; + private static final int MESSAGE_FLUSH = 2; + private static final int MESSAGE_CLOSE = 3; + + protected ImageWorker(Context context) { + mContext = context; + mResources = context.getResources(); + } + + /** + * Load an image specified by the data parameter into an ImageView (override + * {@link ImageWorker#processBitmap(Object)} to define the processing logic). A memory and + * disk cache will be used if an {@link ImageCache} has been added using + * {@link ImageWorker#addImageCache(android.app.FragmentManager, ImageCache.ImageCacheParams)}. If the + * image is found in the memory cache, it is set immediately, otherwise an {@link AsyncTask} + * will be created to asynchronously load the bitmap. + * + * @param path The path of the image to download. + * @param imageView The ImageView to bind the scaled down image to. + */ + public void loadImage(String path, ImageView imageView) { + if(DEBUG) + Log.v(TAG, "loadImage(" + path + ")"); + if (path == null) { + return; + } + + BitmapDrawable value = null; + + if (mImageCache != null) { + value = mImageCache.getBitmapFromMemCache(String.valueOf(path)); + } + + if (value != null) { + // Bitmap found in memory cache + imageView.setImageDrawable(value); + } else if (cancelPotentialWork(path, imageView)) { + ImageAsyncTask task = new BitmapWorkerTask(imageView); + final ThumbnailAsyncDrawable thumbnailAsyncDrawable = + new ThumbnailAsyncDrawable(mResources, mLoadingBitmap, task); + imageView.setImageDrawable(thumbnailAsyncDrawable); + + // NOTE: This uses a custom version of ImageAsyncTask that has been pulled from the + // framework and slightly modified. Refer to the docs at the top of the class + // for more info on what was changed. + task.executeOnExecutor(ImageAsyncTask.DUAL_THREAD_EXECUTOR, path); + } + } + + /** + * Set placeholder bitmap that shows when the the background thread is running. + * + * @param bitmap The bitmap to be used as the 'loading' bitmap + */ + public void setLoadingImage(Bitmap bitmap) { + mLoadingBitmap = bitmap; + } + + /** + * Set placeholder bitmap that shows when the the background thread is running. + * + * @param resId + */ + public void setLoadingImage(int resId) { + mLoadingBitmap = BitmapFactory.decodeResource(mResources, resId); + } + + /** + * Adds an {@link ImageCache} to this {@link ImageWorker} to handle disk and memory bitmap + * caching. + * @param fragmentManager + * @param cacheParams The cache parameters to use for the image cache. + */ + public void addImageCache(FragmentManager fragmentManager, + ImageCache.ImageCacheParams cacheParams) { + mImageCacheParams = cacheParams; + mImageCache = ImageCache.getInstance(fragmentManager, mImageCacheParams); + new CacheAsyncTask().execute(MESSAGE_INIT_DISK_CACHE); + } + + /** + * Adds an {@link ImageCache} to this {@link ImageWorker} to handle disk and memory bitmap + * caching. + * @param activity + * @param diskCacheDirectoryName See + * {@link ImageCache.ImageCacheParams#ImageCacheParams(android.content.Context, String)}. + */ + public void addImageCache(Activity activity, String diskCacheDirectoryName) { + mImageCacheParams = new ImageCache.ImageCacheParams(activity, diskCacheDirectoryName); + mImageCache = ImageCache.getInstance(activity.getFragmentManager(), mImageCacheParams); + new CacheAsyncTask().execute(MESSAGE_INIT_DISK_CACHE); + } + + /** + * If set to true, the image will fade-in once it has been loaded by the background thread. + */ + public void setImageFadeIn(boolean fadeIn) { + mFadeInBitmap = fadeIn; + } + + public void setExitTasksEarly(boolean exitTasksEarly) { + mExitTasksEarly = exitTasksEarly; + setPauseWork(false); + } + + /** + * Subclasses should override this to define any processing or work that must happen to produce + * the final bitmap. This will be executed in a background thread and be long running. For + * example, you could resize a large bitmap here, or pull down an image from the network. + * + * @param data The data to identify which image to process, as provided by + * ImageWorker.loadImage(Object, android.widget.ImageView) + * @return The processed bitmap + */ + protected abstract Bitmap processBitmap(Object data); + + /** + * @return The {@link ImageCache} object currently being used by this ImageWorker. + */ + protected ImageCache getImageCache() { + return mImageCache; + } + + /** + * Cancels any pending work attached to the provided ImageView. + * @param imageView + */ + public static void cancelWork(ImageView imageView) { + final ImageAsyncTask bitmapWorkerTask = getBitmapWorkerTask(imageView); + if (bitmapWorkerTask != null) { + bitmapWorkerTask.cancel(true); +// if (DEBUG) { +// final Object bitmapData = bitmapWorkerTask.data; +// Log.d(TAG, "cancelWork - cancelled work for " + bitmapData); +// } + } + } + + /** + * Returns true if the current work has been canceled or if there was no work in + * progress on this image view. + * Returns false if the work in progress deals with the same data. The work is not + * stopped in that case. + */ + public static boolean cancelPotentialWork(Object data, ImageView imageView) { + final ImageAsyncTask bitmapWorkerTask = getBitmapWorkerTask(imageView); + + if (bitmapWorkerTask != null) { + if(bitmapWorkerTask instanceof BitmapWorkerTask) + { + final Object bitmapData = ((BitmapWorkerTask)bitmapWorkerTask).data; + if (bitmapData == null || !bitmapData.equals(data)) { + bitmapWorkerTask.cancel(true); + if (DEBUG) { + Log.d(TAG, "cancelPotentialWork - cancelled work for " + data); + } + } else { + // The same work is already in progress. + return false; + } + } + } + return true; + } + + /** + * @param imageView Any imageView + * @return Retrieve the currently active work task (if any) associated with this imageView. + * null if there is no such task. + */ + private static ImageAsyncTask getBitmapWorkerTask(ImageView imageView) { + if (imageView != null) { + final Drawable drawable = imageView.getDrawable(); + if (drawable instanceof ThumbnailAsyncDrawable) { + final ThumbnailAsyncDrawable thumbnailAsyncDrawable = (ThumbnailAsyncDrawable) drawable; + return thumbnailAsyncDrawable.getBitmapWorkerTask(); + } + } + return null; + } + + /** + * The actual ImageAsyncTask that will asynchronously process the image. + */ + private class BitmapWorkerTask extends ImageAsyncTask { + private Object data; + private final WeakReference imageViewReference; + + public BitmapWorkerTask(ImageView imageView) { + imageViewReference = new WeakReference(imageView); + } + + /** + * Background processing. + */ + @Override + protected BitmapDrawable doInBackground(Object... params) { + if (DEBUG) { + Log.d(TAG, "doInBackground - starting work"); + } + + data = params[0]; + final String dataString = String.valueOf(data); + Bitmap bitmap = null; + BitmapDrawable drawable = null; + + // Wait here if work is paused and the task is not cancelled + synchronized (mPauseWorkLock) { + while (mPauseWork && !isCancelled()) { + try { + mPauseWorkLock.wait(); + } catch (InterruptedException e) {} + } + } + + // If the image cache is available and this task has not been cancelled by another + // thread and the ImageView that was originally bound to this task is still bound back + // to this task and our "exit early" flag is not set then try and fetch the bitmap from + // the cache + if (mImageCache != null && !isCancelled() && getAttachedImageView() != null + && !mExitTasksEarly) { + bitmap = mImageCache.getBitmapFromDiskCache(dataString); + } + + // If the bitmap was not found in the cache and this task has not been cancelled by + // another thread and the ImageView that was originally bound to this task is still + // bound back to this task and our "exit early" flag is not set, then call the main + // process method (as implemented by a subclass) + if (bitmap == null && !isCancelled() && getAttachedImageView() != null + && !mExitTasksEarly) { + String path = String.valueOf(params[0]).toLowerCase(); + if(path.endsWith(".apk")) + bitmap = processApk(String.valueOf(params[0])); + else + bitmap = processBitmap(params[0]); + } + + // If the bitmap was processed and the image cache is available, then add the processed + // bitmap to the cache for future use. Note we don't check if the task was cancelled + // here, if it was, and the thread is still running, we may as well add the processed + // bitmap to our cache as it might be used again in the future + if (bitmap != null) { + // Running on Honeycomb or newer, so wrap in a standard BitmapDrawable + drawable = new BitmapDrawable(mResources, bitmap); + + if (mImageCache != null) { + mImageCache.addBitmapToCache(dataString, drawable); + } + } + + if (DEBUG) { + Log.d(TAG, "doInBackground - finished work"); + } + + return drawable; + } + + private Bitmap processApk(String path) + { + Bitmap bitmap = null; + PackageInfo packageInfo = mContext.getPackageManager().getPackageArchiveInfo( + path, PackageManager.GET_ACTIVITIES); + if (packageInfo != null) { + ApplicationInfo appInfo = packageInfo.applicationInfo; + if (Build.VERSION.SDK_INT >= 8) { + appInfo.sourceDir = path; + appInfo.publicSourceDir = path; + } + Drawable icon = appInfo.loadIcon(mContext.getPackageManager()); + bitmap = ((BitmapDrawable) icon).getBitmap(); + } + return bitmap; + } + + /** + * Once the image is processed, associates it to the imageView + */ + @Override + protected void onPostExecute(BitmapDrawable value) { + // if cancel was called on this task or the "exit early" flag is set then we're done + if (isCancelled() || mExitTasksEarly) { + value = null; + } + + final ImageView imageView = getAttachedImageView(); + if (value != null && imageView != null) { + if (DEBUG) { + Log.d(TAG, "onPostExecute - setting bitmap"); + } + setImageDrawable(imageView, value); + } + } + + @Override + protected void onCancelled(BitmapDrawable value) { + super.onCancelled(value); + synchronized (mPauseWorkLock) { + mPauseWorkLock.notifyAll(); + } + } + + /** + * Returns the ImageView associated with this task as long as the ImageView's task still + * points to this task as well. Returns null otherwise. + */ + private ImageView getAttachedImageView() { + final ImageView imageView = imageViewReference.get(); + final ImageAsyncTask bitmapWorkerTask = getBitmapWorkerTask(imageView); + + if (this == bitmapWorkerTask) { + return imageView; + } + + return null; + } + } + + /** + * A custom Drawable that will be attached to the imageView while the work is in progress. + * Contains a reference to the actual worker task, so that it can be stopped if a new binding is + * required, and makes sure that only the last started worker process can bind its result, + * independently of the finish order. + */ + private static class ThumbnailAsyncDrawable extends BitmapDrawable { + private final WeakReference bitmapWorkerTaskReference; + + public ThumbnailAsyncDrawable(Resources res, Bitmap bitmap, ImageAsyncTask bitmapWorkerTask) { + super(res, bitmap); + bitmapWorkerTaskReference = + new WeakReference(bitmapWorkerTask); + } + + public ImageAsyncTask getBitmapWorkerTask() { + return bitmapWorkerTaskReference.get(); + } + } + + /** + * Called when the processing is complete and the final drawable should be + * set on the ImageView. + * + * @param imageView + * @param drawable + */ + private void setImageDrawable(ImageView imageView, Drawable drawable) { + if (mFadeInBitmap) { + // Transition drawable with a transparent drawable and the final drawable + final TransitionDrawable td = + new TransitionDrawable(new Drawable[] { + new ColorDrawable(android.R.color.transparent), + drawable + }); + // Set background to loading bitmap + imageView.setBackgroundDrawable( + new BitmapDrawable(mResources, mLoadingBitmap)); + + imageView.setImageDrawable(td); + td.startTransition(FADE_IN_TIME); + } else { + imageView.setImageDrawable(drawable); + } + } + + /** + * Pause any ongoing background work. This can be used as a temporary + * measure to improve performance. For example background work could + * be paused when a ListView or GridView is being scrolled using a + * {@link android.widget.AbsListView.OnScrollListener} to keep + * scrolling smooth. + *

+ * If work is paused, be sure setPauseWork(false) is called again + * before your fragment or activity is destroyed (for example during + * {@link android.app.Activity#onPause()}), or there is a risk the + * background thread will never finish. + */ + public void setPauseWork(boolean pauseWork) { + synchronized (mPauseWorkLock) { + mPauseWork = pauseWork; + if (!mPauseWork) { + mPauseWorkLock.notifyAll(); + } + } + } + + protected class CacheAsyncTask extends ImageAsyncTask { + + @Override + protected Void doInBackground(Object... params) { + switch ((Integer)params[0]) { + case MESSAGE_CLEAR: + clearCacheInternal(); + break; + case MESSAGE_INIT_DISK_CACHE: + initDiskCacheInternal(); + break; + case MESSAGE_FLUSH: + flushCacheInternal(); + break; + case MESSAGE_CLOSE: + closeCacheInternal(); + break; + } + return null; + } + } + + protected void initDiskCacheInternal() { + if (mImageCache != null) { + mImageCache.initDiskCache(); + } + } + + protected void clearCacheInternal() { + if (mImageCache != null) { + mImageCache.clearCache(); + } + } + + protected void flushCacheInternal() { + if (mImageCache != null) { + mImageCache.flush(); + } + } + + protected void closeCacheInternal() { + if (mImageCache != null) { + mImageCache.close(); + mImageCache = null; + } + } + + public void clearCache() { + new CacheAsyncTask().execute(MESSAGE_CLEAR); + } + + public void flushCache() { + new CacheAsyncTask().execute(MESSAGE_FLUSH); + } + + public void closeCache() { + new CacheAsyncTask().execute(MESSAGE_CLOSE); + } +} diff --git a/src/com/cyanogenmod/filemanager/ui/policy/ActionsPolicy.java b/Backbone/src/main/java/me/toolify/backbone/ui/policy/ActionsPolicy.java similarity index 94% rename from src/com/cyanogenmod/filemanager/ui/policy/ActionsPolicy.java rename to Backbone/src/main/java/me/toolify/backbone/ui/policy/ActionsPolicy.java index 8964c5efb..99567ef14 100644 --- a/src/com/cyanogenmod/filemanager/ui/policy/ActionsPolicy.java +++ b/Backbone/src/main/java/me/toolify/backbone/ui/policy/ActionsPolicy.java @@ -14,17 +14,17 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.ui.policy; +package me.toolify.backbone.ui.policy; import android.content.Context; import android.os.AsyncTask; import android.text.Spanned; import android.widget.Toast; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.ui.dialogs.MessageProgressDialog; -import com.cyanogenmod.filemanager.util.DialogHelper; -import com.cyanogenmod.filemanager.util.ExceptionUtil; +import me.toolify.backbone.R; +import me.toolify.backbone.ui.dialogs.MessageProgressDialog; +import me.toolify.backbone.util.DialogHelper; +import me.toolify.backbone.util.ExceptionUtil; /** @@ -33,7 +33,7 @@ public abstract class ActionsPolicy { /** - * An interface for using in conjunction with AsyncTask for have + * An interface for using in conjunction with ImageAsyncTask for have * a */ protected interface BackgroundCallable { diff --git a/src/com/cyanogenmod/filemanager/ui/policy/BookmarksActionPolicy.java b/Backbone/src/main/java/me/toolify/backbone/ui/policy/BookmarksActionPolicy.java similarity index 77% rename from src/com/cyanogenmod/filemanager/ui/policy/BookmarksActionPolicy.java rename to Backbone/src/main/java/me/toolify/backbone/ui/policy/BookmarksActionPolicy.java index bc1d39f60..3ad6e453d 100644 --- a/src/com/cyanogenmod/filemanager/ui/policy/BookmarksActionPolicy.java +++ b/Backbone/src/main/java/me/toolify/backbone/ui/policy/BookmarksActionPolicy.java @@ -14,18 +14,18 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.ui.policy; +package me.toolify.backbone.ui.policy; import android.content.Context; import android.widget.Toast; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.model.Bookmark; -import com.cyanogenmod.filemanager.model.Bookmark.BOOKMARK_TYPE; -import com.cyanogenmod.filemanager.model.FileSystemObject; -import com.cyanogenmod.filemanager.preferences.Bookmarks; -import com.cyanogenmod.filemanager.util.DialogHelper; -import com.cyanogenmod.filemanager.util.ExceptionUtil; +import me.toolify.backbone.R; +import me.toolify.backbone.model.Bookmark; +import me.toolify.backbone.model.Bookmark.BOOKMARK_TYPE; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.preferences.Bookmarks; +import me.toolify.backbone.util.DialogHelper; +import me.toolify.backbone.util.ExceptionUtil; /** * A class with the convenience methods for resolve bookmarks related actions @@ -42,7 +42,7 @@ public static void addToBookmarks(final Context ctx, final FileSystemObject fso) try { // Create the bookmark Bookmark bookmark = - new Bookmark(BOOKMARK_TYPE.USER_DEFINED, fso.getName(), fso.getFullPath()); + new Bookmark(BOOKMARK_TYPE.USER_DEFINED, Bookmark.BOOKMARK_CATEGORY.USER_BOOKMARKS, fso.getName(), fso.getFullPath()); bookmark = Bookmarks.addBookmark(ctx, bookmark); if (bookmark == null) { // The operation fails diff --git a/src/com/cyanogenmod/filemanager/ui/policy/CompressActionPolicy.java b/Backbone/src/main/java/me/toolify/backbone/ui/policy/CompressActionPolicy.java similarity index 92% rename from src/com/cyanogenmod/filemanager/ui/policy/CompressActionPolicy.java rename to Backbone/src/main/java/me/toolify/backbone/ui/policy/CompressActionPolicy.java index 6e5f57fea..19a35d4f9 100644 --- a/src/com/cyanogenmod/filemanager/ui/policy/CompressActionPolicy.java +++ b/Backbone/src/main/java/me/toolify/backbone/ui/policy/CompressActionPolicy.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.ui.policy; +package me.toolify.backbone.ui.policy; import android.app.AlertDialog; import android.content.Context; @@ -23,24 +23,24 @@ import android.text.Spanned; import android.widget.Toast; -import com.cyanogenmod.filemanager.FileManagerApplication; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.commands.AsyncResultListener; -import com.cyanogenmod.filemanager.commands.CompressExecutable; -import com.cyanogenmod.filemanager.commands.UncompressExecutable; -import com.cyanogenmod.filemanager.console.ConsoleBuilder; -import com.cyanogenmod.filemanager.console.ExecutionException; -import com.cyanogenmod.filemanager.console.RelaunchableException; -import com.cyanogenmod.filemanager.listeners.OnRequestRefreshListener; -import com.cyanogenmod.filemanager.listeners.OnSelectionListener; -import com.cyanogenmod.filemanager.model.FileSystemObject; -import com.cyanogenmod.filemanager.preferences.CompressionMode; -import com.cyanogenmod.filemanager.util.CommandHelper; -import com.cyanogenmod.filemanager.util.DialogHelper; -import com.cyanogenmod.filemanager.util.ExceptionUtil; -import com.cyanogenmod.filemanager.util.ExceptionUtil.OnRelaunchCommandResult; -import com.cyanogenmod.filemanager.util.FileHelper; -import com.cyanogenmod.filemanager.util.FixedQueue; +import me.toolify.backbone.FileManagerApplication; +import me.toolify.backbone.R; +import me.toolify.backbone.commands.AsyncResultListener; +import me.toolify.backbone.commands.CompressExecutable; +import me.toolify.backbone.commands.UncompressExecutable; +import me.toolify.backbone.console.ConsoleBuilder; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.RelaunchableException; +import me.toolify.backbone.listeners.OnRequestRefreshListener; +import me.toolify.backbone.listeners.OnSelectionListener; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.preferences.CompressionMode; +import me.toolify.backbone.util.CommandHelper; +import me.toolify.backbone.util.DialogHelper; +import me.toolify.backbone.util.ExceptionUtil; +import me.toolify.backbone.util.ExceptionUtil.OnRelaunchCommandResult; +import me.toolify.backbone.util.FileHelper; +import me.toolify.backbone.util.FixedQueue; import java.io.File; import java.util.ArrayList; @@ -109,9 +109,10 @@ public static void compress( final List selection = onSelectionListener.onRequestSelectedFiles(); if (selection != null && selection.size() > 0) { // Show a dialog to allow the user make the compression mode choice + final String[] labels = getSupportedCompressionModesLabels(ctx, selection); AlertDialog dialog = DialogHelper.createSingleChoiceDialog( ctx, R.string.compression_mode_title, - getSupportedCompressionModesLabels(ctx, selection), + labels, CompressionMode.AC_GZIP.ordinal(), new DialogHelper.OnSelectChoiceListener() { @Override @@ -119,7 +120,7 @@ public void onSelectChoice(int choice) { // Do the compression compress( ctx, - getCompressionModeFromUserChoice(choice), + getCompressionModeFromUserChoice(ctx, labels, choice), selection, onSelectionListener, onRequestRefreshListener); @@ -151,6 +152,7 @@ public static void compress( items.add(fso); // Show a dialog to allow the user make the compression mode choice + final String[] labels = getSupportedCompressionModesLabels(ctx, items); AlertDialog dialog = DialogHelper.createSingleChoiceDialog( ctx, R.string.compression_mode_title, getSupportedCompressionModesLabels(ctx, items), @@ -161,7 +163,7 @@ public void onSelectChoice(int choice) { // Do the compression compress( ctx, - getCompressionModeFromUserChoice(choice), + getCompressionModeFromUserChoice(ctx, labels, choice), items, onSelectionListener, onRequestRefreshListener); @@ -402,6 +404,7 @@ public void onCancelled() { // Any exception? + Thread.sleep(100L); if (this.mListener.mCause != null) { throw this.mListener.mCause; } @@ -656,6 +659,7 @@ public void onCancelled() { // Any exception? + Thread.sleep(100L); if (this.mListener.mCause != null) { throw this.mListener.mCause; } @@ -774,14 +778,29 @@ public void onClick(DialogInterface alertDialog, int which) { private static String[] getSupportedCompressionModesLabels( Context ctx, List fsos) { String[] labels = ctx.getResources().getStringArray(R.array.compression_modes_labels); + // If more than a file are requested, compression is not available + // The same applies if the unique item is a folder if (fsos.size() > 1 || (fsos.size() == 1 && FileHelper.isDirectory(fsos.get(0)))) { - // If more that a file is requested, compression is not available - // The same applies if the unique item is a folder ArrayList validLabels = new ArrayList(); CompressionMode[] values = CompressionMode.values(); int cc = values.length; for (int i = 0; i < cc; i++) { if (values[i].mArchive) { + if (values[i].mCommandId == null || + FileManagerApplication.hasOptionalCommand(values[i].mCommandId)) { + validLabels.add(labels[i]); + } + } + } + labels = validLabels.toArray(new String[]{}); + } else { + // Remove optional commands + ArrayList validLabels = new ArrayList(); + CompressionMode[] values = CompressionMode.values(); + int cc = values.length; + for (int i = 0; i < cc; i++) { + if (values[i].mCommandId == null || + FileManagerApplication.hasOptionalCommand(values[i].mCommandId)) { validLabels.add(labels[i]); } } @@ -793,14 +812,19 @@ private static String[] getSupportedCompressionModesLabels( /** * Method that returns the compression mode from the user choice * + * @param ctx The current context + * @param labels The dialog labels * @param choice The choice of the user * @return CompressionMode The compression mode */ - static CompressionMode getCompressionModeFromUserChoice(int choice) { + static CompressionMode getCompressionModeFromUserChoice( + Context ctx, String[] labels, int choice) { + String label = labels[choice]; + String[] allLabels = ctx.getResources().getStringArray(R.array.compression_modes_labels); CompressionMode[] values = CompressionMode.values(); - int cc = values.length; + int cc = allLabels.length; for (int i = 0; i < cc; i++) { - if (values[i].ordinal() == choice) { + if (allLabels[i].compareTo(label) == 0) { return values[i]; } } diff --git a/src/com/cyanogenmod/filemanager/ui/policy/CopyMoveActionPolicy.java b/Backbone/src/main/java/me/toolify/backbone/ui/policy/CopyMoveActionPolicy.java similarity index 85% rename from src/com/cyanogenmod/filemanager/ui/policy/CopyMoveActionPolicy.java rename to Backbone/src/main/java/me/toolify/backbone/ui/policy/CopyMoveActionPolicy.java index 1f583e960..fd7683358 100644 --- a/src/com/cyanogenmod/filemanager/ui/policy/CopyMoveActionPolicy.java +++ b/Backbone/src/main/java/me/toolify/backbone/ui/policy/CopyMoveActionPolicy.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.ui.policy; +package me.toolify.backbone.ui.policy; import android.app.AlertDialog; import android.content.Context; @@ -22,17 +22,17 @@ import android.text.Html; import android.text.Spanned; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory; -import com.cyanogenmod.filemanager.console.RelaunchableException; -import com.cyanogenmod.filemanager.listeners.OnRequestRefreshListener; -import com.cyanogenmod.filemanager.listeners.OnSelectionListener; -import com.cyanogenmod.filemanager.model.FileSystemObject; -import com.cyanogenmod.filemanager.util.CommandHelper; -import com.cyanogenmod.filemanager.util.DialogHelper; -import com.cyanogenmod.filemanager.util.ExceptionUtil; -import com.cyanogenmod.filemanager.util.ExceptionUtil.OnRelaunchCommandResult; -import com.cyanogenmod.filemanager.util.FileHelper; +import me.toolify.backbone.R; +import me.toolify.backbone.console.NoSuchFileOrDirectory; +import me.toolify.backbone.console.RelaunchableException; +import me.toolify.backbone.listeners.OnCopyMoveListener; +import me.toolify.backbone.listeners.OnRequestRefreshListener; +import me.toolify.backbone.listeners.OnSelectionListener; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.util.CommandHelper; +import me.toolify.backbone.util.DialogHelper; +import me.toolify.backbone.util.ExceptionUtil; +import me.toolify.backbone.util.ExceptionUtil.OnRelaunchCommandResult; import java.io.File; import java.util.ArrayList; @@ -46,7 +46,7 @@ public final class CopyMoveActionPolicy extends ActionsPolicy { /** * @hide */ - private enum COPY_MOVE_OPERATION { + public enum COPY_MOVE_OPERATION { COPY, MOVE, RENAME, @@ -118,81 +118,57 @@ public static void renameFileSystemObject( } /** - * Method that copy an existing file system object. + * Method that marks a selection of file system object for copy-type paste. * - * @param ctx The current context - * @param fso The file system object - * @param onSelectionListener The listener for obtain selection information (required) - * @param onRequestRefreshListener The listener for request a refresh (optional) + * @param selection The list of files to be marked for copying (required) + * @param onCopyMoveListener The listener for request a refresh (optional) */ public static void createCopyFileSystemObject( - final Context ctx, - final FileSystemObject fso, - final OnSelectionListener onSelectionListener, - final OnRequestRefreshListener onRequestRefreshListener) { + final List selection, + final OnCopyMoveListener onCopyMoveListener) { - // Create a non-existing name - List curFiles = onSelectionListener.onRequestCurrentItems(); - String newName = - FileHelper.createNonExistingName( - ctx, curFiles, fso.getName(), R.string.create_copy_regexp); - final File dst = new File(fso.getParent(), newName); - File src = new File(fso.getFullPath()); - - // Create arguments - LinkedResource linkRes = new LinkedResource(src, dst); - List files = new ArrayList(1); - files.add(linkRes); - - // Internal copy - copyOrMoveFileSystemObjects( - ctx, - COPY_MOVE_OPERATION.CREATE_COPY, - files, - onSelectionListener, - onRequestRefreshListener); + onCopyMoveListener.onMarkFilesForPaste( + selection, + COPY_MOVE_OPERATION.COPY); } + /** - * Method that copy an existing file system object. + * Method that marks a selection of file system object for move-type paste. * - * @param ctx The current context - * @param files The list of files to copy - * @param onSelectionListener The listener for obtain selection information (required) - * @param onRequestRefreshListener The listener for request a refresh (optional) + * @param selection The list of files to be marked for copying (required) + * @param onCopyMoveListener The listener for request a refresh (optional) */ - public static void copyFileSystemObjects( - final Context ctx, - final List files, - final OnSelectionListener onSelectionListener, - final OnRequestRefreshListener onRequestRefreshListener) { - // Internal copy - copyOrMoveFileSystemObjects( - ctx, - COPY_MOVE_OPERATION.COPY, - files, - onSelectionListener, - onRequestRefreshListener); + public static void createMoveFileSystemObject( + final List selection, + final OnCopyMoveListener onCopyMoveListener) { + + onCopyMoveListener.onMarkFilesForPaste( + selection, + COPY_MOVE_OPERATION.MOVE); } /** - * Method that copy an existing file system object. + * Method that triggers the stored move or copy operation. * * @param ctx The current context - * @param files The list of files to move + * @param files The list of files to copy + * @param operationMode the operation mode (COPY/MOVE) of the paste operation * @param onSelectionListener The listener for obtain selection information (required) * @param onRequestRefreshListener The listener for request a refresh (optional) */ - public static void moveFileSystemObjects( + public static void triggerCopyMoveFileSystemObjects( final Context ctx, - final List files, + final List files, + final COPY_MOVE_OPERATION operationMode, final OnSelectionListener onSelectionListener, - final OnRequestRefreshListener onRequestRefreshListener) { - // Internal move + final OnRequestRefreshListener onRequestRefreshListener, + final OnCopyMoveListener onCopyMoveListener) { + // Internal copy copyOrMoveFileSystemObjects( ctx, - COPY_MOVE_OPERATION.MOVE, - files, + operationMode, + createLinkedResource(files, onCopyMoveListener.onRequestDestinationDir()), onSelectionListener, onRequestRefreshListener); } @@ -533,4 +509,26 @@ private static boolean checkMoveConsistency( } return true; } + + /** + * Method that creates a {@link me.toolify.backbone.ui.policy.CopyMoveActionPolicy.LinkedResource} for the list of object to the + * destination directory + * + * @param items The list of the source items + * @param destinationDirectory The destination directory + */ + private static List createLinkedResource( + List items, String destinationDirectory) { + + List resources = + new ArrayList(items.size()); + int cc = items.size(); + for (int i = 0; i < cc; i++) { + FileSystemObject fso = items.get(i); + File src = new File(fso.getFullPath()); + File dst = new File(destinationDirectory, fso.getName()); + resources.add(new CopyMoveActionPolicy.LinkedResource(src, dst)); + } + return resources; + } } \ No newline at end of file diff --git a/src/com/cyanogenmod/filemanager/ui/policy/DeleteActionPolicy.java b/Backbone/src/main/java/me/toolify/backbone/ui/policy/DeleteActionPolicy.java similarity index 94% rename from src/com/cyanogenmod/filemanager/ui/policy/DeleteActionPolicy.java rename to Backbone/src/main/java/me/toolify/backbone/ui/policy/DeleteActionPolicy.java index 039f4fe8f..4ea8925ae 100644 --- a/src/com/cyanogenmod/filemanager/ui/policy/DeleteActionPolicy.java +++ b/Backbone/src/main/java/me/toolify/backbone/ui/policy/DeleteActionPolicy.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.ui.policy; +package me.toolify.backbone.ui.policy; import android.app.AlertDialog; import android.content.Context; @@ -22,18 +22,18 @@ import android.text.Html; import android.text.Spanned; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.console.ExecutionException; -import com.cyanogenmod.filemanager.console.RelaunchableException; -import com.cyanogenmod.filemanager.listeners.OnRequestRefreshListener; -import com.cyanogenmod.filemanager.listeners.OnSelectionListener; -import com.cyanogenmod.filemanager.model.FileSystemObject; -import com.cyanogenmod.filemanager.ui.widgets.FlingerListView.OnItemFlingerResponder; -import com.cyanogenmod.filemanager.util.CommandHelper; -import com.cyanogenmod.filemanager.util.DialogHelper; -import com.cyanogenmod.filemanager.util.ExceptionUtil; -import com.cyanogenmod.filemanager.util.ExceptionUtil.OnRelaunchCommandResult; -import com.cyanogenmod.filemanager.util.FileHelper; +import me.toolify.backbone.R; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.RelaunchableException; +import me.toolify.backbone.listeners.OnRequestRefreshListener; +import me.toolify.backbone.listeners.OnSelectionListener; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.ui.widgets.FlingerListView.OnItemFlingerResponder; +import me.toolify.backbone.util.CommandHelper; +import me.toolify.backbone.util.DialogHelper; +import me.toolify.backbone.util.ExceptionUtil; +import me.toolify.backbone.util.ExceptionUtil.OnRelaunchCommandResult; +import me.toolify.backbone.util.FileHelper; import java.util.ArrayList; import java.util.Collections; diff --git a/src/com/cyanogenmod/filemanager/ui/policy/ExecutionActionPolicy.java b/Backbone/src/main/java/me/toolify/backbone/ui/policy/ExecutionActionPolicy.java similarity index 88% rename from src/com/cyanogenmod/filemanager/ui/policy/ExecutionActionPolicy.java rename to Backbone/src/main/java/me/toolify/backbone/ui/policy/ExecutionActionPolicy.java index efd106f16..157bc9f8c 100644 --- a/src/com/cyanogenmod/filemanager/ui/policy/ExecutionActionPolicy.java +++ b/Backbone/src/main/java/me/toolify/backbone/ui/policy/ExecutionActionPolicy.java @@ -14,16 +14,16 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.ui.policy; +package me.toolify.backbone.ui.policy; import android.content.Context; -import com.cyanogenmod.filemanager.commands.AsyncResultListener; -import com.cyanogenmod.filemanager.commands.ExecExecutable; -import com.cyanogenmod.filemanager.model.FileSystemObject; -import com.cyanogenmod.filemanager.ui.dialogs.ExecutionDialog; -import com.cyanogenmod.filemanager.util.CommandHelper; -import com.cyanogenmod.filemanager.util.ExceptionUtil; +import me.toolify.backbone.commands.AsyncResultListener; +import me.toolify.backbone.commands.ExecExecutable; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.ui.dialogs.ExecutionDialog; +import me.toolify.backbone.util.CommandHelper; +import me.toolify.backbone.util.ExceptionUtil; /** * A class with the convenience methods for resolve executions related actions diff --git a/Backbone/src/main/java/me/toolify/backbone/ui/policy/InfoActionPolicy.java b/Backbone/src/main/java/me/toolify/backbone/ui/policy/InfoActionPolicy.java new file mode 100644 index 000000000..23389d2e4 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/ui/policy/InfoActionPolicy.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.ui.policy; + +import android.content.Context; +import android.content.DialogInterface; +import android.widget.Toast; + +import me.toolify.backbone.console.ConsoleBuilder; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.ui.dialogs.ComputeChecksumDialog; +import me.toolify.backbone.util.DialogHelper; +import me.toolify.backbone.util.ExceptionUtil; +import me.toolify.backbone.util.ExceptionUtil.OnRelaunchCommandResult; +import me.toolify.backbone.util.FileHelper; + +/** + * A class with the convenience methods for resolve the display of info actions + */ +public final class InfoActionPolicy extends ActionsPolicy { + + /** + * Method that show a {@link Toast} with the content description of a {@link FileSystemObject}. + * + * @param ctx The current context + * @param fso The file system object + */ + public static void showContentDescription(final Context ctx, final FileSystemObject fso) { + String contentDescription = fso.getFullPath(); + DialogHelper.showToast(ctx, contentDescription, Toast.LENGTH_SHORT); + } + + /** + * Method that show a new dialog for compute checksum of a {@link FileSystemObject}. + * + * @param ctx The current context + * @param fso The file system object + * of the {@link FileSystemObject} were changed (optional) + */ + public static void showComputeChecksumDialog( + final Context ctx, final FileSystemObject fso) { + // Check that we have read access + try { + FileHelper.ensureReadAccess( + ConsoleBuilder.getConsole(ctx), + fso, + null); + + //Show a the filesystem info dialog + final ComputeChecksumDialog dialog = new ComputeChecksumDialog(ctx, fso); + dialog.show(); + + } catch (Exception ex) { + ExceptionUtil.translateException( + ctx, ex, false, true, new OnRelaunchCommandResult() { + @Override + public void onSuccess() { + //Show a the filesystem info dialog + final ComputeChecksumDialog dialog = new ComputeChecksumDialog(ctx, fso); + dialog.show(); + } + + @Override + public void onFailed(Throwable cause) {/**NON BLOCK**/} + + @Override + public void onCancelled() {/**NON BLOCK**/} + }); + } + } +} diff --git a/src/com/cyanogenmod/filemanager/ui/policy/IntentsActionPolicy.java b/Backbone/src/main/java/me/toolify/backbone/ui/policy/IntentsActionPolicy.java similarity index 84% rename from src/com/cyanogenmod/filemanager/ui/policy/IntentsActionPolicy.java rename to Backbone/src/main/java/me/toolify/backbone/ui/policy/IntentsActionPolicy.java index 774509957..930f920c6 100644 --- a/src/com/cyanogenmod/filemanager/ui/policy/IntentsActionPolicy.java +++ b/Backbone/src/main/java/me/toolify/backbone/ui/policy/IntentsActionPolicy.java @@ -14,14 +14,14 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.ui.policy; +package me.toolify.backbone.ui.policy; import android.content.ComponentName; import android.content.Context; -import android.content.IntentFilter; import android.content.DialogInterface.OnCancelListener; import android.content.DialogInterface.OnDismissListener; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.net.Uri; @@ -29,17 +29,17 @@ import android.util.Log; import android.widget.Toast; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.activities.ShortcutActivity; -import com.cyanogenmod.filemanager.model.FileSystemObject; -import com.cyanogenmod.filemanager.model.RegularFile; -import com.cyanogenmod.filemanager.ui.dialogs.AssociationsDialog; -import com.cyanogenmod.filemanager.util.DialogHelper; -import com.cyanogenmod.filemanager.util.ExceptionUtil; -import com.cyanogenmod.filemanager.util.FileHelper; -import com.cyanogenmod.filemanager.util.MimeTypeHelper; -import com.cyanogenmod.filemanager.util.MimeTypeHelper.MimeTypeCategory; -import com.cyanogenmod.filemanager.util.ResourcesHelper; +import me.toolify.backbone.R; +import me.toolify.backbone.activities.ShortcutActivity; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.model.RegularFile; +import me.toolify.backbone.ui.dialogs.AssociationsDialog; +import me.toolify.backbone.util.DialogHelper; +import me.toolify.backbone.util.ExceptionUtil; +import me.toolify.backbone.util.FileHelper; +import me.toolify.backbone.util.MimeTypeHelper; +import me.toolify.backbone.util.MimeTypeHelper.MimeTypeCategory; +import me.toolify.backbone.util.ResourcesHelper; import java.io.File; import java.util.ArrayList; @@ -57,25 +57,25 @@ public final class IntentsActionPolicy extends ActionsPolicy { private static boolean DEBUG = false; // The preferred package when sorting intents - private static final String PREFERRED_PACKAGE = "com.cyanogenmod.filemanager"; //$NON-NLS-1$ + private static final String PREFERRED_PACKAGE = "me.toolify.backbone"; //$NON-NLS-1$ /** * Extra field for the internal action */ public static final String EXTRA_INTERNAL_ACTION = - "com.cyanogenmod.filemanager.extra.INTERNAL_ACTION"; //$NON-NLS-1$ + "me.toolify.backbone.extra.INTERNAL_ACTION"; //$NON-NLS-1$ /** * Category for all the internal app viewers */ public static final String CATEGORY_INTERNAL_VIEWER = - "com.cyanogenmod.filemanager.category.INTERNAL_VIEWER"; //$NON-NLS-1$ + "me.toolify.backbone.category.INTERNAL_VIEWER"; //$NON-NLS-1$ /** * Category for all the app editor */ public static final String CATEGORY_EDITOR = - "com.cyanogenmod.filemanager.category.EDITOR"; //$NON-NLS-1$ + "me.toolify.backbone.category.EDITOR"; //$NON-NLS-1$ /** * Method that opens a {@link FileSystemObject} with the default registered application @@ -145,7 +145,74 @@ public static void sendFileSystemObject( resolveIntent( ctx, intent, - false, + true, + null, + 0, + R.string.associations_dialog_sendwith_title, + R.string.associations_dialog_sendwith_action, + false, onCancelListener, onDismissListener); + + } catch (Exception e) { + ExceptionUtil.translateException(ctx, e); + } + } + + /** + * Method that sends a {@link FileSystemObject} with the default registered application + * by the system, or ask the user for select a registered application. + * + * @param ctx The current context + * @param fsos The file system objects + * @param onCancelListener The cancel listener + * @param onDismissListener The dismiss listener + */ + public static void sendMultipleFileSystemObject( + final Context ctx, final List fsos, + OnCancelListener onCancelListener, OnDismissListener onDismissListener) { + try { + // Create the intent to + Intent intent = new Intent(); + intent.setAction(android.content.Intent.ACTION_SEND_MULTIPLE); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + + // Create an array list of the uris to send + ArrayList uris = new ArrayList(); + int cc = fsos.size(); + String lastMimeType = null; + boolean sameMimeType = true; + for (int i = 0; i < cc; i++) { + FileSystemObject fso = fsos.get(i); + + // Folders are not allowed + if (FileHelper.isDirectory(fso)) continue; + + // Check if we can use a unique mime/type + String mimeType = MimeTypeHelper.getMimeType(ctx, fso); + if (mimeType == null) { + sameMimeType = false; + } + if (sameMimeType && + (mimeType != null && lastMimeType != null && + mimeType.compareTo(lastMimeType) != 0)) { + sameMimeType = false; + } + lastMimeType = mimeType; + + // Add the uri + uris.add(Uri.fromFile(new File(fso.getFullPath()))); + } + if (sameMimeType) { + intent.setType(lastMimeType); + } else { + intent.setType(MimeTypeHelper.ALL_MIME_TYPES); + } + intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); + + // Resolve the intent + resolveIntent( + ctx, + intent, + true, null, 0, R.string.associations_dialog_sendwith_title, diff --git a/src/com/cyanogenmod/filemanager/ui/policy/NavigationActionPolicy.java b/Backbone/src/main/java/me/toolify/backbone/ui/policy/NavigationActionPolicy.java similarity index 82% rename from src/com/cyanogenmod/filemanager/ui/policy/NavigationActionPolicy.java rename to Backbone/src/main/java/me/toolify/backbone/ui/policy/NavigationActionPolicy.java index 020b38c07..40ff9b3ee 100644 --- a/src/com/cyanogenmod/filemanager/ui/policy/NavigationActionPolicy.java +++ b/Backbone/src/main/java/me/toolify/backbone/ui/policy/NavigationActionPolicy.java @@ -14,15 +14,15 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.ui.policy; +package me.toolify.backbone.ui.policy; import android.content.Context; -import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory; -import com.cyanogenmod.filemanager.listeners.OnRequestRefreshListener; -import com.cyanogenmod.filemanager.model.FileSystemObject; -import com.cyanogenmod.filemanager.util.CommandHelper; -import com.cyanogenmod.filemanager.util.ExceptionUtil; +import me.toolify.backbone.console.NoSuchFileOrDirectory; +import me.toolify.backbone.listeners.OnRequestRefreshListener; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.util.CommandHelper; +import me.toolify.backbone.util.ExceptionUtil; /** * A class with the convenience methods for resolve navigation related actions diff --git a/src/com/cyanogenmod/filemanager/ui/policy/NewActionPolicy.java b/Backbone/src/main/java/me/toolify/backbone/ui/policy/NewActionPolicy.java similarity index 95% rename from src/com/cyanogenmod/filemanager/ui/policy/NewActionPolicy.java rename to Backbone/src/main/java/me/toolify/backbone/ui/policy/NewActionPolicy.java index b24cce11a..420b810d6 100644 --- a/src/com/cyanogenmod/filemanager/ui/policy/NewActionPolicy.java +++ b/Backbone/src/main/java/me/toolify/backbone/ui/policy/NewActionPolicy.java @@ -14,18 +14,18 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.ui.policy; +package me.toolify.backbone.ui.policy; import android.content.Context; import android.os.AsyncTask; import android.util.Log; -import com.cyanogenmod.filemanager.console.RelaunchableException; -import com.cyanogenmod.filemanager.listeners.OnRequestRefreshListener; -import com.cyanogenmod.filemanager.listeners.OnSelectionListener; -import com.cyanogenmod.filemanager.model.FileSystemObject; -import com.cyanogenmod.filemanager.util.CommandHelper; -import com.cyanogenmod.filemanager.util.ExceptionUtil; +import me.toolify.backbone.console.RelaunchableException; +import me.toolify.backbone.listeners.OnRequestRefreshListener; +import me.toolify.backbone.listeners.OnSelectionListener; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.util.CommandHelper; +import me.toolify.backbone.util.ExceptionUtil; import java.io.File; diff --git a/Backbone/src/main/java/me/toolify/backbone/ui/preferences/ColorPickerPreference.java b/Backbone/src/main/java/me/toolify/backbone/ui/preferences/ColorPickerPreference.java new file mode 100644 index 000000000..9baa3b4d7 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/ui/preferences/ColorPickerPreference.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.ui.preferences; + +import com.afzkl.development.mColorPicker.views.ColorDialogView; +import com.afzkl.development.mColorPicker.views.ColorPanelView; + +import android.app.AlertDialog.Builder; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.SharedPreferences; +import android.content.res.TypedArray; +import android.os.Parcel; +import android.os.Parcelable; +import android.preference.DialogPreference; +import android.preference.Preference; +import android.util.AttributeSet; +import android.view.View; + +import me.toolify.backbone.R; + +/** + * A {@link Preference} that allow to select/pick a color in a new window dialog. + */ +public class ColorPickerPreference extends DialogPreference { + + private ColorPanelView mColorPicker; + private int mColor; + + private ColorDialogView mColorDlg; + + /** + * Constructor of ColorPickerPreference + * + * @param context The current context + */ + public ColorPickerPreference(Context context) { + this(context, null); + } + + /** + * Constructor of ColorPickerPreference + * + * @param context The current context + * @param attrs The attributes of the XML tag that is inflating the preference. + */ + public ColorPickerPreference(Context context, AttributeSet attrs) { + super(context, attrs); + setWidgetLayoutResource(R.layout.color_picker_pref_item); + } + + /** + * Returns the color of the picker. + * + * @return The color of the picker. + */ + public int getColor() { + return this.mColor; + } + + /** + * Sets the color of the picker and saves it to the {@link SharedPreferences}. + * + * @param color The new color. + */ + public void setColor(int color) { + // Always persist/notify the first time; don't assume the field's default of false. + final boolean changed = this.mColor != color; + if (changed) { + this.mColor = color; + // when called from onSetInitialValue the view is still not set + if (this.mColorPicker != null) { + this.mColorPicker.setColor(color); + } + persistInt(color); + if (changed) { + notifyDependencyChange(shouldDisableDependents()); + notifyChanged(); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + protected Object onGetDefaultValue(TypedArray a, int index) { + return Integer.valueOf(a.getColor(index, 0)); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onSetInitialValue(boolean restoreValue, Object defaultValue) { + setColor(restoreValue ? getPersistedInt(0) : ((Integer)defaultValue).intValue()); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onPrepareDialogBuilder(Builder builder) { + super.onPrepareDialogBuilder(builder); + + // Configure the dialog + this.mColorDlg = new ColorDialogView(getContext()); + this.mColorDlg.setColor(this.mColor); + this.mColorDlg.showAlphaSlider(true); + this.mColorDlg.setAlphaSliderText( + getContext().getString(R.string.color_picker_alpha_slider_text)); + this.mColorDlg.setCurrentColorText( + getContext().getString(R.string.color_picker_current_text)); + this.mColorDlg.setNewColorText( + getContext().getString(R.string.color_picker_new_text)); + this.mColorDlg.setColorLabelText( + getContext().getString(R.string.color_picker_color)); + builder.setView(this.mColorDlg); + + // The color is selected by the user and confirmed by clicking ok + builder.setPositiveButton(android.R.string.ok, new OnClickListener() { + @Override + @SuppressWarnings("synthetic-access") + public void onClick(DialogInterface dialog, int which) { + int color = ColorPickerPreference.this.mColorDlg.getColor(); + if (callChangeListener(Integer.valueOf(color))) { + setColor(color); + } + dialog.dismiss(); + } + }); + } + + + /** + * {@inheritDoc} + */ + @Override + protected void onBindView(View view) { + super.onBindView(view); + View v = view.findViewById(R.id.color_picker); + if (v != null && v instanceof ColorPanelView) { + this.mColorPicker = (ColorPanelView)v; + this.mColorPicker.setColor(this.mColor); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected Parcelable onSaveInstanceState() { + final Parcelable superState = super.onSaveInstanceState(); + if (isPersistent()) { + // No need to save instance state since it's persistent + return superState; + } + + final SavedState myState = new SavedState(superState); + myState.color = getColor(); + return myState; + } + + /** + * {@inheritDoc} + */ + @Override + protected void onRestoreInstanceState(Parcelable state) { + if (state == null || !state.getClass().equals(SavedState.class)) { + // Didn't save state for us in onSaveInstanceState + super.onRestoreInstanceState(state); + return; + } + + SavedState myState = (SavedState) state; + super.onRestoreInstanceState(myState.getSuperState()); + setColor(myState.color); + } + + /** + * A class for managing the instance state of a {@link ColorPickerPreference}. + */ + static class SavedState extends BaseSavedState { + int color; + + /** + * Constructor of SavedState + * + * @param source The source + */ + public SavedState(Parcel source) { + super(source); + this.color = source.readInt(); + } + + /** + * Constructor of SavedState + * + * @param superState The parcelable state + */ + public SavedState(Parcelable superState) { + super(superState); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(this.color); + } + + /** + * A class that generates instances of the SavedState class from a Parcel. + */ + @SuppressWarnings("hiding") + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + + /** + * {@inheritDoc} + */ + @Override + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + /** + * {@inheritDoc} + */ + @Override + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } +} diff --git a/src/com/cyanogenmod/filemanager/ui/preferences/ThemeRoulette.java b/Backbone/src/main/java/me/toolify/backbone/ui/preferences/ThemeRoulette.java similarity index 98% rename from src/com/cyanogenmod/filemanager/ui/preferences/ThemeRoulette.java rename to Backbone/src/main/java/me/toolify/backbone/ui/preferences/ThemeRoulette.java index c64169ce6..e59765285 100644 --- a/src/com/cyanogenmod/filemanager/ui/preferences/ThemeRoulette.java +++ b/Backbone/src/main/java/me/toolify/backbone/ui/preferences/ThemeRoulette.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.ui.preferences; +package me.toolify.backbone.ui.preferences; import android.content.Context; import android.graphics.Paint; @@ -28,8 +28,8 @@ import android.widget.LinearLayout; import android.widget.TextView; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.ui.ThemeManager.Theme; +import me.toolify.backbone.R; +import me.toolify.backbone.ui.ThemeManager.Theme; import java.util.List; diff --git a/src/com/cyanogenmod/filemanager/ui/preferences/ThemeSelectorPreference.java b/Backbone/src/main/java/me/toolify/backbone/ui/preferences/ThemeSelectorPreference.java similarity index 96% rename from src/com/cyanogenmod/filemanager/ui/preferences/ThemeSelectorPreference.java rename to Backbone/src/main/java/me/toolify/backbone/ui/preferences/ThemeSelectorPreference.java index bb70bfbd0..42b632408 100644 --- a/src/com/cyanogenmod/filemanager/ui/preferences/ThemeSelectorPreference.java +++ b/Backbone/src/main/java/me/toolify/backbone/ui/preferences/ThemeSelectorPreference.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.ui.preferences; +package me.toolify.backbone.ui.preferences; import android.app.Activity; import android.content.Context; @@ -34,12 +34,12 @@ import android.widget.ProgressBar; import android.widget.Toast; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.ui.ThemeManager; -import com.cyanogenmod.filemanager.ui.ThemeManager.Theme; -import com.cyanogenmod.filemanager.ui.preferences.ThemeRoulette.OnThemeScrollSelectionListener; -import com.cyanogenmod.filemanager.util.AndroidHelper; -import com.cyanogenmod.filemanager.util.DialogHelper; +import me.toolify.backbone.R; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.ui.ThemeManager.Theme; +import me.toolify.backbone.ui.preferences.ThemeRoulette.OnThemeScrollSelectionListener; +import me.toolify.backbone.util.AndroidHelper; +import me.toolify.backbone.util.DialogHelper; import java.util.ArrayList; import java.util.List; diff --git a/src/com/cyanogenmod/filemanager/ui/preferences/ThemeView.java b/Backbone/src/main/java/me/toolify/backbone/ui/preferences/ThemeView.java similarity index 95% rename from src/com/cyanogenmod/filemanager/ui/preferences/ThemeView.java rename to Backbone/src/main/java/me/toolify/backbone/ui/preferences/ThemeView.java index 872aab482..5015378d6 100644 --- a/src/com/cyanogenmod/filemanager/ui/preferences/ThemeView.java +++ b/Backbone/src/main/java/me/toolify/backbone/ui/preferences/ThemeView.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.ui.preferences; +package me.toolify.backbone.ui.preferences; import android.content.Context; import android.util.AttributeSet; @@ -22,9 +22,9 @@ import android.widget.RelativeLayout; import android.widget.TextView; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.ui.ThemeManager; -import com.cyanogenmod.filemanager.ui.ThemeManager.Theme; +import me.toolify.backbone.R; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.ui.ThemeManager.Theme; /** * A view that display information about a {@link Theme} diff --git a/Backbone/src/main/java/me/toolify/backbone/ui/widgets/BookmarksListView.java b/Backbone/src/main/java/me/toolify/backbone/ui/widgets/BookmarksListView.java new file mode 100644 index 000000000..f279062d7 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/ui/widgets/BookmarksListView.java @@ -0,0 +1,407 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * Copyright (C) 2013 BrandroidTools + * + * 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 me.toolify.backbone.ui.widgets; + +import android.content.Context; +import android.content.res.XmlResourceParser; +import android.database.Cursor; +import android.os.AsyncTask; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ListView; +import android.widget.Toast; + +import java.util.ArrayList; +import java.util.List; + +import de.greenrobot.event.EventBus; +import me.toolify.backbone.FileManagerApplication; +import me.toolify.backbone.R; +import me.toolify.backbone.adapters.BookmarksAdapter; +import me.toolify.backbone.bus.events.BookmarkDeleteEvent; +import me.toolify.backbone.bus.events.BookmarkOpenEvent; +import me.toolify.backbone.bus.events.BookmarkRefreshEvent; +import me.toolify.backbone.model.Bookmark; +import me.toolify.backbone.preferences.AccessMode; +import me.toolify.backbone.preferences.Bookmarks; +import me.toolify.backbone.preferences.FileManagerSettings; +import me.toolify.backbone.preferences.Preferences; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.ui.dialogs.InitialDirectoryDialog; +import me.toolify.backbone.util.DialogHelper; +import me.toolify.backbone.util.ExceptionUtil; +import me.toolify.backbone.util.StorageHelper; +import me.toolify.backbone.util.XmlUtils; + +/** + * A list view for showing bookmarks and links. Used in the main activity's navigation drawer + */ +public class BookmarksListView extends ListView implements OnItemClickListener, OnClickListener { + + private static final String TAG = "BB.BookmarksListView"; //$NON-NLS-1$ + + // Bookmark list XML tags + private static final String TAG_BOOKMARKS = "Bookmarks"; //$NON-NLS-1$ + private static final String TAG_BOOKMARK = "bookmark"; //$NON-NLS-1$ + + private Context mActivity; + private BookmarksAdapter mAdapter; + private boolean mChRooted; + + public BookmarksListView(Context context) { + super(context); + mActivity = context; + initBookmarks(); + } + + public BookmarksListView(Context context, AttributeSet attrs) { + super(context, attrs); + mActivity = context; + initBookmarks(); + } + + public BookmarksListView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + mActivity = context; + initBookmarks(); + } + + /** + * This method receives {@link me.toolify.backbone.bus.events.BookmarkDeleteEvent} events and + * triggers the removal of the referenced bookmark. + * + * @param event an event that references the bookmark to delete. + */ + public void onEvent (BookmarkDeleteEvent event) { + Bookmark b = Bookmarks.getBookmark(mActivity.getContentResolver(), event.path); + Bookmarks.removeBookmark(mActivity, b); + refresh(); + } + + /** + * This method receives {@link me.toolify.backbone.bus.events.BookmarkRefreshEvent} events and + * triggers a refresh of this {@link me.toolify.backbone.ui.widgets.BookmarksListView}'s + * bookmarks + * + * @param event a trigger event that contains no additional information. + */ + public void onEvent (BookmarkRefreshEvent event) { + refresh(); + } + + /** + * Method that initializes the titlebar of the activity. + */ + private void initBookmarks() { + + List bookmarks = new ArrayList(); + mAdapter = new BookmarksAdapter(mActivity, bookmarks, this); + this.setAdapter(mAdapter); + this.setOnItemClickListener(this); + + } + + /** + * Method that makes the refresh of the data. + */ + public void refresh() { + + // Determine whether we are loading bookmarks for a chrooted or rooted environment + this.mChRooted = FileManagerApplication.getAccessMode().compareTo(AccessMode.SAFE) == 0; + + // Load the bookmarks in background + AsyncTask task = new AsyncTask() { + Exception mCause; + List mBookmarks; + + @Override + protected Boolean doInBackground(Void... params) { + try { + this.mBookmarks = loadBookmarks(); + return Boolean.TRUE; + + } catch (Exception e) { + this.mCause = e; + return Boolean.FALSE; + } + } + + @Override + protected void onPreExecute() { + //waiting.setVisibility(View.VISIBLE); + mAdapter.clear(); + } + + @Override + protected void onPostExecute(Boolean result) { + //waiting.setVisibility(View.GONE); + if (result.booleanValue()) { + mAdapter.clear(); + mAdapter.addAll(this.mBookmarks); + BookmarksListView.this.setSelection(0); + + } else { + if (this.mCause != null) { + ExceptionUtil.translateException(mActivity, this.mCause); + } + } + } + }; + task.execute(); + } + + /** + * {@inheritDoc} + */ + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + Bookmark bookmark = mAdapter.getItem(position); + EventBus.getDefault().post(new BookmarkOpenEvent(bookmark.mPath)); + } + + /** + * {@inheritDoc} + */ + @Override + public void onClick(View v) { + //Retrieve the position + final int position = ((Integer)v.getTag()).intValue(); + final Bookmark bookmark = mAdapter.getItem(position); + + //Configure home + if (bookmark.mType.compareTo(Bookmark.BOOKMARK_TYPE.HOME) == 0) { + //Show a dialog for configure initial directory + InitialDirectoryDialog dialog = new InitialDirectoryDialog(mActivity); + dialog.setOnValueChangedListener(new InitialDirectoryDialog.OnValueChangedListener() { + @Override + public void onValueChanged(String newInitialDir) { + mAdapter.getItem(position).mPath = newInitialDir; + mAdapter.notifyDataSetChanged(); + } + }); + dialog.show(); + return; + } + + //Remove bookmark + if (bookmark.mType.compareTo(Bookmark.BOOKMARK_TYPE.USER_DEFINED) == 0) { + boolean result = Bookmarks.removeBookmark(mActivity, bookmark); + if (!result) { + //Show warning + DialogHelper.showToast(mActivity, R.string.msgs_operation_failure, Toast.LENGTH_SHORT); + return; + } + mAdapter.remove(bookmark); + return; + } + } + + /** + * Method that loads all kind of bookmarks and join in + * an array to be used in the listview adapter. + * + * @return List + * @hide + */ + List loadBookmarks() { + // Bookmarks = HOME + FILESYSTEM + SD STORAGES + USER DEFINED + // In ChRooted mode = SD STORAGES + USER DEFINED (from SD STORAGES) + List bookmarks = new ArrayList(); + if (!this.mChRooted) { + bookmarks.add(loadHomeBookmarks()); + bookmarks.addAll(loadFilesystemBookmarks()); + } + bookmarks.addAll(loadSdStorageBookmarks()); + bookmarks.addAll(loadUserBookmarks()); + return bookmarks; + } + + /** + * Method that loads the home bookmark from the user preference. + * + * @return Bookmark The bookmark loaded + */ + private Bookmark loadHomeBookmarks() { + String initialDir = Preferences.getSharedPreferences().getString( + FileManagerSettings.SETTINGS_INITIAL_DIR.getId(), + (String)FileManagerSettings.SETTINGS_INITIAL_DIR.getDefaultValue()); + return new Bookmark(Bookmark.BOOKMARK_TYPE.HOME, Bookmark.BOOKMARK_CATEGORY.LOCATIONS, mActivity.getString(R.string.bookmarks_home), initialDir); + } + + /** + * Method that loads the filesystem bookmarks from the internal xml file. + * (defined by this application) + * + * @return List The bookmarks loaded + */ + private List loadFilesystemBookmarks() { + try { + //Initialize the bookmarks + List bookmarks = new ArrayList(); + + //Read the command list xml file + XmlResourceParser parser = getResources().getXml(R.xml.filesystem_bookmarks); + + try { + //Find the root element + XmlUtils.beginDocument(parser, TAG_BOOKMARKS); + while (true) { + XmlUtils.nextElement(parser); + String element = parser.getName(); + if (element == null) { + break; + } + + if (TAG_BOOKMARK.equals(element)) { + CharSequence name = null; + CharSequence directory = null; + + try { + name = + mActivity.getString(parser.getAttributeResourceValue( + R.styleable.Bookmark_name, 0)); + } catch (Exception e) {/**NON BLOCK**/} + try { + directory = + mActivity.getString(parser.getAttributeResourceValue( + R.styleable.Bookmark_directory, 0)); + } catch (Exception e) {/**NON BLOCK**/} + if (directory == null) { + directory = + parser.getAttributeValue(R.styleable.Bookmark_directory); + } + if (name != null && directory != null) { + bookmarks.add( + new Bookmark( + Bookmark.BOOKMARK_TYPE.FILESYSTEM, + Bookmark.BOOKMARK_CATEGORY.LOCATIONS, + name.toString(), + directory.toString())); + } + } + } + + //Return the bookmarks + return bookmarks; + + } finally { + parser.close(); + } + } catch (Throwable ex) { + Log.e(TAG, "Load filesystem bookmarks failed", ex); //$NON-NLS-1$ + } + + //No data + return new ArrayList(); + } + + /** + * Method that loads the secure digital card storage bookmarks from the system. + * + * @return List The bookmarks loaded + */ + private List loadSdStorageBookmarks() { + //Initialize the bookmarks + List bookmarks = new ArrayList(); + + try { + //Recovery sdcards from storage manager + Object[] volumes = StorageHelper.getStorageVolumes(mActivity); + int cc = volumes.length; + for (int i = 0; i < cc ; i++) { + String path = StorageHelper.getStoragePath(volumes[i]); + if(!StorageHelper.isValidMount(path)) continue; + if (path.toLowerCase().indexOf("usb") != -1) { //$NON-NLS-1$ + bookmarks.add( + new Bookmark( + Bookmark.BOOKMARK_TYPE.USB, + Bookmark.BOOKMARK_CATEGORY.LOCATIONS, + StorageHelper.getStorageVolumeDescription( + mActivity, volumes[i]), + path)); + } else { + bookmarks.add( + new Bookmark( + Bookmark.BOOKMARK_TYPE.SDCARD, + Bookmark.BOOKMARK_CATEGORY.LOCATIONS, + StorageHelper.getStorageVolumeDescription( + mActivity, volumes[i]), + path)); + } + } + + //Return the bookmarks + return bookmarks; + } catch (Throwable ex) { + Log.e(TAG, "Load filesystem bookmarks failed", ex); //$NON-NLS-1$ + } + + //No data + return new ArrayList(); + } + + /** + * Method that loads the user bookmarks (added by the user). + * + * @return List The bookmarks loaded + */ + private List loadUserBookmarks() { + List bookmarks = new ArrayList(); + Cursor cursor = Bookmarks.getAllBookmarks(mActivity.getContentResolver()); + try { + if (cursor != null && cursor.moveToFirst()) { + do { + Bookmark bm = new Bookmark(cursor); + if (this.mChRooted && !StorageHelper.isPathInStorageVolume(bm.mPath)) { + continue; + } + bookmarks.add(bm); + } while (cursor.moveToNext()); + } + } finally { + try { + if (cursor != null) { + cursor.close(); + } + } catch (Exception e) {/**NON BLOCK**/} + } + return bookmarks; + } + + /** + * Method that applies the current theme to the activity + * @hide + */ + void applyTheme() { + ThemeManager.Theme theme = ThemeManager.getCurrentTheme(mActivity); + + if (mAdapter != null) { + mAdapter.notifyThemeChanged(); + mAdapter.notifyDataSetChanged(); + } + this.setBackgroundColor( + theme.getColor(mActivity, "menu_drawer_background_color")); + this.setDivider( + theme.getDrawable(mActivity, "dark_horizontal_divider_drawable")); //$NON-NLS-1$ + this.invalidate(); + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/ui/widgets/Breadcrumb.java b/Backbone/src/main/java/me/toolify/backbone/ui/widgets/Breadcrumb.java new file mode 100644 index 000000000..5372e6509 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/ui/widgets/Breadcrumb.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.ui.widgets; + +import me.toolify.backbone.fragments.NavigationFragment; +import me.toolify.backbone.model.DiskUsage; +import me.toolify.backbone.model.MountPoint; + +/** + * An interface that defines the breadcrumb operations. + */ +public interface Breadcrumb { + + /** + * Method that initializes the loading of data. + */ + void startLoading(); + + /** + * Method that finalizes the loading of data. + */ + void endLoading(); + + /** + * Method that changes the path of the breadcrumb. + * + * @param newPath The new path + * @param chRooted If the breadcrumb should be in a ChRooted environment + */ + void changeBreadcrumbPath(final String newPath, boolean chRooted); + + /** + * Method that adds a new breadcrumb listener. + * + * @param listener The breadcrumb listener to add + */ + void addBreadcrumbListener(BreadcrumbListener listener); + + /** + * Method that adds an active breadcrumb listener. + * + * @param listener The breadcrumb listener to remove + */ + void removeBreadcrumbListener(BreadcrumbListener listener); + + /** + * Method that updates the info and statistics of the current mount point. + */ + void updateMountPointInfo(); + + /** + * Method that sets the free disk space percentage after the widget change his color + * to advise the user + * + * @param percentage The free disk space percentage + */ + void setFreeDiskSpaceWarningLevel(int percentage); + + /** + * Method that returns the active {@link MountPoint} reference. + * + * @return MountPoint The active {@link MountPoint} + */ + MountPoint getMountPointInfo(); + + /** + * Method that sets the active {@link MountPoint} reference. + * + * @param mountPointInfo The active {@link MountPoint} + */ + void setMountPointInfo(MountPoint mountPointInfo); + + /** + * Method that returns the active {@link DiskUsage} reference. + * + * @return DiskUsage The active {@link DiskUsage} + */ + DiskUsage getDiskUsageInfo(); + + /** + * Method that sets the active {@link DiskUsage} reference. + * + * @param diskUsageInfo The active {@link DiskUsage} + */ + void setDiskUsageInfo(DiskUsage diskUsageInfo); + + /** + * Method that associates the supplied {@link me.toolify.backbone.fragments.NavigationFragment} + * with this breadcrumb + * + * @param fragment the fragment to associate with this breadcrumb + */ + void setNavigationFragment(NavigationFragment fragment); +} diff --git a/src/com/cyanogenmod/filemanager/ui/widgets/BreadcrumbItem.java b/Backbone/src/main/java/me/toolify/backbone/ui/widgets/BreadcrumbItem.java similarity index 95% rename from src/com/cyanogenmod/filemanager/ui/widgets/BreadcrumbItem.java rename to Backbone/src/main/java/me/toolify/backbone/ui/widgets/BreadcrumbItem.java index 3516ea6b6..05034ca40 100644 --- a/src/com/cyanogenmod/filemanager/ui/widgets/BreadcrumbItem.java +++ b/Backbone/src/main/java/me/toolify/backbone/ui/widgets/BreadcrumbItem.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.ui.widgets; +package me.toolify.backbone.ui.widgets; import android.content.Context; import android.util.AttributeSet; @@ -23,8 +23,8 @@ import android.widget.TextView; import android.widget.Toast; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.util.DialogHelper; +import me.toolify.backbone.R; +import me.toolify.backbone.util.DialogHelper; /** * A class that represents a item of a breadcrumb. diff --git a/src/com/cyanogenmod/filemanager/ui/widgets/BreadcrumbListener.java b/Backbone/src/main/java/me/toolify/backbone/ui/widgets/BreadcrumbListener.java similarity index 88% rename from src/com/cyanogenmod/filemanager/ui/widgets/BreadcrumbListener.java rename to Backbone/src/main/java/me/toolify/backbone/ui/widgets/BreadcrumbListener.java index 1b1b7b782..bb2b002e1 100644 --- a/src/com/cyanogenmod/filemanager/ui/widgets/BreadcrumbListener.java +++ b/Backbone/src/main/java/me/toolify/backbone/ui/widgets/BreadcrumbListener.java @@ -14,7 +14,9 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.ui.widgets; +package me.toolify.backbone.ui.widgets; + +import java.io.File; /** * Interface with events from a breadcrumb. @@ -25,5 +27,5 @@ public interface BreadcrumbListener { * * @param item The breadcrumb item click */ - void onBreadcrumbItemClick(BreadcrumbItem item); + void onBreadcrumbItemClick(File item); } \ No newline at end of file diff --git a/Backbone/src/main/java/me/toolify/backbone/ui/widgets/BreadcrumbPager.java b/Backbone/src/main/java/me/toolify/backbone/ui/widgets/BreadcrumbPager.java new file mode 100644 index 000000000..0684028fc --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/ui/widgets/BreadcrumbPager.java @@ -0,0 +1,2176 @@ +package me.toolify.backbone.ui.widgets; + +/* + * Copyright (C) 2011 The Android Open Source Project + * + * 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. + */ + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.database.DataSetObserver; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.v4.os.ParcelableCompat; +import android.support.v4.os.ParcelableCompatCreatorCallbacks; +import android.support.v4.view.AccessibilityDelegateCompat; +import android.support.v4.view.PagerAdapter; +import android.support.v4.view.ViewCompat; +import android.support.v4.view.ViewPager; +import android.support.v4.view.ViewPager.OnPageChangeListener; +import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; +import android.support.v4.widget.EdgeEffectCompat; +import android.util.AttributeSet; +import android.util.Log; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.view.accessibility.AccessibilityEvent; +import android.view.animation.Interpolator; +import android.widget.Scroller; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; + +import me.toolify.backbone.FileManagerApplication; +import me.toolify.backbone.R; +import me.toolify.backbone.fragments.NavigationFragment; + +/** + * Layout manager that allows the user to flip left and right + * through pages of data. You supply an implementation of a + * {@link android.support.v4.view.PagerAdapter} to generate the pages that the view shows. + * + *

Note this class is currently under early design and + * development. The API will likely change in later updates of + * the compatibility library, requiring changes to the source code + * of apps when they are compiled against the newer version.

+ * + *

ViewPager is most often used in conjunction with {@link android.app.Fragment}, + * which is a convenient way to supply and manage the lifecycle of each page. + * There are standard adapters implemented for using fragments with the ViewPager, + * which cover the most common use cases. These are + * {@link android.support.v4.app.FragmentPagerAdapter} and + * {@link android.support.v4.app.FragmentStatePagerAdapter}; each of these + * classes have simple code showing how to build a full user interface + * with them. + * + *

Here is a more complicated example of ViewPager, using it in conjuction + * with {@link android.app.ActionBar} tabs. You can find other examples of using + * ViewPager in the API 4+ Support Demos and API 13+ Support Demos sample code. + * + * {@sample development/samples/Support13Demos/src/com/example/android/supportv13/app/ActionBarTabsPager.java + * complete} + */ +public class BreadcrumbPager extends ViewGroup { + private static final String TAG = "ViewPager"; + private static final boolean DEBUG = false; + + private static final boolean USE_CACHE = false; + + private static final int DEFAULT_OFFSCREEN_PAGES = 1; + private static final int MAX_SETTLE_DURATION = 600; // ms + + private static final int DEFAULT_GUTTER_SIZE = 16; // dips + + private static final int[] LAYOUT_ATTRS = new int[] { + android.R.attr.layout_gravity + }; + + private Context mContext; + + /** + * Used to track what the expected number of items in the adapter should be. + * If the app changes this when we don't expect it, we'll throw a big obnoxious exception. + */ + private int mExpectedAdapterCount; + + static class ItemInfo { + BreadcrumbSpinner breadcrumb; + int position; + boolean scrolling; + float widthFactor; + float offset; + } + + private static final Comparator COMPARATOR = new Comparator(){ + @Override + public int compare(ItemInfo lhs, ItemInfo rhs) { + return lhs.position - rhs.position; + } + }; + + private static final Interpolator sInterpolator = new Interpolator() { + public float getInterpolation(float t) { + t -= 1.0f; + return t * t * t * t * t + 1.0f; + } + }; + + private final ArrayList mItems = new ArrayList(); + private final ItemInfo mTempItem = new ItemInfo(); + private HashMap mQueuedFragments = new HashMap(); + + private Paint rectPaint; + private Paint dividerPaint; + + private int indicatorColor = 0xFF666666; + private int dividerColor = 0xFF666666; + private int indicatorHeight = 3; + private int dividerPadding = 12; + private int dividerWidth = 1; + + private PagerAdapter mAdapter; + private int mCurItem; // Index of currently displayed page. + private int mRestoredCurItem = -1; + private Parcelable mRestoredAdapterState = null; + private ClassLoader mRestoredClassLoader = null; + private Scroller mScroller; + private PagerObserver mObserver; + + private int mPageMargin; + private Drawable mMarginDrawable; + private int mTopPageBounds; + private int mBottomPageBounds; + + // Offsets of the first and last items, if known. + // Set during population, used to determine if we are at the beginning + // or end of the pager data set during touch scrolling. + private float mFirstOffset = -Float.MAX_VALUE; + private float mLastOffset = Float.MAX_VALUE; + + private int mChildWidthMeasureSpec; + private int mChildHeightMeasureSpec; + private boolean mInLayout; + + private boolean mScrollingCacheEnabled; + + private boolean mPopulatePending; + private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES; + + private int mDefaultGutterSize; + private int mGutterSize; + /** + * Position of the last motion event. + */ + private int lastScrollX = 0; + + private EdgeEffectCompat mLeftEdge; + private EdgeEffectCompat mRightEdge; + + private boolean mFirstLayout = true; + private boolean mNeedCalculatePageOffsets = false; + private boolean mCalledSuper; + private int mDecorChildCount; + + private ViewPager pager; + private OnPageChangeListener mDelegatePageListener; + private OnPageChangeListener mInternalPageChangeListener; + private OnAdapterChangeListener mAdapterChangeListener; + private PageTransformer mPageTransformer; + private Method mSetChildrenDrawingOrderEnabled; + private final PageListener pageListener = new PageListener(); + + private int mCurrentPage; + private float mPositionOffset; + + private static final int DRAW_ORDER_DEFAULT = 0; + private static final int DRAW_ORDER_FORWARD = 1; + private static final int DRAW_ORDER_REVERSE = 2; + private int mDrawingOrder; + private ArrayList mDrawingOrderedChildren; + private static final ViewPositionComparator sPositionComparator = new ViewPositionComparator(); + + /** + * Indicates that the pager is in an idle, settled state. The current page + * is fully in view and no animation is in progress. + */ + public static final int SCROLL_STATE_IDLE = 0; + + /** + * Indicates that the pager is in the process of settling to a final position. + */ + public static final int SCROLL_STATE_SETTLING = 2; + + private final Runnable mEndScrollRunnable = new Runnable() { + public void run() { + setScrollState(SCROLL_STATE_IDLE); + populate(); + } + }; + + private int mScrollState = SCROLL_STATE_IDLE; + + /** + * A PageTransformer is invoked whenever a visible/attached page is scrolled. + * This offers an opportunity for the application to apply a custom transformation + * to the page views using animation properties. + * + *

As property animation is only supported as of Android 3.0 and forward, + * setting a PageTransformer on a BreadcrumbPager on earlier platform versions will + * be ignored.

+ */ + public interface PageTransformer { + /** + * Apply a property transformation to the given page. + * + * @param page Apply the transformation to this page + * @param position Position of page relative to the current front-and-center + * position of the pager. 0 is front and center. 1 is one full + * page position to the right, and -1 is one page position to the left. + */ + public void transformPage(View page, float position); + } + + /** + * Used internally to monitor when adapters are switched. + */ + interface OnAdapterChangeListener { + public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter); + } + + /** + * Used internally to tag special types of child views that should be added as + * pager decorations by default. + */ + interface Decor {} + + public BreadcrumbPager(Context context) { + this(context, null); + } + + public BreadcrumbPager(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public BreadcrumbPager(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + mContext = context; + initBreadcrumbPager(); + } + + void initBreadcrumbPager() { + + setWillNotDraw(false); + setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); + setFocusable(true); + + final Context context = getContext(); + mScroller = new Scroller(context, sInterpolator); + final ViewConfiguration configuration = ViewConfiguration.get(context); + final float density = context.getResources().getDisplayMetrics().density; + + mLeftEdge = new EdgeEffectCompat(context); + mRightEdge = new EdgeEffectCompat(context); + + //setPageMargin(dividerPadding); + + mDefaultGutterSize = (int) (DEFAULT_GUTTER_SIZE * density); + + rectPaint = new Paint(); + rectPaint.setAntiAlias(true); + rectPaint.setStyle(Paint.Style.FILL); + + dividerPaint = new Paint(); + dividerPaint.setAntiAlias(true); + dividerPaint.setStrokeWidth(dividerWidth); + + ViewCompat.setAccessibilityDelegate(this, new MyAccessibilityDelegate()); + + if (ViewCompat.getImportantForAccessibility(this) + == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { + ViewCompat.setImportantForAccessibility(this, + ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); + } + + // Plug in the internal breadcrumb adapter + setAdapter(new BreadcrumbPagerAdapter(mContext)); + } + + /** + * Method that associates this breadcrumb pager with the target external viewPager + * + * @param pager + */ + public void setViewPager(ViewPager pager) { + this.pager = pager; + + if (pager.getAdapter() == null) { + throw new IllegalStateException("ViewPager does not have adapter instance."); + } + + pager.setOnPageChangeListener(pageListener); + } + + /** + * Method that queues a fragment/breadcrumb association, because the related + * {@link me.toolify.backbone.fragments.NavigationFragment} loaded before we could create the + * corresponding breadcrumb view in the breadcrumbPager. + * + * @param position the position of the fragment in its own viewPager + * @param fragment the fragment to be queued for pairing + */ + public void queuePairFragment(int position, NavigationFragment fragment) { + mQueuedFragments.put(position, fragment); + } + + @Override + protected void onDetachedFromWindow() { + removeCallbacks(mEndScrollRunnable); + super.onDetachedFromWindow(); + } + + private void setScrollState(int newState) { + if (mScrollState == newState) { + return; + } + + mScrollState = newState; + if (mPageTransformer != null) { + // PageTransformers can do complex things that benefit from hardware layers. + enableLayers(newState != SCROLL_STATE_IDLE); + } + if (mDelegatePageListener != null) { + mDelegatePageListener.onPageScrollStateChanged(newState); + } + } + + /** + * Set a PagerAdapter that will supply views for this pager as needed. + * + * @param adapter Adapter to use + */ + public void setAdapter(PagerAdapter adapter) { + if (mAdapter != null) { + mAdapter.unregisterDataSetObserver(mObserver); + mAdapter.startUpdate(this); + for (int i = 0; i < mItems.size(); i++) { + final ItemInfo ii = mItems.get(i); + mAdapter.destroyItem(this, ii.position, ii.breadcrumb); + } + mAdapter.finishUpdate(this); + mItems.clear(); + removeNonDecorViews(); + mCurItem = 0; + scrollTo(0, 0); + } + + final PagerAdapter oldAdapter = mAdapter; + mAdapter = adapter; + mExpectedAdapterCount = 0; + + if (mAdapter != null) { + if (mObserver == null) { + mObserver = new PagerObserver(); + } + mAdapter.registerDataSetObserver(mObserver); + mPopulatePending = false; + final boolean wasFirstLayout = mFirstLayout; + mFirstLayout = true; + mExpectedAdapterCount = mAdapter.getCount(); + if (mRestoredCurItem >= 0) { + mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader); + setCurrentItemInternal(mRestoredCurItem, false, true); + mRestoredCurItem = -1; + mRestoredAdapterState = null; + mRestoredClassLoader = null; + } else if (!wasFirstLayout) { + populate(); + } else { + requestLayout(); + } + } + + if (mAdapterChangeListener != null && oldAdapter != adapter) { + mAdapterChangeListener.onAdapterChanged(oldAdapter, adapter); + } + } + + private void removeNonDecorViews() { + for (int i = 0; i < getChildCount(); i++) { + final View child = getChildAt(i); + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + if (!lp.isDecor) { + removeViewAt(i); + i--; + } + } + } + + /** + * Retrieve the current adapter supplying pages. + * + * @return The currently registered PagerAdapter + */ + public PagerAdapter getAdapter() { + return mAdapter; + } + + void setOnAdapterChangeListener(OnAdapterChangeListener listener) { + mAdapterChangeListener = listener; + } + + private int getClientWidth() { + return getMeasuredWidth() - getPaddingLeft() - getPaddingRight(); + } + + /** + * Set the currently selected page. If the BreadcrumbPager has already been through its first + * layout with its current adapter there will be a smooth animated transition between + * the current item and the specified item. + * + * @param item Item index to select + */ + public void setCurrentItem(int item) { + mPopulatePending = false; + setCurrentItemInternal(item, !mFirstLayout, false); + } + + /** + * Set the currently selected page. + * + * @param item Item index to select + * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately + */ + public void setCurrentItem(int item, boolean smoothScroll) { + mPopulatePending = false; + setCurrentItemInternal(item, smoothScroll, false); + } + + public int getCurrentItem() { + return mCurItem; + } + + void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) { + setCurrentItemInternal(item, smoothScroll, always, 0); + } + + void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) { + if (mAdapter == null || mAdapter.getCount() <= 0) { + setScrollingCacheEnabled(false); + return; + } + if (!always && mCurItem == item && mItems.size() != 0) { + setScrollingCacheEnabled(false); + return; + } + + if (item < 0) { + item = 0; + } else if (item >= mAdapter.getCount()) { + item = mAdapter.getCount() - 1; + } + final int pageLimit = mOffscreenPageLimit; + if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) { + // We are doing a jump by more than one page. To avoid + // glitches, we want to keep all current pages in the view + // until the scroll ends. + for (int i=0; iNote: Prior to Android 3.0 the property animation APIs did not exist. + * As a result, setting a PageTransformer prior to Android 3.0 (API 11) will have no effect.

+ * + * @param reverseDrawingOrder true if the supplied PageTransformer requires page views + * to be drawn from last to first instead of first to last. + * @param transformer PageTransformer that will modify each page's animation properties + */ + public void setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer) { + if (Build.VERSION.SDK_INT >= 11) { + final boolean hasTransformer = transformer != null; + final boolean needsPopulate = hasTransformer != (mPageTransformer != null); + mPageTransformer = transformer; + setChildrenDrawingOrderEnabledCompat(hasTransformer); + if (hasTransformer) { + mDrawingOrder = reverseDrawingOrder ? DRAW_ORDER_REVERSE : DRAW_ORDER_FORWARD; + } else { + mDrawingOrder = DRAW_ORDER_DEFAULT; + } + if (needsPopulate) populate(); + } + } + + void setChildrenDrawingOrderEnabledCompat(boolean enable) { + if (Build.VERSION.SDK_INT >= 7) { + if (mSetChildrenDrawingOrderEnabled == null) { + try { + mSetChildrenDrawingOrderEnabled = ViewGroup.class.getDeclaredMethod( + "setChildrenDrawingOrderEnabled", new Class[] { Boolean.TYPE }); + } catch (NoSuchMethodException e) { + Log.e(TAG, "Can't find setChildrenDrawingOrderEnabled", e); + } + } + try { + mSetChildrenDrawingOrderEnabled.invoke(this, enable); + } catch (Exception e) { + Log.e(TAG, "Error changing children drawing order", e); + } + } + } + + @Override + protected int getChildDrawingOrder(int childCount, int i) { + final int index = mDrawingOrder == DRAW_ORDER_REVERSE ? childCount - 1 - i : i; + final int result = ((LayoutParams) mDrawingOrderedChildren.get(index).getLayoutParams()).childIndex; + return result; + } + + /** + * Set a separate OnPageChangeListener for internal use by the support library. + * + * @param listener Listener to set + * @return The old listener that was set, if any. + */ + OnPageChangeListener setInternalPageChangeListener(OnPageChangeListener listener) { + OnPageChangeListener oldListener = mInternalPageChangeListener; + mInternalPageChangeListener = listener; + return oldListener; + } + + /** + * Returns the number of pages that will be retained to either side of the + * current page in the view hierarchy in an idle state. Defaults to 1. + * + * @return How many pages will be kept offscreen on either side + * @see #setOffscreenPageLimit(int) + */ + public int getOffscreenPageLimit() { + return mOffscreenPageLimit; + } + + /** + * Set the number of pages that should be retained to either side of the + * current page in the view hierarchy in an idle state. Pages beyond this + * limit will be recreated from the adapter when needed. + * + *

This is offered as an optimization. If you know in advance the number + * of pages you will need to support or have lazy-loading mechanisms in place + * on your pages, tweaking this setting can have benefits in perceived smoothness + * of paging animations and interaction. If you have a small number of pages (3-4) + * that you can keep active all at once, less time will be spent in layout for + * newly created view subtrees as the user pages back and forth.

+ * + *

You should keep this limit low, especially if your pages have complex layouts. + * This setting defaults to 1.

+ * + * @param limit How many pages will be kept offscreen in an idle state. + */ + public void setOffscreenPageLimit(int limit) { + if (limit < DEFAULT_OFFSCREEN_PAGES) { + Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " + + DEFAULT_OFFSCREEN_PAGES); + limit = DEFAULT_OFFSCREEN_PAGES; + } + if (limit != mOffscreenPageLimit) { + mOffscreenPageLimit = limit; + populate(); + } + } + + /** + * Set the margin between pages. + * + * @param marginPixels Distance between adjacent pages in pixels + * @see #getPageMargin() + * @see #setPageMarginDrawable(Drawable) + * @see #setPageMarginDrawable(int) + */ + public void setPageMargin(int marginPixels) { + final int oldMargin = mPageMargin; + mPageMargin = marginPixels; + + final int width = getWidth(); + recomputeScrollPosition(width, width, marginPixels, oldMargin); + + requestLayout(); + } + + /** + * Return the margin between pages. + * + * @return The size of the margin in pixels + */ + public int getPageMargin() { + return mPageMargin; + } + + /** + * Set a drawable that will be used to fill the margin between pages. + * + * @param d Drawable to display between pages + */ + public void setPageMarginDrawable(Drawable d) { + mMarginDrawable = d; + if (d != null) refreshDrawableState(); + setWillNotDraw(d == null); + invalidate(); + } + + /** + * Set a drawable that will be used to fill the margin between pages. + * + * @param resId Resource ID of a drawable to display between pages + */ + public void setPageMarginDrawable(int resId) { + setPageMarginDrawable(getContext().getResources().getDrawable(resId)); + } + + @Override + protected boolean verifyDrawable(Drawable who) { + return super.verifyDrawable(who) || who == mMarginDrawable; + } + + @Override + protected void drawableStateChanged() { + super.drawableStateChanged(); + final Drawable d = mMarginDrawable; + if (d != null && d.isStateful()) { + d.setState(getDrawableState()); + } + } + + // We want the duration of the page snap animation to be influenced by the distance that + // the screen has to travel, however, we don't want this duration to be effected in a + // purely linear fashion. Instead, we use this method to moderate the effect that the distance + // of travel has on the overall snap duration. + float distanceInfluenceForSnapDuration(float f) { + f -= 0.5f; // center the values about 0. + f *= 0.3f * Math.PI / 2.0f; + return (float) Math.sin(f); + } + + /** + * Like {@link View#scrollBy}, but scroll smoothly instead of immediately. + * + * @param x the number of pixels to scroll by on the X axis + * @param y the number of pixels to scroll by on the Y axis + */ + void smoothScrollTo(int x, int y) { + smoothScrollTo(x, y, 0); + } + + /** + * Like {@link View#scrollBy}, but scroll smoothly instead of immediately. + * + * @param x the number of pixels to scroll by on the X axis + * @param y the number of pixels to scroll by on the Y axis + * @param velocity the velocity associated with a fling, if applicable. (0 otherwise) + */ + void smoothScrollTo(int x, int y, int velocity) { + if (getChildCount() == 0) { + // Nothing to do. + setScrollingCacheEnabled(false); + return; + } + int sx = getScrollX(); + int sy = getScrollY(); + int dx = x - sx; + int dy = y - sy; + if (dx == 0 && dy == 0) { + completeScroll(false); + populate(); + setScrollState(SCROLL_STATE_IDLE); + return; + } + + setScrollingCacheEnabled(true); + setScrollState(SCROLL_STATE_SETTLING); + + final int width = getClientWidth(); + final int halfWidth = width / 2; + final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width); + final float distance = halfWidth + halfWidth * + distanceInfluenceForSnapDuration(distanceRatio); + + int duration = 0; + velocity = Math.abs(velocity); + if (velocity > 0) { + duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); + } else { + final float pageWidth = width * mAdapter.getPageWidth(mCurItem); + final float pageDelta = (float) Math.abs(dx) / (pageWidth + mPageMargin); + duration = (int) ((pageDelta + 1) * 100); + } + duration = Math.min(duration, MAX_SETTLE_DURATION); + + mScroller.startScroll(sx, sy, dx, dy, duration); + ViewCompat.postInvalidateOnAnimation(this); + } + + ItemInfo addNewItem(int position, int index) { + ItemInfo ii = new ItemInfo(); + ii.position = position; + ii.breadcrumb = (BreadcrumbSpinner)mAdapter.instantiateItem(this, position); + ii.widthFactor = mAdapter.getPageWidth(position); + if (index < 0 || index >= mItems.size()) { + mItems.add(ii); + } else { + mItems.add(index, ii); + } + return ii; + } + + void dataSetChanged() { + // This method only gets called if our observer is attached, so mAdapter is non-null. + + final int adapterCount = mAdapter.getCount(); + mExpectedAdapterCount = adapterCount; + boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 && + mItems.size() < adapterCount; + int newCurrItem = mCurItem; + + boolean isUpdating = false; + for (int i = 0; i < mItems.size(); i++) { + final ItemInfo ii = mItems.get(i); + final int newPos = mAdapter.getItemPosition(ii.breadcrumb); + + if (newPos == PagerAdapter.POSITION_UNCHANGED) { + continue; + } + + if (newPos == PagerAdapter.POSITION_NONE) { + mItems.remove(i); + i--; + + if (!isUpdating) { + mAdapter.startUpdate(this); + isUpdating = true; + } + + mAdapter.destroyItem(this, ii.position, ii.breadcrumb); + needPopulate = true; + + if (mCurItem == ii.position) { + // Keep the current item in the valid range + newCurrItem = Math.max(0, Math.min(mCurItem, adapterCount - 1)); + needPopulate = true; + } + continue; + } + + if (ii.position != newPos) { + if (ii.position == mCurItem) { + // Our current item changed position. Follow it. + newCurrItem = newPos; + } + + ii.position = newPos; + needPopulate = true; + } + } + + if (isUpdating) { + mAdapter.finishUpdate(this); + } + + Collections.sort(mItems, COMPARATOR); + + if (needPopulate) { + // Reset our known page widths; populate will recompute them. + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + if (!lp.isDecor) { + lp.widthFactor = 0.f; + } + } + + setCurrentItemInternal(newCurrItem, false, true); + requestLayout(); + } + } + + void populate() { + populate(mCurItem); + } + + void populate(int newCurrentItem) { + ItemInfo oldCurInfo = null; + int focusDirection = View.FOCUS_FORWARD; + if (mCurItem != newCurrentItem) { + focusDirection = mCurItem < newCurrentItem ? View.FOCUS_RIGHT : View.FOCUS_LEFT; + oldCurInfo = infoForPosition(mCurItem); + mCurItem = newCurrentItem; + } + + if (mAdapter == null) { + sortChildDrawingOrder(); + return; + } + + // Bail now if we are waiting to populate. This is to hold off + // on creating views from the time the user releases their finger to + // fling to a new position until we have finished the scroll to + // that position, avoiding glitches from happening at that point. + if (mPopulatePending) { + if (DEBUG) Log.i(TAG, "populate is pending, skipping for now..."); + sortChildDrawingOrder(); + return; + } + + // Also, don't populate until we are attached to a window. This is to + // avoid trying to populate before we have restored our view hierarchy + // state and conflicting with what is restored. + if (getWindowToken() == null) { + return; + } + + mAdapter.startUpdate(this); + + final int pageLimit = mOffscreenPageLimit; + final int startPos = Math.max(0, mCurItem - pageLimit); + final int N = mAdapter.getCount(); + final int endPos = Math.min(N-1, mCurItem + pageLimit); + + if (N != mExpectedAdapterCount) { + String resName; + try { + resName = getResources().getResourceName(getId()); + } catch (Resources.NotFoundException e) { + resName = Integer.toHexString(getId()); + } + throw new IllegalStateException("The application's PagerAdapter changed the adapter's" + + " contents without calling PagerAdapter#notifyDataSetChanged!" + + " Expected adapter item count: " + mExpectedAdapterCount + ", found: " + N + + " Pager id: " + resName + + " Pager class: " + getClass() + + " Problematic adapter: " + mAdapter.getClass()); + } + + // Locate the currently focused item or add it if needed. + int curIndex = -1; + ItemInfo curItem = null; + for (curIndex = 0; curIndex < mItems.size(); curIndex++) { + final ItemInfo ii = mItems.get(curIndex); + if (ii.position >= mCurItem) { + if (ii.position == mCurItem) curItem = ii; + break; + } + } + + if (curItem == null && N > 0) { + curItem = addNewItem(mCurItem, curIndex); + } + + // Fill 3x the available width or up to the number of offscreen + // pages requested to either side, whichever is larger. + // If we have no current item we have no work to do. + if (curItem != null) { + float extraWidthLeft = 0.f; + int itemIndex = curIndex - 1; + ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; + final float leftWidthNeeded = 2.f - curItem.widthFactor + + (float) getPaddingLeft() / (float) getClientWidth(); + for (int pos = mCurItem - 1; pos >= 0; pos--) { + if (extraWidthLeft >= leftWidthNeeded && pos < startPos) { + if (ii == null) { + break; + } + if (pos == ii.position && !ii.scrolling) { + mItems.remove(itemIndex); + mAdapter.destroyItem(this, pos, ii.breadcrumb); + if (DEBUG) { + Log.i(TAG, "populate() - destroyItem() with pos: " + pos + + " view: " + ((View) ii.breadcrumb)); + } + itemIndex--; + curIndex--; + ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; + } + } else if (ii != null && pos == ii.position) { + extraWidthLeft += ii.widthFactor; + itemIndex--; + ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; + } else { + ii = addNewItem(pos, itemIndex + 1); + extraWidthLeft += ii.widthFactor; + curIndex++; + ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; + } + } + + float extraWidthRight = curItem.widthFactor; + itemIndex = curIndex + 1; + if (extraWidthRight < 2.f) { + ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null; + final float rightWidthNeeded = (float) getPaddingRight() / (float) getClientWidth() + + 2.f; + for (int pos = mCurItem + 1; pos < N; pos++) { + if (extraWidthRight >= rightWidthNeeded && pos > endPos) { + if (ii == null) { + break; + } + if (pos == ii.position && !ii.scrolling) { + mItems.remove(itemIndex); + mAdapter.destroyItem(this, pos, ii.breadcrumb); + if (DEBUG) { + Log.i(TAG, "populate() - destroyItem() with pos: " + pos + + " view: " + ((View) ii.breadcrumb)); + } + ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null; + } + } else if (ii != null && pos == ii.position) { + extraWidthRight += ii.widthFactor; + itemIndex++; + ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null; + } else { + ii = addNewItem(pos, itemIndex); + itemIndex++; + extraWidthRight += ii.widthFactor; + ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null; + } + } + } + + calculatePageOffsets(curItem, curIndex, oldCurInfo); + } + + if (DEBUG) { + Log.i(TAG, "Current page list:"); + for (int i=0; i(); + } else { + mDrawingOrderedChildren.clear(); + } + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + mDrawingOrderedChildren.add(child); + } + Collections.sort(mDrawingOrderedChildren, sPositionComparator); + } + } + + private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo) { + final int N = mAdapter.getCount(); + final int width = getClientWidth(); + final float marginOffset = width > 0 ? (float) mPageMargin / width : 0; + // Fix up offsets for later layout. + if (oldCurInfo != null) { + final int oldCurPosition = oldCurInfo.position; + // Base offsets off of oldCurInfo. + if (oldCurPosition < curItem.position) { + int itemIndex = 0; + ItemInfo ii = null; + float offset = oldCurInfo.offset + oldCurInfo.widthFactor + marginOffset; + for (int pos = oldCurPosition + 1; + pos <= curItem.position && itemIndex < mItems.size(); pos++) { + ii = mItems.get(itemIndex); + while (pos > ii.position && itemIndex < mItems.size() - 1) { + itemIndex++; + ii = mItems.get(itemIndex); + } + while (pos < ii.position) { + // We don't have an item populated for this, + // ask the adapter for an offset. + offset += mAdapter.getPageWidth(pos) + marginOffset; + pos++; + } + ii.offset = offset; + offset += ii.widthFactor + marginOffset; + } + } else if (oldCurPosition > curItem.position) { + int itemIndex = mItems.size() - 1; + ItemInfo ii = null; + float offset = oldCurInfo.offset; + for (int pos = oldCurPosition - 1; + pos >= curItem.position && itemIndex >= 0; pos--) { + ii = mItems.get(itemIndex); + while (pos < ii.position && itemIndex > 0) { + itemIndex--; + ii = mItems.get(itemIndex); + } + while (pos > ii.position) { + // We don't have an item populated for this, + // ask the adapter for an offset. + offset -= mAdapter.getPageWidth(pos) + marginOffset; + pos--; + } + offset -= ii.widthFactor + marginOffset; + ii.offset = offset; + } + } + } + + // Base all offsets off of curItem. + final int itemCount = mItems.size(); + float offset = curItem.offset; + int pos = curItem.position - 1; + mFirstOffset = curItem.position == 0 ? curItem.offset : -Float.MAX_VALUE; + mLastOffset = curItem.position == N - 1 ? + curItem.offset + curItem.widthFactor - 1 : Float.MAX_VALUE; + // Previous pages + for (int i = curIndex - 1; i >= 0; i--, pos--) { + final ItemInfo ii = mItems.get(i); + while (pos > ii.position) { + offset -= mAdapter.getPageWidth(pos--) + marginOffset; + } + offset -= ii.widthFactor + marginOffset; + ii.offset = offset; + if (ii.position == 0) mFirstOffset = offset; + } + offset = curItem.offset + curItem.widthFactor + marginOffset; + pos = curItem.position + 1; + // Next pages + for (int i = curIndex + 1; i < itemCount; i++, pos++) { + final ItemInfo ii = mItems.get(i); + while (pos < ii.position) { + offset += mAdapter.getPageWidth(pos++) + marginOffset; + } + if (ii.position == N - 1) { + mLastOffset = offset + ii.widthFactor - 1; + } + ii.offset = offset; + offset += ii.widthFactor + marginOffset; + } + + mNeedCalculatePageOffsets = false; + } + + /** + * This is the persistent state that is saved by BreadcrumbPager. Only needed + * if you are creating a sublass of BreadcrumbPager that must save its own + * state, in which case it should implement a subclass of this which + * contains that state. + */ + public static class SavedState extends BaseSavedState { + int position; + Parcelable adapterState; + ClassLoader loader; + + public SavedState(Parcelable superState) { + super(superState); + } + + @Override + public void writeToParcel(Parcel out, int flags) { + super.writeToParcel(out, flags); + out.writeInt(position); + out.writeParcelable(adapterState, flags); + } + + @Override + public String toString() { + return "FragmentPager.SavedState{" + + Integer.toHexString(System.identityHashCode(this)) + + " position=" + position + "}"; + } + + public static final Parcelable.Creator CREATOR + = ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks() { + @Override + public SavedState createFromParcel(Parcel in, ClassLoader loader) { + return new SavedState(in, loader); + } + + @Override + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }); + + SavedState(Parcel in, ClassLoader loader) { + super(in); + if (loader == null) { + loader = getClass().getClassLoader(); + } + position = in.readInt(); + adapterState = in.readParcelable(loader); + this.loader = loader; + } + } + + @Override + public Parcelable onSaveInstanceState() { + Parcelable superState = super.onSaveInstanceState(); + SavedState ss = new SavedState(superState); + ss.position = mCurItem; + if (mAdapter != null) { + ss.adapterState = mAdapter.saveState(); + } + return ss; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + if (!(state instanceof SavedState)) { + super.onRestoreInstanceState(state); + return; + } + + SavedState ss = (SavedState)state; + super.onRestoreInstanceState(ss.getSuperState()); + + if (mAdapter != null) { + mAdapter.restoreState(ss.adapterState, ss.loader); + setCurrentItemInternal(ss.position, false, true); + } else { + mRestoredCurItem = ss.position; + mRestoredAdapterState = ss.adapterState; + mRestoredClassLoader = ss.loader; + } + } + + @Override + public void addView(View child, int index, ViewGroup.LayoutParams params) { + if (!checkLayoutParams(params)) { + params = generateLayoutParams(params); + } + final LayoutParams lp = (LayoutParams) params; + lp.isDecor |= child instanceof Decor; + if (mInLayout) { + if (lp != null && lp.isDecor) { + throw new IllegalStateException("Cannot add pager decor view during layout"); + } + lp.needsMeasure = true; + addViewInLayout(child, index, params); + } else { + super.addView(child, index, params); + } + + if (USE_CACHE) { + if (child.getVisibility() != GONE) { + child.setDrawingCacheEnabled(mScrollingCacheEnabled); + } else { + child.setDrawingCacheEnabled(false); + } + } + } + + @Override + public void removeView(View view) { + if (mInLayout) { + removeViewInLayout(view); + } else { + super.removeView(view); + } + } + + ItemInfo infoForChild(View child) { + for (int i=0; i 0 && !mItems.isEmpty()) { + final int widthWithMargin = width - getPaddingLeft() - getPaddingRight() + margin; + final int oldWidthWithMargin = oldWidth - getPaddingLeft() - getPaddingRight() + + oldMargin; + final int xpos = getScrollX(); + final float pageOffset = (float) xpos / oldWidthWithMargin; + final int newOffsetPixels = (int) (pageOffset * widthWithMargin); + + scrollTo(newOffsetPixels, getScrollY()); + if (!mScroller.isFinished()) { + // We now return to your regularly scheduled scroll, already in progress. + final int newDuration = mScroller.getDuration() - mScroller.timePassed(); + ItemInfo targetInfo = infoForPosition(mCurItem); + mScroller.startScroll(newOffsetPixels, 0, + (int) (targetInfo.offset * width), 0, newDuration); + } + } else { + final ItemInfo ii = infoForPosition(mCurItem); + final float scrollOffset = ii != null ? Math.min(ii.offset, mLastOffset) : 0; + final int scrollPos = (int) (scrollOffset * + (width - getPaddingLeft() - getPaddingRight())); + if (scrollPos != getScrollX()) { + completeScroll(false); + scrollTo(scrollPos, getScrollY()); + } + } + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + final int count = getChildCount(); + int width = r - l; + int height = b - t; + int paddingLeft = getPaddingLeft(); + int paddingTop = getPaddingTop(); + int paddingRight = getPaddingRight(); + int paddingBottom = getPaddingBottom(); + final int scrollX = getScrollX(); + + int decorCount = 0; + + // First pass - decor views. We need to do this in two passes so that + // we have the proper offsets for non-decor views later. + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child.getVisibility() != GONE) { + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + int childLeft = 0; + int childTop = 0; + if (lp.isDecor) { + final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; + final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK; + switch (hgrav) { + default: + childLeft = paddingLeft; + break; + case Gravity.LEFT: + childLeft = paddingLeft; + paddingLeft += child.getMeasuredWidth(); + break; + case Gravity.CENTER_HORIZONTAL: + childLeft = Math.max((width - child.getMeasuredWidth()) / 2, + paddingLeft); + break; + case Gravity.RIGHT: + childLeft = width - paddingRight - child.getMeasuredWidth(); + paddingRight += child.getMeasuredWidth(); + break; + } + switch (vgrav) { + default: + childTop = paddingTop; + break; + case Gravity.TOP: + childTop = paddingTop; + paddingTop += child.getMeasuredHeight(); + break; + case Gravity.CENTER_VERTICAL: + childTop = Math.max((height - child.getMeasuredHeight()) / 2, + paddingTop); + break; + case Gravity.BOTTOM: + childTop = height - paddingBottom - child.getMeasuredHeight(); + paddingBottom += child.getMeasuredHeight(); + break; + } + childLeft += scrollX; + child.layout(childLeft, childTop, + childLeft + child.getMeasuredWidth(), + childTop + child.getMeasuredHeight()); + decorCount++; + } + } + } + + final int childWidth = width - paddingLeft - paddingRight; + // Page views. Do this once we have the right padding offsets from above. + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child.getVisibility() != GONE) { + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + ItemInfo ii; + if (!lp.isDecor && (ii = infoForChild(child)) != null) { + int loff = (int) (childWidth * ii.offset); + int childLeft = paddingLeft + loff; + int childTop = paddingTop; + if (lp.needsMeasure) { + // This was added during layout and needs measurement. + // Do it now that we know what we're working with. + lp.needsMeasure = false; + final int widthSpec = MeasureSpec.makeMeasureSpec( + (int) (childWidth * lp.widthFactor), + MeasureSpec.EXACTLY); + final int heightSpec = MeasureSpec.makeMeasureSpec( + (int) (height - paddingTop - paddingBottom), + MeasureSpec.EXACTLY); + child.measure(widthSpec, heightSpec); + } + if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.breadcrumb + + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth() + + "x" + child.getMeasuredHeight()); + child.layout(childLeft, childTop, + childLeft + child.getMeasuredWidth(), + childTop + child.getMeasuredHeight()); + } + } + } + mTopPageBounds = paddingTop; + mBottomPageBounds = height - paddingBottom; + mDecorChildCount = decorCount; + + if (mFirstLayout) { + scrollToItem(mCurItem, false, 0, false); + } + mFirstLayout = false; + } + + private void completeScroll(boolean postEvents) { + boolean needPopulate = mScrollState == SCROLL_STATE_SETTLING; + if (needPopulate) { + // Done with scroll, no longer want to cache view drawing. + setScrollingCacheEnabled(false); + mScroller.abortAnimation(); + int oldX = getScrollX(); + int oldY = getScrollY(); + int x = mScroller.getCurrX(); + int y = mScroller.getCurrY(); + if (oldX != x || oldY != y) { + scrollTo(x, y); + } + } + mPopulatePending = false; + for (int i=0; i 0) || (x > getWidth() - mGutterSize && dx < 0); + } + + private void enableLayers(boolean enable) { + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + final int layerType = enable ? + ViewCompat.LAYER_TYPE_HARDWARE : ViewCompat.LAYER_TYPE_NONE; + ViewCompat.setLayerType(getChildAt(i), layerType, null); + } + } + + /** + * @return Info about the page at the current scroll position. + * This can be synthetic for a missing middle page; the 'breadcrumb' field can be null. + */ + private ItemInfo infoForCurrentScrollPosition() { + final int width = getClientWidth(); + final float scrollOffset = width > 0 ? (float) getScrollX() / width : 0; + final float marginOffset = width > 0 ? (float) mPageMargin / width : 0; + int lastPos = -1; + float lastOffset = 0.f; + float lastWidth = 0.f; + boolean first = true; + + ItemInfo lastItem = null; + for (int i = 0; i < mItems.size(); i++) { + ItemInfo ii = mItems.get(i); + float offset; + if (!first && ii.position != lastPos + 1) { + // Create a synthetic item for a missing page. + ii = mTempItem; + ii.offset = lastOffset + lastWidth + marginOffset; + ii.position = lastPos + 1; + ii.widthFactor = mAdapter.getPageWidth(ii.position); + i--; + } + offset = ii.offset; + + final float leftBound = offset; + final float rightBound = offset + ii.widthFactor + marginOffset; + if (first || scrollOffset >= leftBound) { + if (scrollOffset < rightBound || i == mItems.size() - 1) { + return ii; + } + } else { + return lastItem; + } + first = false; + lastPos = ii.position; + lastOffset = offset; + lastWidth = ii.widthFactor; + lastItem = ii; + } + + return lastItem; + } + + @Override + public void draw(Canvas canvas) { + super.draw(canvas); + boolean needsInvalidate = false; + + final int overScrollMode = ViewCompat.getOverScrollMode(this); + if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS || + (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS && + mAdapter != null && mAdapter.getCount() > 1)) { + if (!mLeftEdge.isFinished()) { + final int restoreCount = canvas.save(); + final int height = getHeight() - getPaddingTop() - getPaddingBottom(); + final int width = getWidth(); + + canvas.rotate(270); + canvas.translate(-height + getPaddingTop(), mFirstOffset * width); + mLeftEdge.setSize(height, width); + needsInvalidate |= mLeftEdge.draw(canvas); + canvas.restoreToCount(restoreCount); + } + if (!mRightEdge.isFinished()) { + final int restoreCount = canvas.save(); + final int width = getWidth(); + final int height = getHeight() - getPaddingTop() - getPaddingBottom(); + + canvas.rotate(90); + canvas.translate(-getPaddingTop(), -(mLastOffset + 1) * width); + mRightEdge.setSize(height, width); + needsInvalidate |= mRightEdge.draw(canvas); + canvas.restoreToCount(restoreCount); + } + } else { + mLeftEdge.finish(); + mRightEdge.finish(); + } + + if (needsInvalidate) { + // Keep animating + ViewCompat.postInvalidateOnAnimation(this); + } + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + final int count = mItems.size(); + final int width = getWidth(); + + if (count > 0) { + + /* TODO get the underline indicator to match motion with the view pager, since it is + currently moving in the opposite direction. + // Draw the underline + rectPaint.setColor(indicatorColor); + final int paddingLeft = getPaddingLeft(); + final float pageWidth = (getWidth() - paddingLeft - getPaddingRight()) / (1f * count); + final float left = paddingLeft + pageWidth * (mCurrentPage + mPositionOffset); + final float right = left + pageWidth; + final float top = getHeight() - indicatorHeight - getPaddingBottom() - 2; + final float bottom = getHeight() - getPaddingBottom() - 2; + canvas.drawRect(left, top, right, bottom, rectPaint); */ + + // Draw the margin drawable between pages if needed. + if (mPageMargin > 0 && mItems.size() > 0 && mAdapter != null) { //&& mMarginDrawable != null + + dividerPaint.setColor(dividerColor); + final int scrollX = getScrollX(); + + final float marginOffset = (float) mPageMargin / width; + int itemIndex = 0; + ItemInfo ii = mItems.get(0); + float offset = ii.offset; + final int itemCount = mItems.size(); + final int firstPos = ii.position; + final int lastPos = mItems.get(itemCount - 1).position; + // TODO move this so it's only calculated after view height changes + final int verticalPadding = (int)(getHeight() * 0.1); + for (int pos = firstPos; pos < lastPos; pos++) { + while (pos > ii.position && itemIndex < itemCount) { + ii = mItems.get(++itemIndex); + } + + float drawAt; + if (pos == ii.position) { + drawAt = (ii.offset + ii.widthFactor) * width; + offset = ii.offset + ii.widthFactor + marginOffset; + } else { + float widthFactor = mAdapter.getPageWidth(pos); + drawAt = (offset + widthFactor) * width; + offset += widthFactor + marginOffset; + } + + if (drawAt + mPageMargin > scrollX) { + + canvas.drawLine((int) drawAt + (mPageMargin / 2), mTopPageBounds + verticalPadding, + (int) drawAt + (mPageMargin / 2), mBottomPageBounds - verticalPadding, dividerPaint); + } + + if (drawAt > scrollX + width) { + break; // No more visible, no sense in continuing + } + } + } + } + } + + public void refreshLayout() { + for (ItemInfo i : mItems) { + i.breadcrumb.refresh(); + } + } + + private void scrollToChild(int position, int xOffset) { + + if (mItems.size() == 0) { + return; + } + + int newScrollX = mItems.get(position).breadcrumb.getLeft() + xOffset; + + if (newScrollX != lastScrollX) { + lastScrollX = newScrollX; + scrollTo(newScrollX, 0); + } + + invalidate(); + } + + private void setScrollingCacheEnabled(boolean enabled) { + if (mScrollingCacheEnabled != enabled) { + mScrollingCacheEnabled = enabled; + if (USE_CACHE) { + final int size = getChildCount(); + for (int i = 0; i < size; ++i) { + final View child = getChildAt(i); + if (child.getVisibility() != GONE) { + child.setDrawingCacheEnabled(enabled); + } + } + } + } + } + + /** + * We only want the current page that is being shown to be focusable. + */ + @Override + public void addFocusables(ArrayList views, int direction, int focusableMode) { + final int focusableCount = views.size(); + + final int descendantFocusability = getDescendantFocusability(); + + if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) { + for (int i = 0; i < getChildCount(); i++) { + final View child = getChildAt(i); + if (child.getVisibility() == VISIBLE) { + ItemInfo ii = infoForChild(child); + if (ii != null && ii.position == mCurItem) { + child.addFocusables(views, direction, focusableMode); + } + } + } + } + + // we add ourselves (if focusable) in all cases except for when we are + // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable. this is + // to avoid the focus search finding layouts when a more precise search + // among the focusable children would be more interesting. + if ( + descendantFocusability != FOCUS_AFTER_DESCENDANTS || + // No focusable descendants + (focusableCount == views.size())) { + // Note that we can't call the superclass here, because it will + // add all views in. So we need to do the same thing View does. + if (!isFocusable()) { + return; + } + if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE && + isInTouchMode() && !isFocusableInTouchMode()) { + return; + } + if (views != null) { + views.add(this); + } + } + } + + /** + * We only want the current page that is being shown to be touchable. + */ + @Override + public void addTouchables(ArrayList views) { + // Note that we don't call super.addTouchables(), which means that + // we don't call View.addTouchables(). This is okay because a BreadcrumbPager + // is itself not touchable. + for (int i = 0; i < getChildCount(); i++) { + final View child = getChildAt(i); + if (child.getVisibility() == VISIBLE) { + ItemInfo ii = infoForChild(child); + if (ii != null && ii.position == mCurItem) { + child.addTouchables(views); + } + } + } + } + + /** + * We only want the current page that is being shown to be focusable. + */ + @Override + protected boolean onRequestFocusInDescendants(int direction, + Rect previouslyFocusedRect) { + int index; + int increment; + int end; + int count = getChildCount(); + if ((direction & FOCUS_FORWARD) != 0) { + index = 0; + increment = 1; + end = count; + } else { + index = count - 1; + increment = -1; + end = -1; + } + for (int i = index; i != end; i += increment) { + View child = getChildAt(i); + if (child.getVisibility() == VISIBLE) { + ItemInfo ii = infoForChild(child); + if (ii != null && ii.position == mCurItem) { + if (child.requestFocus(direction, previouslyFocusedRect)) { + return true; + } + } + } + } + return false; + } + + @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + // BreadcrumbPagers should only report accessibility info for the current page, + // otherwise things get very confusing. + + // TODO: Should this note something about the paging container? + + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + if (child.getVisibility() == VISIBLE) { + final ItemInfo ii = infoForChild(child); + if (ii != null && ii.position == mCurItem && + child.dispatchPopulateAccessibilityEvent(event)) { + return true; + } + } + } + + return false; + } + + @Override + protected ViewGroup.LayoutParams generateDefaultLayoutParams() { + return new LayoutParams(); + } + + @Override + protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { + return generateDefaultLayoutParams(); + } + + @Override + protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { + return p instanceof LayoutParams && super.checkLayoutParams(p); + } + + @Override + public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { + return new LayoutParams(getContext(), attrs); + } + + class MyAccessibilityDelegate extends AccessibilityDelegateCompat { + + @Override + public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(host, event); + event.setClassName(BreadcrumbPager.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { + super.onInitializeAccessibilityNodeInfo(host, info); + info.setClassName(BreadcrumbPager.class.getName()); + info.setScrollable(mAdapter != null && mAdapter.getCount() > 1); + if (mAdapter != null && mCurItem >= 0 && mCurItem < mAdapter.getCount() - 1) { + info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD); + } + if (mAdapter != null && mCurItem > 0 && mCurItem < mAdapter.getCount()) { + info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD); + } + } + + @Override + public boolean performAccessibilityAction(View host, int action, Bundle args) { + if (super.performAccessibilityAction(host, action, args)) { + return true; + } + switch (action) { + case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: { + if (mAdapter != null && mCurItem >= 0 && mCurItem < mAdapter.getCount() - 1) { + setCurrentItem(mCurItem + 1); + return true; + } + } return false; + case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: { + if (mAdapter != null && mCurItem > 0 && mCurItem < mAdapter.getCount()) { + setCurrentItem(mCurItem - 1); + return true; + } + } return false; + } + return false; + } + } + + private class PagerObserver extends DataSetObserver { + @Override + public void onChanged() { + dataSetChanged(); + } + @Override + public void onInvalidated() { + dataSetChanged(); + } + } + + /** + * Layout parameters that should be supplied for views added to a + * BreadcrumbPager. + */ + public static class LayoutParams extends ViewGroup.LayoutParams { + /** + * true if this view is a decoration on the pager itself and not + * a view supplied by the adapter. + */ + public boolean isDecor; + + /** + * Gravity setting for use on decor views only: + * Where to position the view page within the overall BreadcrumbPager + * container; constants are defined in {@link android.view.Gravity}. + */ + public int gravity; + + /** + * Width as a 0-1 multiplier of the measured pager width + */ + float widthFactor = 0.f; + + /** + * true if this view was added during layout and needs to be measured + * before being positioned. + */ + boolean needsMeasure; + + /** + * Adapter position this view is for if !isDecor + */ + int position; + + /** + * Current child index within the BreadcrumbPager that this view occupies + */ + int childIndex; + + public LayoutParams() { + super(FILL_PARENT, FILL_PARENT); + } + + public LayoutParams(Context context, AttributeSet attrs) { + super(context, attrs); + + final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS); + gravity = a.getInteger(0, Gravity.TOP); + a.recycle(); + } + } + + static class ViewPositionComparator implements Comparator { + @Override + public int compare(View lhs, View rhs) { + final LayoutParams llp = (LayoutParams) lhs.getLayoutParams(); + final LayoutParams rlp = (LayoutParams) rhs.getLayoutParams(); + if (llp.isDecor != rlp.isDecor) { + return llp.isDecor ? 1 : -1; + } + return llp.position - rlp.position; + } + } + + private class PageListener implements ViewPager.OnPageChangeListener { + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + + mCurrentPage = position; + mPositionOffset = positionOffset; + + if (position == 1) { + boolean test = true; + } + final int width = mItems.get(position).breadcrumb.getWidth(); + + scrollToChild(position, (int) (positionOffset * width)); + + if (mDelegatePageListener != null) { + mDelegatePageListener.onPageScrolled(position, positionOffset, positionOffsetPixels); + } + } + + @Override + public void onPageScrollStateChanged(int state) { + if (state == ViewPager.SCROLL_STATE_IDLE) { + setCurrentItem(pager.getCurrentItem()); + } + + if (mDelegatePageListener != null) { + mDelegatePageListener.onPageScrollStateChanged(state); + } + } + + @Override + public void onPageSelected(int position) { + if (mDelegatePageListener != null) { + mDelegatePageListener.onPageSelected(position); + } + } + + } + + private class BreadcrumbPagerAdapter extends PagerAdapter { + + private int mNumBreadcrumbs; + private Context mContext; + + public BreadcrumbPagerAdapter(Context context) { + this.mContext = context; + this.mNumBreadcrumbs = FileManagerApplication.NUM_PAGES; + } + + /** + * Get a View that displays the data at the specified position in the data set. + * + * @param position The position of the item within the adapter's data set of the item whose view we want. + * @param pager The ViewPager that this view will eventually be attached to. + * + * @return A View corresponding to the data at the specified position. + */ + public View getView(int position, BreadcrumbPager pager) { + return LayoutInflater.from(this.mContext).inflate(R.layout.breadcrumb_spinner, null, false); + } + + /** + * Determines whether a page View is associated with a specific key breadcrumb as + * returned by instantiateItem(ViewGroup, int). + * + * @param view Page View to check for association with breadcrumb + * @param object Object to check for association with view + * + * @return true if view is associated with the key breadcrumb breadcrumb. + */ + @Override + public boolean isViewFromObject(View view, Object object) { + return view == object; + } + + @Override + public int getCount() { + return mNumBreadcrumbs; + } + + /** + * Create the page for the given position. + * + * @param container The containing View in which the page will be shown. + * @param position The page position to be instantiated. + * + * @return Returns an Object representing the new page. This does not need + * to be a View, but can be some other container of the page. + */ + @Override + public Object instantiateItem(ViewGroup container, int position) { + BreadcrumbPager pager = (BreadcrumbPager) container; + View view = getView(position, pager); + + pager.addView(view); + + // Check to see if a fragment/breadcrumb has been queued (when the fragment loads before + // the corresponding breadcrumb does. + if (mQueuedFragments.containsKey(position)) { + mQueuedFragments.get(position).setBreadcrumb((Breadcrumb) view); + } + + return view; + } + + /** + * Remove a page for the given position. + * + * @param container The containing View from which the page will be removed. + * @param position The page position to be removed. + * @param view The same breadcrumb that was returned by instantiateItem(View, int). + */ + @Override + public void destroyItem(ViewGroup container, int position, Object view) { + ((BreadcrumbPager) container).removeView((View) view); + } + } +} diff --git a/Backbone/src/main/java/me/toolify/backbone/ui/widgets/BreadcrumbSpinner.java b/Backbone/src/main/java/me/toolify/backbone/ui/widgets/BreadcrumbSpinner.java new file mode 100644 index 000000000..bbc5829f6 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/ui/widgets/BreadcrumbSpinner.java @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2013 BrandroidTools + * + * 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 me.toolify.backbone.ui.widgets; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.Spinner; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import de.greenrobot.event.EventBus; +import me.toolify.backbone.adapters.BreadcrumbSpinnerAdapter; +import me.toolify.backbone.bus.events.FilesystemStatusUpdateEvent; +import me.toolify.backbone.fragments.NavigationFragment; +import me.toolify.backbone.model.DiskUsage; +import me.toolify.backbone.model.MountPoint; +import me.toolify.backbone.tasks.FilesystemAsyncTask; +import me.toolify.backbone.util.FileHelper; +import me.toolify.backbone.util.StorageHelper; + +/** + * A view that holds a navigation breadcrumb pattern. + */ +public class BreadcrumbSpinner extends Spinner implements Breadcrumb, OnItemSelectedListener { + + private Context mContext; + /** + * @hide + */ + private MountPoint mMountPointInfo; + /** + * @hide + */ + private DiskUsage mDiskUsageInfo; + private FilesystemAsyncTask mFilesystemAsyncTask; + + private int mFreeDiskSpaceWarningLevel = 95; + + private List mBreadcrumbListeners; + + private BreadcrumbSpinnerAdapter mAdapter; + + private String mCurrentPath; + private NavigationFragment mNavigationFragment; + /** + * @hide + */ + private boolean mPauseSpinnerClicks; + + /** + * Constructor of BreadcrumbSpinner. + * + * @param context The current context + */ + public BreadcrumbSpinner(Context context) { + super(context); + this.mContext = context; + init(); + } + + /** + * Constructor of BreadcrumbSpinner. + * + * @param context The current context + * @param attrs The attributes of the XML tag that is inflating the view. + */ + public BreadcrumbSpinner(Context context, AttributeSet attrs) { + super(context, attrs); + this.mContext = context; + init(); + } + + /** + * Constructor of BreadcrumbSpinner. + * + * @param context The current context + * @param attrs The attributes of the XML tag that is inflating the view. + * @param defStyle The default style to apply to this view. If 0, no style + * will be applied (beyond what is included in the theme). This may + * either be an attribute resource, whose value will be retrieved + * from the current theme, or an explicit style resource. + */ + public BreadcrumbSpinner(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + this.mContext = context; + init(); + } + + /** + * Method that initializes the view. This method loads all the necessary + * information and create an appropriate layout for the view + */ + private void init() { + // Spinner selection fires by default + mPauseSpinnerClicks = false; + + // Initialize the listeners + this.mBreadcrumbListeners = + Collections.synchronizedList(new ArrayList()); + + setOnItemSelectedListener(this); + + // Change the image of filesystem (this is not called after a changeBreadcrumbPath call, + // so if need to be theme previously to protect from errors) + EventBus.getDefault().post(new FilesystemStatusUpdateEvent( + FilesystemStatusUpdateEvent.INDICATOR_WARNING)); + } + + /** + * {@inheritDoc} + */ + @Override + public void setFreeDiskSpaceWarningLevel(int percentage) { + this.mFreeDiskSpaceWarningLevel = percentage; + } + + /** + * {@inheritDoc} + */ + @Override + public void addBreadcrumbListener(BreadcrumbListener listener) { + this.mBreadcrumbListeners.add(listener); + } + + /** + * {@inheritDoc} + */ + @Override + public void removeBreadcrumbListener(BreadcrumbListener listener) { + this.mBreadcrumbListeners.remove(listener); + } + + /** + * {@inheritDoc} + */ + @Override + public void startLoading() { + //Show/Hide views + this.post(new Runnable() { + @Override + public void run() { + EventBus.getDefault().post(new FilesystemStatusUpdateEvent( + FilesystemStatusUpdateEvent.INDICATOR_REFRESHING)); + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public void endLoading() { + //Show/Hide views + this.post(new Runnable() { + @Override + public void run() { + EventBus.getDefault().post(new FilesystemStatusUpdateEvent( + FilesystemStatusUpdateEvent.INDICATOR_STOP_REFRESHING)); + } + }); + } + + /** + * {@inheritDoc} + */ + public void setNavigationFragment(NavigationFragment fragment) { + this.mNavigationFragment = fragment; + } + + /** + * {@inheritDoc} + */ + @Override + public void changeBreadcrumbPath(final String newPath, final boolean chRooted) { + //Sets the current path + this.mCurrentPath = newPath; + + //Update the mount point information + updateMountPointInfo(); + + ArrayList breadcrumbFiles = new ArrayList(); + + // The first is always the root (except if we are in a ChRooted environment) + if (!chRooted) { + breadcrumbFiles.add(new File(FileHelper.ROOT_DIRECTORY)); + } + + //Add the rest of the path + String[] dirs = newPath.split(File.separator); + int cc = dirs.length; + if (chRooted) { + for (int i = 1; i < cc; i++) { + File f = createFile(dirs, i); + if (StorageHelper.isPathInStorageVolume(f.getAbsolutePath())) { + breadcrumbFiles.add(f); + } + } + } else { + for (int i = 1; i < cc; i++) { + breadcrumbFiles.add(createFile(dirs, i)); + } + } + + // Now apply the theme to the breadcrumb + //applyTheme(); + + mAdapter = new BreadcrumbSpinnerAdapter(mContext, breadcrumbFiles); + this.setAdapter(mAdapter); + // Don't perform selection logic for the initial setSelection + mPauseSpinnerClicks = true; + this.setSelection(breadcrumbFiles.size()-1); + } + + /** + * Method that refreshes the breadcrumb based on the current directory, + * which is useful when orientation changes (and the resulting text size changes) + * are triggered. + */ + public void refresh () { + changeBreadcrumbPath(mCurrentPath, mNavigationFragment.mChRooted); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void updateMountPointInfo() { + //Cancel the current execution (if any) and launch again + if (this.mFilesystemAsyncTask != null && this.mFilesystemAsyncTask.isRunning()) { + this.mFilesystemAsyncTask.cancel(true); + } + this.mFilesystemAsyncTask = + new FilesystemAsyncTask( + getContext(), this, this.mFreeDiskSpaceWarningLevel); + this.mFilesystemAsyncTask.execute(this.mCurrentPath); + } + + /** + * Method that creates the a new file reference for a partial + * breadcrumb item. + * + * @param dirs The split strings directory + * @param pos The position up to which to create + * @return File The file reference + */ + @SuppressWarnings("static-method") + private File createFile(String[] dirs, int pos) { + File parent = new File(FileHelper.ROOT_DIRECTORY); + for (int i = 1; i < pos; i++) { + parent = new File(parent, dirs[i]); + } + return new File(parent, dirs[pos]); + } + + /** + * {@inheritDoc} + */ + @Override + public MountPoint getMountPointInfo() { + return this.mMountPointInfo; + } + + /** + * {@inheritDoc} + */ + @Override + public void setMountPointInfo(MountPoint mountPointInfo) { + this.mMountPointInfo = mountPointInfo; + } + + /** + * {@inheritDoc} + */ + @Override + public DiskUsage getDiskUsageInfo() { + return this.mDiskUsageInfo; + } + + /** + * {@inheritDoc} + */ + @Override + public void setDiskUsageInfo(DiskUsage diskUsageInfo) { + this.mDiskUsageInfo = diskUsageInfo; + } + + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + if (!mPauseSpinnerClicks) { + int cc = this.mBreadcrumbListeners.size(); + for (int i = 0; i < cc; i++) { + this.mBreadcrumbListeners.get(i).onBreadcrumbItemClick((File) mAdapter.getItem(position)); + } + } + mPauseSpinnerClicks = false; + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } +} diff --git a/src/com/cyanogenmod/filemanager/ui/widgets/ButtonItem.java b/Backbone/src/main/java/me/toolify/backbone/ui/widgets/ButtonItem.java similarity index 95% rename from src/com/cyanogenmod/filemanager/ui/widgets/ButtonItem.java rename to Backbone/src/main/java/me/toolify/backbone/ui/widgets/ButtonItem.java index 504532b35..3474e646a 100644 --- a/src/com/cyanogenmod/filemanager/ui/widgets/ButtonItem.java +++ b/Backbone/src/main/java/me/toolify/backbone/ui/widgets/ButtonItem.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.ui.widgets; +package me.toolify.backbone.ui.widgets; import android.content.Context; import android.util.AttributeSet; @@ -23,8 +23,8 @@ import android.widget.ImageButton; import android.widget.Toast; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.util.DialogHelper; +import me.toolify.backbone.R; +import me.toolify.backbone.util.DialogHelper; /** * A class that represents a button from an action bar. diff --git a/src/com/cyanogenmod/filemanager/ui/widgets/DirectoryInlineAutocompleteTextView.java b/Backbone/src/main/java/me/toolify/backbone/ui/widgets/DirectoryInlineAutocompleteTextView.java similarity index 96% rename from src/com/cyanogenmod/filemanager/ui/widgets/DirectoryInlineAutocompleteTextView.java rename to Backbone/src/main/java/me/toolify/backbone/ui/widgets/DirectoryInlineAutocompleteTextView.java index b674d98c0..5b80dbb8c 100644 --- a/src/com/cyanogenmod/filemanager/ui/widgets/DirectoryInlineAutocompleteTextView.java +++ b/Backbone/src/main/java/me/toolify/backbone/ui/widgets/DirectoryInlineAutocompleteTextView.java @@ -14,14 +14,14 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.ui.widgets; +package me.toolify.backbone.ui.widgets; import android.content.Context; import android.util.AttributeSet; import android.util.Log; -import com.cyanogenmod.filemanager.util.CommandHelper; -import com.cyanogenmod.filemanager.util.FileHelper; +import me.toolify.backbone.util.CommandHelper; +import me.toolify.backbone.util.FileHelper; import java.io.File; import java.util.List; @@ -149,6 +149,9 @@ public void onTextChanged(String newValue, List currentFilterData) { //Get the new parent String newParent = FileHelper.getParentDir(new File(value)); + if (newParent == null) { + newParent = FileHelper.ROOT_DIRECTORY; + } if (!newParent.endsWith(File.separator)) { newParent += File.separator; } diff --git a/src/com/cyanogenmod/filemanager/ui/widgets/DiskUsageGraph.java b/Backbone/src/main/java/me/toolify/backbone/ui/widgets/DiskUsageGraph.java similarity index 98% rename from src/com/cyanogenmod/filemanager/ui/widgets/DiskUsageGraph.java rename to Backbone/src/main/java/me/toolify/backbone/ui/widgets/DiskUsageGraph.java index 0787e64d1..acced40e3 100644 --- a/src/com/cyanogenmod/filemanager/ui/widgets/DiskUsageGraph.java +++ b/Backbone/src/main/java/me/toolify/backbone/ui/widgets/DiskUsageGraph.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.ui.widgets; +package me.toolify.backbone.ui.widgets; import android.content.Context; import android.graphics.Canvas; @@ -24,9 +24,9 @@ import android.util.AttributeSet; import android.view.View; -import com.cyanogenmod.filemanager.model.DiskUsage; -import com.cyanogenmod.filemanager.ui.ThemeManager; -import com.cyanogenmod.filemanager.ui.ThemeManager.Theme; +import me.toolify.backbone.model.DiskUsage; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.ui.ThemeManager.Theme; import java.util.ArrayList; import java.util.Collections; diff --git a/src/com/cyanogenmod/filemanager/ui/widgets/FlingerListView.java b/Backbone/src/main/java/me/toolify/backbone/ui/widgets/FlingerListView.java similarity index 99% rename from src/com/cyanogenmod/filemanager/ui/widgets/FlingerListView.java rename to Backbone/src/main/java/me/toolify/backbone/ui/widgets/FlingerListView.java index 8079127e4..8b0f45347 100644 --- a/src/com/cyanogenmod/filemanager/ui/widgets/FlingerListView.java +++ b/Backbone/src/main/java/me/toolify/backbone/ui/widgets/FlingerListView.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.ui.widgets; +package me.toolify.backbone.ui.widgets; import android.content.Context; import android.graphics.Rect; @@ -26,7 +26,7 @@ import android.widget.AdapterView; import android.widget.ListView; -import com.cyanogenmod.filemanager.util.AndroidHelper; +import me.toolify.backbone.util.AndroidHelper; /** * A {@link ListView} implementation for remove items using flinging gesture. diff --git a/Backbone/src/main/java/me/toolify/backbone/ui/widgets/FsoPropertiesView.java b/Backbone/src/main/java/me/toolify/backbone/ui/widgets/FsoPropertiesView.java new file mode 100644 index 000000000..712dcb6ff --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/ui/widgets/FsoPropertiesView.java @@ -0,0 +1,1374 @@ +/* + * Copyright (C) 2013 BrandroidTools + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.ui.widgets; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.Resources; +import android.os.AsyncTask; +import android.os.Build; +import android.util.AttributeSet; +import android.util.Log; +import android.util.SparseArray; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.ArrayAdapter; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.Toast; + +import java.io.File; +import java.io.IOException; + +import me.toolify.backbone.FileManagerApplication; +import me.toolify.backbone.R; +import me.toolify.backbone.commands.AsyncResultListener; +import me.toolify.backbone.commands.FolderUsageExecutable; +import me.toolify.backbone.console.ConsoleBuilder; +import me.toolify.backbone.model.AID; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.model.FolderUsage; +import me.toolify.backbone.model.Group; +import me.toolify.backbone.model.GroupPermission; +import me.toolify.backbone.model.OthersPermission; +import me.toolify.backbone.model.Permission; +import me.toolify.backbone.model.Permissions; +import me.toolify.backbone.model.Symlink; +import me.toolify.backbone.model.User; +import me.toolify.backbone.model.UserPermission; +import me.toolify.backbone.preferences.AccessMode; +import me.toolify.backbone.preferences.FileManagerSettings; +import me.toolify.backbone.preferences.Preferences; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.ui.ThemeManager.Theme; +import me.toolify.backbone.util.AIDHelper; +import me.toolify.backbone.util.CommandHelper; +import me.toolify.backbone.util.DialogHelper; +import me.toolify.backbone.util.ExceptionUtil; +import me.toolify.backbone.util.FileHelper; +import me.toolify.backbone.util.MimeTypeHelper; +import me.toolify.backbone.util.MimeTypeHelper.MimeTypeCategory; +import me.toolify.backbone.util.ResourcesHelper; +import me.toolify.backbone.util.StorageHelper; + +/** + * A class that wraps a dialog for showing information about a {@link me.toolify.backbone.model.FileSystemObject} + */ +public class FsoPropertiesView extends RelativeLayout + implements OnClickListener, OnCheckedChangeListener, OnItemSelectedListener, + AsyncResultListener { + + private static final String TAG = "FsoPropertiesView"; //$NON-NLS-1$ + + private static final String OWNER_TYPE = "owner"; //$NON-NLS-1$ + private static final String GROUP_TYPE = "group"; //$NON-NLS-1$ + private static final String OTHERS_TYPE = "others"; //$NON-NLS-1$ + + private static final String AID_FORMAT = "%05d - %s"; //$NON-NLS-1$ + private static final String AID_SEPARATOR = " - "; //$NON-NLS-1$ + + /** + * @hide + */ + FileSystemObject mFso; + /** + * @hide + */ + boolean mHasChanged; + /** + * @hide + */ + boolean mPauseSpinner; + + /** + * @hide + */ + Context mContext; + private View mContentView; + /** + * @hide + */ + CheckBox mChkNoMedia; + /** + * @hide + */ + Spinner mSpnOwner; + /** + * @hide + */ + Spinner mSpnGroup; + /** + * @hide + */ + private CheckBox[] mChkUserPermission; + private CheckBox[] mChkGroupPermission; + private CheckBox[] mChkOthersPermission; + private TextView mInfoMsgView; + /** + * @hide + */ + TextView mTvSize; + /** + * @hide + */ + TextView mTvContains; + + /** + * @hide + */ + boolean mIgnoreCheckEvents; + private boolean mHasPrivileged; + private boolean mIsAdvancedMode; + + private boolean mComputeFolderStatistics; + private FolderUsageExecutable mFolderUsageExecutable; + private FolderUsage mFolderUsage; + /** + * @hide + */ + boolean mDrawingFolderUsage; + + public FsoPropertiesView(Context context) { + super(context); + init(context); + } + + public FsoPropertiesView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public FsoPropertiesView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context); + } + + /** + * Constructor of FsoPropertiesDialog. + * + * @param context The current context + * + */ + private void init(Context context){ + + //Save the context + this.mContext = context; + + //Inflate the view + this.mContentView = inflate(getContext(), R.layout.fso_properties_drawer, null); + addView(mContentView); + + // Apply current theme + applyTheme(); + + } + + /** + * Constructor of FsoPropertiesDialog. + * + * @param fso The file system object reference + */ + public void loadFso(FileSystemObject fso) { + + //Save data + this.mFso = fso; + this.mHasChanged = false; + this.mIgnoreCheckEvents = true; + this.mHasPrivileged = false; + this.mIsAdvancedMode = + FileManagerApplication.getAccessMode().compareTo(AccessMode.SAFE) != 0; + + + // Retrieve the user settings about computing folder statistics + this.mComputeFolderStatistics = + Preferences.getSharedPreferences(). + getBoolean( + FileManagerSettings.SETTINGS_COMPUTE_FOLDER_STATISTICS.getId(), + ((Boolean)FileManagerSettings.SETTINGS_COMPUTE_FOLDER_STATISTICS. + getDefaultValue()).booleanValue()); + + //Fill the dialog + fillData(this.mContentView); + } + + + /** + * Method that return the associated {@link me.toolify.backbone.model.FileSystemObject} reference + * + * @return FileSystemObject The fso + */ + public FileSystemObject getFso() { + return this.mFso; + } + + /** + * Method that returns if the properties of the file was changed + * + * @return boolean If the properties of the file was changed + */ + public boolean isHasChanged() { + return this.mHasChanged; + } + + /** + * Method that fill the dialog with the data of the mount point. + * + * @param contentView The content view + */ + private void fillData(View contentView) { + + //Gets text views + TextView tvName = (TextView)contentView.findViewById(R.id.fso_properties_name); + TextView tvParent = (TextView)contentView.findViewById(R.id.fso_properties_parent); + TextView tvType = (TextView)contentView.findViewById(R.id.fso_properties_type); + View vCategoryRow = contentView.findViewById(R.id.fso_properties_category_row); + TextView tvCategory = (TextView)contentView.findViewById(R.id.fso_properties_category); + View vLinkRow = contentView.findViewById(R.id.fso_properties_link_row); + TextView tvLink = (TextView)contentView.findViewById(R.id.fso_properties_link); + this.mTvSize = (TextView)contentView.findViewById(R.id.fso_properties_size); + View vContatinsRow = contentView.findViewById(R.id.fso_properties_contains_row); + this.mTvContains = (TextView)contentView.findViewById(R.id.fso_properties_contains); + TextView tvLastAccessedTime = + (TextView)contentView.findViewById(R.id.fso_properties_last_accessed); + TextView tvLastModifiedTime = + (TextView)contentView.findViewById(R.id.fso_properties_last_modified); + TextView tvLastChangedTime = + (TextView)contentView.findViewById(R.id.fso_properties_last_changed); + this.mChkNoMedia = (CheckBox)contentView.findViewById(R.id.fso_include_in_media_scan); + this.mSpnOwner = (Spinner)contentView.findViewById(R.id.fso_properties_owner); + this.mSpnGroup = (Spinner)contentView.findViewById(R.id.fso_properties_group); + this.mInfoMsgView = (TextView)contentView.findViewById(R.id.fso_info_msg); + + //Fill the text views for the info section + tvName.setText(this.mFso.getName()); + if (FileHelper.isRootDirectory(this.mFso)) { + tvParent.setText("-"); //$NON-NLS-1$ + } else { + tvParent.setText(this.mFso.getParent()); + } + tvType.setText(MimeTypeHelper.getMimeTypeDescription(this.mContext, this.mFso)); + if (this.mFso instanceof Symlink) { + Symlink link = (Symlink)this.mFso; + if (link.getLinkRef() != null) { + tvLink.setText(link.getLinkRef().getFullPath()); + } else { + tvLink.setText("-"); //$NON-NLS-1$ + } + } + MimeTypeCategory category = MimeTypeHelper.getCategory(this.mContext, this.mFso); + if (category.compareTo(MimeTypeCategory.NONE) == 0) { + vCategoryRow.setVisibility(View.GONE); + } else { + tvCategory.setText( + MimeTypeHelper.getCategoryDescription( + this.mContext, category)); + } + vLinkRow.setVisibility(this.mFso instanceof Symlink ? View.VISIBLE : View.GONE); + String size = FileHelper.getHumanReadableSize(this.mFso); + if (size.length() == 0) { + size = "-"; //$NON-NLS-1$ + } + this.mTvSize.setText(size); + this.mTvContains.setText("-"); //$NON-NLS-1$ + tvLastAccessedTime.setText( + FileHelper.formatFileTime(this.mContext, this.mFso.getLastAccessedTime())); + tvLastModifiedTime.setText( + FileHelper.formatFileTime(this.mContext, this.mFso.getLastModifiedTime())); + tvLastChangedTime.setText( + FileHelper.formatFileTime(this.mContext, this.mFso.getLastChangedTime())); + + //Fill the text views, spinners and checkboxes for the permissions section + String loadingMsg = this.mContext.getString(R.string.loading_message); + setSpinnerMsg(this.mContext, FsoPropertiesView.this.mSpnOwner, loadingMsg); + setSpinnerMsg(this.mContext, FsoPropertiesView.this.mSpnGroup, loadingMsg); + updatePermissions(); + + // Load owners and groups AIDs in background + loadAIDs(); + + // Compute folder usage if this fso is a folder + if (FileHelper.isDirectory(this.mFso)) { + vContatinsRow.setVisibility(View.VISIBLE); + if (this.mComputeFolderStatistics) { + computeFolderUsage(); + } + } + + // Check if permissions operations are allowed + try { + this.mHasPrivileged = ConsoleBuilder.getConsole(this.mContext).isPrivileged(); + } catch (Throwable ex) {/**NON BLOCK**/} + this.mSpnOwner.setEnabled(this.mHasPrivileged); + this.mSpnGroup.setEnabled(this.mHasPrivileged); + // Not allowed for symlinks + if (!(this.mFso instanceof Symlink)) { + setCheckBoxesPermissionsEnable(this.mChkUserPermission, this.mHasPrivileged); + setCheckBoxesPermissionsEnable(this.mChkGroupPermission, this.mHasPrivileged); + setCheckBoxesPermissionsEnable(this.mChkOthersPermission, this.mHasPrivileged); + } else { + setCheckBoxesPermissionsEnable(this.mChkUserPermission, false); + setCheckBoxesPermissionsEnable(this.mChkGroupPermission, false); + setCheckBoxesPermissionsEnable(this.mChkOthersPermission, false); + } + if (!this.mHasPrivileged && this.mIsAdvancedMode) { + this.mInfoMsgView.setVisibility(View.VISIBLE); + this.mInfoMsgView.setOnClickListener(this); + } + + // Add the listener after set the value to avoid raising triggers + this.mSpnOwner.setOnItemSelectedListener(this); + this.mSpnGroup.setOnItemSelectedListener(this); + setPermissionCheckBoxesListener(this.mChkUserPermission); + setPermissionCheckBoxesListener(this.mChkGroupPermission); + setPermissionCheckBoxesListener(this.mChkOthersPermission); + + // Check if we should show "Skip media scan" toggle + if (!FileHelper.isDirectory(this.mFso) || + !StorageHelper.isPathInStorageVolume(this.mFso.getFullPath())) { + LinearLayout fsoSkipMediaScanView = + (LinearLayout)contentView.findViewById(R.id.fso_skip_media_scan_view); + fsoSkipMediaScanView.setVisibility(View.GONE); + } else { + //attach the click events + this.mChkNoMedia.setChecked(isNoMediaFilePresent()); + this.mChkNoMedia.setOnCheckedChangeListener(this); + } + + this.mInfoMsgView.setVisibility( + this.mHasPrivileged || !this.mIsAdvancedMode ? View.GONE : View.VISIBLE); + + this.mIgnoreCheckEvents = false; + } + + /** + * Method that loads the AIDs in background + */ + private void loadAIDs() { + mPauseSpinner = true; + + // Load owners and groups AIDs in background + AsyncTask> aidsTask = + new AsyncTask>() { + @Override + protected SparseArray doInBackground(Void...params) { + return AIDHelper.getAIDs(FsoPropertiesView.this.mContext, true); + } + + @Override + protected void onPostExecute(SparseArray aids) { + if (!isCancelled()) { + // Ensure that at least one AID was loaded + if (aids == null) { + String errorMsg = + FsoPropertiesView.this.mContext.getString(R.string.error_message); + setSpinnerMsg( + FsoPropertiesView.this.mContext, + FsoPropertiesView.this.mSpnOwner, errorMsg); + setSpinnerMsg( + FsoPropertiesView.this.mContext, + FsoPropertiesView.this.mSpnGroup, errorMsg); + return; + } + + // Position of the owner and group + int owner = FsoPropertiesView.this.mFso.getUser().getId(); + int group = FsoPropertiesView.this.mFso.getGroup().getId(); + int ownerPosition = 0; + int groupPosition = 0; + + // Convert the SparseArray in an array of string of "uid - name" + int len = aids.size(); + final String[] data = new String[len]; + for (int i = 0; i < len; i++) { + AID aid = aids.valueAt(i); + data[i] = String.format(AID_FORMAT, + Integer.valueOf(aid.getId()), aid.getName()); + if (owner == aid.getId()) ownerPosition = i; + if (group == aid.getId()) groupPosition = i; + } + + // Change the adapter of the spinners + setSpinnerData( + FsoPropertiesView.this.mContext, + FsoPropertiesView.this.mSpnOwner, data, ownerPosition); + setSpinnerData( + FsoPropertiesView.this.mContext, + FsoPropertiesView.this.mSpnGroup, data, groupPosition); + // Spinner population has finished, so we can allow spinner onItemselected calls + // to fire now + mPauseSpinner = false; + + // Adjust the size of the spinners to match the parent view width + adjustSpinnerSize(FsoPropertiesView.this.mSpnOwner); + adjustSpinnerSize(FsoPropertiesView.this.mSpnGroup); + } + } + }; + aidsTask.execute(); + } + + /** + * Method that computes the disk usage of the folder in background + */ + private void computeFolderUsage() { + try { + if (this.mFso instanceof Symlink && ((Symlink) this.mFso).getLinkRef() != null) { + this.mFolderUsageExecutable = + CommandHelper.getFolderUsage( + this.mContext, + ((Symlink) this.mFso).getLinkRef().getFullPath(), this, null); + } else { + this.mFolderUsageExecutable = + CommandHelper.getFolderUsage( + this.mContext, this.mFso.getFullPath(), this, null); + } + } catch (Exception cause) { + //Capture the exception + ExceptionUtil.translateException(this.mContext, cause, true, false); + this.mTvSize.setText(R.string.error_message); + this.mTvContains.setText(R.string.error_message); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onClick(View v) { + switch (v.getId()) { + + case R.id.fso_info_msg: + //Change the console + boolean superuser = ConsoleBuilder.changeToPrivilegedConsole(this.mContext); + if (superuser) { + this.mInfoMsgView.setOnClickListener(null); + this.mInfoMsgView.setVisibility(View.GONE); + if(Build.VERSION.SDK_INT >= 16) { + this.mInfoMsgView.setBackground(null); + } else { + this.mInfoMsgView.setBackgroundDrawable(null); + } + + // Enable controls + this.mSpnOwner.setEnabled(true); + this.mSpnGroup.setEnabled(true); + setCheckBoxesPermissionsEnable(this.mChkUserPermission, true); + setCheckBoxesPermissionsEnable(this.mChkGroupPermission, true); + setCheckBoxesPermissionsEnable(this.mChkOthersPermission, true); + // Not allowed for symlinks + if (!(this.mFso instanceof Symlink)) { + setCheckBoxesPermissionsEnable(this.mChkUserPermission, true); + setCheckBoxesPermissionsEnable(this.mChkGroupPermission, true); + setCheckBoxesPermissionsEnable(this.mChkOthersPermission, true); + } + this.mHasPrivileged = true; + } + break; + + default: + break; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + switch (buttonView.getId()) { + case R.id.fso_include_in_media_scan: + onNoMediaCheckedChanged(buttonView, isChecked); + break; + + default: + onPermissionsCheckedChanged(buttonView, isChecked); + break; + } + } + + /** + * Method that manage a check changed event + * + * @param buttonView The checkbox + * @param isChecked If the checkbox is checked + */ + private void onNoMediaCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (this.mIgnoreCheckEvents) { + this.mIgnoreCheckEvents = false; + return; + } + // Checked means "skip media scan" + final File nomedia = FileHelper.getNoMediaFile(this.mFso); + if (isChecked) { + preventMediaScan(nomedia); + } else { + allowMediaScan(nomedia); + } + } + + /** + * Method that manage a check changed event + * + * @param buttonView The checkbox + * @param isChecked If the checkbox is checked + */ + private void onPermissionsCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (this.mIgnoreCheckEvents) return; + try { + // Cancel the folder usage command + cancelFolderUsageCommand(); + + // Retrieve the permissions and send to operating system + Permissions permissions = getPermissions(); + if (!CommandHelper.changePermissions( + this.mContext, this.mFso.getFullPath(), permissions, null)) { + // Show the warning message + setMsg(this.mContext.getString( + R.string.fso_properties_failed_to_change_permission_msg)); + + // Update the permissions with the previous information + updatePermissions(); + return; + } + + // Some filesystem, like sdcards, doesn't allow to change the permissions. + // But the system doesn't return the fail. Read again the fso and compare to + // ensure that the permission was changed + try { + FileSystemObject systemFso = + CommandHelper.getFileInfo( + this.mContext, this.mFso.getFullPath(), false, null); + if (systemFso == null || systemFso.getPermissions().compareTo(permissions) != 0) { + // Show the warning message + setMsg(FsoPropertiesView.this.mContext.getString( + R.string.fso_properties_failed_to_change_permission_msg)); + + // Update the permissions with the previous information + updatePermissions(); + return; + } + } catch (Exception e) { + // Show the warning message + setMsg(FsoPropertiesView.this.mContext.getString( + R.string.fso_properties_failed_to_change_permission_msg)); + + // Update the permissions with the previous information + updatePermissions(); + return; + } + + // The permission was changed. Refresh the information + this.mFso.setPermissions(permissions); + this.mHasChanged = true; + setMsg(null); + + } catch (Exception ex) { + // Capture the exception and show warning message + ExceptionUtil.translateException( + this.mContext, ex, true, true, new ExceptionUtil.OnRelaunchCommandResult() { + @Override + public void onSuccess() { + // Hide the message + setMsg(null); + } + + @Override + public void onCancelled() { + // Update the permissions with the previous information + updatePermissions(); + setMsg(null); + } + + @Override + public void onFailed(Throwable cause) { + // Show the warning message + setMsg(FsoPropertiesView.this.mContext.getString( + R.string.fso_properties_failed_to_change_permission_msg)); + + // Update the permissions with the previous information + updatePermissions(); + } + }); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + User user = null; + Group group = null; + String msg = null; + + // We don't want the initial onItemSelected call to trigger while the spinners are still + // loading, so this is explicitly disabled until we get the go ahead from loadAIDs() + if(!mPauseSpinner) { + try { +// // Apply theme +// Theme theme = ThemeManager.getCurrentTheme(this.mContext); +// theme.setTextColor( +// this.mContext, ((TextView) parent.getChildAt(0)), "text_color"); //$NON-NLS-1$ + + String row = parent.getItemAtPosition(position).toString(); + int uid = Integer.parseInt(row.substring(0, row.indexOf(AID_SEPARATOR))); + String name = row.substring(row.indexOf(AID_SEPARATOR) + 3); + + // Check which spinner was changed + switch (parent.getId()) { + case R.id.fso_properties_owner: + //Owner + user = new User(uid, name); + group = this.mFso.getGroup(); + msg = this.mContext.getString( + R.string.fso_properties_failed_to_change_owner_msg); + break; + case R.id.fso_properties_group: + //Group + user = this.mFso.getUser(); + group = new Group(uid, name); + msg = this.mContext.getString( + R.string.fso_properties_failed_to_change_group_msg); + break; + + default: + break; + } + } catch (Exception ex) { + // Capture the exception + ExceptionUtil.translateException(this.mContext, ex); + + // Exit from dialog. The dialog may have inconsistency + //this.mDialog.dismiss(); + //TODO: Make the parent activity close its drawer instead of + return; + } + + // Has changed? + if (this.mFso.getUser().compareTo(user) == 0 && + this.mFso.getGroup().compareTo(group) == 0) { + return; + } + + // Cancel the folder usage command + cancelFolderUsageCommand(); + + // Change the owner and group of the fso + try { + if (!CommandHelper.changeOwner( + this.mContext, this.mFso.getFullPath(), user, group, null)) { + // Show the warning message + setMsg(msg); + + // Update the information of owner and group + updateSpinnerFromAid(this.mSpnOwner, this.mFso.getUser()); + updateSpinnerFromAid(this.mSpnGroup, this.mFso.getGroup()); + return; + } + + //Change the fso reference + this.mFso.setUser(user); + this.mFso.setGroup(group); + this.mHasChanged = true; + setMsg(null); + + } catch (Exception ex) { + // Capture the exception and show warning message + final String txtMsg = msg; + ExceptionUtil.translateException( + this.mContext, ex, true, true, new ExceptionUtil.OnRelaunchCommandResult() { + @Override + public void onSuccess() { + // Hide the message + setMsg(null); + } + + @Override + public void onCancelled() { + // Update the information of owner and group + updateSpinnerFromAid( + FsoPropertiesView.this.mSpnOwner, + FsoPropertiesView.this.mFso.getUser()); + updateSpinnerFromAid( + FsoPropertiesView.this.mSpnGroup, + FsoPropertiesView.this.mFso.getGroup()); + setMsg(null); + } + + @Override + public void onFailed(Throwable cause) { + setMsg(txtMsg); + + // Update the information of owner and group + updateSpinnerFromAid( + FsoPropertiesView.this.mSpnOwner, + FsoPropertiesView.this.mFso.getUser()); + updateSpinnerFromAid( + FsoPropertiesView.this.mSpnGroup, + FsoPropertiesView.this.mFso.getGroup()); + return; + } + }); + + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onNothingSelected(AdapterView parent) {/**NON BLOCK**/} + + /** + * Method that shows a simple message on the spinner (loading, error, ...) + * + * @param ctx The current context + * @param spinner The spinner + * @param msg The message + * @hide + */ + static void setSpinnerMsg(Context ctx, Spinner spinner, String msg) { + ArrayAdapter loadingAdapter = + new ArrayAdapter( + ctx, R.layout.spinner_item, new String[]{msg}); + loadingAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinner.setAdapter(loadingAdapter); + spinner.setEnabled(false); + } + + /** + * Method that fills the spinner with the data + * + * @param ctx The current context + * @param spinner The spinner + * @param data The data + * @param selection The object to select + * @hide + */ + void setSpinnerData( + Context ctx, Spinner spinner, String[] data, int selection) { + ArrayAdapter adapter = + new ArrayAdapter( + ctx, R.layout.spinner_item, data); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinner.setAdapter(adapter); + spinner.setSelection(selection); + spinner.setEnabled(this.mHasPrivileged); + } + + /** + * Method that update a spinner from an {@link me.toolify.backbone.model.AID} reference + * + * @param spinner The spinner to update + * @param aid The {@link me.toolify.backbone.model.AID} reference + */ + @SuppressWarnings({"unchecked", "boxing"}) + public static void updateSpinnerFromAid(Spinner spinner, AID aid) { + ArrayAdapter adapter = (ArrayAdapter)spinner.getAdapter(); + int position = adapter.getPosition(String.format(AID_FORMAT, aid.getId(), aid.getName())); + if (position != -1) { + spinner.setSelection(position); + } + } + + /** + * Method that refresh the information of permissions + * @hide + */ + void updatePermissions() { + // Update the permissions with the previous information + FsoPropertiesView.this.mIgnoreCheckEvents = true; + try { + Permissions permissions = this.mFso.getPermissions(); + this.mChkUserPermission = + loadCheckBoxUserPermission( + this.mContext, this.mContentView, permissions.getUser()); + this.mChkGroupPermission = + loadCheckBoxGroupPermission( + this.mContext, this.mContentView, permissions.getGroup()); + this.mChkOthersPermission = + loadCheckBoxOthersPermission( + this.mContext, this.mContentView, permissions.getOthers()); + } finally { + FsoPropertiesView.this.mIgnoreCheckEvents = false; + } + } + + /** + * Method that load the checkboxes for a user permission + * + * @param ctx The current context + * @param rootView The root view + * @return UserPermission The user permission + */ + private static CheckBox[] loadCheckBoxUserPermission ( + Context ctx, View rootView, UserPermission permission) { + CheckBox[] chkPermissions = loadPermissionCheckBoxes(ctx, rootView, OWNER_TYPE); + chkPermissions[0].setChecked(permission.isSetUID()); + setCheckBoxesPermissions(chkPermissions, permission); + return chkPermissions; + } + + /** + * Method that load the checkboxes for a group permission + * + * @param ctx The current context + * @param rootView The root view + * @return UserPermission The user permission + */ + private static CheckBox[] loadCheckBoxGroupPermission ( + Context ctx, View rootView, GroupPermission permission) { + CheckBox[] chkPermissions = loadPermissionCheckBoxes(ctx, rootView, GROUP_TYPE); + chkPermissions[0].setChecked(permission.isSetGID()); + setCheckBoxesPermissions(chkPermissions, permission); + return chkPermissions; + } + + /** + * Method that load the checkboxes for a group permission + * + * @param ctx The current context + * @param rootView The root view + * @return UserPermission The user permission + */ + private static CheckBox[] loadCheckBoxOthersPermission ( + Context ctx, View rootView, OthersPermission permission) { + CheckBox[] chkPermissions = loadPermissionCheckBoxes(ctx, rootView, OTHERS_TYPE); + chkPermissions[0].setChecked(permission.isStickybit()); + setCheckBoxesPermissions(chkPermissions, permission); + return chkPermissions; + } + + /** + * Method that check/uncheck the common permission for a permission checkboxes + * + * @param chkPermissions The checkboxes + * @param permission The permission + */ + private static void setCheckBoxesPermissions ( + CheckBox[] chkPermissions, Permission permission) { + chkPermissions[1].setChecked(permission.isRead()); + chkPermissions[2].setChecked(permission.isWrite()); + chkPermissions[3].setChecked(permission.isExecute()); + } + + /** + * Method that check/uncheck the common permission for a permission checkboxes + * + * @param chkPermissions The checkboxes + * @param enabled If the checkbox should be enabled + */ + private static void setCheckBoxesPermissionsEnable ( + CheckBox[] chkPermissions, boolean enabled) { + int cc = chkPermissions.length; + for (int i = 0; i < cc; i++) { + chkPermissions[i].setEnabled(enabled); + } + } + + /** + * Method that load the checkboxes associated with a type of permission + * + * @param ctx The current context + * @param rootView The root view + * @param type The type of permission [owner, group, others] + * @return CheckBox[] The checkboxes associated + */ + private static CheckBox[] loadPermissionCheckBoxes(Context ctx, View rootView, String type) { + Resources res = ctx.getResources(); + CheckBox[] chkPermissions = new CheckBox[4]; + chkPermissions[0] = (CheckBox)rootView.findViewById( + ResourcesHelper.getIdentifier( + res, "id", //$NON-NLS-1$ + String.format("fso_permissions_%s_special", type))); //$NON-NLS-1$ + chkPermissions[1] = (CheckBox)rootView.findViewById( + ResourcesHelper.getIdentifier( + res, "id", //$NON-NLS-1$ + String.format("fso_permissions_%s_read", type))); //$NON-NLS-1$ + chkPermissions[2] = (CheckBox)rootView.findViewById( + ResourcesHelper.getIdentifier( + res, "id", //$NON-NLS-1$ + String.format("fso_permissions_%s_write", type))); //$NON-NLS-1$ + chkPermissions[3] = (CheckBox)rootView.findViewById( + ResourcesHelper.getIdentifier( + res, "id", //$NON-NLS-1$ + String.format("fso_permissions_%s_execute", type))); //$NON-NLS-1$ + return chkPermissions; + } + + /** + * Method that sets the listener for the permission checkboxes + * + * @param chkPermissions The checkboxes + */ + private void setPermissionCheckBoxesListener(CheckBox[] chkPermissions) { + int cc = chkPermissions.length; + for (int i = 0; i < cc; i++) { + chkPermissions[i].setOnCheckedChangeListener(this); + } + } + + /** + * Method that retrieves the current permissions selected by the user + * + * @return Permissions The permissions selected by the user + */ + private Permissions getPermissions() { + UserPermission userPermission = + new UserPermission( + this.mChkUserPermission[1].isChecked(), + this.mChkUserPermission[2].isChecked(), + this.mChkUserPermission[3].isChecked(), + this.mChkUserPermission[0].isChecked()); + GroupPermission groupPermission = + new GroupPermission( + this.mChkGroupPermission[1].isChecked(), + this.mChkGroupPermission[2].isChecked(), + this.mChkGroupPermission[3].isChecked(), + this.mChkGroupPermission[0].isChecked()); + OthersPermission othersPermission = + new OthersPermission( + this.mChkOthersPermission[1].isChecked(), + this.mChkOthersPermission[2].isChecked(), + this.mChkOthersPermission[3].isChecked(), + this.mChkOthersPermission[0].isChecked()); + Permissions permissions = + new Permissions(userPermission, groupPermission, othersPermission); + return permissions; + } + + /** + * Method that set a message in the dialog. If the message is {@link null} then + * the view is hidden + * + * @param msg The message to show. {@link null} to hide the dialog + * @hide + */ + void setMsg(String msg) { + this.mInfoMsgView.setText(msg); + this.mInfoMsgView.setVisibility( + !this.mIsAdvancedMode || (this.mHasPrivileged && msg == null) ? + View.GONE : + View.VISIBLE); + } + + /** + * {@inheritDoc} + */ + @Override + public void onAsyncStart() { + this.mDrawingFolderUsage = false; + this.mFolderUsage = new FolderUsage(this.mFso.getFullPath()); + printFolderUsage(true, false); + } + + /** + * {@inheritDoc} + */ + @Override + public void onAsyncEnd(final boolean cancelled) { + try { + // Clone the reference + FsoPropertiesView.this.mFolderUsage = + (FolderUsage)this.mFolderUsageExecutable.getFolderUsage().clone(); + printFolderUsage(true, cancelled); + } catch (Exception ex) {/**NON BLOCK**/} + } + + /** + * {@inheritDoc} + */ + @Override + public void onPartialResult(final Object partialResults) { + try { + // Do not saturate ui thread + if (this.mDrawingFolderUsage) { + return; + } + + // Clone the reference + FsoPropertiesView.this.mFolderUsage = + (FolderUsage)(((FolderUsage)partialResults).clone()); + printFolderUsage(true, false); + } catch (Exception ex) {/**NON BLOCK**/} + } + + /** + * {@inheritDoc} + */ + @Override + public void onAsyncExitCode(int exitCode) {/**NON BLOCK**/} + + /** + * {@inheritDoc} + */ + @Override + public void onException(Exception cause) { + //Capture the exception + ExceptionUtil.translateException(this.mContext, cause); + } + + /** + * Method that cancels the folder usage command execution + */ + private void cancelFolderUsageCommand() { + if (this.mComputeFolderStatistics) { + // Cancel the folder usage command + try { + if (this.mFolderUsageExecutable != null && + this.mFolderUsageExecutable.isCancellable() && + !this.mFolderUsageExecutable.isCancelled()) { + this.mFolderUsageExecutable.cancel(); + } + } catch (Exception ex) { + Log.e(TAG, "Failed to cancel the folder usage command", ex); //$NON-NLS-1$ + } + } + } + + /** + * Method that redraws the information about folder usage + * + * @param computing If the process if computing the data + * @param cancelled If the process was cancelled + */ + private void printFolderUsage(final boolean computing, final boolean cancelled) { + // Mark that a drawing is in progress + this.mDrawingFolderUsage = true; + + final Resources res = this.mContext.getResources(); + if (cancelled) { + try { + FsoPropertiesView.this.mTvSize.setText(R.string.cancelled_message); + FsoPropertiesView.this.mTvContains.setText(R.string.cancelled_message); + } catch (Throwable e) {/**NON BLOCK**/} + + // End of drawing + this.mDrawingFolderUsage = false; + } else { + // Calculate size prior to use ui thread + final String size = FileHelper.getHumanReadableSize(this.mFolderUsage.getTotalSize()); + + // Compute folders and files string + String folders = res.getQuantityString( + R.plurals.n_folders, + this.mFolderUsage.getNumberOfFolders(), + Integer.valueOf(this.mFolderUsage.getNumberOfFolders())); + String files = res.getQuantityString( + R.plurals.n_files, + this.mFolderUsage.getNumberOfFiles(), + Integer.valueOf(this.mFolderUsage.getNumberOfFiles())); + final String contains = res.getString( + R.string.fso_properties_dialog_folder_items, + folders, files); + + // Update the dialog + ((Activity)this.mContext).runOnUiThread(new Runnable() { + @Override + public void run() { + if (computing) { + FsoPropertiesView.this.mTvSize.setText( + res.getString(R.string.computing_message, size)); + FsoPropertiesView.this.mTvContains.setText( + res.getString(R.string.computing_message_ln, contains)); + } else { + FsoPropertiesView.this.mTvSize.setText(size); + FsoPropertiesView.this.mTvContains.setText(contains); + } + + // End of drawing + FsoPropertiesView.this.mDrawingFolderUsage = false; + } + }); + } + } + + /** + * Method that adjust the size of the spinner to fit the window + * + * @param spinner The spinner + */ + private void adjustSpinnerSize(final Spinner spinner) { + final View rootView = this.mContentView.findViewById(R.id.fso_properties); + spinner.post(new Runnable() { + @Override + public void run() { + // Align with the last checkbox of the column + int vW = rootView.getMeasuredWidth(); + int viewOffset = (int) getRelativeX(spinner, rootView); + + // Set the width + spinner.getLayoutParams().width = vW - viewOffset - + FsoPropertiesView.this.mContext.getResources(). + getDimensionPixelSize(R.dimen.default_margin); + } + }); + } + + /** + * Method that determines the total horizontal distance between the left side of the specified + * root or parent view and the left side of the specified child view. + * + * @param childView the child view to obtain a relative X position for + * @param rootView the parent view whose left edge should be measured against + * @return the X coordinate position of the child view relative to the specified parent view + * (in pixels) + */ + public float getRelativeX(View childView, View rootView) { + if (childView.getParent() == rootView) + return childView.getX(); + else + return childView.getX() + getRelativeX((View)childView.getParent(), rootView); + } + + /** + * Method that applies the current theme to the activity + */ + private void applyTheme() { + Theme theme = ThemeManager.getCurrentTheme(this.mContext); + theme.setBackgroundDrawable( + this.mContext, this.mContentView, "background_drawable"); //$NON-NLS-1$ + View v = null; +// v = this.mContentView.findViewById(R.id.fso_properties_dialog_tab_divider1); +// theme.setBackgroundColor(this.mContext, v, "horizontal_divider_color"); //$NON-NLS-1$ +// v = this.mContentView.findViewById(R.id.fso_properties_dialog_tab_divider2); +// theme.setBackgroundColor(this.mContext, v, "vertical_divider_color"); //$NON-NLS-1$ +// v = this.mContentView.findViewById(R.id.fso_properties_dialog_tab_divider3); +// theme.setBackgroundColor(this.mContext, v, "vertical_divider_color"); //$NON-NLS-1$ + +// v = this.mContentView.findViewById(R.id.fso_properties_name_label); +// theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ +// v = this.mContentView.findViewById(R.id.fso_properties_name); +// theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ +// v = this.mContentView.findViewById(R.id.fso_properties_parent_label); +// theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ +// v = this.mContentView.findViewById(R.id.fso_properties_parent); +// theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ +// v = this.mContentView.findViewById(R.id.fso_properties_type_label); +// theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ +// v = this.mContentView.findViewById(R.id.fso_properties_type); +// theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ +// v = this.mContentView.findViewById(R.id.fso_properties_category_label); +// theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ +// v = this.mContentView.findViewById(R.id.fso_properties_category); +// theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ +// v = this.mContentView.findViewById(R.id.fso_properties_link_label); +// theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ +// v = this.mContentView.findViewById(R.id.fso_properties_link); +// theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ +// v = this.mContentView.findViewById(R.id.fso_properties_size_label); +// theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ +// v = this.mContentView.findViewById(R.id.fso_properties_size); +// theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ +// v = this.mContentView.findViewById(R.id.fso_properties_contains_label); +// theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ +// v = this.mContentView.findViewById(R.id.fso_properties_contains); +// theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ +// v = this.mContentView.findViewById(R.id.fso_properties_last_accessed_label); +// theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ +// v = this.mContentView.findViewById(R.id.fso_properties_last_accessed); +// theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ +// v = this.mContentView.findViewById(R.id.fso_properties_last_modified_label); +// theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ +// v = this.mContentView.findViewById(R.id.fso_properties_last_modified); +// theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ +// v = this.mContentView.findViewById(R.id.fso_properties_last_changed_label); +// theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ +// v = this.mContentView.findViewById(R.id.fso_properties_last_changed); +// theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ +// v = this.mContentView.findViewById(R.id.fso_include_in_media_scan_label); +// theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ +// +// v = this.mContentView.findViewById(R.id.fso_properties_owner_label); +// theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ +// v = this.mContentView.findViewById(R.id.fso_properties_group_label); +// theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ +// v = this.mContentView.findViewById(R.id.fso_properties_permissions_special_label); +// theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ +// v = this.mContentView.findViewById(R.id.fso_properties_permissions_read_label); +// theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ +// v = this.mContentView.findViewById(R.id.fso_properties_permissions_write_label); +// theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ +// v = this.mContentView.findViewById(R.id.fso_properties_permissions_execute_label); +// theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ +// v = this.mContentView.findViewById(R.id.fso_properties_permissions_owner_label); +// theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ +// v = this.mContentView.findViewById(R.id.fso_properties_permissions_group_label); +// theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ +// v = this.mContentView.findViewById(R.id.fso_properties_permissions_others_label); +// theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ +// v = this.mContentView.findViewById(R.id.fso_info_msg); +// theme.setTextColor(this.mContext, (TextView)v, "text_color"); //$NON-NLS-1$ +// ((TextView)v).setCompoundDrawablesWithIntrinsicBounds( +// theme.getDrawable(this.mContext, "filesystem_warning_drawable"), //$NON-NLS-1$ +// null, null, null); + } + + /** + * Method that prevents media scan in the directory (creates a new .nomedia file) + * + * @param nomedia The .nomedia file + */ + private void preventMediaScan(final File nomedia) { + // Create .nomedia file. The file should not exist here + try { + if (!nomedia.createNewFile()) { + // failed to create .nomedia file + DialogHelper.showToast( + this.mContext, + this.mContext.getString( + R.string.fso_failed_to_prevent_media_scan), + Toast.LENGTH_SHORT); + this.mIgnoreCheckEvents = true; + this.mChkNoMedia.setChecked(false); + return; + } + + // Refresh the listview + this.mHasChanged = true; + + } catch (IOException ex) { + // failed to create .nomedia file + ExceptionUtil.translateException(this.mContext, ex, true, false, null); + DialogHelper.showToast( + this.mContext, + this.mContext.getString( + R.string.fso_failed_to_prevent_media_scan), + Toast.LENGTH_SHORT); + this.mIgnoreCheckEvents = true; + this.mChkNoMedia.setChecked(false); + } + } + + /** + * Method that allows media scan in the directory (removes the .nomedia file) + * + * @param nomedia The .nomedia file + */ + private void allowMediaScan(final File nomedia) { + // Delete .nomedia file. The file should exist here + + // .nomedia is a directory? Then ask the user prior to remove completely the folder + if (nomedia.isDirectory()) { + // confirm removing the dir + AlertDialog alert = DialogHelper.createYesNoDialog( + this.mContext, + R.string.fso_delete_nomedia_dir_title, + R.string.fso_delete_nomedia_dir_body, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (which == DialogInterface.BUTTON_POSITIVE) { + boolean ret = FileHelper.deleteFolder(nomedia); + if (!ret) { + DialogHelper.showToast( + FsoPropertiesView.this.mContext, + FsoPropertiesView.this.mContext.getString( + R.string.fso_failed_to_allow_media_scan), + Toast.LENGTH_SHORT); + FsoPropertiesView.this.mIgnoreCheckEvents = true; + FsoPropertiesView.this.mChkNoMedia.setChecked(true); + return; + } + + // Refresh the listview + FsoPropertiesView.this.mHasChanged = true; + + } else { + FsoPropertiesView.this.mIgnoreCheckEvents = true; + FsoPropertiesView.this.mChkNoMedia.setChecked(true); + } + } + }); + DialogHelper.delegateDialogShow(this.mContext, alert); + + // .nomedia file is not empty? Then ask the user prior to remove the file + } else if (nomedia.length() != 0) { + // confirm removing non empty file + AlertDialog alert = DialogHelper.createYesNoDialog( + this.mContext, + R.string.fso_delete_nomedia_non_empty_title, + R.string.fso_delete_nomedia_non_empty_body, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (which == DialogInterface.BUTTON_POSITIVE) { + if (!nomedia.delete()) { + DialogHelper.showToast( + FsoPropertiesView.this.mContext, + FsoPropertiesView.this.mContext.getString( + R.string.fso_failed_to_allow_media_scan), + Toast.LENGTH_SHORT); + FsoPropertiesView.this.mIgnoreCheckEvents = true; + FsoPropertiesView.this.mChkNoMedia.setChecked(true); + return; + } + + // Refresh the listview + FsoPropertiesView.this.mHasChanged = true; + + } else { + FsoPropertiesView.this.mIgnoreCheckEvents = true; + FsoPropertiesView.this.mChkNoMedia.setChecked(true); + } + } + }); + DialogHelper.delegateDialogShow(this.mContext, alert); + + // Normal .nomedia file + } else { + if (!nomedia.delete()) { + //failed to delete .nomedia file + DialogHelper.showToast( + this.mContext, + this.mContext.getString( + R.string.fso_failed_to_allow_media_scan), + Toast.LENGTH_SHORT); + FsoPropertiesView.this.mIgnoreCheckEvents = true; + FsoPropertiesView.this.mChkNoMedia.setChecked(true); + return; + } + + // Refresh the listview + FsoPropertiesView.this.mHasChanged = true; + } + } + + /** + * Method that checks if the .nomedia file is present + * + * @return boolean If the .nomedia file is present + */ + private boolean isNoMediaFilePresent() { + final File nomedia = FileHelper.getNoMediaFile(this.mFso); + return nomedia.exists(); + } + +} diff --git a/Backbone/src/main/java/me/toolify/backbone/ui/widgets/FullDrawerLayout.java b/Backbone/src/main/java/me/toolify/backbone/ui/widgets/FullDrawerLayout.java new file mode 100644 index 000000000..8f385c1f4 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/ui/widgets/FullDrawerLayout.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2013 BrandroidTools + * + * 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 me.toolify.backbone.ui.widgets; + +import android.content.Context; +import android.support.v4.widget.DrawerLayout; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.View; + + +/* + * A class that extends DrawerLayout in order to permit the use of a full width drawer + */ +public class FullDrawerLayout extends DrawerLayout { + + private static final int MIN_DRAWER_MARGIN = 0; // dp + + public FullDrawerLayout(Context context) { + super(context); + } + + public FullDrawerLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public FullDrawerLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final int widthMode = MeasureSpec.getMode(widthMeasureSpec); + final int heightMode = MeasureSpec.getMode(heightMeasureSpec); + final int widthSize = MeasureSpec.getSize(widthMeasureSpec); + final int heightSize = MeasureSpec.getSize(heightMeasureSpec); + + if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) { + throw new IllegalArgumentException( + "DrawerLayout must be measured with MeasureSpec.EXACTLY."); + } + + setMeasuredDimension(widthSize, heightSize); + + // Gravity value for each drawer we've seen. Only one of each permitted. + int foundDrawers = 0; + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + + if (child.getVisibility() == GONE) { + continue; + } + + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + + if (isContentView(child)) { + // Content views get measured at exactly the layout's size. + final int contentWidthSpec = MeasureSpec.makeMeasureSpec( + widthSize - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY); + final int contentHeightSpec = MeasureSpec.makeMeasureSpec( + heightSize - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY); + child.measure(contentWidthSpec, contentHeightSpec); + } else if (isDrawerView(child)) { + final int childGravity = + getDrawerViewGravity(child) & Gravity.HORIZONTAL_GRAVITY_MASK; + if ((foundDrawers & childGravity) != 0) { + throw new IllegalStateException("Child drawer has absolute gravity " + + gravityToString(childGravity) + " but this already has a " + + "drawer view along that edge"); + } + final int drawerWidthSpec = getChildMeasureSpec(widthMeasureSpec, + MIN_DRAWER_MARGIN + lp.leftMargin + lp.rightMargin, + lp.width); + final int drawerHeightSpec = getChildMeasureSpec(heightMeasureSpec, + lp.topMargin + lp.bottomMargin, + lp.height); + child.measure(drawerWidthSpec, drawerHeightSpec); + } else { + throw new IllegalStateException("Child " + child + " at index " + i + + " does not have a valid layout_gravity - must be Gravity.LEFT, " + + "Gravity.RIGHT or Gravity.NO_GRAVITY"); + } + } + } + + boolean isContentView(View child) { + return ((LayoutParams) child.getLayoutParams()).gravity == Gravity.NO_GRAVITY; + } + + boolean isDrawerView(View child) { + final int gravity = ((LayoutParams) child.getLayoutParams()).gravity; + final int absGravity = Gravity.getAbsoluteGravity(gravity, + child.getLayoutDirection()); + return (absGravity & (Gravity.LEFT | Gravity.RIGHT)) != 0; + } + + int getDrawerViewGravity(View drawerView) { + final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity; + return Gravity.getAbsoluteGravity(gravity, drawerView.getLayoutDirection()); + } + + static String gravityToString(int gravity) { + if ((gravity & Gravity.LEFT) == Gravity.LEFT) { + return "LEFT"; + } + if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) { + return "RIGHT"; + } + return Integer.toHexString(gravity); + } +} diff --git a/src/com/cyanogenmod/filemanager/ui/widgets/InlineAutocompleteTextView.java b/Backbone/src/main/java/me/toolify/backbone/ui/widgets/InlineAutocompleteTextView.java similarity index 98% rename from src/com/cyanogenmod/filemanager/ui/widgets/InlineAutocompleteTextView.java rename to Backbone/src/main/java/me/toolify/backbone/ui/widgets/InlineAutocompleteTextView.java index 308be39fb..5185ec307 100644 --- a/src/com/cyanogenmod/filemanager/ui/widgets/InlineAutocompleteTextView.java +++ b/Backbone/src/main/java/me/toolify/backbone/ui/widgets/InlineAutocompleteTextView.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.ui.widgets; +package me.toolify.backbone.ui.widgets; import android.app.Activity; import android.content.Context; @@ -33,10 +33,10 @@ import android.widget.TextView; import android.widget.Toast; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.ui.ThemeManager; -import com.cyanogenmod.filemanager.ui.ThemeManager.Theme; -import com.cyanogenmod.filemanager.util.DialogHelper; +import me.toolify.backbone.R; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.ui.ThemeManager.Theme; +import me.toolify.backbone.util.DialogHelper; import java.util.ArrayList; import java.util.Iterator; @@ -385,7 +385,7 @@ void doTab() { private static List filter(List data, String current) { List filter = new ArrayList(data); int size = filter.size(); - for (int i=size-1; i>=0; i--) { + for (int i = size-1; i >= 0; i--) { String s = filter.get(i); if (!s.startsWith(current)) { filter.remove(i); diff --git a/src/com/cyanogenmod/filemanager/ui/widgets/NavigationCustomTitleView.java b/Backbone/src/main/java/me/toolify/backbone/ui/widgets/NavigationCustomTitleView.java similarity index 95% rename from src/com/cyanogenmod/filemanager/ui/widgets/NavigationCustomTitleView.java rename to Backbone/src/main/java/me/toolify/backbone/ui/widgets/NavigationCustomTitleView.java index 90dde5979..8c6398c8a 100644 --- a/src/com/cyanogenmod/filemanager/ui/widgets/NavigationCustomTitleView.java +++ b/Backbone/src/main/java/me/toolify/backbone/ui/widgets/NavigationCustomTitleView.java @@ -14,14 +14,14 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.ui.widgets; +package me.toolify.backbone.ui.widgets; import android.content.Context; import android.util.AttributeSet; import android.widget.ViewFlipper; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.listeners.OnHistoryListener; +import me.toolify.backbone.R; +import me.toolify.backbone.listeners.OnHistoryListener; /** * A {@link ViewFlipper} implementation for the navigation custom title. diff --git a/src/com/cyanogenmod/filemanager/ui/widgets/NonFocusableButtonItem.java b/Backbone/src/main/java/me/toolify/backbone/ui/widgets/NonFocusableButtonItem.java similarity index 93% rename from src/com/cyanogenmod/filemanager/ui/widgets/NonFocusableButtonItem.java rename to Backbone/src/main/java/me/toolify/backbone/ui/widgets/NonFocusableButtonItem.java index 8239602b6..d72a8e621 100644 --- a/src/com/cyanogenmod/filemanager/ui/widgets/NonFocusableButtonItem.java +++ b/Backbone/src/main/java/me/toolify/backbone/ui/widgets/NonFocusableButtonItem.java @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.ui.widgets; +package me.toolify.backbone.ui.widgets; import android.content.Context; import android.util.AttributeSet; -import com.cyanogenmod.filemanager.R; +import me.toolify.backbone.R; /** * A class that represents a button from an action bar that can't gain focus. @@ -67,7 +67,7 @@ public NonFocusableButtonItem(Context context, AttributeSet attrs, int defStyle) */ private void init() { //Remove focus - setBackgroundResource(R.drawable.holo_selector_nonfocusable); + //setBackgroundResource(R.drawable.holo_selector_nonfocusable); setFocusable(false); setFocusableInTouchMode(false); } diff --git a/src/com/cyanogenmod/filemanager/ui/widgets/RelevanceView.java b/Backbone/src/main/java/me/toolify/backbone/ui/widgets/RelevanceView.java similarity index 98% rename from src/com/cyanogenmod/filemanager/ui/widgets/RelevanceView.java rename to Backbone/src/main/java/me/toolify/backbone/ui/widgets/RelevanceView.java index 0cf73417e..39e6ecd0e 100644 --- a/src/com/cyanogenmod/filemanager/ui/widgets/RelevanceView.java +++ b/Backbone/src/main/java/me/toolify/backbone/ui/widgets/RelevanceView.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.ui.widgets; +package me.toolify.backbone.ui.widgets; import android.content.Context; import android.graphics.Canvas; @@ -24,7 +24,7 @@ import android.util.AttributeSet; import android.view.View; -import com.cyanogenmod.filemanager.R; +import me.toolify.backbone.R; import java.util.Iterator; import java.util.Map; diff --git a/src/com/cyanogenmod/filemanager/ui/widgets/TransparentNonFocusableButtonItem.java b/Backbone/src/main/java/me/toolify/backbone/ui/widgets/TransparentNonFocusableButtonItem.java similarity index 91% rename from src/com/cyanogenmod/filemanager/ui/widgets/TransparentNonFocusableButtonItem.java rename to Backbone/src/main/java/me/toolify/backbone/ui/widgets/TransparentNonFocusableButtonItem.java index 2eb9da6de..a000c82c2 100644 --- a/src/com/cyanogenmod/filemanager/ui/widgets/TransparentNonFocusableButtonItem.java +++ b/Backbone/src/main/java/me/toolify/backbone/ui/widgets/TransparentNonFocusableButtonItem.java @@ -14,11 +14,14 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.ui.widgets; +package me.toolify.backbone.ui.widgets; import android.content.Context; +import android.os.Build; import android.util.AttributeSet; +import me.toolify.backbone.R; + /** * A class that represents a button from an action bar that can't gain focus * without background. @@ -66,6 +69,9 @@ public TransparentNonFocusableButtonItem(Context context, AttributeSet attrs, in */ private void init() { //Remove focus - setBackground(null); + if(Build.VERSION.SDK_INT >= 16) + setBackground(null); + else + setBackgroundDrawable(null); } } diff --git a/src/com/cyanogenmod/filemanager/util/AIDHelper.java b/Backbone/src/main/java/me/toolify/backbone/util/AIDHelper.java similarity index 97% rename from src/com/cyanogenmod/filemanager/util/AIDHelper.java rename to Backbone/src/main/java/me/toolify/backbone/util/AIDHelper.java index d49f1687a..ebb29b370 100644 --- a/src/com/cyanogenmod/filemanager/util/AIDHelper.java +++ b/Backbone/src/main/java/me/toolify/backbone/util/AIDHelper.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.util; +package me.toolify.backbone.util; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -22,8 +22,8 @@ import android.util.Log; import android.util.SparseArray; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.model.AID; +import me.toolify.backbone.R; +import me.toolify.backbone.model.AID; import java.util.Iterator; import java.util.List; diff --git a/src/com/cyanogenmod/filemanager/util/AndroidHelper.java b/Backbone/src/main/java/me/toolify/backbone/util/AndroidHelper.java similarity index 97% rename from src/com/cyanogenmod/filemanager/util/AndroidHelper.java rename to Backbone/src/main/java/me/toolify/backbone/util/AndroidHelper.java index 57260ec1e..a307b6b39 100644 --- a/src/com/cyanogenmod/filemanager/util/AndroidHelper.java +++ b/Backbone/src/main/java/me/toolify/backbone/util/AndroidHelper.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.util; +package me.toolify.backbone.util; import android.content.Context; import android.content.pm.PackageManager; @@ -23,7 +23,7 @@ import android.util.DisplayMetrics; import android.view.ViewConfiguration; -import com.cyanogenmod.filemanager.R; +import me.toolify.backbone.R; /** * A helper class with useful methods for deal with android. diff --git a/src/com/cyanogenmod/filemanager/util/BookmarksHelper.java b/Backbone/src/main/java/me/toolify/backbone/util/BookmarksHelper.java similarity index 94% rename from src/com/cyanogenmod/filemanager/util/BookmarksHelper.java rename to Backbone/src/main/java/me/toolify/backbone/util/BookmarksHelper.java index 0bc5dfeee..f5ac92e15 100644 --- a/src/com/cyanogenmod/filemanager/util/BookmarksHelper.java +++ b/Backbone/src/main/java/me/toolify/backbone/util/BookmarksHelper.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.util; +package me.toolify.backbone.util; -import com.cyanogenmod.filemanager.model.Bookmark; +import me.toolify.backbone.model.Bookmark; /** * A helper class with useful methods for deal with bookmarks. diff --git a/src/com/cyanogenmod/filemanager/util/CommandHelper.java b/Backbone/src/main/java/me/toolify/backbone/util/CommandHelper.java similarity index 93% rename from src/com/cyanogenmod/filemanager/util/CommandHelper.java rename to Backbone/src/main/java/me/toolify/backbone/util/CommandHelper.java index f79cb9e99..0c90206be 100644 --- a/src/com/cyanogenmod/filemanager/util/CommandHelper.java +++ b/Backbone/src/main/java/me/toolify/backbone/util/CommandHelper.java @@ -14,71 +14,72 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.util; +package me.toolify.backbone.util; import android.content.Context; - -import com.cyanogenmod.filemanager.commands.AsyncResultListener; -import com.cyanogenmod.filemanager.commands.ChangeCurrentDirExecutable; -import com.cyanogenmod.filemanager.commands.ChangeOwnerExecutable; -import com.cyanogenmod.filemanager.commands.ChangePermissionsExecutable; -import com.cyanogenmod.filemanager.commands.CompressExecutable; -import com.cyanogenmod.filemanager.commands.CopyExecutable; -import com.cyanogenmod.filemanager.commands.CreateDirExecutable; -import com.cyanogenmod.filemanager.commands.CreateFileExecutable; -import com.cyanogenmod.filemanager.commands.CurrentDirExecutable; -import com.cyanogenmod.filemanager.commands.DeleteDirExecutable; -import com.cyanogenmod.filemanager.commands.DeleteFileExecutable; -import com.cyanogenmod.filemanager.commands.DiskUsageExecutable; -import com.cyanogenmod.filemanager.commands.EchoExecutable; -import com.cyanogenmod.filemanager.commands.ExecExecutable; -import com.cyanogenmod.filemanager.commands.Executable; -import com.cyanogenmod.filemanager.commands.FindExecutable; -import com.cyanogenmod.filemanager.commands.FolderUsageExecutable; -import com.cyanogenmod.filemanager.commands.GroupsExecutable; -import com.cyanogenmod.filemanager.commands.IdentityExecutable; -import com.cyanogenmod.filemanager.commands.LinkExecutable; -import com.cyanogenmod.filemanager.commands.ListExecutable; -import com.cyanogenmod.filemanager.commands.MountExecutable; -import com.cyanogenmod.filemanager.commands.MountPointInfoExecutable; -import com.cyanogenmod.filemanager.commands.MoveExecutable; -import com.cyanogenmod.filemanager.commands.ParentDirExecutable; -import com.cyanogenmod.filemanager.commands.ProcessIdExecutable; -import com.cyanogenmod.filemanager.commands.QuickFolderSearchExecutable; -import com.cyanogenmod.filemanager.commands.ReadExecutable; -import com.cyanogenmod.filemanager.commands.ResolveLinkExecutable; -import com.cyanogenmod.filemanager.commands.SIGNAL; -import com.cyanogenmod.filemanager.commands.SendSignalExecutable; -import com.cyanogenmod.filemanager.commands.SyncResultExecutable; -import com.cyanogenmod.filemanager.commands.UncompressExecutable; -import com.cyanogenmod.filemanager.commands.WritableExecutable; -import com.cyanogenmod.filemanager.commands.WriteExecutable; -import com.cyanogenmod.filemanager.commands.shell.InvalidCommandDefinitionException; -import com.cyanogenmod.filemanager.console.CommandNotFoundException; -import com.cyanogenmod.filemanager.console.Console; -import com.cyanogenmod.filemanager.console.ConsoleAllocException; -import com.cyanogenmod.filemanager.console.ConsoleBuilder; -import com.cyanogenmod.filemanager.console.ExecutionException; -import com.cyanogenmod.filemanager.console.InsufficientPermissionsException; -import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory; -import com.cyanogenmod.filemanager.console.OperationTimeoutException; -import com.cyanogenmod.filemanager.console.ReadOnlyFilesystemException; -import com.cyanogenmod.filemanager.model.DiskUsage; -import com.cyanogenmod.filemanager.model.FileSystemObject; -import com.cyanogenmod.filemanager.model.FolderUsage; -import com.cyanogenmod.filemanager.model.Group; -import com.cyanogenmod.filemanager.model.Identity; -import com.cyanogenmod.filemanager.model.MountPoint; -import com.cyanogenmod.filemanager.model.Permissions; -import com.cyanogenmod.filemanager.model.Query; -import com.cyanogenmod.filemanager.model.SearchResult; -import com.cyanogenmod.filemanager.model.User; -import com.cyanogenmod.filemanager.preferences.CompressionMode; +import android.util.Log; import java.io.FileNotFoundException; import java.io.IOException; import java.util.List; +import me.toolify.backbone.BuildConfig; +import me.toolify.backbone.commands.AsyncResultListener; +import me.toolify.backbone.commands.ChangeOwnerExecutable; +import me.toolify.backbone.commands.ChangePermissionsExecutable; +import me.toolify.backbone.commands.ChecksumExecutable; +import me.toolify.backbone.commands.CompressExecutable; +import me.toolify.backbone.commands.CopyExecutable; +import me.toolify.backbone.commands.CreateDirExecutable; +import me.toolify.backbone.commands.CreateFileExecutable; +import me.toolify.backbone.commands.DeleteDirExecutable; +import me.toolify.backbone.commands.DeleteFileExecutable; +import me.toolify.backbone.commands.DiskUsageExecutable; +import me.toolify.backbone.commands.EchoExecutable; +import me.toolify.backbone.commands.ExecExecutable; +import me.toolify.backbone.commands.Executable; +import me.toolify.backbone.commands.FindExecutable; +import me.toolify.backbone.commands.FolderUsageExecutable; +import me.toolify.backbone.commands.GroupsExecutable; +import me.toolify.backbone.commands.IdentityExecutable; +import me.toolify.backbone.commands.LinkExecutable; +import me.toolify.backbone.commands.ListExecutable; +import me.toolify.backbone.commands.MountExecutable; +import me.toolify.backbone.commands.MountPointInfoExecutable; +import me.toolify.backbone.commands.MoveExecutable; +import me.toolify.backbone.commands.ParentDirExecutable; +import me.toolify.backbone.commands.ProcessIdExecutable; +import me.toolify.backbone.commands.QuickFolderSearchExecutable; +import me.toolify.backbone.commands.ReadExecutable; +import me.toolify.backbone.commands.ResolveLinkExecutable; +import me.toolify.backbone.commands.SIGNAL; +import me.toolify.backbone.commands.SendSignalExecutable; +import me.toolify.backbone.commands.SyncResultExecutable; +import me.toolify.backbone.commands.UncompressExecutable; +import me.toolify.backbone.commands.WritableExecutable; +import me.toolify.backbone.commands.WriteExecutable; +import me.toolify.backbone.commands.shell.InvalidCommandDefinitionException; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.Console; +import me.toolify.backbone.console.ConsoleAllocException; +import me.toolify.backbone.console.ConsoleBuilder; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.console.NoSuchFileOrDirectory; +import me.toolify.backbone.console.OperationTimeoutException; +import me.toolify.backbone.console.ReadOnlyFilesystemException; +import me.toolify.backbone.model.DiskUsage; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.model.FolderUsage; +import me.toolify.backbone.model.Group; +import me.toolify.backbone.model.Identity; +import me.toolify.backbone.model.MountPoint; +import me.toolify.backbone.model.Permissions; +import me.toolify.backbone.model.Query; +import me.toolify.backbone.model.SearchResult; +import me.toolify.backbone.model.User; +import me.toolify.backbone.preferences.CompressionMode; + /** * A helper class with useful methods for deal with commands. @@ -189,37 +190,6 @@ private CommandHelper() { super(); } - /** - * Method that changes the current directory of the shell. - * - * @param context The current context (needed if console == null) - * @param dst The new directory - * @return boolean The operation result - * @param console The console in which execute the program. null - * to attach to the default console - * @throws FileNotFoundException If the initial directory not exists - * @throws IOException If initial directory couldn't be checked - * @throws InvalidCommandDefinitionException If the command has an invalid definition - * @throws NoSuchFileOrDirectory If the file or directory was not found - * @throws ConsoleAllocException If the console can't be allocated - * @throws InsufficientPermissionsException If an operation requires elevated permissions - * @throws CommandNotFoundException If the command was not found - * @throws OperationTimeoutException If the operation exceeded the maximum time of wait - * @throws ExecutionException If the operation returns a invalid exit code - * @see ChangeCurrentDirExecutable - */ - public static boolean changeCurrentDir(Context context, String dst, Console console) - throws FileNotFoundException, IOException, ConsoleAllocException, - NoSuchFileOrDirectory, InsufficientPermissionsException, - CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException { - Console c = ensureConsole(context, console); - ChangeCurrentDirExecutable executable = - c.getExecutableFactory().newCreator().createChangeCurrentDirExecutable(dst); - execute(context, executable, c); - return executable.getResult().booleanValue(); - } - /** * Method that changes the owner of a file system object. * @@ -450,36 +420,6 @@ public static FileSystemObject resolveSymlink(Context context, String symlink, C return executable.getResult(); } - /** - * Method that retrieves the current directory of the shell. - * - * @param context The current context (needed if console == null) - * @param console The console in which execute the program. null - * to attach to the default console - * @return String The current directory - * @throws FileNotFoundException If the initial directory not exists - * @throws IOException If initial directory couldn't be checked - * @throws InvalidCommandDefinitionException If the command has an invalid definition - * @throws NoSuchFileOrDirectory If the file or directory was not found - * @throws ConsoleAllocException If the console can't be allocated - * @throws InsufficientPermissionsException If an operation requires elevated permissions - * @throws CommandNotFoundException If the command was not found - * @throws OperationTimeoutException If the operation exceeded the maximum time of wait - * @throws ExecutionException If the operation returns a invalid exit code - * @see CurrentDirExecutable - */ - public static String getCurrentDir(Context context, Console console) - throws FileNotFoundException, IOException, ConsoleAllocException, - NoSuchFileOrDirectory, InsufficientPermissionsException, - CommandNotFoundException, OperationTimeoutException, - ExecutionException, InvalidCommandDefinitionException { - Console c = ensureConsole(context, console); - CurrentDirExecutable executable = - c.getExecutableFactory().newCreator().createCurrentDirExecutable(); - execute(context, executable, c); - return executable.getResult(); - } - /** * Method that retrieves the information of a file system object. * @@ -541,7 +481,9 @@ public static FileSystemObject getFileInfo( List files = executable.getResult(); if (files != null && files.size() > 0) { // Resolve symlinks prior to return the object - FileHelper.resolveSymlinks(context, files); + if (followSymlinks) { + FileHelper.resolveSymlinks(context, files); + } return files.get(0); } return null; @@ -969,9 +911,16 @@ public static DiskUsage getDiskUsage(Context context, String dir, Console consol DiskUsageExecutable executable = c.getExecutableFactory().newCreator().createDiskUsageExecutable(dir); execute(context, executable, c); - List du = executable.getResult(); - if (du != null && du.size() > 0) { - return du.get(0); + List dus = executable.getResult(); + if (dus != null && dus.size() > 0) { + for(DiskUsage du : dus) + { + if(!du.getMountPoint().equals(dir)) continue; + return du; + } + if(BuildConfig.DEBUG) + Log.w("CommandHelper", "Unable to find correct DiskUsage for " + dir); + return dus.get(0); // return first if none match } return null; } @@ -1069,6 +1018,39 @@ public static List quickFolderSearch(Context context, String regexp, Con return executable.getResult(); } + /** + * Method that retrieves the process identifier of all the processes (a program + * owned by the main process of this application). + * + * @param context The current context (needed if console == null) + * @param pid The process id of the shell where the command is running + * @param console The console in which execute the program. null + * to attach to the default console + * @return List The processes identifiers of the program or null if not exists + * @throws FileNotFoundException If the initial directory not exists + * @throws IOException If initial directory couldn't be checked + * @throws InvalidCommandDefinitionException If the command has an invalid definition + * @throws NoSuchFileOrDirectory If the file or directory was not found + * @throws ConsoleAllocException If the console can't be allocated + * @throws InsufficientPermissionsException If an operation requires elevated permissions + * @throws CommandNotFoundException If the command was not found + * @throws OperationTimeoutException If the operation exceeded the maximum time of wait + * @throws ExecutionException If the operation returns a invalid exit code + * @see ProcessIdExecutable + */ + public static List getProcessesIds( + Context context, int pid, Console console) + throws FileNotFoundException, IOException, ConsoleAllocException, + NoSuchFileOrDirectory, InsufficientPermissionsException, + CommandNotFoundException, OperationTimeoutException, + ExecutionException, InvalidCommandDefinitionException { + Console c = ensureConsole(context, console); + ProcessIdExecutable executable = + c.getExecutableFactory().newCreator().createProcessIdExecutable(pid); + execute(context, executable, c); + return executable.getResult(); + } + /** * Method that retrieves the process identifier of a process (a program * owned by the main process of this application). @@ -1100,7 +1082,11 @@ public static Integer getProcessId( ProcessIdExecutable executable = c.getExecutableFactory().newCreator().createProcessIdExecutable(pid, processName); execute(context, executable, c); - return executable.getResult(); + List pids = executable.getResult(); + if (pids != null && pids.size() > 0) { + return pids.get(0); + } + return null; } /** @@ -1313,9 +1299,18 @@ public static CompressExecutable compress( wrapperListener.mUnmount = unmount; wrapperListener.mMountPoint = executable2.getDstWritableMountPoint(); - //- Compress - execute(context, executable1, c); - return executable1; + // Some archive modes requires a new file. Ensure that the created + // file doesn't exists + DeleteFileExecutable executable3 = + c.getExecutableFactory(). + newCreator(). + createDeleteFileExecutable(compressOutFile); + writableExecute(context, executable3, c, true); + if (executable3.getResult().booleanValue()) { + //- Compress + execute(context, executable1, c); + return executable1; + } } throw new ExecutionException( String.format("Fail to create file %s", compressOutFile)); //$NON-NLS-1$ @@ -1458,6 +1453,41 @@ public static UncompressExecutable uncompress( String.format("Fail to uncompress to %s", compressOutFile)); //$NON-NLS-1$ } + /** + * Method that calculates the checksum of a file system object. + * + * @param context The current context (needed if console == null) + * @param src The source file + * @param asyncResultListener The partial result listener + * @param console The console in which execute the program. + * null to attach to the default console + * @return WriteExecutable The command executed in background + * @throws FileNotFoundException If the initial directory not exists + * @throws IOException If initial directory couldn't be checked + * @throws InvalidCommandDefinitionException If the command has an invalid definition + * @throws NoSuchFileOrDirectory If the file or directory was not found + * @throws ConsoleAllocException If the console can't be allocated + * @throws InsufficientPermissionsException If an operation requires elevated permissions + * @throws CommandNotFoundException If the command was not found + * @throws OperationTimeoutException If the operation exceeded the maximum time of wait + * @throws ExecutionException If the operation returns a invalid exit code + * @see WriteExecutable + */ + public static ChecksumExecutable checksum( + Context context, String src, + AsyncResultListener asyncResultListener, Console console) + throws FileNotFoundException, IOException, ConsoleAllocException, + NoSuchFileOrDirectory, InsufficientPermissionsException, + CommandNotFoundException, OperationTimeoutException, + ExecutionException, InvalidCommandDefinitionException { + Console c = ensureConsole(context, console); + ChecksumExecutable executable = + c.getExecutableFactory().newCreator(). + createChecksumExecutable(src, asyncResultListener); + execute(context, executable, c); + return executable; + } + /** * Method that re-execute the command. * diff --git a/src/com/cyanogenmod/filemanager/util/DialogHelper.java b/Backbone/src/main/java/me/toolify/backbone/util/DialogHelper.java similarity index 98% rename from src/com/cyanogenmod/filemanager/util/DialogHelper.java rename to Backbone/src/main/java/me/toolify/backbone/util/DialogHelper.java index 0dc9965c8..b9896ba52 100644 --- a/src/com/cyanogenmod/filemanager/util/DialogHelper.java +++ b/Backbone/src/main/java/me/toolify/backbone/util/DialogHelper.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.util; +package me.toolify.backbone.util; import android.app.AlertDialog; import android.content.Context; @@ -34,10 +34,10 @@ import android.widget.TextView; import android.widget.Toast; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.adapters.CheckableListAdapter; -import com.cyanogenmod.filemanager.ui.ThemeManager; -import com.cyanogenmod.filemanager.ui.ThemeManager.Theme; +import me.toolify.backbone.R; +import me.toolify.backbone.adapters.CheckableListAdapter; +import me.toolify.backbone.ui.ThemeManager; +import me.toolify.backbone.ui.ThemeManager.Theme; import java.util.ArrayList; import java.util.List; diff --git a/Backbone/src/main/java/me/toolify/backbone/util/DiskLruCache.java b/Backbone/src/main/java/me/toolify/backbone/util/DiskLruCache.java new file mode 100644 index 000000000..3497a90d7 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/util/DiskLruCache.java @@ -0,0 +1,953 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * 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 me.toolify.backbone.util; + +import java.io.BufferedInputStream; +import java.io.BufferedWriter; +import java.io.Closeable; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.StringWriter; +import java.io.Writer; +import java.lang.reflect.Array; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + ****************************************************************************** + * Taken from the JB source code, can be found in: + * libcore/luni/src/main/java/libcore/io/DiskLruCache.java + * or direct link: + * https://android.googlesource.com/platform/libcore/+/android-4.1.1_r1/luni/src/main/java/libcore/io/DiskLruCache.java + ****************************************************************************** + * + * A cache that uses a bounded amount of space on a filesystem. Each cache + * entry has a string key and a fixed number of values. Values are byte + * sequences, accessible as streams or files. Each value must be between {@code + * 0} and {@code Integer.MAX_VALUE} bytes in length. + * + *

The cache stores its data in a directory on the filesystem. This + * directory must be exclusive to the cache; the cache may delete or overwrite + * files from its directory. It is an error for multiple processes to use the + * same cache directory at the same time. + * + *

This cache limits the number of bytes that it will store on the + * filesystem. When the number of stored bytes exceeds the limit, the cache will + * remove entries in the background until the limit is satisfied. The limit is + * not strict: the cache may temporarily exceed it while waiting for files to be + * deleted. The limit does not include filesystem overhead or the cache + * journal so space-sensitive applications should set a conservative limit. + * + *

Clients call {@link #edit} to create or update the values of an entry. An + * entry may have only one editor at one time; if a value is not available to be + * edited then {@link #edit} will return null. + *

    + *
  • When an entry is being created it is necessary to + * supply a full set of values; the empty value should be used as a + * placeholder if necessary. + *
  • When an entry is being edited, it is not necessary + * to supply data for every value; values default to their previous + * value. + *
+ * Every {@link #edit} call must be matched by a call to {@link Editor#commit} + * or {@link Editor#abort}. Committing is atomic: a read observes the full set + * of values as they were before or after the commit, but never a mix of values. + * + *

Clients call {@link #get} to read a snapshot of an entry. The read will + * observe the value at the time that {@link #get} was called. Updates and + * removals after the call do not impact ongoing reads. + * + *

This class is tolerant of some I/O errors. If files are missing from the + * filesystem, the corresponding entries will be dropped from the cache. If + * an error occurs while writing a cache value, the edit will fail silently. + * Callers should handle other problems by catching {@code IOException} and + * responding appropriately. + */ +public final class DiskLruCache implements Closeable { + static final String JOURNAL_FILE = "journal"; + static final String JOURNAL_FILE_TMP = "journal.tmp"; + static final String MAGIC = "libcore.io.DiskLruCache"; + static final String VERSION_1 = "1"; + static final long ANY_SEQUENCE_NUMBER = -1; + private static final String CLEAN = "CLEAN"; + private static final String DIRTY = "DIRTY"; + private static final String REMOVE = "REMOVE"; + private static final String READ = "READ"; + + private static final Charset UTF_8 = Charset.forName("UTF-8"); + private static final int IO_BUFFER_SIZE = 8 * 1024; + + /* + * This cache uses a journal file named "journal". A typical journal file + * looks like this: + * libcore.io.DiskLruCache + * 1 + * 100 + * 2 + * + * CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054 + * DIRTY 335c4c6028171cfddfbaae1a9c313c52 + * CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342 + * REMOVE 335c4c6028171cfddfbaae1a9c313c52 + * DIRTY 1ab96a171faeeee38496d8b330771a7a + * CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234 + * READ 335c4c6028171cfddfbaae1a9c313c52 + * READ 3400330d1dfc7f3f7f4b8d4d803dfcf6 + * + * The first five lines of the journal form its header. They are the + * constant string "libcore.io.DiskLruCache", the disk cache's version, + * the application's version, the value count, and a blank line. + * + * Each of the subsequent lines in the file is a record of the state of a + * cache entry. Each line contains space-separated values: a state, a key, + * and optional state-specific values. + * o DIRTY lines track that an entry is actively being created or updated. + * Every successful DIRTY action should be followed by a CLEAN or REMOVE + * action. DIRTY lines without a matching CLEAN or REMOVE indicate that + * temporary files may need to be deleted. + * o CLEAN lines track a cache entry that has been successfully published + * and may be read. A publish line is followed by the lengths of each of + * its values. + * o READ lines track accesses for LRU. + * o REMOVE lines track entries that have been deleted. + * + * The journal file is appended to as cache operations occur. The journal may + * occasionally be compacted by dropping redundant lines. A temporary file named + * "journal.tmp" will be used during compaction; that file should be deleted if + * it exists when the cache is opened. + */ + + private final File directory; + private final File journalFile; + private final File journalFileTmp; + private final int appVersion; + private final long maxSize; + private final int valueCount; + private long size = 0; + private Writer journalWriter; + private final LinkedHashMap lruEntries + = new LinkedHashMap(0, 0.75f, true); + private int redundantOpCount; + + /** + * To differentiate between old and current snapshots, each entry is given + * a sequence number each time an edit is committed. A snapshot is stale if + * its sequence number is not equal to its entry's sequence number. + */ + private long nextSequenceNumber = 0; + + /* From java.util.Arrays */ + @SuppressWarnings("unchecked") + private static T[] copyOfRange(T[] original, int start, int end) { + final int originalLength = original.length; // For exception priority compatibility. + if (start > end) { + throw new IllegalArgumentException(); + } + if (start < 0 || start > originalLength) { + throw new ArrayIndexOutOfBoundsException(); + } + final int resultLength = end - start; + final int copyLength = Math.min(resultLength, originalLength - start); + final T[] result = (T[]) Array + .newInstance(original.getClass().getComponentType(), resultLength); + System.arraycopy(original, start, result, 0, copyLength); + return result; + } + + /** + * Returns the remainder of 'reader' as a string, closing it when done. + */ + public static String readFully(Reader reader) throws IOException { + try { + StringWriter writer = new StringWriter(); + char[] buffer = new char[1024]; + int count; + while ((count = reader.read(buffer)) != -1) { + writer.write(buffer, 0, count); + } + return writer.toString(); + } finally { + reader.close(); + } + } + + /** + * Returns the ASCII characters up to but not including the next "\r\n", or + * "\n". + * + * @throws java.io.EOFException if the stream is exhausted before the next newline + * character. + */ + public static String readAsciiLine(InputStream in) throws IOException { + // TODO: support UTF-8 here instead + + StringBuilder result = new StringBuilder(80); + while (true) { + int c = in.read(); + if (c == -1) { + throw new EOFException(); + } else if (c == '\n') { + break; + } + + result.append((char) c); + } + int length = result.length(); + if (length > 0 && result.charAt(length - 1) == '\r') { + result.setLength(length - 1); + } + return result.toString(); + } + + /** + * Closes 'closeable', ignoring any checked exceptions. Does nothing if 'closeable' is null. + */ + public static void closeQuietly(Closeable closeable) { + if (closeable != null) { + try { + closeable.close(); + } catch (RuntimeException rethrown) { + throw rethrown; + } catch (Exception ignored) { + } + } + } + + /** + * Recursively delete everything in {@code dir}. + */ + // TODO: this should specify paths as Strings rather than as Files + public static void deleteContents(File dir) throws IOException { + File[] files = dir.listFiles(); + if (files == null) { + throw new IllegalArgumentException("not a directory: " + dir); + } + for (File file : files) { + if (file.isDirectory()) { + deleteContents(file); + } + if (!file.delete()) { + throw new IOException("failed to delete file: " + file); + } + } + } + + /** This cache uses a single background thread to evict entries. */ + private final ExecutorService executorService = new ThreadPoolExecutor(0, 1, + 60L, TimeUnit.SECONDS, new LinkedBlockingQueue()); + private final Callable cleanupCallable = new Callable() { + @Override public Void call() throws Exception { + synchronized (DiskLruCache.this) { + if (journalWriter == null) { + return null; // closed + } + trimToSize(); + if (journalRebuildRequired()) { + rebuildJournal(); + redundantOpCount = 0; + } + } + return null; + } + }; + + private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) { + this.directory = directory; + this.appVersion = appVersion; + this.journalFile = new File(directory, JOURNAL_FILE); + this.journalFileTmp = new File(directory, JOURNAL_FILE_TMP); + this.valueCount = valueCount; + this.maxSize = maxSize; + } + + /** + * Opens the cache in {@code directory}, creating a cache if none exists + * there. + * + * @param directory a writable directory + * @param appVersion + * @param valueCount the number of values per cache entry. Must be positive. + * @param maxSize the maximum number of bytes this cache should use to store + * @throws java.io.IOException if reading or writing the cache directory fails + */ + public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize) + throws IOException { + if (maxSize <= 0) { + throw new IllegalArgumentException("maxSize <= 0"); + } + if (valueCount <= 0) { + throw new IllegalArgumentException("valueCount <= 0"); + } + + // prefer to pick up where we left off + DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize); + if (cache.journalFile.exists()) { + try { + cache.readJournal(); + cache.processJournal(); + cache.journalWriter = new BufferedWriter(new FileWriter(cache.journalFile, true), + IO_BUFFER_SIZE); + return cache; + } catch (IOException journalIsCorrupt) { +// System.logW("DiskLruCache " + directory + " is corrupt: " +// + journalIsCorrupt.getMessage() + ", removing"); + cache.delete(); + } + } + + // create a new empty cache + directory.mkdirs(); + cache = new DiskLruCache(directory, appVersion, valueCount, maxSize); + cache.rebuildJournal(); + return cache; + } + + private void readJournal() throws IOException { + InputStream in = new BufferedInputStream(new FileInputStream(journalFile), IO_BUFFER_SIZE); + try { + String magic = readAsciiLine(in); + String version = readAsciiLine(in); + String appVersionString = readAsciiLine(in); + String valueCountString = readAsciiLine(in); + String blank = readAsciiLine(in); + if (!MAGIC.equals(magic) + || !VERSION_1.equals(version) + || !Integer.toString(appVersion).equals(appVersionString) + || !Integer.toString(valueCount).equals(valueCountString) + || !"".equals(blank)) { + throw new IOException("unexpected journal header: [" + + magic + ", " + version + ", " + valueCountString + ", " + blank + "]"); + } + + while (true) { + try { + readJournalLine(readAsciiLine(in)); + } catch (EOFException endOfJournal) { + break; + } + } + } finally { + closeQuietly(in); + } + } + + private void readJournalLine(String line) throws IOException { + String[] parts = line.split(" "); + if (parts.length < 2) { + throw new IOException("unexpected journal line: " + line); + } + + String key = parts[1]; + if (parts[0].equals(REMOVE) && parts.length == 2) { + lruEntries.remove(key); + return; + } + + Entry entry = lruEntries.get(key); + if (entry == null) { + entry = new Entry(key); + lruEntries.put(key, entry); + } + + if (parts[0].equals(CLEAN) && parts.length == 2 + valueCount) { + entry.readable = true; + entry.currentEditor = null; + entry.setLengths(copyOfRange(parts, 2, parts.length)); + } else if (parts[0].equals(DIRTY) && parts.length == 2) { + entry.currentEditor = new Editor(entry); + } else if (parts[0].equals(READ) && parts.length == 2) { + // this work was already done by calling lruEntries.get() + } else { + throw new IOException("unexpected journal line: " + line); + } + } + + /** + * Computes the initial size and collects garbage as a part of opening the + * cache. Dirty entries are assumed to be inconsistent and will be deleted. + */ + private void processJournal() throws IOException { + deleteIfExists(journalFileTmp); + for (Iterator i = lruEntries.values().iterator(); i.hasNext(); ) { + Entry entry = i.next(); + if (entry.currentEditor == null) { + for (int t = 0; t < valueCount; t++) { + size += entry.lengths[t]; + } + } else { + entry.currentEditor = null; + for (int t = 0; t < valueCount; t++) { + deleteIfExists(entry.getCleanFile(t)); + deleteIfExists(entry.getDirtyFile(t)); + } + i.remove(); + } + } + } + + /** + * Creates a new journal that omits redundant information. This replaces the + * current journal if it exists. + */ + private synchronized void rebuildJournal() throws IOException { + if (journalWriter != null) { + journalWriter.close(); + } + + Writer writer = new BufferedWriter(new FileWriter(journalFileTmp), IO_BUFFER_SIZE); + writer.write(MAGIC); + writer.write("\n"); + writer.write(VERSION_1); + writer.write("\n"); + writer.write(Integer.toString(appVersion)); + writer.write("\n"); + writer.write(Integer.toString(valueCount)); + writer.write("\n"); + writer.write("\n"); + + for (Entry entry : lruEntries.values()) { + if (entry.currentEditor != null) { + writer.write(DIRTY + ' ' + entry.key + '\n'); + } else { + writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n'); + } + } + + writer.close(); + journalFileTmp.renameTo(journalFile); + journalWriter = new BufferedWriter(new FileWriter(journalFile, true), IO_BUFFER_SIZE); + } + + private static void deleteIfExists(File file) throws IOException { +// try { +// Libcore.os.remove(file.getPath()); +// } catch (ErrnoException errnoException) { +// if (errnoException.errno != OsConstants.ENOENT) { +// throw errnoException.rethrowAsIOException(); +// } +// } + if (file.exists() && !file.delete()) { + throw new IOException(); + } + } + + /** + * Returns a snapshot of the entry named {@code key}, or null if it doesn't + * exist is not currently readable. If a value is returned, it is moved to + * the head of the LRU queue. + */ + public synchronized Snapshot get(String key) throws IOException { + checkNotClosed(); + validateKey(key); + Entry entry = lruEntries.get(key); + if (entry == null) { + return null; + } + + if (!entry.readable) { + return null; + } + + /* + * Open all streams eagerly to guarantee that we see a single published + * snapshot. If we opened streams lazily then the streams could come + * from different edits. + */ + InputStream[] ins = new InputStream[valueCount]; + try { + for (int i = 0; i < valueCount; i++) { + ins[i] = new FileInputStream(entry.getCleanFile(i)); + } + } catch (FileNotFoundException e) { + // a file must have been deleted manually! + return null; + } + + redundantOpCount++; + journalWriter.append(READ + ' ' + key + '\n'); + if (journalRebuildRequired()) { + executorService.submit(cleanupCallable); + } + + return new Snapshot(key, entry.sequenceNumber, ins); + } + + /** + * Returns an editor for the entry named {@code key}, or null if another + * edit is in progress. + */ + public Editor edit(String key) throws IOException { + return edit(key, ANY_SEQUENCE_NUMBER); + } + + private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException { + checkNotClosed(); + validateKey(key); + Entry entry = lruEntries.get(key); + if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER + && (entry == null || entry.sequenceNumber != expectedSequenceNumber)) { + return null; // snapshot is stale + } + if (entry == null) { + entry = new Entry(key); + lruEntries.put(key, entry); + } else if (entry.currentEditor != null) { + return null; // another edit is in progress + } + + Editor editor = new Editor(entry); + entry.currentEditor = editor; + + // flush the journal before creating files to prevent file leaks + journalWriter.write(DIRTY + ' ' + key + '\n'); + journalWriter.flush(); + return editor; + } + + /** + * Returns the directory where this cache stores its data. + */ + public File getDirectory() { + return directory; + } + + /** + * Returns the maximum number of bytes that this cache should use to store + * its data. + */ + public long maxSize() { + return maxSize; + } + + /** + * Returns the number of bytes currently being used to store the values in + * this cache. This may be greater than the max size if a background + * deletion is pending. + */ + public synchronized long size() { + return size; + } + + private synchronized void completeEdit(Editor editor, boolean success) throws IOException { + Entry entry = editor.entry; + if (entry.currentEditor != editor) { + throw new IllegalStateException(); + } + + // if this edit is creating the entry for the first time, every index must have a value + if (success && !entry.readable) { + for (int i = 0; i < valueCount; i++) { + if (!entry.getDirtyFile(i).exists()) { + editor.abort(); + throw new IllegalStateException("edit didn't create file " + i); + } + } + } + + for (int i = 0; i < valueCount; i++) { + File dirty = entry.getDirtyFile(i); + if (success) { + if (dirty.exists()) { + File clean = entry.getCleanFile(i); + dirty.renameTo(clean); + long oldLength = entry.lengths[i]; + long newLength = clean.length(); + entry.lengths[i] = newLength; + size = size - oldLength + newLength; + } + } else { + deleteIfExists(dirty); + } + } + + redundantOpCount++; + entry.currentEditor = null; + if (entry.readable | success) { + entry.readable = true; + journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n'); + if (success) { + entry.sequenceNumber = nextSequenceNumber++; + } + } else { + lruEntries.remove(entry.key); + journalWriter.write(REMOVE + ' ' + entry.key + '\n'); + } + + if (size > maxSize || journalRebuildRequired()) { + executorService.submit(cleanupCallable); + } + } + + /** + * We only rebuild the journal when it will halve the size of the journal + * and eliminate at least 2000 ops. + */ + private boolean journalRebuildRequired() { + final int REDUNDANT_OP_COMPACT_THRESHOLD = 2000; + return redundantOpCount >= REDUNDANT_OP_COMPACT_THRESHOLD + && redundantOpCount >= lruEntries.size(); + } + + /** + * Drops the entry for {@code key} if it exists and can be removed. Entries + * actively being edited cannot be removed. + * + * @return true if an entry was removed. + */ + public synchronized boolean remove(String key) throws IOException { + checkNotClosed(); + validateKey(key); + Entry entry = lruEntries.get(key); + if (entry == null || entry.currentEditor != null) { + return false; + } + + for (int i = 0; i < valueCount; i++) { + File file = entry.getCleanFile(i); + if (!file.delete()) { + throw new IOException("failed to delete " + file); + } + size -= entry.lengths[i]; + entry.lengths[i] = 0; + } + + redundantOpCount++; + journalWriter.append(REMOVE + ' ' + key + '\n'); + lruEntries.remove(key); + + if (journalRebuildRequired()) { + executorService.submit(cleanupCallable); + } + + return true; + } + + /** + * Returns true if this cache has been closed. + */ + public boolean isClosed() { + return journalWriter == null; + } + + private void checkNotClosed() { + if (journalWriter == null) { + throw new IllegalStateException("cache is closed"); + } + } + + /** + * Force buffered operations to the filesystem. + */ + public synchronized void flush() throws IOException { + checkNotClosed(); + trimToSize(); + journalWriter.flush(); + } + + /** + * Closes this cache. Stored values will remain on the filesystem. + */ + public synchronized void close() throws IOException { + if (journalWriter == null) { + return; // already closed + } + for (Entry entry : new ArrayList(lruEntries.values())) { + if (entry.currentEditor != null) { + entry.currentEditor.abort(); + } + } + trimToSize(); + journalWriter.close(); + journalWriter = null; + } + + private void trimToSize() throws IOException { + while (size > maxSize) { +// Map.Entry toEvict = lruEntries.eldest(); + final Map.Entry toEvict = lruEntries.entrySet().iterator().next(); + remove(toEvict.getKey()); + } + } + + /** + * Closes the cache and deletes all of its stored values. This will delete + * all files in the cache directory including files that weren't created by + * the cache. + */ + public void delete() throws IOException { + close(); + deleteContents(directory); + } + + private void validateKey(String key) { + if (key.contains(" ") || key.contains("\n") || key.contains("\r")) { + throw new IllegalArgumentException( + "keys must not contain spaces or newlines: \"" + key + "\""); + } + } + + private static String inputStreamToString(InputStream in) throws IOException { + return readFully(new InputStreamReader(in, UTF_8)); + } + + /** + * A snapshot of the values for an entry. + */ + public final class Snapshot implements Closeable { + private final String key; + private final long sequenceNumber; + private final InputStream[] ins; + + private Snapshot(String key, long sequenceNumber, InputStream[] ins) { + this.key = key; + this.sequenceNumber = sequenceNumber; + this.ins = ins; + } + + /** + * Returns an editor for this snapshot's entry, or null if either the + * entry has changed since this snapshot was created or if another edit + * is in progress. + */ + public Editor edit() throws IOException { + return DiskLruCache.this.edit(key, sequenceNumber); + } + + /** + * Returns the unbuffered stream with the value for {@code index}. + */ + public InputStream getInputStream(int index) { + return ins[index]; + } + + /** + * Returns the string value for {@code index}. + */ + public String getString(int index) throws IOException { + return inputStreamToString(getInputStream(index)); + } + + @Override public void close() { + for (InputStream in : ins) { + closeQuietly(in); + } + } + } + + /** + * Edits the values for an entry. + */ + public final class Editor { + private final Entry entry; + private boolean hasErrors; + + private Editor(Entry entry) { + this.entry = entry; + } + + /** + * Returns an unbuffered input stream to read the last committed value, + * or null if no value has been committed. + */ + public InputStream newInputStream(int index) throws IOException { + synchronized (DiskLruCache.this) { + if (entry.currentEditor != this) { + throw new IllegalStateException(); + } + if (!entry.readable) { + return null; + } + return new FileInputStream(entry.getCleanFile(index)); + } + } + + /** + * Returns the last committed value as a string, or null if no value + * has been committed. + */ + public String getString(int index) throws IOException { + InputStream in = newInputStream(index); + return in != null ? inputStreamToString(in) : null; + } + + /** + * Returns a new unbuffered output stream to write the value at + * {@code index}. If the underlying output stream encounters errors + * when writing to the filesystem, this edit will be aborted when + * {@link #commit} is called. The returned output stream does not throw + * IOExceptions. + */ + public OutputStream newOutputStream(int index) throws IOException { + synchronized (DiskLruCache.this) { + if (entry.currentEditor != this) { + throw new IllegalStateException(); + } + return new FaultHidingOutputStream(new FileOutputStream(entry.getDirtyFile(index))); + } + } + + /** + * Sets the value at {@code index} to {@code value}. + */ + public void set(int index, String value) throws IOException { + Writer writer = null; + try { + writer = new OutputStreamWriter(newOutputStream(index), UTF_8); + writer.write(value); + } finally { + closeQuietly(writer); + } + } + + /** + * Commits this edit so it is visible to readers. This releases the + * edit lock so another edit may be started on the same key. + */ + public void commit() throws IOException { + if (hasErrors) { + completeEdit(this, false); + remove(entry.key); // the previous entry is stale + } else { + completeEdit(this, true); + } + } + + /** + * Aborts this edit. This releases the edit lock so another edit may be + * started on the same key. + */ + public void abort() throws IOException { + completeEdit(this, false); + } + + private class FaultHidingOutputStream extends FilterOutputStream { + private FaultHidingOutputStream(OutputStream out) { + super(out); + } + + @Override public void write(int oneByte) { + try { + out.write(oneByte); + } catch (IOException e) { + hasErrors = true; + } + } + + @Override public void write(byte[] buffer, int offset, int length) { + try { + out.write(buffer, offset, length); + } catch (IOException e) { + hasErrors = true; + } + } + + @Override public void close() { + try { + out.close(); + } catch (IOException e) { + hasErrors = true; + } + } + + @Override public void flush() { + try { + out.flush(); + } catch (IOException e) { + hasErrors = true; + } + } + } + } + + private final class Entry { + private final String key; + + /** Lengths of this entry's files. */ + private final long[] lengths; + + /** True if this entry has ever been published */ + private boolean readable; + + /** The ongoing edit or null if this entry is not being edited. */ + private Editor currentEditor; + + /** The sequence number of the most recently committed edit to this entry. */ + private long sequenceNumber; + + private Entry(String key) { + this.key = key; + this.lengths = new long[valueCount]; + } + + public String getLengths() throws IOException { + StringBuilder result = new StringBuilder(); + for (long size : lengths) { + result.append(' ').append(size); + } + return result.toString(); + } + + /** + * Set lengths using decimal numbers like "10123". + */ + private void setLengths(String[] strings) throws IOException { + if (strings.length != valueCount) { + throw invalidLengths(strings); + } + + try { + for (int i = 0; i < strings.length; i++) { + lengths[i] = Long.parseLong(strings[i]); + } + } catch (NumberFormatException e) { + throw invalidLengths(strings); + } + } + + private IOException invalidLengths(String[] strings) throws IOException { + throw new IOException("unexpected journal line: " + Arrays.toString(strings)); + } + + public File getCleanFile(int i) { + return new File(directory, key + "." + i); + } + + public File getDirtyFile(int i) { + return new File(directory, key + "." + i + ".tmp"); + } + } +} diff --git a/src/com/cyanogenmod/filemanager/util/ExceptionUtil.java b/Backbone/src/main/java/me/toolify/backbone/util/ExceptionUtil.java similarity index 92% rename from src/com/cyanogenmod/filemanager/util/ExceptionUtil.java rename to Backbone/src/main/java/me/toolify/backbone/util/ExceptionUtil.java index 658a28ccf..9b15966df 100644 --- a/src/com/cyanogenmod/filemanager/util/ExceptionUtil.java +++ b/Backbone/src/main/java/me/toolify/backbone/util/ExceptionUtil.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.util; +package me.toolify.backbone.util; import android.app.Activity; import android.app.AlertDialog; @@ -25,20 +25,20 @@ import android.util.Log; import android.widget.Toast; -import com.cyanogenmod.filemanager.FileManagerApplication; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.commands.SyncResultExecutable; -import com.cyanogenmod.filemanager.commands.shell.InvalidCommandDefinitionException; -import com.cyanogenmod.filemanager.console.CommandNotFoundException; -import com.cyanogenmod.filemanager.console.ConsoleAllocException; -import com.cyanogenmod.filemanager.console.ConsoleBuilder; -import com.cyanogenmod.filemanager.console.ExecutionException; -import com.cyanogenmod.filemanager.console.InsufficientPermissionsException; -import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory; -import com.cyanogenmod.filemanager.console.OperationTimeoutException; -import com.cyanogenmod.filemanager.console.ReadOnlyFilesystemException; -import com.cyanogenmod.filemanager.console.RelaunchableException; -import com.cyanogenmod.filemanager.preferences.AccessMode; +import me.toolify.backbone.FileManagerApplication; +import me.toolify.backbone.R; +import me.toolify.backbone.commands.SyncResultExecutable; +import me.toolify.backbone.commands.shell.InvalidCommandDefinitionException; +import me.toolify.backbone.console.CommandNotFoundException; +import me.toolify.backbone.console.ConsoleAllocException; +import me.toolify.backbone.console.ConsoleBuilder; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.console.NoSuchFileOrDirectory; +import me.toolify.backbone.console.OperationTimeoutException; +import me.toolify.backbone.console.ReadOnlyFilesystemException; +import me.toolify.backbone.console.RelaunchableException; +import me.toolify.backbone.preferences.AccessMode; import java.io.FileNotFoundException; import java.io.IOException; @@ -324,6 +324,15 @@ public void onClick(DialogInterface dialog, int which) { } } }); + alert.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + // Operation cancelled + if (listener != null) { + listener.onCancelled(); + } + } + }); DialogHelper.delegateDialogShow(context, alert); } diff --git a/Backbone/src/main/java/me/toolify/backbone/util/FastXmlSerializer.java b/Backbone/src/main/java/me/toolify/backbone/util/FastXmlSerializer.java new file mode 100644 index 000000000..e4766cd8b --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/util/FastXmlSerializer.java @@ -0,0 +1,357 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * 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 me.toolify.backbone.util; + +import org.xmlpull.v1.XmlSerializer; + +import java.io.*; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.*; + +/** + * This is a quick and dirty implementation of XmlSerializer that isn't horribly + * painfully slow like the normal one. It only does what is needed for the + * specific XML files being written with it. + */ +public class FastXmlSerializer implements XmlSerializer { + private static final String ESCAPE_TABLE[] = new String[] { + null, null, null, null, null, null, null, null, // 0-7 + null, null, null, null, null, null, null, null, // 8-15 + null, null, null, null, null, null, null, null, // 16-23 + null, null, null, null, null, null, null, null, // 24-31 + null, null, """, null, null, null, "&", null, // 32-39 + null, null, null, null, null, null, null, null, // 40-47 + null, null, null, null, null, null, null, null, // 48-55 + null, null, null, null, "<", null, ">", null, // 56-63 + }; + + private static final int BUFFER_LEN = 8192; + + private final char[] mText = new char[BUFFER_LEN]; + private int mPos; + + private Writer mWriter; + + private OutputStream mOutputStream; + private CharsetEncoder mCharset; + private ByteBuffer mBytes = ByteBuffer.allocate(BUFFER_LEN); + + private boolean mInTag; + + private void append(char c) throws IOException { + int pos = mPos; + if (pos >= (BUFFER_LEN-1)) { + flush(); + pos = mPos; + } + mText[pos] = c; + mPos = pos+1; + } + + private void append(String str, int i, final int length) throws IOException { + if (length > BUFFER_LEN) { + final int end = i + length; + while (i < end) { + int next = i + BUFFER_LEN; + append(str, i, next BUFFER_LEN) { + flush(); + pos = mPos; + } + str.getChars(i, i+length, mText, pos); + mPos = pos + length; + } + + private void append(char[] buf, int i, final int length) throws IOException { + if (length > BUFFER_LEN) { + final int end = i + length; + while (i < end) { + int next = i + BUFFER_LEN; + append(buf, i, next BUFFER_LEN) { + flush(); + pos = mPos; + } + System.arraycopy(buf, i, mText, pos, length); + mPos = pos + length; + } + + private void append(String str) throws IOException { + append(str, 0, str.length()); + } + + private void escapeAndAppendString(final String string) throws IOException { + final int N = string.length(); + final char NE = (char)ESCAPE_TABLE.length; + final String[] escapes = ESCAPE_TABLE; + int lastPos = 0; + int pos; + for (pos=0; pos= NE) continue; + String escape = escapes[c]; + if (escape == null) continue; + if (lastPos < pos) append(string, lastPos, pos-lastPos); + lastPos = pos + 1; + append(escape); + } + if (lastPos < pos) append(string, lastPos, pos-lastPos); + } + + private void escapeAndAppendString(char[] buf, int start, int len) throws IOException { + final char NE = (char)ESCAPE_TABLE.length; + final String[] escapes = ESCAPE_TABLE; + int end = start+len; + int lastPos = start; + int pos; + for (pos=start; pos= NE) continue; + String escape = escapes[c]; + if (escape == null) continue; + if (lastPos < pos) append(buf, lastPos, pos-lastPos); + lastPos = pos + 1; + append(escape); + } + if (lastPos < pos) append(buf, lastPos, pos-lastPos); + } + + public XmlSerializer attribute(String namespace, String name, String value) throws IOException, + IllegalArgumentException, IllegalStateException { + append(' '); + if (namespace != null) { + append(namespace); + append(':'); + } + append(name); + append("=\""); + + escapeAndAppendString(value); + append('"'); + return this; + } + + public void cdsect(String text) throws IOException, IllegalArgumentException, + IllegalStateException { + throw new UnsupportedOperationException(); + } + + public void comment(String text) throws IOException, IllegalArgumentException, + IllegalStateException { + throw new UnsupportedOperationException(); + } + + public void docdecl(String text) throws IOException, IllegalArgumentException, + IllegalStateException { + throw new UnsupportedOperationException(); + } + + public void endDocument() throws IOException, IllegalArgumentException, IllegalStateException { + flush(); + } + + public XmlSerializer endTag(String namespace, String name) throws IOException, + IllegalArgumentException, IllegalStateException { + if (mInTag) { + append(" />\n"); + } else { + append("\n"); + } + mInTag = false; + return this; + } + + public void entityRef(String text) throws IOException, IllegalArgumentException, + IllegalStateException { + throw new UnsupportedOperationException(); + } + + private void flushBytes() throws IOException { + int position; + if ((position = mBytes.position()) > 0) { + mBytes.flip(); + mOutputStream.write(mBytes.array(), 0, position); + mBytes.clear(); + } + } + + public void flush() throws IOException { + //Log.i("PackageManager", "flush mPos=" + mPos); + if (mPos > 0) { + if (mOutputStream != null) { + CharBuffer charBuffer = CharBuffer.wrap(mText, 0, mPos); + CoderResult result = mCharset.encode(charBuffer, mBytes, true); + while (true) { + if (result.isError()) { + throw new IOException(result.toString()); + } else if (result.isOverflow()) { + flushBytes(); + result = mCharset.encode(charBuffer, mBytes, true); + continue; + } + break; + } + flushBytes(); + mOutputStream.flush(); + } else { + mWriter.write(mText, 0, mPos); + mWriter.flush(); + } + mPos = 0; + } + } + + public int getDepth() { + throw new UnsupportedOperationException(); + } + + public boolean getFeature(String name) { + throw new UnsupportedOperationException(); + } + + public String getName() { + throw new UnsupportedOperationException(); + } + + public String getNamespace() { + throw new UnsupportedOperationException(); + } + + public String getPrefix(String namespace, boolean generatePrefix) + throws IllegalArgumentException { + throw new UnsupportedOperationException(); + } + + public Object getProperty(String name) { + throw new UnsupportedOperationException(); + } + + public void ignorableWhitespace(String text) throws IOException, IllegalArgumentException, + IllegalStateException { + throw new UnsupportedOperationException(); + } + + public void processingInstruction(String text) throws IOException, IllegalArgumentException, + IllegalStateException { + throw new UnsupportedOperationException(); + } + + public void setFeature(String name, boolean state) throws IllegalArgumentException, + IllegalStateException { + if (name.equals("http://xmlpull.org/v1/doc/features.html#indent-output")) { + return; + } + throw new UnsupportedOperationException(); + } + + public void setOutput(OutputStream os, String encoding) throws IOException, + IllegalArgumentException, IllegalStateException { + if (os == null) + throw new IllegalArgumentException(); + if (true) { + try { + mCharset = Charset.forName(encoding).newEncoder(); + } catch (IllegalCharsetNameException e) { + throw (UnsupportedEncodingException) (new UnsupportedEncodingException( + encoding).initCause(e)); + } catch (UnsupportedCharsetException e) { + throw (UnsupportedEncodingException) (new UnsupportedEncodingException( + encoding).initCause(e)); + } + mOutputStream = os; + } else { + setOutput( + encoding == null + ? new OutputStreamWriter(os) + : new OutputStreamWriter(os, encoding)); + } + } + + public void setOutput(Writer writer) throws IOException, IllegalArgumentException, + IllegalStateException { + mWriter = writer; + } + + public void setPrefix(String prefix, String namespace) throws IOException, + IllegalArgumentException, IllegalStateException { + throw new UnsupportedOperationException(); + } + + public void setProperty(String name, Object value) throws IllegalArgumentException, + IllegalStateException { + throw new UnsupportedOperationException(); + } + + public void startDocument(String encoding, Boolean standalone) throws IOException, + IllegalArgumentException, IllegalStateException { + append("\n"); + } + + public XmlSerializer startTag(String namespace, String name) throws IOException, + IllegalArgumentException, IllegalStateException { + if (mInTag) { + append(">\n"); + } + append('<'); + if (namespace != null) { + append(namespace); + append(':'); + } + append(name); + mInTag = true; + return this; + } + + public XmlSerializer text(char[] buf, int start, int len) throws IOException, + IllegalArgumentException, IllegalStateException { + if (mInTag) { + append(">"); + mInTag = false; + } + escapeAndAppendString(buf, start, len); + return this; + } + + public XmlSerializer text(String text) throws IOException, IllegalArgumentException, + IllegalStateException { + if (mInTag) { + append(">"); + mInTag = false; + } + escapeAndAppendString(text); + return this; + } + +} \ No newline at end of file diff --git a/Backbone/src/main/java/me/toolify/backbone/util/FileHelper.java b/Backbone/src/main/java/me/toolify/backbone/util/FileHelper.java new file mode 100644 index 000000000..e35ac540e --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/util/FileHelper.java @@ -0,0 +1,1354 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.util; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.util.Log; + +import me.toolify.backbone.FileManagerApplication; +import me.toolify.backbone.R; +import me.toolify.backbone.commands.SyncResultExecutable; +import me.toolify.backbone.commands.shell.ResolveLinkCommand; +import me.toolify.backbone.console.Console; +import me.toolify.backbone.console.ExecutionException; +import me.toolify.backbone.console.InsufficientPermissionsException; +import me.toolify.backbone.model.AID; +import me.toolify.backbone.model.BlockDevice; +import me.toolify.backbone.model.CharacterDevice; +import me.toolify.backbone.model.Directory; +import me.toolify.backbone.model.DomainSocket; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.model.Group; +import me.toolify.backbone.model.Identity; +import me.toolify.backbone.model.NamedPipe; +import me.toolify.backbone.model.ParentDirectory; +import me.toolify.backbone.model.Permissions; +import me.toolify.backbone.model.RegularFile; +import me.toolify.backbone.model.Symlink; +import me.toolify.backbone.model.SystemFile; +import me.toolify.backbone.model.User; +import me.toolify.backbone.preferences.DisplayRestrictions; +import me.toolify.backbone.preferences.FileManagerSettings; +import me.toolify.backbone.preferences.FileTimeFormatMode; +import me.toolify.backbone.preferences.NavigationSortMode; +import me.toolify.backbone.preferences.ObjectIdentifier; +import me.toolify.backbone.preferences.ObjectStringIdentifier; +import me.toolify.backbone.preferences.Preferences; +import me.toolify.backbone.util.MimeTypeHelper.MimeTypeCategory; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * A helper class with useful methods for deal with files. + */ +public final class FileHelper { + + private static final String TAG = "FileHelper"; //$NON-NLS-1$ + + /** + * Special extension for compressed tar files + */ + private static final String[] COMPRESSED_TAR = + { + "tar.gz", "tar.bz2", "tar.lzma" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + }; + + /** + * The root directory. + * @hide + */ + public static final String ROOT_DIRECTORY = "/"; //$NON-NLS-1$ + + /** + * The parent directory string. + * @hide + */ + public static final String PARENT_DIRECTORY = ".."; //$NON-NLS-1$ + + /** + * The current directory string. + * @hide + */ + public static final String CURRENT_DIRECTORY = "."; //$NON-NLS-1$ + + /** + * The administrator user. + * @hide + */ + public static final String USER_ROOT = "root"; //$NON-NLS-1$ + + /** + * The newline string. + * @hide + */ + public static final String NEWLINE = System.getProperty("line.separator"); //$NON-NLS-1$ + + // The date/time formats objects + /** + * @hide + */ + public final static Object DATETIME_SYNC = new Object(); + /** + * @hide + */ + public static boolean sReloadDateTimeFormats = true; + private static String sDateTimeFormatOrder = null; + private static FileTimeFormatMode sFiletimeFormatMode = null; + private static DateFormat sDateFormat = null; + private static DateFormat sTimeFormat = null; + + /** + * Constructor of FileHelper. + */ + private FileHelper() { + super(); + } + + /** + * Method that check if a file is a symbolic link. + * + * @param file File to check + * @return boolean If file is a symbolic link + * @throws IOException If real file couldn't be checked + */ + public static boolean isSymlink(File file) throws IOException { + return file.getAbsolutePath().compareTo(file.getCanonicalPath()) != 0; + } + + /** + * Method that resolves a symbolic link to the real file or directory. + * + * @param file File to check + * @return File The real file or directory + * @throws IOException If real file couldn't be resolved + */ + public static File resolveSymlink(File file) throws IOException { + return file.getCanonicalFile(); + } + + /** + * Method that returns a more human readable of the size + * of a file system object. + * + * @param fso File system object + * @return String The human readable size (void if fso don't supports size) + */ + public static String getHumanReadableSize(FileSystemObject fso) { + //Only if has size + if (fso instanceof Directory) { + return ""; //$NON-NLS-1$ + } + if (hasSymlinkRef(fso)) { + if (isSymlinkRefDirectory(fso)) { + return ""; //$NON-NLS-1$ + } + return getHumanReadableSize(((Symlink)fso).getLinkRef().getSize()); + } + return getHumanReadableSize(fso.getSize()); + } + + /** + * Method that returns a more human readable of a size in bytes. + * + * @param size The size in bytes + * @return String The human readable size + */ + public static String getHumanReadableSize(long size) { + Resources res = FileManagerApplication.getInstance().getResources(); + final int[] magnitude = { + R.string.size_bytes, + R.string.size_kilobytes, + R.string.size_megabytes, + R.string.size_gigabytes + }; + + long aux = size; + int cc = magnitude.length; + for (int i = 0; i < cc; i++) { + long s = aux / 1024; + if (aux < 1024) { + return Long.toString(aux) + " " + res.getString(magnitude[i]); //$NON-NLS-1$ + } + aux = s; + } + return Long.toString(aux) + " " + res.getString(magnitude[cc - 1]); //$NON-NLS-1$ + } + + /** + * Method that returns if the file system object if the root directory. + * + * @param fso The file system object to check + * @return boolean if the file system object if the root directory + */ + public static boolean isRootDirectory(FileSystemObject fso) { + if (fso.getName() == null) return true; + return fso.getName().compareTo(FileHelper.ROOT_DIRECTORY) == 0; + } + + /** + * Method that returns if the folder if the root directory. + * + * @param folder The folder + * @return boolean if the folder if the root directory + */ + public static boolean isRootDirectory(String folder) { + if (folder == null) return true; + return isRootDirectory(new File(folder)); + } + + /** + * Method that returns if the folder if the root directory. + * + * @param folder The folder + * @return boolean if the folder if the root directory + */ + public static boolean isRootDirectory(File folder) { + if (folder.getPath() == null) return true; + return folder.getPath().compareTo(FileHelper.ROOT_DIRECTORY) == 0; + } + + /** + * Method that returns if the parent file system object if the root directory. + * + * @param fso The parent file system object to check + * @return boolean if the parent file system object if the root directory + */ + public static boolean isParentRootDirectory(FileSystemObject fso) { + if (fso.getParent() == null) return true; + return fso.getParent().compareTo(FileHelper.ROOT_DIRECTORY) == 0; + } + + /** + * Method that returns the name without the extension of a file system object. + * + * @param fso The file system object + * @return The name without the extension of the file system object. + */ + public static String getName(FileSystemObject fso) { + return getName(fso.getName()); + } + + /** + * Method that returns the name without the extension of a file system object. + * + * @param name The name of file system object + * @return The name without the extension of the file system object. + */ + public static String getName(String name) { + String ext = getExtension(name); + if (ext == null) return name; + return name.substring(0, name.length() - ext.length() - 1); + } + + /** + * Method that returns the extension of a file system object. + * + * @param fso The file system object + * @return The extension of the file system object, or null + * if fso has no extension. + */ + public static String getExtension(FileSystemObject fso) { + return getExtension(fso.getName()); + } + + /** + * Method that returns the extension of a file system object. + * + * @param name The name of file system object + * @return The extension of the file system object, or null + * if fso has no extension. + */ + public static String getExtension(String name) { + final char dot = '.'; + int pos = name.lastIndexOf(dot); + if (pos == -1 || pos == 0) { // Hidden files doesn't have extensions + return null; + } + + // Exceptions to the general extraction method + int cc = COMPRESSED_TAR.length; + for (int i = 0; i < cc; i++) { + if (name.endsWith("." + COMPRESSED_TAR[i])) { //$NON-NLS-1$ + return COMPRESSED_TAR[i]; + } + } + + // General extraction method + return name.substring(pos + 1); + } + + /** + * Method to determine the directory depth of the file system object. + * + * @return + */ + public static int getDepth(FileSystemObject file) + { + String mParent = file.getParent(); + if(mParent == null) + return 0; + return mParent.split("/").length; + } + + public static File getFile(FileSystemObject file) + { + return new File(FileHelper.addTrailingSlash(file.getParent()) + file.getName()); + } + + /** + * Method that returns the parent directory of a file/folder + * + * @param path The file/folder + * @return String The parent directory + */ + public static String getParentDir(String path) { + return getParentDir(new File(path)); + } + + /** + * Method that returns the parent directory of a file/folder + * + * @param path The file/folder + * @return String The parent directory + */ + public static String getParentDir(File path) { + String parent = path.getParent(); + if (parent == null && path.getAbsolutePath().compareTo(FileHelper.ROOT_DIRECTORY) != 0) { + parent = FileHelper.ROOT_DIRECTORY; + } + return parent; + } + + /** + * Method that evaluates if a path is relative. + * + * @param src The path to check + * @return boolean If a path is relative + */ + public static boolean isRelativePath(String src) { + if (src.startsWith(CURRENT_DIRECTORY + File.separator)) { + return true; + } + if (src.startsWith(PARENT_DIRECTORY + File.separator)) { + return true; + } + if (src.indexOf(File.separator + CURRENT_DIRECTORY + File.separator) != -1) { + return true; + } + if (src.indexOf(File.separator + PARENT_DIRECTORY + File.separator) != -1) { + return true; + } + if (!src.startsWith(ROOT_DIRECTORY)) { + return true; + } + return false; + } + + /** + * Method that check if the file system object is a {@link Symlink} and + * has a link reference. + * + * @param fso The file system object to check + * @return boolean If file system object the has a link reference + */ + public static boolean hasSymlinkRef(FileSystemObject fso) { + if (fso instanceof Symlink) { + return ((Symlink)fso).getLinkRef() != null; + } + return false; + } + + /** + * Method that check if the file system object is a {@link Symlink} and + * the link reference is a directory. + * + * @param fso The file system object to check + * @return boolean If file system object the link reference is a directory + */ + public static boolean isSymlinkRefDirectory(FileSystemObject fso) { + if (!hasSymlinkRef(fso)) { + return false; + } + return ((Symlink)fso).getLinkRef() instanceof Directory; + } + + /** + * Method that check if the file system object is a {@link Symlink} and + * the link reference is a system file. + * + * @param fso The file system object to check + * @return boolean If file system object the link reference is a system file + */ + public static boolean isSymlinkRefSystemFile(FileSystemObject fso) { + if (!hasSymlinkRef(fso)) { + return false; + } + return ((Symlink)fso).getLinkRef() instanceof SystemFile; + } + + /** + * Method that check if the file system object is a {@link Symlink} and + * the link reference is a block device. + * + * @param fso The file system object to check + * @return boolean If file system object the link reference is a block device + */ + public static boolean isSymlinkRefBlockDevice(FileSystemObject fso) { + if (!hasSymlinkRef(fso)) { + return false; + } + return ((Symlink)fso).getLinkRef() instanceof BlockDevice; + } + + /** + * Method that check if the file system object is a {@link Symlink} and + * the link reference is a character device. + * + * @param fso The file system object to check + * @return boolean If file system object the link reference is a character device + */ + public static boolean isSymlinkRefCharacterDevice(FileSystemObject fso) { + if (!hasSymlinkRef(fso)) { + return false; + } + return ((Symlink)fso).getLinkRef() instanceof CharacterDevice; + } + + /** + * Method that check if the file system object is a {@link Symlink} and + * the link reference is a named pipe. + * + * @param fso The file system object to check + * @return boolean If file system object the link reference is a named pipe + */ + public static boolean isSymlinkRefNamedPipe(FileSystemObject fso) { + if (!hasSymlinkRef(fso)) { + return false; + } + return ((Symlink)fso).getLinkRef() instanceof NamedPipe; + } + + /** + * Method that check if the file system object is a {@link Symlink} and + * the link reference is a domain socket. + * + * @param fso The file system object to check + * @return boolean If file system object the link reference is a domain socket + */ + public static boolean isSymlinkRefDomainSocket(FileSystemObject fso) { + if (!hasSymlinkRef(fso)) { + return false; + } + return ((Symlink)fso).getLinkRef() instanceof DomainSocket; + } + + /** + * Method that checks if a file system object is a directory (real o symlink). + * + * @param fso The file system object to check + * @return boolean If file system object is a directory + */ + public static boolean isDirectory(FileSystemObject fso) { + if (fso instanceof Directory) { + return true; + } + if (isSymlinkRefDirectory(fso)) { + return true; + } + return false; + } + + public static boolean isSubDirectoryOf(String childPath, String comparisonPath) + { + String parentPath = getParentDir(childPath); + + while( parentPath != null ) { + if( parentPath == comparisonPath ) { + return true ; + } + + parentPath = getParentDir(parentPath); + } + + return false ; + } + + /** + * Method that checks if a file system object is a system file (real o symlink). + * + * @param fso The file system object to check + * @return boolean If file system object is a system file + */ + public static boolean isSystemFile(FileSystemObject fso) { + if (fso instanceof SystemFile) { + return true; + } + if (isSymlinkRefSystemFile(fso)) { + return true; + } + return false; + } + + /** + * Method that returns the real reference of a file system object + * (the reference file system object if the file system object is a symlink. + * Otherwise the same reference). + * + * @param fso The file system object to check + * @return FileSystemObject The real file system object reference + */ + public static FileSystemObject getReference(FileSystemObject fso) { + if (hasSymlinkRef(fso)) { + return ((Symlink)fso).getLinkRef(); + } + return fso; + } + + /** + * Method that applies the configuration modes to the listed files + * (sort mode, hidden files, ...). + * + * @param files The listed files + * @param restrictions The restrictions to apply when displaying files + * @param chRooted If app run with no privileges + * @return List The applied mode listed files + */ + public static List applyUserPreferences( + List files, Map restrictions, boolean chRooted) { + return applyUserPreferences(files, restrictions, false, chRooted); + } + + /** + * Method that applies the configuration modes to the listed files + * (sort mode, hidden files, ...). + * + * @param files The listed files + * @param restrictions The restrictions to apply when displaying files + * @param noSort If sort must be applied + * @param chRooted If app run with no privileges + * @return List The applied mode listed files + */ + public static List applyUserPreferences( + List files, Map restrictions, + boolean noSort, boolean chRooted) { + //Retrieve user preferences + SharedPreferences prefs = Preferences.getSharedPreferences(); + FileManagerSettings sortModePref = FileManagerSettings.SETTINGS_SORT_MODE; + FileManagerSettings showDirsFirstPref = FileManagerSettings.SETTINGS_SHOW_DIRS_FIRST; + FileManagerSettings showHiddenPref = FileManagerSettings.SETTINGS_SHOW_HIDDEN; + FileManagerSettings showSystemPref = FileManagerSettings.SETTINGS_SHOW_SYSTEM; + FileManagerSettings showSymlinksPref = FileManagerSettings.SETTINGS_SHOW_SYMLINKS; + + //Remove all unnecessary files (no required by the user) + int cc = files.size(); + for (int i = cc - 1; i >= 0; i--) { + FileSystemObject file = files.get(i); + + //Hidden files + if (!prefs.getBoolean( + showHiddenPref.getId(), + ((Boolean)showHiddenPref.getDefaultValue()).booleanValue()) || chRooted) { + if (file.isHidden()) { + files.remove(i); + continue; + } + } + + //System files + if (!prefs.getBoolean( + showSystemPref.getId(), + ((Boolean)showSystemPref.getDefaultValue()).booleanValue()) || chRooted) { + if (file instanceof SystemFile) { + files.remove(i); + continue; + } + } + + //Symlinks files + if (!prefs.getBoolean( + showSymlinksPref.getId(), + ((Boolean)showSymlinksPref.getDefaultValue()).booleanValue()) || chRooted) { + if (file instanceof Symlink) { + files.remove(i); + continue; + } + } + + // Restrictions (only apply to files) + if (restrictions != null) { + if (!isDirectory(file)) { + if (!isDisplayAllowed(file, restrictions)) { + files.remove(i); + continue; + } + } + } + } + + //Apply sort mode + if (!noSort) { + final boolean showDirsFirst = + prefs.getBoolean( + showDirsFirstPref.getId(), + ((Boolean)showDirsFirstPref.getDefaultValue()).booleanValue()); + final NavigationSortMode sortMode = + NavigationSortMode.fromId( + prefs.getInt(sortModePref.getId(), + ((ObjectIdentifier)sortModePref.getDefaultValue()).getId())); + Collections.sort(files, new Comparator() { + @Override + public int compare(FileSystemObject lhs, FileSystemObject rhs) { + //Parent directory always goes first + boolean isLhsParentDirectory = lhs instanceof ParentDirectory; + boolean isRhsParentDirectory = rhs instanceof ParentDirectory; + if (isLhsParentDirectory || isRhsParentDirectory) { + if (isLhsParentDirectory && isRhsParentDirectory) { + return 0; + } + return (isLhsParentDirectory) ? -1 : 1; + } + + //Need to sort directory first? + if (showDirsFirst) { + boolean isLhsDirectory = FileHelper.isDirectory(lhs); + boolean isRhsDirectory = FileHelper.isDirectory(rhs); + if (isLhsDirectory || isRhsDirectory) { + if (isLhsDirectory && isRhsDirectory) { + //Apply sort mode + return FileHelper.doCompare(lhs, rhs, sortMode); + } + return (isLhsDirectory) ? -1 : 1; + } + } + + //Apply sort mode + return FileHelper.doCompare(lhs, rhs, sortMode); + } + + }); + } + + //Return the files + return files; + } + + /** + * Method that check if a file should be displayed according to the restrictions + * + * @param fso The file system object to check + * @param restrictions The restrictions map + * @return boolean If the file should be displayed + */ + private static boolean isDisplayAllowed( + FileSystemObject fso, Map restrictions) { + Iterator it = restrictions.keySet().iterator(); + while (it.hasNext()) { + DisplayRestrictions restriction = it.next(); + Object value = restrictions.get(restriction); + if (value == null) { + continue; + } + switch (restriction) { + case CATEGORY_TYPE_RESTRICTION: + if (value instanceof MimeTypeCategory) { + MimeTypeCategory cat1 = (MimeTypeCategory)value; + // NOTE: We don't need the context here, because mime-type + // database should be loaded prior to this call + MimeTypeCategory cat2 = MimeTypeHelper.getCategory(null, fso); + if (cat1.compareTo(cat2) != 0) { + return false; + } + } + break; + + case MIME_TYPE_RESTRICTION: + if (value instanceof String) { + String mimeType = (String)value; + if (mimeType.compareTo(MimeTypeHelper.ALL_MIME_TYPES) != 0) { + // NOTE: We don't need the context here, because mime-type + // database should be loaded prior to this call + if (!MimeTypeHelper.matchesMimeType(null, fso, mimeType)) { + return false; + } + } + } + break; + + case SIZE_RESTRICTION: + if (value instanceof Long) { + Long maxSize = (Long)value; + if (fso.getSize() > maxSize.longValue()) { + return false; + } + } + break; + + case DIRECTORY_ONLY_RESTRICTION: + if (value instanceof Boolean) { + Boolean directoryOnly = (Boolean) value; + if (directoryOnly.booleanValue() && !FileHelper.isDirectory(fso)) { + return false; + } + } + break; + + case LOCAL_FILESYSTEM_ONLY_RESTRICTION: + if (value instanceof Boolean) { + Boolean localOnly = (Boolean)value; + if (localOnly.booleanValue()) { + /** TODO Needed when CMFM gets networking **/ + } + } + break; + + default: + break; + } + } + return true; + } + + /** + * Method that resolve the symbolic links of the list of files passed as argument.
+ * This method invokes the {@link ResolveLinkCommand} in those files that hasn't a valid + * symlink reference + * + * @param context The current context + * @param files The listed files + */ + public static void resolveSymlinks(Context context, List files) { + int cc = files.size(); + for (int i = 0; i < cc; i++) { + FileSystemObject fso = files.get(i); + if (fso instanceof Symlink && ((Symlink)fso).getLinkRef() == null) { + try { + FileSystemObject symlink = + CommandHelper.resolveSymlink(context, fso.getFullPath(), null); + ((Symlink)fso).setLinkRef(symlink); + } catch (Throwable ex) {/**NON BLOCK**/} + } + } + } + + /** + * Method that do a comparison between 2 file system objects. + * + * @param fso1 The first file system objects + * @param fso2 The second file system objects + * @param mode The sort mode + * @return int a negative integer if {@code fso1} is less than {@code fso2}; + * a positive integer if {@code fso1} is greater than {@code fso2}; + * 0 if {@code fso1} has the same order as {@code fso2}. + */ + public static int doCompare( + final FileSystemObject fso1, + final FileSystemObject fso2, + final NavigationSortMode mode) { + + // Retrieve the user preference for case sensitive sort + boolean caseSensitive = + Preferences.getSharedPreferences(). + getBoolean( + FileManagerSettings.SETTINGS_CASE_SENSITIVE_SORT.getId(), + ((Boolean)FileManagerSettings.SETTINGS_CASE_SENSITIVE_SORT. + getDefaultValue()).booleanValue()); + + //Name (ascending) + if (mode.getId() == NavigationSortMode.NAME_ASC.getId()) { + if (!caseSensitive) { + return fso1.getName().compareToIgnoreCase(fso2.getName()); + } + return fso1.getName().compareTo(fso2.getName()); + } + //Name (descending) + if (mode.getId() == NavigationSortMode.NAME_DESC.getId()) { + if (!caseSensitive) { + return fso1.getName().compareToIgnoreCase(fso2.getName()) * -1; + } + return fso1.getName().compareTo(fso2.getName()) * -1; + } + + //Date (ascending) + if (mode.getId() == NavigationSortMode.DATE_ASC.getId()) { + return fso1.getLastModifiedTime().compareTo(fso2.getLastModifiedTime()); + } + //Date (descending) + if (mode.getId() == NavigationSortMode.DATE_DESC.getId()) { + return fso1.getLastModifiedTime().compareTo(fso2.getLastModifiedTime()) * -1; + } + + //Comparison between files directly + return fso1.compareTo(fso2); + } + + /** + * Method that add to the path the trailing slash + * + * @param path The path + * @return String The path with the trailing slash + */ + public static String addTrailingSlash(String path) { + if (path == null) return null; + return path.endsWith(File.separator) ? path : path + File.separator; + } + + /** + * Method that cleans the path and removes the trailing slash + * + * @param path The path to clean + * @return String The path without the trailing slash + */ + public static String removeTrailingSlash(String path) { + if (path == null) return null; + if (path.trim().compareTo(ROOT_DIRECTORY) == 0) return path; + if (path.endsWith(File.separator)) { + return path.substring(0, path.length()-1); + } + return path; + } + + /** + * Method that creates a new name based on the name of the {@link FileSystemObject} + * that is not current used by the filesystem. + * + * @param ctx The current context + * @param files The list of files of the current directory + * @param attemptedName The attempted name + * @param regexp The resource of the regular expression to create the new name + * @return String The new non-existing name + */ + public static String createNonExistingName( + final Context ctx, final List files, + final String attemptedName, int regexp) { + // Find a non-exiting name + String newName = attemptedName; + if (!isNameExists(files, newName)) return newName; + do { + String name = FileHelper.getName(newName); + String ext = FileHelper.getExtension(newName); + if (ext == null) { + ext = ""; //$NON-NLS-1$ + } else { + ext = "." + ext; //$NON-NLS-1$ + } + newName = ctx.getString(regexp, name, ext); + } while (isNameExists(files, newName)); + return newName; + } + + /** + * Method that checks if a name exists in the current directory. + * + * @param files The list of files of the current directory + * @param name The name to check + * @return boolean Indicate if the name exists in the current directory + */ + public static boolean isNameExists(List files, String name) { + //Verify if the name exists in the current file list + int cc = files.size(); + for (int i = 0; i < cc; i++) { + FileSystemObject fso = files.get(i); + if (fso.getName().compareTo(name) == 0) { + return true; + } + } + return false; + } + + /** + * Method that returns is a {@link FileSystemObject} can be handled by this application + * allowing the uncompression of the file + * + * @param fso The file system object to verify + * @return boolean If the file is supported + */ + @SuppressWarnings("nls") + public static boolean isSupportedUncompressedFile(FileSystemObject fso) { + // Valid uncompressed formats are: + final String[] VALID = + { + "tar", "tgz", "tar.gz", "tar.bz2", "tar.lzma", + "zip", "gz", "bz2", "lzma", "xz", "Z", "rar" + }; + // Null values for required commands + final String[] OPT_KEYS = + { + null, null, null, null, null, + "unzip", null, null, "unlzma", "unxz", "uncompress", "unrar" + }; + + // Check that have a valid file + if (fso == null) return false; + + //Only regular files + if (isDirectory(fso) || fso instanceof Symlink) { + return false; + } + String ext = getExtension(fso); + if (ext != null) { + int cc = VALID.length; + for (int i = 0; i < cc; i++) { + if (VALID[i].compareToIgnoreCase(ext) == 0) { + // Is the command present + if (OPT_KEYS[i] != null && + FileManagerApplication.hasOptionalCommand(OPT_KEYS[i])) { + return true; + } + return false; + } + } + } + return false; + } + + /** + * Method that converts an absolute path to a relative path + * + * @param path The absolute path to convert + * @param relativeTo The absolute path from which make path relative to (a folder) + * @return String The relative path + */ + public static String toRelativePath(String path, String relativeTo) { + // Normalize the paths + File f1 = new File(path); + File f2 = new File(relativeTo); + String s1 = f1.getAbsolutePath(); + String s2 = f2.getAbsolutePath(); + if (!s2.endsWith(File.separator)) { + s2 = s2 + File.separator; + } + + // If s2 contains s1 then the relative is replace of the start of the path + if (s1.startsWith(s2)) { + return s1.substring(s2.length()); + } + + StringBuffer relative = new StringBuffer(); + do { + File f3 = new File(s2); + relative.append(String.format("..%s", File.separator)); //$NON-NLS-1$ + s2 = f3.getParent() + File.separator; + } while (!s1.startsWith(s2) && !s1.startsWith(new File(s2).getAbsolutePath())); + s2 = new File(s2).getAbsolutePath(); + return relative.toString() + s1.substring(s2.length()); + } + + /** + * Method that creates a {@link FileSystemObject} from a {@link File} + * + * @param file The file or folder reference + * @return FileSystemObject The file system object reference + */ + public static FileSystemObject createFileSystemObject(File file) { + try { + // The user and group name of the files. In ChRoot, aosp give restrict access to + // this user and group. + final String USER = "system"; //$NON-NLS-1$ + final String GROUP = "sdcard_r"; //$NON-NLS-1$ + final String PERMISSIONS = "----rwxr-x"; //$NON-NLS-1$ + + // The user and group name of the files. In ChRoot, aosp give restrict access to + // this user and group. This applies for permission also. This has no really much + // interest if we not allow to change the permissions + AID userAID = AIDHelper.getAIDFromName(USER); + AID groupAID = AIDHelper.getAIDFromName(GROUP); + User user = new User(userAID.getId(), userAID.getName()); + Group group = new Group(groupAID.getId(), groupAID.getName()); + Permissions perm = Permissions.fromRawString(PERMISSIONS); + + // Build a directory? + Date lastModified = new Date(file.lastModified()); + if (file.isDirectory()) { + return + new Directory( + file.getName(), + file.getParent(), + user, group, perm, + lastModified, lastModified, lastModified); // The only date we have + } + + // Build a regular file + return + new RegularFile( + file.getName(), + file.getParent(), + user, group, perm, + file.length(), + lastModified, lastModified, lastModified); // The only date we have + } catch (Exception e) { + Log.e(TAG, "Exception retrieving the fso", e); //$NON-NLS-1$ + } + return null; + } + + /** + * Method that copies recursively to the destination + * + * @param src The source file or folder + * @param dst The destination file or folder + * @param bufferSize The buffer size for the operation + * @return boolean If the operation complete successfully + * @throws ExecutionException If a problem was detected in the operation + */ + public static boolean copyRecursive( + final File src, final File dst, int bufferSize) throws ExecutionException { + if (src.isDirectory()) { + // Create the directory + if (dst.exists() && !dst.isDirectory()) { + Log.e(TAG, + String.format("Failed to check destionation dir: %s", dst)); //$NON-NLS-1$ + throw new ExecutionException("the path exists but is not a folder"); //$NON-NLS-1$ + } + if (!dst.exists()) { + if (!dst.mkdir()) { + Log.e(TAG, String.format("Failed to create directory: %s", dst)); //$NON-NLS-1$ + return false; + } + } + File[] files = src.listFiles(); + if (files != null) { + for (int i = 0; i < files.length; i++) { + if (!copyRecursive(files[i], new File(dst, files[i].getName()), bufferSize)) { + return false; + } + } + } + } else { + // Copy the directory + if (!bufferedCopy(src, dst,bufferSize)) { + return false; + } + } + return true; + } + + /** + * Method that copies a file + * + * @param src The source file + * @param dst The destination file + * @param bufferSize The buffer size for the operation + * @return boolean If the operation complete successfully + */ + public static boolean bufferedCopy(final File src, final File dst, int bufferSize) { + BufferedInputStream bis = null; + BufferedOutputStream bos = null; + try { + bis = new BufferedInputStream(new FileInputStream(src), bufferSize); + bos = new BufferedOutputStream(new FileOutputStream(dst), bufferSize); + int read = 0; + byte[] data = new byte[bufferSize]; + while ((read = bis.read(data, 0, bufferSize)) != -1) { + bos.write(data, 0, read); + } + return true; + + } catch (Throwable e) { + Log.e(TAG, + String.format(TAG, "Failed to copy from %s to %d", src, dst), e); //$NON-NLS-1$ + return false; + } finally { + try { + if (bis != null) { + bis.close(); + } + } catch (Throwable e) {/**NON BLOCK**/} + try { + if (bos != null) { + bos.close(); + } + } catch (Throwable e) {/**NON BLOCK**/} + } + } + + /** + * Method that deletes a folder recursively + * + * @param folder The folder to delete + * @return boolean If the folder was deleted + */ + public static boolean deleteFolder(File folder) { + File[] files = folder.listFiles(); + if (files != null) { + for (int i = 0; i < files.length; i++) { + if (files[i].isDirectory()) { + if (!deleteFolder(files[i])) { + return false; + } + } else { + if (!files[i].delete()) { + return false; + } + } + } + } + return folder.delete(); + } + + /** + * Method that returns the canonical/absolute path of the path.
+ * This method performs path resolution + * + * @param path The path to convert + * @return String The canonical/absolute path + */ + public static String getAbsPath(String path) { + try { + return new File(path).getCanonicalPath(); + } catch (Exception e) { + return new File(path).getAbsolutePath(); + } + } + + /** + * Method that returns the .nomedia file + * + * @param fso The folder that contains the .nomedia file + * @return File The .nomedia file + */ + public static File getNoMediaFile(FileSystemObject fso) { + File file = null; + try { + file = new File(fso.getFullPath()).getCanonicalFile(); + } catch (Exception e) { + file = new File(fso.getFullPath()).getAbsoluteFile(); + } + return new File(file, ".nomedia").getAbsoluteFile(); //$NON-NLS-1$ + } + + /** + * Method that ensures that the actual console has access to read the + * {@link FileSystemObject} passed. + * + * @param console The console + * @param fso The {@link FileSystemObject} to check + * @param executable The executable to associate to the {@link InsufficientPermissionsException} + * @throws InsufficientPermissionsException If the console doesn't have enough rights + */ + public static void ensureReadAccess( + Console console, FileSystemObject fso, SyncResultExecutable executable) + throws InsufficientPermissionsException { + try { + if (console.isPrivileged()) { + // Should have access + return; + } + Identity identity = console.getIdentity(); + if (identity == null) { + throw new InsufficientPermissionsException(executable); + } + Permissions permissions = fso.getPermissions(); + User user = fso.getUser(); + Group group = fso.getGroup(); + List groups = identity.getGroups(); + if ( permissions == null || user == null || group == null) { + throw new InsufficientPermissionsException(executable); + } + // Check others + if (permissions.getOthers().isRead()) { + return; + } + // Check user + if (user.getId() == identity.getUser().getId() && permissions.getUser().isRead()) { + return; + } + // Check group + if (group.getId() == identity.getGroup().getId() && permissions.getGroup().isRead()) { + return; + } + // Check groups + int cc = groups.size(); + for (int i = 0; i < cc; i++) { + Group g = groups.get(i); + if (group.getId() == g.getId() && permissions.getGroup().isRead()) { + return; + } + } + + } catch (Exception e) { + Log.e(TAG, "Failed to check fso read permission,", e); //$NON-NLS-1$ + } + throw new InsufficientPermissionsException(executable); + } + + /** + * Method that ensures that the actual console has access to write the + * {@link FileSystemObject} passed. + * + * @param console The console + * @param fso The {@link FileSystemObject} to check + * @param executable The executable to associate to the {@link InsufficientPermissionsException} + * @throws InsufficientPermissionsException If the console doesn't have enough rights + */ + public static void ensureWriteAccess( + Console console, FileSystemObject fso, SyncResultExecutable executable) + throws InsufficientPermissionsException { + try { + if (console.isPrivileged()) { + // Should have access + return; + } + Identity identity = console.getIdentity(); + if (identity == null) { + throw new InsufficientPermissionsException(executable); + } + Permissions permissions = fso.getPermissions(); + User user = fso.getUser(); + Group group = fso.getGroup(); + List groups = identity.getGroups(); + if ( permissions == null || user == null || group == null) { + throw new InsufficientPermissionsException(executable); + } + // Check others + if (permissions.getOthers().isWrite()) { + return; + } + // Check user + if (user.getId() == identity.getUser().getId() && permissions.getUser().isWrite()) { + return; + } + // Check group + if (group.getId() == identity.getGroup().getId() && permissions.getGroup().isWrite()) { + return; + } + // Check groups + int cc = groups.size(); + for (int i = 0; i < cc; i++) { + Group g = groups.get(i); + if (group.getId() == g.getId() && permissions.getGroup().isWrite()) { + return; + } + } + + } catch (Exception e) { + Log.e(TAG, "Failed to check fso write permission,", e); //$NON-NLS-1$ + } + throw new InsufficientPermissionsException(executable); + } + + /** + * Method that ensures that the actual console has access to execute the + * {@link FileSystemObject} passed. + * + * @param console The console + * @param fso The {@link FileSystemObject} to check + * @param executable The executable to associate to the {@link InsufficientPermissionsException} + * @throws InsufficientPermissionsException If the console doesn't have enough rights + */ + public static void ensureExecuteAccess( + Console console, FileSystemObject fso, SyncResultExecutable executable) + throws InsufficientPermissionsException { + try { + if (console.isPrivileged()) { + // Should have access + return; + } + Identity identity = console.getIdentity(); + if (identity == null) { + throw new InsufficientPermissionsException(executable); + } + Permissions permissions = fso.getPermissions(); + User user = fso.getUser(); + Group group = fso.getGroup(); + List groups = identity.getGroups(); + if ( permissions == null || user == null || group == null) { + throw new InsufficientPermissionsException(executable); + } + // Check others + if (permissions.getOthers().isExecute()) { + return; + } + // Check user + if (user.getId() == identity.getUser().getId() && permissions.getUser().isExecute()) { + return; + } + // Check group + if (group.getId() == identity.getGroup().getId() && permissions.getGroup().isExecute()) { + return; + } + // Check groups + int cc = groups.size(); + for (int i = 0; i < cc; i++) { + Group g = groups.get(i); + if (group.getId() == g.getId() && permissions.getGroup().isExecute()) { + return; + } + } + + } catch (Exception e) { + Log.e(TAG, "Failed to check fso execute permission,", e); //$NON-NLS-1$ + } + throw new InsufficientPermissionsException(executable); + } + + /** + * Method that formats a filetime date with the specific system settings + * + * @param ctx The current context + * @param filetime The filetime date + * @return String The filetime date formatted + */ + public static String formatFileTime(Context ctx, Date filetime) { + synchronized (DATETIME_SYNC) { + if (sReloadDateTimeFormats) { + String defaultValue = + ((ObjectStringIdentifier)FileManagerSettings. + SETTINGS_FILETIME_FORMAT_MODE.getDefaultValue()).getId(); + String id = FileManagerSettings.SETTINGS_FILETIME_FORMAT_MODE.getId(); + sFiletimeFormatMode = + FileTimeFormatMode.fromId( + Preferences.getSharedPreferences().getString(id, defaultValue)); + if (sFiletimeFormatMode.compareTo(FileTimeFormatMode.SYSTEM) == 0) { + sDateTimeFormatOrder = ctx.getString(R.string.datetime_format_order); + sDateFormat = android.text.format.DateFormat.getDateFormat(ctx); + sTimeFormat = android.text.format.DateFormat.getTimeFormat(ctx); + } else if (sFiletimeFormatMode.compareTo(FileTimeFormatMode.LOCALE) == 0) { + sDateFormat = + DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT); + } else { + sDateFormat = new SimpleDateFormat(sFiletimeFormatMode.getFormat()); + } + sReloadDateTimeFormats = false; + } + } + + // Apply the user settings + if (sFiletimeFormatMode.compareTo(FileTimeFormatMode.SYSTEM) == 0) { + String date = sDateFormat.format(filetime); + String time = sTimeFormat.format(filetime); + return String.format(sDateTimeFormatOrder, date, time); + } else { + return sDateFormat.format(filetime); + } + } +} diff --git a/src/com/cyanogenmod/filemanager/util/FixedQueue.java b/Backbone/src/main/java/me/toolify/backbone/util/FixedQueue.java similarity index 95% rename from src/com/cyanogenmod/filemanager/util/FixedQueue.java rename to Backbone/src/main/java/me/toolify/backbone/util/FixedQueue.java index 47b6aebe7..6dd6e17c5 100644 --- a/src/com/cyanogenmod/filemanager/util/FixedQueue.java +++ b/Backbone/src/main/java/me/toolify/backbone/util/FixedQueue.java @@ -1,195 +1,195 @@ -/* - * Copyright (C) 2012 The CyanogenMod Project - * - * 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 com.cyanogenmod.filemanager.util; - -import java.util.ArrayList; -import java.util.List; - -/** - * A class that represent a FIFO queue with a fixed size. When the queue reach the maximum defined - * size then extract the next element from the queue. - * @param The type of object to hold. - */ -@SuppressWarnings("unchecked") -public class FixedQueue { - - /** - * An exception thrown when the queue hasn't more elements - */ - public static class EmptyQueueException extends Exception { - private static final long serialVersionUID = 1L; - } - - private final Object[] mQueue; - private final int mSize; - private int mHead; - private int mTail; - - /** - * Constructor of FixedQueue - * - * @param size The size of the queue. The limit of objects in queue. Beyond this limits - * the older objects are recycled. - */ - public FixedQueue(int size) { - super(); - this.mQueue = new Object[size]; - this.mSize = size; - this.mHead = 0; - this.mTail = 0; - } - - /** - * Method that inserts a new object to the queue. - * - * @param o The object to insert - * @return The object inserted (for concatenation purpose) - */ - public T insert(T o) { - synchronized (this.mQueue) { - if (o == null) throw new NullPointerException(); - if (this.mQueue[this.mHead] != null) { - try { - noSynchronizedRemove(); - } catch (Throwable ex) {/**NON BLOCK**/} - } - this.mQueue[this.mHead] = o; - this.mHead++; - if (this.mHead >= this.mSize) { - this.mHead = 0; - } - return o; - } - } - - /** - * Method that extract the first element in the queue - * - * @return The item extracted - * @throws EmptyQueueException If the queue hasn't element - */ - public T remove() throws EmptyQueueException { - synchronized (this.mQueue) { - return noSynchronizedRemove(); - } - } - - /** - * Method that extract all the items from the queue - * - * @return The items extracted - * @throws EmptyQueueException If the queue hasn't element - */ - public List removeAll() throws EmptyQueueException { - synchronized (this.mQueue) { - if (isEmpty()) throw new EmptyQueueException(); - List l = new ArrayList(); - while (!isEmpty()) { - l.add(noSynchronizedRemove()); - } - return l; - } - } - - /** - * Method that retrieves the first element in the queue. This method doesn't remove the item - * from queue. - * - * @return The item retrieved - * @throws EmptyQueueException If the queue hasn't element - */ - public T peek() throws EmptyQueueException { - synchronized (this.mQueue) { - T o = (T)this.mQueue[this.mTail]; - if (o == null) throw new EmptyQueueException(); - return o; - } - - } - - /** - * Method that retrieves all the items from the queue. This method doesn't remove any item - * from queue. - * - * @return The items retrieved - * @throws EmptyQueueException If the queue hasn't element - */ - public List peekAll() throws EmptyQueueException { - synchronized (this.mQueue) { - if (isEmpty()) throw new EmptyQueueException(); - List l = new ArrayList(); - int head = this.mHead; - int tail = this.mTail; - do { - l.add((T)this.mQueue[tail]); - tail++; - if (tail >= this.mSize) { - tail = 0; - } - } while (head != tail); - return l; - } - } - - /** - * Method that returns if the queue is empty - * - * @return boolean If the queue is empty - */ - public boolean isEmpty() { - synchronized (this.mQueue) { - return this.mQueue[this.mTail] == null; - } - } - - /** - * Method that returns the number of items in the queue - * - * @return int The number of items - */ - public int items() { - int cc = 0; - int head = this.mHead; - int tail = this.mTail; - do { - cc++; - tail++; - if (tail >= this.mSize) { - tail = 0; - } - } while (head != tail); - return cc; - } - - /** - * Method that remove one item without synchronization (for be called from - * synchronized method). - * - * @return The item extracted - * @throws EmptyQueueException If the queue hasn't element - */ - private T noSynchronizedRemove() throws EmptyQueueException { - T o = (T)this.mQueue[this.mTail]; - if (o == null) throw new EmptyQueueException(); - this.mQueue[this.mTail] = null; - this.mTail++; - if (this.mTail >= this.mSize) { - this.mTail = 0; - } - return o; - } +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.util; + +import java.util.ArrayList; +import java.util.List; + +/** + * A class that represent a FIFO queue with a fixed size. When the queue reach the maximum defined + * size then extract the next element from the queue. + * @param The type of object to hold. + */ +@SuppressWarnings("unchecked") +public class FixedQueue { + + /** + * An exception thrown when the queue hasn't more elements + */ + public static class EmptyQueueException extends Exception { + private static final long serialVersionUID = 1L; + } + + private final Object[] mQueue; + private final int mSize; + private int mHead; + private int mTail; + + /** + * Constructor of FixedQueue + * + * @param size The size of the queue. The limit of objects in queue. Beyond this limits + * the older objects are recycled. + */ + public FixedQueue(int size) { + super(); + this.mQueue = new Object[size]; + this.mSize = size; + this.mHead = 0; + this.mTail = 0; + } + + /** + * Method that inserts a new object to the queue. + * + * @param o The object to insert + * @return The object inserted (for concatenation purpose) + */ + public T insert(T o) { + synchronized (this.mQueue) { + if (o == null) throw new NullPointerException(); + if (this.mQueue[this.mHead] != null) { + try { + noSynchronizedRemove(); + } catch (Throwable ex) {/**NON BLOCK**/} + } + this.mQueue[this.mHead] = o; + this.mHead++; + if (this.mHead >= this.mSize) { + this.mHead = 0; + } + return o; + } + } + + /** + * Method that extract the first element in the queue + * + * @return The item extracted + * @throws EmptyQueueException If the queue hasn't element + */ + public T remove() throws EmptyQueueException { + synchronized (this.mQueue) { + return noSynchronizedRemove(); + } + } + + /** + * Method that extract all the items from the queue + * + * @return The items extracted + * @throws EmptyQueueException If the queue hasn't element + */ + public List removeAll() throws EmptyQueueException { + synchronized (this.mQueue) { + if (isEmpty()) throw new EmptyQueueException(); + List l = new ArrayList(); + while (!isEmpty()) { + l.add(noSynchronizedRemove()); + } + return l; + } + } + + /** + * Method that retrieves the first element in the queue. This method doesn't remove the item + * from queue. + * + * @return The item retrieved + * @throws EmptyQueueException If the queue hasn't element + */ + public T peek() throws EmptyQueueException { + synchronized (this.mQueue) { + T o = (T)this.mQueue[this.mTail]; + if (o == null) throw new EmptyQueueException(); + return o; + } + + } + + /** + * Method that retrieves all the items from the queue. This method doesn't remove any item + * from queue. + * + * @return The items retrieved + * @throws EmptyQueueException If the queue hasn't element + */ + public List peekAll() throws EmptyQueueException { + synchronized (this.mQueue) { + if (isEmpty()) throw new EmptyQueueException(); + List l = new ArrayList(); + int head = this.mHead; + int tail = this.mTail; + do { + l.add((T)this.mQueue[tail]); + tail++; + if (tail >= this.mSize) { + tail = 0; + } + } while (head != tail); + return l; + } + } + + /** + * Method that returns if the queue is empty + * + * @return boolean If the queue is empty + */ + public boolean isEmpty() { + synchronized (this.mQueue) { + return this.mQueue[this.mTail] == null; + } + } + + /** + * Method that returns the number of items in the queue + * + * @return int The number of items + */ + public int items() { + int cc = 0; + int head = this.mHead; + int tail = this.mTail; + do { + cc++; + tail++; + if (tail >= this.mSize) { + tail = 0; + } + } while (head != tail); + return cc; + } + + /** + * Method that remove one item without synchronization (for be called from + * synchronized method). + * + * @return The item extracted + * @throws EmptyQueueException If the queue hasn't element + */ + private T noSynchronizedRemove() throws EmptyQueueException { + T o = (T)this.mQueue[this.mTail]; + if (o == null) throw new EmptyQueueException(); + this.mQueue[this.mTail] = null; + this.mTail++; + if (this.mTail >= this.mSize) { + this.mTail = 0; + } + return o; + } } \ No newline at end of file diff --git a/Backbone/src/main/java/me/toolify/backbone/util/HexDump.java b/Backbone/src/main/java/me/toolify/backbone/util/HexDump.java new file mode 100644 index 000000000..61677f35b --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/util/HexDump.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * 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 me.toolify.backbone.util; + +public class HexDump +{ + private final static char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + + public static String dumpHexString(byte[] array) + { + return dumpHexString(array, 0, array.length); + } + + public static String dumpHexString(byte[] array, int offset, int length) + { + StringBuilder result = new StringBuilder(); + + byte[] line = new byte[16]; + int lineIndex = 0; + + result.append("\n0x"); + result.append(toHexString(offset)); + + for (int i = offset ; i < offset + length ; i++) + { + if (lineIndex == 16) + { + result.append(" "); + + for (int j = 0 ; j < 16 ; j++) + { + if (line[j] > ' ' && line[j] < '~') + { + result.append(new String(line, j, 1)); + } + else + { + result.append("."); + } + } + + result.append("\n0x"); + result.append(toHexString(i)); + lineIndex = 0; + } + + byte b = array[i]; + result.append(" "); + result.append(HEX_DIGITS[(b >>> 4) & 0x0F]); + result.append(HEX_DIGITS[b & 0x0F]); + + line[lineIndex++] = b; + } + + if (lineIndex != 16) + { + int count = (16 - lineIndex) * 3; + count++; + for (int i = 0 ; i < count ; i++) + { + result.append(" "); + } + + for (int i = 0 ; i < lineIndex ; i++) + { + if (line[i] > ' ' && line[i] < '~') + { + result.append(new String(line, i, 1)); + } + else + { + result.append("."); + } + } + } + + return result.toString(); + } + + public static String toHexString(byte b) + { + return toHexString(toByteArray(b)); + } + + public static String toHexString(byte[] array) + { + return toHexString(array, 0, array.length); + } + + public static String toHexString(byte[] array, int offset, int length) + { + char[] buf = new char[length * 2]; + + int bufIndex = 0; + for (int i = offset ; i < offset + length; i++) + { + byte b = array[i]; + buf[bufIndex++] = HEX_DIGITS[(b >>> 4) & 0x0F]; + buf[bufIndex++] = HEX_DIGITS[b & 0x0F]; + } + + return new String(buf); + } + + public static String toHexString(int i) + { + return toHexString(toByteArray(i)); + } + + public static byte[] toByteArray(byte b) + { + byte[] array = new byte[1]; + array[0] = b; + return array; + } + + public static byte[] toByteArray(int i) + { + byte[] array = new byte[4]; + + array[3] = (byte)(i & 0xFF); + array[2] = (byte)((i >> 8) & 0xFF); + array[1] = (byte)((i >> 16) & 0xFF); + array[0] = (byte)((i >> 24) & 0xFF); + + return array; + } + + private static int toByte(char c) + { + if (c >= '0' && c <= '9') return (c - '0'); + if (c >= 'A' && c <= 'F') return (c - 'A' + 10); + if (c >= 'a' && c <= 'f') return (c - 'a' + 10); + + throw new RuntimeException ("Invalid hex char '" + c + "'"); + } + + public static byte[] hexStringToByteArray(String hexString) + { + int length = hexString.length(); + byte[] buffer = new byte[length / 2]; + + for (int i = 0 ; i < length ; i += 2) + { + buffer[i / 2] = (byte)((toByte(hexString.charAt(i)) << 4) | toByte(hexString.charAt(i+1))); + } + + return buffer; + } +} diff --git a/src/com/cyanogenmod/filemanager/util/MimeTypeHelper.java b/Backbone/src/main/java/me/toolify/backbone/util/MimeTypeHelper.java similarity index 78% rename from src/com/cyanogenmod/filemanager/util/MimeTypeHelper.java rename to Backbone/src/main/java/me/toolify/backbone/util/MimeTypeHelper.java index 5a6f8689b..effae5c69 100644 --- a/src/com/cyanogenmod/filemanager/util/MimeTypeHelper.java +++ b/Backbone/src/main/java/me/toolify/backbone/util/MimeTypeHelper.java @@ -14,26 +14,27 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.util; +package me.toolify.backbone.util; import android.content.Context; import android.content.res.Resources; import android.text.TextUtils; import android.util.Log; -import com.cyanogenmod.filemanager.R; -import com.cyanogenmod.filemanager.model.BlockDevice; -import com.cyanogenmod.filemanager.model.CharacterDevice; -import com.cyanogenmod.filemanager.model.Directory; -import com.cyanogenmod.filemanager.model.DomainSocket; -import com.cyanogenmod.filemanager.model.FileSystemObject; -import com.cyanogenmod.filemanager.model.NamedPipe; -import com.cyanogenmod.filemanager.model.Symlink; -import com.cyanogenmod.filemanager.model.SystemFile; +import me.toolify.backbone.R; +import me.toolify.backbone.model.BlockDevice; +import me.toolify.backbone.model.CharacterDevice; +import me.toolify.backbone.model.Directory; +import me.toolify.backbone.model.DomainSocket; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.model.NamedPipe; +import me.toolify.backbone.model.Symlink; +import me.toolify.backbone.model.SystemFile; import java.io.File; import java.util.Enumeration; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import java.util.Properties; @@ -120,6 +121,7 @@ private static class MimeTypeInfo { public MimeTypeCategory mCategory; public String mMimeType; public String mDrawable; + public String mIsDynamic; /** * {@inheritDoc} @@ -134,6 +136,8 @@ public int hashCode() { + ((this.mDrawable == null) ? 0 : this.mDrawable.hashCode()); result = prime * result + ((this.mMimeType == null) ? 0 : this.mMimeType.hashCode()); + result = prime * result + + ((this.mIsDynamic == null) ? 0 : this.mIsDynamic.hashCode()); return result; } @@ -161,6 +165,11 @@ public boolean equals(Object obj) { return false; } else if (!this.mMimeType.equals(other.mMimeType)) return false; + if (this.mIsDynamic == null) { + if (other.mIsDynamic != null) + return false; + } else if (!this.mIsDynamic.equals(other.mIsDynamic)) + return false; return true; } @@ -171,7 +180,8 @@ public boolean equals(Object obj) { public String toString() { return "MimeTypeInfo [mCategory=" + this.mCategory + //$NON-NLS-1$ ", mMimeType="+ this.mMimeType + //$NON-NLS-1$ - ", mDrawable=" + this.mDrawable + "]"; //$NON-NLS-1$ //$NON-NLS-2$ + ", mDrawable=" + this.mDrawable + //$NON-NLS-1$ + ", mIsDynamic" + this.mIsDynamic + "]"; //$NON-NLS-1$ //$NON-NLS-2$ } } @@ -246,7 +256,7 @@ public static final String getIcon(Context context, FileSystemObject fso) { //Get the extension and delivery String ext = FileHelper.getExtension(fso); if (ext != null) { - MimeTypeInfo mimeTypeInfo = sMimeTypes.get(ext.toLowerCase()); + MimeTypeInfo mimeTypeInfo = sMimeTypes.get(ext.toLowerCase(Locale.ROOT)); if (mimeTypeInfo != null) { // Create a new drawable if (!TextUtils.isEmpty(mimeTypeInfo.mDrawable)) { @@ -292,16 +302,13 @@ public static final String getMimeType(Context context, FileSystemObject fso) { loadMimeTypes(context); } - //Get the extension and delivery - String ext = FileHelper.getExtension(fso); - if (ext != null) { - //Load from the database of mime types - MimeTypeInfo mimeTypeInfo = sMimeTypes.get(ext.toLowerCase()); - if (mimeTypeInfo != null) { - return mimeTypeInfo.mMimeType; - } + //Directories don't have a mime type + if (FileHelper.isDirectory(fso)) { + return null; } - return null; + + //Get the extension and delivery + return getMimeTypeFromExtension(fso); } /** @@ -342,17 +349,29 @@ public static final String getMimeTypeDescription(Context context, FileSystemObj } //Get the extension and delivery - String ext = FileHelper.getExtension(fso); - if (ext != null) { - //Load from the database of mime types - MimeTypeInfo mimeTypeInfo = sMimeTypes.get(ext.toLowerCase()); - if (mimeTypeInfo != null) { - return mimeTypeInfo.mMimeType; - } + String mime = getMimeTypeFromExtension(fso); + if (mime != null) { + return mime; } + return res.getString(R.string.mime_unknown); } + private static final String getMimeTypeFromExtension(final FileSystemObject fso) { + String ext = FileHelper.getExtension(fso); + if (ext == null) { + return null; + } + + //Load from the database of mime types + MimeTypeInfo mimeTypeInfo = sMimeTypes.get(ext.toLowerCase(Locale.ROOT)); + if (mimeTypeInfo == null) { + return null; + } + + return mimeTypeInfo.mMimeType; + } + /** * Method that returns the mime/type category of the file. * @@ -372,7 +391,7 @@ public static final MimeTypeCategory getCategoryFromExt(Context context, String } if (ext != null) { //Load from the database of mime types - MimeTypeInfo mimeTypeInfo = sMimeTypes.get(ext.toLowerCase()); + MimeTypeInfo mimeTypeInfo = sMimeTypes.get(ext.toLowerCase(Locale.ROOT)); if (mimeTypeInfo != null) { return mimeTypeInfo.mCategory; } @@ -406,17 +425,7 @@ public static final MimeTypeCategory getCategory(Context context, File file) { } //Get the extension and delivery - String ext = FileHelper.getExtension(file.getName()); - if (ext != null) { - //Load from the database of mime types - MimeTypeInfo mimeTypeInfo = sMimeTypes.get(ext.toLowerCase()); - if (mimeTypeInfo != null) { - return mimeTypeInfo.mCategory; - } - } - - // No category - return MimeTypeCategory.NONE; + return getCategoryFromExt(context, FileHelper.getExtension(file.getName())); } /** @@ -446,21 +455,15 @@ public static final MimeTypeCategory getCategory(Context context, FileSystemObje } //Get the extension and delivery - String ext = FileHelper.getExtension(fso); - if (ext != null) { - //Load from the database of mime types - MimeTypeInfo mimeTypeInfo = sMimeTypes.get(ext.toLowerCase()); - if (mimeTypeInfo != null) { - return mimeTypeInfo.mCategory; - } - } + final MimeTypeCategory category = getCategoryFromExt(context, + FileHelper.getExtension(fso)); + // Check system file - if (fso instanceof SystemFile) { + if (category == MimeTypeCategory.NONE && fso instanceof SystemFile) { return MimeTypeCategory.SYSTEM; } - // No category - return MimeTypeCategory.NONE; + return category; } /** @@ -476,7 +479,7 @@ public static final String getCategoryDescription( return "-"; //$NON-NLS-1$ } try { - String id = "category_" + category.toString().toLowerCase(); //$NON-NLS-1$ + String id = "category_" + category.toString().toLowerCase(Locale.ROOT); //$NON-NLS-1$ int resid = ResourcesHelper.getIdentifier( context.getResources(), "string", id); //$NON-NLS-1$ return context.getString(resid); @@ -484,6 +487,37 @@ public static final String getCategoryDescription( return "-"; //$NON-NLS-1$ } + /** + * Method that returns whether or not the icon of the {@link FileSystemObject} is dynamic, + * e.g. an apk file with its own icon or an image file with a thumbnail. + * + * @param context The current context + * @param fso The file system object + * @return boolean Whether or not the file has a dynamic icon + */ + public static final boolean getIsDynamic(Context context, FileSystemObject fso) { + //Ensure that mime types are loaded + if (sMimeTypes == null) { + loadMimeTypes(context); + } + + //Directories don't have a mime type + if (FileHelper.isDirectory(fso)) { + return false; + } + + //Get the extension and delivery + String ext = FileHelper.getExtension(fso); + if (ext != null) { + //Load from the database of mime types + MimeTypeInfo mimeTypeInfo = sMimeTypes.get(ext.toLowerCase()); + if (mimeTypeInfo != null) { + return mimeTypeInfo.mIsDynamic.equals("Y"); + } + } + return false; + } + /** * Method that returns if a file system object matches with a mime-type expression. * @@ -513,7 +547,7 @@ public static synchronized void loadMimeTypes(Context context) { mimeTypes.load(context.getResources().openRawResource(R.raw.mime_types)); // Parse the properties to an in-memory structure - // Format: = | | + // Format: = | | | sMimeTypes = new HashMap(mimeTypes.size()); Enumeration e = mimeTypes.keys(); while (e.hasMoreElements()) { @@ -527,6 +561,7 @@ public static synchronized void loadMimeTypes(Context context) { mimeTypeInfo.mCategory = MimeTypeCategory.valueOf(mimeData[0].trim()); mimeTypeInfo.mMimeType = mimeData[1].trim(); mimeTypeInfo.mDrawable = mimeData[2].trim(); + mimeTypeInfo.mIsDynamic = mimeData[3].trim(); sMimeTypes.put(extension, mimeTypeInfo); } catch (Exception e2) { /**NON BLOCK**/} @@ -548,4 +583,44 @@ private static String convertToRegExp(String mimeTypeExpression) { return mimeTypeExpression.replaceAll("\\*", ".\\*"); //$NON-NLS-1$ //$NON-NLS-2$ } + + /** + * Class for resolve known mime types + */ + public static final class KnownMimeTypeResolver { + private static final String MIME_TYPE_APK = "application/vnd.android.package-archive"; + + /** + * Method that returns if the FileSystemObject is an Android app. + * + * @param context The current context + * @param fso The FileSystemObject to check + * @return boolean If the FileSystemObject is an Android app. + */ + public static boolean isAndroidApp(Context context, FileSystemObject fso) { + return MIME_TYPE_APK.equals(MimeTypeHelper.getMimeType(context, fso)); + } + + /** + * Method that returns if the FileSystemObject is an image file. + * + * @param context The current context + * @param fso The FileSystemObject to check + * @return boolean If the FileSystemObject is an image file. + */ + public static boolean isImage(Context context, FileSystemObject fso) { + return MimeTypeHelper.getCategory(context, fso).compareTo(MimeTypeCategory.IMAGE) == 0; + } + + /** + * Method that returns if the FileSystemObject is an video file. + * + * @param context The current context + * @param fso The FileSystemObject to check + * @return boolean If the FileSystemObject is an video file. + */ + public static boolean isVideo(Context context, FileSystemObject fso) { + return MimeTypeHelper.getCategory(context, fso).compareTo(MimeTypeCategory.VIDEO) == 0; + } + } } diff --git a/src/com/cyanogenmod/filemanager/util/MountPointHelper.java b/Backbone/src/main/java/me/toolify/backbone/util/MountPointHelper.java similarity index 95% rename from src/com/cyanogenmod/filemanager/util/MountPointHelper.java rename to Backbone/src/main/java/me/toolify/backbone/util/MountPointHelper.java index e2e7b4c54..16e3b3133 100644 --- a/src/com/cyanogenmod/filemanager/util/MountPointHelper.java +++ b/Backbone/src/main/java/me/toolify/backbone/util/MountPointHelper.java @@ -13,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.cyanogenmod.filemanager.util; +package me.toolify.backbone.util; import android.util.Log; -import com.cyanogenmod.filemanager.FileManagerApplication; -import com.cyanogenmod.filemanager.commands.MountExecutable; -import com.cyanogenmod.filemanager.console.Console; -import com.cyanogenmod.filemanager.model.DiskUsage; -import com.cyanogenmod.filemanager.model.FileSystemObject; -import com.cyanogenmod.filemanager.model.MountPoint; +import me.toolify.backbone.FileManagerApplication; +import me.toolify.backbone.commands.MountExecutable; +import me.toolify.backbone.console.Console; +import me.toolify.backbone.model.DiskUsage; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.model.MountPoint; import java.util.Arrays; import java.util.Collections; diff --git a/src/com/cyanogenmod/filemanager/util/ParseHelper.java b/Backbone/src/main/java/me/toolify/backbone/util/ParseHelper.java similarity index 95% rename from src/com/cyanogenmod/filemanager/util/ParseHelper.java rename to Backbone/src/main/java/me/toolify/backbone/util/ParseHelper.java index b593875a6..8cc868ae2 100644 --- a/src/com/cyanogenmod/filemanager/util/ParseHelper.java +++ b/Backbone/src/main/java/me/toolify/backbone/util/ParseHelper.java @@ -14,25 +14,25 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.util; - -import com.cyanogenmod.filemanager.model.BlockDevice; -import com.cyanogenmod.filemanager.model.CharacterDevice; -import com.cyanogenmod.filemanager.model.Directory; -import com.cyanogenmod.filemanager.model.DiskUsage; -import com.cyanogenmod.filemanager.model.DomainSocket; -import com.cyanogenmod.filemanager.model.FileSystemObject; -import com.cyanogenmod.filemanager.model.Group; -import com.cyanogenmod.filemanager.model.GroupPermission; -import com.cyanogenmod.filemanager.model.MountPoint; -import com.cyanogenmod.filemanager.model.NamedPipe; -import com.cyanogenmod.filemanager.model.OthersPermission; -import com.cyanogenmod.filemanager.model.Permission; -import com.cyanogenmod.filemanager.model.Permissions; -import com.cyanogenmod.filemanager.model.RegularFile; -import com.cyanogenmod.filemanager.model.Symlink; -import com.cyanogenmod.filemanager.model.User; -import com.cyanogenmod.filemanager.model.UserPermission; +package me.toolify.backbone.util; + +import me.toolify.backbone.model.BlockDevice; +import me.toolify.backbone.model.CharacterDevice; +import me.toolify.backbone.model.Directory; +import me.toolify.backbone.model.DiskUsage; +import me.toolify.backbone.model.DomainSocket; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.model.Group; +import me.toolify.backbone.model.GroupPermission; +import me.toolify.backbone.model.MountPoint; +import me.toolify.backbone.model.NamedPipe; +import me.toolify.backbone.model.OthersPermission; +import me.toolify.backbone.model.Permission; +import me.toolify.backbone.model.Permissions; +import me.toolify.backbone.model.RegularFile; +import me.toolify.backbone.model.Symlink; +import me.toolify.backbone.model.User; +import me.toolify.backbone.model.UserPermission; import java.io.File; import java.text.ParseException; diff --git a/src/com/cyanogenmod/filemanager/util/ResourcesHelper.java b/Backbone/src/main/java/me/toolify/backbone/util/ResourcesHelper.java similarity index 95% rename from src/com/cyanogenmod/filemanager/util/ResourcesHelper.java rename to Backbone/src/main/java/me/toolify/backbone/util/ResourcesHelper.java index e561c4768..697dcd4ad 100644 --- a/src/com/cyanogenmod/filemanager/util/ResourcesHelper.java +++ b/Backbone/src/main/java/me/toolify/backbone/util/ResourcesHelper.java @@ -14,11 +14,11 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.util; +package me.toolify.backbone.util; import android.content.res.Resources; -import com.cyanogenmod.filemanager.R; +import me.toolify.backbone.R; import java.lang.reflect.Field; diff --git a/src/com/cyanogenmod/filemanager/util/SearchHelper.java b/Backbone/src/main/java/me/toolify/backbone/util/SearchHelper.java similarity index 96% rename from src/com/cyanogenmod/filemanager/util/SearchHelper.java rename to Backbone/src/main/java/me/toolify/backbone/util/SearchHelper.java index 90fbb81fd..9131c8f44 100644 --- a/src/com/cyanogenmod/filemanager/util/SearchHelper.java +++ b/Backbone/src/main/java/me/toolify/backbone/util/SearchHelper.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.util; +package me.toolify.backbone.util; import android.graphics.Typeface; import android.text.Spannable; @@ -22,12 +22,13 @@ import android.text.style.BackgroundColorSpan; import android.text.style.StyleSpan; -import com.cyanogenmod.filemanager.model.FileSystemObject; -import com.cyanogenmod.filemanager.model.Query; -import com.cyanogenmod.filemanager.model.SearchResult; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.model.Query; +import me.toolify.backbone.model.SearchResult; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -68,8 +69,8 @@ public static String toIgnoreCaseRegExp(final String query, boolean javaRegExp) } //Convert the string to lower and upper - final String lowerCase = q.toLowerCase(); - final String upperCase = q.toUpperCase(); + final String lowerCase = q.toLowerCase(Locale.ROOT); + final String upperCase = q.toUpperCase(Locale.ROOT); //Create the regular expression filter StringBuffer sb = new StringBuffer(); diff --git a/src/com/cyanogenmod/filemanager/util/SelectionHelper.java b/Backbone/src/main/java/me/toolify/backbone/util/SelectionHelper.java similarity index 93% rename from src/com/cyanogenmod/filemanager/util/SelectionHelper.java rename to Backbone/src/main/java/me/toolify/backbone/util/SelectionHelper.java index 3648c9473..969822cea 100644 --- a/src/com/cyanogenmod/filemanager/util/SelectionHelper.java +++ b/Backbone/src/main/java/me/toolify/backbone/util/SelectionHelper.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.util; +package me.toolify.backbone.util; -import com.cyanogenmod.filemanager.model.FileSystemObject; +import me.toolify.backbone.model.FileSystemObject; import java.util.List; diff --git a/src/com/cyanogenmod/filemanager/util/ShellHelper.java b/Backbone/src/main/java/me/toolify/backbone/util/ShellHelper.java similarity index 95% rename from src/com/cyanogenmod/filemanager/util/ShellHelper.java rename to Backbone/src/main/java/me/toolify/backbone/util/ShellHelper.java index e497bcf61..39476bcc9 100644 --- a/src/com/cyanogenmod/filemanager/util/ShellHelper.java +++ b/Backbone/src/main/java/me/toolify/backbone/util/ShellHelper.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.cyanogenmod.filemanager.util; +package me.toolify.backbone.util; -import com.cyanogenmod.filemanager.commands.shell.Command; +import me.toolify.backbone.commands.shell.Command; /** * A helper class with useful methods for deal with linux shells. diff --git a/Backbone/src/main/java/me/toolify/backbone/util/StorageHelper.java b/Backbone/src/main/java/me/toolify/backbone/util/StorageHelper.java new file mode 100644 index 000000000..3e00b488b --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/util/StorageHelper.java @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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 me.toolify.backbone.util; + +import android.content.Context; +import android.os.storage.StorageManager; +import android.util.Log; + +import java.io.File; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import me.toolify.backbone.FileManagerApplication; +import me.toolify.backbone.R; +import me.toolify.backbone.model.DiskUsage; +import me.toolify.backbone.model.FileSystemObject; +import me.toolify.backbone.model.MountPoint; +import me.toolify.backbone.model.StorageVolume; + + +/** + * A helper class with useful methods for deal with storages. + */ +public final class StorageHelper { + private final static String TAG = "StorageHelper"; + private final static boolean DEBUG = true; + + private static Object[] sStorageVolumes; + + /** + * Method that returns the storage volumes defined in the system. This method uses + * reflection to retrieve the method because CM10 has a {@link Context} + * as first parameter, that AOSP hasn't. + * + * @param ctx The current context + * @return StorageVolume[] The storage volumes defined in the system + */ + @SuppressWarnings("boxing") + public static synchronized Object[] getStorageVolumes(Context ctx) { + if (sStorageVolumes == null) { + + //IMP!! Android SDK doesn't have a "getVolumeList" but is supported by CM10. + //Use reflect to get this value (if possible) + try { + StorageManager sm = (StorageManager) ctx.getSystemService(Context.STORAGE_SERVICE); + Method method = sm.getClass().getMethod("getVolumeList"); //$NON-NLS-1$ + List volumes = new ArrayList(); + for(Object o : (Object[])method.invoke(sm)) + { + String path = getStoragePath(o); + DiskUsage du = CommandHelper.getDiskUsage(ctx, path, null); + if(du == null) continue; + if(du.getTotal() <= 0) continue; // Ensure validity by checking for disk space + volumes.add(o); + } + if(volumes.size() == 0) throw new Exception("No valid volumes"); + return volumes.toArray(); + + } catch (Exception ex) { + //Ignore. Android SDK StorageManager class doesn't have this method + //Use default android information from environment + try { + HashMap lst = new HashMap(); + //lst.add(convertToStorageVolume(getInternalStorageDirectory())); + for(MountPoint mp : CommandHelper.getMountPoints(ctx, null)) + { + if(!MountPointHelper.isReadWrite(mp)) continue; + String path = mp.getMountPoint(); + if(!mp.getDevice().startsWith("/")) continue; + if(path.matches("/(dev|persist|cache|proc|efs|data|acct|sys)/.*")) + continue; + if(path.matches("/(dev|persist|cache|proc|efs|data|acct|sys)")) + continue; + StorageVolume sv = null; + try { + FileSystemObject fso = CommandHelper.getFileInfo(ctx, path, null); + sv = convertToStorageVolume(fso); + } catch(Exception e3) { } + if(sv == null) continue; + if(!lst.containsKey(mp.getDevice())) // Only one volume per device + lst.put(mp.getDevice(), sv); + } + sStorageVolumes = lst.values().toArray(new StorageVolume[lst.size()]); + } catch (Exception ex2) { + if(DEBUG) + Log.e(TAG, "Unable to fallback to getStorageVolumes", ex2); + } + } + if (sStorageVolumes == null) { + sStorageVolumes = new StorageVolume[]{}; + } + } + return sStorageVolumes; + } + + /** + * Method that returns a {@link me.toolify.backbone.model.StorageVolume} + * based on a supplied {@link me.toolify.backbone.model.FileSystemObject} + * + * @param fso the fso object to be converted to a StorageVolume + * @return + */ + private static StorageVolume convertToStorageVolume(FileSystemObject fso) { + String path = fso.getFullPath(); + int description = R.string.internal_storage; + if (path.toLowerCase().indexOf("usb") != -1) //$NON-NLS-1$ + description = R.string.usb_storage; + else if(path.toLowerCase().indexOf("ext") > -1 || + path.indexOf("1") > -1) + description = R.string.external_storage; + return new StorageVolume(FileHelper.getFile(fso), description, false, + description == R.string.usb_storage, false, 0, false, 0, null); + } + + /** + * Method that returns the storage volume description. This method uses + * reflection to retrieve the description because CM10 has a {@link Context} + * as first parameter, that AOSP hasn't. + * + * @param ctx The current context + * @param volume The storage volume + * @return String The description of the storage volume + */ + public static String getStorageVolumeDescription(Context ctx, Object volume) { + try { + Method m = volume.getClass().getMethod("getDescription", Context.class); + return (String)m.invoke(volume, ctx); + } catch (Throwable _throw) { + // Returns the volume storage path + return getStoragePath(volume); + } + } + + /** + * Method that returns if the path is in a volume storage + * + * @param path The path + * @return boolean If the path is in a volume storage + */ + public static boolean isPathInStorageVolume(String path) { + String fso = FileHelper.getAbsPath(path); + Object[] volumes = + getStorageVolumes(FileManagerApplication.getInstance().getApplicationContext()); + int cc = volumes.length; + for (int i = 0; i < cc; i++) { + Object vol = volumes[i]; + if (fso.startsWith(getStoragePath(vol))) { + return true; + } + } + return false; + } + + /** + * Method that determines the mount path of a {@link StorageVolume} object. + * + * @param volume Object returned from StorageHelper.getStorageVolumes() + * @return Mounted path + */ + public static String getStoragePath(Object volume) { + try { + Method gpm = volume.getClass().getMethod("getPath"); + Object gpo = gpm.invoke(volume); + return String.valueOf(gpo); + } catch (Exception e) { + if(volume instanceof String) return (String)volume; + e.printStackTrace(); + } + return null; + } + + /** + * Method that determines whether a path returned from + * getStorageVolumes is a valid, mounted, path. + * @param path Path to root of mount + * @return boolean Whether path is mounted & valid + */ + public static boolean isValidMount(String path) { + MountPoint mp = MountPointHelper.getMountPointFromDirectory(path); + if(mp == null) return false; + if(mp.getOptions() == null) return false; + String opts = mp.getOptions(); + if(opts != null && opts.indexOf("mode=0")>-1) + return false; // Skip drives that are not owner read/write + return true; + } + + /** + * Method that returns if the path is a storage volume + * + * @param path The path + * @return boolean If the path is a storage volume + */ + public static boolean isStorageVolume(String path) { + Object[] volumes = + getStorageVolumes(FileManagerApplication.getInstance().getApplicationContext()); + int cc = volumes.length; + for (int i = 0; i < cc; i++) { + Object vol = volumes[i]; + String p = new File(path).getAbsolutePath(); + String v = new File(getStoragePath(vol)).getAbsolutePath(); + if (p.compareTo(v) == 0) { + return true; + } + } + return false; + } + + /** + * Method that return the chrooted path of an absolute path. xe: /storage/sdcard0 --> sdcard0. + * + * @param path The path + * @return String The chrooted path + */ + public static String getChrootedPath(String path) { + Object[] volumes = + getStorageVolumes(FileManagerApplication.getInstance().getApplicationContext()); + int cc = volumes.length; + for (int i = 0; i < cc; i++) { + Object vol = volumes[i]; + File p = new File(path); + File v = new File(getStoragePath(vol)); + if (p.getAbsolutePath().startsWith(v.getAbsolutePath())) { + return v.getName() + path.substring(v.getAbsolutePath().length()); + } + } + return null; + } + +} diff --git a/Backbone/src/main/java/me/toolify/backbone/util/XmlUtils.java b/Backbone/src/main/java/me/toolify/backbone/util/XmlUtils.java new file mode 100644 index 000000000..489f688c0 --- /dev/null +++ b/Backbone/src/main/java/me/toolify/backbone/util/XmlUtils.java @@ -0,0 +1,900 @@ + +/* + * Copyright (C) 2006 The Android Open Source Project + * + * 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 me.toolify.backbone.util; + + +import android.util.Xml; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.*; + +/** {@hide} */ +public class XmlUtils +{ + + public static void skipCurrentTag(XmlPullParser parser) + throws XmlPullParserException, IOException { + int outerDepth = parser.getDepth(); + int type; + while ((type=parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG + || parser.getDepth() > outerDepth)) { + } + } + + public static final int + convertValueToList(CharSequence value, String[] options, int defaultValue) + { + if (null != value) { + for (int i = 0; i < options.length; i++) { + if (value.equals(options[i])) + return i; + } + } + + return defaultValue; + } + + public static final boolean + convertValueToBoolean(CharSequence value, boolean defaultValue) + { + boolean result = false; + + if (null == value) + return defaultValue; + + if (value.equals("1") + || value.equals("true") + || value.equals("TRUE")) + result = true; + + return result; + } + + public static final int + convertValueToInt(CharSequence charSeq, int defaultValue) + { + if (null == charSeq) + return defaultValue; + + String nm = charSeq.toString(); + + // XXX This code is copied from Integer.decode() so we don't + // have to instantiate an Integer! + + int value; + int sign = 1; + int index = 0; + int len = nm.length(); + int base = 10; + + if ('-' == nm.charAt(0)) { + sign = -1; + index++; + } + + if ('0' == nm.charAt(index)) { + // Quick check for a zero by itself + if (index == (len - 1)) + return 0; + + char c = nm.charAt(index + 1); + + if ('x' == c || 'X' == c) { + index += 2; + base = 16; + } else { + index++; + base = 8; + } + } + else if ('#' == nm.charAt(index)) + { + index++; + base = 16; + } + + return Integer.parseInt(nm.substring(index), base) * sign; + } + + public static final int + convertValueToUnsignedInt(String value, int defaultValue) + { + if (null == value) + return defaultValue; + + return parseUnsignedIntAttribute(value); + } + + public static final int + parseUnsignedIntAttribute(CharSequence charSeq) + { + String value = charSeq.toString(); + + long bits; + int index = 0; + int len = value.length(); + int base = 10; + + if ('0' == value.charAt(index)) { + // Quick check for zero by itself + if (index == (len - 1)) + return 0; + + char c = value.charAt(index + 1); + + if ('x' == c || 'X' == c) { // check for hex + index += 2; + base = 16; + } else { // check for octal + index++; + base = 8; + } + } else if ('#' == value.charAt(index)) { + index++; + base = 16; + } + + return (int) Long.parseLong(value.substring(index), base); + } + + /** + * Flatten a Map into an output stream as XML. The map can later be + * read back with readMapXml(). + * + * @param val The map to be flattened. + * @param out Where to write the XML data. + * + * @see #writeMapXml(Map, String, XmlSerializer) + * @see #writeListXml + * @see #writeValueXml + * @see #readMapXml + */ + public static final void writeMapXml(Map val, OutputStream out) + throws XmlPullParserException, java.io.IOException { + XmlSerializer serializer = new FastXmlSerializer(); + serializer.setOutput(out, "utf-8"); + serializer.startDocument(null, true); + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + writeMapXml(val, null, serializer); + serializer.endDocument(); + } + + /** + * Flatten a List into an output stream as XML. The list can later be + * read back with readListXml(). + * + * @param val The list to be flattened. + * @param out Where to write the XML data. + * + * @see #writeListXml(List, String, XmlSerializer) + * @see #writeMapXml + * @see #writeValueXml + * @see #readListXml + */ + public static final void writeListXml(List val, OutputStream out) + throws XmlPullParserException, java.io.IOException + { + XmlSerializer serializer = Xml.newSerializer(); + serializer.setOutput(out, "utf-8"); + serializer.startDocument(null, true); + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + writeListXml(val, null, serializer); + serializer.endDocument(); + } + + /** + * Flatten a Map into an XmlSerializer. The map can later be read back + * with readThisMapXml(). + * + * @param val The map to be flattened. + * @param name Name attribute to include with this list's tag, or null for + * none. + * @param out XmlSerializer to write the map into. + * + * @see #writeMapXml(Map, OutputStream) + * @see #writeListXml + * @see #writeValueXml + * @see #readMapXml + */ + public static final void writeMapXml(Map val, String name, XmlSerializer out) + throws XmlPullParserException, java.io.IOException + { + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + + Set s = val.entrySet(); + Iterator i = s.iterator(); + + out.startTag(null, "map"); + if (name != null) { + out.attribute(null, "name", name); + } + + while (i.hasNext()) { + Map.Entry e = (Map.Entry)i.next(); + writeValueXml(e.getValue(), (String)e.getKey(), out); + } + + out.endTag(null, "map"); + } + + /** + * Flatten a List into an XmlSerializer. The list can later be read back + * with readThisListXml(). + * + * @param val The list to be flattened. + * @param name Name attribute to include with this list's tag, or null for + * none. + * @param out XmlSerializer to write the list into. + * + * @see #writeListXml(List, OutputStream) + * @see #writeMapXml + * @see #writeValueXml + * @see #readListXml + */ + public static final void writeListXml(List val, String name, XmlSerializer out) + throws XmlPullParserException, java.io.IOException + { + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + + out.startTag(null, "list"); + if (name != null) { + out.attribute(null, "name", name); + } + + int N = val.size(); + int i=0; + while (i < N) { + writeValueXml(val.get(i), null, out); + i++; + } + + out.endTag(null, "list"); + } + + public static final void writeSetXml(Set val, String name, XmlSerializer out) + throws XmlPullParserException, java.io.IOException { + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + + out.startTag(null, "set"); + if (name != null) { + out.attribute(null, "name", name); + } + + for (Object v : val) { + writeValueXml(v, null, out); + } + + out.endTag(null, "set"); + } + + /** + * Flatten a byte[] into an XmlSerializer. The list can later be read back + * with readThisByteArrayXml(). + * + * @param val The byte array to be flattened. + * @param name Name attribute to include with this array's tag, or null for + * none. + * @param out XmlSerializer to write the array into. + * + * @see #writeMapXml + * @see #writeValueXml + */ + public static final void writeByteArrayXml(byte[] val, String name, + XmlSerializer out) + throws XmlPullParserException, java.io.IOException { + + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + + out.startTag(null, "byte-array"); + if (name != null) { + out.attribute(null, "name", name); + } + + final int N = val.length; + out.attribute(null, "num", Integer.toString(N)); + + StringBuilder sb = new StringBuilder(val.length*2); + for (int i=0; i>4; + sb.append(h >= 10 ? ('a'+h-10) : ('0'+h)); + h = b&0xff; + sb.append(h >= 10 ? ('a'+h-10) : ('0'+h)); + } + + out.text(sb.toString()); + + out.endTag(null, "byte-array"); + } + + /** + * Flatten an int[] into an XmlSerializer. The list can later be read back + * with readThisIntArrayXml(). + * + * @param val The int array to be flattened. + * @param name Name attribute to include with this array's tag, or null for + * none. + * @param out XmlSerializer to write the array into. + * + * @see #writeMapXml + * @see #writeValueXml + * @see #readThisIntArrayXml + */ + public static final void writeIntArrayXml(int[] val, String name, + XmlSerializer out) + throws XmlPullParserException, java.io.IOException { + + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + + out.startTag(null, "int-array"); + if (name != null) { + out.attribute(null, "name", name); + } + + final int N = val.length; + out.attribute(null, "num", Integer.toString(N)); + + for (int i=0; iafter the tag that begins the map. + * + * @param parser The XmlPullParser from which to read the map data. + * @param endTag Name of the tag that will end the map, usually "map". + * @param name An array of one string, used to return the name attribute + * of the map's tag. + * + * @return HashMap The newly generated map. + * + * @see #readMapXml + */ + public static final HashMap readThisMapXml(XmlPullParser parser, String endTag, String[] name) + throws XmlPullParserException, java.io.IOException + { + HashMap map = new HashMap(); + + int eventType = parser.getEventType(); + do { + if (eventType == parser.START_TAG) { + Object val = readThisValueXml(parser, name); + if (name[0] != null) { + //System.out.println("Adding to map: " + name + " -> " + val); + map.put(name[0], val); + } else { + throw new XmlPullParserException( + "Map value without name attribute: " + parser.getName()); + } + } else if (eventType == parser.END_TAG) { + if (parser.getName().equals(endTag)) { + return map; + } + throw new XmlPullParserException( + "Expected " + endTag + " end tag at: " + parser.getName()); + } + eventType = parser.next(); + } while (eventType != parser.END_DOCUMENT); + + throw new XmlPullParserException( + "Document ended before " + endTag + " end tag"); + } + + /** + * Read an ArrayList object from an XmlPullParser. The XML data could + * previously have been generated by writeListXml(). The XmlPullParser + * must be positioned after the tag that begins the list. + * + * @param parser The XmlPullParser from which to read the list data. + * @param endTag Name of the tag that will end the list, usually "list". + * @param name An array of one string, used to return the name attribute + * of the list's tag. + * + * @return HashMap The newly generated list. + * + * @see #readListXml + */ + public static final ArrayList readThisListXml(XmlPullParser parser, String endTag, String[] name) + throws XmlPullParserException, java.io.IOException + { + ArrayList list = new ArrayList(); + + int eventType = parser.getEventType(); + do { + if (eventType == parser.START_TAG) { + Object val = readThisValueXml(parser, name); + list.add(val); + //System.out.println("Adding to list: " + val); + } else if (eventType == parser.END_TAG) { + if (parser.getName().equals(endTag)) { + return list; + } + throw new XmlPullParserException( + "Expected " + endTag + " end tag at: " + parser.getName()); + } + eventType = parser.next(); + } while (eventType != parser.END_DOCUMENT); + + throw new XmlPullParserException( + "Document ended before " + endTag + " end tag"); + } + + /** + * Read a HashSet object from an XmlPullParser. The XML data could previously + * have been generated by writeSetXml(). The XmlPullParser must be positioned + * after the tag that begins the set. + * + * @param parser The XmlPullParser from which to read the set data. + * @param endTag Name of the tag that will end the set, usually "set". + * @param name An array of one string, used to return the name attribute + * of the set's tag. + * + * @return HashSet The newly generated set. + * + * @throws XmlPullParserException + * @throws java.io.IOException + * + * @see #readSetXml + */ + public static final HashSet readThisSetXml(XmlPullParser parser, String endTag, String[] name) + throws XmlPullParserException, java.io.IOException { + HashSet set = new HashSet(); + + int eventType = parser.getEventType(); + do { + if (eventType == parser.START_TAG) { + Object val = readThisValueXml(parser, name); + set.add(val); + //System.out.println("Adding to set: " + val); + } else if (eventType == parser.END_TAG) { + if (parser.getName().equals(endTag)) { + return set; + } + throw new XmlPullParserException( + "Expected " + endTag + " end tag at: " + parser.getName()); + } + eventType = parser.next(); + } while (eventType != parser.END_DOCUMENT); + + throw new XmlPullParserException( + "Document ended before " + endTag + " end tag"); + } + + /** + * Read an int[] object from an XmlPullParser. The XML data could + * previously have been generated by writeIntArrayXml(). The XmlPullParser + * must be positioned after the tag that begins the list. + * + * @param parser The XmlPullParser from which to read the list data. + * @param endTag Name of the tag that will end the list, usually "list". + * @param name An array of one string, used to return the name attribute + * of the list's tag. + * + * @return Returns a newly generated int[]. + * + * @see #readListXml + */ + public static final int[] readThisIntArrayXml(XmlPullParser parser, + String endTag, String[] name) + throws XmlPullParserException, java.io.IOException { + + int num; + try { + num = Integer.parseInt(parser.getAttributeValue(null, "num")); + } catch (NullPointerException e) { + throw new XmlPullParserException( + "Need num attribute in byte-array"); + } catch (NumberFormatException e) { + throw new XmlPullParserException( + "Not a number in num attribute in byte-array"); + } + + int[] array = new int[num]; + int i = 0; + + int eventType = parser.getEventType(); + do { + if (eventType == parser.START_TAG) { + if (parser.getName().equals("item")) { + try { + array[i] = Integer.parseInt( + parser.getAttributeValue(null, "value")); + } catch (NullPointerException e) { + throw new XmlPullParserException( + "Need value attribute in item"); + } catch (NumberFormatException e) { + throw new XmlPullParserException( + "Not a number in value attribute in item"); + } + } else { + throw new XmlPullParserException( + "Expected item tag at: " + parser.getName()); + } + } else if (eventType == parser.END_TAG) { + if (parser.getName().equals(endTag)) { + return array; + } else if (parser.getName().equals("item")) { + i++; + } else { + throw new XmlPullParserException( + "Expected " + endTag + " end tag at: " + + parser.getName()); + } + } + eventType = parser.next(); + } while (eventType != parser.END_DOCUMENT); + + throw new XmlPullParserException( + "Document ended before " + endTag + " end tag"); + } + + /** + * Read a flattened object from an XmlPullParser. The XML data could + * previously have been written with writeMapXml(), writeListXml(), or + * writeValueXml(). The XmlPullParser must be positioned at the + * tag that defines the value. + * + * @param parser The XmlPullParser from which to read the object. + * @param name An array of one string, used to return the name attribute + * of the value's tag. + * + * @return Object The newly generated value object. + * + * @see #readMapXml + * @see #readListXml + * @see #writeValueXml + */ + public static final Object readValueXml(XmlPullParser parser, String[] name) + throws XmlPullParserException, java.io.IOException + { + int eventType = parser.getEventType(); + do { + if (eventType == parser.START_TAG) { + return readThisValueXml(parser, name); + } else if (eventType == parser.END_TAG) { + throw new XmlPullParserException( + "Unexpected end tag at: " + parser.getName()); + } else if (eventType == parser.TEXT) { + throw new XmlPullParserException( + "Unexpected text: " + parser.getText()); + } + eventType = parser.next(); + } while (eventType != parser.END_DOCUMENT); + + throw new XmlPullParserException( + "Unexpected end of document"); + } + + private static final Object readThisValueXml(XmlPullParser parser, String[] name) + throws XmlPullParserException, java.io.IOException + { + final String valueName = parser.getAttributeValue(null, "name"); + final String tagName = parser.getName(); + + //System.out.println("Reading this value tag: " + tagName + ", name=" + valueName); + + Object res; + + if (tagName.equals("null")) { + res = null; + } else if (tagName.equals("string")) { + String value = ""; + int eventType; + while ((eventType = parser.next()) != parser.END_DOCUMENT) { + if (eventType == parser.END_TAG) { + if (parser.getName().equals("string")) { + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + value); + return value; + } + throw new XmlPullParserException( + "Unexpected end tag in : " + parser.getName()); + } else if (eventType == parser.TEXT) { + value += parser.getText(); + } else if (eventType == parser.START_TAG) { + throw new XmlPullParserException( + "Unexpected start tag in : " + parser.getName()); + } + } + throw new XmlPullParserException( + "Unexpected end of document in "); + } else if (tagName.equals("int")) { + res = Integer.parseInt(parser.getAttributeValue(null, "value")); + } else if (tagName.equals("long")) { + res = Long.valueOf(parser.getAttributeValue(null, "value")); + } else if (tagName.equals("float")) { + res = new Float(parser.getAttributeValue(null, "value")); + } else if (tagName.equals("double")) { + res = new Double(parser.getAttributeValue(null, "value")); + } else if (tagName.equals("boolean")) { + res = Boolean.valueOf(parser.getAttributeValue(null, "value")); + } else if (tagName.equals("int-array")) { + parser.next(); + res = readThisIntArrayXml(parser, "int-array", name); + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + res); + return res; + } else if (tagName.equals("map")) { + parser.next(); + res = readThisMapXml(parser, "map", name); + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + res); + return res; + } else if (tagName.equals("list")) { + parser.next(); + res = readThisListXml(parser, "list", name); + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + res); + return res; + } else if (tagName.equals("set")) { + parser.next(); + res = readThisSetXml(parser, "set", name); + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + res); + return res; + } else { + throw new XmlPullParserException( + "Unknown tag: " + tagName); + } + + // Skip through to end tag. + int eventType; + while ((eventType = parser.next()) != parser.END_DOCUMENT) { + if (eventType == parser.END_TAG) { + if (parser.getName().equals(tagName)) { + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + res); + return res; + } + throw new XmlPullParserException( + "Unexpected end tag in <" + tagName + ">: " + parser.getName()); + } else if (eventType == parser.TEXT) { + throw new XmlPullParserException( + "Unexpected text in <" + tagName + ">: " + parser.getName()); + } else if (eventType == parser.START_TAG) { + throw new XmlPullParserException( + "Unexpected start tag in <" + tagName + ">: " + parser.getName()); + } + } + throw new XmlPullParserException( + "Unexpected end of document in <" + tagName + ">"); + } + + public static final void beginDocument(XmlPullParser parser, String firstElementName) throws XmlPullParserException, IOException + { + int type; + while ((type=parser.next()) != parser.START_TAG + && type != parser.END_DOCUMENT) { + ; + } + + if (type != parser.START_TAG) { + throw new XmlPullParserException("No start tag found"); + } + + if (!parser.getName().equals(firstElementName)) { + throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() + + ", expected " + firstElementName); + } + } + + public static final void nextElement(XmlPullParser parser) throws XmlPullParserException, IOException + { + int type; + while ((type=parser.next()) != parser.START_TAG + && type != parser.END_DOCUMENT) { + ; + } + } + + public static boolean nextElementWithin(XmlPullParser parser, int outerDepth) + throws IOException, XmlPullParserException { + for (;;) { + int type = parser.next(); + if (type == XmlPullParser.END_DOCUMENT + || (type == XmlPullParser.END_TAG && parser.getDepth() == outerDepth)) { + return false; + } + if (type == XmlPullParser.START_TAG + && parser.getDepth() == outerDepth + 1) { + return true; + } + } + } +} \ No newline at end of file diff --git a/proguard.flags b/Backbone/src/main/proguard.flags similarity index 100% rename from proguard.flags rename to Backbone/src/main/proguard.flags diff --git a/Backbone/src/main/res/anim/hold_in.xml b/Backbone/src/main/res/anim/hold_in.xml new file mode 100644 index 000000000..1dea39723 --- /dev/null +++ b/Backbone/src/main/res/anim/hold_in.xml @@ -0,0 +1,26 @@ + + + + diff --git a/Backbone/src/main/res/anim/hold_out.xml b/Backbone/src/main/res/anim/hold_out.xml new file mode 100644 index 000000000..fad4ec5d9 --- /dev/null +++ b/Backbone/src/main/res/anim/hold_out.xml @@ -0,0 +1,26 @@ + + + + diff --git a/Backbone/src/main/res/anim/translate_to_left_in.xml b/Backbone/src/main/res/anim/translate_to_left_in.xml new file mode 100644 index 000000000..6c6c4e39a --- /dev/null +++ b/Backbone/src/main/res/anim/translate_to_left_in.xml @@ -0,0 +1,27 @@ + + + + + + + + \ No newline at end of file diff --git a/Backbone/src/main/res/anim/translate_to_left_out.xml b/Backbone/src/main/res/anim/translate_to_left_out.xml new file mode 100644 index 000000000..10ef48a4c --- /dev/null +++ b/Backbone/src/main/res/anim/translate_to_left_out.xml @@ -0,0 +1,27 @@ + + + + + + + + \ No newline at end of file diff --git a/Backbone/src/main/res/anim/translate_to_right_in.xml b/Backbone/src/main/res/anim/translate_to_right_in.xml new file mode 100644 index 000000000..505a35526 --- /dev/null +++ b/Backbone/src/main/res/anim/translate_to_right_in.xml @@ -0,0 +1,27 @@ + + + + + + + + \ No newline at end of file diff --git a/Backbone/src/main/res/anim/translate_to_right_out.xml b/Backbone/src/main/res/anim/translate_to_right_out.xml new file mode 100644 index 000000000..e1298775f --- /dev/null +++ b/Backbone/src/main/res/anim/translate_to_right_out.xml @@ -0,0 +1,27 @@ + + + + + + + + \ No newline at end of file diff --git a/Backbone/src/main/res/drawable-hdpi/ab_bottom_solid_holo_dark.9.png b/Backbone/src/main/res/drawable-hdpi/ab_bottom_solid_holo_dark.9.png new file mode 100644 index 000000000..a3abccfbe Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ab_bottom_solid_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ab_solid_holo_dark.9.png b/Backbone/src/main/res/drawable-hdpi/ab_solid_holo_dark.9.png new file mode 100644 index 000000000..d48f9d484 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ab_solid_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ab_stacked_solid_holo_dark.9.png b/Backbone/src/main/res/drawable-hdpi/ab_stacked_solid_holo_dark.9.png new file mode 100644 index 000000000..003e60e3e Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ab_stacked_solid_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ab_texture_tile_holo_dark.png b/Backbone/src/main/res/drawable-hdpi/ab_texture_tile_holo_dark.png new file mode 100644 index 000000000..903e54888 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ab_texture_tile_holo_dark.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ab_transparent_holo_dark.9.png b/Backbone/src/main/res/drawable-hdpi/ab_transparent_holo_dark.9.png new file mode 100644 index 000000000..c58997f11 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ab_transparent_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/btn_cab_done_default_holo_dark.9.png b/Backbone/src/main/res/drawable-hdpi/btn_cab_done_default_holo_dark.9.png new file mode 100644 index 000000000..c8d351eda Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/btn_cab_done_default_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/btn_cab_done_focused_holo_dark.9.png b/Backbone/src/main/res/drawable-hdpi/btn_cab_done_focused_holo_dark.9.png new file mode 100644 index 000000000..d3c46282e Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/btn_cab_done_focused_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/btn_cab_done_pressed_holo_dark.9.png b/Backbone/src/main/res/drawable-hdpi/btn_cab_done_pressed_holo_dark.9.png new file mode 100644 index 000000000..ef1f8f0bd Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/btn_cab_done_pressed_holo_dark.9.png differ diff --git a/themes/res/drawable-hdpi/btn_holo_dark_check_off_normal.png b/Backbone/src/main/res/drawable-hdpi/btn_holo_dark_check_off_normal.png similarity index 100% rename from themes/res/drawable-hdpi/btn_holo_dark_check_off_normal.png rename to Backbone/src/main/res/drawable-hdpi/btn_holo_dark_check_off_normal.png diff --git a/themes/res/drawable-hdpi/btn_holo_dark_check_on_normal.png b/Backbone/src/main/res/drawable-hdpi/btn_holo_dark_check_on_normal.png similarity index 100% rename from themes/res/drawable-hdpi/btn_holo_dark_check_on_normal.png rename to Backbone/src/main/res/drawable-hdpi/btn_holo_dark_check_on_normal.png diff --git a/themes/res/drawable-hdpi/btn_holo_dark_check_on_normal_inverted.png b/Backbone/src/main/res/drawable-hdpi/btn_holo_dark_check_on_normal_inverted.png similarity index 100% rename from themes/res/drawable-hdpi/btn_holo_dark_check_on_normal_inverted.png rename to Backbone/src/main/res/drawable-hdpi/btn_holo_dark_check_on_normal_inverted.png diff --git a/res/drawable-hdpi/btn_holo_light_check_off_normal.png b/Backbone/src/main/res/drawable-hdpi/btn_holo_light_check_off_normal.png similarity index 100% rename from res/drawable-hdpi/btn_holo_light_check_off_normal.png rename to Backbone/src/main/res/drawable-hdpi/btn_holo_light_check_off_normal.png diff --git a/res/drawable-hdpi/btn_holo_light_check_on_normal.png b/Backbone/src/main/res/drawable-hdpi/btn_holo_light_check_on_normal.png similarity index 100% rename from res/drawable-hdpi/btn_holo_light_check_on_normal.png rename to Backbone/src/main/res/drawable-hdpi/btn_holo_light_check_on_normal.png diff --git a/res/drawable-hdpi/btn_holo_light_check_on_normal_inverted.png b/Backbone/src/main/res/drawable-hdpi/btn_holo_light_check_on_normal_inverted.png similarity index 100% rename from res/drawable-hdpi/btn_holo_light_check_on_normal_inverted.png rename to Backbone/src/main/res/drawable-hdpi/btn_holo_light_check_on_normal_inverted.png diff --git a/Backbone/src/main/res/drawable-hdpi/cab_background_bottom_holo_dark.9.png b/Backbone/src/main/res/drawable-hdpi/cab_background_bottom_holo_dark.9.png new file mode 100644 index 000000000..941da90b8 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/cab_background_bottom_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/cab_background_top_holo_dark.9.png b/Backbone/src/main/res/drawable-hdpi/cab_background_top_holo_dark.9.png new file mode 100644 index 000000000..a99853461 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/cab_background_top_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/dashclock_cloud.png b/Backbone/src/main/res/drawable-hdpi/dashclock_cloud.png new file mode 100644 index 000000000..5a1603668 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/dashclock_cloud.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/dashclock_database.png b/Backbone/src/main/res/drawable-hdpi/dashclock_database.png new file mode 100644 index 000000000..0f519e617 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/dashclock_database.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/dashclock_folder.png b/Backbone/src/main/res/drawable-hdpi/dashclock_folder.png new file mode 100644 index 000000000..d65eb7cca Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/dashclock_folder.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/dashclock_phone.png b/Backbone/src/main/res/drawable-hdpi/dashclock_phone.png new file mode 100644 index 000000000..3403b8f08 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/dashclock_phone.png differ diff --git a/res/drawable-hdpi/ic_holo_light_sdcard.png b/Backbone/src/main/res/drawable-hdpi/dashclock_sd.png similarity index 100% rename from res/drawable-hdpi/ic_holo_light_sdcard.png rename to Backbone/src/main/res/drawable-hdpi/dashclock_sd.png diff --git a/res/drawable-hdpi/ic_holo_light_usb.png b/Backbone/src/main/res/drawable-hdpi/dashclock_usb.png similarity index 100% rename from res/drawable-hdpi/ic_holo_light_usb.png rename to Backbone/src/main/res/drawable-hdpi/dashclock_usb.png diff --git a/res/drawable-hdpi/divider_horizontal_bright_opaque.9.png b/Backbone/src/main/res/drawable-hdpi/divider_horizontal_bright_opaque.9.png similarity index 100% rename from res/drawable-hdpi/divider_horizontal_bright_opaque.9.png rename to Backbone/src/main/res/drawable-hdpi/divider_horizontal_bright_opaque.9.png diff --git a/themes/res/drawable-hdpi/divider_horizontal_dark_opaque.9.png b/Backbone/src/main/res/drawable-hdpi/divider_horizontal_dark_opaque.9.png similarity index 100% rename from themes/res/drawable-hdpi/divider_horizontal_dark_opaque.9.png rename to Backbone/src/main/res/drawable-hdpi/divider_horizontal_dark_opaque.9.png diff --git a/res/drawable-hdpi/divider_vertical_bright_opaque.9.png b/Backbone/src/main/res/drawable-hdpi/divider_vertical_bright_opaque.9.png similarity index 100% rename from res/drawable-hdpi/divider_vertical_bright_opaque.9.png rename to Backbone/src/main/res/drawable-hdpi/divider_vertical_bright_opaque.9.png diff --git a/themes/res/drawable-hdpi/divider_vertical_dark_opaque.9.png b/Backbone/src/main/res/drawable-hdpi/divider_vertical_dark_opaque.9.png similarity index 100% rename from themes/res/drawable-hdpi/divider_vertical_dark_opaque.9.png rename to Backbone/src/main/res/drawable-hdpi/divider_vertical_dark_opaque.9.png diff --git a/Backbone/src/main/res/drawable-hdpi/drawer_shadow.9.png b/Backbone/src/main/res/drawable-hdpi/drawer_shadow.9.png new file mode 100644 index 000000000..224cc4ff4 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/drawer_shadow.9.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark.png b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark.png new file mode 100644 index 000000000..b99f19e5d Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_add.png b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_add.png new file mode 100644 index 000000000..7ebcc32bd Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_add.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_copy.png b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_copy.png new file mode 100644 index 000000000..4cfc094b5 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_copy.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_cut.png b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_cut.png new file mode 100644 index 000000000..02a1fc485 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_cut.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_delete.png b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_delete.png new file mode 100644 index 000000000..889f29ee2 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_delete.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_history.png b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_history.png new file mode 100644 index 000000000..ac0a52b6b Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_history.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_layout.png b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_layout.png new file mode 100644 index 000000000..c358cb0e4 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_layout.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_lock_closed.png b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_lock_closed.png new file mode 100644 index 000000000..4c3cd2d7a Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_lock_closed.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_lock_open.png b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_lock_open.png new file mode 100644 index 000000000..bd4637c89 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_lock_open.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_overflow.png b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_overflow.png new file mode 100644 index 000000000..cf20a1e54 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_overflow.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_paste.png b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_paste.png new file mode 100644 index 000000000..2dcf1ebda Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_paste.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_properties.png b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_properties.png new file mode 100644 index 000000000..379b86b5d Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_properties.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_refresh.png b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_refresh.png new file mode 100644 index 000000000..ecaa789a9 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_refresh.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_save.png b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_save.png new file mode 100644 index 000000000..306d55a9c Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_save.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_search.png b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_search.png new file mode 100644 index 000000000..95d302db8 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_search.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_select_all.png b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_select_all.png new file mode 100644 index 000000000..b1bdb1fb6 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_select_all.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_settings.png b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_settings.png new file mode 100644 index 000000000..3ac83c749 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_settings.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_share.png b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_share.png new file mode 100644 index 000000000..7b6c0dd21 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_share.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_sort_alphabetically.png b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_sort_alphabetically.png new file mode 100644 index 000000000..92d27f889 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_sort_alphabetically.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_view.png b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_view.png new file mode 100644 index 000000000..c73456589 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_view.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_warning.png b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_warning.png new file mode 100644 index 000000000..af0184607 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_dark_warning.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ic_action_holo_light_info.png b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_light_info.png new file mode 100644 index 000000000..af06546dd Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_action_holo_light_info.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ic_drawer.png b/Backbone/src/main/res/drawable-hdpi/ic_drawer.png new file mode 100644 index 000000000..ff7b1def9 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_drawer.png differ diff --git a/res/drawable-hdpi/ic_fso_default.png b/Backbone/src/main/res/drawable-hdpi/ic_fso_default.png similarity index 100% rename from res/drawable-hdpi/ic_fso_default.png rename to Backbone/src/main/res/drawable-hdpi/ic_fso_default.png diff --git a/res/drawable-hdpi/ic_fso_folder.png b/Backbone/src/main/res/drawable-hdpi/ic_fso_folder.png similarity index 100% rename from res/drawable-hdpi/ic_fso_folder.png rename to Backbone/src/main/res/drawable-hdpi/ic_fso_folder.png diff --git a/res/drawable-hdpi/ic_fso_type_app.png b/Backbone/src/main/res/drawable-hdpi/ic_fso_type_app.png similarity index 100% rename from res/drawable-hdpi/ic_fso_type_app.png rename to Backbone/src/main/res/drawable-hdpi/ic_fso_type_app.png diff --git a/res/drawable-hdpi/ic_fso_type_audio.png b/Backbone/src/main/res/drawable-hdpi/ic_fso_type_audio.png similarity index 100% rename from res/drawable-hdpi/ic_fso_type_audio.png rename to Backbone/src/main/res/drawable-hdpi/ic_fso_type_audio.png diff --git a/res/drawable-hdpi/ic_fso_type_binary.png b/Backbone/src/main/res/drawable-hdpi/ic_fso_type_binary.png similarity index 100% rename from res/drawable-hdpi/ic_fso_type_binary.png rename to Backbone/src/main/res/drawable-hdpi/ic_fso_type_binary.png diff --git a/res/drawable-hdpi/ic_fso_type_calendar.png b/Backbone/src/main/res/drawable-hdpi/ic_fso_type_calendar.png similarity index 100% rename from res/drawable-hdpi/ic_fso_type_calendar.png rename to Backbone/src/main/res/drawable-hdpi/ic_fso_type_calendar.png diff --git a/res/drawable-hdpi/ic_fso_type_cdimage.png b/Backbone/src/main/res/drawable-hdpi/ic_fso_type_cdimage.png similarity index 100% rename from res/drawable-hdpi/ic_fso_type_cdimage.png rename to Backbone/src/main/res/drawable-hdpi/ic_fso_type_cdimage.png diff --git a/res/drawable-hdpi/ic_fso_type_compress.png b/Backbone/src/main/res/drawable-hdpi/ic_fso_type_compress.png similarity index 100% rename from res/drawable-hdpi/ic_fso_type_compress.png rename to Backbone/src/main/res/drawable-hdpi/ic_fso_type_compress.png diff --git a/res/drawable-hdpi/ic_fso_type_contact.png b/Backbone/src/main/res/drawable-hdpi/ic_fso_type_contact.png similarity index 100% rename from res/drawable-hdpi/ic_fso_type_contact.png rename to Backbone/src/main/res/drawable-hdpi/ic_fso_type_contact.png diff --git a/res/drawable-hdpi/ic_fso_type_database.png b/Backbone/src/main/res/drawable-hdpi/ic_fso_type_database.png similarity index 100% rename from res/drawable-hdpi/ic_fso_type_database.png rename to Backbone/src/main/res/drawable-hdpi/ic_fso_type_database.png diff --git a/res/drawable-hdpi/ic_fso_type_document.png b/Backbone/src/main/res/drawable-hdpi/ic_fso_type_document.png similarity index 100% rename from res/drawable-hdpi/ic_fso_type_document.png rename to Backbone/src/main/res/drawable-hdpi/ic_fso_type_document.png diff --git a/res/drawable-hdpi/ic_fso_type_ebook.png b/Backbone/src/main/res/drawable-hdpi/ic_fso_type_ebook.png similarity index 100% rename from res/drawable-hdpi/ic_fso_type_ebook.png rename to Backbone/src/main/res/drawable-hdpi/ic_fso_type_ebook.png diff --git a/res/drawable-hdpi/ic_fso_type_email.png b/Backbone/src/main/res/drawable-hdpi/ic_fso_type_email.png similarity index 100% rename from res/drawable-hdpi/ic_fso_type_email.png rename to Backbone/src/main/res/drawable-hdpi/ic_fso_type_email.png diff --git a/res/drawable-hdpi/ic_fso_type_executable.png b/Backbone/src/main/res/drawable-hdpi/ic_fso_type_executable.png similarity index 100% rename from res/drawable-hdpi/ic_fso_type_executable.png rename to Backbone/src/main/res/drawable-hdpi/ic_fso_type_executable.png diff --git a/res/drawable-hdpi/ic_fso_type_feed.png b/Backbone/src/main/res/drawable-hdpi/ic_fso_type_feed.png similarity index 100% rename from res/drawable-hdpi/ic_fso_type_feed.png rename to Backbone/src/main/res/drawable-hdpi/ic_fso_type_feed.png diff --git a/res/drawable-hdpi/ic_fso_type_font.png b/Backbone/src/main/res/drawable-hdpi/ic_fso_type_font.png similarity index 100% rename from res/drawable-hdpi/ic_fso_type_font.png rename to Backbone/src/main/res/drawable-hdpi/ic_fso_type_font.png diff --git a/res/drawable-hdpi/ic_fso_type_image.png b/Backbone/src/main/res/drawable-hdpi/ic_fso_type_image.png similarity index 100% rename from res/drawable-hdpi/ic_fso_type_image.png rename to Backbone/src/main/res/drawable-hdpi/ic_fso_type_image.png diff --git a/res/drawable-hdpi/ic_fso_type_markup_document.png b/Backbone/src/main/res/drawable-hdpi/ic_fso_type_markup_document.png similarity index 100% rename from res/drawable-hdpi/ic_fso_type_markup_document.png rename to Backbone/src/main/res/drawable-hdpi/ic_fso_type_markup_document.png diff --git a/res/drawable-hdpi/ic_fso_type_pdf.png b/Backbone/src/main/res/drawable-hdpi/ic_fso_type_pdf.png similarity index 100% rename from res/drawable-hdpi/ic_fso_type_pdf.png rename to Backbone/src/main/res/drawable-hdpi/ic_fso_type_pdf.png diff --git a/res/drawable-hdpi/ic_fso_type_presentation.png b/Backbone/src/main/res/drawable-hdpi/ic_fso_type_presentation.png similarity index 100% rename from res/drawable-hdpi/ic_fso_type_presentation.png rename to Backbone/src/main/res/drawable-hdpi/ic_fso_type_presentation.png diff --git a/res/drawable-hdpi/ic_fso_type_security.png b/Backbone/src/main/res/drawable-hdpi/ic_fso_type_security.png similarity index 100% rename from res/drawable-hdpi/ic_fso_type_security.png rename to Backbone/src/main/res/drawable-hdpi/ic_fso_type_security.png diff --git a/res/drawable-hdpi/ic_fso_type_shell.png b/Backbone/src/main/res/drawable-hdpi/ic_fso_type_shell.png similarity index 100% rename from res/drawable-hdpi/ic_fso_type_shell.png rename to Backbone/src/main/res/drawable-hdpi/ic_fso_type_shell.png diff --git a/res/drawable-hdpi/ic_fso_type_source.png b/Backbone/src/main/res/drawable-hdpi/ic_fso_type_source.png similarity index 100% rename from res/drawable-hdpi/ic_fso_type_source.png rename to Backbone/src/main/res/drawable-hdpi/ic_fso_type_source.png diff --git a/res/drawable-hdpi/ic_fso_type_spreadsheet.png b/Backbone/src/main/res/drawable-hdpi/ic_fso_type_spreadsheet.png similarity index 100% rename from res/drawable-hdpi/ic_fso_type_spreadsheet.png rename to Backbone/src/main/res/drawable-hdpi/ic_fso_type_spreadsheet.png diff --git a/res/drawable-hdpi/ic_fso_type_system.png b/Backbone/src/main/res/drawable-hdpi/ic_fso_type_system.png similarity index 100% rename from res/drawable-hdpi/ic_fso_type_system.png rename to Backbone/src/main/res/drawable-hdpi/ic_fso_type_system.png diff --git a/res/drawable-hdpi/ic_fso_type_text.png b/Backbone/src/main/res/drawable-hdpi/ic_fso_type_text.png similarity index 100% rename from res/drawable-hdpi/ic_fso_type_text.png rename to Backbone/src/main/res/drawable-hdpi/ic_fso_type_text.png diff --git a/res/drawable-hdpi/ic_fso_type_video.png b/Backbone/src/main/res/drawable-hdpi/ic_fso_type_video.png similarity index 100% rename from res/drawable-hdpi/ic_fso_type_video.png rename to Backbone/src/main/res/drawable-hdpi/ic_fso_type_video.png diff --git a/themes/res/drawable-hdpi/ic_holo_dark_accept.png b/Backbone/src/main/res/drawable-hdpi/ic_holo_dark_accept.png similarity index 100% rename from themes/res/drawable-hdpi/ic_holo_dark_accept.png rename to Backbone/src/main/res/drawable-hdpi/ic_holo_dark_accept.png diff --git a/themes/res/drawable-hdpi/ic_holo_dark_close.png b/Backbone/src/main/res/drawable-hdpi/ic_holo_dark_close.png similarity index 100% rename from themes/res/drawable-hdpi/ic_holo_dark_close.png rename to Backbone/src/main/res/drawable-hdpi/ic_holo_dark_close.png diff --git a/themes/res/drawable-hdpi/ic_holo_dark_config.png b/Backbone/src/main/res/drawable-hdpi/ic_holo_dark_config.png similarity index 100% rename from themes/res/drawable-hdpi/ic_holo_dark_config.png rename to Backbone/src/main/res/drawable-hdpi/ic_holo_dark_config.png diff --git a/themes/res/drawable-hdpi/ic_holo_dark_contextual_action.png b/Backbone/src/main/res/drawable-hdpi/ic_holo_dark_contextual_action.png similarity index 100% rename from themes/res/drawable-hdpi/ic_holo_dark_contextual_action.png rename to Backbone/src/main/res/drawable-hdpi/ic_holo_dark_contextual_action.png diff --git a/themes/res/drawable-hdpi/ic_holo_dark_expander_close.png b/Backbone/src/main/res/drawable-hdpi/ic_holo_dark_expander_close.png similarity index 100% rename from themes/res/drawable-hdpi/ic_holo_dark_expander_close.png rename to Backbone/src/main/res/drawable-hdpi/ic_holo_dark_expander_close.png diff --git a/themes/res/drawable-hdpi/ic_holo_dark_expander_open.png b/Backbone/src/main/res/drawable-hdpi/ic_holo_dark_expander_open.png similarity index 100% rename from themes/res/drawable-hdpi/ic_holo_dark_expander_open.png rename to Backbone/src/main/res/drawable-hdpi/ic_holo_dark_expander_open.png diff --git a/themes/res/drawable-hdpi/ic_holo_dark_filesystem.png b/Backbone/src/main/res/drawable-hdpi/ic_holo_dark_filesystem.png similarity index 100% rename from themes/res/drawable-hdpi/ic_holo_dark_filesystem.png rename to Backbone/src/main/res/drawable-hdpi/ic_holo_dark_filesystem.png diff --git a/themes/res/drawable-hdpi/ic_holo_dark_fs_locked.png b/Backbone/src/main/res/drawable-hdpi/ic_holo_dark_fs_locked.png similarity index 100% rename from themes/res/drawable-hdpi/ic_holo_dark_fs_locked.png rename to Backbone/src/main/res/drawable-hdpi/ic_holo_dark_fs_locked.png diff --git a/themes/res/drawable-hdpi/ic_holo_dark_fs_unlocked.png b/Backbone/src/main/res/drawable-hdpi/ic_holo_dark_fs_unlocked.png similarity index 100% rename from themes/res/drawable-hdpi/ic_holo_dark_fs_unlocked.png rename to Backbone/src/main/res/drawable-hdpi/ic_holo_dark_fs_unlocked.png diff --git a/themes/res/drawable-hdpi/ic_holo_dark_fs_warning.png b/Backbone/src/main/res/drawable-hdpi/ic_holo_dark_fs_warning.png similarity index 100% rename from themes/res/drawable-hdpi/ic_holo_dark_fs_warning.png rename to Backbone/src/main/res/drawable-hdpi/ic_holo_dark_fs_warning.png diff --git a/Backbone/src/main/res/drawable-hdpi/ic_holo_dark_home.png b/Backbone/src/main/res/drawable-hdpi/ic_holo_dark_home.png new file mode 100644 index 000000000..0f519e617 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_holo_dark_home.png differ diff --git a/themes/res/drawable-hdpi/ic_holo_dark_save.png b/Backbone/src/main/res/drawable-hdpi/ic_holo_dark_save.png similarity index 100% rename from themes/res/drawable-hdpi/ic_holo_dark_save.png rename to Backbone/src/main/res/drawable-hdpi/ic_holo_dark_save.png diff --git a/Backbone/src/main/res/drawable-hdpi/ic_holo_dark_sdcard.png b/Backbone/src/main/res/drawable-hdpi/ic_holo_dark_sdcard.png new file mode 100644 index 000000000..56fceb5a7 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_holo_dark_sdcard.png differ diff --git a/themes/res/drawable-hdpi/ic_holo_dark_tab.png b/Backbone/src/main/res/drawable-hdpi/ic_holo_dark_tab.png similarity index 100% rename from themes/res/drawable-hdpi/ic_holo_dark_tab.png rename to Backbone/src/main/res/drawable-hdpi/ic_holo_dark_tab.png diff --git a/Backbone/src/main/res/drawable-hdpi/ic_holo_dark_usb.png b/Backbone/src/main/res/drawable-hdpi/ic_holo_dark_usb.png new file mode 100644 index 000000000..59a72b78f Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_holo_dark_usb.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ic_holo_dark_user_defined_bookmark.png b/Backbone/src/main/res/drawable-hdpi/ic_holo_dark_user_defined_bookmark.png new file mode 100644 index 000000000..20a1a7c1c Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_holo_dark_user_defined_bookmark.png differ diff --git a/res/drawable-hdpi/ic_holo_light_accept.png b/Backbone/src/main/res/drawable-hdpi/ic_holo_light_accept.png similarity index 100% rename from res/drawable-hdpi/ic_holo_light_accept.png rename to Backbone/src/main/res/drawable-hdpi/ic_holo_light_accept.png diff --git a/res/drawable-hdpi/ic_holo_light_breadcrumb_divider.png b/Backbone/src/main/res/drawable-hdpi/ic_holo_light_breadcrumb_divider.png similarity index 100% rename from res/drawable-hdpi/ic_holo_light_breadcrumb_divider.png rename to Backbone/src/main/res/drawable-hdpi/ic_holo_light_breadcrumb_divider.png diff --git a/res/drawable-hdpi/ic_holo_light_contextual_action.png b/Backbone/src/main/res/drawable-hdpi/ic_holo_light_contextual_action.png similarity index 100% rename from res/drawable-hdpi/ic_holo_light_contextual_action.png rename to Backbone/src/main/res/drawable-hdpi/ic_holo_light_contextual_action.png diff --git a/Backbone/src/main/res/drawable-hdpi/ic_holo_light_copy.png b/Backbone/src/main/res/drawable-hdpi/ic_holo_light_copy.png new file mode 100644 index 000000000..623b71504 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_holo_light_copy.png differ diff --git a/res/drawable-hdpi/ic_holo_light_expander_close.png b/Backbone/src/main/res/drawable-hdpi/ic_holo_light_expander_close.png similarity index 100% rename from res/drawable-hdpi/ic_holo_light_expander_close.png rename to Backbone/src/main/res/drawable-hdpi/ic_holo_light_expander_close.png diff --git a/res/drawable-hdpi/ic_holo_light_expander_open.png b/Backbone/src/main/res/drawable-hdpi/ic_holo_light_expander_open.png similarity index 100% rename from res/drawable-hdpi/ic_holo_light_expander_open.png rename to Backbone/src/main/res/drawable-hdpi/ic_holo_light_expander_open.png diff --git a/res/drawable-hdpi/ic_holo_light_history_search.png b/Backbone/src/main/res/drawable-hdpi/ic_holo_light_history_search.png similarity index 100% rename from res/drawable-hdpi/ic_holo_light_history_search.png rename to Backbone/src/main/res/drawable-hdpi/ic_holo_light_history_search.png diff --git a/res/drawable-hdpi/ic_holo_light_save.png b/Backbone/src/main/res/drawable-hdpi/ic_holo_light_save.png similarity index 100% rename from res/drawable-hdpi/ic_holo_light_save.png rename to Backbone/src/main/res/drawable-hdpi/ic_holo_light_save.png diff --git a/res/drawable-hdpi/ic_holo_light_tab.png b/Backbone/src/main/res/drawable-hdpi/ic_holo_light_tab.png similarity index 100% rename from res/drawable-hdpi/ic_holo_light_tab.png rename to Backbone/src/main/res/drawable-hdpi/ic_holo_light_tab.png diff --git a/Backbone/src/main/res/drawable-hdpi/ic_indicator_holo_dark_file.png b/Backbone/src/main/res/drawable-hdpi/ic_indicator_holo_dark_file.png new file mode 100644 index 000000000..8fe76b739 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_indicator_holo_dark_file.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ic_indicator_holo_dark_folder.png b/Backbone/src/main/res/drawable-hdpi/ic_indicator_holo_dark_folder.png new file mode 100644 index 000000000..21d035b23 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_indicator_holo_dark_folder.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ic_launcher.png b/Backbone/src/main/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 000000000..f89852414 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_launcher.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ic_launcher_dashclock.png b/Backbone/src/main/res/drawable-hdpi/ic_launcher_dashclock.png new file mode 100644 index 000000000..463ddf1c4 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_launcher_dashclock.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ic_preference_black_about.png b/Backbone/src/main/res/drawable-hdpi/ic_preference_black_about.png new file mode 100644 index 000000000..56f2c9812 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_preference_black_about.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ic_preference_black_dashclock.png b/Backbone/src/main/res/drawable-hdpi/ic_preference_black_dashclock.png new file mode 100644 index 000000000..2a1fdc1a1 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_preference_black_dashclock.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ic_preference_black_editor.png b/Backbone/src/main/res/drawable-hdpi/ic_preference_black_editor.png new file mode 100644 index 000000000..e07d8cc79 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_preference_black_editor.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ic_preference_black_general.png b/Backbone/src/main/res/drawable-hdpi/ic_preference_black_general.png new file mode 100644 index 000000000..69e49b342 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_preference_black_general.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/ic_preference_black_search.png b/Backbone/src/main/res/drawable-hdpi/ic_preference_black_search.png new file mode 100644 index 000000000..a799a5efe Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/ic_preference_black_search.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/list_divider_holo_dark.9.png b/Backbone/src/main/res/drawable-hdpi/list_divider_holo_dark.9.png new file mode 100644 index 000000000..986ab0b97 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/list_divider_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/list_divider_holo_light.9.png b/Backbone/src/main/res/drawable-hdpi/list_divider_holo_light.9.png new file mode 100644 index 000000000..0279e17a1 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/list_divider_holo_light.9.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/list_focused_holo_dark.9.png b/Backbone/src/main/res/drawable-hdpi/list_focused_holo_dark.9.png new file mode 100644 index 000000000..426d73fb4 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/list_focused_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/list_section_divider_holo_dark.9.png b/Backbone/src/main/res/drawable-hdpi/list_section_divider_holo_dark.9.png new file mode 100644 index 000000000..d5790c6e1 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/list_section_divider_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/list_section_divider_holo_light.9.png b/Backbone/src/main/res/drawable-hdpi/list_section_divider_holo_light.9.png new file mode 100644 index 000000000..b7b292e92 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/list_section_divider_holo_light.9.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/menu_dropdown_panel_holo_dark.9.png b/Backbone/src/main/res/drawable-hdpi/menu_dropdown_panel_holo_dark.9.png new file mode 100644 index 000000000..bab5e3ba7 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/menu_dropdown_panel_holo_dark.9.png differ diff --git a/themes/res/drawable-hdpi/progress_bg_holo_dark.9.png b/Backbone/src/main/res/drawable-hdpi/progress_bg_holo_dark.9.png similarity index 100% rename from themes/res/drawable-hdpi/progress_bg_holo_dark.9.png rename to Backbone/src/main/res/drawable-hdpi/progress_bg_holo_dark.9.png diff --git a/res/drawable-hdpi/progress_bg_holo_light.9.png b/Backbone/src/main/res/drawable-hdpi/progress_bg_holo_light.9.png similarity index 100% rename from res/drawable-hdpi/progress_bg_holo_light.9.png rename to Backbone/src/main/res/drawable-hdpi/progress_bg_holo_light.9.png diff --git a/themes/res/drawable-hdpi/progress_primary_holo_dark.9.png b/Backbone/src/main/res/drawable-hdpi/progress_primary_holo_dark.9.png similarity index 100% rename from themes/res/drawable-hdpi/progress_primary_holo_dark.9.png rename to Backbone/src/main/res/drawable-hdpi/progress_primary_holo_dark.9.png diff --git a/res/drawable-hdpi/progress_primary_holo_light.9.png b/Backbone/src/main/res/drawable-hdpi/progress_primary_holo_light.9.png similarity index 100% rename from res/drawable-hdpi/progress_primary_holo_light.9.png rename to Backbone/src/main/res/drawable-hdpi/progress_primary_holo_light.9.png diff --git a/themes/res/drawable-hdpi/progress_secondary_holo_dark.9.png b/Backbone/src/main/res/drawable-hdpi/progress_secondary_holo_dark.9.png similarity index 100% rename from themes/res/drawable-hdpi/progress_secondary_holo_dark.9.png rename to Backbone/src/main/res/drawable-hdpi/progress_secondary_holo_dark.9.png diff --git a/res/drawable-hdpi/progress_secondary_holo_light.9.png b/Backbone/src/main/res/drawable-hdpi/progress_secondary_holo_light.9.png similarity index 100% rename from res/drawable-hdpi/progress_secondary_holo_light.9.png rename to Backbone/src/main/res/drawable-hdpi/progress_secondary_holo_light.9.png diff --git a/Backbone/src/main/res/drawable-hdpi/spinner_ab_default_holo_dark.9.png b/Backbone/src/main/res/drawable-hdpi/spinner_ab_default_holo_dark.9.png new file mode 100644 index 000000000..4fd4aeba0 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/spinner_ab_default_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/spinner_ab_disabled_holo_dark.9.png b/Backbone/src/main/res/drawable-hdpi/spinner_ab_disabled_holo_dark.9.png new file mode 100644 index 000000000..d42c97b85 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/spinner_ab_disabled_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/spinner_ab_focused_holo_dark.9.png b/Backbone/src/main/res/drawable-hdpi/spinner_ab_focused_holo_dark.9.png new file mode 100644 index 000000000..c4b4f8c34 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/spinner_ab_focused_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/spinner_ab_pressed_holo_dark.9.png b/Backbone/src/main/res/drawable-hdpi/spinner_ab_pressed_holo_dark.9.png new file mode 100644 index 000000000..39f778c82 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/spinner_ab_pressed_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/tab_selected_focused_holo_dark.9.png b/Backbone/src/main/res/drawable-hdpi/tab_selected_focused_holo_dark.9.png new file mode 100644 index 000000000..c43fb1326 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/tab_selected_focused_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/tab_selected_holo_dark.9.png b/Backbone/src/main/res/drawable-hdpi/tab_selected_holo_dark.9.png new file mode 100644 index 000000000..b8f44c21e Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/tab_selected_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/tab_selected_pressed_holo_dark.9.png b/Backbone/src/main/res/drawable-hdpi/tab_selected_pressed_holo_dark.9.png new file mode 100644 index 000000000..5cb9817db Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/tab_selected_pressed_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/tab_unselected_focused_holo_dark.9.png b/Backbone/src/main/res/drawable-hdpi/tab_unselected_focused_holo_dark.9.png new file mode 100644 index 000000000..57c5f1821 Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/tab_unselected_focused_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/tab_unselected_holo_dark.9.png b/Backbone/src/main/res/drawable-hdpi/tab_unselected_holo_dark.9.png new file mode 100644 index 000000000..7cd46d63d Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/tab_unselected_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-hdpi/tab_unselected_pressed_holo_dark.9.png b/Backbone/src/main/res/drawable-hdpi/tab_unselected_pressed_holo_dark.9.png new file mode 100644 index 000000000..f44171cce Binary files /dev/null and b/Backbone/src/main/res/drawable-hdpi/tab_unselected_pressed_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ab_bottom_solid_holo_dark.9.png b/Backbone/src/main/res/drawable-mdpi/ab_bottom_solid_holo_dark.9.png new file mode 100644 index 000000000..aa84aa5c2 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ab_bottom_solid_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ab_solid_holo_dark.9.png b/Backbone/src/main/res/drawable-mdpi/ab_solid_holo_dark.9.png new file mode 100644 index 000000000..50880b225 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ab_solid_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ab_stacked_solid_holo_dark.9.png b/Backbone/src/main/res/drawable-mdpi/ab_stacked_solid_holo_dark.9.png new file mode 100644 index 000000000..c9a5459a5 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ab_stacked_solid_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ab_texture_tile_holo_dark.png b/Backbone/src/main/res/drawable-mdpi/ab_texture_tile_holo_dark.png new file mode 100644 index 000000000..6d0d6d459 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ab_texture_tile_holo_dark.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ab_transparent_holo_dark.9.png b/Backbone/src/main/res/drawable-mdpi/ab_transparent_holo_dark.9.png new file mode 100644 index 000000000..d101b0748 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ab_transparent_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/btn_cab_done_default_holo_dark.9.png b/Backbone/src/main/res/drawable-mdpi/btn_cab_done_default_holo_dark.9.png new file mode 100644 index 000000000..70f1520f1 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/btn_cab_done_default_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/btn_cab_done_focused_holo_dark.9.png b/Backbone/src/main/res/drawable-mdpi/btn_cab_done_focused_holo_dark.9.png new file mode 100644 index 000000000..be7948427 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/btn_cab_done_focused_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/btn_cab_done_pressed_holo_dark.9.png b/Backbone/src/main/res/drawable-mdpi/btn_cab_done_pressed_holo_dark.9.png new file mode 100644 index 000000000..15cb5d89a Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/btn_cab_done_pressed_holo_dark.9.png differ diff --git a/themes/res/drawable-mdpi/btn_holo_dark_check_off_normal.png b/Backbone/src/main/res/drawable-mdpi/btn_holo_dark_check_off_normal.png similarity index 100% rename from themes/res/drawable-mdpi/btn_holo_dark_check_off_normal.png rename to Backbone/src/main/res/drawable-mdpi/btn_holo_dark_check_off_normal.png diff --git a/themes/res/drawable-mdpi/btn_holo_dark_check_on_normal.png b/Backbone/src/main/res/drawable-mdpi/btn_holo_dark_check_on_normal.png similarity index 100% rename from themes/res/drawable-mdpi/btn_holo_dark_check_on_normal.png rename to Backbone/src/main/res/drawable-mdpi/btn_holo_dark_check_on_normal.png diff --git a/themes/res/drawable-mdpi/btn_holo_dark_check_on_normal_inverted.png b/Backbone/src/main/res/drawable-mdpi/btn_holo_dark_check_on_normal_inverted.png similarity index 100% rename from themes/res/drawable-mdpi/btn_holo_dark_check_on_normal_inverted.png rename to Backbone/src/main/res/drawable-mdpi/btn_holo_dark_check_on_normal_inverted.png diff --git a/res/drawable-mdpi/btn_holo_light_check_off_normal.png b/Backbone/src/main/res/drawable-mdpi/btn_holo_light_check_off_normal.png similarity index 100% rename from res/drawable-mdpi/btn_holo_light_check_off_normal.png rename to Backbone/src/main/res/drawable-mdpi/btn_holo_light_check_off_normal.png diff --git a/res/drawable-mdpi/btn_holo_light_check_on_normal.png b/Backbone/src/main/res/drawable-mdpi/btn_holo_light_check_on_normal.png similarity index 100% rename from res/drawable-mdpi/btn_holo_light_check_on_normal.png rename to Backbone/src/main/res/drawable-mdpi/btn_holo_light_check_on_normal.png diff --git a/res/drawable-mdpi/btn_holo_light_check_on_normal_inverted.png b/Backbone/src/main/res/drawable-mdpi/btn_holo_light_check_on_normal_inverted.png similarity index 100% rename from res/drawable-mdpi/btn_holo_light_check_on_normal_inverted.png rename to Backbone/src/main/res/drawable-mdpi/btn_holo_light_check_on_normal_inverted.png diff --git a/Backbone/src/main/res/drawable-mdpi/cab_background_bottom_holo_dark.9.png b/Backbone/src/main/res/drawable-mdpi/cab_background_bottom_holo_dark.9.png new file mode 100644 index 000000000..2baf04584 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/cab_background_bottom_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/cab_background_top_holo_dark.9.png b/Backbone/src/main/res/drawable-mdpi/cab_background_top_holo_dark.9.png new file mode 100644 index 000000000..7f60c6734 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/cab_background_top_holo_dark.9.png differ diff --git a/res/drawable-mdpi/divider_horizontal_bright_opaque.9.png b/Backbone/src/main/res/drawable-mdpi/divider_horizontal_bright_opaque.9.png similarity index 100% rename from res/drawable-mdpi/divider_horizontal_bright_opaque.9.png rename to Backbone/src/main/res/drawable-mdpi/divider_horizontal_bright_opaque.9.png diff --git a/themes/res/drawable-mdpi/divider_horizontal_dark_opaque.9.png b/Backbone/src/main/res/drawable-mdpi/divider_horizontal_dark_opaque.9.png similarity index 100% rename from themes/res/drawable-mdpi/divider_horizontal_dark_opaque.9.png rename to Backbone/src/main/res/drawable-mdpi/divider_horizontal_dark_opaque.9.png diff --git a/res/drawable-mdpi/divider_vertical_bright_opaque.9.png b/Backbone/src/main/res/drawable-mdpi/divider_vertical_bright_opaque.9.png similarity index 100% rename from res/drawable-mdpi/divider_vertical_bright_opaque.9.png rename to Backbone/src/main/res/drawable-mdpi/divider_vertical_bright_opaque.9.png diff --git a/themes/res/drawable-mdpi/divider_vertical_dark_opaque.9.png b/Backbone/src/main/res/drawable-mdpi/divider_vertical_dark_opaque.9.png similarity index 100% rename from themes/res/drawable-mdpi/divider_vertical_dark_opaque.9.png rename to Backbone/src/main/res/drawable-mdpi/divider_vertical_dark_opaque.9.png diff --git a/Backbone/src/main/res/drawable-mdpi/drawer_shadow.9.png b/Backbone/src/main/res/drawable-mdpi/drawer_shadow.9.png new file mode 100644 index 000000000..3797f99c0 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/drawer_shadow.9.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark.png b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark.png new file mode 100644 index 000000000..f5dded947 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_add.png b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_add.png new file mode 100644 index 000000000..507b4a62c Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_add.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_copy.png b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_copy.png new file mode 100644 index 000000000..8698dc618 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_copy.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_cut.png b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_cut.png new file mode 100644 index 000000000..60cf321bd Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_cut.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_delete.png b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_delete.png new file mode 100644 index 000000000..e04c96cac Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_delete.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_history.png b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_history.png new file mode 100644 index 000000000..8f13c66fb Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_history.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_layout.png b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_layout.png new file mode 100644 index 000000000..cdb65d175 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_layout.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_lock_closed.png b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_lock_closed.png new file mode 100644 index 000000000..5e22126b6 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_lock_closed.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_lock_open.png b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_lock_open.png new file mode 100644 index 000000000..56404c5fb Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_lock_open.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_overflow.png b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_overflow.png new file mode 100644 index 000000000..de3fb594d Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_overflow.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_paste.png b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_paste.png new file mode 100644 index 000000000..8f27bcdbf Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_paste.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_properties.png b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_properties.png new file mode 100644 index 000000000..48411dcc7 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_properties.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_refresh.png b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_refresh.png new file mode 100644 index 000000000..43cf79718 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_refresh.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_save.png b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_save.png new file mode 100644 index 000000000..ad0769553 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_save.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_search.png b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_search.png new file mode 100644 index 000000000..c0f1c89fc Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_search.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_select_all.png b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_select_all.png new file mode 100644 index 000000000..fc228e4b0 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_select_all.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_settings.png b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_settings.png new file mode 100644 index 000000000..550603059 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_settings.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_share.png b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_share.png new file mode 100644 index 000000000..23b456c7e Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_share.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_sort_alphabetically.png b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_sort_alphabetically.png new file mode 100644 index 000000000..52b2b98e7 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_sort_alphabetically.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_view.png b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_view.png new file mode 100644 index 000000000..f449bb3b6 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_view.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_warning.png b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_warning.png new file mode 100644 index 000000000..6fbd911e5 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_dark_warning.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ic_action_holo_light_info.png b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_light_info.png new file mode 100644 index 000000000..103144169 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_action_holo_light_info.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ic_drawer.png b/Backbone/src/main/res/drawable-mdpi/ic_drawer.png new file mode 100644 index 000000000..fb681ba26 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_drawer.png differ diff --git a/res/drawable-mdpi/ic_fso_default.png b/Backbone/src/main/res/drawable-mdpi/ic_fso_default.png similarity index 100% rename from res/drawable-mdpi/ic_fso_default.png rename to Backbone/src/main/res/drawable-mdpi/ic_fso_default.png diff --git a/res/drawable-mdpi/ic_fso_folder.png b/Backbone/src/main/res/drawable-mdpi/ic_fso_folder.png similarity index 100% rename from res/drawable-mdpi/ic_fso_folder.png rename to Backbone/src/main/res/drawable-mdpi/ic_fso_folder.png diff --git a/res/drawable-mdpi/ic_fso_type_app.png b/Backbone/src/main/res/drawable-mdpi/ic_fso_type_app.png similarity index 100% rename from res/drawable-mdpi/ic_fso_type_app.png rename to Backbone/src/main/res/drawable-mdpi/ic_fso_type_app.png diff --git a/res/drawable-mdpi/ic_fso_type_audio.png b/Backbone/src/main/res/drawable-mdpi/ic_fso_type_audio.png similarity index 100% rename from res/drawable-mdpi/ic_fso_type_audio.png rename to Backbone/src/main/res/drawable-mdpi/ic_fso_type_audio.png diff --git a/res/drawable-mdpi/ic_fso_type_binary.png b/Backbone/src/main/res/drawable-mdpi/ic_fso_type_binary.png similarity index 100% rename from res/drawable-mdpi/ic_fso_type_binary.png rename to Backbone/src/main/res/drawable-mdpi/ic_fso_type_binary.png diff --git a/res/drawable-mdpi/ic_fso_type_calendar.png b/Backbone/src/main/res/drawable-mdpi/ic_fso_type_calendar.png similarity index 100% rename from res/drawable-mdpi/ic_fso_type_calendar.png rename to Backbone/src/main/res/drawable-mdpi/ic_fso_type_calendar.png diff --git a/res/drawable-mdpi/ic_fso_type_cdimage.png b/Backbone/src/main/res/drawable-mdpi/ic_fso_type_cdimage.png similarity index 100% rename from res/drawable-mdpi/ic_fso_type_cdimage.png rename to Backbone/src/main/res/drawable-mdpi/ic_fso_type_cdimage.png diff --git a/res/drawable-mdpi/ic_fso_type_compress.png b/Backbone/src/main/res/drawable-mdpi/ic_fso_type_compress.png similarity index 100% rename from res/drawable-mdpi/ic_fso_type_compress.png rename to Backbone/src/main/res/drawable-mdpi/ic_fso_type_compress.png diff --git a/res/drawable-mdpi/ic_fso_type_contact.png b/Backbone/src/main/res/drawable-mdpi/ic_fso_type_contact.png similarity index 100% rename from res/drawable-mdpi/ic_fso_type_contact.png rename to Backbone/src/main/res/drawable-mdpi/ic_fso_type_contact.png diff --git a/res/drawable-mdpi/ic_fso_type_database.png b/Backbone/src/main/res/drawable-mdpi/ic_fso_type_database.png similarity index 100% rename from res/drawable-mdpi/ic_fso_type_database.png rename to Backbone/src/main/res/drawable-mdpi/ic_fso_type_database.png diff --git a/res/drawable-mdpi/ic_fso_type_document.png b/Backbone/src/main/res/drawable-mdpi/ic_fso_type_document.png similarity index 100% rename from res/drawable-mdpi/ic_fso_type_document.png rename to Backbone/src/main/res/drawable-mdpi/ic_fso_type_document.png diff --git a/res/drawable-mdpi/ic_fso_type_ebook.png b/Backbone/src/main/res/drawable-mdpi/ic_fso_type_ebook.png similarity index 100% rename from res/drawable-mdpi/ic_fso_type_ebook.png rename to Backbone/src/main/res/drawable-mdpi/ic_fso_type_ebook.png diff --git a/res/drawable-mdpi/ic_fso_type_email.png b/Backbone/src/main/res/drawable-mdpi/ic_fso_type_email.png similarity index 100% rename from res/drawable-mdpi/ic_fso_type_email.png rename to Backbone/src/main/res/drawable-mdpi/ic_fso_type_email.png diff --git a/res/drawable-mdpi/ic_fso_type_executable.png b/Backbone/src/main/res/drawable-mdpi/ic_fso_type_executable.png similarity index 100% rename from res/drawable-mdpi/ic_fso_type_executable.png rename to Backbone/src/main/res/drawable-mdpi/ic_fso_type_executable.png diff --git a/res/drawable-mdpi/ic_fso_type_feed.png b/Backbone/src/main/res/drawable-mdpi/ic_fso_type_feed.png similarity index 100% rename from res/drawable-mdpi/ic_fso_type_feed.png rename to Backbone/src/main/res/drawable-mdpi/ic_fso_type_feed.png diff --git a/res/drawable-mdpi/ic_fso_type_font.png b/Backbone/src/main/res/drawable-mdpi/ic_fso_type_font.png similarity index 100% rename from res/drawable-mdpi/ic_fso_type_font.png rename to Backbone/src/main/res/drawable-mdpi/ic_fso_type_font.png diff --git a/res/drawable-mdpi/ic_fso_type_image.png b/Backbone/src/main/res/drawable-mdpi/ic_fso_type_image.png similarity index 100% rename from res/drawable-mdpi/ic_fso_type_image.png rename to Backbone/src/main/res/drawable-mdpi/ic_fso_type_image.png diff --git a/res/drawable-mdpi/ic_fso_type_markup_document.png b/Backbone/src/main/res/drawable-mdpi/ic_fso_type_markup_document.png similarity index 100% rename from res/drawable-mdpi/ic_fso_type_markup_document.png rename to Backbone/src/main/res/drawable-mdpi/ic_fso_type_markup_document.png diff --git a/res/drawable-mdpi/ic_fso_type_pdf.png b/Backbone/src/main/res/drawable-mdpi/ic_fso_type_pdf.png similarity index 100% rename from res/drawable-mdpi/ic_fso_type_pdf.png rename to Backbone/src/main/res/drawable-mdpi/ic_fso_type_pdf.png diff --git a/res/drawable-mdpi/ic_fso_type_presentation.png b/Backbone/src/main/res/drawable-mdpi/ic_fso_type_presentation.png similarity index 100% rename from res/drawable-mdpi/ic_fso_type_presentation.png rename to Backbone/src/main/res/drawable-mdpi/ic_fso_type_presentation.png diff --git a/res/drawable-mdpi/ic_fso_type_security.png b/Backbone/src/main/res/drawable-mdpi/ic_fso_type_security.png similarity index 100% rename from res/drawable-mdpi/ic_fso_type_security.png rename to Backbone/src/main/res/drawable-mdpi/ic_fso_type_security.png diff --git a/res/drawable-mdpi/ic_fso_type_shell.png b/Backbone/src/main/res/drawable-mdpi/ic_fso_type_shell.png similarity index 100% rename from res/drawable-mdpi/ic_fso_type_shell.png rename to Backbone/src/main/res/drawable-mdpi/ic_fso_type_shell.png diff --git a/res/drawable-mdpi/ic_fso_type_source.png b/Backbone/src/main/res/drawable-mdpi/ic_fso_type_source.png similarity index 100% rename from res/drawable-mdpi/ic_fso_type_source.png rename to Backbone/src/main/res/drawable-mdpi/ic_fso_type_source.png diff --git a/res/drawable-mdpi/ic_fso_type_spreadsheet.png b/Backbone/src/main/res/drawable-mdpi/ic_fso_type_spreadsheet.png similarity index 100% rename from res/drawable-mdpi/ic_fso_type_spreadsheet.png rename to Backbone/src/main/res/drawable-mdpi/ic_fso_type_spreadsheet.png diff --git a/res/drawable-mdpi/ic_fso_type_system.png b/Backbone/src/main/res/drawable-mdpi/ic_fso_type_system.png similarity index 100% rename from res/drawable-mdpi/ic_fso_type_system.png rename to Backbone/src/main/res/drawable-mdpi/ic_fso_type_system.png diff --git a/res/drawable-mdpi/ic_fso_type_text.png b/Backbone/src/main/res/drawable-mdpi/ic_fso_type_text.png similarity index 100% rename from res/drawable-mdpi/ic_fso_type_text.png rename to Backbone/src/main/res/drawable-mdpi/ic_fso_type_text.png diff --git a/res/drawable-mdpi/ic_fso_type_video.png b/Backbone/src/main/res/drawable-mdpi/ic_fso_type_video.png similarity index 100% rename from res/drawable-mdpi/ic_fso_type_video.png rename to Backbone/src/main/res/drawable-mdpi/ic_fso_type_video.png diff --git a/themes/res/drawable-mdpi/ic_holo_dark_accept.png b/Backbone/src/main/res/drawable-mdpi/ic_holo_dark_accept.png similarity index 100% rename from themes/res/drawable-mdpi/ic_holo_dark_accept.png rename to Backbone/src/main/res/drawable-mdpi/ic_holo_dark_accept.png diff --git a/themes/res/drawable-mdpi/ic_holo_dark_close.png b/Backbone/src/main/res/drawable-mdpi/ic_holo_dark_close.png similarity index 100% rename from themes/res/drawable-mdpi/ic_holo_dark_close.png rename to Backbone/src/main/res/drawable-mdpi/ic_holo_dark_close.png diff --git a/themes/res/drawable-mdpi/ic_holo_dark_config.png b/Backbone/src/main/res/drawable-mdpi/ic_holo_dark_config.png similarity index 100% rename from themes/res/drawable-mdpi/ic_holo_dark_config.png rename to Backbone/src/main/res/drawable-mdpi/ic_holo_dark_config.png diff --git a/themes/res/drawable-mdpi/ic_holo_dark_contextual_action.png b/Backbone/src/main/res/drawable-mdpi/ic_holo_dark_contextual_action.png similarity index 100% rename from themes/res/drawable-mdpi/ic_holo_dark_contextual_action.png rename to Backbone/src/main/res/drawable-mdpi/ic_holo_dark_contextual_action.png diff --git a/themes/res/drawable-mdpi/ic_holo_dark_expander_close.png b/Backbone/src/main/res/drawable-mdpi/ic_holo_dark_expander_close.png similarity index 100% rename from themes/res/drawable-mdpi/ic_holo_dark_expander_close.png rename to Backbone/src/main/res/drawable-mdpi/ic_holo_dark_expander_close.png diff --git a/themes/res/drawable-mdpi/ic_holo_dark_expander_open.png b/Backbone/src/main/res/drawable-mdpi/ic_holo_dark_expander_open.png similarity index 100% rename from themes/res/drawable-mdpi/ic_holo_dark_expander_open.png rename to Backbone/src/main/res/drawable-mdpi/ic_holo_dark_expander_open.png diff --git a/themes/res/drawable-mdpi/ic_holo_dark_filesystem.png b/Backbone/src/main/res/drawable-mdpi/ic_holo_dark_filesystem.png similarity index 100% rename from themes/res/drawable-mdpi/ic_holo_dark_filesystem.png rename to Backbone/src/main/res/drawable-mdpi/ic_holo_dark_filesystem.png diff --git a/themes/res/drawable-mdpi/ic_holo_dark_fs_locked.png b/Backbone/src/main/res/drawable-mdpi/ic_holo_dark_fs_locked.png similarity index 100% rename from themes/res/drawable-mdpi/ic_holo_dark_fs_locked.png rename to Backbone/src/main/res/drawable-mdpi/ic_holo_dark_fs_locked.png diff --git a/themes/res/drawable-mdpi/ic_holo_dark_fs_unlocked.png b/Backbone/src/main/res/drawable-mdpi/ic_holo_dark_fs_unlocked.png similarity index 100% rename from themes/res/drawable-mdpi/ic_holo_dark_fs_unlocked.png rename to Backbone/src/main/res/drawable-mdpi/ic_holo_dark_fs_unlocked.png diff --git a/themes/res/drawable-mdpi/ic_holo_dark_fs_warning.png b/Backbone/src/main/res/drawable-mdpi/ic_holo_dark_fs_warning.png similarity index 100% rename from themes/res/drawable-mdpi/ic_holo_dark_fs_warning.png rename to Backbone/src/main/res/drawable-mdpi/ic_holo_dark_fs_warning.png diff --git a/Backbone/src/main/res/drawable-mdpi/ic_holo_dark_home.png b/Backbone/src/main/res/drawable-mdpi/ic_holo_dark_home.png new file mode 100644 index 000000000..f91387734 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_holo_dark_home.png differ diff --git a/themes/res/drawable-mdpi/ic_holo_dark_save.png b/Backbone/src/main/res/drawable-mdpi/ic_holo_dark_save.png similarity index 100% rename from themes/res/drawable-mdpi/ic_holo_dark_save.png rename to Backbone/src/main/res/drawable-mdpi/ic_holo_dark_save.png diff --git a/Backbone/src/main/res/drawable-mdpi/ic_holo_dark_sdcard.png b/Backbone/src/main/res/drawable-mdpi/ic_holo_dark_sdcard.png new file mode 100644 index 000000000..c10561a77 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_holo_dark_sdcard.png differ diff --git a/themes/res/drawable-mdpi/ic_holo_dark_tab.png b/Backbone/src/main/res/drawable-mdpi/ic_holo_dark_tab.png similarity index 100% rename from themes/res/drawable-mdpi/ic_holo_dark_tab.png rename to Backbone/src/main/res/drawable-mdpi/ic_holo_dark_tab.png diff --git a/Backbone/src/main/res/drawable-mdpi/ic_holo_dark_usb.png b/Backbone/src/main/res/drawable-mdpi/ic_holo_dark_usb.png new file mode 100644 index 000000000..3bfca3337 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_holo_dark_usb.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ic_holo_dark_user_defined_bookmark.png b/Backbone/src/main/res/drawable-mdpi/ic_holo_dark_user_defined_bookmark.png new file mode 100644 index 000000000..2593763dc Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_holo_dark_user_defined_bookmark.png differ diff --git a/res/drawable-mdpi/ic_holo_light_accept.png b/Backbone/src/main/res/drawable-mdpi/ic_holo_light_accept.png similarity index 100% rename from res/drawable-mdpi/ic_holo_light_accept.png rename to Backbone/src/main/res/drawable-mdpi/ic_holo_light_accept.png diff --git a/res/drawable-mdpi/ic_holo_light_breadcrumb_divider.png b/Backbone/src/main/res/drawable-mdpi/ic_holo_light_breadcrumb_divider.png similarity index 100% rename from res/drawable-mdpi/ic_holo_light_breadcrumb_divider.png rename to Backbone/src/main/res/drawable-mdpi/ic_holo_light_breadcrumb_divider.png diff --git a/res/drawable-mdpi/ic_holo_light_contextual_action.png b/Backbone/src/main/res/drawable-mdpi/ic_holo_light_contextual_action.png similarity index 100% rename from res/drawable-mdpi/ic_holo_light_contextual_action.png rename to Backbone/src/main/res/drawable-mdpi/ic_holo_light_contextual_action.png diff --git a/Backbone/src/main/res/drawable-mdpi/ic_holo_light_copy.png b/Backbone/src/main/res/drawable-mdpi/ic_holo_light_copy.png new file mode 100644 index 000000000..efb2445f0 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_holo_light_copy.png differ diff --git a/res/drawable-mdpi/ic_holo_light_expander_close.png b/Backbone/src/main/res/drawable-mdpi/ic_holo_light_expander_close.png similarity index 100% rename from res/drawable-mdpi/ic_holo_light_expander_close.png rename to Backbone/src/main/res/drawable-mdpi/ic_holo_light_expander_close.png diff --git a/res/drawable-mdpi/ic_holo_light_expander_open.png b/Backbone/src/main/res/drawable-mdpi/ic_holo_light_expander_open.png similarity index 100% rename from res/drawable-mdpi/ic_holo_light_expander_open.png rename to Backbone/src/main/res/drawable-mdpi/ic_holo_light_expander_open.png diff --git a/res/drawable-mdpi/ic_holo_light_history_search.png b/Backbone/src/main/res/drawable-mdpi/ic_holo_light_history_search.png similarity index 100% rename from res/drawable-mdpi/ic_holo_light_history_search.png rename to Backbone/src/main/res/drawable-mdpi/ic_holo_light_history_search.png diff --git a/res/drawable-mdpi/ic_holo_light_save.png b/Backbone/src/main/res/drawable-mdpi/ic_holo_light_save.png similarity index 100% rename from res/drawable-mdpi/ic_holo_light_save.png rename to Backbone/src/main/res/drawable-mdpi/ic_holo_light_save.png diff --git a/res/drawable-mdpi/ic_holo_light_tab.png b/Backbone/src/main/res/drawable-mdpi/ic_holo_light_tab.png similarity index 100% rename from res/drawable-mdpi/ic_holo_light_tab.png rename to Backbone/src/main/res/drawable-mdpi/ic_holo_light_tab.png diff --git a/Backbone/src/main/res/drawable-mdpi/ic_indicator_holo_dark_file.png b/Backbone/src/main/res/drawable-mdpi/ic_indicator_holo_dark_file.png new file mode 100644 index 000000000..c65a575d9 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_indicator_holo_dark_file.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ic_indicator_holo_dark_folder.png b/Backbone/src/main/res/drawable-mdpi/ic_indicator_holo_dark_folder.png new file mode 100644 index 000000000..488419103 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_indicator_holo_dark_folder.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ic_launcher.png b/Backbone/src/main/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 000000000..7fdececff Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_launcher.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ic_launcher_dashclock.png b/Backbone/src/main/res/drawable-mdpi/ic_launcher_dashclock.png new file mode 100644 index 000000000..80555f956 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_launcher_dashclock.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ic_preference_black_about.png b/Backbone/src/main/res/drawable-mdpi/ic_preference_black_about.png new file mode 100644 index 000000000..8ae02a74e Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_preference_black_about.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ic_preference_black_dashclock.png b/Backbone/src/main/res/drawable-mdpi/ic_preference_black_dashclock.png new file mode 100644 index 000000000..b0800fe31 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_preference_black_dashclock.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ic_preference_black_editor.png b/Backbone/src/main/res/drawable-mdpi/ic_preference_black_editor.png new file mode 100644 index 000000000..e1f176cd1 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_preference_black_editor.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ic_preference_black_general.png b/Backbone/src/main/res/drawable-mdpi/ic_preference_black_general.png new file mode 100644 index 000000000..098739365 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_preference_black_general.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/ic_preference_black_search.png b/Backbone/src/main/res/drawable-mdpi/ic_preference_black_search.png new file mode 100644 index 000000000..f1850ec77 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/ic_preference_black_search.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/list_divider_holo_dark.9.png b/Backbone/src/main/res/drawable-mdpi/list_divider_holo_dark.9.png new file mode 100644 index 000000000..986ab0b97 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/list_divider_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/list_divider_holo_light.9.png b/Backbone/src/main/res/drawable-mdpi/list_divider_holo_light.9.png new file mode 100644 index 000000000..0279e17a1 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/list_divider_holo_light.9.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/list_focused_holo_dark.9.png b/Backbone/src/main/res/drawable-mdpi/list_focused_holo_dark.9.png new file mode 100644 index 000000000..4d3bcbfe0 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/list_focused_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/list_section_divider_holo_dark.9.png b/Backbone/src/main/res/drawable-mdpi/list_section_divider_holo_dark.9.png new file mode 100644 index 000000000..cea4c75f1 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/list_section_divider_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/list_section_divider_holo_light.9.png b/Backbone/src/main/res/drawable-mdpi/list_section_divider_holo_light.9.png new file mode 100644 index 000000000..c2f2dd87c Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/list_section_divider_holo_light.9.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/menu_dropdown_panel_holo_dark.9.png b/Backbone/src/main/res/drawable-mdpi/menu_dropdown_panel_holo_dark.9.png new file mode 100644 index 000000000..2b2458ef6 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/menu_dropdown_panel_holo_dark.9.png differ diff --git a/themes/res/drawable-mdpi/progress_bg_holo_dark.9.png b/Backbone/src/main/res/drawable-mdpi/progress_bg_holo_dark.9.png similarity index 100% rename from themes/res/drawable-mdpi/progress_bg_holo_dark.9.png rename to Backbone/src/main/res/drawable-mdpi/progress_bg_holo_dark.9.png diff --git a/themes/res/drawable-mdpi/progress_primary_holo_dark.9.png b/Backbone/src/main/res/drawable-mdpi/progress_primary_holo_dark.9.png similarity index 100% rename from themes/res/drawable-mdpi/progress_primary_holo_dark.9.png rename to Backbone/src/main/res/drawable-mdpi/progress_primary_holo_dark.9.png diff --git a/res/drawable-mdpi/progress_primary_holo_light.9.png b/Backbone/src/main/res/drawable-mdpi/progress_primary_holo_light.9.png similarity index 100% rename from res/drawable-mdpi/progress_primary_holo_light.9.png rename to Backbone/src/main/res/drawable-mdpi/progress_primary_holo_light.9.png diff --git a/themes/res/drawable-mdpi/progress_secondary_holo_dark.9.png b/Backbone/src/main/res/drawable-mdpi/progress_secondary_holo_dark.9.png similarity index 100% rename from themes/res/drawable-mdpi/progress_secondary_holo_dark.9.png rename to Backbone/src/main/res/drawable-mdpi/progress_secondary_holo_dark.9.png diff --git a/res/drawable-mdpi/progress_secondary_holo_light.9.png b/Backbone/src/main/res/drawable-mdpi/progress_secondary_holo_light.9.png similarity index 100% rename from res/drawable-mdpi/progress_secondary_holo_light.9.png rename to Backbone/src/main/res/drawable-mdpi/progress_secondary_holo_light.9.png diff --git a/Backbone/src/main/res/drawable-mdpi/spinner_ab_default_holo_dark.9.png b/Backbone/src/main/res/drawable-mdpi/spinner_ab_default_holo_dark.9.png new file mode 100644 index 000000000..9aeafee20 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/spinner_ab_default_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/spinner_ab_disabled_holo_dark.9.png b/Backbone/src/main/res/drawable-mdpi/spinner_ab_disabled_holo_dark.9.png new file mode 100644 index 000000000..88dd44151 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/spinner_ab_disabled_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/spinner_ab_focused_holo_dark.9.png b/Backbone/src/main/res/drawable-mdpi/spinner_ab_focused_holo_dark.9.png new file mode 100644 index 000000000..757f15b7c Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/spinner_ab_focused_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/spinner_ab_pressed_holo_dark.9.png b/Backbone/src/main/res/drawable-mdpi/spinner_ab_pressed_holo_dark.9.png new file mode 100644 index 000000000..73922383b Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/spinner_ab_pressed_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/tab_selected_focused_holo_dark.9.png b/Backbone/src/main/res/drawable-mdpi/tab_selected_focused_holo_dark.9.png new file mode 100644 index 000000000..084b4e027 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/tab_selected_focused_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/tab_selected_holo_dark.9.png b/Backbone/src/main/res/drawable-mdpi/tab_selected_holo_dark.9.png new file mode 100644 index 000000000..09d42dc82 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/tab_selected_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/tab_selected_pressed_holo_dark.9.png b/Backbone/src/main/res/drawable-mdpi/tab_selected_pressed_holo_dark.9.png new file mode 100644 index 000000000..d699a48b3 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/tab_selected_pressed_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/tab_unselected_focused_holo_dark.9.png b/Backbone/src/main/res/drawable-mdpi/tab_unselected_focused_holo_dark.9.png new file mode 100644 index 000000000..0fa12dbfe Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/tab_unselected_focused_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/tab_unselected_holo_dark.9.png b/Backbone/src/main/res/drawable-mdpi/tab_unselected_holo_dark.9.png new file mode 100644 index 000000000..ad2dbae95 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/tab_unselected_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-mdpi/tab_unselected_pressed_holo_dark.9.png b/Backbone/src/main/res/drawable-mdpi/tab_unselected_pressed_holo_dark.9.png new file mode 100644 index 000000000..2c13a5550 Binary files /dev/null and b/Backbone/src/main/res/drawable-mdpi/tab_unselected_pressed_holo_dark.9.png differ diff --git a/res/drawable-nodpi/bg_holo_background.9.png b/Backbone/src/main/res/drawable-nodpi/bg_holo_background.9.png similarity index 100% rename from res/drawable-nodpi/bg_holo_background.9.png rename to Backbone/src/main/res/drawable-nodpi/bg_holo_background.9.png diff --git a/res/drawable-nodpi/bg_holo_popup_background.9.png b/Backbone/src/main/res/drawable-nodpi/bg_holo_popup_background.9.png similarity index 100% rename from res/drawable-nodpi/bg_holo_popup_background.9.png rename to Backbone/src/main/res/drawable-nodpi/bg_holo_popup_background.9.png diff --git a/res/drawable-nodpi/bg_holo_selectionbar.9.png b/Backbone/src/main/res/drawable-nodpi/bg_holo_selectionbar.9.png similarity index 100% rename from res/drawable-nodpi/bg_holo_selectionbar.9.png rename to Backbone/src/main/res/drawable-nodpi/bg_holo_selectionbar.9.png diff --git a/res/drawable-nodpi/bg_holo_statusbar.9.png b/Backbone/src/main/res/drawable-nodpi/bg_holo_statusbar.9.png similarity index 100% rename from res/drawable-nodpi/bg_holo_statusbar.9.png rename to Backbone/src/main/res/drawable-nodpi/bg_holo_statusbar.9.png diff --git a/res/drawable-nodpi/bg_holo_titlebar.9.png b/Backbone/src/main/res/drawable-nodpi/bg_holo_titlebar.9.png similarity index 100% rename from res/drawable-nodpi/bg_holo_titlebar.9.png rename to Backbone/src/main/res/drawable-nodpi/bg_holo_titlebar.9.png diff --git a/themes/res/drawable-nodpi/dark_background.9.png b/Backbone/src/main/res/drawable-nodpi/dark_background.9.png similarity index 100% rename from themes/res/drawable-nodpi/dark_background.9.png rename to Backbone/src/main/res/drawable-nodpi/dark_background.9.png diff --git a/themes/res/drawable-nodpi/dark_popup_background.9.png b/Backbone/src/main/res/drawable-nodpi/dark_popup_background.9.png similarity index 100% rename from themes/res/drawable-nodpi/dark_popup_background.9.png rename to Backbone/src/main/res/drawable-nodpi/dark_popup_background.9.png diff --git a/themes/res/drawable-nodpi/dark_selectionbar.9.png b/Backbone/src/main/res/drawable-nodpi/dark_selectionbar.9.png similarity index 100% rename from themes/res/drawable-nodpi/dark_selectionbar.9.png rename to Backbone/src/main/res/drawable-nodpi/dark_selectionbar.9.png diff --git a/themes/res/drawable-nodpi/dark_statusbar.9.png b/Backbone/src/main/res/drawable-nodpi/dark_statusbar.9.png similarity index 100% rename from themes/res/drawable-nodpi/dark_statusbar.9.png rename to Backbone/src/main/res/drawable-nodpi/dark_statusbar.9.png diff --git a/themes/res/drawable-nodpi/dark_theme_no_preview.png b/Backbone/src/main/res/drawable-nodpi/dark_theme_no_preview.png similarity index 100% rename from themes/res/drawable-nodpi/dark_theme_no_preview.png rename to Backbone/src/main/res/drawable-nodpi/dark_theme_no_preview.png diff --git a/themes/res/drawable-nodpi/dark_theme_preview.png b/Backbone/src/main/res/drawable-nodpi/dark_theme_preview.png similarity index 100% rename from themes/res/drawable-nodpi/dark_theme_preview.png rename to Backbone/src/main/res/drawable-nodpi/dark_theme_preview.png diff --git a/themes/res/drawable-nodpi/dark_titlebar.9.png b/Backbone/src/main/res/drawable-nodpi/dark_titlebar.9.png similarity index 100% rename from themes/res/drawable-nodpi/dark_titlebar.9.png rename to Backbone/src/main/res/drawable-nodpi/dark_titlebar.9.png diff --git a/res/drawable-nodpi/theme_no_preview.png b/Backbone/src/main/res/drawable-nodpi/theme_no_preview.png similarity index 100% rename from res/drawable-nodpi/theme_no_preview.png rename to Backbone/src/main/res/drawable-nodpi/theme_no_preview.png diff --git a/res/drawable-nodpi/theme_preview.png b/Backbone/src/main/res/drawable-nodpi/theme_preview.png similarity index 100% rename from res/drawable-nodpi/theme_preview.png rename to Backbone/src/main/res/drawable-nodpi/theme_preview.png diff --git a/Backbone/src/main/res/drawable-xhdpi/ab_bottom_solid_holo_dark.9.png b/Backbone/src/main/res/drawable-xhdpi/ab_bottom_solid_holo_dark.9.png new file mode 100644 index 000000000..c33dd625d Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ab_bottom_solid_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ab_solid_holo_dark.9.png b/Backbone/src/main/res/drawable-xhdpi/ab_solid_holo_dark.9.png new file mode 100644 index 000000000..ccfc1fe84 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ab_solid_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ab_stacked_solid_holo_dark.9.png b/Backbone/src/main/res/drawable-xhdpi/ab_stacked_solid_holo_dark.9.png new file mode 100644 index 000000000..127541963 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ab_stacked_solid_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ab_texture_tile_holo_dark.png b/Backbone/src/main/res/drawable-xhdpi/ab_texture_tile_holo_dark.png new file mode 100644 index 000000000..7b2d780f9 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ab_texture_tile_holo_dark.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ab_transparent_holo_dark.9.png b/Backbone/src/main/res/drawable-xhdpi/ab_transparent_holo_dark.9.png new file mode 100644 index 000000000..671fa8452 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ab_transparent_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/btn_cab_done_default_holo_dark.9.png b/Backbone/src/main/res/drawable-xhdpi/btn_cab_done_default_holo_dark.9.png new file mode 100644 index 000000000..3521ae38d Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/btn_cab_done_default_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/btn_cab_done_focused_holo_dark.9.png b/Backbone/src/main/res/drawable-xhdpi/btn_cab_done_focused_holo_dark.9.png new file mode 100644 index 000000000..2ae6ea3f1 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/btn_cab_done_focused_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/btn_cab_done_pressed_holo_dark.9.png b/Backbone/src/main/res/drawable-xhdpi/btn_cab_done_pressed_holo_dark.9.png new file mode 100644 index 000000000..33b9d1da4 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/btn_cab_done_pressed_holo_dark.9.png differ diff --git a/themes/res/drawable-xhdpi/btn_holo_dark_check_off_normal.png b/Backbone/src/main/res/drawable-xhdpi/btn_holo_dark_check_off_normal.png similarity index 100% rename from themes/res/drawable-xhdpi/btn_holo_dark_check_off_normal.png rename to Backbone/src/main/res/drawable-xhdpi/btn_holo_dark_check_off_normal.png diff --git a/themes/res/drawable-xhdpi/btn_holo_dark_check_on_normal.png b/Backbone/src/main/res/drawable-xhdpi/btn_holo_dark_check_on_normal.png similarity index 100% rename from themes/res/drawable-xhdpi/btn_holo_dark_check_on_normal.png rename to Backbone/src/main/res/drawable-xhdpi/btn_holo_dark_check_on_normal.png diff --git a/themes/res/drawable-xhdpi/btn_holo_dark_check_on_normal_inverted.png b/Backbone/src/main/res/drawable-xhdpi/btn_holo_dark_check_on_normal_inverted.png similarity index 100% rename from themes/res/drawable-xhdpi/btn_holo_dark_check_on_normal_inverted.png rename to Backbone/src/main/res/drawable-xhdpi/btn_holo_dark_check_on_normal_inverted.png diff --git a/res/drawable-xhdpi/btn_holo_light_check_off_normal.png b/Backbone/src/main/res/drawable-xhdpi/btn_holo_light_check_off_normal.png similarity index 100% rename from res/drawable-xhdpi/btn_holo_light_check_off_normal.png rename to Backbone/src/main/res/drawable-xhdpi/btn_holo_light_check_off_normal.png diff --git a/res/drawable-xhdpi/btn_holo_light_check_on_normal.png b/Backbone/src/main/res/drawable-xhdpi/btn_holo_light_check_on_normal.png similarity index 100% rename from res/drawable-xhdpi/btn_holo_light_check_on_normal.png rename to Backbone/src/main/res/drawable-xhdpi/btn_holo_light_check_on_normal.png diff --git a/res/drawable-xhdpi/btn_holo_light_check_on_normal_inverted.png b/Backbone/src/main/res/drawable-xhdpi/btn_holo_light_check_on_normal_inverted.png similarity index 100% rename from res/drawable-xhdpi/btn_holo_light_check_on_normal_inverted.png rename to Backbone/src/main/res/drawable-xhdpi/btn_holo_light_check_on_normal_inverted.png diff --git a/Backbone/src/main/res/drawable-xhdpi/cab_background_bottom_holo_dark.9.png b/Backbone/src/main/res/drawable-xhdpi/cab_background_bottom_holo_dark.9.png new file mode 100644 index 000000000..ab3262ef2 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/cab_background_bottom_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/cab_background_top_holo_dark.9.png b/Backbone/src/main/res/drawable-xhdpi/cab_background_top_holo_dark.9.png new file mode 100644 index 000000000..7b0f83ce0 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/cab_background_top_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/dashclock_cloud.png b/Backbone/src/main/res/drawable-xhdpi/dashclock_cloud.png new file mode 100644 index 000000000..5a1603668 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/dashclock_cloud.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/dashclock_database.png b/Backbone/src/main/res/drawable-xhdpi/dashclock_database.png new file mode 100644 index 000000000..e594082ae Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/dashclock_database.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/dashclock_folder.png b/Backbone/src/main/res/drawable-xhdpi/dashclock_folder.png new file mode 100644 index 000000000..9a531cd27 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/dashclock_folder.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/dashclock_phone.png b/Backbone/src/main/res/drawable-xhdpi/dashclock_phone.png new file mode 100644 index 000000000..7d924b55c Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/dashclock_phone.png differ diff --git a/res/drawable-xhdpi/ic_holo_light_sdcard.png b/Backbone/src/main/res/drawable-xhdpi/dashclock_sd.png similarity index 100% rename from res/drawable-xhdpi/ic_holo_light_sdcard.png rename to Backbone/src/main/res/drawable-xhdpi/dashclock_sd.png diff --git a/res/drawable-xhdpi/ic_holo_light_usb.png b/Backbone/src/main/res/drawable-xhdpi/dashclock_usb.png similarity index 100% rename from res/drawable-xhdpi/ic_holo_light_usb.png rename to Backbone/src/main/res/drawable-xhdpi/dashclock_usb.png diff --git a/res/drawable-xhdpi/divider_horizontal_bright_opaque.9.png b/Backbone/src/main/res/drawable-xhdpi/divider_horizontal_bright_opaque.9.png similarity index 100% rename from res/drawable-xhdpi/divider_horizontal_bright_opaque.9.png rename to Backbone/src/main/res/drawable-xhdpi/divider_horizontal_bright_opaque.9.png diff --git a/themes/res/drawable-xhdpi/divider_horizontal_dark_opaque.9.png b/Backbone/src/main/res/drawable-xhdpi/divider_horizontal_dark_opaque.9.png similarity index 100% rename from themes/res/drawable-xhdpi/divider_horizontal_dark_opaque.9.png rename to Backbone/src/main/res/drawable-xhdpi/divider_horizontal_dark_opaque.9.png diff --git a/res/drawable-xhdpi/divider_vertical_bright_opaque.9.png b/Backbone/src/main/res/drawable-xhdpi/divider_vertical_bright_opaque.9.png similarity index 100% rename from res/drawable-xhdpi/divider_vertical_bright_opaque.9.png rename to Backbone/src/main/res/drawable-xhdpi/divider_vertical_bright_opaque.9.png diff --git a/themes/res/drawable-xhdpi/divider_vertical_dark_opaque.9.png b/Backbone/src/main/res/drawable-xhdpi/divider_vertical_dark_opaque.9.png similarity index 100% rename from themes/res/drawable-xhdpi/divider_vertical_dark_opaque.9.png rename to Backbone/src/main/res/drawable-xhdpi/divider_vertical_dark_opaque.9.png diff --git a/Backbone/src/main/res/drawable-xhdpi/drawer_shadow.9.png b/Backbone/src/main/res/drawable-xhdpi/drawer_shadow.9.png new file mode 100644 index 000000000..fa3d853e9 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/drawer_shadow.9.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark.png b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark.png new file mode 100644 index 000000000..0b8a6f09f Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_add.png b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_add.png new file mode 100644 index 000000000..e6f527978 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_add.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_copy.png b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_copy.png new file mode 100644 index 000000000..b32b2ffa5 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_copy.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_cut.png b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_cut.png new file mode 100644 index 000000000..b197d746b Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_cut.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_delete.png b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_delete.png new file mode 100644 index 000000000..1b4445551 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_delete.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_history.png b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_history.png new file mode 100644 index 000000000..a4ac42b7f Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_history.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_layout.png b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_layout.png new file mode 100644 index 000000000..04adebd5b Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_layout.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_lock_closed.png b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_lock_closed.png new file mode 100644 index 000000000..b92d8e5f9 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_lock_closed.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_lock_open.png b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_lock_open.png new file mode 100644 index 000000000..7f5810ab0 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_lock_open.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_overflow.png b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_overflow.png new file mode 100644 index 000000000..3f459fd00 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_overflow.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_paste.png b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_paste.png new file mode 100644 index 000000000..0821f62c1 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_paste.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_properties.png b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_properties.png new file mode 100644 index 000000000..eea71a791 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_properties.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_refresh.png b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_refresh.png new file mode 100644 index 000000000..166f3a3f2 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_refresh.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_save.png b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_save.png new file mode 100644 index 000000000..272060253 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_save.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_search.png b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_search.png new file mode 100644 index 000000000..a19183401 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_search.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_select_all.png b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_select_all.png new file mode 100644 index 000000000..96d26d64d Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_select_all.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_settings.png b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_settings.png new file mode 100644 index 000000000..429e369b7 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_settings.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_share.png b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_share.png new file mode 100644 index 000000000..c23b2f8b1 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_share.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_sort_alphabetically.png b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_sort_alphabetically.png new file mode 100644 index 000000000..2b38f73b1 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_sort_alphabetically.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_view.png b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_view.png new file mode 100644 index 000000000..913a362c0 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_view.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_warning.png b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_warning.png new file mode 100644 index 000000000..fb5421988 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_dark_warning.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_light_info.png b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_light_info.png new file mode 100644 index 000000000..0a6548f71 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_action_holo_light_info.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_drawer.png b/Backbone/src/main/res/drawable-xhdpi/ic_drawer.png new file mode 100644 index 000000000..b9bc3d70f Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_drawer.png differ diff --git a/res/drawable-xhdpi/ic_fso_default.png b/Backbone/src/main/res/drawable-xhdpi/ic_fso_default.png similarity index 100% rename from res/drawable-xhdpi/ic_fso_default.png rename to Backbone/src/main/res/drawable-xhdpi/ic_fso_default.png diff --git a/res/drawable-xhdpi/ic_fso_folder.png b/Backbone/src/main/res/drawable-xhdpi/ic_fso_folder.png similarity index 100% rename from res/drawable-xhdpi/ic_fso_folder.png rename to Backbone/src/main/res/drawable-xhdpi/ic_fso_folder.png diff --git a/res/drawable-xhdpi/ic_fso_type_app.png b/Backbone/src/main/res/drawable-xhdpi/ic_fso_type_app.png similarity index 100% rename from res/drawable-xhdpi/ic_fso_type_app.png rename to Backbone/src/main/res/drawable-xhdpi/ic_fso_type_app.png diff --git a/res/drawable-xhdpi/ic_fso_type_audio.png b/Backbone/src/main/res/drawable-xhdpi/ic_fso_type_audio.png similarity index 100% rename from res/drawable-xhdpi/ic_fso_type_audio.png rename to Backbone/src/main/res/drawable-xhdpi/ic_fso_type_audio.png diff --git a/res/drawable-xhdpi/ic_fso_type_binary.png b/Backbone/src/main/res/drawable-xhdpi/ic_fso_type_binary.png similarity index 100% rename from res/drawable-xhdpi/ic_fso_type_binary.png rename to Backbone/src/main/res/drawable-xhdpi/ic_fso_type_binary.png diff --git a/res/drawable-xhdpi/ic_fso_type_calendar.png b/Backbone/src/main/res/drawable-xhdpi/ic_fso_type_calendar.png similarity index 100% rename from res/drawable-xhdpi/ic_fso_type_calendar.png rename to Backbone/src/main/res/drawable-xhdpi/ic_fso_type_calendar.png diff --git a/res/drawable-xhdpi/ic_fso_type_cdimage.png b/Backbone/src/main/res/drawable-xhdpi/ic_fso_type_cdimage.png similarity index 100% rename from res/drawable-xhdpi/ic_fso_type_cdimage.png rename to Backbone/src/main/res/drawable-xhdpi/ic_fso_type_cdimage.png diff --git a/res/drawable-xhdpi/ic_fso_type_compress.png b/Backbone/src/main/res/drawable-xhdpi/ic_fso_type_compress.png similarity index 100% rename from res/drawable-xhdpi/ic_fso_type_compress.png rename to Backbone/src/main/res/drawable-xhdpi/ic_fso_type_compress.png diff --git a/res/drawable-xhdpi/ic_fso_type_contact.png b/Backbone/src/main/res/drawable-xhdpi/ic_fso_type_contact.png similarity index 100% rename from res/drawable-xhdpi/ic_fso_type_contact.png rename to Backbone/src/main/res/drawable-xhdpi/ic_fso_type_contact.png diff --git a/res/drawable-xhdpi/ic_fso_type_database.png b/Backbone/src/main/res/drawable-xhdpi/ic_fso_type_database.png similarity index 100% rename from res/drawable-xhdpi/ic_fso_type_database.png rename to Backbone/src/main/res/drawable-xhdpi/ic_fso_type_database.png diff --git a/res/drawable-xhdpi/ic_fso_type_document.png b/Backbone/src/main/res/drawable-xhdpi/ic_fso_type_document.png similarity index 100% rename from res/drawable-xhdpi/ic_fso_type_document.png rename to Backbone/src/main/res/drawable-xhdpi/ic_fso_type_document.png diff --git a/res/drawable-xhdpi/ic_fso_type_ebook.png b/Backbone/src/main/res/drawable-xhdpi/ic_fso_type_ebook.png similarity index 100% rename from res/drawable-xhdpi/ic_fso_type_ebook.png rename to Backbone/src/main/res/drawable-xhdpi/ic_fso_type_ebook.png diff --git a/res/drawable-xhdpi/ic_fso_type_email.png b/Backbone/src/main/res/drawable-xhdpi/ic_fso_type_email.png similarity index 100% rename from res/drawable-xhdpi/ic_fso_type_email.png rename to Backbone/src/main/res/drawable-xhdpi/ic_fso_type_email.png diff --git a/res/drawable-xhdpi/ic_fso_type_executable.png b/Backbone/src/main/res/drawable-xhdpi/ic_fso_type_executable.png similarity index 100% rename from res/drawable-xhdpi/ic_fso_type_executable.png rename to Backbone/src/main/res/drawable-xhdpi/ic_fso_type_executable.png diff --git a/res/drawable-xhdpi/ic_fso_type_feed.png b/Backbone/src/main/res/drawable-xhdpi/ic_fso_type_feed.png similarity index 100% rename from res/drawable-xhdpi/ic_fso_type_feed.png rename to Backbone/src/main/res/drawable-xhdpi/ic_fso_type_feed.png diff --git a/res/drawable-xhdpi/ic_fso_type_font.png b/Backbone/src/main/res/drawable-xhdpi/ic_fso_type_font.png similarity index 100% rename from res/drawable-xhdpi/ic_fso_type_font.png rename to Backbone/src/main/res/drawable-xhdpi/ic_fso_type_font.png diff --git a/res/drawable-xhdpi/ic_fso_type_image.png b/Backbone/src/main/res/drawable-xhdpi/ic_fso_type_image.png similarity index 100% rename from res/drawable-xhdpi/ic_fso_type_image.png rename to Backbone/src/main/res/drawable-xhdpi/ic_fso_type_image.png diff --git a/res/drawable-xhdpi/ic_fso_type_markup_document.png b/Backbone/src/main/res/drawable-xhdpi/ic_fso_type_markup_document.png similarity index 100% rename from res/drawable-xhdpi/ic_fso_type_markup_document.png rename to Backbone/src/main/res/drawable-xhdpi/ic_fso_type_markup_document.png diff --git a/res/drawable-xhdpi/ic_fso_type_pdf.png b/Backbone/src/main/res/drawable-xhdpi/ic_fso_type_pdf.png similarity index 100% rename from res/drawable-xhdpi/ic_fso_type_pdf.png rename to Backbone/src/main/res/drawable-xhdpi/ic_fso_type_pdf.png diff --git a/res/drawable-xhdpi/ic_fso_type_presentation.png b/Backbone/src/main/res/drawable-xhdpi/ic_fso_type_presentation.png similarity index 100% rename from res/drawable-xhdpi/ic_fso_type_presentation.png rename to Backbone/src/main/res/drawable-xhdpi/ic_fso_type_presentation.png diff --git a/res/drawable-xhdpi/ic_fso_type_security.png b/Backbone/src/main/res/drawable-xhdpi/ic_fso_type_security.png similarity index 100% rename from res/drawable-xhdpi/ic_fso_type_security.png rename to Backbone/src/main/res/drawable-xhdpi/ic_fso_type_security.png diff --git a/res/drawable-xhdpi/ic_fso_type_shell.png b/Backbone/src/main/res/drawable-xhdpi/ic_fso_type_shell.png similarity index 100% rename from res/drawable-xhdpi/ic_fso_type_shell.png rename to Backbone/src/main/res/drawable-xhdpi/ic_fso_type_shell.png diff --git a/res/drawable-xhdpi/ic_fso_type_source.png b/Backbone/src/main/res/drawable-xhdpi/ic_fso_type_source.png similarity index 100% rename from res/drawable-xhdpi/ic_fso_type_source.png rename to Backbone/src/main/res/drawable-xhdpi/ic_fso_type_source.png diff --git a/res/drawable-xhdpi/ic_fso_type_spreadsheet.png b/Backbone/src/main/res/drawable-xhdpi/ic_fso_type_spreadsheet.png similarity index 100% rename from res/drawable-xhdpi/ic_fso_type_spreadsheet.png rename to Backbone/src/main/res/drawable-xhdpi/ic_fso_type_spreadsheet.png diff --git a/res/drawable-xhdpi/ic_fso_type_system.png b/Backbone/src/main/res/drawable-xhdpi/ic_fso_type_system.png similarity index 100% rename from res/drawable-xhdpi/ic_fso_type_system.png rename to Backbone/src/main/res/drawable-xhdpi/ic_fso_type_system.png diff --git a/res/drawable-xhdpi/ic_fso_type_text.png b/Backbone/src/main/res/drawable-xhdpi/ic_fso_type_text.png similarity index 100% rename from res/drawable-xhdpi/ic_fso_type_text.png rename to Backbone/src/main/res/drawable-xhdpi/ic_fso_type_text.png diff --git a/res/drawable-xhdpi/ic_fso_type_video.png b/Backbone/src/main/res/drawable-xhdpi/ic_fso_type_video.png similarity index 100% rename from res/drawable-xhdpi/ic_fso_type_video.png rename to Backbone/src/main/res/drawable-xhdpi/ic_fso_type_video.png diff --git a/themes/res/drawable-xhdpi/ic_holo_dark_accept.png b/Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_accept.png similarity index 100% rename from themes/res/drawable-xhdpi/ic_holo_dark_accept.png rename to Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_accept.png diff --git a/themes/res/drawable-xhdpi/ic_holo_dark_bookmarks.png b/Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_bookmarks.png similarity index 100% rename from themes/res/drawable-xhdpi/ic_holo_dark_bookmarks.png rename to Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_bookmarks.png diff --git a/themes/res/drawable-xhdpi/ic_holo_dark_close.png b/Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_close.png similarity index 100% rename from themes/res/drawable-xhdpi/ic_holo_dark_close.png rename to Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_close.png diff --git a/themes/res/drawable-xhdpi/ic_holo_dark_config.png b/Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_config.png similarity index 100% rename from themes/res/drawable-xhdpi/ic_holo_dark_config.png rename to Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_config.png diff --git a/themes/res/drawable-xhdpi/ic_holo_dark_expander_close.png b/Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_expander_close.png similarity index 100% rename from themes/res/drawable-xhdpi/ic_holo_dark_expander_close.png rename to Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_expander_close.png diff --git a/themes/res/drawable-xhdpi/ic_holo_dark_expander_open.png b/Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_expander_open.png similarity index 100% rename from themes/res/drawable-xhdpi/ic_holo_dark_expander_open.png rename to Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_expander_open.png diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_filesystem.png b/Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_filesystem.png new file mode 100644 index 000000000..2caf74c95 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_filesystem.png differ diff --git a/themes/res/drawable-xhdpi/ic_holo_dark_fs_locked.png b/Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_fs_locked.png similarity index 100% rename from themes/res/drawable-xhdpi/ic_holo_dark_fs_locked.png rename to Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_fs_locked.png diff --git a/themes/res/drawable-xhdpi/ic_holo_dark_fs_unlocked.png b/Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_fs_unlocked.png similarity index 100% rename from themes/res/drawable-xhdpi/ic_holo_dark_fs_unlocked.png rename to Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_fs_unlocked.png diff --git a/themes/res/drawable-xhdpi/ic_holo_dark_fs_warning.png b/Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_fs_warning.png similarity index 100% rename from themes/res/drawable-xhdpi/ic_holo_dark_fs_warning.png rename to Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_fs_warning.png diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_home.png b/Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_home.png new file mode 100644 index 000000000..81f0835bc Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_home.png differ diff --git a/themes/res/drawable-xhdpi/ic_holo_dark_save.png b/Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_save.png similarity index 100% rename from themes/res/drawable-xhdpi/ic_holo_dark_save.png rename to Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_save.png diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_sdcard.png b/Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_sdcard.png new file mode 100644 index 000000000..105d22e57 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_sdcard.png differ diff --git a/themes/res/drawable-xhdpi/ic_holo_dark_tab.png b/Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_tab.png similarity index 100% rename from themes/res/drawable-xhdpi/ic_holo_dark_tab.png rename to Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_tab.png diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_usb.png b/Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_usb.png new file mode 100644 index 000000000..e54c1cea3 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_usb.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_user_defined_bookmark.png b/Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_user_defined_bookmark.png new file mode 100644 index 000000000..0b8a14d30 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_holo_dark_user_defined_bookmark.png differ diff --git a/res/drawable-xhdpi/ic_holo_light_accept.png b/Backbone/src/main/res/drawable-xhdpi/ic_holo_light_accept.png similarity index 100% rename from res/drawable-xhdpi/ic_holo_light_accept.png rename to Backbone/src/main/res/drawable-xhdpi/ic_holo_light_accept.png diff --git a/res/drawable-xhdpi/ic_holo_light_breadcrumb_divider.png b/Backbone/src/main/res/drawable-xhdpi/ic_holo_light_breadcrumb_divider.png similarity index 100% rename from res/drawable-xhdpi/ic_holo_light_breadcrumb_divider.png rename to Backbone/src/main/res/drawable-xhdpi/ic_holo_light_breadcrumb_divider.png diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_holo_light_copy.png b/Backbone/src/main/res/drawable-xhdpi/ic_holo_light_copy.png new file mode 100644 index 000000000..00bff33c7 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_holo_light_copy.png differ diff --git a/res/drawable-xhdpi/ic_holo_light_expander_close.png b/Backbone/src/main/res/drawable-xhdpi/ic_holo_light_expander_close.png similarity index 100% rename from res/drawable-xhdpi/ic_holo_light_expander_close.png rename to Backbone/src/main/res/drawable-xhdpi/ic_holo_light_expander_close.png diff --git a/res/drawable-xhdpi/ic_holo_light_expander_open.png b/Backbone/src/main/res/drawable-xhdpi/ic_holo_light_expander_open.png similarity index 100% rename from res/drawable-xhdpi/ic_holo_light_expander_open.png rename to Backbone/src/main/res/drawable-xhdpi/ic_holo_light_expander_open.png diff --git a/res/drawable-xhdpi/ic_holo_light_history_search.png b/Backbone/src/main/res/drawable-xhdpi/ic_holo_light_history_search.png similarity index 100% rename from res/drawable-xhdpi/ic_holo_light_history_search.png rename to Backbone/src/main/res/drawable-xhdpi/ic_holo_light_history_search.png diff --git a/res/drawable-xhdpi/ic_holo_light_save.png b/Backbone/src/main/res/drawable-xhdpi/ic_holo_light_save.png similarity index 100% rename from res/drawable-xhdpi/ic_holo_light_save.png rename to Backbone/src/main/res/drawable-xhdpi/ic_holo_light_save.png diff --git a/res/drawable-xhdpi/ic_holo_light_tab.png b/Backbone/src/main/res/drawable-xhdpi/ic_holo_light_tab.png similarity index 100% rename from res/drawable-xhdpi/ic_holo_light_tab.png rename to Backbone/src/main/res/drawable-xhdpi/ic_holo_light_tab.png diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_indicator_holo_dark_file.png b/Backbone/src/main/res/drawable-xhdpi/ic_indicator_holo_dark_file.png new file mode 100644 index 000000000..f45b6c991 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_indicator_holo_dark_file.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_indicator_holo_dark_folder.png b/Backbone/src/main/res/drawable-xhdpi/ic_indicator_holo_dark_folder.png new file mode 100644 index 000000000..10a815036 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_indicator_holo_dark_folder.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_launcher.png b/Backbone/src/main/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 000000000..68d7b9b1c Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_launcher.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_launcher_dashclock.png b/Backbone/src/main/res/drawable-xhdpi/ic_launcher_dashclock.png new file mode 100644 index 000000000..23eb8f182 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_launcher_dashclock.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_preference_black_about.png b/Backbone/src/main/res/drawable-xhdpi/ic_preference_black_about.png new file mode 100644 index 000000000..68ee63b4d Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_preference_black_about.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_preference_black_dashclock.png b/Backbone/src/main/res/drawable-xhdpi/ic_preference_black_dashclock.png new file mode 100644 index 000000000..3032058fa Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_preference_black_dashclock.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_preference_black_editor.png b/Backbone/src/main/res/drawable-xhdpi/ic_preference_black_editor.png new file mode 100644 index 000000000..b6667cb09 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_preference_black_editor.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_preference_black_general.png b/Backbone/src/main/res/drawable-xhdpi/ic_preference_black_general.png new file mode 100644 index 000000000..3d849eb98 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_preference_black_general.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/ic_preference_black_search.png b/Backbone/src/main/res/drawable-xhdpi/ic_preference_black_search.png new file mode 100644 index 000000000..1b450d14b Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/ic_preference_black_search.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/list_divider_holo_dark.9.png b/Backbone/src/main/res/drawable-xhdpi/list_divider_holo_dark.9.png new file mode 100644 index 000000000..e62f011d4 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/list_divider_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/list_divider_holo_light.9.png b/Backbone/src/main/res/drawable-xhdpi/list_divider_holo_light.9.png new file mode 100644 index 000000000..65061c0f4 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/list_divider_holo_light.9.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/list_focused_holo_dark.9.png b/Backbone/src/main/res/drawable-xhdpi/list_focused_holo_dark.9.png new file mode 100644 index 000000000..011da445d Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/list_focused_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/list_section_divider_holo_dark.9.png b/Backbone/src/main/res/drawable-xhdpi/list_section_divider_holo_dark.9.png new file mode 100644 index 000000000..7e249cbf4 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/list_section_divider_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/list_section_divider_holo_light.9.png b/Backbone/src/main/res/drawable-xhdpi/list_section_divider_holo_light.9.png new file mode 100644 index 000000000..4ad088ffc Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/list_section_divider_holo_light.9.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/menu_dropdown_panel_holo_dark.9.png b/Backbone/src/main/res/drawable-xhdpi/menu_dropdown_panel_holo_dark.9.png new file mode 100644 index 000000000..2974663c7 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/menu_dropdown_panel_holo_dark.9.png differ diff --git a/themes/res/drawable-xhdpi/progress_bg_holo_dark.9.png b/Backbone/src/main/res/drawable-xhdpi/progress_bg_holo_dark.9.png similarity index 100% rename from themes/res/drawable-xhdpi/progress_bg_holo_dark.9.png rename to Backbone/src/main/res/drawable-xhdpi/progress_bg_holo_dark.9.png diff --git a/themes/res/drawable-xhdpi/progress_primary_holo_dark.9.png b/Backbone/src/main/res/drawable-xhdpi/progress_primary_holo_dark.9.png similarity index 100% rename from themes/res/drawable-xhdpi/progress_primary_holo_dark.9.png rename to Backbone/src/main/res/drawable-xhdpi/progress_primary_holo_dark.9.png diff --git a/res/drawable-xhdpi/progress_primary_holo_light.9.png b/Backbone/src/main/res/drawable-xhdpi/progress_primary_holo_light.9.png similarity index 100% rename from res/drawable-xhdpi/progress_primary_holo_light.9.png rename to Backbone/src/main/res/drawable-xhdpi/progress_primary_holo_light.9.png diff --git a/themes/res/drawable-xhdpi/progress_secondary_holo_dark.9.png b/Backbone/src/main/res/drawable-xhdpi/progress_secondary_holo_dark.9.png similarity index 100% rename from themes/res/drawable-xhdpi/progress_secondary_holo_dark.9.png rename to Backbone/src/main/res/drawable-xhdpi/progress_secondary_holo_dark.9.png diff --git a/res/drawable-xhdpi/progress_secondary_holo_light.9.png b/Backbone/src/main/res/drawable-xhdpi/progress_secondary_holo_light.9.png similarity index 100% rename from res/drawable-xhdpi/progress_secondary_holo_light.9.png rename to Backbone/src/main/res/drawable-xhdpi/progress_secondary_holo_light.9.png diff --git a/Backbone/src/main/res/drawable-xhdpi/spinner_ab_default_holo_dark.9.png b/Backbone/src/main/res/drawable-xhdpi/spinner_ab_default_holo_dark.9.png new file mode 100644 index 000000000..14b1401de Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/spinner_ab_default_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/spinner_ab_disabled_holo_dark.9.png b/Backbone/src/main/res/drawable-xhdpi/spinner_ab_disabled_holo_dark.9.png new file mode 100644 index 000000000..c9dfbd605 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/spinner_ab_disabled_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/spinner_ab_focused_holo_dark.9.png b/Backbone/src/main/res/drawable-xhdpi/spinner_ab_focused_holo_dark.9.png new file mode 100644 index 000000000..1c8d33d0b Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/spinner_ab_focused_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/spinner_ab_pressed_holo_dark.9.png b/Backbone/src/main/res/drawable-xhdpi/spinner_ab_pressed_holo_dark.9.png new file mode 100644 index 000000000..870d2bec8 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/spinner_ab_pressed_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/tab_selected_focused_holo_dark.9.png b/Backbone/src/main/res/drawable-xhdpi/tab_selected_focused_holo_dark.9.png new file mode 100644 index 000000000..ae8e82b6a Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/tab_selected_focused_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/tab_selected_holo_dark.9.png b/Backbone/src/main/res/drawable-xhdpi/tab_selected_holo_dark.9.png new file mode 100644 index 000000000..34eb4ec00 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/tab_selected_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/tab_selected_pressed_holo_dark.9.png b/Backbone/src/main/res/drawable-xhdpi/tab_selected_pressed_holo_dark.9.png new file mode 100644 index 000000000..26d0bfc0e Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/tab_selected_pressed_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/tab_unselected_focused_holo_dark.9.png b/Backbone/src/main/res/drawable-xhdpi/tab_unselected_focused_holo_dark.9.png new file mode 100644 index 000000000..3c87dd9c0 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/tab_unselected_focused_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/tab_unselected_holo_dark.9.png b/Backbone/src/main/res/drawable-xhdpi/tab_unselected_holo_dark.9.png new file mode 100644 index 000000000..e9ab742e8 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/tab_unselected_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable-xhdpi/tab_unselected_pressed_holo_dark.9.png b/Backbone/src/main/res/drawable-xhdpi/tab_unselected_pressed_holo_dark.9.png new file mode 100644 index 000000000..fd190dbe2 Binary files /dev/null and b/Backbone/src/main/res/drawable-xhdpi/tab_unselected_pressed_holo_dark.9.png differ diff --git a/Backbone/src/main/res/drawable/ab_background_textured_holo_dark.xml b/Backbone/src/main/res/drawable/ab_background_textured_holo_dark.xml new file mode 100644 index 000000000..724630e10 --- /dev/null +++ b/Backbone/src/main/res/drawable/ab_background_textured_holo_dark.xml @@ -0,0 +1,21 @@ + + + + \ No newline at end of file diff --git a/Backbone/src/main/res/drawable/background_tab.xml b/Backbone/src/main/res/drawable/background_tab.xml new file mode 100644 index 000000000..885cf036a --- /dev/null +++ b/Backbone/src/main/res/drawable/background_tab.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Backbone/src/main/res/drawable/btn_cab_done_holo_dark.xml b/Backbone/src/main/res/drawable/btn_cab_done_holo_dark.xml new file mode 100644 index 000000000..755109896 --- /dev/null +++ b/Backbone/src/main/res/drawable/btn_cab_done_holo_dark.xml @@ -0,0 +1,27 @@ + + + + + + + + diff --git a/res/drawable/checkable_selector.xml b/Backbone/src/main/res/drawable/checkable_selector.xml similarity index 77% rename from res/drawable/checkable_selector.xml rename to Backbone/src/main/res/drawable/checkable_selector.xml index cac107b2e..d5a0328f4 100644 --- a/res/drawable/checkable_selector.xml +++ b/Backbone/src/main/res/drawable/checkable_selector.xml @@ -16,10 +16,10 @@ - - + + diff --git a/Backbone/src/main/res/drawable/dark_checkable_selector.xml b/Backbone/src/main/res/drawable/dark_checkable_selector.xml new file mode 100644 index 000000000..5c3971c3f --- /dev/null +++ b/Backbone/src/main/res/drawable/dark_checkable_selector.xml @@ -0,0 +1,25 @@ + + + + + + + + + diff --git a/Backbone/src/main/res/drawable/dark_holo_button_selector.xml b/Backbone/src/main/res/drawable/dark_holo_button_selector.xml new file mode 100644 index 000000000..3d7ed3ca5 --- /dev/null +++ b/Backbone/src/main/res/drawable/dark_holo_button_selector.xml @@ -0,0 +1,30 @@ + + + + + + + + + + diff --git a/Backbone/src/main/res/drawable/dark_holo_list_selector_deselected.xml b/Backbone/src/main/res/drawable/dark_holo_list_selector_deselected.xml new file mode 100644 index 000000000..3c25ef22c --- /dev/null +++ b/Backbone/src/main/res/drawable/dark_holo_list_selector_deselected.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + diff --git a/Backbone/src/main/res/drawable/dark_holo_list_selector_selected.xml b/Backbone/src/main/res/drawable/dark_holo_list_selector_selected.xml new file mode 100644 index 000000000..c85bd8958 --- /dev/null +++ b/Backbone/src/main/res/drawable/dark_holo_list_selector_selected.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + diff --git a/Backbone/src/main/res/drawable/dark_holo_popup_selector.xml b/Backbone/src/main/res/drawable/dark_holo_popup_selector.xml new file mode 100644 index 000000000..39f77e9b1 --- /dev/null +++ b/Backbone/src/main/res/drawable/dark_holo_popup_selector.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + diff --git a/Backbone/src/main/res/drawable/dark_holo_selection.xml b/Backbone/src/main/res/drawable/dark_holo_selection.xml new file mode 100644 index 000000000..b0c4b129f --- /dev/null +++ b/Backbone/src/main/res/drawable/dark_holo_selection.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + diff --git a/Backbone/src/main/res/drawable/dark_progress_horizontal_holo.xml b/Backbone/src/main/res/drawable/dark_progress_horizontal_holo.xml new file mode 100644 index 000000000..55ec57e18 --- /dev/null +++ b/Backbone/src/main/res/drawable/dark_progress_horizontal_holo.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + diff --git a/res/drawable/fso_type_app.xml b/Backbone/src/main/res/drawable/fso_type_app.xml similarity index 86% rename from res/drawable/fso_type_app.xml rename to Backbone/src/main/res/drawable/fso_type_app.xml index dfac60d00..5744de679 100644 --- a/res/drawable/fso_type_app.xml +++ b/Backbone/src/main/res/drawable/fso_type_app.xml @@ -14,9 +14,9 @@ limitations under the License. --> - + - - + + diff --git a/res/drawable/fso_type_audio.xml b/Backbone/src/main/res/drawable/fso_type_audio.xml similarity index 85% rename from res/drawable/fso_type_audio.xml rename to Backbone/src/main/res/drawable/fso_type_audio.xml index 5ec1e7781..388408ab5 100644 --- a/res/drawable/fso_type_audio.xml +++ b/Backbone/src/main/res/drawable/fso_type_audio.xml @@ -14,9 +14,9 @@ limitations under the License. --> - + - - + + diff --git a/res/drawable/fso_type_binary.xml b/Backbone/src/main/res/drawable/fso_type_binary.xml similarity index 85% rename from res/drawable/fso_type_binary.xml rename to Backbone/src/main/res/drawable/fso_type_binary.xml index 089715f27..3cd5305b8 100644 --- a/res/drawable/fso_type_binary.xml +++ b/Backbone/src/main/res/drawable/fso_type_binary.xml @@ -14,9 +14,9 @@ limitations under the License. --> - + - - + + diff --git a/res/drawable/fso_type_calendar.xml b/Backbone/src/main/res/drawable/fso_type_calendar.xml similarity index 85% rename from res/drawable/fso_type_calendar.xml rename to Backbone/src/main/res/drawable/fso_type_calendar.xml index 318b05f9b..2e52b5f45 100644 --- a/res/drawable/fso_type_calendar.xml +++ b/Backbone/src/main/res/drawable/fso_type_calendar.xml @@ -14,9 +14,9 @@ limitations under the License. --> - + - - + + diff --git a/res/drawable/fso_type_cdimage.xml b/Backbone/src/main/res/drawable/fso_type_cdimage.xml similarity index 85% rename from res/drawable/fso_type_cdimage.xml rename to Backbone/src/main/res/drawable/fso_type_cdimage.xml index a1ea80bf8..2f5fd2174 100644 --- a/res/drawable/fso_type_cdimage.xml +++ b/Backbone/src/main/res/drawable/fso_type_cdimage.xml @@ -14,9 +14,9 @@ limitations under the License. --> - + - - + + diff --git a/res/drawable/fso_type_compress.xml b/Backbone/src/main/res/drawable/fso_type_compress.xml similarity index 85% rename from res/drawable/fso_type_compress.xml rename to Backbone/src/main/res/drawable/fso_type_compress.xml index e64f53748..a347c6559 100644 --- a/res/drawable/fso_type_compress.xml +++ b/Backbone/src/main/res/drawable/fso_type_compress.xml @@ -14,9 +14,9 @@ limitations under the License. --> - + - - + + diff --git a/res/drawable/fso_type_contact.xml b/Backbone/src/main/res/drawable/fso_type_contact.xml similarity index 85% rename from res/drawable/fso_type_contact.xml rename to Backbone/src/main/res/drawable/fso_type_contact.xml index f87a7d5d0..dad050e27 100644 --- a/res/drawable/fso_type_contact.xml +++ b/Backbone/src/main/res/drawable/fso_type_contact.xml @@ -14,9 +14,9 @@ limitations under the License. --> - + - - + + diff --git a/res/drawable/fso_type_database.xml b/Backbone/src/main/res/drawable/fso_type_database.xml similarity index 85% rename from res/drawable/fso_type_database.xml rename to Backbone/src/main/res/drawable/fso_type_database.xml index 09ef9d478..3f7cfd21c 100644 --- a/res/drawable/fso_type_database.xml +++ b/Backbone/src/main/res/drawable/fso_type_database.xml @@ -14,9 +14,9 @@ limitations under the License. --> - + - - + + diff --git a/res/drawable/fso_type_document.xml b/Backbone/src/main/res/drawable/fso_type_document.xml similarity index 85% rename from res/drawable/fso_type_document.xml rename to Backbone/src/main/res/drawable/fso_type_document.xml index cd98ac538..0cd2c2b9c 100644 --- a/res/drawable/fso_type_document.xml +++ b/Backbone/src/main/res/drawable/fso_type_document.xml @@ -14,9 +14,9 @@ limitations under the License. --> - + - - + + diff --git a/res/drawable/fso_type_ebook.xml b/Backbone/src/main/res/drawable/fso_type_ebook.xml similarity index 85% rename from res/drawable/fso_type_ebook.xml rename to Backbone/src/main/res/drawable/fso_type_ebook.xml index 73dcccb27..25b8204b7 100644 --- a/res/drawable/fso_type_ebook.xml +++ b/Backbone/src/main/res/drawable/fso_type_ebook.xml @@ -14,9 +14,9 @@ limitations under the License. --> - + - - + + diff --git a/res/drawable/fso_type_email.xml b/Backbone/src/main/res/drawable/fso_type_email.xml similarity index 85% rename from res/drawable/fso_type_email.xml rename to Backbone/src/main/res/drawable/fso_type_email.xml index b945ead56..94af058b5 100644 --- a/res/drawable/fso_type_email.xml +++ b/Backbone/src/main/res/drawable/fso_type_email.xml @@ -14,9 +14,9 @@ limitations under the License. --> - + - - + + diff --git a/res/drawable/fso_type_executable.xml b/Backbone/src/main/res/drawable/fso_type_executable.xml similarity index 85% rename from res/drawable/fso_type_executable.xml rename to Backbone/src/main/res/drawable/fso_type_executable.xml index da0322849..501550912 100644 --- a/res/drawable/fso_type_executable.xml +++ b/Backbone/src/main/res/drawable/fso_type_executable.xml @@ -14,9 +14,9 @@ limitations under the License. --> - + - - + + diff --git a/res/drawable/fso_type_feed.xml b/Backbone/src/main/res/drawable/fso_type_feed.xml similarity index 85% rename from res/drawable/fso_type_feed.xml rename to Backbone/src/main/res/drawable/fso_type_feed.xml index 06a05412c..ab04d7509 100644 --- a/res/drawable/fso_type_feed.xml +++ b/Backbone/src/main/res/drawable/fso_type_feed.xml @@ -14,9 +14,9 @@ limitations under the License. --> - + - - + + diff --git a/res/drawable/fso_type_font.xml b/Backbone/src/main/res/drawable/fso_type_font.xml similarity index 85% rename from res/drawable/fso_type_font.xml rename to Backbone/src/main/res/drawable/fso_type_font.xml index a28e384d1..441d5bdb1 100644 --- a/res/drawable/fso_type_font.xml +++ b/Backbone/src/main/res/drawable/fso_type_font.xml @@ -14,9 +14,9 @@ limitations under the License. --> - + - - + + diff --git a/res/drawable/fso_type_image.xml b/Backbone/src/main/res/drawable/fso_type_image.xml similarity index 85% rename from res/drawable/fso_type_image.xml rename to Backbone/src/main/res/drawable/fso_type_image.xml index 1fbd6b80c..8669b762b 100644 --- a/res/drawable/fso_type_image.xml +++ b/Backbone/src/main/res/drawable/fso_type_image.xml @@ -14,9 +14,9 @@ limitations under the License. --> - + - - + + diff --git a/res/drawable/fso_type_markup_document.xml b/Backbone/src/main/res/drawable/fso_type_markup_document.xml similarity index 84% rename from res/drawable/fso_type_markup_document.xml rename to Backbone/src/main/res/drawable/fso_type_markup_document.xml index e2c510957..d038e0876 100644 --- a/res/drawable/fso_type_markup_document.xml +++ b/Backbone/src/main/res/drawable/fso_type_markup_document.xml @@ -14,9 +14,9 @@ limitations under the License. --> - + - - + + diff --git a/res/drawable/fso_type_pdf.xml b/Backbone/src/main/res/drawable/fso_type_pdf.xml similarity index 86% rename from res/drawable/fso_type_pdf.xml rename to Backbone/src/main/res/drawable/fso_type_pdf.xml index 748fdaa83..b79bbc3b2 100644 --- a/res/drawable/fso_type_pdf.xml +++ b/Backbone/src/main/res/drawable/fso_type_pdf.xml @@ -14,9 +14,9 @@ limitations under the License. --> - + - - + + diff --git a/res/drawable/fso_type_presentation.xml b/Backbone/src/main/res/drawable/fso_type_presentation.xml similarity index 85% rename from res/drawable/fso_type_presentation.xml rename to Backbone/src/main/res/drawable/fso_type_presentation.xml index c1aed9d7f..13093c746 100644 --- a/res/drawable/fso_type_presentation.xml +++ b/Backbone/src/main/res/drawable/fso_type_presentation.xml @@ -14,9 +14,9 @@ limitations under the License. --> - + - - + + diff --git a/res/drawable/fso_type_security.xml b/Backbone/src/main/res/drawable/fso_type_security.xml similarity index 85% rename from res/drawable/fso_type_security.xml rename to Backbone/src/main/res/drawable/fso_type_security.xml index aaabb5291..7fe1ddc88 100644 --- a/res/drawable/fso_type_security.xml +++ b/Backbone/src/main/res/drawable/fso_type_security.xml @@ -14,9 +14,9 @@ limitations under the License. --> - + - - + + diff --git a/res/drawable/fso_type_shell.xml b/Backbone/src/main/res/drawable/fso_type_shell.xml similarity index 85% rename from res/drawable/fso_type_shell.xml rename to Backbone/src/main/res/drawable/fso_type_shell.xml index d7e439072..b14823721 100644 --- a/res/drawable/fso_type_shell.xml +++ b/Backbone/src/main/res/drawable/fso_type_shell.xml @@ -14,9 +14,9 @@ limitations under the License. --> - + - - + + diff --git a/res/drawable/fso_type_source.xml b/Backbone/src/main/res/drawable/fso_type_source.xml similarity index 85% rename from res/drawable/fso_type_source.xml rename to Backbone/src/main/res/drawable/fso_type_source.xml index 32702700d..0ed7e8349 100644 --- a/res/drawable/fso_type_source.xml +++ b/Backbone/src/main/res/drawable/fso_type_source.xml @@ -14,9 +14,9 @@ limitations under the License. --> - + - - + + diff --git a/res/drawable/fso_type_spreadsheet.xml b/Backbone/src/main/res/drawable/fso_type_spreadsheet.xml similarity index 85% rename from res/drawable/fso_type_spreadsheet.xml rename to Backbone/src/main/res/drawable/fso_type_spreadsheet.xml index d53d292b8..04dc0910c 100644 --- a/res/drawable/fso_type_spreadsheet.xml +++ b/Backbone/src/main/res/drawable/fso_type_spreadsheet.xml @@ -14,9 +14,9 @@ limitations under the License. --> - + - - + + diff --git a/res/drawable/fso_type_system.xml b/Backbone/src/main/res/drawable/fso_type_system.xml similarity index 85% rename from res/drawable/fso_type_system.xml rename to Backbone/src/main/res/drawable/fso_type_system.xml index 4f17694c0..77d7bb3b3 100644 --- a/res/drawable/fso_type_system.xml +++ b/Backbone/src/main/res/drawable/fso_type_system.xml @@ -14,9 +14,9 @@ limitations under the License. --> - + - - + + diff --git a/res/drawable/fso_type_text.xml b/Backbone/src/main/res/drawable/fso_type_text.xml similarity index 85% rename from res/drawable/fso_type_text.xml rename to Backbone/src/main/res/drawable/fso_type_text.xml index c55106454..b074685ee 100644 --- a/res/drawable/fso_type_text.xml +++ b/Backbone/src/main/res/drawable/fso_type_text.xml @@ -14,9 +14,9 @@ limitations under the License. --> - + - - + + diff --git a/res/drawable/fso_type_video.xml b/Backbone/src/main/res/drawable/fso_type_video.xml similarity index 85% rename from res/drawable/fso_type_video.xml rename to Backbone/src/main/res/drawable/fso_type_video.xml index 1b81c6f19..6b4c9158a 100644 --- a/res/drawable/fso_type_video.xml +++ b/Backbone/src/main/res/drawable/fso_type_video.xml @@ -14,9 +14,9 @@ limitations under the License. --> - + - - + + diff --git a/Backbone/src/main/res/drawable/holo_button_selector.xml b/Backbone/src/main/res/drawable/holo_button_selector.xml new file mode 100644 index 000000000..3d5ea459f --- /dev/null +++ b/Backbone/src/main/res/drawable/holo_button_selector.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + diff --git a/Backbone/src/main/res/drawable/holo_list_selector_deselected.xml b/Backbone/src/main/res/drawable/holo_list_selector_deselected.xml new file mode 100644 index 000000000..88c918119 --- /dev/null +++ b/Backbone/src/main/res/drawable/holo_list_selector_deselected.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + diff --git a/Backbone/src/main/res/drawable/holo_list_selector_selected.xml b/Backbone/src/main/res/drawable/holo_list_selector_selected.xml new file mode 100644 index 000000000..4cf3ec154 --- /dev/null +++ b/Backbone/src/main/res/drawable/holo_list_selector_selected.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + diff --git a/Backbone/src/main/res/drawable/holo_popup_selector.xml b/Backbone/src/main/res/drawable/holo_popup_selector.xml new file mode 100644 index 000000000..89e715876 --- /dev/null +++ b/Backbone/src/main/res/drawable/holo_popup_selector.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + diff --git a/Backbone/src/main/res/drawable/holo_selection.xml b/Backbone/src/main/res/drawable/holo_selection.xml new file mode 100644 index 000000000..3fe216e15 --- /dev/null +++ b/Backbone/src/main/res/drawable/holo_selection.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + diff --git a/Backbone/src/main/res/drawable/holo_selector.xml b/Backbone/src/main/res/drawable/holo_selector.xml new file mode 100644 index 000000000..4ff5f603e --- /dev/null +++ b/Backbone/src/main/res/drawable/holo_selector.xml @@ -0,0 +1,30 @@ + + + + + + + + + + diff --git a/Backbone/src/main/res/drawable/holo_selector_nonfocusable.xml b/Backbone/src/main/res/drawable/holo_selector_nonfocusable.xml new file mode 100644 index 000000000..f84d745f8 --- /dev/null +++ b/Backbone/src/main/res/drawable/holo_selector_nonfocusable.xml @@ -0,0 +1,26 @@ + + + + + + + + + diff --git a/Backbone/src/main/res/drawable/pressed_background_holo_dark.xml b/Backbone/src/main/res/drawable/pressed_background_holo_dark.xml new file mode 100644 index 000000000..f5c4cdeb6 --- /dev/null +++ b/Backbone/src/main/res/drawable/pressed_background_holo_dark.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/Backbone/src/main/res/drawable/progress_horizontal_holo_dark.xml b/Backbone/src/main/res/drawable/progress_horizontal_holo_dark.xml new file mode 100644 index 000000000..2a65389ca --- /dev/null +++ b/Backbone/src/main/res/drawable/progress_horizontal_holo_dark.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + diff --git a/Backbone/src/main/res/drawable/selectable_background_holo_dark.xml b/Backbone/src/main/res/drawable/selectable_background_holo_dark.xml new file mode 100644 index 000000000..17120725e --- /dev/null +++ b/Backbone/src/main/res/drawable/selectable_background_holo_dark.xml @@ -0,0 +1,26 @@ + + + + + + + + \ No newline at end of file diff --git a/Backbone/src/main/res/drawable/spinner_background_ab_holo_dark.xml b/Backbone/src/main/res/drawable/spinner_background_ab_holo_dark.xml new file mode 100644 index 000000000..65634656a --- /dev/null +++ b/Backbone/src/main/res/drawable/spinner_background_ab_holo_dark.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/Backbone/src/main/res/drawable/tab_indicator_ab_holo_dark.xml b/Backbone/src/main/res/drawable/tab_indicator_ab_holo_dark.xml new file mode 100644 index 000000000..9c7d6eb34 --- /dev/null +++ b/Backbone/src/main/res/drawable/tab_indicator_ab_holo_dark.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Backbone/src/main/res/layout-land/theme_view.xml b/Backbone/src/main/res/layout-land/theme_view.xml new file mode 100644 index 000000000..f9c997003 --- /dev/null +++ b/Backbone/src/main/res/layout-land/theme_view.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Backbone/src/main/res/layout-sw600dp/theme_view.xml b/Backbone/src/main/res/layout-sw600dp/theme_view.xml new file mode 100644 index 000000000..0ce21151c --- /dev/null +++ b/Backbone/src/main/res/layout-sw600dp/theme_view.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + diff --git a/Backbone/src/main/res/layout-sw720dp/theme_view.xml b/Backbone/src/main/res/layout-sw720dp/theme_view.xml new file mode 100644 index 000000000..0ce21151c --- /dev/null +++ b/Backbone/src/main/res/layout-sw720dp/theme_view.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + diff --git a/Backbone/src/main/res/layout/actionbar_indeterminate_progress.xml b/Backbone/src/main/res/layout/actionbar_indeterminate_progress.xml new file mode 100644 index 000000000..51c43a053 --- /dev/null +++ b/Backbone/src/main/res/layout/actionbar_indeterminate_progress.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/Backbone/src/main/res/layout/associations_dialog.xml b/Backbone/src/main/res/layout/associations_dialog.xml new file mode 100644 index 000000000..7c62d0e87 --- /dev/null +++ b/Backbone/src/main/res/layout/associations_dialog.xml @@ -0,0 +1,38 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Backbone/src/main/res/layout/associations_item.xml b/Backbone/src/main/res/layout/associations_item.xml new file mode 100644 index 000000000..1c42f1cf8 --- /dev/null +++ b/Backbone/src/main/res/layout/associations_item.xml @@ -0,0 +1,44 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Backbone/src/main/res/layout/bookmarks.xml b/Backbone/src/main/res/layout/bookmarks.xml new file mode 100644 index 000000000..d5988fdf3 --- /dev/null +++ b/Backbone/src/main/res/layout/bookmarks.xml @@ -0,0 +1,28 @@ + + + + + + + + \ No newline at end of file diff --git a/Backbone/src/main/res/layout/bookmarks_fragment.xml b/Backbone/src/main/res/layout/bookmarks_fragment.xml new file mode 100644 index 000000000..b15774deb --- /dev/null +++ b/Backbone/src/main/res/layout/bookmarks_fragment.xml @@ -0,0 +1,36 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Backbone/src/main/res/layout/bookmarks_item.xml b/Backbone/src/main/res/layout/bookmarks_item.xml new file mode 100644 index 000000000..381be3efd --- /dev/null +++ b/Backbone/src/main/res/layout/bookmarks_item.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Backbone/src/main/res/layout/breadcrumb_spinner.xml b/Backbone/src/main/res/layout/breadcrumb_spinner.xml new file mode 100644 index 000000000..39a835eca --- /dev/null +++ b/Backbone/src/main/res/layout/breadcrumb_spinner.xml @@ -0,0 +1,22 @@ + + + + \ No newline at end of file diff --git a/Backbone/src/main/res/layout/breadcrumb_spinner_dropdown_item.xml b/Backbone/src/main/res/layout/breadcrumb_spinner_dropdown_item.xml new file mode 100644 index 000000000..2d144a3cf --- /dev/null +++ b/Backbone/src/main/res/layout/breadcrumb_spinner_dropdown_item.xml @@ -0,0 +1,21 @@ + + + + \ No newline at end of file diff --git a/Backbone/src/main/res/layout/breadcrumb_spinner_selected_item.xml b/Backbone/src/main/res/layout/breadcrumb_spinner_selected_item.xml new file mode 100644 index 000000000..ecbfdd925 --- /dev/null +++ b/Backbone/src/main/res/layout/breadcrumb_spinner_selected_item.xml @@ -0,0 +1,33 @@ + + + + + + + + + diff --git a/Backbone/src/main/res/layout/color_picker_pref_item.xml b/Backbone/src/main/res/layout/color_picker_pref_item.xml new file mode 100644 index 000000000..4e0b19737 --- /dev/null +++ b/Backbone/src/main/res/layout/color_picker_pref_item.xml @@ -0,0 +1,30 @@ + + + + + + + diff --git a/Backbone/src/main/res/layout/compute_checksum_dialog.xml b/Backbone/src/main/res/layout/compute_checksum_dialog.xml new file mode 100644 index 000000000..7f9e14c34 --- /dev/null +++ b/Backbone/src/main/res/layout/compute_checksum_dialog.xml @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Backbone/src/main/res/layout/dialog_message.xml b/Backbone/src/main/res/layout/dialog_message.xml new file mode 100644 index 000000000..de4c71511 --- /dev/null +++ b/Backbone/src/main/res/layout/dialog_message.xml @@ -0,0 +1,31 @@ + + + + + + + + \ No newline at end of file diff --git a/Backbone/src/main/res/layout/dialog_title.xml b/Backbone/src/main/res/layout/dialog_title.xml new file mode 100644 index 000000000..0f0929336 --- /dev/null +++ b/Backbone/src/main/res/layout/dialog_title.xml @@ -0,0 +1,41 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Backbone/src/main/res/layout/editor.xml b/Backbone/src/main/res/layout/editor.xml new file mode 100644 index 000000000..1ee9dd40e --- /dev/null +++ b/Backbone/src/main/res/layout/editor.xml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Backbone/src/main/res/layout/execution_dialog.xml b/Backbone/src/main/res/layout/execution_dialog.xml new file mode 100644 index 000000000..aa075a079 --- /dev/null +++ b/Backbone/src/main/res/layout/execution_dialog.xml @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Backbone/src/main/res/layout/filesystem_info_dialog.xml b/Backbone/src/main/res/layout/filesystem_info_dialog.xml new file mode 100644 index 000000000..26ee75ea6 --- /dev/null +++ b/Backbone/src/main/res/layout/filesystem_info_dialog.xml @@ -0,0 +1,354 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Backbone/src/main/res/layout/fso_properties_dialog.xml b/Backbone/src/main/res/layout/fso_properties_dialog.xml new file mode 100644 index 000000000..a4a1464ef --- /dev/null +++ b/Backbone/src/main/res/layout/fso_properties_dialog.xml @@ -0,0 +1,671 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Backbone/src/main/res/layout/fso_properties_drawer.xml b/Backbone/src/main/res/layout/fso_properties_drawer.xml new file mode 100644 index 000000000..e99f98bd5 --- /dev/null +++ b/Backbone/src/main/res/layout/fso_properties_drawer.xml @@ -0,0 +1,632 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Backbone/src/main/res/layout/history.xml b/Backbone/src/main/res/layout/history.xml new file mode 100644 index 000000000..d5988fdf3 --- /dev/null +++ b/Backbone/src/main/res/layout/history.xml @@ -0,0 +1,28 @@ + + + + + + + + \ No newline at end of file diff --git a/Backbone/src/main/res/layout/history_fragment.xml b/Backbone/src/main/res/layout/history_fragment.xml new file mode 100644 index 000000000..233d734cf --- /dev/null +++ b/Backbone/src/main/res/layout/history_fragment.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Backbone/src/main/res/layout/history_item.xml b/Backbone/src/main/res/layout/history_item.xml new file mode 100644 index 000000000..0d55f3c5e --- /dev/null +++ b/Backbone/src/main/res/layout/history_item.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/horizontal_divider.xml b/Backbone/src/main/res/layout/horizontal_divider.xml similarity index 82% rename from res/layout/horizontal_divider.xml rename to Backbone/src/main/res/layout/horizontal_divider.xml index ea8833633..2c13248b6 100644 --- a/res/layout/horizontal_divider.xml +++ b/Backbone/src/main/res/layout/horizontal_divider.xml @@ -15,6 +15,6 @@ --> + style="@style/horizontal_divider" + android:layout_width="@dimen/horizontal_divider_width" + android:layout_height="match_parent"/> diff --git a/Backbone/src/main/res/layout/initial_directory.xml b/Backbone/src/main/res/layout/initial_directory.xml new file mode 100644 index 000000000..5a2f0f067 --- /dev/null +++ b/Backbone/src/main/res/layout/initial_directory.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/Backbone/src/main/res/layout/inline_autocomplete.xml b/Backbone/src/main/res/layout/inline_autocomplete.xml new file mode 100644 index 000000000..69db30f20 --- /dev/null +++ b/Backbone/src/main/res/layout/inline_autocomplete.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Backbone/src/main/res/layout/input_name_dialog.xml b/Backbone/src/main/res/layout/input_name_dialog.xml new file mode 100644 index 000000000..3b8790e7d --- /dev/null +++ b/Backbone/src/main/res/layout/input_name_dialog.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Backbone/src/main/res/layout/license_item.xml b/Backbone/src/main/res/layout/license_item.xml new file mode 100644 index 000000000..3577d5209 --- /dev/null +++ b/Backbone/src/main/res/layout/license_item.xml @@ -0,0 +1,45 @@ + + + + + + + \ No newline at end of file diff --git a/Backbone/src/main/res/layout/licenses.xml b/Backbone/src/main/res/layout/licenses.xml new file mode 100644 index 000000000..a87a27ba3 --- /dev/null +++ b/Backbone/src/main/res/layout/licenses.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Backbone/src/main/res/layout/menu_frame_holder.xml b/Backbone/src/main/res/layout/menu_frame_holder.xml new file mode 100644 index 000000000..5284f72f3 --- /dev/null +++ b/Backbone/src/main/res/layout/menu_frame_holder.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/Backbone/src/main/res/layout/menu_item.xml b/Backbone/src/main/res/layout/menu_item.xml new file mode 100644 index 000000000..1a7b47fa8 --- /dev/null +++ b/Backbone/src/main/res/layout/menu_item.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + diff --git a/res/layout/menu_item_separator.xml b/Backbone/src/main/res/layout/menu_item_separator.xml similarity index 78% rename from res/layout/menu_item_separator.xml rename to Backbone/src/main/res/layout/menu_item_separator.xml index 90d12e315..58c623dc4 100644 --- a/res/layout/menu_item_separator.xml +++ b/Backbone/src/main/res/layout/menu_item_separator.xml @@ -18,12 +18,12 @@ it is not resolved in themed context, because theme context and app context are not the same --> + android:layout_width="wrap_content" + android:layout_height="0.75dp"> - + diff --git a/Backbone/src/main/res/layout/message_progress_dialog.xml b/Backbone/src/main/res/layout/message_progress_dialog.xml new file mode 100644 index 000000000..68b230f7b --- /dev/null +++ b/Backbone/src/main/res/layout/message_progress_dialog.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + diff --git a/Backbone/src/main/res/layout/navigation.xml b/Backbone/src/main/res/layout/navigation.xml new file mode 100644 index 000000000..ed222e2bd --- /dev/null +++ b/Backbone/src/main/res/layout/navigation.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Backbone/src/main/res/layout/navigation_action_mode.xml b/Backbone/src/main/res/layout/navigation_action_mode.xml new file mode 100644 index 000000000..c138e0aa8 --- /dev/null +++ b/Backbone/src/main/res/layout/navigation_action_mode.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Backbone/src/main/res/layout/navigation_fragment.xml b/Backbone/src/main/res/layout/navigation_fragment.xml new file mode 100644 index 000000000..85b1aa5bf --- /dev/null +++ b/Backbone/src/main/res/layout/navigation_fragment.xml @@ -0,0 +1,25 @@ + + + + + + diff --git a/Backbone/src/main/res/layout/navigation_pager.xml b/Backbone/src/main/res/layout/navigation_pager.xml new file mode 100644 index 000000000..e72a54fe5 --- /dev/null +++ b/Backbone/src/main/res/layout/navigation_pager.xml @@ -0,0 +1,26 @@ + + + + + + + diff --git a/Backbone/src/main/res/layout/navigation_view_customtitle.xml b/Backbone/src/main/res/layout/navigation_view_customtitle.xml new file mode 100644 index 000000000..f0b9452d0 --- /dev/null +++ b/Backbone/src/main/res/layout/navigation_view_customtitle.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + diff --git a/Backbone/src/main/res/layout/navigation_view_customtitle_breadcrumb.xml b/Backbone/src/main/res/layout/navigation_view_customtitle_breadcrumb.xml new file mode 100644 index 000000000..955bb8e0f --- /dev/null +++ b/Backbone/src/main/res/layout/navigation_view_customtitle_breadcrumb.xml @@ -0,0 +1,36 @@ + + + + + + + + + + diff --git a/Backbone/src/main/res/layout/navigation_view_customtitle_configuration.xml b/Backbone/src/main/res/layout/navigation_view_customtitle_configuration.xml new file mode 100644 index 000000000..f47536063 --- /dev/null +++ b/Backbone/src/main/res/layout/navigation_view_customtitle_configuration.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + diff --git a/Backbone/src/main/res/layout/navigation_view_details.xml b/Backbone/src/main/res/layout/navigation_view_details.xml new file mode 100644 index 000000000..f435f022f --- /dev/null +++ b/Backbone/src/main/res/layout/navigation_view_details.xml @@ -0,0 +1,21 @@ + + + + diff --git a/Backbone/src/main/res/layout/navigation_view_details_item.xml b/Backbone/src/main/res/layout/navigation_view_details_item.xml new file mode 100644 index 000000000..bf234e0f7 --- /dev/null +++ b/Backbone/src/main/res/layout/navigation_view_details_item.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/navigation_view_icons.xml b/Backbone/src/main/res/layout/navigation_view_icons.xml similarity index 79% rename from res/layout/navigation_view_icons.xml rename to Backbone/src/main/res/layout/navigation_view_icons.xml index 8f9ce330c..6e28a8a0a 100644 --- a/res/layout/navigation_view_icons.xml +++ b/Backbone/src/main/res/layout/navigation_view_icons.xml @@ -15,7 +15,7 @@ --> + android:id="@+id/navigation_view_layout" + style="@style/navigation_grid" + android:layout_width="match_parent" + android:layout_height="match_parent"/> diff --git a/Backbone/src/main/res/layout/navigation_view_icons_item.xml b/Backbone/src/main/res/layout/navigation_view_icons_item.xml new file mode 100644 index 000000000..b8fc6b313 --- /dev/null +++ b/Backbone/src/main/res/layout/navigation_view_icons_item.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + diff --git a/Backbone/src/main/res/layout/navigation_view_simple.xml b/Backbone/src/main/res/layout/navigation_view_simple.xml new file mode 100644 index 000000000..f435f022f --- /dev/null +++ b/Backbone/src/main/res/layout/navigation_view_simple.xml @@ -0,0 +1,21 @@ + + + + diff --git a/Backbone/src/main/res/layout/navigation_view_simple_item.xml b/Backbone/src/main/res/layout/navigation_view_simple_item.xml new file mode 100644 index 000000000..063567320 --- /dev/null +++ b/Backbone/src/main/res/layout/navigation_view_simple_item.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Backbone/src/main/res/layout/option_list_item.xml b/Backbone/src/main/res/layout/option_list_item.xml new file mode 100644 index 000000000..70ce9dfa1 --- /dev/null +++ b/Backbone/src/main/res/layout/option_list_item.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + diff --git a/Backbone/src/main/res/layout/picker.xml b/Backbone/src/main/res/layout/picker.xml new file mode 100644 index 000000000..735a4cede --- /dev/null +++ b/Backbone/src/main/res/layout/picker.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/Backbone/src/main/res/layout/search.xml b/Backbone/src/main/res/layout/search.xml new file mode 100644 index 000000000..be35b254c --- /dev/null +++ b/Backbone/src/main/res/layout/search.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + diff --git a/Backbone/src/main/res/layout/search_item.xml b/Backbone/src/main/res/layout/search_item.xml new file mode 100644 index 000000000..a8e394190 --- /dev/null +++ b/Backbone/src/main/res/layout/search_item.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/Backbone/src/main/res/layout/simple_icon.xml b/Backbone/src/main/res/layout/simple_icon.xml new file mode 100644 index 000000000..8848e4af3 --- /dev/null +++ b/Backbone/src/main/res/layout/simple_icon.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/Backbone/src/main/res/layout/spinner_item.xml b/Backbone/src/main/res/layout/spinner_item.xml new file mode 100644 index 000000000..73099e5af --- /dev/null +++ b/Backbone/src/main/res/layout/spinner_item.xml @@ -0,0 +1,24 @@ + + + + diff --git a/Backbone/src/main/res/layout/theme_roulette.xml b/Backbone/src/main/res/layout/theme_roulette.xml new file mode 100644 index 000000000..349347e1d --- /dev/null +++ b/Backbone/src/main/res/layout/theme_roulette.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + diff --git a/Backbone/src/main/res/layout/theme_selector_preference.xml b/Backbone/src/main/res/layout/theme_selector_preference.xml new file mode 100644 index 000000000..3c715b474 --- /dev/null +++ b/Backbone/src/main/res/layout/theme_selector_preference.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + +