diff --git a/code/map_data_render.js b/code/map_data_render.js index 198c376b..4e7e969b 100644 --- a/code/map_data_render.js +++ b/code/map_data_render.js @@ -440,14 +440,20 @@ window.Render.prototype.resetPortalClusters = function() { if (!(cid in this.portalClusters)) this.portalClusters[cid] = []; - this.portalClusters[cid].push(p.options.guid); + this.portalClusters[cid].push(pguid); } - // now, for each cluster, sort by some arbitrary data (the guid will do), and display the first CLUSTER_PORTAL_LIMIT + // now, for each cluster, sort by some arbitrary data (the level+guid will do), and display the first CLUSTER_PORTAL_LIMIT for (var cid in this.portalClusters) { var c = this.portalClusters[cid]; - c.sort(); + c.sort(function(a,b) { + var ka = (8-portals[a].options.level)+a; + var kb = (8-portals[b].options.level)+b; + if (kakb) return 1; + else return 0; + }); for (var i=0; i mDialogStack = new Stack(); private String mPermalink = null; + private String mSearchTerm = null; // Used for custom back stack handling private final Stack mBackStack = new Stack(); @@ -258,20 +261,20 @@ public class IITC_Mobile extends Activity private void handleGeoUri(final Uri uri) throws URISyntaxException { final String[] parts = uri.getSchemeSpecificPart().split("\\?", 2); - Double lat, lon; + Double lat = null, lon = null; Integer z = null; + String search = null; // parts[0] may contain an 'uncertainty' parameter, delimited by a semicolon final String[] pos = parts[0].split(";", 2)[0].split(",", 2); - if (pos.length != 2) throw new URISyntaxException(uri.toString(), "URI does not contain a valid position"); - - try { - lat = Double.valueOf(pos[0]); - lon = Double.valueOf(pos[1]); - } catch (final NumberFormatException e) { - final URISyntaxException use = new URISyntaxException(uri.toString(), "position could not be parsed"); - use.initCause(e); - throw use; + if (pos.length == 2) { + try { + lat = Double.valueOf(pos[0]); + lon = Double.valueOf(pos[1]); + } catch (final NumberFormatException e) { + lat = null; + lon = null; + } } if (parts.length > 1) { // query string present @@ -281,21 +284,47 @@ public class IITC_Mobile extends Activity try { z = Integer.valueOf(param.substring(2)); } catch (final NumberFormatException e) { - final URISyntaxException use = new URISyntaxException( - uri.toString(), "could not parse zoom level"); - use.initCause(e); - throw use; } - break; + } + if (param.startsWith("q=")) { + search = param.substring(2); + final Pattern pattern = Pattern.compile("^(-?\\d+(\\.\\d+)?),(-?\\d+(\\.\\d+)?)\\s*\\(.+\\)"); + final Matcher matcher = pattern.matcher(search); + if (matcher.matches()) { + try { + lat = Double.valueOf(matcher.group(1)); + lon = Double.valueOf(matcher.group(3)); + search = null; // if we have a position, we don't need the search term + } catch (final NumberFormatException e) { + lat = null; + lon = null; + } + } } } } - String url = "http://www.ingress.com/intel?ll=" + lat + "," + lon; - if (z != null) { - url += "&z=" + z; + if (lat != null && lon != null) { + String url = mIntelUrl + "?ll=" + lat + "," + lon; + if (z != null) { + url += "&z=" + z; + } + loadUrl(url); + return; } - loadUrl(url); + + if (search != null) { + if (mIsLoading) { + mSearchTerm = search; + loadUrl(mIntelUrl); + } else { + switchToPane(Pane.MAP); + mIitcWebView.loadUrl("javascript:search('" + search + "');"); + } + return; + } + + throw new URISyntaxException(uri.toString(), "position could not be parsed"); } @Override @@ -447,8 +476,7 @@ public class IITC_Mobile extends Activity // Get the SearchView and set the searchable configuration final SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE); mSearchMenuItem = menu.findItem(R.id.menu_search); - final SearchView searchView = - (SearchView) mSearchMenuItem.getActionView(); + final SearchView searchView = (SearchView) mSearchMenuItem.getActionView(); // Assumes current activity is the searchable activity searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName())); searchView.setIconifiedByDefault(false); // Do not iconify the widget; expand it by default @@ -458,8 +486,8 @@ public class IITC_Mobile extends Activity @Override public boolean onPrepareOptionsMenu(final Menu menu) { boolean visible = false; - if (mNavigationHelper != null) - visible = !mNavigationHelper.isDrawerOpened(); + if (mNavigationHelper != null) visible = !mNavigationHelper.isDrawerOpened(); + if (mIsLoading) visible = false; for (int i = 0; i < menu.size(); i++) { final MenuItem item = menu.getItem(i); @@ -475,6 +503,7 @@ public class IITC_Mobile extends Activity case R.id.locate: item.setVisible(visible); + item.setEnabled(!mIsLoading); item.setIcon(mUserLocation.isFollowing() ? R.drawable.ic_action_location_follow : R.drawable.ic_action_location_found); @@ -658,10 +687,20 @@ public class IITC_Mobile extends Activity public void setLoadingState(final boolean isLoading) { mIsLoading = isLoading; - mNavigationHelper.onLoadingStateChanged(); - + invalidateOptionsMenu(); updateViews(); + + if (mSearchTerm != null && !isLoading) { + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + // switchToPane(Pane.MAP); + mIitcWebView.loadUrl("javascript:search('" + mSearchTerm + "');"); + mSearchTerm = null; + } + }, 5000); + } } private void updateViews() { diff --git a/mobile/src/com/cradle/iitc_mobile/fragments/MainSettings.java b/mobile/src/com/cradle/iitc_mobile/fragments/MainSettings.java index 79a46ef6..f12e5267 100644 --- a/mobile/src/com/cradle/iitc_mobile/fragments/MainSettings.java +++ b/mobile/src/com/cradle/iitc_mobile/fragments/MainSettings.java @@ -5,7 +5,6 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Bundle; -import android.preference.EditTextPreference; import android.preference.ListPreference; import android.preference.Preference; import android.preference.Preference.OnPreferenceChangeListener; @@ -23,39 +22,39 @@ import com.cradle.iitc_mobile.R; public class MainSettings extends PreferenceFragment { @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.preferences); // set versions - String iitcVersion = getArguments().getString("iitc_version"); + final String iitcVersion = getArguments().getString("iitc_version"); String buildVersion = "unknown"; - PackageManager pm = getActivity().getPackageManager(); + final PackageManager pm = getActivity().getPackageManager(); try { - PackageInfo info = pm.getPackageInfo(getActivity().getPackageName(), 0); + final PackageInfo info = pm.getPackageInfo(getActivity().getPackageName(), 0); buildVersion = info.versionName; - } catch (NameNotFoundException e) { + } catch (final NameNotFoundException e) { Log.w(e); } - IITC_AboutDialogPreference pref_about = (IITC_AboutDialogPreference) findPreference("pref_about"); + final IITC_AboutDialogPreference pref_about = (IITC_AboutDialogPreference) findPreference("pref_about"); pref_about.setVersions(iitcVersion, buildVersion); final ListPreference pref_user_location_mode = (ListPreference) findPreference("pref_user_location_mode"); pref_user_location_mode.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - int mode = Integer.parseInt((String) newValue); + public boolean onPreferenceChange(final Preference preference, final Object newValue) { + final int mode = Integer.parseInt((String) newValue); preference.setSummary(getResources().getStringArray(R.array.pref_user_location_titles)[mode]); return true; } }); - String value = getPreferenceManager().getSharedPreferences().getString("pref_user_location_mode", "0"); - int mode = Integer.parseInt(value); + final String value = getPreferenceManager().getSharedPreferences().getString("pref_user_location_mode", "0"); + final int mode = Integer.parseInt(value); pref_user_location_mode.setSummary(getResources().getStringArray(R.array.pref_user_location_titles)[mode]); } @@ -64,7 +63,7 @@ public class MainSettings extends PreferenceFragment { // so we need some additional hacks... // thx to http://stackoverflow.com/a/16800527/2638486 !! @Override - public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { + public boolean onPreferenceTreeClick(final PreferenceScreen preferenceScreen, final Preference preference) { if (preference.getTitle().toString().equals(getString(R.string.pref_advanced_options)) || preference.getTitle().toString().equals(getString(R.string.pref_about_title))) { initializeActionBar((PreferenceScreen) preference); @@ -76,27 +75,27 @@ public class MainSettings extends PreferenceFragment { // because PreferenceScreens are dialogs which swallow // events instead of passing to the activity // Related Issue: https://code.google.com/p/android/issues/detail?id=4611 - public static void initializeActionBar(PreferenceScreen preferenceScreen) { + public static void initializeActionBar(final PreferenceScreen preferenceScreen) { final Dialog dialog = preferenceScreen.getDialog(); if (dialog != null) { if (dialog.getActionBar() != null) dialog.getActionBar().setDisplayHomeAsUpEnabled(true); - View homeBtn = dialog.findViewById(android.R.id.home); + final View homeBtn = dialog.findViewById(android.R.id.home); if (homeBtn != null) { - View.OnClickListener dismissDialogClickListener = new View.OnClickListener() { + final View.OnClickListener dismissDialogClickListener = new View.OnClickListener() { @Override - public void onClick(View v) { + public void onClick(final View v) { dialog.dismiss(); } }; - ViewParent homeBtnContainer = homeBtn.getParent(); + final ViewParent homeBtnContainer = homeBtn.getParent(); // The home button is an ImageView inside a FrameLayout if (homeBtnContainer instanceof FrameLayout) { - ViewGroup containerParent = (ViewGroup) homeBtnContainer.getParent(); + final ViewGroup containerParent = (ViewGroup) homeBtnContainer.getParent(); if (containerParent instanceof LinearLayout) { // This view also contains the title text, set the whole view as clickable diff --git a/mobile/src/com/cradle/iitc_mobile/share/IntentFragmentAdapter.java b/mobile/src/com/cradle/iitc_mobile/share/FragmentAdapter.java similarity index 56% rename from mobile/src/com/cradle/iitc_mobile/share/IntentFragmentAdapter.java rename to mobile/src/com/cradle/iitc_mobile/share/FragmentAdapter.java index fd803345..fa3dfbdf 100644 --- a/mobile/src/com/cradle/iitc_mobile/share/IntentFragmentAdapter.java +++ b/mobile/src/com/cradle/iitc_mobile/share/FragmentAdapter.java @@ -7,16 +7,16 @@ import android.support.v4.app.FragmentPagerAdapter; import java.util.ArrayList; import java.util.List; -public class IntentFragmentAdapter extends FragmentPagerAdapter { - private final List mTabs; +public class FragmentAdapter extends FragmentPagerAdapter { + private final List mTabs; - public IntentFragmentAdapter(FragmentManager fm) { + public FragmentAdapter(final FragmentManager fm) { super(fm); - mTabs = new ArrayList(); + mTabs = new ArrayList(); } - public void add(IntentFragment fragment) { + public void add(final IntentListFragment fragment) { mTabs.add(fragment); } @@ -26,12 +26,12 @@ public class IntentFragmentAdapter extends FragmentPagerAdapter { } @Override - public Fragment getItem(int position) { + public Fragment getItem(final int position) { return mTabs.get(position); } @Override - public CharSequence getPageTitle(int position) { + public CharSequence getPageTitle(final int position) { return mTabs.get(position).getTitle(); } } \ No newline at end of file diff --git a/mobile/src/com/cradle/iitc_mobile/share/IntentAdapter.java b/mobile/src/com/cradle/iitc_mobile/share/IntentAdapter.java new file mode 100644 index 00000000..3c3cde4a --- /dev/null +++ b/mobile/src/com/cradle/iitc_mobile/share/IntentAdapter.java @@ -0,0 +1,68 @@ +package com.cradle.iitc_mobile.share; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.graphics.drawable.Drawable; +import android.util.DisplayMetrics; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + +import com.cradle.iitc_mobile.Log; +import com.cradle.iitc_mobile.R; + +import java.util.Collections; +import java.util.List; + +class IntentAdapter extends ArrayAdapter { + private static final int MDPI_PX = 36; + + private final PackageManager mPackageManager; + + public IntentAdapter(final Context context) { + super(context, android.R.layout.simple_list_item_1); + mPackageManager = getContext().getPackageManager(); + } + + @Override + public View getView(final int position, final View convertView, final ViewGroup parent) { + final LayoutInflater inflater = ((Activity) getContext()).getLayoutInflater(); + final TextView view = (TextView) inflater.inflate(android.R.layout.simple_list_item_1, parent, false); + + final Intent item = getItem(position); + + view.setText(IntentGenerator.getTitle(item)); + view.setCompoundDrawablePadding((int) getContext().getResources().getDimension(R.dimen.icon_margin)); + + // get icon and scale it manually to ensure that all have the same size + final DisplayMetrics dm = new DisplayMetrics(); + ((Activity) getContext()).getWindowManager().getDefaultDisplay().getMetrics(dm); + final float densityScale = dm.density; + final float scaledWidth = MDPI_PX * densityScale; + final float scaledHeight = MDPI_PX * densityScale; + + try { + final Drawable icon = mPackageManager.getActivityIcon(item); + icon.setBounds(0, 0, Math.round(scaledWidth), Math.round(scaledHeight)); + view.setCompoundDrawables(icon, null, null, null); + } catch (final NameNotFoundException e) { + Log.e(e); + } + + return view; + } + + public void setIntents(final List intents) { + Collections.sort(intents, ((ShareActivity) getContext()).getIntentComparator()); + + setNotifyOnChange(false); + clear(); + addAll(intents); + notifyDataSetChanged(); + } +} \ No newline at end of file diff --git a/mobile/src/com/cradle/iitc_mobile/share/IntentComparator.java b/mobile/src/com/cradle/iitc_mobile/share/IntentComparator.java index 6ae1d0a8..8e0dead9 100644 --- a/mobile/src/com/cradle/iitc_mobile/share/IntentComparator.java +++ b/mobile/src/com/cradle/iitc_mobile/share/IntentComparator.java @@ -1,7 +1,8 @@ package com.cradle.iitc_mobile.share; import android.app.Activity; -import android.content.pm.PackageManager; +import android.content.ComponentName; +import android.content.Intent; import android.content.pm.ResolveInfo; import com.cradle.iitc_mobile.Log; @@ -16,7 +17,112 @@ import java.io.Serializable; import java.util.Comparator; import java.util.HashMap; -public class IntentComparator implements Comparator { +public class IntentComparator implements Comparator { + private static final String INTENT_MAP_FILE = "share_intent_map"; + + private final ShareActivity mActivity; + + private HashMap mIntentMap = new HashMap(); + + IntentComparator(final ShareActivity activity) { + mActivity = activity; + load(); + } + + @SuppressWarnings("unchecked") + private void load() { + ObjectInputStream objectIn = null; + + try { + final FileInputStream fileIn = mActivity.openFileInput(INTENT_MAP_FILE); + objectIn = new ObjectInputStream(fileIn); + mIntentMap = (HashMap) objectIn.readObject(); + } catch (final FileNotFoundException e) { + // Do nothing + } catch (final IOException e) { + Log.w(e); + } catch (final ClassNotFoundException e) { + Log.w(e); + } finally { + if (objectIn != null) { + try { + objectIn.close(); + } catch (final IOException e) { + Log.w(e); + } + } + } + } + + @Override + public int compare(final Intent lhs, final Intent rhs) { + int order; + + // we might be merging multiple intents, so there could be more than one default + if (IntentGenerator.isDefault(lhs) && !IntentGenerator.isDefault(rhs)) + return -1; + if (IntentGenerator.isDefault(rhs) && !IntentGenerator.isDefault(lhs)) + return 1; + + final ComponentName lComponent = lhs.getComponent(); + final ComponentName rComponent = rhs.getComponent(); + + // Show more frequently used items in top + Integer lCount = mIntentMap.get(new Component(lComponent)); + Integer rCount = mIntentMap.get(new Component(rComponent)); + + if (lCount == null) lCount = 0; + if (rCount == null) rCount = 0; + + if (lCount > rCount) return -1; + if (lCount < rCount) return 1; + + // still no order. fall back to alphabetical order + order = IntentGenerator.getTitle(lhs).compareTo(IntentGenerator.getTitle(rhs)); + if (order != 0) return order; + + order = lComponent.getPackageName().compareTo(rComponent.getPackageName()); + if (order != 0) return order; + + order = lComponent.getClassName().compareTo(rComponent.getClassName()); + if (order != 0) return order; + + return 0; + } + + public void save() { + ObjectOutputStream objectOut = null; + try { + final FileOutputStream fileOut = mActivity.openFileOutput(INTENT_MAP_FILE, Activity.MODE_PRIVATE); + objectOut = new ObjectOutputStream(fileOut); + objectOut.writeObject(mIntentMap); + fileOut.getFD().sync(); + } catch (final IOException e) { + Log.w(e); + } finally { + if (objectOut != null) { + try { + objectOut.close(); + } catch (final IOException e) { + Log.w(e); + } + } + } + } + + public void trackIntentSelection(final Intent intent) { + final Component component = new Component(intent.getComponent()); + + Integer counter = mIntentMap.get(component); + if (counter == null) { + counter = 1; + } else { + counter++; + } + + mIntentMap.put(component, counter); + } + public static class Component implements Serializable { private static final long serialVersionUID = -5043782754318376792L; @@ -28,19 +134,24 @@ public class IntentComparator implements Comparator { packageName = null; } - public Component(ResolveInfo info) { + public Component(final ComponentName cn) { + name = cn.getClassName(); + packageName = cn.getPackageName(); + } + + public Component(final ResolveInfo info) { name = info.activityInfo.name; packageName = info.activityInfo.applicationInfo.packageName; } @Override - public boolean equals(Object o) { + public boolean equals(final Object o) { if (this == o) return true; if (o == null) return false; if (o.getClass() != this.getClass()) return false; - Component c = (Component) o; + final Component c = (Component) o; if (name == null) { if (c.name != null) return false; @@ -83,120 +194,4 @@ public class IntentComparator implements Comparator { : name); } } - - private static final String INTENT_MAP_FILE = "share_intent_map"; - - private ShareActivity mActivity; - private HashMap mIntentMap = new HashMap(); - private PackageManager mPackageManager; - - IntentComparator(ShareActivity activity) { - mActivity = activity; - mPackageManager = activity.getPackageManager(); - - load(); - } - - @SuppressWarnings("unchecked") - private void load() { - ObjectInputStream objectIn = null; - - try { - FileInputStream fileIn = mActivity.openFileInput(INTENT_MAP_FILE); - objectIn = new ObjectInputStream(fileIn); - mIntentMap = (HashMap) objectIn.readObject(); - } catch (FileNotFoundException e) { - // Do nothing - } catch (IOException e) { - Log.w(e); - } catch (ClassNotFoundException e) { - Log.w(e); - } finally { - if (objectIn != null) { - try { - objectIn.close(); - } catch (IOException e) { - Log.w(e); - } - } - } - } - - @Override - public int compare(ResolveInfo lhs, ResolveInfo rhs) { - int order; - - // we might be merging multiple intents, so there could be more than one default - if (lhs.isDefault && !rhs.isDefault) - return -1; - if (rhs.isDefault && !lhs.isDefault) - return 1; - - // Show more frequently used items in top - Integer lCount = mIntentMap.get(new Component(lhs)); - Integer rCount = mIntentMap.get(new Component(rhs)); - - if (lCount == null) lCount = 0; - if (rCount == null) rCount = 0; - - if (lCount > rCount) return -1; - if (lCount < rCount) return 1; - - // don't known how these are set (or if they can be set at all), but it sounds promising... - if (lhs.preferredOrder != rhs.preferredOrder) - return rhs.preferredOrder - lhs.preferredOrder; - if (lhs.priority != rhs.priority) - return rhs.priority - lhs.priority; - - // still no order. fall back to alphabetical order - order = lhs.loadLabel(mPackageManager).toString().compareTo( - rhs.loadLabel(mPackageManager).toString()); - if (order != 0) return order; - - if (lhs.nonLocalizedLabel != null && rhs.nonLocalizedLabel != null) { - order = lhs.nonLocalizedLabel.toString().compareTo(rhs.nonLocalizedLabel.toString()); - if (order != 0) return order; - } - - order = lhs.activityInfo.packageName.compareTo(rhs.activityInfo.packageName); - if (order != 0) return order; - - order = lhs.activityInfo.name.compareTo(rhs.activityInfo.name); - if (order != 0) return order; - - return 0; - } - - public void save() { - ObjectOutputStream objectOut = null; - try { - FileOutputStream fileOut = mActivity.openFileOutput(INTENT_MAP_FILE, Activity.MODE_PRIVATE); - objectOut = new ObjectOutputStream(fileOut); - objectOut.writeObject(mIntentMap); - fileOut.getFD().sync(); - } catch (IOException e) { - Log.w(e); - } finally { - if (objectOut != null) { - try { - objectOut.close(); - } catch (IOException e) { - Log.w(e); - } - } - } - } - - public void trackIntentSelection(ResolveInfo info) { - Component component = new Component(info); - - Integer counter = mIntentMap.get(component); - if (counter == null) { - counter = 1; - } else { - counter++; - } - - mIntentMap.put(component, counter); - } } \ No newline at end of file diff --git a/mobile/src/com/cradle/iitc_mobile/share/IntentGenerator.java b/mobile/src/com/cradle/iitc_mobile/share/IntentGenerator.java new file mode 100644 index 00000000..6d6d4694 --- /dev/null +++ b/mobile/src/com/cradle/iitc_mobile/share/IntentGenerator.java @@ -0,0 +1,143 @@ +package com.cradle.iitc_mobile.share; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.net.Uri; + +import com.cradle.iitc_mobile.Log; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +public class IntentGenerator { + private static final String EXTRA_FLAG_IS_DEFAULT = "IITCM_IS_DEFAULT"; + private static final String EXTRA_FLAG_TITLE = "IITCM_TITLE"; + private static final HashSet KNOWN_COPY_HANDLERS = new HashSet(); + + static { + if (KNOWN_COPY_HANDLERS.isEmpty()) { + + KNOWN_COPY_HANDLERS.add(new ComponentName( + "com.google.android.apps.docs", + "com.google.android.apps.docs.app.SendTextToClipboardActivity")); + + KNOWN_COPY_HANDLERS.add(new ComponentName( + "com.aokp.romcontrol", + "com.aokp.romcontrol.ShareToClipboard")); + } + } + + public static String getTitle(final Intent intent) { + if (intent.hasExtra(EXTRA_FLAG_TITLE)) + return intent.getStringExtra(EXTRA_FLAG_TITLE); + + throw new IllegalArgumentException("Got an intent not generated by IntentGenerator"); + } + + public static boolean isDefault(final Intent intent) { + return intent.hasExtra(EXTRA_FLAG_IS_DEFAULT) && intent.getBooleanExtra(EXTRA_FLAG_IS_DEFAULT, false); + } + + private final Context mContext; + + private final PackageManager mPackageManager; + + public IntentGenerator(final Context context) { + mContext = context; + mPackageManager = mContext.getPackageManager(); + } + + private boolean containsCopyIntent(final List targets) { + for (final Intent intent : targets) { + for (final ComponentName handler : KNOWN_COPY_HANDLERS) { + if (handler.equals(intent.getComponent())) return true; + } + } + return false; + } + + private ArrayList resolveTargets(final Intent intent) { + final String packageName = mContext.getPackageName(); + final List activityList = mPackageManager.queryIntentActivities(intent, 0); + final ResolveInfo defaultTarget = mPackageManager.resolveActivity(intent, 0); + + final ArrayList list = new ArrayList(activityList.size()); + + for (final ResolveInfo resolveInfo : activityList) { + final ActivityInfo activity = resolveInfo.activityInfo; + final ComponentName component = new ComponentName(activity.packageName, activity.name); + + // remove IITCm from list + if (activity.packageName.equals(packageName)) continue; + + final Intent targetIntent = new Intent(intent) + .setComponent(component) + .putExtra(EXTRA_FLAG_TITLE, activity.loadLabel(mPackageManager)); + + if (resolveInfo.activityInfo.name.equals(defaultTarget.activityInfo.name) && + resolveInfo.activityInfo.packageName.equals(defaultTarget.activityInfo.packageName)) { + targetIntent.putExtra(EXTRA_FLAG_IS_DEFAULT, true); + } + + list.add(targetIntent); + } + + return list; + } + + public ArrayList getBrowserIntents(final String title, final String url) { + final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)) + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); + + return resolveTargets(intent); + } + + public ArrayList getGeoIntents(final String title, final String mLl, final int mZoom) { + final Intent intent = new Intent(android.content.Intent.ACTION_VIEW, + Uri.parse(String.format("geo:%s&z=%d", mLl, mZoom))) + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); + + final ArrayList targets = resolveTargets(intent); + + // According to https://developer.android.com/guide/components/intents-common.html, markers can be labeled. + // Unfortunately, only Google Maps supports this, most other apps fail + for (final Intent target : targets) { + final ComponentName cn = target.getComponent(); + if ("com.google.android.apps.maps".equals(cn.getPackageName())) { + try { + final String encodedTitle = URLEncoder.encode(title, "UTF-8"); + target.setData(Uri.parse(String.format("geo:0,0?q=%s%%20(%s)&z=%d", mLl, encodedTitle, mZoom))); + } catch (final UnsupportedEncodingException e) { + Log.w(e); + } + break; + } + } + + return targets; + } + + public ArrayList getShareIntents(final String title, final String text) { + final Intent intent = new Intent(Intent.ACTION_SEND) + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) + .setType("text/plain") + .putExtra(Intent.EXTRA_TEXT, text) + .putExtra(Intent.EXTRA_SUBJECT, title); + + final ArrayList targets = resolveTargets(intent); + + if (!containsCopyIntent(targets)) { + // add SendToClipboard intent in case Drive is not installed + targets.add(new Intent(intent).setComponent(new ComponentName(mContext, SendToClipboard.class))); + } + + return targets; + } +} diff --git a/mobile/src/com/cradle/iitc_mobile/share/IntentFragment.java b/mobile/src/com/cradle/iitc_mobile/share/IntentListFragment.java similarity index 58% rename from mobile/src/com/cradle/iitc_mobile/share/IntentFragment.java rename to mobile/src/com/cradle/iitc_mobile/share/IntentListFragment.java index ef3aaaeb..4258e219 100644 --- a/mobile/src/com/cradle/iitc_mobile/share/IntentFragment.java +++ b/mobile/src/com/cradle/iitc_mobile/share/IntentListFragment.java @@ -10,13 +10,15 @@ import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; +import android.widget.ListView; import java.util.ArrayList; -public class IntentFragment extends Fragment implements OnScrollListener, OnItemClickListener { +public class IntentListFragment extends Fragment implements OnScrollListener, OnItemClickListener { private ArrayList mIntents; - private IntentListView mListView; + private IntentAdapter mAdapter; private int mScrollIndex, mScrollTop; + private ListView mListView; public int getIcon() { return getArguments().getInt("icon"); @@ -27,12 +29,15 @@ public class IntentFragment extends Fragment implements OnScrollListener, OnItem } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - Bundle args = getArguments(); + public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) { + final Bundle args = getArguments(); mIntents = args.getParcelableArrayList("intents"); - mListView = new IntentListView(getActivity()); - mListView.setIntents(mIntents); + + mAdapter = new IntentAdapter(getActivity()); + mAdapter.setIntents(mIntents); + + mListView = new ListView(getActivity()); if (mScrollIndex != -1 && mScrollTop != -1) { mListView.setSelectionFromTop(mScrollIndex, mScrollTop); } @@ -43,23 +48,22 @@ public class IntentFragment extends Fragment implements OnScrollListener, OnItem } @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - ((ShareActivity) getActivity()).getIntentComparator().trackIntentSelection(mListView.getItem(position)); + public void onItemClick(final AdapterView parent, final View view, final int position, final long id) { + final Intent intent = mAdapter.getItem(position); + ((ShareActivity) getActivity()).getIntentComparator().trackIntentSelection(intent); - Intent intent = mListView.getTargetIntent(position); startActivity(intent); - getActivity().finish(); } @Override - public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { + public void onScroll(final AbsListView lv, final int firstItem, final int visibleItems, final int totalItems) { mScrollIndex = mListView.getFirstVisiblePosition(); - View v = mListView.getChildAt(0); + final View v = mListView.getChildAt(0); mScrollTop = (v == null) ? 0 : v.getTop(); } @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { + public void onScrollStateChanged(final AbsListView view, final int scrollState) { } } \ No newline at end of file diff --git a/mobile/src/com/cradle/iitc_mobile/share/IntentListView.java b/mobile/src/com/cradle/iitc_mobile/share/IntentListView.java deleted file mode 100644 index 629132f1..00000000 --- a/mobile/src/com/cradle/iitc_mobile/share/IntentListView.java +++ /dev/null @@ -1,227 +0,0 @@ -package com.cradle.iitc_mobile.share; - -import android.app.Activity; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.util.DisplayMetrics; -import android.util.Pair; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.ListView; -import android.widget.TextView; - -import com.cradle.iitc_mobile.R; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; - -public class IntentListView extends ListView { - private static class CopyHandler extends Pair { - public CopyHandler(ResolveInfo resolveInfo) { - super(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name); - } - - public CopyHandler(String packageName, String name) { - super(packageName, name); - } - } - - private class IntentAdapter extends ArrayAdapter { - - // actually the mdpi pixel size is 48, but this looks ugly...so scale icons down for listView - private static final int MDPI_PX = 36; - - private IntentAdapter() { - super(IntentListView.this.getContext(), android.R.layout.simple_list_item_1); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - LayoutInflater inflater = ((Activity) getContext()).getLayoutInflater(); - TextView view = (TextView) inflater.inflate(android.R.layout.simple_list_item_1, parent, false); - - ActivityInfo info = getItem(position).activityInfo; - CharSequence label = info.loadLabel(mPackageManager); - - // get icon and scale it manually to ensure that all have the same size - Drawable icon = info.loadIcon(mPackageManager); - DisplayMetrics dm = new DisplayMetrics(); - ((Activity) getContext()).getWindowManager().getDefaultDisplay().getMetrics(dm); - float densityScale = dm.density; - float scaledWidth = MDPI_PX * densityScale; - float scaledHeight = MDPI_PX * densityScale; - icon.setBounds(0,0,Math.round(scaledWidth),Math.round(scaledHeight)); - - view.setText(label); - view.setCompoundDrawablePadding((int) getResources().getDimension(R.dimen.icon_margin)); - view.setCompoundDrawables(icon, null, null, null); - - return view; - } - } - - private static final HashSet KNOWN_COPY_HANDLERS = new HashSet(); - - static { - if (KNOWN_COPY_HANDLERS.isEmpty()) { - - KNOWN_COPY_HANDLERS.add(new CopyHandler( - "com.google.android.apps.docs", - "com.google.android.apps.docs.app.SendTextToClipboardActivity")); - - KNOWN_COPY_HANDLERS.add(new CopyHandler( - "com.aokp.romcontrol", - "com.aokp.romcontrol.ShareToClipboard")); - } - } - - private static final HashSet GEOLABEL_WHITELIST = new HashSet(); - - static { - if (GEOLABEL_WHITELIST.isEmpty()) { - GEOLABEL_WHITELIST.add("com.google.android.apps.maps"); - } - } - - private HashMap mActivities = new HashMap(); - - private IntentAdapter mAdapter; - private PackageManager mPackageManager; - - public IntentListView(Context context) { - super(context); - init(); - } - - public IntentListView(Context context, AttributeSet attrs) { - super(context, attrs); - init(); - } - - public IntentListView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - init(); - } - - private void init() { - mPackageManager = getContext().getPackageManager(); - mAdapter = new IntentAdapter(); - setAdapter(mAdapter); - } - - public ResolveInfo getItem(int position) { - return mAdapter.getItem(position); - } - - public Intent getTargetIntent(int position) { - ActivityInfo activity = mAdapter.getItem(position).activityInfo; - - ComponentName activityId = new ComponentName(activity.packageName, activity.name); - - Intent intentType = mActivities.get(activityId); - - Intent intent = new Intent(intentType) - .setComponent(activityId) - .setPackage(activity.packageName); - - return intent; - } - - // wrapper method for single intents - public void setIntent(Intent intent) { - ArrayList intentList = new ArrayList(1); - intentList.add(intent); - setIntents(intentList); - } - - public void setIntents(ArrayList intents) { - mAdapter.setNotifyOnChange(false); - mAdapter.clear(); - - String packageName = getContext().getPackageName(); - - ArrayList allActivities = new ArrayList(); - - for (Intent intent : intents) { - List activityList = mPackageManager.queryIntentActivities(intent, 0); - - ResolveInfo defaultTarget = mPackageManager.resolveActivity(intent, 0); - - boolean hasCopyIntent = false; - for (ResolveInfo resolveInfo : activityList) { // search for "Copy to clipboard" handler - CopyHandler handler = new CopyHandler(resolveInfo); - - if (KNOWN_COPY_HANDLERS.contains(handler)) { - hasCopyIntent = true; - } - } - - // use traditional loop since list may change during iteration - for (int i = 0; i < activityList.size(); i++) { - ResolveInfo info = activityList.get(i); - ActivityInfo activity = info.activityInfo; - - // remove all apps that don't support a geo intent like geo:0,0?q=lat,lng(label) - // they'll receive a default geo intent like geo:lat,lng - if (intent.getData() != null && - "geo:0,0?q=".regionMatches(false, 0, intent.getData().toString(), 0, 10) && - !GEOLABEL_WHITELIST.contains(activity.packageName)) { - activityList.remove(i); - i--; - continue; - } - - // fix bug in PackageManager - a replaced package name might cause non-exported intents to appear - if (!activity.exported && !activity.packageName.equals(packageName)) { - activityList.remove(i); - i--; - continue; - } - - // remove all IITCm intents, except for SendToClipboard in case Drive is not installed - if (activity.packageName.equals(packageName)) { - if (hasCopyIntent || !activity.name.equals(SendToClipboard.class.getCanonicalName())) { - activityList.remove(i); - i--; - continue; - } - } - } - - // add to activity hash map if they doesn't exist - for (ResolveInfo resolveInfo : activityList) { - - ActivityInfo activity = resolveInfo.activityInfo; - ComponentName activityId = new ComponentName(activity.packageName, activity.name); - - // ResolveInfo.isDefault usually means "The target would like to be considered a default action that the - // user can perform on this data." It is set by the package manager, but we overwrite it to store - // whether this app is the default for the given intent - resolveInfo.isDefault = resolveInfo.activityInfo.name.equals(defaultTarget.activityInfo.name) - && resolveInfo.activityInfo.packageName.equals(defaultTarget.activityInfo.packageName); - - if (!mActivities.containsKey(activityId)) { - mActivities.put(activityId, intent); - allActivities.add(resolveInfo); - } - } - } - - Collections.sort(allActivities, ((ShareActivity) getContext()).getIntentComparator()); - - mAdapter.addAll(allActivities); - mAdapter.setNotifyOnChange(true); - mAdapter.notifyDataSetChanged(); - } -} diff --git a/mobile/src/com/cradle/iitc_mobile/share/ShareActivity.java b/mobile/src/com/cradle/iitc_mobile/share/ShareActivity.java index c6bded02..e62fbbf5 100644 --- a/mobile/src/com/cradle/iitc_mobile/share/ShareActivity.java +++ b/mobile/src/com/cradle/iitc_mobile/share/ShareActivity.java @@ -4,7 +4,6 @@ import android.app.ActionBar; import android.app.FragmentTransaction; import android.content.Intent; import android.content.SharedPreferences; -import android.net.Uri; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.v4.app.FragmentActivity; @@ -12,16 +11,14 @@ import android.support.v4.app.NavUtils; import android.support.v4.view.ViewPager; import android.view.MenuItem; -import com.cradle.iitc_mobile.Log; import com.cradle.iitc_mobile.R; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; import java.util.ArrayList; public class ShareActivity extends FragmentActivity implements ActionBar.TabListener { private IntentComparator mComparator; - private IntentFragmentAdapter mFragmentAdapter; + private FragmentAdapter mFragmentAdapter; + private IntentGenerator mGenerator; private boolean mIsPortal; private String mLl; private SharedPreferences mSharedPrefs = null; @@ -29,9 +26,9 @@ public class ShareActivity extends FragmentActivity implements ActionBar.TabList private ViewPager mViewPager; private int mZoom; - private void addTab(ArrayList intents, int label, int icon) { - IntentFragment fragment = new IntentFragment(); - Bundle args = new Bundle(); + private void addTab(final ArrayList intents, final int label, final int icon) { + final IntentListFragment fragment = new IntentListFragment(); + final Bundle args = new Bundle(); args.putParcelableArrayList("intents", intents); args.putString("title", getString(label)); args.putInt("icon", icon); @@ -39,13 +36,7 @@ public class ShareActivity extends FragmentActivity implements ActionBar.TabList mFragmentAdapter.add(fragment); } - private void addTab(Intent intent, int label, int icon) { - ArrayList intents = new ArrayList(1); - intents.add(intent); - addTab(intents, label, icon); - } - - private String getUrl() { + private String getIntelUrl() { String url = "http://www.ingress.com/intel?ll=" + mLl + "&z=" + mZoom; if (mIsPortal) { url += "&pll=" + mLl; @@ -53,7 +44,7 @@ public class ShareActivity extends FragmentActivity implements ActionBar.TabList return url; } - private void setSelected(int position) { + private void setSelected(final int position) { // Activity not fully loaded yet (may occur during tab creation) if (mSharedPrefs == null) return; @@ -63,57 +54,20 @@ public class ShareActivity extends FragmentActivity implements ActionBar.TabList .apply(); } - private void setupIntents() { - setupShareIntent(getUrl()); - - // gmaps supports labeled markers via geo intent...most other navigation apps don't - // so provide two different geo intents and filter them in IntentListView - ArrayList intents = new ArrayList(); - String gMapsUri; - try { - /* - * doesn't work anymore since gmaps v7.6.1 - gMapsUri = "http://maps.google.com/?q=loc:" + mLl - + "%20(" + URLEncoder.encode(mTitle, "UTF-8") + ")&z=" + mZoom; - */ - gMapsUri = "geo:0,0?q=" + mLl + "%20(" + URLEncoder.encode(mTitle, "UTF-8") + ")"; - } catch (UnsupportedEncodingException e) { - gMapsUri = "http://maps.google.com/?ll=" + mLl + "&z=" + mZoom; - Log.w(e); - } - Intent gMapsIntent = new Intent(android.content.Intent.ACTION_VIEW, Uri.parse(gMapsUri)); - intents.add(gMapsIntent); - String geoUri = "geo:" + mLl; - Intent geoIntent = new Intent(android.content.Intent.ACTION_VIEW, Uri.parse(geoUri)); - intents.add(geoIntent); - addTab(intents, R.string.tab_map, R.drawable.ic_action_place); - - Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(getUrl())); - addTab(intent, R.string.tab_browser, R.drawable.ic_action_web_site); - } - - private void setupShareIntent(String str) { - Intent intent = new Intent(Intent.ACTION_SEND); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); - intent.setType("text/plain"); - intent.putExtra(Intent.EXTRA_TEXT, str); - intent.putExtra(Intent.EXTRA_SUBJECT, mTitle); - addTab(intent, R.string.tab_share, R.drawable.ic_action_share); - } - @Override - protected void onCreate(Bundle savedInstanceState) { + protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_share); mComparator = new IntentComparator(this); + mGenerator = new IntentGenerator(this); - mFragmentAdapter = new IntentFragmentAdapter(getSupportFragmentManager()); + mFragmentAdapter = new FragmentAdapter(getSupportFragmentManager()); final ActionBar actionBar = getActionBar(); actionBar.setDisplayHomeAsUpEnabled(true); - Intent intent = getIntent(); + final Intent intent = getIntent(); // from portallinks/permalinks we build 3 intents (share / geo / vanilla-intel-link) if (!intent.getBooleanExtra("onlyShare", false)) { mTitle = intent.getStringExtra("title"); @@ -122,10 +76,21 @@ public class ShareActivity extends FragmentActivity implements ActionBar.TabList mIsPortal = intent.getBooleanExtra("isPortal", false); actionBar.setTitle(mTitle); - setupIntents(); + + addTab(mGenerator.getShareIntents(mTitle, getIntelUrl()), + R.string.tab_share, + R.drawable.ic_action_share); + addTab(mGenerator.getGeoIntents(mTitle, mLl, mZoom), + R.string.tab_map, + R.drawable.ic_action_place); + addTab(mGenerator.getBrowserIntents(mTitle, getIntelUrl()), + R.string.tab_browser, + R.drawable.ic_action_web_site); } else { mTitle = getString(R.string.app_name); - setupShareIntent(intent.getStringExtra("shareString")); + final String shareString = intent.getStringExtra("shareString"); + + addTab(mGenerator.getShareIntents(mTitle, shareString), R.string.tab_share, R.drawable.ic_action_share); } mViewPager = (ViewPager) findViewById(R.id.pager); @@ -133,7 +98,7 @@ public class ShareActivity extends FragmentActivity implements ActionBar.TabList mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { @Override - public void onPageSelected(int position) { + public void onPageSelected(final int position) { if (actionBar.getNavigationMode() != ActionBar.NAVIGATION_MODE_STANDARD) { actionBar.setSelectedNavigationItem(position); } @@ -142,7 +107,7 @@ public class ShareActivity extends FragmentActivity implements ActionBar.TabList }); for (int i = 0; i < mFragmentAdapter.getCount(); i++) { - IntentFragment fragment = (IntentFragment) mFragmentAdapter.getItem(i); + final IntentListFragment fragment = (IntentListFragment) mFragmentAdapter.getItem(i); actionBar.addTab(actionBar .newTab() @@ -156,7 +121,7 @@ public class ShareActivity extends FragmentActivity implements ActionBar.TabList } mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); - int selected = mSharedPrefs.getInt("pref_share_selected_tab", 0); + final int selected = mSharedPrefs.getInt("pref_share_selected_tab", 0); if (selected < mFragmentAdapter.getCount()) { mViewPager.setCurrentItem(selected); if (actionBar.getNavigationMode() != ActionBar.NAVIGATION_MODE_STANDARD) { @@ -176,7 +141,7 @@ public class ShareActivity extends FragmentActivity implements ActionBar.TabList } @Override - public boolean onOptionsItemSelected(MenuItem item) { + public boolean onOptionsItemSelected(final MenuItem item) { switch (item.getItemId()) { case android.R.id.home: NavUtils.navigateUpFromSameTask(this); @@ -186,17 +151,17 @@ public class ShareActivity extends FragmentActivity implements ActionBar.TabList } @Override - public void onTabReselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) { + public void onTabReselected(final ActionBar.Tab tab, final FragmentTransaction fragmentTransaction) { } @Override - public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) { - int position = tab.getPosition(); + public void onTabSelected(final ActionBar.Tab tab, final FragmentTransaction fragmentTransaction) { + final int position = tab.getPosition(); mViewPager.setCurrentItem(position); setSelected(position); } @Override - public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) { + public void onTabUnselected(final ActionBar.Tab tab, final FragmentTransaction fragmentTransaction) { } }