diff --git a/mobile/AndroidManifest.xml b/mobile/AndroidManifest.xml index fdf4f9c1..c8ea1e87 100644 --- a/mobile/AndroidManifest.xml +++ b/mobile/AndroidManifest.xml @@ -72,12 +72,38 @@ + + + + + + + + + + + + + - @@ -107,4 +132,4 @@ android:value="com.cradle.iitc_mobile.IITC_Mobile"/> - + \ No newline at end of file diff --git a/mobile/libs/android-support-v4.jar b/mobile/libs/android-support-v4.jar new file mode 100644 index 00000000..428bdbc0 Binary files /dev/null and b/mobile/libs/android-support-v4.jar differ diff --git a/mobile/res/layout/activity_share.xml b/mobile/res/layout/activity_share.xml new file mode 100644 index 00000000..843ebf18 --- /dev/null +++ b/mobile/res/layout/activity_share.xml @@ -0,0 +1,6 @@ + diff --git a/mobile/res/values/dimens.xml b/mobile/res/values/dimens.xml index 4fb5f825..c67784b4 100644 --- a/mobile/res/values/dimens.xml +++ b/mobile/res/values/dimens.xml @@ -8,4 +8,6 @@ 400dip 200dip + 8dp + \ No newline at end of file diff --git a/mobile/src/com/cradle/iitc_mobile/IITC_JSInterface.java b/mobile/src/com/cradle/iitc_mobile/IITC_JSInterface.java index 7a4d7419..583a2003 100644 --- a/mobile/src/com/cradle/iitc_mobile/IITC_JSInterface.java +++ b/mobile/src/com/cradle/iitc_mobile/IITC_JSInterface.java @@ -5,7 +5,6 @@ import java.util.HashMap; import org.json.JSONArray; import org.json.JSONException; -import android.app.Activity; import android.app.AlertDialog; import android.content.ClipData; import android.content.ClipboardManager; @@ -13,11 +12,13 @@ import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnMultiChoiceClickListener; -import android.os.Bundle; +import android.content.Intent; import android.util.Log; import android.webkit.JavascriptInterface; import android.widget.Toast; +import com.cradle.iitc_mobile.share.ShareActivity; + // provide communication between IITC script and android app public class IITC_JSInterface { @@ -38,15 +39,12 @@ public class IITC_JSInterface { // open dialog to send geo intent for navigation apps like gmaps or waze etc... @JavascriptInterface public void intentPosLink(double lat, double lng, int zoom, String portalName) { - Bundle args = new Bundle(); - args.putDouble("lat", lat); - args.putDouble("lng", lng); - args.putInt("zoom", zoom); - args.putString("title", portalName); - - IITC_ShareDialog dialog = new IITC_ShareDialog(); - dialog.setArguments(args); - dialog.show(((Activity) context).getFragmentManager(), "ShareDialog"); + Intent intent = new Intent(context, ShareActivity.class); + intent.putExtra("lat", lat); + intent.putExtra("lng", lng); + intent.putExtra("zoom", zoom); + intent.putExtra("title", portalName); + context.startActivity(intent); } // disable javascript injection while spinner is enabled diff --git a/mobile/src/com/cradle/iitc_mobile/IITC_ShareDialog.java b/mobile/src/com/cradle/iitc_mobile/IITC_ShareDialog.java deleted file mode 100644 index cf0c9a2d..00000000 --- a/mobile/src/com/cradle/iitc_mobile/IITC_ShareDialog.java +++ /dev/null @@ -1,194 +0,0 @@ -package com.cradle.iitc_mobile; - -import java.util.ArrayList; -import java.util.List; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.DialogFragment; -import android.content.ClipData; -import android.content.ClipboardManager; -import android.content.ComponentName; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.pm.ComponentInfo; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.net.Uri; -import android.os.Bundle; -import android.os.Parcelable; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.TextView; -import android.widget.Toast; - -public class IITC_ShareDialog extends DialogFragment { - private abstract class Action { - private int mIcon; - private String mLabel; - - private Action(String label, int icon) { - mLabel = label; - mIcon = icon; - } - - abstract void invoke(); - } - - public class OnClickListener implements DialogInterface.OnClickListener { - @Override - public void onClick(DialogInterface dialog, int which) { - mAdapter.getItem(which).invoke(); - } - } - - class ActionAdapter extends ArrayAdapter { - private LayoutInflater mInflater; - - public ActionAdapter(Context context) { - super(context, android.R.layout.simple_list_item_1, ACTIONS); - mInflater = LayoutInflater.from(context); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - Action action = getItem(position); - - if (convertView == null) - convertView = mInflater.inflate(android.R.layout.simple_list_item_1, null); - - TextView tv = (TextView) convertView; - tv.setText(action.mLabel); - tv.setCompoundDrawablePadding(8); - tv.setCompoundDrawablesWithIntrinsicBounds(action.mIcon, 0, 0, 0); - return convertView; - } - } - - private final Action[] ACTIONS = { - new Action("Share…", R.drawable.ic_dialog_share) { - void invoke() { - 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, getUrl()); - intent.putExtra(Intent.EXTRA_SUBJECT, mTitle); - - startIntent(intent); - } - }, - new Action("View map with app…", R.drawable.location_map) { - void invoke() { - String geoUri = "geo:" + mLl; - Intent intent = new Intent(android.content.Intent.ACTION_VIEW, Uri.parse(geoUri)); - - startIntent(intent); - } - }, - new Action("Open with browser…", R.drawable.ic_dialog_browser) { - void invoke() { - Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(getUrl())); - - startIntent(intent); - } - }, - new Action("Copy to clipboard", R.drawable.ic_dialog_copy) { - void invoke() { - ClipData clip = ClipData.newPlainText("Copied text", getUrl()); - - ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService( - Activity.CLIPBOARD_SERVICE); - clipboard.setPrimaryClip(clip); - - Toast.makeText(getActivity(), "copied to clipboard", Toast.LENGTH_SHORT).show(); - } - } - }; - - private ActionAdapter mAdapter; - private boolean mIsPortal = true; - private String mLl; - private String mTitle; - private int mZoom; - - private String getUrl() { - String url = "http://www.ingress.com/intel?ll=" + mLl + "&z=" + mZoom; - if (mIsPortal) - url += "&pll=" + mLl; - return url; - } - - private void startIntent(Intent intent) { - // for geo: and Intel Map intents, the user may choose a default application to handle the intent. Since we have - // suitable intent filters declared, it might be that we *are* the default application. In theses cases, a list - // of designated applications is presented to the user - - String packageName = getActivity().getPackageName(); - - PackageManager pm = getActivity().getPackageManager(); - ResolveInfo mInfo = pm.resolveActivity(intent, 0); - - if (mInfo.activityInfo.packageName.equals(packageName)) { - // note: Intent.createChooser would be shorter, but it also includes IITCm. - // Therefore, we'll filter the available activities - String label = null; - if (intent.getAction().equals(Intent.ACTION_SEND)) - label = "Share via"; - else if (intent.getAction().equals(Intent.ACTION_VIEW)) - label = "Open with"; - - List intents = new ArrayList(); - List activities = getActivity().getPackageManager().queryIntentActivities(intent, 0); - - if (!activities.isEmpty()) { - for (ResolveInfo resolveInfo : activities) { - ComponentInfo info; - if (resolveInfo.activityInfo != null) - info = resolveInfo.activityInfo; - else - // Exactly one if these two must be non-null (according to docs) - info = resolveInfo.serviceInfo; - - if (info.packageName.equals(packageName)) // don't show IITCm - continue; - - // setComponent is used to route the intent towards the component - // setPackage is used to prevent Android from showing IITCm - // (without a package set, Android would still show all available components, including IITCm) - intents.add(new Intent(intent) - .setComponent(new ComponentName(info.packageName, info.name)) - .setPackage(info.packageName)); - } - - intent = Intent.createChooser(intents.remove(intents.size() - 1), label); - intent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intents.toArray(new Parcelable[] {})); - } - } - - startActivity(intent); - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - Bundle args = getArguments(); - mTitle = args.getString("title"); - mLl = args.getDouble("lat") + "," + args.getDouble("lng"); - mZoom = args.getInt("zoom"); - - if (mTitle == null) { - mTitle = "Intel Map"; - mIsPortal = false; - } - - mAdapter = new ActionAdapter(getActivity()); - - return new AlertDialog.Builder(getActivity()) - .setTitle(mTitle) - .setAdapter(mAdapter, new OnClickListener()) - .create(); - } -} diff --git a/mobile/src/com/cradle/iitc_mobile/share/IntentFragment.java b/mobile/src/com/cradle/iitc_mobile/share/IntentFragment.java new file mode 100644 index 00000000..d80e7c35 --- /dev/null +++ b/mobile/src/com/cradle/iitc_mobile/share/IntentFragment.java @@ -0,0 +1,55 @@ +package com.cradle.iitc_mobile.share; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AbsListView; +import android.widget.AbsListView.OnScrollListener; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; + +public class IntentFragment extends Fragment implements OnScrollListener, OnItemClickListener { + private Intent mIntent; + private IntentListView mListView; + private int mScrollIndex, mScrollTop; + + public String getTitle() { + return getArguments().getString("title"); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + Bundle args = getArguments(); + + mIntent = args.getParcelable("intent"); + mListView = new IntentListView(getActivity()); + mListView.setIntent(mIntent); + if (mScrollIndex != -1 && mScrollTop != -1) + mListView.setSelectionFromTop(mScrollIndex, mScrollTop); + mListView.setOnScrollListener(this); + mListView.setOnItemClickListener(this); + + return mListView; + } + + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) + { + Intent intent = mListView.getTargetIntent(position); + startActivity(intent); + } + + @Override + public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { + mScrollIndex = mListView.getFirstVisiblePosition(); + View v = mListView.getChildAt(0); + mScrollTop = (v == null) ? 0 : v.getTop(); + } + + @Override + public void onScrollStateChanged(AbsListView view, int scrollState) { + } +} \ No newline at end of file diff --git a/mobile/src/com/cradle/iitc_mobile/share/IntentFragmentAdapter.java b/mobile/src/com/cradle/iitc_mobile/share/IntentFragmentAdapter.java new file mode 100644 index 00000000..1460348c --- /dev/null +++ b/mobile/src/com/cradle/iitc_mobile/share/IntentFragmentAdapter.java @@ -0,0 +1,37 @@ +package com.cradle.iitc_mobile.share; + +import java.util.ArrayList; +import java.util.List; + +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentPagerAdapter; + +public class IntentFragmentAdapter extends FragmentPagerAdapter { + private List mTabs; + + public IntentFragmentAdapter(FragmentManager fm) { + super(fm); + + mTabs = new ArrayList(); + } + + public void add(IntentFragment fragment) { + mTabs.add(fragment); + } + + @Override + public int getCount() { + return mTabs.size(); + } + + @Override + public Fragment getItem(int position) { + return mTabs.get(position); + } + + @Override + public CharSequence getPageTitle(int position) { + return mTabs.get(position).getTitle(); + } +} \ 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 new file mode 100644 index 00000000..4fabe548 --- /dev/null +++ b/mobile/src/com/cradle/iitc_mobile/share/IntentListView.java @@ -0,0 +1,136 @@ +package com.cradle.iitc_mobile.share; + +import java.util.List; + +import com.cradle.iitc_mobile.R; + +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.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.TextView; + +public class IntentListView extends ListView { + private class IntentAdapter extends ArrayAdapter + { + 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); + Drawable icon = info.loadIcon(mPackageManager); + + view.setText(label); + view.setCompoundDrawablePadding((int) getResources().getDimension(R.dimen.icon_margin)); + view.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null); + + return view; + } + } + + private IntentAdapter mAdapter; + private PackageManager mPackageManager; + private Intent mIntent = null; + + 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 void setIntent(Intent intent) + { + mIntent = intent; + + mAdapter.setNotifyOnChange(false); + mAdapter.clear(); + + String packageName = getContext().getPackageName(); + // TODO find default, show on top + // TODO exclude IITCm + + List activities = mPackageManager.queryIntentActivities(intent, 0); + ResolveInfo defaultTarget = mPackageManager.resolveActivity(intent, 0); + + boolean hasCopyIntent = false; + for (ResolveInfo resolveInfo : activities) { // search for "Copy to clipboard" target, provided by Drive + if (resolveInfo.activityInfo.name.equals("com.google.android.apps.docs.app.SendTextToClipboardActivity") + && resolveInfo.activityInfo.packageName.equals("com.google.android.apps.docs")) + hasCopyIntent = true; + } + + for (int i = 0; i < activities.size(); i++) { // use traditional loop since List may change during interation + ResolveInfo info = activities.get(i); + ActivityInfo activity = info.activityInfo; + + // 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())) + { + activities.remove(i); + i--; + continue; + } + } + + // move default Intent to top + if (info.activityInfo.packageName.equals(defaultTarget.activityInfo.packageName) + && info.activityInfo.name.equals(defaultTarget.activityInfo.name)) + { + activities.remove(i); + activities.add(0, info); + } + } + + mAdapter.addAll(activities); + mAdapter.setNotifyOnChange(true); + mAdapter.notifyDataSetChanged(); + } + + public Intent getTargetIntent(int position) { + ActivityInfo activity = mAdapter.getItem(position).activityInfo; + + Intent intent = new Intent(mIntent) + .setComponent(new ComponentName(activity.packageName, activity.name)) + .setPackage(activity.packageName); + + return intent; + } +} diff --git a/mobile/src/com/cradle/iitc_mobile/share/SendToClipboard.java b/mobile/src/com/cradle/iitc_mobile/share/SendToClipboard.java new file mode 100644 index 00000000..142e50ff --- /dev/null +++ b/mobile/src/com/cradle/iitc_mobile/share/SendToClipboard.java @@ -0,0 +1,28 @@ +package com.cradle.iitc_mobile.share; + +import android.app.Activity; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.widget.Toast; + +public class SendToClipboard extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + String text = getIntent().getStringExtra(Intent.EXTRA_TEXT); + + ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); + + ClipData clip = ClipData.newPlainText("Copied Text ", text); + clipboard.setPrimaryClip(clip); + + Toast.makeText(this, "Copied to clipboard…", Toast.LENGTH_SHORT).show(); + + finish(); + setResult(RESULT_OK); + } +} diff --git a/mobile/src/com/cradle/iitc_mobile/share/ShareActivity.java b/mobile/src/com/cradle/iitc_mobile/share/ShareActivity.java new file mode 100644 index 00000000..58ac99f1 --- /dev/null +++ b/mobile/src/com/cradle/iitc_mobile/share/ShareActivity.java @@ -0,0 +1,120 @@ +package com.cradle.iitc_mobile.share; + +import android.app.ActionBar; +import android.app.FragmentTransaction; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; +import android.support.v4.app.NavUtils; +import android.support.v4.view.ViewPager; +import android.view.MenuItem; + +import com.cradle.iitc_mobile.R; + +public class ShareActivity extends FragmentActivity implements ActionBar.TabListener { + private boolean mIsPortal; + private String mLl; + private String mTitle; + private int mZoom; + IntentFragmentAdapter mFragmentAdapter; + ViewPager mViewPager; + + private void addTab(Intent intent, String label) + { + IntentFragment fragment = new IntentFragment(); + Bundle args = new Bundle(); + args.putParcelable("intent", intent); + args.putString("title", label); + fragment.setArguments(args); + mFragmentAdapter.add(fragment); + } + + private String getUrl() { + String url = "http://www.ingress.com/intel?ll=" + mLl + "&z=" + mZoom; + if (mIsPortal) + url += "&pll=" + mLl; + return url; + } + + private void setupIntents() { + 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, getUrl()); + intent.putExtra(Intent.EXTRA_SUBJECT, mTitle); + addTab(intent, "Share"); + + String geoUri = "geo:" + mLl; + intent = new Intent(android.content.Intent.ACTION_VIEW, Uri.parse(geoUri)); + addTab(intent, "Map"); + + intent = new Intent(Intent.ACTION_VIEW, Uri.parse(getUrl())); + addTab(intent, "Browser"); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_share); + + Intent intent = getIntent(); + mTitle = intent.getStringExtra("title"); + mLl = intent.getDoubleExtra("lat", 0) + "," + intent.getDoubleExtra("lng", 0); + mZoom = intent.getIntExtra("zoom", 0); + + if (mTitle == null) { + mTitle = "Intel Map"; + mIsPortal = false; + } + + final ActionBar actionBar = getActionBar(); + actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); + actionBar.setDisplayHomeAsUpEnabled(true); + + mFragmentAdapter = new IntentFragmentAdapter(getSupportFragmentManager()); + setupIntents(); + + mViewPager = (ViewPager) findViewById(R.id.pager); + mViewPager.setAdapter(mFragmentAdapter); + + mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { + @Override + public void onPageSelected(int position) { + actionBar.setSelectedNavigationItem(position); + } + }); + + for (int i = 0; i < mFragmentAdapter.getCount(); i++) { + IntentFragment fragment = (IntentFragment) mFragmentAdapter.getItem(i); + + actionBar.addTab(actionBar + .newTab() + .setText(fragment.getTitle()) + .setTabListener(this)); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + NavUtils.navigateUpFromSameTask(this); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onTabReselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) { + } + + @Override + public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) { + mViewPager.setCurrentItem(tab.getPosition()); + } + + @Override + public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) { + } +}