diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 279ecb5..5ad723f 100755 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -6,6 +6,7 @@ + App Usage Data Permission Required This app requires access to app usage data from your device\'s settings. + + Battery Optimization Enabled + This app uses device features that do not function consistently while the app is subject to battery optimizations. Please grant a battery optimization exemption to ensure full and consistent functioning. diff --git a/src/com/audacious_software/passive_data_kit/generators/Generators.java b/src/com/audacious_software/passive_data_kit/generators/Generators.java index a90f321..b98ad43 100755 --- a/src/com/audacious_software/passive_data_kit/generators/Generators.java +++ b/src/com/audacious_software/passive_data_kit/generators/Generators.java @@ -5,6 +5,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Bundle; +import android.os.PowerManager; import android.preference.PreferenceManager; import android.util.Log; import android.util.SparseArray; @@ -32,6 +33,7 @@ public class Generators { private HashMap> mGeneratorMap = new HashMap<>(); private SparseArray> mViewTypeMap = new SparseArray<>(); private HashSet mGeneratorUpdatedListeners = new HashSet<>(); + private HashMap mWakeLocks = new HashMap<>(); public void start() { if (!this.mStarted) @@ -257,6 +259,28 @@ public void notifyGeneratorUpdated(String identifier, Bundle bundle) { } } + public void acquireWakeLock(String tag, int lockType) { + this.releaseWakeLock(tag); + + PowerManager power = (PowerManager) this.mContext.getSystemService(Context.POWER_SERVICE); + + PowerManager.WakeLock lock = power.newWakeLock(lockType, tag); + + this.mWakeLocks.put(tag, lock); + } + + public void releaseWakeLock(String tag) { + if (this.mWakeLocks.containsKey(tag)) { + PowerManager.WakeLock lock = this.mWakeLocks.get(tag); + + if (lock.isHeld()) { + lock.release(); + } + + this.mWakeLocks.remove(tag); + } + } + private static class GeneratorsHolder { public static Generators instance = new Generators(); } diff --git a/src/com/audacious_software/passive_data_kit/generators/device/ForegroundApplication.java b/src/com/audacious_software/passive_data_kit/generators/device/ForegroundApplication.java index c664a3a..85c58de 100755 --- a/src/com/audacious_software/passive_data_kit/generators/device/ForegroundApplication.java +++ b/src/com/audacious_software/passive_data_kit/generators/device/ForegroundApplication.java @@ -180,7 +180,9 @@ public static ArrayList diagnostics(final Context context) { actions.add(new DiagnosticAction(context.getString(R.string.diagnostic_usage_stats_permission_required_title), context.getString(R.string.diagnostic_usage_stats_permission_required), new Runnable() { @Override public void run() { - context.startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS)); + Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); } })); } diff --git a/src/com/audacious_software/passive_data_kit/generators/sensors/Accelerometer.java b/src/com/audacious_software/passive_data_kit/generators/sensors/Accelerometer.java index 823e776..d15e5e8 100755 --- a/src/com/audacious_software/passive_data_kit/generators/sensors/Accelerometer.java +++ b/src/com/audacious_software/passive_data_kit/generators/sensors/Accelerometer.java @@ -3,6 +3,7 @@ import android.app.Activity; import android.content.ContentValues; import android.content.Context; +import android.content.Intent; import android.content.SharedPreferences; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; @@ -14,8 +15,10 @@ import android.os.Bundle; import android.os.Handler; import android.os.Looper; +import android.os.PowerManager; +import android.preference.PreferenceManager; +import android.provider.Settings; import android.support.v4.content.ContextCompat; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -52,6 +55,15 @@ public class Accelerometer extends SensorGenerator implements SensorEventListene private static final String ENABLED = "com.audacious_software.passive_data_kit.generators.device.Accelerometer.ENABLED"; private static final boolean ENABLED_DEFAULT = true; + private static final String IGNORE_POWER_MANAGEMENT = "com.audacious_software.passive_data_kit.generators.sensors.Accelerometer.IGNORE_POWER_MANAGEMENT"; + private static final boolean IGNORE_POWER_MANAGEMENT_DEFAULT = true; + + private static final String REFRESH_INTERVAL = "com.audacious_software.passive_data_kit.generators.sensors.Accelerometer.REFRESH_INTERVAL"; + private static final long REFRESH_INTERVAL_DEFAULT = 0; + + private static final String REFRESH_DURATION = "com.audacious_software.passive_data_kit.generators.sensors.Accelerometer.REFRESH_DURATION"; + private static final long REFRESH_DURATION_DEFAULT = (5 * 60 * 1000); + private static final String DATABASE_PATH = "pdk-sensor-accelerometer.sqlite"; private static final int DATABASE_VERSION = 1; @@ -93,6 +105,7 @@ public class Accelerometer extends SensorGenerator implements SensorEventListene long mBaseTimestamp = 0; private long mLatestTimestamp = 0; + private Thread mIntervalThread = null; public static Accelerometer getInstance(Context context) { if (Accelerometer.sInstance == null) { @@ -110,6 +123,39 @@ public static void start(final Context context) { Accelerometer.getInstance(context).startGenerator(); } + public void setIgnorePowerManagement(boolean ignore) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.mContext); + SharedPreferences.Editor e = prefs.edit(); + + e.putBoolean(Accelerometer.IGNORE_POWER_MANAGEMENT, ignore); + e.apply(); + + this.stopGenerator(); + this.startGenerator(); + } + + public void setRefreshInterval(long interval) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.mContext); + SharedPreferences.Editor e = prefs.edit(); + + e.putLong(Accelerometer.REFRESH_INTERVAL, interval); + e.apply(); + + this.stopGenerator(); + this.startGenerator(); + } + + public void setRefreshDuration(long duration) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.mContext); + SharedPreferences.Editor e = prefs.edit(); + + e.putLong(Accelerometer.REFRESH_DURATION, duration); + e.apply(); + + this.stopGenerator(); + this.startGenerator(); + } + private void startGenerator() { final SensorManager sensors = (SensorManager) this.mContext.getSystemService(Context.SENSOR_SERVICE); @@ -158,36 +204,95 @@ public void run() else sensors.registerListener(me, me.mSensor, SensorManager.SENSOR_DELAY_NORMAL, Accelerometer.sHandler); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(me.mContext); + + final long refreshInterval = prefs.getLong(Accelerometer.REFRESH_INTERVAL, Accelerometer.REFRESH_INTERVAL_DEFAULT); + + if (refreshInterval > 0) { + final long refreshDuration = prefs.getLong(Accelerometer.REFRESH_DURATION, Accelerometer.REFRESH_DURATION_DEFAULT); + + if (me.mIntervalThread != null) { + me.mIntervalThread.interrupt(); + me.mIntervalThread = null; + } + + Runnable managerRunnable = new Runnable() { + @Override + public void run() { + try { + while (true) { + Thread.sleep(refreshDuration); + + sensors.unregisterListener(me, me.mSensor); + + Thread.sleep(refreshInterval - refreshDuration); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) + sensors.registerListener(me, me.mSensor, SensorManager.SENSOR_DELAY_NORMAL, 0, Accelerometer.sHandler); + else + sensors.registerListener(me, me.mSensor, SensorManager.SENSOR_DELAY_NORMAL, Accelerometer.sHandler); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }; + + me.mIntervalThread = new Thread(managerRunnable, "accelerometer-interval"); + me.mIntervalThread.start(); + } + Looper.loop(); } }; Thread t = new Thread(r, "accelerometer"); t.start(); + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(me.mContext); + + if (prefs.getBoolean(Accelerometer.IGNORE_POWER_MANAGEMENT, Accelerometer.IGNORE_POWER_MANAGEMENT_DEFAULT)) { + Generators.getInstance(this.mContext).acquireWakeLock(Accelerometer.IDENTIFIER, PowerManager.PARTIAL_WAKE_LOCK); + } else { + Generators.getInstance(this.mContext).releaseWakeLock(Accelerometer.IDENTIFIER); + } } else { - if (this.mSensor != null) { - sensors.unregisterListener(this, this.mSensor); + this.stopGenerator(); + } + } - if (Accelerometer.sHandler != null) { - Looper loop = Accelerometer.sHandler.getLooper(); - loop.quit(); + private void stopGenerator() { + final SensorManager sensors = (SensorManager) this.mContext.getSystemService(Context.SENSOR_SERVICE); - Accelerometer.sHandler = null; - } + if (this.mSensor != null) { + if (this.mIntervalThread != null) { + this.mIntervalThread.interrupt(); + this.mIntervalThread = null; + } - me.mXValueBuffers = null; - me.mYValueBuffers = null; - me.mZValueBuffers = null; - me.mAccuracyBuffers = null; - me.mRawTimestampBuffers = null; - me.mTimestampBuffers = null; + sensors.unregisterListener(this, this.mSensor); - me.mActiveBuffersIndex = 0; - me.mCurrentBufferIndex = 0; + if (Accelerometer.sHandler != null) { + Looper loop = Accelerometer.sHandler.getLooper(); + loop.quit(); - this.mSensor = null; + Accelerometer.sHandler = null; } + + this.mXValueBuffers = null; + this.mYValueBuffers = null; + this.mZValueBuffers = null; + this.mAccuracyBuffers = null; + this.mRawTimestampBuffers = null; + this.mTimestampBuffers = null; + + this.mActiveBuffersIndex = 0; + this.mCurrentBufferIndex = 0; + + this.mSensor = null; } + + Generators.getInstance(this.mContext).releaseWakeLock(Accelerometer.IDENTIFIER); } public static boolean isEnabled(Context context) { @@ -204,8 +309,30 @@ public static boolean isRunning(Context context) { return Accelerometer.sInstance.mSensor != null; } - public static ArrayList diagnostics(Context context) { - return new ArrayList<>(); + public static ArrayList diagnostics(final Context context) { + ArrayList actions = new ArrayList<>(); + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + + if (prefs.getBoolean(Accelerometer.IGNORE_POWER_MANAGEMENT, Accelerometer.IGNORE_POWER_MANAGEMENT_DEFAULT)) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + PowerManager power = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + + if (power.isIgnoringBatteryOptimizations(context.getPackageName()) == false) { + actions.add(new DiagnosticAction(context.getString(R.string.diagnostic_battery_optimization_exempt_title), context.getString(R.string.diagnostic_battery_optimization_exempt), new Runnable() { + + @Override + public void run() { + Intent intent = new Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } + })); + } + } + } + + return actions; } public static void bindViewHolder(final DataPointViewHolder holder) { @@ -483,12 +610,9 @@ public String getFormattedValue(float value, AxisBase axis) { dateLabel.setText(R.string.label_never_pdk); } - - Log.e("PDK", "MAIN DONE: " + (System.currentTimeMillis() - drawStart)); } - public static View fetchView(ViewGroup parent) - { + public static View fetchView(ViewGroup parent) { return LayoutInflater.from(parent.getContext()).inflate(R.layout.card_generator_sensors_accelerometer, parent, false); } @@ -539,8 +663,6 @@ public void onSensorChanged(SensorEvent sensorEvent) { } } -// Log.e("PDK", "ACCEL[" + this.mCurrentBufferIndex + "/" + this.mActiveBuffersIndex + "] = " + sensorEvent.values[0] + " -- " + sensorEvent.values[1] + " -- " + sensorEvent.values[2]); - this.mXValueBuffers[this.mActiveBuffersIndex][this.mCurrentBufferIndex] = sensorEvent.values[0]; this.mYValueBuffers[this.mActiveBuffersIndex][this.mCurrentBufferIndex] = sensorEvent.values[1]; this.mZValueBuffers[this.mActiveBuffersIndex][this.mCurrentBufferIndex] = sensorEvent.values[2]; @@ -602,7 +724,7 @@ public void run() { if (now - me.mLastCleanup > me.mCleanupInterval) { me.mLastCleanup = now; - long start = (now - (2 * 24 * 60 * 60 * 1000)) * 1000 * 1000; + long start = (now - (24 * 60 * 60 * 1000)) * 1000 * 1000; String where = Accelerometer.HISTORY_OBSERVED + " < ?"; String[] args = { "" + start }; diff --git a/src/com/audacious_software/passive_data_kit/generators/sensors/AmbientLight.java b/src/com/audacious_software/passive_data_kit/generators/sensors/AmbientLight.java index eee9a3b..145cf6e 100755 --- a/src/com/audacious_software/passive_data_kit/generators/sensors/AmbientLight.java +++ b/src/com/audacious_software/passive_data_kit/generators/sensors/AmbientLight.java @@ -3,6 +3,7 @@ import android.app.Activity; import android.content.ContentValues; import android.content.Context; +import android.content.Intent; import android.content.SharedPreferences; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; @@ -14,6 +15,9 @@ import android.os.Bundle; import android.os.Handler; import android.os.Looper; +import android.os.PowerManager; +import android.preference.PreferenceManager; +import android.provider.Settings; import android.support.v4.content.ContextCompat; import android.util.Log; import android.view.LayoutInflater; @@ -52,6 +56,9 @@ public class AmbientLight extends SensorGenerator implements SensorEventListener private static final String ENABLED = "com.audacious_software.passive_data_kit.generators.device.AmbientLight.ENABLED"; private static final boolean ENABLED_DEFAULT = true; + private static final String IGNORE_POWER_MANAGEMENT = "com.audacious_software.passive_data_kit.generators.sensors.AmbientLight.IGNORE_POWER_MANAGEMENT"; + private static final boolean IGNORE_POWER_MANAGEMENT_DEFAULT = true; + private static final String DATABASE_PATH = "pdk-sensor-ambient-light.sqlite"; private static final int DATABASE_VERSION = 1; @@ -100,6 +107,17 @@ public AmbientLight(Context context) { super(context); } + public void setIgnorePowerManagement(boolean ignore) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.mContext); + SharedPreferences.Editor e = prefs.edit(); + + e.putBoolean(AmbientLight.IGNORE_POWER_MANAGEMENT, ignore); + e.apply(); + + this.stopGenerator(); + this.startGenerator(); + } + public static void start(final Context context) { AmbientLight.getInstance(context).startGenerator(); } @@ -156,28 +174,44 @@ public void run() Thread t = new Thread(r, "ambient-light"); t.start(); - } else { - if (this.mSensor != null) { - sensors.unregisterListener(this, this.mSensor); - if (AmbientLight.sHandler != null) { - Looper loop = AmbientLight.sHandler.getLooper(); - loop.quit(); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.mContext); - AmbientLight.sHandler = null; - } + if (prefs.getBoolean(AmbientLight.IGNORE_POWER_MANAGEMENT, AmbientLight.IGNORE_POWER_MANAGEMENT_DEFAULT)) { + Generators.getInstance(this.mContext).acquireWakeLock(Accelerometer.IDENTIFIER, PowerManager.PARTIAL_WAKE_LOCK); + } else { + Generators.getInstance(this.mContext).releaseWakeLock(Accelerometer.IDENTIFIER); + } + } else { + this.stopGenerator(); + } + } - me.mValueBuffers = null; - me.mAccuracyBuffers = null; - me.mRawTimestampBuffers = null; - me.mTimestampBuffers = null; + private void stopGenerator() { + final SensorManager sensors = (SensorManager) this.mContext.getSystemService(Context.SENSOR_SERVICE); - me.mActiveBuffersIndex = 0; - me.mCurrentBufferIndex = 0; + if (this.mSensor != null) { + sensors.unregisterListener(this, this.mSensor); - this.mSensor = null; + if (AmbientLight.sHandler != null) { + Looper loop = AmbientLight.sHandler.getLooper(); + loop.quit(); + + AmbientLight.sHandler = null; } + + this.mValueBuffers = null; + this.mAccuracyBuffers = null; + this.mRawTimestampBuffers = null; + this.mTimestampBuffers = null; + + this.mActiveBuffersIndex = 0; + this.mCurrentBufferIndex = 0; + + this.mSensor = null; } + + Generators.getInstance(this.mContext).releaseWakeLock(AmbientLight.IDENTIFIER); } public static boolean isEnabled(Context context) { @@ -194,8 +228,30 @@ public static boolean isRunning(Context context) { return AmbientLight.sInstance.mSensor != null; } - public static ArrayList diagnostics(Context context) { - return new ArrayList<>(); + public static ArrayList diagnostics(final Context context) { + ArrayList actions = new ArrayList<>(); + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + + if (prefs.getBoolean(AmbientLight.IGNORE_POWER_MANAGEMENT, AmbientLight.IGNORE_POWER_MANAGEMENT_DEFAULT)) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + PowerManager power = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + + if (power.isIgnoringBatteryOptimizations(context.getPackageName()) == false) { + actions.add(new DiagnosticAction(context.getString(R.string.diagnostic_battery_optimization_exempt_title), context.getString(R.string.diagnostic_battery_optimization_exempt), new Runnable() { + + @Override + public void run() { + Intent intent = new Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } + })); + } + } + } + + return actions; } public static void bindViewHolder(final DataPointViewHolder holder) { @@ -508,7 +564,7 @@ public void run() { if (now - me.mLastCleanup > me.mCleanupInterval) { me.mLastCleanup = now; - long start = (now - (2 * 24 * 60 * 60 * 1000)) * 1000 * 1000; + long start = (now - (24 * 60 * 60 * 1000)) * 1000 * 1000; String where = AmbientLight.HISTORY_OBSERVED + " < ?"; String[] args = { "" + start }; diff --git a/src/com/audacious_software/passive_data_kit/generators/wearables/WithingsDevice.java b/src/com/audacious_software/passive_data_kit/generators/wearables/WithingsDevice.java index b56eeb9..0d09ce2 100755 --- a/src/com/audacious_software/passive_data_kit/generators/wearables/WithingsDevice.java +++ b/src/com/audacious_software/passive_data_kit/generators/wearables/WithingsDevice.java @@ -1126,6 +1126,7 @@ public void run() { builder.appendQueryParameter("oauth_signature", signature.trim()); Intent intent = new Intent(Intent.ACTION_VIEW, builder.build()); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } catch (NoSuchAlgorithmException e) { AppEvent.getInstance(context).logThrowable(e);