Java tutorial
/* * Copyright 2014 Bevbot LLC <info@bevbot.com> * * This file is part of the Kegtab package from the Kegbot project. For * more information on Kegtab or Kegbot, see <http://kegbot.org/>. * * Kegtab is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free * Software Foundation, version 2. * * Kegtab 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 General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with Kegtab. If not, see <http://www.gnu.org/licenses/>. */ package org.kegbot.app; import android.app.ActionBar; import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; import android.app.Fragment; import android.app.FragmentManager; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.support.v13.app.FragmentStatePagerAdapter; import android.support.v4.view.ViewPager; import android.text.Editable; import android.text.TextWatcher; import android.util.Log; import android.util.Pair; import android.view.View; import android.view.View.OnClickListener; import android.view.WindowManager; import android.widget.Button; import android.widget.EditText; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.ViewFlipper; import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.squareup.otto.Subscribe; import org.kegbot.app.camera.CameraFragment; import org.kegbot.app.config.AppConfiguration; import org.kegbot.app.event.FlowUpdateEvent; import org.kegbot.app.event.PictureDiscardedEvent; import org.kegbot.app.event.PictureTakenEvent; import org.kegbot.app.util.ImageDownloader; import org.kegbot.app.util.Units; import org.kegbot.app.util.Utils; import org.kegbot.app.view.BadgeView; import org.kegbot.backend.Backend; import org.kegbot.backend.LocalBackend; import org.kegbot.core.AuthenticationManager; import org.kegbot.core.Flow; import org.kegbot.core.FlowManager; import org.kegbot.core.KegbotCore; import org.kegbot.proto.Models; import org.kegbot.proto.Models.KegTap; import org.kegbot.proto.Models.User; import org.kegbot.proto.Models.ThermoSensor; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; import javax.annotation.concurrent.GuardedBy; import butterknife.ButterKnife; /** * Activity shown while a pour is in progress. * * @author mike wakerly (opensource@hoho.com) */ public class PourInProgressActivity extends CoreActivity { private static final String TAG = PourInProgressActivity.class.getSimpleName(); private static final boolean DEBUG = false; /** Delay after a flow has ended, during which a dialog is show. */ private static final long FLOW_FINISH_DELAY_MILLIS = TimeUnit.SECONDS.toMillis(1); /** Minimum idle seconds before a flow is considered idle for display purposes. */ private static final long IDLE_SCROLL_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(3); /** Request code. */ private static final int REQUEST_AUTH_DRINKER = 100; private static final String EXTRA_FLOW_ID = "flow_id"; private KegbotCore mCore; private FlowManager mFlowManager; private AppConfiguration mConfig; private ImageDownloader mImageDownloader; private CameraFragment mCameraFragment; private boolean mShowCamera; private final Handler mHandler = new Handler(Looper.getMainLooper()); private PouringTapAdapter mPouringTapAdapter; private ViewFlipper mControlsFlipper; private Button mClaimPourButton; private TextView mDrinkerName; private ImageView mDrinkerImage; private TextView mShoutText; private Button mDoneButton; private ViewPager mTapPager; private Button mAddButton; private Button mSubtractButton; //for backgrounds private ImageView mImageView0; private LinearLayout mImageView1; private final DialogFragment mIdleDialogFragment = new DialogFragment() { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { AlertDialog.Builder builder = new AlertDialog.Builder(PourInProgressActivity.this); builder.setMessage("Hey, are you still pouring?").setCancelable(false) .setPositiveButton("Continue Pouring", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { for (final Flow flow : mFlowManager.getAllActiveFlows()) { flow.pokeActivity(); } dialog.cancel(); } }).setNegativeButton("Done Pouring", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { for (final Flow flow : mFlowManager.getAllActiveFlows()) { mFlowManager.endFlow(flow); } cancelIdleWarning(); } }); return builder.create(); } }; private boolean mIdleDialogShowing = false; private final Object mTapsLock = new Object(); @GuardedBy("mTapsLock") private final List<KegTap> mTapList = Lists.newArrayList(); private Set<Flow> mActiveFlows = Sets.newLinkedHashSet(); @Subscribe public void onPictureTakenEvent(PictureTakenEvent event) { final String filename = event.getFilename(); Log.d(TAG, "Got photo: " + filename); for (final Flow flow : mFlowManager.getAllActiveFlows()) { Log.d(TAG, " - attached to flow: " + flow); flow.setImage(filename); } } @Subscribe public void onPictureDiscardedEvent(PictureDiscardedEvent event) { final String filename = event.getFilename(); Log.d(TAG, "Discarded photo: " + filename); for (final Flow flow : mFlowManager.getAllActiveFlows()) { if (filename.equals(flow.getImagePath())) { flow.removeImage(); } } } @Subscribe public void onFlowUpdateEvent(FlowUpdateEvent event) { refreshFlows(); } private final Runnable REFRESH_FLOWS_RUNNABLE = new Runnable() { @Override public void run() { refreshFlows(); mHandler.postDelayed(this, 1000); } }; private final ViewPager.OnPageChangeListener mPageChangeListener = new ViewPager.OnPageChangeListener() { @Override public void onPageSelected(int position) { updateControlsForFlow(getCurrentlyFocusedFlow()); } @Override public void onPageScrolled(int arg0, float arg1, int arg2) { } @Override public void onPageScrollStateChanged(int arg0) { } }; public class PouringTapAdapter extends FragmentStatePagerAdapter { public PouringTapAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int position) { final KegTap tap; synchronized (mTapsLock) { tap = mTapList.get(position); } final PourStatusFragment frag = PourStatusFragment.forTap(tap); final Flow flow = mFlowManager.getFlowForTap(tap); if (flow != null) { frag.updateWithFlow(flow); } return frag; } @Override public int getItemPosition(Object object) { int index; synchronized (mTapsLock) { index = mTapList.indexOf(object); } if (index >= 0) { return index; } return POSITION_NONE; } @Override public int getCount() { synchronized (mTapsLock) { return mTapList.size(); } } } public static class PourFinishProgressDialog extends DialogFragment { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { ProgressDialog dialog = new ProgressDialog(getActivity()); dialog.setIndeterminate(true); dialog.setCancelable(false); dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); dialog.setMessage("Please wait.."); dialog.setTitle("Saving drink"); return dialog; } } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d(TAG, "onCreate()"); mCore = KegbotCore.getInstance(this); mFlowManager = mCore.getFlowManager(); mConfig = mCore.getConfiguration(); mImageDownloader = mCore.getImageDownloader(); overridePendingTransition(R.anim.image_fade_in, R.anim.image_fade_in); final ActionBar actionBar = getActionBar(); // if (actionBar != null) { // actionBar.hide(); // } if (mConfig.getShowActionBar()) { actionBar.show(); } else { actionBar.hide(); } setContentView(R.layout.pour_in_progress_activity); mTapPager = (ViewPager) findViewById(R.id.tapPager); mPouringTapAdapter = new PouringTapAdapter(getFragmentManager()); mTapPager.setAdapter(mPouringTapAdapter); mTapPager.setOnPageChangeListener(mPageChangeListener); mControlsFlipper = (ViewFlipper) findViewById(R.id.pour_controls_flipper); mClaimPourButton = (Button) findViewById(R.id.claimPourButton); mDrinkerName = (TextView) findViewById(R.id.pourDrinkerName); mDoneButton = (Button) findViewById(R.id.pourEndButton); mDrinkerImage = (ImageView) findViewById(R.id.pourDrinkerImage); mShoutText = (TextView) findViewById(R.id.shoutText); mAddButton = (Button) findViewById(R.id.pourAddButton); mSubtractButton = (Button) findViewById(R.id.pourSubtractButton); //for backgrounds mImageView0 = (ImageView) findViewById(R.id.imageView0); mImageView1 = (LinearLayout) findViewById(R.id.imageView1); mClaimPourButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { final Flow flow = getCurrentlyFocusedFlow(); if (flow == null || flow.isAuthenticated() || flow.isFinished()) { return; } Log.d(TAG, "Attempting to claim flow id=" + flow.getFlowId()); final Intent intent = KegtabCommon.getAuthDrinkerActivityIntent(PourInProgressActivity.this); intent.putExtra(EXTRA_FLOW_ID, flow.getFlowId()); startActivityForResult(intent, REQUEST_AUTH_DRINKER); overridePendingTransition(R.anim.image_fade_in, R.anim.image_fade_in); } }); mDoneButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { final FlowManager flowManager = mCore.getFlowManager(); final Flow flow = getCurrentlyFocusedFlow(); if (flow == null) { return; } Log.d(TAG, "Done button pressed, ending flow " + flow.getFlowId()); overridePendingTransition(R.anim.image_fade_in, R.anim.image_fade_in); flowManager.endFlow(flow); // If we're finishing a non-dormant flow, and other dormant flows // exist, assume those were started optimistically and finish them // now. if (flow.getVolumeMl() > 0) { final AppConfiguration config = mCore.getConfiguration(); final long minVolume = config.getMinimumVolumeMl(); for (final Flow suspectFlow : flowManager.getAllActiveFlows()) { if (suspectFlow.getVolumeMl() < minVolume) { Log.d(TAG, "Also ending dormant flow: " + suspectFlow.getFlowId()); flowManager.endFlow(suspectFlow); } } } } }); mAddButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { final FlowManager flowManager = mCore.getFlowManager(); final Flow flow = getCurrentlyFocusedFlow(); if (flow == null) { return; } flow.addTicks(135); refreshFlows(); } }); mSubtractButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { final FlowManager flowManager = mCore.getFlowManager(); final Flow flow = getCurrentlyFocusedFlow(); if (flow == null) { return; } flow.addTicks(-135); refreshFlows(); } }); mShoutText = (EditText) findViewById(R.id.shoutText); mShoutText.addTextChangedListener(new TextWatcher() { @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void afterTextChanged(Editable s) { final Flow flow = getCurrentlyFocusedFlow(); if (flow == null) { Log.w(TAG, "Flow went away, dropping shout."); return; } flow.setShout(s.toString()); flow.pokeActivity(); } }); mShowCamera = true; mCameraFragment = (CameraFragment) getFragmentManager().findFragmentById(R.id.camera); mAddButton.setVisibility(View.GONE); mSubtractButton.setVisibility(View.GONE); if (!mConfig.getUseCamera() || !mConfig.getTakePhotosDuringPour()) { mShowCamera = false; getFragmentManager().beginTransaction().hide(mCameraFragment).commit(); mAddButton.setVisibility(View.VISIBLE); mSubtractButton.setVisibility(View.VISIBLE); } refreshFlows(); } /** Returns the currently-focused {@link Flow}, or {@code null}. */ private Flow getCurrentlyFocusedFlow() { synchronized (mTapsLock) { final KegTap tap = getCurrentlyFocusedTap(); if (tap == null) { Log.w(TAG, "No current tap, ughuh?!"); return null; } return mFlowManager.getFlowForTap(tap); } } /** Returns the currently-focused {@link KegTap}, or {@code null}. */ private KegTap getCurrentlyFocusedTap() { synchronized (mTapsLock) { final int currentIndex = mTapPager.getCurrentItem(); if (currentIndex >= mTapList.size()) { return null; } return mTapList.get(currentIndex); } } private void updateControlsForFlow(Flow flow) { if (flow == null) { // Tap is inactive. mControlsFlipper.setDisplayedChild(0); return; } mControlsFlipper.setDisplayedChild(1); if (!mConfig.useAccounts()) { mClaimPourButton.setVisibility(View.GONE); mShoutText.setVisibility(View.GONE); mDrinkerName.setVisibility(View.GONE); mDrinkerImage.setVisibility(View.GONE); } else { boolean imageWasReplaced = false; mClaimPourButton.setEnabled(true); if (flow.isAnonymous()) { mClaimPourButton.setVisibility(View.VISIBLE); mDrinkerName.setVisibility(View.GONE); } else { final String username = flow.getUsername(); mClaimPourButton.setVisibility(View.GONE); mDrinkerName.setVisibility(View.VISIBLE); mDrinkerName.setText("Pouring: " + username); final AuthenticationManager authManager = mCore.getAuthenticationManager(); final User user = authManager.getUserDetail(username); if (user != null && user.hasImage()) { // NOTE(mikey): Use the full-sized image rather than the thumbnail; // in many cases the former will already be in the cache from // DrinkerSelectActivity. final String thumbnailUrl = user.getImage().getThumbnailUrl(); if (!Strings.isNullOrEmpty(thumbnailUrl)) { mImageDownloader.download(thumbnailUrl, mDrinkerImage); imageWasReplaced = true; } } else { Log.d(TAG, "No user info."); } } if (!imageWasReplaced) { mDrinkerImage.setImageBitmap(null); Utils.setBackground(mDrinkerImage, getResources().getDrawable(R.drawable.unknown_drinker)); } } //dummy tap status { final TextView title = (TextView) findViewById(R.id.tapTitle); final TextView subtitle = (TextView) findViewById(R.id.tapSubtitle); KegTap tap = getCurrentlyFocusedTap(); final String tapName = tap.getName(); if (!Strings.isNullOrEmpty(tapName)) { subtitle.setText(tapName); } if (!tap.hasCurrentKeg()) { return; } final Models.Keg keg = tap.getCurrentKeg(); title.setText(keg.getBeverage().getName()); // Badge 1: Pints Poured final BadgeView badge1 = (BadgeView) findViewById(R.id.tapStatsBadge1); double mlPoured = keg.getServedVolumeMl(); Pair<String, String> qtyPoured = Units.localize(mCore.getConfiguration(), mlPoured); badge1.setBadgeValue(qtyPoured.first); badge1.setBadgeCaption("Total " + Units.capitalizeUnits(qtyPoured.second) + " Poured"); // Badge 2: Pints Remain final BadgeView badge2 = (BadgeView) findViewById(R.id.tapStatsBadge2); Pair<String, String> qtyRemain = Units.localize(mCore.getConfiguration(), keg.getRemainingVolumeMl()); badge2.setBadgeValue(qtyRemain.first); badge2.setBadgeCaption(Units.capitalizeUnits(qtyRemain.second) + " Left"); // Badge 3: Temperature final BadgeView badge3 = (BadgeView) findViewById(R.id.tapStatsBadge3); double lastTemperature = Double.NaN; if (mConfig.isLocalBackend()) { final LocalBackend backend = (LocalBackend) mCore.getBackend(); lastTemperature = backend.mTemperature; badge3.setVisibility(View.VISIBLE); } else if (tap.hasLastTemperature()) { lastTemperature = tap.getLastTemperature().getTemperatureC(); badge3.setVisibility(View.VISIBLE); } else { badge3.setVisibility(View.GONE); } if (!Double.isNaN(lastTemperature)) { String units = "C"; if (!mCore.getConfiguration().getTemperaturesCelsius()) { lastTemperature = Units.temperatureCToF(lastTemperature); units = "F"; } final String tempValue = String.format("%.1f\u00B0", Double.valueOf(lastTemperature)); badge3.setBadgeValue(tempValue); badge3.setBadgeCaption(String.format("Temperature (%s)", units)); } else { badge3.setVisibility(View.GONE); } } //for backgrounds switch (mTapPager.getCurrentItem()) { case 0: mImageView0.setImageResource(R.drawable.e1); mImageView1.setBackgroundResource(R.drawable.e2); break; case 1: mImageView0.setImageResource(R.drawable.a1); mImageView1.setBackgroundResource(R.drawable.a2); break; case 2: mImageView0.setImageResource(R.drawable.b1); mImageView1.setBackgroundResource(R.drawable.b2); break; case 3: mImageView0.setImageResource(R.drawable.c1); mImageView1.setBackgroundResource(R.drawable.c2); break; case 4: mImageView0.setImageResource(R.drawable.d1); mImageView1.setBackgroundResource(R.drawable.d2); break; default: mImageView0.setImageResource(R.drawable.e1); mImageView1.setBackgroundResource(R.drawable.e2); break; } } @Override protected void onStart() { super.onStart(); synchronized (mTapsLock) { for (final KegTap tap : mCore.getTapManager().getVisibleTaps()) { mTapList.add(tap); } mPouringTapAdapter.notifyDataSetChanged(); } } @Override protected void onResume() { super.onResume(); Log.d(TAG, "onResume"); mCore.getBus().register(this); mHandler.post(REFRESH_FLOWS_RUNNABLE); } @Override protected void onPostResume() { super.onPostResume(); final Flow flow = getCurrentlyFocusedFlow(); Log.d(TAG, "onPostResume: focusedFlow: " + flow); if (flow != null) { updateControlsForFlow(flow); if (Strings.isNullOrEmpty(flow.getImagePath())) { if (mShowCamera && mConfig.getEnableAutoTakePhoto()) { mCameraFragment.schedulePicture(); } } } } @Override protected void onPause() { Log.d(TAG, "onPause"); mHandler.removeCallbacks(REFRESH_FLOWS_RUNNABLE); mCore.getBus().unregister(this); super.onPause(); if (mConfig.wakeDuringPour()) { getWindow().addFlags( WindowManager.LayoutParams.FLAG_FULLSCREEN | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); } } @Override protected void onStop() { if (isFinishing()) { endAllFlows(); } mTapList.clear(); super.onStop(); } @Override public void onBackPressed() { if (!mFlowManager.getAllActiveFlows().isEmpty()) { endAllFlows(); } else { super.onBackPressed(); } } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); setIntent(intent); refreshFlows(); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case REQUEST_AUTH_DRINKER: if (data == null) { Log.i(TAG, "No user selected."); return; } String username = data.getStringExtra(KegtabCommon.ACTIVITY_AUTH_DRINKER_RESULT_EXTRA_USERNAME); if (Strings.isNullOrEmpty(username)) { Log.i(TAG, "No user selected."); return; } int flowId = data.getIntExtra(EXTRA_FLOW_ID, 0); final Flow flow = mFlowManager.getFlowForFlowId(flowId); if (flow == null) { Log.w(TAG, "No flow for id " + flowId); return; } Log.i(TAG, "Flow " + flowId + " claimed by: " + username); flow.setUsername(username); return; default: super.onActivityResult(requestCode, resultCode, data); } } private KegTap getMostActiveTap() { KegTap tap = getCurrentlyFocusedTap(); long leastIdle = Long.MAX_VALUE; for (Flow flow : mFlowManager.getAllActiveFlows()) { final KegTap flowTap = flow.getTap(); if (flowTap != null && flow.getIdleTimeMs() < leastIdle) { tap = flowTap; leastIdle = flow.getIdleTimeMs(); } } return tap; } private void scrollToMostActiveTap() { final Flow currentFlow = getCurrentlyFocusedFlow(); // Current flow is active; don't scroll. if (currentFlow != null && currentFlow.getIdleTimeMs() < IDLE_SCROLL_TIMEOUT_MILLIS) { return; } // Current flow is most active; still don't scroll. final KegTap mostActive = getMostActiveTap(); if (currentFlow != null && mostActive == currentFlow.getTap()) { return; } // No active taps. if (mostActive == null) { Log.d(TAG, "Could not find an active tap."); return; } // We have a candidate. final Flow candidateFlow = mFlowManager.getFlowForTap(mostActive); if (candidateFlow != null) { final KegTap tap = candidateFlow.getTap(); synchronized (mTapsLock) { final int position = mTapList.indexOf(tap); if (position >= 0) { scrollToPosition(position); } } } } private void scrollToPosition(int position) { if (position != mTapPager.getCurrentItem()) { Log.d(TAG, "scrollToPosition: " + position); mTapPager.setCurrentItem(position, true); } } private void refreshFlows() { final Set<Flow> activeFlows = Sets.newLinkedHashSet(mFlowManager.getAllActiveFlows()); mActiveFlows.addAll(activeFlows); final Set<Flow> oldFlows = Sets.newLinkedHashSet(Sets.difference(mActiveFlows, activeFlows)); for (final Flow oldFlow : oldFlows) { Log.d(TAG, "Removing inactive flow: " + oldFlow); mActiveFlows.remove(oldFlow); } if (mShowCamera) { mCameraFragment.setEnabled(!mActiveFlows.isEmpty()); } if (mActiveFlows.isEmpty()) { cancelIdleWarning(); finish(); return; } long largestIdleTime = Long.MIN_VALUE; for (final Flow flow : mActiveFlows) { if (flow.getTap() == null) { continue; } if (DEBUG) { Log.d(TAG, "Refreshing with flow: " + flow); } final long idleTimeMs = flow.getIdleTimeMs(); if (idleTimeMs > largestIdleTime) { largestIdleTime = idleTimeMs; } } if (largestIdleTime >= mConfig.getIdleWarningMs()) { sendIdleWarning(); } else { cancelIdleWarning(); } scrollToMostActiveTap(); } private void endAllFlows() { for (final Flow flow : mFlowManager.getAllActiveFlows()) { mFlowManager.endFlow(flow); } cancelIdleWarning(); if (mShowCamera) { mCameraFragment.cancelPendingPicture(); } } private void sendIdleWarning() { if (mIdleDialogShowing) { return; } mIdleDialogFragment.show(getFragmentManager(), "idle"); mIdleDialogShowing = true; } private void cancelIdleWarning() { if (mIdleDialogShowing) { mIdleDialogFragment.dismiss(); getFragmentManager().executePendingTransactions(); mIdleDialogShowing = false; } } public static Intent getStartIntent(Context context) { final Intent intent = new Intent(context, PourInProgressActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); return intent; } }