Java tutorial
/* * Copyright (C) 2012 Simon Robinson * * This file is part of Com-Me. * * Com-Me is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 3 of the * License, or (at your option) any later version. * * Com-Me is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General * Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Com-Me. * If not, see <http://www.gnu.org/licenses/>. */ package ac.robinson.mediaphone.activity; import ac.robinson.mediaphone.BrowserActivity; import ac.robinson.mediaphone.MediaPhone; import ac.robinson.mediaphone.R; import ac.robinson.mediaphone.provider.FrameAdapter; import ac.robinson.mediaphone.provider.MediaPhoneProvider; import ac.robinson.mediaphone.provider.NarrativeAdapter; import ac.robinson.mediaphone.provider.NarrativeItem; import ac.robinson.mediaphone.provider.NarrativesManager; import ac.robinson.mediaphone.view.HorizontalListView; import ac.robinson.mediaphone.view.NarrativeViewHolder; import ac.robinson.mediaphone.view.NarrativesListView; import ac.robinson.util.ImageCacheUtilities; import ac.robinson.util.UIUtilities; import android.app.Activity; import android.app.AlertDialog; import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; import android.content.SharedPreferences; import android.database.Cursor; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.view.Gravity; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.TextView; public class TemplateBrowserActivity extends BrowserActivity { private NarrativesListView mTemplates; private NarrativeAdapter mTemplateAdapter; private final AdapterView.OnItemClickListener mFrameClickListener = new FrameClickListener(); private AdapterView.OnItemLongClickListener mFrameLongClickListener = new FrameLongClickListener(); private boolean mAllowTemplateDeletion = false; private final Handler mScrollHandler = new ScrollHandler(); private int mScrollState = ScrollManager.SCROLL_STATE_IDLE; private boolean mPendingIconsUpdate; private boolean mFingerUp = true; private View mFrameAdapterEmptyView = null; private String mNewNarrativeInternalId = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); UIUtilities.configureActionBar(this, false, true, R.string.template_list_header, 0); setContentView(R.layout.template_browser); initialiseTemplatesView(); UIUtilities.showToast(TemplateBrowserActivity.this, R.string.select_template_hint); } @Override protected void onResume() { super.onResume(); // reload previous scroll position SharedPreferences rotationSettings = getSharedPreferences(MediaPhone.APPLICATION_NAME, Context.MODE_PRIVATE); mTemplates.setSelectionFromTop(rotationSettings.getInt(getString(R.string.key_template_list_top), 0), rotationSettings.getInt(getString(R.string.key_template_list_position), 0)); } @Override protected void onPause() { super.onPause(); mScrollHandler.removeMessages(R.id.msg_update_template_icons); // need to do this manually because the default implementation resets when we change the adapters int listTop = mTemplates.getFirstVisiblePosition(); int listPosition = 0; View firstView = mTemplates.getChildAt(0); if (firstView != null) { listPosition = firstView.getTop(); } updateListPositions(listTop, listPosition); } @Override protected void onDestroy() { if (isFinishing()) { updateListPositions(0, 0); } ImageCacheUtilities.cleanupCache(); super.onDestroy(); } @Override public void onBackPressed() { setResult(Activity.RESULT_OK); finish(); } @Override public boolean onCreateOptionsMenu(Menu menu) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { getMenuInflater().inflate(R.menu.cancel, menu); } return super.onCreateOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: case R.id.menu_cancel: onBackPressed(); return true; default: return super.onOptionsItemSelected(item); } } @Override protected void loadPreferences(SharedPreferences mediaPhoneSettings) { mAllowTemplateDeletion = mediaPhoneSettings.getBoolean(getString(R.string.key_allow_deleting_templates), getResources().getBoolean(R.bool.default_allow_deleting_templates)); } @Override protected void configureInterfacePreferences(SharedPreferences mediaPhoneSettings) { // the soft back button (necessary in some circumstances) int newVisibility = View.VISIBLE; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB || !mediaPhoneSettings.getBoolean(getString(R.string.key_show_back_button), getResources().getBoolean(R.bool.default_show_back_button))) { newVisibility = View.GONE; } findViewById(R.id.button_finished_templates).setVisibility(newVisibility); } private void initialiseTemplatesView() { mTemplates = (NarrativesListView) findViewById(R.id.list_templates); // for API 11 and above, buttons are in the action bar if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { LayoutInflater layoutInflater = getLayoutInflater(); View headerRow = layoutInflater.inflate(R.layout.templates_header, null, false); mTemplates.addHeaderView(headerRow, null, false); // false = not selectable View emptyView = layoutInflater.inflate(R.layout.templates_empty, null, false); ((ViewGroup) mTemplates.getParent()).addView(emptyView); mTemplates.setEmptyView(emptyView); // must add separately as the header isn't shown when empty } else { // initial empty list placeholder - add manually as the < v11 version includes the header row TextView emptyView = new TextView(TemplateBrowserActivity.this); emptyView.setGravity(Gravity.CENTER | Gravity.TOP); emptyView.setPadding(10, getResources().getDimensionPixelSize(R.dimen.template_list_empty_hint_top_padding), 10, 10); // temporary emptyView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); emptyView.setText(getString(R.string.template_list_empty)); ((ViewGroup) mTemplates.getParent()).addView(emptyView); mTemplates.setEmptyView(emptyView); } mTemplateAdapter = new NarrativeAdapter(this, false, true); mTemplates.setAdapter(mTemplateAdapter); getSupportLoaderManager().initLoader(R.id.loader_templates_completed, null, this); mTemplates.setOnScrollListener(new ScrollManager()); mTemplates.setOnTouchListener(new FingerTracker()); mTemplates.setOnItemSelectedListener(new SelectionTracker()); mTemplates.setOnItemClickListener(new NarrativeViewer()); } private void updateListPositions(int listTop, int listPosition) { SharedPreferences rotationSettings = getSharedPreferences(MediaPhone.APPLICATION_NAME, Context.MODE_PRIVATE); SharedPreferences.Editor prefsEditor = rotationSettings.edit(); prefsEditor.putInt(getString(R.string.key_template_list_top), listTop); prefsEditor.putInt(getString(R.string.key_template_list_position), listPosition); prefsEditor.commit(); // apply() is better, but only in SDK >= 9 } @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { return new CursorLoader(TemplateBrowserActivity.this, NarrativeItem.TEMPLATE_CONTENT_URI, NarrativeItem.PROJECTION_ALL, NarrativeItem.SELECTION_NOT_DELETED, null, NarrativeItem.DEFAULT_SORT_ORDER); } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { switch (loader.getId()) { case R.id.loader_templates_completed: mTemplateAdapter.swapCursor(cursor); break; } } @Override public void onLoaderReset(Loader<Cursor> loader) { mTemplateAdapter.swapCursor(null); // data now unavailable for some reason - remove cursor } @Override public int getScrollState() { return mScrollState; // for NarrativeAdapter purposes } @Override public boolean isPendingIconsUpdate() { return mPendingIconsUpdate; // for NarrativeAdapter purposes } @Override public int getFrameAdapterScrollPosition(String narrativeId) { return -1; // for NarrativeAdapter purposes - we're not saving rotation state here TODO: should we? } @Override public View getFrameAdapterEmptyView() { return mFrameAdapterEmptyView; // for FrameAdapter purposes } @Override public void setFrameAdapterEmptyView(View view) { mFrameAdapterEmptyView = view; // for FrameAdapter purposes } @Override public AdapterView.OnItemClickListener getFrameClickListener() { return mFrameClickListener; // for NarrativeAdapter purposes } @Override public AdapterView.OnItemLongClickListener getFrameLongClickListener() { return mFrameLongClickListener; // for NarrativeAdapter purposes } private class ScrollManager implements AbsListView.OnScrollListener { public void onScrollStateChanged(AbsListView view, int scrollState) { if (mScrollState == ScrollManager.SCROLL_STATE_FLING && scrollState != ScrollManager.SCROLL_STATE_FLING) { mPendingIconsUpdate = true; final Handler handler = mScrollHandler; handler.removeMessages(R.id.msg_update_template_icons); final Message message = handler.obtainMessage(R.id.msg_update_template_icons, TemplateBrowserActivity.this); handler.sendMessageDelayed(message, mFingerUp ? 0 : MediaPhone.ANIMATION_ICON_SHOW_DELAY); } else if (scrollState == ScrollManager.SCROLL_STATE_FLING) { mPendingIconsUpdate = false; mScrollHandler.removeMessages(R.id.msg_update_template_icons); } mScrollState = scrollState; } // for showing the overlay with current item information - no need at the moment public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { // TODO show the template ids that are currently visible return; } } private void updateTemplateIcons() { mPendingIconsUpdate = false; for (int i = 0, n = mTemplates.getChildCount(); i < n; i++) { final Object viewTag = mTemplates.getChildAt(i).getTag(); if (viewTag instanceof NarrativeViewHolder) { final NarrativeViewHolder holder = (NarrativeViewHolder) viewTag; if (holder.queryIcons) { mTemplateAdapter.attachAdapter(holder); holder.queryIcons = false; } } } } private static class ScrollHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case R.id.msg_update_template_icons: ((TemplateBrowserActivity) msg.obj).updateTemplateIcons(); break; } } } private void postUpdateTemplateIcons() { mPendingIconsUpdate = true; Handler handler = mScrollHandler; handler.removeMessages(R.id.msg_update_template_icons); Message message = handler.obtainMessage(R.id.msg_update_template_icons, TemplateBrowserActivity.this); handler.sendMessage(message); } private class FingerTracker implements View.OnTouchListener { public boolean onTouch(View view, MotionEvent event) { final int action = event.getAction(); mFingerUp = action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL; if (mFingerUp && mScrollState != ScrollManager.SCROLL_STATE_FLING) { postUpdateTemplateIcons(); } return false; } } private class SelectionTracker implements AdapterView.OnItemSelectedListener { public void onItemSelected(AdapterView<?> adapterView, View view, int position, long id) { if (mScrollState != ScrollManager.SCROLL_STATE_IDLE) { mScrollState = ScrollManager.SCROLL_STATE_IDLE; postUpdateTemplateIcons(); } } public void onNothingSelected(AdapterView<?> adapterView) { } } private class NarrativeViewer implements AdapterView.OnItemClickListener { public void onItemClick(AdapterView<?> parent, View view, int position, long id) { // no need to do anything here - we don't allow clicking entire narratives } } @Override protected void onBackgroundTaskCompleted(int taskId) { if (taskId == R.id.make_load_template_task_complete) { UIUtilities.showToast(TemplateBrowserActivity.this, R.string.template_to_narrative_complete); onBackPressed(); } } private class FrameClickListener implements AdapterView.OnItemClickListener { public void onItemClick(AdapterView<?> parent, View view, int position, long id) { // sometimes we get the event without the view (they released at the last minute?) if (view != null && parent != null) { mNewNarrativeInternalId = MediaPhoneProvider.getNewInternalId(); runQueuedBackgroundTask(getNarrativeTemplateRunnable( ((FrameAdapter) ((HorizontalListView) parent).getAdapter()).getParentFilter(), mNewNarrativeInternalId, false)); } } } private class FrameLongClickListener implements AdapterView.OnItemLongClickListener { @Override // this is a hack to allow long pressing one or two items via the same listener public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long insertNewFrameBefore) { // sometimes we get the event without the view (they released at the last minute?) if (view != null && parent != null) { final CharSequence[] items; if (mAllowTemplateDeletion) { items = new CharSequence[] { getString(R.string.export_template), getString(R.string.delete_template) }; } else { items = new CharSequence[] { getString(R.string.export_template) }; } final String templateId = ((FrameAdapter) ((HorizontalListView) parent).getAdapter()) .getParentFilter(); AlertDialog.Builder builder = new AlertDialog.Builder(TemplateBrowserActivity.this); builder.setTitle(R.string.template_actions); // builder.setMessage(R.string.edit_template_hint); builder.setIcon(android.R.drawable.ic_dialog_info); builder.setNegativeButton(android.R.string.cancel, null); builder.setItems(items, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int item) { switch (item) { case 0: exportContent(templateId, true); break; case 1: AlertDialog.Builder builder = new AlertDialog.Builder(TemplateBrowserActivity.this); builder.setTitle(R.string.delete_template_confirmation); builder.setMessage(R.string.delete_template_hint); builder.setIcon(android.R.drawable.ic_dialog_alert); builder.setNegativeButton(android.R.string.cancel, null); builder.setPositiveButton(R.string.button_delete, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int whichButton) { ContentResolver contentResolver = getContentResolver(); NarrativeItem templateToDelete = NarrativesManager .findTemplateByInternalId(contentResolver, templateId); templateToDelete.setDeleted(true); NarrativesManager.updateTemplate(contentResolver, templateToDelete); UIUtilities.showToast(TemplateBrowserActivity.this, R.string.delete_template_succeeded); } }); AlertDialog alert = builder.create(); alert.show(); break; } dialog.dismiss(); } }); AlertDialog alert = builder.create(); alert.show(); } return true; } } public void handleButtonClicks(View currentButton) { if (!verifyButtonClick(currentButton)) { return; } switch (currentButton.getId()) { case R.id.button_finished_templates: onBackPressed(); break; } } }