/* * Copyright (C) 2007 The Android Open Source Project * * 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 net.redgeek.android.eventrend.graph; import java.util.ArrayList; import java.util.Calendar; import net.redgeek.android.eventrend.EvenTrendActivity; import net.redgeek.android.eventrend.Preferences; import net.redgeek.android.eventrend.R; import net.redgeek.android.eventrend.backgroundtasks.CorrelateTask; import net.redgeek.android.eventrend.calendar.CalendarActivity; import net.redgeek.android.eventrend.db.CategoryDbTable; import net.redgeek.android.eventrend.graph.plugins.LinearMatrixCorrelator; import net.redgeek.android.eventrend.primitives.TimeSeries; import net.redgeek.android.eventrend.primitives.TimeSeriesCollector; import net.redgeek.android.eventrend.util.DateUtil; import net.redgeek.android.eventrend.util.DynamicSpinner; import net.redgeek.android.eventrend.util.GUITaskQueue; import net.redgeek.android.eventrend.util.ProgressIndicator; import android.app.AlertDialog; import android.app.Dialog; import android.app.AlertDialog.Builder; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.os.Debug; import android.view.Gravity; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.Window; import android.view.ViewGroup.LayoutParams; import android.widget.AdapterView; import android.widget.CompoundButton; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.Spinner; import android.widget.TextView; import android.widget.ToggleButton; import android.widget.ZoomControls; public class GraphActivity extends EvenTrendActivity { // Menu items private static final int MENU_GRAPH_FILTER_ID = Menu.FIRST; private static final int MENU_GRAPH_CORRELATE_ID = Menu.FIRST + 1; private static final int MENU_GRAPH_SNAP_TO_PERIOD_ID = Menu.FIRST + 2; private static final int MENU_CALENDAR_VIEW_ID = Menu.FIRST + 3; private static final int MENU_GRAPH_PREFS_ID = Menu.FIRST + 4; // Dialogs private static final int DIALOG_GRAPH_FILTER = 0; private static final int DIALOG_GRAPH_CORRELATE = 1; // UI elements private ToggleButton mShowTrendsToggle; private ToggleButton mShowGoalsToggle; private ToggleButton mShowMarkersToggle; private ZoomControls mZoomControls; private LinearLayout mGraphControls; private GraphView mGraphView; private DynamicSpinner mAggregationSpinner; private LinearLayout mAggregationSpinnerLayout; private LinearLayout mGraphPlotLayout; private LinearLayout mGraphZoomLayout; private TextView mGraphStatus; private GraphFilterListAdapter mGFLA; private Dialog mFilterDialog; private ProgressIndicator.Titlebar mProgress; // Special aggregation period: private static final String AUTO_AGGREGATION = "Automatic"; // Listeners private Spinner.OnItemSelectedListener mAggregationSpinnerListener; private CompoundButton.OnCheckedChangeListener mShowTrendsToggleListener; private CompoundButton.OnCheckedChangeListener mShowGoalsToggleListener; private CompoundButton.OnCheckedChangeListener mShowMarkersToggleListener; // Private data private TimeSeriesCollector mTSC; private ArrayList<Integer> mSeriesEnabled; private int mHistory; private float mSmoothing; private float mSensitivity; private int mDecimals; // Saved across orientation changes private long mStartMs; private long mEndMs; private long mAggregation = CategoryDbTable.KEY_PERIOD_MS_AUTO; // Tasks private CorrelateTask mCorrelator; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); getPrefs(); setupData(icicle); setupTasks(); setupUI(); populateFields(); } private void getPrefs() { mHistory = Preferences.getHistory(getCtx()); mSmoothing = Preferences.getSmoothingConstant(getCtx()); mDecimals = Preferences.getDecimalPlaces(getCtx()); mSensitivity = Preferences.getStdDevSensitivity(getCtx()); } private void setupTasks() { mCorrelator = new CorrelateTask(); } private void setupData(Bundle icicle) { mTSC = new TimeSeriesCollector(getDbh()); mTSC.setHistory(mHistory); mTSC.setSmoothing(mSmoothing); mTSC.setSensitivity(mSensitivity); mTSC.setInterpolators(((EvenTrendActivity) getCtx()).getInterpolators()); mTSC.updateTimeSeriesMetaLocking(true); mSeriesEnabled = getIntent().getIntegerArrayListExtra(VIEW_DEFAULT_CATIDS); if (mSeriesEnabled != null) { for (int i = 0; i < mSeriesEnabled.size(); i++) { Integer j = mSeriesEnabled.get(i); mTSC.setSeriesEnabled(j.longValue(), true); } } Calendar cal = Calendar.getInstance(); mStartMs = getIntent().getLongExtra(GRAPH_START_MS, cal.getTimeInMillis() - DateUtil.DAY_MS * 7); mEndMs = getIntent().getLongExtra(GRAPH_END_MS, cal.getTimeInMillis()); mAggregation = getIntent().getLongExtra(GRAPH_START_MS, CategoryDbTable.KEY_PERIOD_MS_AUTO); if (icicle != null) { mStartMs = icicle.getLong(GRAPH_START_MS); mEndMs = icicle.getLong(GRAPH_END_MS); mAggregation = icicle.getLong(GRAPH_AGGREGATION); mSeriesEnabled = icicle.getIntegerArrayList(VIEW_DEFAULT_CATIDS); if (mSeriesEnabled != null) { for (int i = 0; i < mSeriesEnabled.size(); i++) { Integer j = mSeriesEnabled.get(i); mTSC.setSeriesEnabled(j.longValue(), true); } } } } private void setupUI() { setupListeners(); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); setContentView(R.layout.graph_view); mGraphStatus = (TextView) findViewById(R.id.graph_status); mZoomControls = createZoomControls(); mGraphView = new GraphView(this, mZoomControls, mTSC); mGraphView.resetZoom(); mGraphControls = (LinearLayout) findViewById(R.id.graph_controls); mGraphControls.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL); mAggregationSpinner = new DynamicSpinner(getCtx()); mAggregationSpinner.setPrompt("Aggregate By"); mAggregationSpinnerLayout = (LinearLayout) findViewById(R.id.graph_view_agg_menu); mAggregationSpinnerLayout.addView(mAggregationSpinner, new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT)); mAggregationSpinner.addSpinnerItem(AUTO_AGGREGATION, new Long( CategoryDbTable.KEY_PERIOD_MS_AUTO)); for (int i = 0; i < CategoryDbTable.KEY_PERIODS.length; i++) { mAggregationSpinner.addSpinnerItem(CategoryDbTable.KEY_PERIODS[i], new Long(i)); } mAggregationSpinner.setOnItemSelectedListener(mAggregationSpinnerListener); mGraphPlotLayout = (LinearLayout) findViewById(R.id.graph_plot); mGraphPlotLayout.addView(mGraphView); mGraphZoomLayout = (LinearLayout) findViewById(R.id.graph_zoom); mGraphZoomLayout.addView(mZoomControls, new LinearLayout.LayoutParams( LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); mShowTrendsToggle = (ToggleButton) findViewById(R.id.graph_view_trends); mShowTrendsToggle.setOnCheckedChangeListener(mShowTrendsToggleListener); mShowGoalsToggle = (ToggleButton) findViewById(R.id.graph_view_goals); mShowGoalsToggle.setOnCheckedChangeListener(mShowGoalsToggleListener); mShowMarkersToggle = (ToggleButton) findViewById(R.id.graph_view_markers); mShowMarkersToggle.setOnCheckedChangeListener(mShowMarkersToggleListener); mProgress = new ProgressIndicator.Titlebar(getCtx()); } private void setupListeners() { mAggregationSpinnerListener = new Spinner.OnItemSelectedListener() { public void onItemSelected(AdapterView parent, View v, int position, long id) { String period = ((TextView) v).getText().toString(); if (period.equals(AUTO_AGGREGATION)) { mAggregation = CategoryDbTable.KEY_PERIOD_MS_AUTO; mTSC.setAutoAggregation(true); } else { mAggregation = CategoryDbTable.mapPeriodToMs(period); mTSC.setAutoAggregation(false); mTSC.setAggregationMs(mAggregation); } graph(); return; } public void onNothingSelected(AdapterView arg0) { return; } }; mShowTrendsToggleListener = new CompoundButton.OnCheckedChangeListener() { public void onCheckedChanged(CompoundButton view, boolean isChecked) { mGraphView.setTrendView(isChecked); graph(); } }; mShowGoalsToggleListener = new CompoundButton.OnCheckedChangeListener() { public void onCheckedChanged(CompoundButton view, boolean isChecked) { mGraphView.setGoalView(isChecked); graph(); } }; mShowMarkersToggleListener = new CompoundButton.OnCheckedChangeListener() { public void onCheckedChanged(CompoundButton view, boolean isChecked) { mGraphView.setMarkersView(isChecked); graph(); } }; } private ZoomControls createZoomControls() { ZoomControls zoomControls = new ZoomControls(this); zoomControls.setLayoutParams(new FrameLayout.LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, Gravity.BOTTOM + Gravity.CENTER_HORIZONTAL)); zoomControls.setVisibility(View.INVISIBLE); return zoomControls; } private void populateFields() { mGraphView.getGraph().setGraphRange(mStartMs, mEndMs); if (mAggregation == CategoryDbTable.KEY_PERIOD_MS_AUTO) { mTSC.setAutoAggregation(true); mAggregationSpinner.setSelection(0); } else { mTSC.setAutoAggregation(false); mTSC.setAggregationMs(mAggregation); // XXX ugly hack. We add one because we inserted "Auto" ahead of // everything else mAggregationSpinner.setSelection(CategoryDbTable .mapMsToIndex(mAggregation) + 1); } } @Override public void executeNonGuiTask() throws Exception { mCorrelator.correlate(); } @Override public void afterExecute() { showDialog(DIALOG_GRAPH_CORRELATE); } @Override public void onFailure(Throwable t) { } @Override public boolean onCreateOptionsMenu(Menu menu) { boolean result = super.onCreateOptionsMenu(menu); menu.add(0, MENU_GRAPH_FILTER_ID, 0, R.string.graph_filter).setIcon( R.drawable.filter_small); menu.add(0, MENU_GRAPH_CORRELATE_ID, 0, R.string.graph_correlate).setIcon( R.drawable.correlate_small); menu.add(0, MENU_GRAPH_SNAP_TO_PERIOD_ID, 0, R.string.graph_snap_to) .setIcon(R.drawable.snapto); menu.add(0, MENU_CALENDAR_VIEW_ID, 0, R.string.menu_calendar).setIcon( android.R.drawable.ic_menu_today); menu.add(0, MENU_GRAPH_PREFS_ID, 0, R.string.menu_app_prefs).setIcon( android.R.drawable.ic_menu_preferences); return result; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case MENU_GRAPH_FILTER_ID: showDialog(DIALOG_GRAPH_FILTER); removeDialog(DIALOG_GRAPH_CORRELATE); return true; case MENU_GRAPH_CORRELATE_ID: LinearMatrixCorrelator c = new LinearMatrixCorrelator(); mCorrelator.setCorrelator(c); mCorrelator.setTimeSeries(mTSC.getAllEnabledSeries()); GUITaskQueue.getInstance().addTask(mProgress, this); return true; case MENU_GRAPH_SNAP_TO_PERIOD_ID: mGraphView.snapToSpan(); graph(); return true; case MENU_CALENDAR_VIEW_ID: calendarView(); return true; case MENU_GRAPH_PREFS_ID: editPrefs(); return true; } return super.onOptionsItemSelected(item); } private void editPrefs() { Intent i = new Intent(this, Preferences.class); startActivityForResult(i, PREFS_EDIT); } private void graph() { mGraphView.updateData(); mGraphView.invalidate(); } private void calendarView() { mSeriesEnabled.clear(); ArrayList<TimeSeries> series = mTSC.getAllEnabledSeries(); for (int j = 0; j < series.size(); j++) { TimeSeries ts = series.get(j); if (ts != null) mSeriesEnabled.add(new Integer((int) ts.getDbRow().getId())); } Intent i = new Intent(this, CalendarActivity.class); i.putIntegerArrayListExtra(VIEW_DEFAULT_CATIDS, mSeriesEnabled); i.putExtra(GRAPH_START_MS, mGraphView.getGraph().getGraphStart()); startActivityForResult(i, CALENDAR_VIEW); } public TextView getGraphStatusTextView() { return mGraphStatus; } @Override protected Dialog onCreateDialog(int id) { switch (id) { case DIALOG_GRAPH_FILTER: mFilterDialog = filterDialog("Show Series"); return mFilterDialog; case DIALOG_GRAPH_CORRELATE: Dialog d; d = correlateDialog("Correlations"); return d; default: } return null; } private Dialog filterDialog(String title) { Builder b = new AlertDialog.Builder(getCtx()); b.setTitle(title); mGFLA = new GraphFilterListAdapter(this, mTSC); for (int i = 0; i < mTSC.numSeries(); i++) { TimeSeries ts = mTSC.getSeries(i); long id = ts.getDbRow().getId(); String name = ts.getDbRow().getCategoryName(); String color = ts.getDbRow().getColor(); int rank = ts.getDbRow().getRank(); mGFLA.addItem(new GraphFilterRow(id, name, color, rank)); } b.setAdapter(mGFLA, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { } }); b.setPositiveButton("Ok", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { ((GraphActivity) getCtx()).graph(); } }); Dialog d = b.create(); return d; } private Dialog correlateDialog(String title) { Builder b = new AlertDialog.Builder(getCtx()); b.setTitle(title); b.setMessage(mCorrelator.mOutput); b.setPositiveButton("Ok", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { } }); Dialog d = b.create(); return d; } @Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { super.onActivityResult(requestCode, resultCode, intent); graph(); } @Override protected void onSaveInstanceState(Bundle outState) { mSeriesEnabled.clear(); ArrayList<TimeSeries> series = mTSC.getAllEnabledSeries(); for (int j = 0; j < series.size(); j++) { TimeSeries ts = series.get(j); if (ts != null) mSeriesEnabled.add(new Integer((int) ts.getDbRow().getId())); } outState.putIntegerArrayList(VIEW_DEFAULT_CATIDS, mSeriesEnabled); outState.putLong(GRAPH_START_MS, mGraphView.getGraph().getGraphStart()); outState.putLong(GRAPH_END_MS, mGraphView.getGraph().getGraphEnd()); outState.putLong(GRAPH_AGGREGATION, mAggregation); super.onSaveInstanceState(outState); } @Override protected void onResume() { getPrefs(); mTSC.setHistory(mHistory); mGraphView.getGraph().setDecimals(mDecimals); mGraphView.setColorScheme(); graph(); super.onResume(); } @Override protected void onPause() { super.onPause(); } }