Java tutorial
/* * StorageTrac application - keeps track of the external storage usage * Copyright (C) 2012 Narendra M.A. * This program 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, either version 3 of the License, or * (at your option) any later version. * * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */ package com.nma.util.sdcardtrac; import android.app.Activity; import android.app.ProgressDialog; import android.graphics.Color; import android.os.Environment; import android.support.v4.app.LoaderManager; import android.support.v4.content.Loader; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.Fragment; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import com.jjoe64.graphview.CustomLabelFormatter; import com.jjoe64.graphview.GraphView; import com.jjoe64.graphview.LineGraphView; import com.jjoe64.graphview.GraphViewSeries; import java.text.DateFormat; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import java.lang.Math; /** * A simple {@link Fragment} subclass. * Activities that contain this fragment must implement the * {@link GraphFragment.OnFragmentInteractionListener} interface * to handle interaction events. * Use the {@link GraphFragment#newInstance} factory method to * create an instance of this fragment. * */ public class GraphFragment extends Fragment implements LoaderManager.LoaderCallbacks<List<DatabaseLoader.DatabaseRow>>, GraphView.GraphSelectHandler, Button.OnClickListener { // TODO: Rename parameter arguments, choose names that match // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER public static final String ARG_STORAGE_TYPE = "storage_type"; public static final String ARG_TIME_INTERVAL = "time_interval"; // TODO: Rename and change types of parameters private String storageType; private String timeInterval; private OnFragmentInteractionListener mListener; private List<DatabaseLoader.DatabaseRow> locData; private String logMessages[]; private GraphView storageGraph; private GraphViewSeries graphSeries; private String graphLabel; private long maxStorage, startTime, endTime, viewPortStart, viewPortWidth; public static final float GRAPHVIEW_TEXT_SIZE_DIP = 8.0f; private static final float GRAPHVIEW_POINT_SIZE_DIP = 4.0f; private static boolean firstView = true; private boolean secondSelect = false; private int selectedPoint = 0; private int navSelect = 0; private int graphDataLen = 0; private boolean redraw = false; private boolean buttonNav = false; private boolean buttonSelect = false; /** * Use this factory method to create a new instance of * this fragment using the provided parameters. * * @param param1 Parameter 1. * @param param2 Parameter 2. * @return A new instance of fragment GraphFragment. */ // TODO: Rename and change types and number of parameters public static GraphFragment newInstance(String param1, String param2) { GraphFragment fragment = new GraphFragment(); Bundle args = new Bundle(); args.putString(ARG_STORAGE_TYPE, param1); args.putString(ARG_TIME_INTERVAL, param2); fragment.setArguments(args); return fragment; } public GraphFragment() { // Required empty public constructor } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); redraw = false; if (getArguments() != null) { storageType = getArguments().getString(ARG_STORAGE_TYPE); timeInterval = getArguments().getString(ARG_TIME_INTERVAL); Log.d("GraphFragOnCreate", "Got args " + storageType + ", " + this); } } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); setRetainInstance(true); if (savedInstanceState != null) { viewPortStart = savedInstanceState.getLong("viewportstart"); viewPortWidth = savedInstanceState.getLong("viewportwidth"); redraw = true; } // Load data getLoaderManager().initLoader(0, null, this); //Log.d("Fragment:Activity", "Done with initLoader"); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { setRetainInstance(true); // Inflate the layout for this fragment View retView = inflater.inflate(R.layout.fragment_graph, container, false); return retView; } // TODO: Rename method, update argument and hook method into UI event public void onButtonPressed(String reason) { if (mListener != null) { mListener.onFragmentInteraction(reason); } } @Override public void onAttach(Activity activity) { super.onAttach(activity); try { mListener = (OnFragmentInteractionListener) activity; } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement OnFragmentInteractionListener"); } } @Override public void onDetach() { super.onDetach(); mListener = null; } @Override public Loader onCreateLoader(int id, Bundle args) { //Log.d("FragLoader", "Returning created loader"); getActivity().setProgressBarIndeterminateVisibility(true); return new DatabaseLoader(getActivity(), storageType); } @Override public void onLoadFinished(Loader<List<DatabaseLoader.DatabaseRow>> loader, List<DatabaseLoader.DatabaseRow> data) { if (SettingsActivity.ENABLE_DEBUG) Log.d("FragLoader", "Done loading with " + data.size() + " items"); locData = data; getActivity().setProgressBarIndeterminateVisibility(false); if (locData.size() <= 1) { // Flag an error Toast.makeText(getActivity(), R.string.db_empty_error, Toast.LENGTH_SHORT).show(); // Goto settings if (firstView && mListener != null) { firstView = false; mListener.onFragmentInteraction(getResources().getString(R.string.act_goto_settings)); } } else { createGraphData(); if (graphSeries != null) drawGraph(((LinearLayout) getView().findViewById(R.id.graph_fragment_layout)), false); else Toast.makeText(getActivity(), R.string.db_empty_error, Toast.LENGTH_SHORT).show(); } } @Override public void onLoaderReset(Loader loader) { //if (getActivity() != null) // getActivity().setProgressBarIndeterminateVisibility(true); locData = null; } @Override public void onViewCreated(View view, Bundle savedInstanceState) { if (graphSeries != null) { // Restore state if (savedInstanceState != null) { viewPortStart = savedInstanceState.getLong("viewportstart"); viewPortWidth = savedInstanceState.getLong("viewportwidth"); redraw = true; } drawGraph((LinearLayout) view.findViewById(R.id.graph_fragment_layout), redraw); } // Setup navigation callbacks ImageButton graphFirst, graphLast, graphNext, graphPrev, graphList; graphFirst = (ImageButton) view.findViewById(R.id.graph_first); graphLast = (ImageButton) view.findViewById(R.id.graph_last); graphPrev = (ImageButton) view.findViewById(R.id.graph_prev); graphNext = (ImageButton) view.findViewById(R.id.graph_next); graphList = (ImageButton) view.findViewById(R.id.graph_view_list); graphFirst.setOnClickListener(this); graphLast.setOnClickListener(this); graphPrev.setOnClickListener(this); graphNext.setOnClickListener(this); graphList.setOnClickListener(this); } @Override public void onSaveInstanceState(final Bundle outState) { super.onSaveInstanceState(outState); outState.putLong("viewportstart", viewPortStart); outState.putLong("viewportwidth", viewPortWidth); } // Create the graph plot private void createGraphData() { int i = 0; long maxUsage = 0, timeStamp; if (locData == null) { Log.e(getClass().getName(), "No data created by loader!"); return; } // Build the graph if (SettingsActivity.ENABLE_DEBUG) Log.d(getClass().getName(), "Creating data of len " + locData.size()); GraphView.GraphViewData[] graphData = new GraphView.GraphViewData[1]; ArrayList<GraphView.GraphViewData> dataArr = new ArrayList<GraphView.GraphViewData>(); logMessages = new String[locData.size()]; if (locData.size() > 0) startTime = locData.get(0).getTime(); else startTime = 0; viewPortStart = startTime; for (DatabaseLoader.DatabaseRow row : locData) { timeStamp = row.getTime(); endTime = timeStamp; long usage = Math.abs(row.getUsage()); //graphData[i] = new GraphView.GraphViewData(timeStamp, usage); if (row.getChangeLog().contains("/")) // Add only paths dataArr.add(new GraphView.GraphViewData(timeStamp, usage)); else continue; if (usage > maxUsage) maxUsage = usage; // Log logMessages[i] = row.getChangeLog(); i++; } graphData = dataArr.toArray(graphData); if (SettingsActivity.ENABLE_DEBUG) Log.d("createGraphData", "Creating graph data len " + dataArr.size() + " max " + maxUsage); if (dataArr.size() == 0) { graphSeries = null; return; } graphSeries = new GraphViewSeries(graphData); maxStorage = Environment.getExternalStorageDirectory().getTotalSpace(); //String usageRatioInt = new DecimalFormat("#.#").format((maxUsage * 100f) / maxStorage); //usageRatio = DatabaseLoader.convertToStorageUnits(maxUsage) + " max used (" // + usageRatioInt + "%) of " + DatabaseLoader.convertToStorageUnits(maxStorage); graphLabel = Environment.getExternalStorageDirectory().getAbsolutePath() + " - " + DatabaseLoader.convertToStorageUnits(maxStorage); // Round up to GB if (maxStorage * 0.7 > maxUsage) maxStorage = (long) Math.ceil((double) maxUsage / 1000000000) * 1000000000; else maxStorage = (long) Math.ceil((double) maxStorage / 1000000000) * 1000000000; navSelect = dataArr.size() - 1; graphDataLen = navSelect; } private void drawGraph(LinearLayout view, boolean redraw) { float textSize, dispScale, pointSize; // Determine text size dispScale = getActivity().getResources().getDisplayMetrics().density; textSize = (GRAPHVIEW_TEXT_SIZE_DIP * dispScale) + 0.5f; pointSize = (GRAPHVIEW_POINT_SIZE_DIP * dispScale) + 0.5f; storageGraph = new LineGraphView(getActivity(), graphLabel); storageGraph.setCustomLabelFormatter(new CustomLabelFormatter() { String prevDate = ""; @Override public String formatLabel(double value, boolean isValueX, int index, int lastIndex) { String retValue; boolean valueXinRange; valueXinRange = (index == 0) || (index == lastIndex); if (isValueX) { // Format time in human readable form if (valueXinRange) { String dateStr; Date currDate = new Date((long) value); dateStr = DateFormat.getDateInstance(DateFormat.MEDIUM).format(currDate); if (dateStr.equals(prevDate)) { // Show hh:mm retValue = DateFormat.getTimeInstance(DateFormat.SHORT).format(currDate); } else { retValue = dateStr; } prevDate = dateStr; //Log.d(getClass().getName(), "Label is : " + retValue); } else { retValue = " "; } } else { // Format size in human readable form retValue = DatabaseLoader.convertToStorageUnits(value); //prevDate = ""; } //return super.formatLabel(value, isValueX); // let the y-value be normal-formatted return retValue; } }); storageGraph.addSeries(graphSeries); storageGraph.setManualYAxis(true); storageGraph.setManualYAxisBounds(maxStorage, 0); storageGraph.setScalable(false); //storageGraph.setScrollable(true); storageGraph.getGraphViewStyle().setGridColor(Color.GREEN); storageGraph.getGraphViewStyle().setHorizontalLabelsColor(Color.YELLOW); storageGraph.getGraphViewStyle().setVerticalLabelsColor(Color.RED); storageGraph.getGraphViewStyle().setTextSize(textSize); storageGraph.getGraphViewStyle().setNumHorizontalLabels(2); storageGraph.getGraphViewStyle().setNumVerticalLabels(5); storageGraph.getGraphViewStyle().setVerticalLabelsWidth((int) (textSize * 4)); //storageGraph.setMultiLineXLabel(true, ";"); ((LineGraphView) storageGraph).setDrawBackground(true); ((LineGraphView) storageGraph).setDrawDataPoints(true); ((LineGraphView) storageGraph).setDataPointsRadius(pointSize); //storageGraph.highlightSample(0, true, locData.size() - 1); // Add selector callback storageGraph.setSelectHandler(this); setViewport(redraw); if (view != null) { view.addView(storageGraph); } if (SettingsActivity.ENABLE_DEBUG) Log.d(getClass().getName(), "Drew the graph, redraw=" + redraw); } // Helper to manipulate graph viewport public void setViewport(boolean redraw) { Calendar calcView[]; // Start time calcView = new Calendar[2]; calcView[0] = Calendar.getInstance(); calcView[0].setTimeInMillis(viewPortStart); calcView[1] = (Calendar) calcView[0].clone(); if (timeInterval == null) timeInterval = "Day"; // Override with setting if (timeInterval.equals("Hour")) calcView[1].add(Calendar.HOUR_OF_DAY, -1); else if (timeInterval.equals("Day")) calcView[1].add(Calendar.DAY_OF_MONTH, -1); else if (timeInterval.equals("Week")) calcView[1].add(Calendar.WEEK_OF_YEAR, -1); else if (timeInterval.equals("Month")) calcView[1].add(Calendar.MONTH, -1); else calcView[1].add(Calendar.YEAR, -1); viewPortWidth = calcView[0].getTimeInMillis() - calcView[1].getTimeInMillis(); //Calendar.getInstance().getTimeInMillis() - calcView.getTimeInMillis(); secondSelect = false; if (storageGraph != null) { storageGraph.setViewPort(viewPortStart, viewPortWidth); if (viewPortWidth > (endTime - startTime)) storageGraph.setScrollable(false); else { storageGraph.setScrollable(true); if (!redraw) storageGraph.scrollToEnd(); } Log.d("GraphFrag", "Updated viewport to " + timeInterval + ", " + viewPortStart + " - " + viewPortWidth); } } public void setTimeInterval(String interval, boolean visible) { timeInterval = interval; if (visible) { Log.d("GraphFrag", "Refreshing fragment " + this); // Option 1: let the activity refresh the fragment mListener.onFragmentInteraction(storageType); } // Option 2: do it here //getLoaderManager().restartLoader(0, null, this); } public void restartLoader() { Log.d(getClass().getName(), "Restarting loader in " + storageType); getLoaderManager().restartLoader(0, null, this); } // Graph select handler @Override public void onGraphSelect(int i, int start) { String[] logLines; if (logMessages == null) { Log.e(getClass().getName(), "No log messages to display!"); return; } if (logMessages.length == 0) { logLines = new String[2]; logLines[0] = "No Data"; logLines[1] = "-"; } else { int showInd = i; if (i > 0 && start > 0) showInd -= 1; //Log.d(getClass().getName(), "Selected index[" + i + "] = " + logMessages[i]); // Split each line of the log logLines = logMessages[showInd].split("\n"); } if (secondSelect == false && i == selectedPoint) secondSelect = true; if (SettingsActivity.ENABLE_DEBUG) Log.d(getClass().getName(), "onSelect " + i + ", selected " + selectedPoint + ", starting " + start); int selInd = (i - start); if (start > 0) selInd += 1; storageGraph.highlightSample(0, true, selInd); if ((secondSelect && !buttonNav) || buttonSelect) { // Call dialog ChangeLogFragment dialog = ChangeLogFragment.newInstance(logLines); dialog.show(getActivity().getSupportFragmentManager(), "dialog"); } selectedPoint = i; if (!buttonNav) navSelect = i; buttonSelect = false; buttonNav = false; secondSelect = false; } @Override public void onClick(View v) { int buttonId = v.getId(); boolean act = false; switch (buttonId) { case R.id.graph_first: navSelect = 0; act = true; break; case R.id.graph_last: navSelect = graphDataLen; act = true; break; case R.id.graph_prev: if (navSelect > 0) { navSelect--; act = true; } break; case R.id.graph_next: if (navSelect < (graphDataLen - 1)) { navSelect++; act = true; } break; case R.id.graph_view_list: // Just show current point act = true; buttonSelect = true; break; default: act = false; } if (SettingsActivity.ENABLE_DEBUG) Log.d(getClass().getName(), "Button " + buttonId + " selected point=" + navSelect + ", act=" + act); if (act) { buttonNav = true; //if (buttonId != R.id.graph_view_list) // secondSelect = false; storageGraph.gotoSample(0, navSelect); } } /** * This interface must be implemented by activities that contain this * fragment to allow an interaction in this fragment to be communicated * to the activity and potentially other fragments contained in that * activity. * <p> * See the Android Training lesson <a href= * "http://developer.android.com/training/basics/fragments/communicating.html" * >Communicating with Other Fragments</a> for more information. */ public interface OnFragmentInteractionListener { // TODO: Update argument type and name public void onFragmentInteraction(String reason); } }