kopia lustrzana https://github.com/ryukoposting/Signal-Android
Add shake-to-report for internal users.
rodzic
7ef77bf16c
commit
61c5fc1057
|
@ -17,6 +17,7 @@
|
||||||
package org.thoughtcrime.securesms;
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.hardware.SensorManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
@ -32,6 +33,7 @@ import com.google.android.gms.security.ProviderInstaller;
|
||||||
|
|
||||||
import org.conscrypt.Conscrypt;
|
import org.conscrypt.Conscrypt;
|
||||||
import org.signal.aesgcmprovider.AesGcmProvider;
|
import org.signal.aesgcmprovider.AesGcmProvider;
|
||||||
|
import org.signal.core.util.ShakeDetector;
|
||||||
import org.signal.core.util.concurrent.SignalExecutors;
|
import org.signal.core.util.concurrent.SignalExecutors;
|
||||||
import org.signal.core.util.logging.AndroidLogger;
|
import org.signal.core.util.logging.AndroidLogger;
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
|
@ -69,6 +71,7 @@ import org.thoughtcrime.securesms.service.LocalBackupListener;
|
||||||
import org.thoughtcrime.securesms.service.RotateSenderCertificateListener;
|
import org.thoughtcrime.securesms.service.RotateSenderCertificateListener;
|
||||||
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
|
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
|
||||||
import org.thoughtcrime.securesms.service.UpdateApkRefreshListener;
|
import org.thoughtcrime.securesms.service.UpdateApkRefreshListener;
|
||||||
|
import org.thoughtcrime.securesms.shakereport.ShakeToReport;
|
||||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
||||||
import org.thoughtcrime.securesms.util.AppStartup;
|
import org.thoughtcrime.securesms.util.AppStartup;
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
|
@ -177,6 +180,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
||||||
executePendingContactSync();
|
executePendingContactSync();
|
||||||
KeyCachingService.onAppForegrounded(this);
|
KeyCachingService.onAppForegrounded(this);
|
||||||
ApplicationDependencies.getMegaphoneRepository().onAppForegrounded();
|
ApplicationDependencies.getMegaphoneRepository().onAppForegrounded();
|
||||||
|
ApplicationDependencies.getShakeToReport().enable();
|
||||||
checkBuildExpiration();
|
checkBuildExpiration();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -190,6 +194,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
||||||
KeyCachingService.onAppBackgrounded(this);
|
KeyCachingService.onAppBackgrounded(this);
|
||||||
ApplicationDependencies.getMessageNotifier().clearVisibleThread();
|
ApplicationDependencies.getMessageNotifier().clearVisibleThread();
|
||||||
ApplicationDependencies.getFrameRateTracker().end();
|
ApplicationDependencies.getFrameRateTracker().end();
|
||||||
|
ApplicationDependencies.getShakeToReport().disable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ExpiringMessageManager getExpiringMessageManager() {
|
public ExpiringMessageManager getExpiringMessageManager() {
|
||||||
|
|
|
@ -15,6 +15,7 @@ import androidx.core.app.ActivityCompat;
|
||||||
import androidx.core.app.ActivityOptionsCompat;
|
import androidx.core.app.ActivityOptionsCompat;
|
||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.util.ConfigurationUtil;
|
import org.thoughtcrime.securesms.util.ConfigurationUtil;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;
|
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;
|
||||||
|
@ -44,6 +45,7 @@ public abstract class BaseActivity extends AppCompatActivity {
|
||||||
@Override
|
@Override
|
||||||
protected void onStart() {
|
protected void onStart() {
|
||||||
logEvent("onStart()");
|
logEvent("onStart()");
|
||||||
|
ApplicationDependencies.getShakeToReport().registerActivity(this);
|
||||||
super.onStart();
|
super.onStart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||||
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
||||||
import org.thoughtcrime.securesms.recipients.LiveRecipientCache;
|
import org.thoughtcrime.securesms.recipients.LiveRecipientCache;
|
||||||
import org.thoughtcrime.securesms.service.TrimThreadsByDateManager;
|
import org.thoughtcrime.securesms.service.TrimThreadsByDateManager;
|
||||||
|
import org.thoughtcrime.securesms.shakereport.ShakeToReport;
|
||||||
import org.thoughtcrime.securesms.util.EarlyMessageCache;
|
import org.thoughtcrime.securesms.util.EarlyMessageCache;
|
||||||
import org.thoughtcrime.securesms.util.FrameRateTracker;
|
import org.thoughtcrime.securesms.util.FrameRateTracker;
|
||||||
import org.thoughtcrime.securesms.util.Hex;
|
import org.thoughtcrime.securesms.util.Hex;
|
||||||
|
@ -69,6 +70,7 @@ public class ApplicationDependencies {
|
||||||
private static volatile TypingStatusSender typingStatusSender;
|
private static volatile TypingStatusSender typingStatusSender;
|
||||||
private static volatile DatabaseObserver databaseObserver;
|
private static volatile DatabaseObserver databaseObserver;
|
||||||
private static volatile TrimThreadsByDateManager trimThreadsByDateManager;
|
private static volatile TrimThreadsByDateManager trimThreadsByDateManager;
|
||||||
|
private static volatile ShakeToReport shakeToReport;
|
||||||
|
|
||||||
@MainThread
|
@MainThread
|
||||||
public static void init(@NonNull Application application, @NonNull Provider provider) {
|
public static void init(@NonNull Application application, @NonNull Provider provider) {
|
||||||
|
@ -321,6 +323,18 @@ public class ApplicationDependencies {
|
||||||
return databaseObserver;
|
return databaseObserver;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static @NonNull ShakeToReport getShakeToReport() {
|
||||||
|
if (shakeToReport == null) {
|
||||||
|
synchronized (LOCK) {
|
||||||
|
if (shakeToReport == null) {
|
||||||
|
shakeToReport = provider.provideShakeToReport();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return shakeToReport;
|
||||||
|
}
|
||||||
|
|
||||||
public interface Provider {
|
public interface Provider {
|
||||||
@NonNull GroupsV2Operations provideGroupsV2Operations();
|
@NonNull GroupsV2Operations provideGroupsV2Operations();
|
||||||
@NonNull SignalServiceAccountManager provideSignalServiceAccountManager();
|
@NonNull SignalServiceAccountManager provideSignalServiceAccountManager();
|
||||||
|
@ -340,5 +354,6 @@ public class ApplicationDependencies {
|
||||||
@NonNull TypingStatusRepository provideTypingStatusRepository();
|
@NonNull TypingStatusRepository provideTypingStatusRepository();
|
||||||
@NonNull TypingStatusSender provideTypingStatusSender();
|
@NonNull TypingStatusSender provideTypingStatusSender();
|
||||||
@NonNull DatabaseObserver provideDatabaseObserver();
|
@NonNull DatabaseObserver provideDatabaseObserver();
|
||||||
|
@NonNull ShakeToReport provideShakeToReport();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,7 @@ import org.thoughtcrime.securesms.push.SecurityEventListener;
|
||||||
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
||||||
import org.thoughtcrime.securesms.recipients.LiveRecipientCache;
|
import org.thoughtcrime.securesms.recipients.LiveRecipientCache;
|
||||||
import org.thoughtcrime.securesms.service.TrimThreadsByDateManager;
|
import org.thoughtcrime.securesms.service.TrimThreadsByDateManager;
|
||||||
|
import org.thoughtcrime.securesms.shakereport.ShakeToReport;
|
||||||
import org.thoughtcrime.securesms.util.AlarmSleepTimer;
|
import org.thoughtcrime.securesms.util.AlarmSleepTimer;
|
||||||
import org.thoughtcrime.securesms.util.ByteUnit;
|
import org.thoughtcrime.securesms.util.ByteUnit;
|
||||||
import org.thoughtcrime.securesms.util.EarlyMessageCache;
|
import org.thoughtcrime.securesms.util.EarlyMessageCache;
|
||||||
|
@ -201,6 +202,11 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr
|
||||||
return new DatabaseObserver(context);
|
return new DatabaseObserver(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull ShakeToReport provideShakeToReport() {
|
||||||
|
return new ShakeToReport(context);
|
||||||
|
}
|
||||||
|
|
||||||
private static class DynamicCredentialsProvider implements CredentialsProvider {
|
private static class DynamicCredentialsProvider implements CredentialsProvider {
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
|
|
@ -0,0 +1,138 @@
|
||||||
|
package org.thoughtcrime.securesms.shakereport;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.Application;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
|
import org.signal.core.util.ShakeDetector;
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
|
import org.signal.core.util.tracing.Tracer;
|
||||||
|
import org.thoughtcrime.securesms.ApplicationContext;
|
||||||
|
import org.thoughtcrime.securesms.R;
|
||||||
|
import org.thoughtcrime.securesms.logsubmit.SubmitDebugLogRepository;
|
||||||
|
import org.thoughtcrime.securesms.sharing.ShareIntents;
|
||||||
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
|
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class that will detect a shake and then prompts the user to submit a debuglog. Basically a
|
||||||
|
* shortcut to submit a debuglog from anywhere.
|
||||||
|
*/
|
||||||
|
public final class ShakeToReport implements ShakeDetector.Listener {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(ShakeToReport.class);
|
||||||
|
|
||||||
|
private final Application application;
|
||||||
|
private final ShakeDetector detector;
|
||||||
|
|
||||||
|
private WeakReference<Activity> weakActivity;
|
||||||
|
|
||||||
|
public ShakeToReport(@NonNull Application application) {
|
||||||
|
this.application = application;
|
||||||
|
this.detector = new ShakeDetector(this);
|
||||||
|
this.weakActivity = new WeakReference<>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void enable() {
|
||||||
|
if (!FeatureFlags.internalUser()) return;
|
||||||
|
|
||||||
|
detector.start(ServiceUtil.getSensorManager(application));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disable() {
|
||||||
|
if (!FeatureFlags.internalUser()) return;
|
||||||
|
|
||||||
|
detector.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerActivity(@NonNull Activity activity) {
|
||||||
|
if (!FeatureFlags.internalUser()) return;
|
||||||
|
|
||||||
|
this.weakActivity = new WeakReference<>(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onShakeDetected() {
|
||||||
|
Activity activity = weakActivity.get();
|
||||||
|
if (activity == null) {
|
||||||
|
Log.w(TAG, "No registered activity!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
disable();
|
||||||
|
|
||||||
|
new AlertDialog.Builder(activity)
|
||||||
|
.setTitle(R.string.ShakeToReport_shake_detected)
|
||||||
|
.setMessage(R.string.ShakeToReport_submit_debug_log)
|
||||||
|
.setNegativeButton(android.R.string.cancel, (d, i) -> {
|
||||||
|
d.dismiss();
|
||||||
|
enableIfVisible();
|
||||||
|
})
|
||||||
|
.setPositiveButton(R.string.ShakeToReport_submit, (d, i) -> {
|
||||||
|
d.dismiss();
|
||||||
|
submitLog(activity);
|
||||||
|
})
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void submitLog(@NonNull Activity activity) {
|
||||||
|
AlertDialog spinner = SimpleProgressDialog.show(activity);
|
||||||
|
SubmitDebugLogRepository repo = new SubmitDebugLogRepository();
|
||||||
|
|
||||||
|
Log.i(TAG, "Submitting log...");
|
||||||
|
|
||||||
|
repo.getLogLines(lines -> {
|
||||||
|
Log.i(TAG, "Retrieved log lines...");
|
||||||
|
|
||||||
|
repo.submitLog(lines, Tracer.getInstance().serialize(), url -> {
|
||||||
|
Log.i(TAG, "Logs uploaded!");
|
||||||
|
|
||||||
|
Util.runOnMain(() -> {
|
||||||
|
spinner.dismiss();
|
||||||
|
|
||||||
|
if (url.isPresent()) {
|
||||||
|
showPostSubmitDialog(activity, url.get());
|
||||||
|
} else {
|
||||||
|
Toast.makeText(activity, R.string.ShakeToReport_failed_to_submit, Toast.LENGTH_SHORT).show();
|
||||||
|
enableIfVisible();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showPostSubmitDialog(@NonNull Activity activity, @NonNull String url) {
|
||||||
|
AlertDialog dialog = new AlertDialog.Builder(activity)
|
||||||
|
.setTitle(R.string.ShakeToReport_success)
|
||||||
|
.setMessage(url)
|
||||||
|
.setNegativeButton(android.R.string.cancel, (d, i) -> {
|
||||||
|
d.dismiss();
|
||||||
|
enableIfVisible();
|
||||||
|
})
|
||||||
|
.setPositiveButton(R.string.ShakeToReport_share, (d, i) -> {
|
||||||
|
d.dismiss();
|
||||||
|
enableIfVisible();
|
||||||
|
|
||||||
|
activity.startActivity(new ShareIntents.Builder(activity)
|
||||||
|
.setText(url)
|
||||||
|
.build());
|
||||||
|
})
|
||||||
|
.show();
|
||||||
|
|
||||||
|
((TextView) dialog.findViewById(android.R.id.message)).setTextIsSelectable(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enableIfVisible() {
|
||||||
|
if (ApplicationContext.getInstance(application).isAppVisible()) {
|
||||||
|
enable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -41,8 +41,8 @@ public final class ShareIntents {
|
||||||
@Nullable StickerLocator extraSticker,
|
@Nullable StickerLocator extraSticker,
|
||||||
boolean isBorderless)
|
boolean isBorderless)
|
||||||
{
|
{
|
||||||
this.extraText = extraText;
|
this.extraText = extraText;
|
||||||
this.extraMedia = extraMedia;
|
this.extraMedia = extraMedia;
|
||||||
this.extraSticker = extraSticker;
|
this.extraSticker = extraSticker;
|
||||||
this.isBorderless = isBorderless;
|
this.isBorderless = isBorderless;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1518,6 +1518,14 @@
|
||||||
<string name="SearchFragment_header_contacts">Contacts</string>
|
<string name="SearchFragment_header_contacts">Contacts</string>
|
||||||
<string name="SearchFragment_header_messages">Messages</string>
|
<string name="SearchFragment_header_messages">Messages</string>
|
||||||
|
|
||||||
|
<!-- ShakeToReport -->
|
||||||
|
<string name="ShakeToReport_shake_detected" translatable="false">Shake detected</string>
|
||||||
|
<string name="ShakeToReport_submit_debug_log" translatable="false">Submit debug log?</string>
|
||||||
|
<string name="ShakeToReport_submit" translatable="false">Submit</string>
|
||||||
|
<string name="ShakeToReport_failed_to_submit" translatable="false">Failed to submit :(</string>
|
||||||
|
<string name="ShakeToReport_success" translatable="false">Success!</string>
|
||||||
|
<string name="ShakeToReport_share" translatable="false">Share</string>
|
||||||
|
|
||||||
<!-- SharedContactDetailsActivity -->
|
<!-- SharedContactDetailsActivity -->
|
||||||
<string name="SharedContactDetailsActivity_add_to_contacts">Add to Contacts</string>
|
<string name="SharedContactDetailsActivity_add_to_contacts">Add to Contacts</string>
|
||||||
<string name="SharedContactDetailsActivity_invite_to_signal">Invite to Signal</string>
|
<string name="SharedContactDetailsActivity_invite_to_signal">Invite to Signal</string>
|
||||||
|
|
|
@ -0,0 +1,238 @@
|
||||||
|
// Copyright 2010 Square, Inc.
|
||||||
|
// Modified 2020 Signal
|
||||||
|
|
||||||
|
package org.signal.core.util;
|
||||||
|
|
||||||
|
import android.hardware.Sensor;
|
||||||
|
import android.hardware.SensorEvent;
|
||||||
|
import android.hardware.SensorEventListener;
|
||||||
|
import android.hardware.SensorManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detects phone shaking. If more than 75% of the samples taken in the past 0.5s are
|
||||||
|
* accelerating, the device is a) shaking, or b) free falling 1.84m (h =
|
||||||
|
* 1/2*g*t^2*3/4).
|
||||||
|
*
|
||||||
|
* @author Bob Lee (bob@squareup.com)
|
||||||
|
* @author Eric Burke (eric@squareup.com)
|
||||||
|
*/
|
||||||
|
public class ShakeDetector implements SensorEventListener {
|
||||||
|
|
||||||
|
private static final int SHAKE_THRESHOLD = 13;
|
||||||
|
|
||||||
|
/** Listens for shakes. */
|
||||||
|
public interface Listener {
|
||||||
|
/** Called on the main thread when the device is shaken. */
|
||||||
|
void onShakeDetected();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final SampleQueue queue = new SampleQueue();
|
||||||
|
private final Listener listener;
|
||||||
|
|
||||||
|
private SensorManager sensorManager;
|
||||||
|
private Sensor accelerometer;
|
||||||
|
|
||||||
|
public ShakeDetector(Listener listener) {
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts listening for shakes on devices with appropriate hardware.
|
||||||
|
*
|
||||||
|
* @return true if the device supports shake detection.
|
||||||
|
*/
|
||||||
|
public boolean start(SensorManager sensorManager) {
|
||||||
|
if (accelerometer != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
|
||||||
|
|
||||||
|
if (accelerometer != null) {
|
||||||
|
this.sensorManager = sensorManager;
|
||||||
|
sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_FASTEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
return accelerometer != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops listening. Safe to call when already stopped. Ignored on devices without appropriate
|
||||||
|
* hardware.
|
||||||
|
*/
|
||||||
|
public void stop() {
|
||||||
|
if (accelerometer != null) {
|
||||||
|
queue.clear();
|
||||||
|
sensorManager.unregisterListener(this, accelerometer);
|
||||||
|
sensorManager = null;
|
||||||
|
accelerometer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSensorChanged(SensorEvent event) {
|
||||||
|
boolean accelerating = isAccelerating(event);
|
||||||
|
long timestamp = event.timestamp;
|
||||||
|
|
||||||
|
queue.add(timestamp, accelerating);
|
||||||
|
|
||||||
|
if (queue.isShaking()) {
|
||||||
|
queue.clear();
|
||||||
|
listener.onShakeDetected();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns true if the device is currently accelerating. */
|
||||||
|
private boolean isAccelerating(SensorEvent event) {
|
||||||
|
float ax = event.values[0];
|
||||||
|
float ay = event.values[1];
|
||||||
|
float az = event.values[2];
|
||||||
|
|
||||||
|
// Instead of comparing magnitude to ACCELERATION_THRESHOLD,
|
||||||
|
// compare their squares. This is equivalent and doesn't need the
|
||||||
|
// actual magnitude, which would be computed using (expensive) Math.sqrt().
|
||||||
|
final double magnitudeSquared = ax * ax + ay * ay + az * az;
|
||||||
|
|
||||||
|
return magnitudeSquared > SHAKE_THRESHOLD * SHAKE_THRESHOLD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Queue of samples. Keeps a running average. */
|
||||||
|
static class SampleQueue {
|
||||||
|
|
||||||
|
/** Window size in ns. Used to compute the average. */
|
||||||
|
private static final long MAX_WINDOW_SIZE = 500000000; // 0.5s
|
||||||
|
private static final long MIN_WINDOW_SIZE = MAX_WINDOW_SIZE >> 1; // 0.25s
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure the queue size never falls below this size, even if the device
|
||||||
|
* fails to deliver this many events during the time window. The LG Ally
|
||||||
|
* is one such device.
|
||||||
|
*/
|
||||||
|
private static final int MIN_QUEUE_SIZE = 4;
|
||||||
|
|
||||||
|
private final SamplePool pool = new SamplePool();
|
||||||
|
|
||||||
|
private Sample oldest;
|
||||||
|
private Sample newest;
|
||||||
|
private int sampleCount;
|
||||||
|
private int acceleratingCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a sample.
|
||||||
|
*
|
||||||
|
* @param timestamp in nanoseconds of sample
|
||||||
|
* @param accelerating true if > {@link #SHAKE_THRESHOLD}.
|
||||||
|
*/
|
||||||
|
void add(long timestamp, boolean accelerating) {
|
||||||
|
purge(timestamp - MAX_WINDOW_SIZE);
|
||||||
|
|
||||||
|
Sample added = pool.acquire();
|
||||||
|
|
||||||
|
added.timestamp = timestamp;
|
||||||
|
added.accelerating = accelerating;
|
||||||
|
added.next = null;
|
||||||
|
|
||||||
|
if (newest != null) {
|
||||||
|
newest.next = added;
|
||||||
|
}
|
||||||
|
|
||||||
|
newest = added;
|
||||||
|
|
||||||
|
if (oldest == null) {
|
||||||
|
oldest = added;
|
||||||
|
}
|
||||||
|
|
||||||
|
sampleCount++;
|
||||||
|
|
||||||
|
if (accelerating) {
|
||||||
|
acceleratingCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Removes all samples from this queue. */
|
||||||
|
void clear() {
|
||||||
|
while (oldest != null) {
|
||||||
|
Sample removed = oldest;
|
||||||
|
oldest = removed.next;
|
||||||
|
pool.release(removed);
|
||||||
|
}
|
||||||
|
|
||||||
|
newest = null;
|
||||||
|
sampleCount = 0;
|
||||||
|
acceleratingCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Purges samples with timestamps older than cutoff. */
|
||||||
|
void purge(long cutoff) {
|
||||||
|
while (sampleCount >= MIN_QUEUE_SIZE && oldest != null && cutoff - oldest.timestamp > 0) {
|
||||||
|
Sample removed = oldest;
|
||||||
|
|
||||||
|
if (removed.accelerating) {
|
||||||
|
acceleratingCount--;
|
||||||
|
}
|
||||||
|
|
||||||
|
sampleCount--;
|
||||||
|
|
||||||
|
oldest = removed.next;
|
||||||
|
|
||||||
|
if (oldest == null) {
|
||||||
|
newest = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pool.release(removed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if we have enough samples and more than 3/4 of those samples
|
||||||
|
* are accelerating.
|
||||||
|
*/
|
||||||
|
boolean isShaking() {
|
||||||
|
return newest != null &&
|
||||||
|
oldest != null &&
|
||||||
|
newest.timestamp - oldest.timestamp >= MIN_WINDOW_SIZE &&
|
||||||
|
acceleratingCount >= (sampleCount >> 1) + (sampleCount >> 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** An accelerometer sample. */
|
||||||
|
static class Sample {
|
||||||
|
/** Time sample was taken. */
|
||||||
|
long timestamp;
|
||||||
|
|
||||||
|
/** If acceleration > {@link #SHAKE_THRESHOLD}. */
|
||||||
|
boolean accelerating;
|
||||||
|
|
||||||
|
/** Next sample in the queue or pool. */
|
||||||
|
Sample next;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pools samples. Avoids garbage collection. */
|
||||||
|
static class SamplePool {
|
||||||
|
private Sample head;
|
||||||
|
|
||||||
|
/** Acquires a sample from the pool. */
|
||||||
|
Sample acquire() {
|
||||||
|
Sample acquired = head;
|
||||||
|
|
||||||
|
if (acquired == null) {
|
||||||
|
acquired = new Sample();
|
||||||
|
} else {
|
||||||
|
head = acquired.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return acquired;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a sample to the pool. */
|
||||||
|
void release(Sample sample) {
|
||||||
|
sample.next = head;
|
||||||
|
head = sample;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAccuracyChanged(Sensor sensor, int accuracy) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Ładowanie…
Reference in New Issue