package com.aero.control.fragments; import android.app.AlertDialog; import android.app.Fragment; import android.content.Context; import android.content.DialogInterface; import android.graphics.Color; import android.graphics.Point; import android.os.Bundle; import android.os.SystemClock; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.widget.ListView; import android.widget.TextView; import com.aero.control.AeroActivity; import com.aero.control.R; import com.aero.control.adapter.StatisticAdapter; import com.aero.control.adapter.statisticInit; import com.aero.control.helpers.FilePath; import com.echo.holographlibrary.PieGraph; import com.echo.holographlibrary.PieSlice; import com.github.amlcurran.showcaseview.ShowcaseView; import com.github.amlcurran.showcaseview.targets.Target; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.concurrent.TimeUnit; /** * Created by Alexander Christ on 10.01.14. * CPU Statistics Fragment */ public class StatisticsFragment extends Fragment { public int mIndex = 0; private int mColorIndex = 0; public ViewGroup root; public String[] data; public ListView statisticView; public PieGraph pg; public TextView txtFreq; public TextView txtPercentage; public TextView txtTime; private double mCompleteTime = 0; public ShowcaseView mShowCase; public static final String FILENAME_STATISTICS = "firstrun_statistics"; private final static String NO_DATA_FOUND = "Unavailable"; public ArrayList<Long> cpuTime = new ArrayList<Long>(); public ArrayList<Long> cpuOverallTime = new ArrayList<Long>(); public ArrayList<Long> cpuFreq = new ArrayList<Long>(); public ArrayList<Long> cpuPercentage = new ArrayList<Long>(); public ArrayList<Long> cpuResetTime; public statisticInit[] mResult = new statisticInit[0]; // Override for custom view; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { setHasOptionsMenu(true); root = (ViewGroup) inflater.inflate(R.layout.statistics, null); // Clear UI: clearUI(); loadResetState(); loadUI(true); return root; } // Create our options menu; @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.statistic_menu, menu); super.onCreateOptionsMenu(menu, inflater); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_refresh: clearUI(); loadUI(true); break; case R.id.action_reload: showResetDialog(); break; } return super.onOptionsItemSelected(item); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // Set up our file; int output = 0; if (AeroActivity.genHelper.doesExist(getActivity().getFilesDir().getAbsolutePath() + "/" + FILENAME_STATISTICS)) { output = 1; } // Only show showcase once; if (output == 0) DrawFirstStart(R.string.showcase_statistics_fragment, R.string.showcase_statistics_fragment_sum); } public void DrawFirstStart(int header, int content) { try { final FileOutputStream fos = getActivity().openFileOutput(FILENAME_STATISTICS, Context.MODE_PRIVATE); fos.write("1".getBytes()); fos.close(); } catch (IOException e) { Log.e("Aero", "Could not save file. ", e); } Target homeTarget = new Target() { @Override public Point getPoint() { // Get approximate position of overflow action icon's center int actionBarSize = getActivity().findViewById(R.id.action_refresh).getHeight(); int x = getResources().getDisplayMetrics().widthPixels - actionBarSize / 2; int y = actionBarSize / 2; return new Point(x, y); } }; mShowCase = new ShowcaseView.Builder(getActivity()) .setContentTitle(header) .setContentText(content) .setTarget(homeTarget) .build(); } private void showResetDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); LayoutInflater inflater = getActivity().getLayoutInflater(); View layout = inflater.inflate(R.layout.about_screen, null); TextView aboutText = (TextView) layout.findViewById(R.id.aboutScreen); builder.setTitle(R.string.proceed_with_reset); builder.setIcon(R.drawable.warning); aboutText.setText(R.string.delete_statistics); builder.setView(layout) .setPositiveButton(R.string.got_it, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { // Continue with resetting resetStatistics(); } }) .setNegativeButton(R.string.maybe_later, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { } }) ; builder.show(); } private void resetStatistics() { // Take the current time; Long[] time = cpuOverallTime.toArray(new Long[0]); // Add the time to our reset timer; if (cpuResetTime != null) cpuResetTime = null; cpuResetTime = new ArrayList<Long>(); for (Long t : time) { cpuResetTime.add(t); } Collections.reverse(cpuResetTime); Long[] reversedTime = cpuResetTime.toArray(new Long[0]); try { final FileOutputStream fos = getActivity().openFileOutput("offset_stat", Context.MODE_PRIVATE); String a = ""; for (long f : reversedTime) { // Last Time is actually complete Time; if (reversedTime.length == 0) { // do nothing; } else { a = f + " " + a; } } fos.write(a.getBytes()); fos.close(); } catch (IOException e) { Log.e("Aero", "Could not save file. ", e); } clearUI(); loadUI(false); } public void loadResetState() { /* * First Case; user resetted statistics, but closed app * Second Case; user resetted statistics once, but rebooted */ File a = new File(FilePath.OFFSET_STAT); // Handle First case; if (a.exists() && cpuResetTime == null) { cpuResetTime = new ArrayList<Long>(); String[] array = AeroActivity.shell.getInfoArray(FilePath.OFFSET_STAT, 0, 0); for (String b : array) { if (array.length > 1) cpuResetTime.add(Long.parseLong(b)); } //Handle second case here; try { if (Long.parseLong(array[array.length - 1]) > (SystemClock.elapsedRealtime() / 10)) { a.delete(); cpuResetTime = null; } } catch (NumberFormatException e) { a.delete(); cpuResetTime = null; } } } /* * Will load all data into different arrays. Some error checks are * also calculated here. HoloSlices will be added according to found * data. */ private void loadUI(boolean firstView) { final ArrayList<String> cpuGraphValues = new ArrayList<String>(); Long[] cpuFreqArray; double a; int cpuData = getCpuData(); mCompleteTime = 0; pg = (PieGraph) root.findViewById(R.id.graph); // Handle no cpu data found; if (cpuData == 0) { root.findViewById(R.id.noCpuData).setVisibility(View.VISIBLE); } else root.findViewById(R.id.noCpuData).setVisibility(View.GONE); for (int k = 0; k < cpuData; k++) { String b = data[k]; String[] c = b.split(" "); if(k == 0) { a = Long.parseLong(c[0]); } else { a = Long.parseLong(c[1]); } cpuOverallTime.add((long)a); mCompleteTime = mCompleteTime + a; } cpuOverallTime.add((long)mCompleteTime); // Handle Uptime here, maybe we don't want to reset it anyway... if (cpuResetTime != null) { String resetUptime = NO_DATA_FOUND; long mResetTime = (long)0; if (new File(FilePath.OFFSET_STAT).exists()) resetUptime = (AeroActivity.shell.getInfoArray(FilePath.OFFSET_STAT, 0, 0))[(AeroActivity.shell.getInfoArray(FilePath.OFFSET_STAT, 0, 0)).length - 1]; if (!resetUptime.equals(NO_DATA_FOUND)) mResetTime = Long.parseLong(resetUptime); mCompleteTime = mCompleteTime - mResetTime; } for (int i = 0; i < cpuData; i++) { String b = data[i]; String[] c = b.split(" "); Long offsetTime = (long)0; File offsetFile = new File(FilePath.OFFSET_STAT); if (offsetFile.exists()) { try { offsetTime = Long.parseLong(AeroActivity.shell.getInfoArray(FilePath.OFFSET_STAT, 0, 0)[i]); } catch (ArrayIndexOutOfBoundsException e) { // The file may be corrupt or didn't save correctly.. Log.e("Aero", "The offset file might be smaller as assumed. " + e); offsetFile.delete(); } catch (NumberFormatException e) { Log.e("Aero", "The offset file might be unavailable. " + e); offsetFile.delete(); } } /* * Handle deepsleep, if statistics are resetted hook into the calculation process; */ if(i == 0) { cpuFreq.add((long)0); if (cpuResetTime != null) { cpuTime.add((long) Integer.parseInt(c[0]) - offsetTime); } else { cpuTime.add((long)Integer.parseInt(c[0])); } } else { cpuFreq.add((long)Integer.parseInt(c[0])); if (cpuResetTime != null) { cpuTime.add((long)Integer.parseInt(c[1]) - offsetTime); } else { cpuTime.add((long)Integer.parseInt(c[1])); } } } cpuFreqArray = cpuFreq.toArray(new Long[0]); int i = 0; int j = 0; for(long g: cpuTime) { PieSlice slice; String frequency, time_in_state; int percentage; // Color change; if (j == 8) j = 0; if(cpuFreqArray[i] == 0) frequency = "DeepSleep"; else frequency = AeroActivity.shell.toMHz(cpuFreqArray[i].toString()); time_in_state = convertTime(g); percentage = (int)Math.round((g / mCompleteTime) * 100); // Safe all percentages in our array; cpuPercentage.add((long) percentage); if (g != 0 && percentage >= 1) { slice = new PieSlice(); cpuGraphValues.add(frequency + " " + time_in_state + " " + percentage + "%"); /* * We are setting the value here to some sort of placeholder * which will be later replaced by the animation handler */ slice.setValue(10); slice.setGoalValue(percentage); slice.setColor(Color.parseColor(FilePath.color_code[j])); pg.setThickness(30); pg.addSlice(slice); j++; } i++; } // Fill our listview with final values and load TextViews; createList(cpuFreq, cpuTime, cpuPercentage); if (firstView) handleOnClick(cpuGraphValues); pg.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { if(motionEvent.getAction() == MotionEvent.ACTION_DOWN){ handleOnClick(cpuGraphValues); return true; } return false; } }); // Animate the slices here pg.setDuration(1000); pg.setInterpolator(new AccelerateDecelerateInterpolator()); pg.animateToGoalValues(); } private void clearUI() { /* * Cleanup the whole UI. * Notice: PieGraph and data might be cleaned anyway, * clearing cpuTime/cpuFreq/cpuPercentage AND mResult * is _really_ necessary: */ if(pg != null) pg.removeSlices(); if(data != null) data = new String[0]; if(cpuTime != null) cpuTime.clear(); if(cpuOverallTime != null) cpuOverallTime.clear(); if(cpuFreq != null) cpuFreq.clear(); if(cpuPercentage != null) cpuPercentage.clear(); if (statisticView != null) mResult = new statisticInit[0]; mColorIndex = 0; mIndex = 0; } public final void handleOnClick(ArrayList<String> list) { for (String a: list) { int arrayLength = list.size(); if(mIndex == arrayLength) { mIndex = 0; mColorIndex = 0; } /* * Fix exception; */ if (mColorIndex >= 8) mColorIndex = 0; String currentRow = list.get(mIndex); String[] tmp = currentRow.split(" "); txtFreq = (TextView)root.findViewById(R.id.statisticFreq); txtTime = (TextView)root.findViewById(R.id.statisticTime); txtPercentage = (TextView)root.findViewById(R.id.statisticPercentage); if (tmp[1].contains("MHz")) { tmp[0] = tmp[0] + " MHz"; tmp[1] = tmp[2]; tmp[2] = tmp[3]; } txtFreq.setText(tmp[0]); txtTime.setText(tmp[1]); txtPercentage.setText(tmp[2]); txtFreq.setTypeface(FilePath.kitkatFont); txtTime.setTypeface(FilePath.kitkatFont); txtPercentage.setTypeface(FilePath.kitkatFont); txtFreq.setTextColor(Color.parseColor(FilePath.color_code[mColorIndex])); txtTime.setTextColor(Color.parseColor(FilePath.color_code[mColorIndex])); txtPercentage.setTextColor(Color.parseColor(FilePath.color_code[mColorIndex])); } mColorIndex++; mIndex++; } /* * Convert usertime in human readable values; */ public final String convertTime(long msTime) { msTime = msTime * 10; return String.format("%02dh:%02dm:%02ds", TimeUnit.MILLISECONDS.toHours(msTime), TimeUnit.MILLISECONDS.toMinutes(msTime) - TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(msTime)), TimeUnit.MILLISECONDS.toSeconds(msTime) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(msTime)) ); } /* * Finally creates our list from three array sources */ public final void createList(ArrayList<Long> cpuFreq, ArrayList<Long> cpuTime, ArrayList<Long> cpuPercentage) { // Add Complete Uptime; cpuFreq.add((long)1); cpuTime.add((long)mCompleteTime); cpuPercentage.add((long)100); // Get Data; Long[] freq = cpuFreq.toArray(new Long[0]); Long[] time = cpuTime.toArray(new Long[0]); Long[] percentage = cpuPercentage.toArray(new Long[0]); ArrayDataLoader adl = new ArrayDataLoader(); adl.loadSingleEntry(freq, time, percentage); statisticView = (ListView) root.findViewById(R.id.statisticListView); StatisticAdapter adapter = new StatisticAdapter(getActivity(), R.layout.statistic_layout, mResult); statisticView.setAdapter(adapter); } public final int getCpuData() { if (!(AeroActivity.genHelper.doesExist(FilePath.TIME_IN_STATE_PATH))) return 0; data = AeroActivity.shell.getInfo(FilePath.TIME_IN_STATE_PATH, true); if (data == null) return 0; return data.length; } /* * Loads our preloaded data into our listview; */ private final class ArrayDataLoader { public final void loadSingleEntry(Long[] freq, Long[] time, Long[] percentage) { int length = freq.length; for(int j = 0; j < length; j++) { // Doing the percentage check here again; if (percentage[j] != 0 && percentage[j] >= 1) { String convertedFreq = AeroActivity.shell.toMHz(freq[j] + ""); // Small UI-Tweak; if(convertedFreq.length() < 8) convertedFreq = convertedFreq + "\t"; else if (convertedFreq.length() < 7) convertedFreq = convertedFreq + "\t\t"; // Handle Deepsleep if(j == 0) loadArray(mResult, new statisticInit("Deepsleep", convertTime(time[j]) + "", percentage[j] + "%")); else if (j == length - 1) loadArray(mResult, new statisticInit("Uptime ", convertTime(time[j]) + "", percentage[j] + "%")); else loadArray(mResult, new statisticInit(convertedFreq, convertTime(time[j]) + "", percentage[j] + "%")); } } } /* * Just a wrapper; */ public final void loadArray (statisticInit[] resultSet, statisticInit data) { mResult = fillArray(resultSet, data); } public final statisticInit[] fillArray (statisticInit[] resultSet, statisticInit data) { statisticInit[] result = Arrays.copyOf(resultSet, resultSet.length + 1); result[resultSet.length] = data; return result; } } }