diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index bc947fdb0..c890c954a 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -153,10 +153,26 @@
android:name=".activities.EditorActivity"
android:label="@string/editor"
android:configChanges="orientation|keyboardHidden|screenSize"
- android:icon="@drawable/ic_launcher_editor"
- android:exported="false">
+ android:icon="@drawable/ic_launcher_editor">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/com/cyanogenmod/filemanager/activities/EditorActivity.java b/src/com/cyanogenmod/filemanager/activities/EditorActivity.java
index 912c0befb..6b682ad2c 100644
--- a/src/com/cyanogenmod/filemanager/activities/EditorActivity.java
+++ b/src/com/cyanogenmod/filemanager/activities/EditorActivity.java
@@ -459,7 +459,10 @@ private void readFile() {
this, R.string.editor_invalid_file_msg, Toast.LENGTH_SHORT);
return;
}
- this.mReadOnly = (action.compareTo(Intent.ACTION_VIEW) == 0);
+ // 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();
diff --git a/src/com/cyanogenmod/filemanager/ui/dialogs/AssociationsDialog.java b/src/com/cyanogenmod/filemanager/ui/dialogs/AssociationsDialog.java
index 370043319..096bacdbd 100644
--- a/src/com/cyanogenmod/filemanager/ui/dialogs/AssociationsDialog.java
+++ b/src/com/cyanogenmod/filemanager/ui/dialogs/AssociationsDialog.java
@@ -127,8 +127,7 @@ public AssociationsDialog(
*/
private void init(int icon, String title, String action,
OnCancelListener onCancelListener, OnDismissListener onDismissListener) {
- boolean isPlatformSigned =
- AndroidHelper.isAppPlatformSignature(this.mContext);
+ boolean isPlatformSigned = AndroidHelper.isAppPlatformSignature(this.mContext);
//Create the layout, and retrieve the views
LayoutInflater li =
@@ -138,7 +137,9 @@ private void init(int icon, String title, String action,
this.mRemember.setVisibility(
isPlatformSigned && this.mAllowPreferred ? View.VISIBLE : View.GONE);
this.mGrid = (GridView)v.findViewById(R.id.associations_gridview);
- this.mGrid.setAdapter(new AssociationsAdapter(this.mContext, this.mIntents, this));
+ AssociationsAdapter adapter =
+ new AssociationsAdapter(this.mContext, this.mIntents, this);
+ this.mGrid.setAdapter(adapter);
// Ensure a default title dialog
String dialogTitle = title;
@@ -164,27 +165,9 @@ private void init(int icon, String title, String action,
@Override
public void onClick(DialogInterface dialog, int which) {
ResolveInfo ri = getSelected();
- Intent intent = new Intent(AssociationsDialog.this.mRequestIntent);
- if (isInternalEditor(ri)) {
- // The action for internal editors (for default VIEW)
- String a = Intent.ACTION_VIEW;
- if (ri.activityInfo.metaData != null) {
- a = ri.activityInfo.metaData.getString(
- IntentsActionPolicy.EXTRA_INTERNAL_ACTION,
- Intent.ACTION_VIEW);
- }
- intent.setAction(a);
- }
- intent.setFlags(
- intent.getFlags() &~
- Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
- intent.addFlags(
- Intent.FLAG_ACTIVITY_FORWARD_RESULT |
- Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
- intent.setComponent(
- new ComponentName(
- ri.activityInfo.applicationInfo.packageName,
- ri.activityInfo.name));
+ Intent intent =
+ IntentsActionPolicy.getIntentFromResolveInfo(
+ ri, AssociationsDialog.this.mRequestIntent);
// Open the intent (and remember the action is the check is marked)
onIntentSelected(
@@ -228,6 +211,16 @@ public void onItemClick(AdapterView> parent, View view, int position, long id)
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);
+ }
+
// Enable action button
this.mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(true);
}
@@ -255,10 +248,7 @@ boolean checkUserPreferences() {
if (item != null) {
if (!item.isSelected()) {
onItemClick(null, item, i, item.getId());
-
- // Not allow to revert remember status
this.mRemember.setChecked(true);
- this.mRemember.setEnabled(false);
ret = false;
} else {
this.mLoaded = true;
@@ -348,82 +338,103 @@ ResolveInfo getSelected() {
*/
@SuppressWarnings({"deprecation"})
void onIntentSelected(ResolveInfo ri, Intent intent, boolean remember) {
- if (remember && !isInternalEditor(ri) && ri.filter != null) {
- // Build a reasonable intent filter, based on what matched.
- IntentFilter filter = new IntentFilter();
- if (intent.getAction() != null) {
- filter.addAction(intent.getAction());
- }
- Set categories = intent.getCategories();
- if (categories != null) {
- for (String cat : categories) {
- filter.addCategory(cat);
- }
+ boolean isPlatformSigned = AndroidHelper.isAppPlatformSignature(this.mContext);
+
+ // Register preferred association is only allowed by platform signature
+ // The app will be signed with this signature, but when is launch from
+ // inside ADT, the app is signed with testkey.
+ if (isPlatformSigned && this.mAllowPreferred) {
+
+ PackageManager pm = this.mContext.getPackageManager();
+
+ // Remove preferred application if user don't want to remember it
+ if (this.mPreferred != null && !remember) {
+ pm.clearPackagePreferredActivities(
+ this.mPreferred.activityInfo.packageName);
}
- filter.addCategory(Intent.CATEGORY_DEFAULT);
- int cat = ri.match & IntentFilter.MATCH_CATEGORY_MASK;
- Uri data = intent.getData();
- if (cat == IntentFilter.MATCH_CATEGORY_TYPE) {
- String mimeType = intent.resolveType(this.mContext);
- if (mimeType != null) {
- try {
- filter.addDataType(mimeType);
- } catch (IntentFilter.MalformedMimeTypeException e) {
- Log.w(TAG, e);
- filter = null;
+ // Associate the activity under these circumstances:
+ // - The user has selected the remember option
+ // - The selected intent is not an internal editor (internal editors are private and
+ // can be associated)
+ // - The selected intent is not the current preferred selection
+ if (remember && !IntentsActionPolicy.isInternalEditor(ri) && !isPreferredSelected()) {
+
+ // Build a reasonable intent filter, based on what matched.
+ IntentFilter filter = new IntentFilter();
+
+ if (intent.getAction() != null) {
+ filter.addAction(intent.getAction());
+ }
+ Set categories = intent.getCategories();
+ if (categories != null) {
+ for (String cat : categories) {
+ filter.addCategory(cat);
}
}
- }
- if (data != null && data.getScheme() != null && filter != null) {
- // We need the data specification if there was no type,
- // OR if the scheme is not one of our magical "file:"
- // or "content:" schemes (see IntentFilter for the reason).
- if (cat != IntentFilter.MATCH_CATEGORY_TYPE
- || (!"file".equals(data.getScheme()) //$NON-NLS-1$
- && !"content".equals(data.getScheme()))) { //$NON-NLS-1$
- filter.addDataScheme(data.getScheme());
-
- // Look through the resolved filter to determine which part
- // of it matched the original Intent.
- Iterator aIt = ri.filter.authoritiesIterator();
- if (aIt != null) {
- while (aIt.hasNext()) {
- IntentFilter.AuthorityEntry a = aIt.next();
- if (a.match(data) >= 0) {
- int port = a.getPort();
- filter.addDataAuthority(a.getHost(),
- port >= 0 ? Integer.toString(port) : null);
- break;
- }
+ filter.addCategory(Intent.CATEGORY_DEFAULT);
+
+ int cat = ri.match & IntentFilter.MATCH_CATEGORY_MASK;
+ Uri data = intent.getData();
+ if (cat == IntentFilter.MATCH_CATEGORY_TYPE) {
+ String mimeType = intent.resolveType(this.mContext);
+ if (mimeType != null) {
+ try {
+ filter.addDataType(mimeType);
+ } catch (IntentFilter.MalformedMimeTypeException e) {
+ Log.w(TAG, e);
+ filter = null;
}
}
- Iterator pIt = ri.filter.pathsIterator();
- if (pIt != null) {
- String path = data.getPath();
- while (path != null && pIt.hasNext()) {
- PatternMatcher p = pIt.next();
- if (p.match(path)) {
- filter.addDataPath(p.getPath(), p.getType());
- break;
+ }
+ if (data != null && data.getScheme() != null && filter != null) {
+ // We need the data specification if there was no type,
+ // OR if the scheme is not one of our magical "file:"
+ // or "content:" schemes (see IntentFilter for the reason).
+ if (cat != IntentFilter.MATCH_CATEGORY_TYPE
+ || (!"file".equals(data.getScheme()) //$NON-NLS-1$
+ && !"content".equals(data.getScheme()))) { //$NON-NLS-1$
+ filter.addDataScheme(data.getScheme());
+
+ // Look through the resolved filter to determine which part
+ // of it matched the original Intent.
+ // ri.filter should not be null here because the activity matches a filter
+ // Anyway protect the access
+ if (ri.filter != null) {
+ Iterator aIt =
+ ri.filter.authoritiesIterator();
+ if (aIt != null) {
+ while (aIt.hasNext()) {
+ IntentFilter.AuthorityEntry a = aIt.next();
+ if (a.match(data) >= 0) {
+ int port = a.getPort();
+ filter.addDataAuthority(a.getHost(),
+ port >= 0 ? Integer.toString(port) : null);
+ break;
+ }
+ }
+ }
+ Iterator pIt = ri.filter.pathsIterator();
+ if (pIt != null) {
+ String path = data.getPath();
+ while (path != null && pIt.hasNext()) {
+ PatternMatcher p = pIt.next();
+ if (p.match(path)) {
+ filter.addDataPath(p.getPath(), p.getType());
+ break;
+ }
+ }
}
}
}
}
- }
- // Register preferred association is only allowed by platform signature
- // The app will be signed with this signature, but when is launch from
- // inside ADT, the app is signed with testkey.
- // Ignore it if the preferred can be saved. Only notify the user and open the
- // intent
- boolean isPlatformSigned =
- AndroidHelper.isAppPlatformSignature(this.mContext);
- if (isPlatformSigned && this.mAllowPreferred) {
- if (filter != null && !isPreferredSelected()) {
+ // If we don't have a filter then don't try to associate
+ if (filter != null) {
try {
- AssociationsAdapter adapter = (AssociationsAdapter)this.mGrid.getAdapter();
+ AssociationsAdapter adapter =
+ (AssociationsAdapter)this.mGrid.getAdapter();
final int cc = adapter.getCount();
ComponentName[] set = new ComponentName[cc];
int bestMatch = 0;
@@ -437,13 +448,12 @@ void onIntentSelected(ResolveInfo ri, Intent intent, boolean remember) {
}
}
- PackageManager pm = this.mContext.getPackageManager();
-
// The only way i found to ensure of the use of the preferred activity
// selected is to clear preferred activity associations
- // Maybe it's necessary also remove the rest of activities?
- pm.clearPackagePreferredActivities(
- this.mPreferred.activityInfo.packageName);
+ if (this.mPreferred != null) {
+ pm.clearPackagePreferredActivities(
+ this.mPreferred.activityInfo.packageName);
+ }
// This is allowed for now in AOSP, but probably in the future this will
// not work at all
@@ -465,18 +475,4 @@ void onIntentSelected(ResolveInfo ri, Intent intent, boolean remember) {
this.mContext.startActivity(intent);
}
}
-
- /**
- * Method that returns if the selected resolve info is about an internal viewer
- *
- * @param ri The resolve info
- * @return boolean If the selected resolve info is about an internal viewer
- * @hide
- */
- @SuppressWarnings("static-method")
- boolean isInternalEditor(ResolveInfo ri) {
- return ri.activityInfo.metaData != null &&
- ri.activityInfo.metaData.getBoolean(
- IntentsActionPolicy.CATEGORY_INTERNAL_VIEWER, false);
- }
}
diff --git a/src/com/cyanogenmod/filemanager/ui/policy/IntentsActionPolicy.java b/src/com/cyanogenmod/filemanager/ui/policy/IntentsActionPolicy.java
index 004719b0e..774509957 100644
--- a/src/com/cyanogenmod/filemanager/ui/policy/IntentsActionPolicy.java
+++ b/src/com/cyanogenmod/filemanager/ui/policy/IntentsActionPolicy.java
@@ -16,7 +16,9 @@
package com.cyanogenmod.filemanager.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;
@@ -41,6 +43,8 @@
import java.io.File;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
/**
@@ -52,6 +56,9 @@ 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$
+
/**
* Extra field for the internal action
*/
@@ -84,7 +91,7 @@ public static void openFileSystemObject(
final Context ctx, final FileSystemObject fso, final boolean choose,
OnCancelListener onCancelListener, OnDismissListener onDismissListener) {
try {
- // Create the intent to
+ // Create the intent to open the file
Intent intent = new Intent();
intent.setAction(android.content.Intent.ACTION_VIEW);
@@ -177,6 +184,22 @@ private static void resolveIntent(
List info =
packageManager.
queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
+ Collections.sort(info, new Comparator() {
+ @Override
+ public int compare(ResolveInfo lhs, ResolveInfo rhs) {
+ boolean isLshCMFM =
+ lhs.activityInfo.packageName.compareTo(PREFERRED_PACKAGE) == 0;
+ boolean isRshCMFM =
+ rhs.activityInfo.packageName.compareTo(PREFERRED_PACKAGE) == 0;
+ if (isLshCMFM && !isRshCMFM) {
+ return -1;
+ }
+ if (!isLshCMFM && isRshCMFM) {
+ return 1;
+ }
+ return lhs.activityInfo.name.compareTo(rhs.activityInfo.name);
+ }
+ });
// Add the internal editors
int count = 0;
@@ -184,47 +207,70 @@ private static void resolveIntent(
int cc = internals.size();
for (int i = 0; i < cc; i++) {
Intent ii = internals.get(i);
- List ris =
+ List ie =
packageManager.
queryIntentActivities(ii, 0);
- if (ris.size() > 0) {
- ResolveInfo ri = ris.get(0);
+ if (ie.size() > 0) {
+ ResolveInfo rie = ie.get(0);
+
+ // Only if the internal is not in the query list
+ boolean exists = false;
+ int ccc = info.size();
+ for (int j = 0; j < ccc; j++) {
+ ResolveInfo ri = info.get(j);
+ if (ri.activityInfo.packageName.compareTo(
+ rie.activityInfo.packageName) == 0 &&
+ ri.activityInfo.name.compareTo(
+ rie.activityInfo.name) == 0) {
+ exists = true;
+ break;
+ }
+ }
+ if (exists) {
+ continue;
+ }
+
// Mark as internal
- if (ri.activityInfo.metaData == null) {
- ri.activityInfo.metaData = new Bundle();
- ri.activityInfo.metaData.putString(EXTRA_INTERNAL_ACTION, ii.getAction());
- ri.activityInfo.metaData.putBoolean(CATEGORY_INTERNAL_VIEWER, true);
+ if (rie.activityInfo.metaData == null) {
+ rie.activityInfo.metaData = new Bundle();
+ rie.activityInfo.metaData.putString(EXTRA_INTERNAL_ACTION, ii.getAction());
+ rie.activityInfo.metaData.putBoolean(CATEGORY_INTERNAL_VIEWER, true);
}
// Only one result must be matched
- info.add(count, ri);
+ info.add(count, rie);
count++;
}
}
}
- // Retrieve the preferred activity that can handle the file
- final ResolveInfo mPreferredInfo = packageManager.resolveActivity(intent, 0);
-
// No registered application
if (info.size() == 0) {
DialogHelper.showToast(ctx, R.string.msgs_not_registered_app, Toast.LENGTH_SHORT);
return;
}
+ // Retrieve the preferred activity that can handle the file. We only want the
+ // resolved activity if the activity is a preferred activity. Other case, the
+ // resolved activity was never added by addPreferredActivity
+ ResolveInfo mPreferredInfo = findPreferredActivity(ctx, intent, info);
+
// Is a simple open and we have an application that can handle the file?
- if (!choose &&
- ((mPreferredInfo != null && mPreferredInfo.match != 0) || info.size() == 1)) {
- // But not if the only match is the an internal editor
+ //---
+ // If we have a preferred application, then use it
+ if (!choose && (mPreferredInfo != null && mPreferredInfo.match != 0)) {
+ ctx.startActivity(getIntentFromResolveInfo(mPreferredInfo, intent));
+ return;
+ }
+ // If there are only one activity (app or internal editor), then use it
+ if (!choose && info.size() == 1) {
ResolveInfo ri = info.get(0);
- if (ri.activityInfo.metaData == null ||
- !ri.activityInfo.metaData.getBoolean(CATEGORY_INTERNAL_VIEWER, false)) {
- ctx.startActivity(intent);
- return;
- }
+ ctx.startActivity(getIntentFromResolveInfo(ri, intent));
+ return;
}
- // Otherwise, we have to show the open with dialog
+ // If we have multiples apps and there is not a preferred application then show
+ // open with dialog
AssociationsDialog dialog =
new AssociationsDialog(
ctx,
@@ -316,7 +362,7 @@ private static List createEditorIntent(Context ctx, FileSystemObject fso
category.compareTo(MimeTypeCategory.EXEC) == 0 ||
category.compareTo(MimeTypeCategory.TEXT) == 0)) {
Intent editorIntent = new Intent();
- editorIntent.setAction(Intent.ACTION_EDIT);
+ editorIntent.setAction(Intent.ACTION_VIEW);
editorIntent.addCategory(CATEGORY_INTERNAL_VIEWER);
editorIntent.addCategory(CATEGORY_EDITOR);
intents.add(editorIntent);
@@ -324,4 +370,138 @@ private static List createEditorIntent(Context ctx, FileSystemObject fso
return intents;
}
+
+ /**
+ * Method that returns an {@link Intent} from his {@link ResolveInfo}
+ *
+ * @param ri The ResolveInfo
+ * @param request The requested intent
+ * @return Intent The intent
+ */
+ public static final Intent getIntentFromResolveInfo(ResolveInfo ri, Intent request) {
+ Intent intent =
+ getIntentFromComponentName(
+ new ComponentName(
+ ri.activityInfo.applicationInfo.packageName,
+ ri.activityInfo.name),
+ request);
+ if (isInternalEditor(ri)) {
+ String a = Intent.ACTION_VIEW;
+ if (ri.activityInfo.metaData != null) {
+ a = ri.activityInfo.metaData.getString(
+ IntentsActionPolicy.EXTRA_INTERNAL_ACTION,
+ Intent.ACTION_VIEW);
+ }
+ intent.setAction(a);
+ }
+ return intent;
+ }
+
+ /**
+ * Method that returns an {@link Intent} from his {@link ComponentName}
+ *
+ * @param cn The ComponentName
+ * @param request The requested intent
+ * @return Intent The intent
+ */
+ public static final Intent getIntentFromComponentName(ComponentName cn, Intent request) {
+ Intent intent = new Intent(request);
+ intent.setFlags(
+ intent.getFlags() &~
+ Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ intent.addFlags(
+ Intent.FLAG_ACTIVITY_FORWARD_RESULT |
+ Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
+ intent.setComponent(
+ new ComponentName(
+ cn.getPackageName(),
+ cn.getClassName()));
+ return intent;
+ }
+
+ /**
+ * Method that returns if the selected resolve info is about an internal viewer
+ *
+ * @param ri The resolve info
+ * @return boolean If the selected resolve info is about an internal viewer
+ * @hide
+ */
+ public static final boolean isInternalEditor(ResolveInfo ri) {
+ return ri.activityInfo.metaData != null &&
+ ri.activityInfo.metaData.getBoolean(
+ IntentsActionPolicy.CATEGORY_INTERNAL_VIEWER, false);
+ }
+
+ /**
+ * Method that retrieve the finds the preferred activity, if one exists. In case
+ * of multiple preferred activity exists the try to choose the better
+ *
+ * @param ctx The current context
+ * @param intent The query intent
+ * @param info The initial info list
+ * @return ResolveInfo The resolved info
+ */
+ private static final ResolveInfo findPreferredActivity(
+ Context ctx, Intent intent, List info) {
+
+ final PackageManager packageManager = ctx.getPackageManager();
+
+ // Retrieve the preferred activity that can handle the file. We only want the
+ // resolved activity if the activity is a preferred activity. Other case, the
+ // resolved activity was never added by addPreferredActivity
+ List pref = new ArrayList();
+ int cc = info.size();
+ for (int i = 0; i < cc; i++) {
+ ResolveInfo ri = info.get(i);
+ if (isInternalEditor(ri)) continue;
+ if (ri.activityInfo == null || ri.activityInfo.packageName == null) continue;
+ List prefActList = new ArrayList();
+ List intentList = new ArrayList();
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(intent.getAction());
+ try {
+ filter.addDataType(intent.getType());
+ } catch (Exception ex) {/**NON BLOCK**/}
+ intentList.add(filter);
+ packageManager.getPreferredActivities(
+ intentList, prefActList, ri.activityInfo.packageName);
+ if (prefActList.size() > 0) {
+ pref.add(ri);
+ }
+ }
+
+ // No preferred activity is selected
+ if (pref.size() == 0) {
+ return null;
+ }
+
+ // Sort and return the first activity
+ Collections.sort(pref, new Comparator() {
+ @Override
+ public int compare(ResolveInfo lhs, ResolveInfo rhs) {
+ if (lhs.priority > rhs.priority) {
+ return -1;
+ } else if (lhs.priority < rhs.priority) {
+ return 1;
+ }
+ if (lhs.preferredOrder > rhs.preferredOrder) {
+ return -1;
+ } else if (lhs.preferredOrder < rhs.preferredOrder) {
+ return 1;
+ }
+ if (lhs.isDefault && !rhs.isDefault) {
+ return -1;
+ } else if (!lhs.isDefault && rhs.isDefault) {
+ return 1;
+ }
+ if (lhs.match > rhs.match) {
+ return -1;
+ } else if (lhs.match > rhs.match) {
+ return 1;
+ }
+ return 0;
+ }
+ });
+ return pref.get(0);
+ }
}