Loading AndroidManifest.xml +18 −2 Original line number Diff line number Diff line Loading @@ -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"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.EDIT" /> <category android:name="android.intent.category.DEFAULT" /> <data android:scheme="file" /> <data android:mimeType="text/*" /> <data android:mimeType="application/javascript" /> <data android:mimeType="application/json" /> <data android:mimeType="application/xhtml+xml" /> <data android:mimeType="application/xml" /> <data android:mimeType="application/x-msdownload" /> <data android:mimeType="application/x-csh" /> <data android:mimeType="application/x-sh" /> </intent-filter> <intent-filter> <action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.EDIT" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="com.cyanogenmod.filemanager.category.INTERNAL_VIEWER" /> <category android:name="com.cyanogenmod.filemanager.category.EDITOR" /> </intent-filter> Loading src/com/cyanogenmod/filemanager/activities/EditorActivity.java +4 −1 Original line number Diff line number Diff line Loading @@ -459,7 +459,10 @@ public class EditorActivity extends Activity implements TextWatcher { 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(); Loading src/com/cyanogenmod/filemanager/ui/dialogs/AssociationsDialog.java +105 −109 Original line number Diff line number Diff line Loading @@ -127,8 +127,7 @@ public class AssociationsDialog implements OnItemClickListener { */ 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 = Loading @@ -138,7 +137,9 @@ public class AssociationsDialog implements OnItemClickListener { 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; Loading @@ -164,27 +165,9 @@ public class AssociationsDialog implements OnItemClickListener { @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( Loading Loading @@ -228,6 +211,16 @@ public class AssociationsDialog implements OnItemClickListener { 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); } Loading Loading @@ -255,10 +248,7 @@ public class AssociationsDialog implements OnItemClickListener { 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; Loading Loading @@ -348,7 +338,29 @@ public class AssociationsDialog implements OnItemClickListener { */ @SuppressWarnings({"deprecation"}) void onIntentSelected(ResolveInfo ri, Intent intent, boolean remember) { if (remember && !isInternalEditor(ri) && ri.filter != null) { 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); } // 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(); Loading Loading @@ -387,7 +399,11 @@ public class AssociationsDialog implements OnItemClickListener { // Look through the resolved filter to determine which part // of it matched the original Intent. Iterator<IntentFilter.AuthorityEntry> aIt = ri.filter.authoritiesIterator(); // ri.filter should not be null here because the activity matches a filter // Anyway protect the access if (ri.filter != null) { Iterator<IntentFilter.AuthorityEntry> aIt = ri.filter.authoritiesIterator(); if (aIt != null) { while (aIt.hasNext()) { IntentFilter.AuthorityEntry a = aIt.next(); Loading @@ -412,18 +428,13 @@ public class AssociationsDialog implements OnItemClickListener { } } } } // 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; Loading @@ -437,13 +448,12 @@ public class AssociationsDialog implements OnItemClickListener { } } 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? 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 Loading @@ -465,18 +475,4 @@ public class AssociationsDialog implements OnItemClickListener { 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); } } src/com/cyanogenmod/filemanager/ui/policy/IntentsActionPolicy.java +202 −22 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -41,6 +43,8 @@ import com.cyanogenmod.filemanager.util.ResourcesHelper; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; /** Loading @@ -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 */ Loading Loading @@ -84,7 +91,7 @@ public final class IntentsActionPolicy extends ActionsPolicy { 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); Loading Loading @@ -177,6 +184,22 @@ public final class IntentsActionPolicy extends ActionsPolicy { List<ResolveInfo> info = packageManager. queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); Collections.sort(info, new Comparator<ResolveInfo>() { @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; Loading @@ -184,47 +207,70 @@ public final class IntentsActionPolicy extends ActionsPolicy { int cc = internals.size(); for (int i = 0; i < cc; i++) { Intent ii = internals.get(i); List<ResolveInfo> ris = List<ResolveInfo> 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 ResolveInfo ri = info.get(0); if (ri.activityInfo.metaData == null || !ri.activityInfo.metaData.getBoolean(CATEGORY_INTERNAL_VIEWER, false)) { ctx.startActivity(intent); //--- // 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); 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, Loading Loading @@ -316,7 +362,7 @@ public final class IntentsActionPolicy extends ActionsPolicy { 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); Loading @@ -324,4 +370,138 @@ public final class IntentsActionPolicy extends ActionsPolicy { 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<ResolveInfo> 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<ResolveInfo> pref = new ArrayList<ResolveInfo>(); 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<ComponentName> prefActList = new ArrayList<ComponentName>(); List<IntentFilter> intentList = new ArrayList<IntentFilter>(); 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<ResolveInfo>() { @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); } } Loading
AndroidManifest.xml +18 −2 Original line number Diff line number Diff line Loading @@ -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"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.EDIT" /> <category android:name="android.intent.category.DEFAULT" /> <data android:scheme="file" /> <data android:mimeType="text/*" /> <data android:mimeType="application/javascript" /> <data android:mimeType="application/json" /> <data android:mimeType="application/xhtml+xml" /> <data android:mimeType="application/xml" /> <data android:mimeType="application/x-msdownload" /> <data android:mimeType="application/x-csh" /> <data android:mimeType="application/x-sh" /> </intent-filter> <intent-filter> <action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.EDIT" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="com.cyanogenmod.filemanager.category.INTERNAL_VIEWER" /> <category android:name="com.cyanogenmod.filemanager.category.EDITOR" /> </intent-filter> Loading
src/com/cyanogenmod/filemanager/activities/EditorActivity.java +4 −1 Original line number Diff line number Diff line Loading @@ -459,7 +459,10 @@ public class EditorActivity extends Activity implements TextWatcher { 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(); Loading
src/com/cyanogenmod/filemanager/ui/dialogs/AssociationsDialog.java +105 −109 Original line number Diff line number Diff line Loading @@ -127,8 +127,7 @@ public class AssociationsDialog implements OnItemClickListener { */ 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 = Loading @@ -138,7 +137,9 @@ public class AssociationsDialog implements OnItemClickListener { 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; Loading @@ -164,27 +165,9 @@ public class AssociationsDialog implements OnItemClickListener { @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( Loading Loading @@ -228,6 +211,16 @@ public class AssociationsDialog implements OnItemClickListener { 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); } Loading Loading @@ -255,10 +248,7 @@ public class AssociationsDialog implements OnItemClickListener { 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; Loading Loading @@ -348,7 +338,29 @@ public class AssociationsDialog implements OnItemClickListener { */ @SuppressWarnings({"deprecation"}) void onIntentSelected(ResolveInfo ri, Intent intent, boolean remember) { if (remember && !isInternalEditor(ri) && ri.filter != null) { 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); } // 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(); Loading Loading @@ -387,7 +399,11 @@ public class AssociationsDialog implements OnItemClickListener { // Look through the resolved filter to determine which part // of it matched the original Intent. Iterator<IntentFilter.AuthorityEntry> aIt = ri.filter.authoritiesIterator(); // ri.filter should not be null here because the activity matches a filter // Anyway protect the access if (ri.filter != null) { Iterator<IntentFilter.AuthorityEntry> aIt = ri.filter.authoritiesIterator(); if (aIt != null) { while (aIt.hasNext()) { IntentFilter.AuthorityEntry a = aIt.next(); Loading @@ -412,18 +428,13 @@ public class AssociationsDialog implements OnItemClickListener { } } } } // 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; Loading @@ -437,13 +448,12 @@ public class AssociationsDialog implements OnItemClickListener { } } 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? 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 Loading @@ -465,18 +475,4 @@ public class AssociationsDialog implements OnItemClickListener { 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); } }
src/com/cyanogenmod/filemanager/ui/policy/IntentsActionPolicy.java +202 −22 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -41,6 +43,8 @@ import com.cyanogenmod.filemanager.util.ResourcesHelper; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; /** Loading @@ -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 */ Loading Loading @@ -84,7 +91,7 @@ public final class IntentsActionPolicy extends ActionsPolicy { 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); Loading Loading @@ -177,6 +184,22 @@ public final class IntentsActionPolicy extends ActionsPolicy { List<ResolveInfo> info = packageManager. queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); Collections.sort(info, new Comparator<ResolveInfo>() { @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; Loading @@ -184,47 +207,70 @@ public final class IntentsActionPolicy extends ActionsPolicy { int cc = internals.size(); for (int i = 0; i < cc; i++) { Intent ii = internals.get(i); List<ResolveInfo> ris = List<ResolveInfo> 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 ResolveInfo ri = info.get(0); if (ri.activityInfo.metaData == null || !ri.activityInfo.metaData.getBoolean(CATEGORY_INTERNAL_VIEWER, false)) { ctx.startActivity(intent); //--- // 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); 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, Loading Loading @@ -316,7 +362,7 @@ public final class IntentsActionPolicy extends ActionsPolicy { 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); Loading @@ -324,4 +370,138 @@ public final class IntentsActionPolicy extends ActionsPolicy { 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<ResolveInfo> 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<ResolveInfo> pref = new ArrayList<ResolveInfo>(); 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<ComponentName> prefActList = new ArrayList<ComponentName>(); List<IntentFilter> intentList = new ArrayList<IntentFilter>(); 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<ResolveInfo>() { @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); } }