/* * Copyright 2012 The Stanford MobiSocial Laboratory * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package mobisocial.musubi.ui.fragments; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import mobisocial.musubi.App; import mobisocial.musubi.R; import mobisocial.musubi.feed.iface.FeedAction; import mobisocial.musubi.model.MApp; import mobisocial.musubi.model.helpers.AppManager; import mobisocial.musubi.model.helpers.FeedManager; import mobisocial.musubi.objects.WebAppObj; import mobisocial.musubi.provider.MusubiContentProvider; import mobisocial.musubi.provider.MusubiContentProvider.Provided; import mobisocial.musubi.ui.MusubiBaseActivity; import mobisocial.musubi.util.InstrumentedActivity; import mobisocial.musubi.webapp.AppCorralActivity; import mobisocial.musubi.webapp.WebAppActivity; import mobisocial.socialkit.Obj; import mobisocial.socialkit.musubi.Musubi; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.ContentUris; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.LabeledIntent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.app.SupportActivity; import android.support.v4.content.AsyncTaskLoader; import android.support.v4.content.Loader; import android.text.TextUtils.TruncateAt; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemLongClickListener; import android.widget.ArrayAdapter; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; public class AppSelectDialog extends DialogFragment implements LoaderCallbacks<List<AppSelectDialog.IMusubiApp>> { private static final String TAG = "LaunchAppDialog"; private static final boolean DBG = MusubiBaseActivity.DBG; private Uri mFeedUri; private MusubiAppAdapter mAppAdapter; private boolean mHomeScreen; private IMusubiApp mSelectedApp; private IMusubiApp mMusubiFeedApp; private Activity mActivity; private static final String ARG_FEED_URI = "feedUri"; private static final String ARG_HOME_SCREEN = "homeScreen"; static final String CATEGORY_MUSUBI_MENU = "musubi.intent.category.MENU"; public static final String SKETCH_APP_ID = "musubi.sketch"; public static final String SHOUT_APP_ID = "musubi.shout"; public static AppSelectDialog newInstance(boolean forHomeScreen, Uri feedUri) { if (DBG) Log.d(TAG, "New AppSelectDialog."); AppSelectDialog frag = new AppSelectDialog(); Bundle args = new Bundle(); args.putParcelable(ARG_FEED_URI, feedUri); args.putBoolean(ARG_HOME_SCREEN, forHomeScreen); frag.setArguments(args); return frag; } @Override public void onAttach(SupportActivity activity) { super.onAttach(activity); mActivity = activity.asActivity(); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setStyle(STYLE_NO_TITLE, R.style.Theme_D1dialog); mFeedUri = getArguments().getParcelable(ARG_FEED_URI); mHomeScreen = getArguments().getBoolean(ARG_HOME_SCREEN); if (getTargetFragment() != null) { setRetainInstance(true); } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.applaunch_dialog, container, false); final Activity context = mActivity; if (mHomeScreen) { view.findViewById(R.id.appbar).setVisibility(View.GONE); mMusubiFeedApp = new MusubiFeed(context); mSelectedApp = mMusubiFeedApp; } else { setActionItems((ViewGroup)view.findViewById(R.id.appbar)); } ListView listView = (ListView)view.findViewById(android.R.id.list); mAppAdapter = new MusubiAppAdapter(context); listView.setAdapter(mAppAdapter); listView.setOnItemClickListener(mAppAdapter); listView.setOnItemLongClickListener(mAppAdapter); view.findViewById(R.id.content).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent webStore = new Intent(context, AppCorralActivity.class); webStore.putExtra(Musubi.EXTRA_FEED_URI, mFeedUri); startActivity(webStore); getDialog().dismiss(); } }); getLoaderManager().initLoader(0, null, this); return view; } private void setActionItems(ViewGroup view) { final Context context = mActivity; FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); List<FeedAction> actions = FeedAction.getFeedActions(); for (final FeedAction a : actions) { if (a.isActive(context)) { ft.add(a, a.getName()); ImageButton b = new ImageButton(context); b.setLayoutParams(new LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); b.setImageDrawable(a.getIcon(mActivity)); b.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { a.onClick(context, mFeedUri); Dialog d = getDialog(); if (d != null) d.dismiss(); } }); view.addView(b); } } ft.commit(); } private class MusubiAppAdapter extends ArrayAdapter<IMusubiApp> implements OnItemClickListener, OnItemLongClickListener { private final Context mContext; public MusubiAppAdapter(Context context) { super(context, 0, 0); mContext = context; } public MusubiAppAdapter(Context context, List<IMusubiApp> objects) { super(context, 0, 0, objects); mContext = context; } @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = newView(parent); } IMusubiApp app = getItem(position); ((TextView)convertView.findViewById(R.id.text)).setText(app.getName()); ((ImageView)convertView.findViewById(R.id.icon)).setImageDrawable(app.getIcon()); return convertView; } private View newView(ViewGroup parent) { LinearLayout frame = new LinearLayout(mContext); AbsListView.LayoutParams lp = new AbsListView.LayoutParams( AbsListView.LayoutParams.MATCH_PARENT, AbsListView.LayoutParams.MATCH_PARENT); frame.setLayoutParams(lp); frame.setOrientation(LinearLayout.HORIZONTAL); frame.setGravity(Gravity.CENTER_HORIZONTAL); ImageView icon = new ImageView(mContext); icon.setId(R.id.icon); icon.setLayoutParams(new LayoutParams(80, 80)); icon.setPadding(6, 6, 6, 6); frame.addView(icon); TextView label = new TextView(mContext); label.setId(R.id.text); label.setLayoutParams(new LayoutParams( LayoutParams.FILL_PARENT, LayoutParams.MATCH_PARENT)); label.setTextSize(24); label.setEllipsize(TruncateAt.END); label.setGravity(Gravity.CENTER_VERTICAL); frame.addView(label); return frame; } @Override public void onItemClick(AdapterView<?> adapterView, View view, int item, long itemId) { mSelectedApp = getItem(item); Fragment target = getTargetFragment(); if (target != null) { int requestCode = getTargetRequestCode(); getTargetFragment().onActivityResult(requestCode, Activity.RESULT_OK, null); } else { Intent launch = new Intent(mSelectedApp.getLaunchIntent(mActivity, mFeedUri)); String appIdStr = mSelectedApp.getAppId(); long feedId = ContentUris.parseId(mFeedUri); SQLiteOpenHelper db = App.getDatabaseSource(getActivity()); MApp app = new AppManager(db).ensureApp(appIdStr); new FeedManager(db).ensureFeedApp(feedId, app.id_); mActivity.startActivity(launch); } dismiss(); } @Override public boolean onItemLongClick(AdapterView<?> adapterView, View view, int item, long itemId) { IMusubiApp app = getItem(item); if (!(app instanceof MusubiWebApp)) { return false; } MusubiWebApp web = (MusubiWebApp) app; String appId = web.getAppId(); String name = web.getName(); if (SKETCH_APP_ID.equals(appId) || SHOUT_APP_ID.equals(appId)) { return false; } //TODO: add an internal flag or something that we can consult when deciding if the //delete option should be available ((InstrumentedActivity)getActivity()).showDialog( WebAppContextMenu.newInstance(appId, name)); return true; } } /** * Gets the app the user currently has selected. */ public IMusubiApp getSelectedApp() { return mSelectedApp; } public interface IMusubiApp { public String getAppId(); public String getName(); public Drawable getIcon(); public LabeledIntent getLaunchIntent(Context context, Uri feedUri); } /** * An "app" that is just a Musubi feed. */ static class MusubiFeed implements IMusubiApp { final Context mContext; public MusubiFeed(Context c) { mContext = c; } @Override public String getName() { return "New Conversation..."; } @Override public Drawable getIcon() { return mContext.getResources().getDrawable(R.drawable.ic_text_holo_light); } @Override public LabeledIntent getLaunchIntent(Context context, Uri feedUri) { Intent i = new Intent(Intent.ACTION_VIEW); i.setDataAndType(feedUri, MusubiContentProvider.getType(Provided.FEEDS_ID)); i.setPackage(context.getPackageName()); LabeledIntent l = new LabeledIntent(i, context.getPackageName(), getName(), R.drawable.ic_text_holo_light); return l; } @Override public String getAppId() { return MusubiContentProvider.SUPER_APP_ID; } } public static class MusubiWebApp implements IMusubiApp { final String mName; final String mWebAppUrl; final String mAppId; final Context mContext; final int mIconResource; public MusubiWebApp(Context context, String name, String appId, String webAppUrl, int iconResource) { mContext = context; mName = name; mAppId = appId; mWebAppUrl = webAppUrl; mIconResource = iconResource; } @Override public String getName() { return mName; } public Drawable getIcon() { return mContext.getResources().getDrawable(mIconResource); } public Uri getUri() { return Uri.parse(mWebAppUrl); } @Override public LabeledIntent getLaunchIntent(Context context, Uri feedUri) { Intent appIntent = new Intent(Intent.ACTION_VIEW); appIntent.setClass(context, WebAppActivity.class); appIntent.putExtra(Musubi.EXTRA_FEED_URI, feedUri); appIntent.putExtra(WebAppActivity.EXTRA_APP_NAME, mName); appIntent.putExtra(WebAppActivity.EXTRA_APP_URI, getUri()); appIntent.putExtra(WebAppActivity.EXTRA_APP_ID, mAppId); LabeledIntent labeled = new LabeledIntent(appIntent, context.getPackageName(), mName, mIconResource); return labeled; } @Override public String getAppId() { return mAppId; } } static class MusubiInstalledApp implements IMusubiApp { private final PackageManager mPackageManager; private final ResolveInfo mInfo; private final String mLabel; public MusubiInstalledApp(PackageManager pm, ResolveInfo info) { mPackageManager = pm; mInfo = info; mLabel = info.loadLabel(pm).toString(); } public ResolveInfo getAppInfo() { return mInfo; } @Override public String getName() { return mLabel; } @Override public Drawable getIcon() { return getAppInfo().loadIcon(mPackageManager); } @Override public LabeledIntent getLaunchIntent(Context context, Uri feedUri) { String sourcePkg = getAppInfo().activityInfo.packageName; int labelRes = getAppInfo().activityInfo.labelRes; int iconRes = getAppInfo().activityInfo.icon; Intent intent = new Intent(Intent.ACTION_MAIN); intent.addCategory(CATEGORY_MUSUBI_MENU); intent.setClassName(sourcePkg, getAppInfo().activityInfo.name); intent.putExtra(Musubi.EXTRA_FEED_URI, feedUri); LabeledIntent labeled = new LabeledIntent(intent, sourcePkg, labelRes, iconRes); return labeled; } @Override public String getAppId() { return getAppInfo().activityInfo.packageName; } } public interface OnAppSelectedListener { public void onAppSelected(AppSelectDialog dialog, IMusubiApp app); } static class WebAppContextMenu extends DialogFragment { static final String APP_ID = "app_id"; static final String NAME = "name"; public static WebAppContextMenu newInstance(String appId, String name) { WebAppContextMenu d = new WebAppContextMenu(); Bundle b = new Bundle(); b.putString(APP_ID, appId); b.putString(NAME, name); d.setArguments(b); return d; } public WebAppContextMenu() { } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { final String name = getArguments().getString(NAME); final String appId = getArguments().getString(APP_ID); return new AlertDialog.Builder(getActivity()) .setTitle(name) .setItems(new String[] { "Remove", "Copy to clipboard" }, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (which == 0) { AppManager am = new AppManager(App.getDatabaseSource(getActivity())); am.deleteAppWithId(appId); getActivity().getContentResolver().notifyChange( MusubiContentProvider.uriForDir(Provided.OBJECTS), null); Toast.makeText(getActivity(), "Removed app " + name + ".", Toast.LENGTH_SHORT).show(); } else if (which == 1) { AppManager am = new AppManager(App.getDatabaseSource(getActivity())); MApp app = am.lookupAppByAppId(appId); if (app == null) { Toast.makeText(getActivity(), "Error getting app.", Toast.LENGTH_SHORT).show(); return; } if (app.webAppUrl_ == null) { Toast.makeText(getActivity(), "Not a copyable app.", Toast.LENGTH_SHORT).show(); return; } Obj obj = WebAppObj.forUri(Uri.parse(app.webAppUrl_)); new ClipboardKeeper(getActivity()).store(obj); Toast.makeText(getActivity(), "Put content on your clipboard. Use the pin menu to paste it.", Toast.LENGTH_SHORT).show(); } } }) .create(); } } @Override public Loader<List<IMusubiApp>> onCreateLoader(int id, Bundle args) { return new MusubiAppLoader(getActivity()); } @Override public void onLoadFinished(Loader<List<IMusubiApp>> loader, List<IMusubiApp> data) { mAppAdapter.clear(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { mAppAdapter.addAll(data); } else { for (IMusubiApp app : data) { mAppAdapter.add(app); } } } @Override public void onLoaderReset(Loader<List<IMusubiApp>> loader) { } static class MusubiAppLoader extends AsyncTaskLoader<List<IMusubiApp>> { private List<IMusubiApp> mData; public MusubiAppLoader(Context context) { super(context); } @Override protected void onStartLoading() { if (mData != null) { deliverResult(mData); } else { forceLoad(); } } @Override public List<IMusubiApp> loadInBackground() { mData = getAvailableApps(getContext()); return mData; } private List<IMusubiApp> getAvailableApps(Context context) { final PackageManager mgr = context.getPackageManager(); final List<IMusubiApp> apps = new LinkedList<IMusubiApp>(); // Web Apps apps.addAll(getWebApps(context)); // User installed apps Intent i = new Intent(); List<ResolveInfo> infos; i.setAction(Intent.ACTION_MAIN); i.addCategory(CATEGORY_MUSUBI_MENU); infos = mgr.queryIntentActivities(i, 0); if (DBG) Log.d(TAG, "Queried " + infos.size() + " feed apps."); for (int j = 0; j < infos.size(); j++) { apps.add(new MusubiInstalledApp(mgr, infos.get(j))); } return apps; } List<MusubiWebApp> getWebApps(Context context) { SQLiteDatabase db = App.getDatabaseSource(context).getReadableDatabase(); String table = MApp.TABLE; String[] columns = new String[] { MApp.COL_ID, MApp.COL_APP_ID, MApp.COL_NAME, MApp.COL_WEB_APP_URL }; String selection = MApp.COL_WEB_APP_URL + " is not null AND " + MApp.COL_DELETED + "=0"; String[] selectionArgs = null; String groupBy = null, having = null, orderBy = null; Cursor c = db.query(table, columns, selection, selectionArgs, groupBy, having, orderBy); List<MusubiWebApp> apps = new ArrayList<MusubiWebApp>(); try { while (c.moveToNext()) { String appId = c.getString(1); String name = c.getString(2); String webUrl = c.getString(3); int iconResource; if (SKETCH_APP_ID.equals(appId)) { iconResource = R.drawable.sketch; } else if (SHOUT_APP_ID.equals(appId)) { iconResource = R.drawable.shout; } else { iconResource = R.drawable.ic_menu_globe; } apps.add(new MusubiWebApp(context, name, appId, webUrl, iconResource)); } return apps; } finally { c.close(); } } } }