Java tutorial
/* * 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; import java.util.BitSet; import java.util.List; import mobisocial.crypto.IBHashedIdentity.Authority; import mobisocial.musubi.App; import mobisocial.musubi.R; import mobisocial.musubi.model.MIdentity; import mobisocial.musubi.model.helpers.IdentitiesManager; import mobisocial.musubi.objects.AppObj; import mobisocial.musubi.provider.MusubiContentProvider; import mobisocial.musubi.provider.MusubiContentProvider.Provided; import mobisocial.musubi.service.WizardStepHandler; import mobisocial.musubi.ui.fragments.FeedListFragment; import mobisocial.musubi.ui.fragments.FeedViewFragment; import mobisocial.musubi.util.LessSpammyContentObserver; import mobisocial.nfc.NdefFactory; import mobisocial.nfc.NdefHandler; import mobisocial.nfc.Nfc; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.database.sqlite.SQLiteOpenHelper; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Align; import android.graphics.Paint.Style; import android.graphics.RectF; import android.graphics.drawable.BitmapDrawable; import android.net.Uri; import android.nfc.FormatException; import android.nfc.NdefMessage; import android.nfc.NdefRecord; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; import android.support.v4.view.Menu; import android.support.v4.view.MenuItem; import android.util.Log; import android.view.MenuInflater; import android.widget.Toast; /** * Displays a list of all user-accessible threads (feeds). */ public class FeedListActivity extends MusubiBaseActivity implements FeedListFragment.OnFeedSelectedListener { public static final boolean DBG = true; public static final String TAG = "DungBeetleActivity"; public static final String SHARE_SCHEME = "db-share-contact"; public static final String GROUP_SESSION_SCHEME = "dungbeetle-group-session"; public static final String GROUP_SCHEME = "dungbeetle-group"; public static final String AUTO_UPDATE_URL_BASE = "http://mobisocial.stanford.edu/files"; public static final String AUTO_UPDATE_METADATA_FILE = "dungbeetle_version.json"; public static final String AUTO_UPDATE_APK_FILE = "dungbeetle-debug.apk"; public static final int DIALOG_PLZ_LINK_ACCCOUNT = 1; private Nfc mNfc; private FeedListFragment mFeedListFragment; private static final String FRAGMENT_FEED_ACTIONS = "feedActions"; private boolean mLoaded = false; private boolean mDualPane; private IdentitiesManager mIdentitiesManager; private LessSpammyContentObserver mObserver; private Menu mMenu; private boolean mSavedInstance; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); SQLiteOpenHelper databaseSource = App.getDatabaseSource(this); mIdentitiesManager = new IdentitiesManager(databaseSource); // TODO: NfcFragment mNfc = new Nfc(this); SharedPreferences p = getSharedPreferences(WizardStepHandler.WIZARD_PREFS_NAME, 0); boolean doRestore = p.getBoolean(WizardStepHandler.DO_RESTORE, WizardStepHandler.DO_RESTORE_DEFAULT); if (doRestore) { WizardStepHandler.restoreStepsFromDatabase(this); } // TODO: Hack. try { if (getIntent().hasExtra(AppObj.EXTRA_APPLICATION_ARGUMENT)) { getIntent().setData(Uri.parse(getIntent().getStringExtra(AppObj.EXTRA_APPLICATION_ARGUMENT))); } } catch (ClassCastException e) { } setContentView(R.layout.activity_feed_list); mFeedListFragment = new FeedListFragment(); mDualPane = (null != findViewById(R.id.feed_view)); if (mDualPane) { Bundle b = new Bundle(); b.putBoolean(FeedListFragment.DUAL_PANE, true); mFeedListFragment.setArguments(b); } if (savedInstanceState == null) { getSupportFragmentManager().beginTransaction().replace(R.id.feed_list, mFeedListFragment).commit(); } // TODO: Combine doHandleInput calls in onNewIntent. doHandleInput(getIntent().getData()); mNfc.addNdefHandler(new NdefHandler() { public int handleNdef(final NdefMessage[] messages) { FeedListActivity.this.runOnUiThread(new Runnable() { public void run() { Log.d(TAG, "Handling ndef uri: " + uriFromNdef(messages)); doHandleInput(uriFromNdef(messages)); } }); return NDEF_CONSUME; } }); mNfc.setOnTagWriteListener(new Nfc.OnTagWriteListener() { public void onTagWrite(final int status) { FeedListActivity.this.runOnUiThread(new Runnable() { public void run() { if (status == WRITE_OK) { Toast.makeText(FeedListActivity.this, "Wrote successfully!", Toast.LENGTH_SHORT).show(); } else if (status == WRITE_ERROR_READ_ONLY) { Toast.makeText(FeedListActivity.this, "Can't write read-only tag!", Toast.LENGTH_SHORT) .show(); } else { Toast.makeText(FeedListActivity.this, "Failed to write!", Toast.LENGTH_SHORT).show(); } pushContactInfoViaNfc(); } }); } }); mObserver = new LessSpammyContentObserver(new Handler(getMainLooper())) { @Override public void lessSpammyOnChange(boolean arg0) { fillInCounter(); } }; //rerun it whenever something about a feed changes, because feed creation/modification implies graylist alteration getContentResolver().registerContentObserver(MusubiContentProvider.uriForDir(Provided.FEEDS), false, mObserver); if (mDualPane && getIntent() != null && getIntent().getData() != null) { Uri data = getIntent().getData(); if (data.toString().startsWith(MusubiContentProvider.uriForDir(Provided.FEEDS_ID).toString())) { onFeedSelected(data); } } } @Override protected Dialog onCreateDialog(int id) { switch (id) { case DIALOG_PLZ_LINK_ACCCOUNT: final Activity activity = FeedListActivity.this; AlertDialog.Builder builder = new AlertDialog.Builder(activity); //name = cursor.getString(cursor.getColumnIndexOrThrow(Contact.NAME)); builder.setMessage( "Before you can send any messages to your friends, you'll need to link to your Google or Facebook account. Would you like to do that now?") .setCancelable(true).setPositiveButton("Link Account", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { Intent intent = new Intent(activity, SettingsActivity.class); intent.putExtra(SettingsActivity.ACTION, SettingsActivity.SettingsAction.ACCOUNT.toString()); startActivity(intent); } }).setNegativeButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dialog.cancel(); } }); return builder.create(); } return null; } /* Creates the menu items */ public boolean onCreateOptionsMenu(android.support.v4.view.Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.feed_list_activity, menu); mMenu = menu; fillInCounter(); return true; } class FillInCounter extends AsyncTask<Void, Void, Void> { int mCount; MenuItem mRelationshipItem; public FillInCounter() { //grab it now to avoid threading woes mRelationshipItem = (MenuItem) mMenu.findItem(R.id.menu_relationships); } @Override protected Void doInBackground(Void... params) { mCount = mIdentitiesManager.getPendingGraylistCount(); return null; } @Override protected void onPostExecute(Void result) { Bitmap icon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_menu_allfriends); icon.setDensity(Bitmap.DENSITY_NONE); Bitmap dn = icon.copy(icon.getConfig(), true); if (mCount > 0) { Canvas c = new Canvas(dn); Paint p = new Paint(); p.setAntiAlias(true); p.setStrokeWidth(1); p.setStyle(Style.FILL_AND_STROKE); p.setColor(Color.rgb(180, 0, 1)); RectF bubble = new RectF(dn.getWidth() / 2, dn.getHeight() / 2, dn.getWidth(), dn.getHeight()); c.drawRoundRect(bubble, 5, 5, p); p.setColor(Color.rgb(255, 255, 255)); p.setTextAlign(Align.CENTER); p.setTextSize(bubble.height() * 2 / 3); c.drawText("" + mCount, bubble.centerX(), bubble.centerY() + bubble.height() * 1 / 4, p); } mRelationshipItem.setIcon(new BitmapDrawable(dn)); } } private void fillInCounter() { if (mMenu == null) return; new FillInCounter().execute(); } /* Handles item selections */ public boolean onOptionsItemSelected(android.support.v4.view.MenuItem item) { switch (item.getItemId()) { case R.id.menu_settings: Intent intent = new Intent(this, SettingsActivity.class); startActivity(intent); return true; case R.id.menu_relationships: List<MIdentity> owned = mIdentitiesManager.getOwnedIdentities(); for (MIdentity ident : owned) { if (ident.type_ != Authority.Local) { intent = new Intent(this, RelationshipsActivity.class); if (mIdentitiesManager.getPendingGraylistCount() > 0) { intent.putExtra(RelationshipsActivity.EXTRA_SHOW_REQUESTS, true); } startActivity(intent); return true; } } showDialog(DIALOG_PLZ_LINK_ACCCOUNT); return true; } return false; } public Uri uriFromNdef(NdefMessage... messages) { if (messages.length == 0 || messages[0].getRecords().length == 0) { return null; } try { return NdefFactory.parseUri(messages[0].getRecords()[0]); } catch (FormatException e) { return null; } } protected void doHandleInput(Uri uri) { if (DBG) Log.d(TAG, "Handling input uri " + uri); if (uri == null) { return; } if (uri.getScheme() == null) { Log.w(TAG, "Null uri scheme for " + uri); return; } //TODO: if there are URI's that are opened with the FeedListActivity that we want to //handle, e.g. an NFC url, then we put that code here. We used to have friend invites //but now we don't have to worry about that. //If we want to share a URL that opens a specific Musubi feed, then we need to create a //URL that links to it without giving away the secret feed capability. if (uri.getScheme().equals("content")) { if (uri.getAuthority().equals("vnd.mobisocial.db")) { if (uri.getPath().startsWith("/feed")) { // Intent view = new Intent(Intent.ACTION_VIEW, uri); // view.addCategory(Intent.CATEGORY_DEFAULT); // // TODO: fix in AndroidManifest. // //view.setClass(this, FeedActivity.class); // view.setClass(this, FeedHomeActivity.class); // startActivity(view); // finish(); // return; } } } // Re-push the contact info ndef pushContactInfoViaNfc(); } public void writeGroupToTag(Uri uri) { NdefRecord urlRecord = new NdefRecord(NdefRecord.TNF_ABSOLUTE_URI, NdefRecord.RTD_URI, new byte[] {}, uri.toString().getBytes()); NdefMessage ndef = new NdefMessage(new NdefRecord[] { urlRecord }); mNfc.enableTagWriteMode(ndef); Toast.makeText(this, "Touch a tag to write the group...", Toast.LENGTH_SHORT).show(); } public static byte[] toByteArray(BitSet bits) { byte[] bytes = new byte[bits.length() / 8 + 1]; for (int i = 0; i < bits.length(); i++) { if (bits.get(i)) { bytes[bytes.length - i / 8 - 1] |= 1 << (i % 8); } } return bytes; } public void pushGroupInfoViaNfc(Uri uri) { mNfc.share(NdefFactory.fromUri(uri)); } public void pushContactInfoViaNfc() { //for now just share musubi over NFC mNfc.share(NdefFactory.fromUri("https://market.android.com/details?id=mobisocial.musubi")); } @Override public void onPause() { super.onPause(); mNfc.onPause(this); getContentResolver().unregisterContentObserver(mObserver); } @Override public void onResume() { super.onResume(); mSavedInstance = false; mNfc.onResume(this); pushContactInfoViaNfc(); fillInCounter(); getContentResolver().registerContentObserver(MusubiContentProvider.uriForDir(Provided.FEEDS), false, mObserver); mObserver.resetTimeout(); mObserver.dispatchChange(false); } @Override public void onNewIntent(Intent intent) { if (mNfc.onNewIntent(this, intent)) { return; } setIntent(intent); getContentResolver().registerContentObserver(MusubiContentProvider.uriForDir(Provided.FEEDS), false, mObserver); if (mDualPane && getIntent() != null && getIntent().getData() != null) { Uri data = getIntent().getData(); if (data.toString().startsWith(MusubiContentProvider.uriForDir(Provided.FEEDS_ID).toString())) { onFeedSelected(data); } } } @Override public void onDestroy() { getContentResolver().unregisterContentObserver(mObserver); super.onDestroy(); } @Override public void onBackPressed() { super.onBackPressed(); finish(); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); mSavedInstance = true; } @Override public void onFeedSelected(Uri feedUri) { if (mDualPane) { if (mSavedInstance) { return; } Bundle args = new Bundle(); args.putParcelable(FeedViewFragment.ARG_FEED_URI, feedUri); args.putBoolean(FeedViewFragment.ARG_DUAL_PANE, mDualPane); Fragment feedView = new FeedViewFragment(); feedView.setArguments(args); Fragment oldSelector = getSupportFragmentManager().findFragmentByTag(FRAGMENT_FEED_ACTIONS); FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); if (oldSelector != null) { ft.remove(oldSelector); } ft.replace(R.id.feed_view, feedView); ft.commit(); } else { Intent panner = new Intent(this, FeedPannerActivity.class); panner.setData(feedUri); startActivity(panner); } } }