/* * Copyright (C) 2012 Pixmob (http://github.com/pixmob) * * 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 org.pixmob.freemobile.netstat.ui; import static org.pixmob.freemobile.netstat.BuildConfig.DEBUG; import static org.pixmob.freemobile.netstat.Constants.INTERVAL_ONE_MONTH; import static org.pixmob.freemobile.netstat.Constants.INTERVAL_ONE_WEEK; import static org.pixmob.freemobile.netstat.Constants.INTERVAL_TODAY; import static org.pixmob.freemobile.netstat.Constants.SP_KEY_TIME_INTERVAL; import static org.pixmob.freemobile.netstat.Constants.SP_NAME; import static org.pixmob.freemobile.netstat.Constants.TAG; import java.util.Calendar; import java.util.Date; import org.pixmob.freemobile.netstat.Event; import org.pixmob.freemobile.netstat.MobileOperator; import org.pixmob.freemobile.netstat.R; import org.pixmob.freemobile.netstat.content.NetstatContract.Events; import org.pixmob.freemobile.netstat.ui.StatisticsFragment.Statistics; import org.pixmob.freemobile.netstat.util.DateUtils; import android.app.Activity; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.database.ContentObserver; import android.database.Cursor; import android.os.Bundle; import android.os.Handler; import android.os.SystemClock; import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.content.AsyncTaskLoader; import android.support.v4.content.Loader; import android.telephony.TelephonyManager; import android.text.format.Time; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.ProgressBar; import android.widget.TextView; /** * Fragment showing statistics using charts. * @author Pixmob */ public class StatisticsFragment extends Fragment implements LoaderCallbacks<Statistics> { private static final String STAT_NO_VALUE = "-"; private static ExportTask exportTask; private ContentObserver contentMonitor; private View statisticsGroup; private ProgressBar progressBar; private MobileNetworkChart mobileNetworkChart; private BatteryChart batteryChart; private TextView onFreeMobileNetwork; private TextView onOrangeNetwork; private TextView statMobileNetwork; private TextView statMobileCode; private TextView statScreenOn; private TextView statWifiOn; private TextView statOnOrange; private TextView statOnFreeMobile; private TextView statBattery; @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); if (!isSimSupported(getActivity())) { new UnsupportedSimDialogFragment().show(getFragmentManager(), "error"); } if (exportTask != null) { exportTask.setFragmentManager(getFragmentManager()); } // Monitor database updates: when new data is available, this fragment // is updated with the new values. contentMonitor = new ContentObserver(new Handler()) { @Override public void onChange(boolean selfChange) { super.onChange(selfChange); Log.i(TAG, "Content updated: refresh statistics"); refresh(); } }; // Get widgets. final Activity a = getActivity(); statisticsGroup = a.findViewById(R.id.statistics); statisticsGroup.setVisibility(View.INVISIBLE); progressBar = (ProgressBar) a.findViewById(R.id.states_progress); mobileNetworkChart = (MobileNetworkChart) a.findViewById(R.id.mobile_network_chart); batteryChart = (BatteryChart) a.findViewById(R.id.battery_chart); onOrangeNetwork = (TextView) a.findViewById(R.id.on_orange_network); onFreeMobileNetwork = (TextView) a.findViewById(R.id.on_free_mobile_network); statMobileNetwork = (TextView) a.findViewById(R.id.stat_mobile_network); statMobileCode = (TextView) a.findViewById(R.id.stat_mobile_code); statScreenOn = (TextView) a.findViewById(R.id.stat_screen); statWifiOn = (TextView) a.findViewById(R.id.stat_wifi); statOnOrange = (TextView) a.findViewById(R.id.stat_on_orange); statOnFreeMobile = (TextView) a.findViewById(R.id.stat_on_free_mobile); statBattery = (TextView) a.findViewById(R.id.stat_battery); // The fields are hidden the first time this fragment is displayed, // while statistics data are being loaded. statisticsGroup.setVisibility(View.INVISIBLE); setHasOptionsMenu(true); getLoaderManager().initLoader(0, null, this); } /** * Check if the SIM card is supported. */ public static boolean isSimSupported(Context context) { if (!DEBUG) { final TelephonyManager tm = (TelephonyManager) context .getSystemService(Context.TELEPHONY_SERVICE); if (TelephonyManager.SIM_STATE_READY != tm.getSimState()) { return false; } return MobileOperator.FREE_MOBILE.equals(MobileOperator.fromString(tm.getSimOperator())); } return true; } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); inflater.inflate(R.menu.menu_statistics, menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_export: onMenuExport(); return true; case R.id.menu_preferences: onMenuPreferences(); return true; } return super.onOptionsItemSelected(item); } private void onMenuExport() { exportTask = new ExportTask(getActivity().getApplicationContext(), getFragmentManager()); exportTask.execute(); } private void onMenuPreferences() { startActivity(new Intent(getActivity(), Preferences.class)); } @Override public void onResume() { super.onResume(); // Monitor database updates if the fragment is displayed. final ContentResolver cr = getActivity().getContentResolver(); cr.registerContentObserver(Events.CONTENT_URI, true, contentMonitor); } @Override public void onPause() { super.onPause(); // Stop monitoring database updates. getActivity().getContentResolver().unregisterContentObserver(contentMonitor); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.statistics_fragment, container, false); } public void refresh() { if (isDetached()) { return; } if (getLoaderManager().hasRunningLoaders()) { if (DEBUG) { Log.d(TAG, "Skip statistics refresh: already running"); } return; } if (DEBUG) { Log.d(TAG, "Refresh statistics"); } getLoaderManager().restartLoader(0, null, this); } @Override public Loader<Statistics> onCreateLoader(int id, Bundle args) { progressBar.setVisibility(View.VISIBLE); return new StatisticsLoader(getActivity()); } @Override public void onLoaderReset(Loader<Statistics> loader) { } @Override public void onLoadFinished(Loader<Statistics> loader, Statistics s) { Log.i(TAG, "Statistics loaded: " + s); onOrangeNetwork.setText(s.orangeUsePercent + "%"); onFreeMobileNetwork.setText(s.freeMobileUsePercent + "%"); mobileNetworkChart.setData(s.orangeUsePercent, s.freeMobileUsePercent); final Activity a = getActivity(); statMobileNetwork.setText(s.mobileOperator == null ? STAT_NO_VALUE : s.mobileOperator.toName(a)); statMobileCode.setText(s.mobileOperatorCode == null ? STAT_NO_VALUE : s.mobileOperatorCode); setDurationText(statScreenOn, s.screenOnTime); setDurationText(statWifiOn, s.wifiOnTime); setDurationText(statOnOrange, s.orangeTime); setDurationText(statOnFreeMobile, s.freeMobileTime); statBattery.setText(s.battery == 0 ? STAT_NO_VALUE : String.valueOf(s.battery) + "%"); batteryChart.setData(s.events); progressBar.setVisibility(View.INVISIBLE); statisticsGroup.setVisibility(View.VISIBLE); statisticsGroup.invalidate(); batteryChart.invalidate(); } private void setDurationText(TextView tv, long duration) { if (duration < 1) { tv.setText(STAT_NO_VALUE); } else { tv.setText(formatDuration(duration)); } } /** * Return a formatted string for a duration value. */ private CharSequence formatDuration(long duration) { return DateUtils.formatDuration(duration, getActivity(), STAT_NO_VALUE); } /** * {@link Loader} implementation for loading events from the database, and * computing statistics. * @author Pixmob */ private static class StatisticsLoader extends AsyncTaskLoader<Statistics> { public StatisticsLoader(final Context context) { super(context); if (DEBUG) { Log.d(TAG, "New StatisticsLoader"); } } @Override protected void onStartLoading() { super.onStartLoading(); forceLoad(); if (DEBUG) { Log.d(TAG, "StatisticsLoader.onStartLoading()"); } } @Override public Statistics loadInBackground() { if (DEBUG) { Log.d(TAG, "StatisticsLoader.loadInBackground()"); } final long now = System.currentTimeMillis(); final SharedPreferences prefs = getContext().getSharedPreferences(SP_NAME, Context.MODE_PRIVATE); final int interval = prefs.getInt(SP_KEY_TIME_INTERVAL, 0); final long fromTimestamp; if (interval == INTERVAL_ONE_MONTH) { final Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(now); cal.add(Calendar.MONTH, -1); fromTimestamp = cal.getTimeInMillis(); } else if (interval == INTERVAL_ONE_WEEK) { final Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(now); cal.add(Calendar.DATE, -7); fromTimestamp = cal.getTimeInMillis(); } else if (interval == INTERVAL_TODAY) { // Get the date at midnight today. final Time t = new Time(); t.set(now); t.hour = 0; t.minute = 0; t.second = 0; fromTimestamp = t.toMillis(false); } else { fromTimestamp = now - SystemClock.elapsedRealtime(); } Log.i(TAG, "Loading statistics from " + new Date(fromTimestamp) + " to now"); final Statistics s = new Statistics(); final TelephonyManager tm = (TelephonyManager) getContext().getSystemService( Context.TELEPHONY_SERVICE); s.mobileOperatorCode = tm.getNetworkOperator(); s.mobileOperator = MobileOperator.fromString(s.mobileOperatorCode); if (s.mobileOperator == null) { s.mobileOperatorCode = null; } long connectionTimestamp = 0; Cursor c = null; try { c = getContext().getContentResolver().query( Events.CONTENT_URI, new String[] { Events.TIMESTAMP, Events.SCREEN_ON, Events.WIFI_CONNECTED, Events.MOBILE_CONNECTED, Events.MOBILE_OPERATOR, Events.BATTERY_LEVEL, Events.POWER_ON }, Events.TIMESTAMP + ">?", new String[] { String.valueOf(fromTimestamp) }, Events.TIMESTAMP + " ASC"); final int rowCount = c.getCount(); s.events = new Event[rowCount]; for (int i = 0; c.moveToNext(); ++i) { final Event e = new Event(); e.read(c); s.events[i] = e; if (i > 0) { final Event e0 = s.events[i - 1]; if (e.powerOn && !e0.powerOn) { continue; } final long dt = e.timestamp - e0.timestamp; final MobileOperator op = MobileOperator.fromString(e.mobileOperator); final MobileOperator op0 = MobileOperator.fromString(e0.mobileOperator); if (op != null && op.equals(op0)) { if (MobileOperator.ORANGE.equals(op)) { s.orangeTime += dt; } else if (MobileOperator.FREE_MOBILE.equals(op)) { s.freeMobileTime += dt; } } if (e.mobileConnected && !e0.mobileConnected) { connectionTimestamp = e.timestamp; } if (!e.mobileConnected) { connectionTimestamp = 0; } if (e.wifiConnected && e0.wifiConnected) { s.wifiOnTime += dt; } if (e.screenOn && e0.screenOn) { s.screenOnTime += dt; } } } if (s.events.length > 0) { s.battery = s.events[s.events.length - 1].batteryLevel; } final double sTime = s.orangeTime + s.freeMobileTime; s.freeMobileUsePercent = (int) Math.round(s.freeMobileTime / sTime * 100d); s.orangeUsePercent = 100 - s.freeMobileUsePercent; s.connectionTime = now - connectionTimestamp; } catch (Exception e) { Log.e(TAG, "Failed to load statistics", e); s.events = new Event[0]; } finally { try { if (c != null) { c.close(); } } catch (Exception ignore) { } } if (DEBUG) { final long end = System.currentTimeMillis(); Log.d(TAG, "Statistics loaded in " + (end - now) + " ms"); } return s; } } /** * Store statistics. * @author Pixmob */ public static class Statistics { public Event[] events = new Event[0]; public long orangeTime; public long freeMobileTime; public int orangeUsePercent; public int freeMobileUsePercent; public MobileOperator mobileOperator; public String mobileOperatorCode; public long connectionTime; public long screenOnTime; public long wifiOnTime; public int battery; @Override public String toString() { return "Statistics[events=" + events.length + "; orange=" + orangeUsePercent + "%; free=" + freeMobileUsePercent + "%]"; } } }