Skip to content

Commit

Permalink
Merge 3fb00f0 into 624de03
Browse files Browse the repository at this point in the history
  • Loading branch information
AnastasiaKubova committed Aug 1, 2019
2 parents 624de03 + 3fb00f0 commit 5a9ef5a
Show file tree
Hide file tree
Showing 2 changed files with 199 additions and 1 deletion.
Expand Up @@ -6,10 +6,13 @@
package com.microsoft.appcenter.crashes;

import android.annotation.SuppressLint;
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.res.Configuration;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.support.annotation.WorkerThread;

import com.microsoft.appcenter.AbstractAppCenterService;
import com.microsoft.appcenter.Constants;
Expand Down Expand Up @@ -54,6 +57,11 @@
import java.util.Map;
import java.util.UUID;

import static android.content.ComponentCallbacks2.TRIM_MEMORY_COMPLETE;
import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL;
import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE;

/**
* Crashes service.
*/
Expand All @@ -80,6 +88,12 @@ public class Crashes extends AbstractAppCenterService {
@VisibleForTesting
public static final String PREF_KEY_ALWAYS_SEND = "com.microsoft.appcenter.crashes.always.send";

/**
* Preference storage key for memory running level.
*/
@VisibleForTesting
public static final String PREF_KEY_MEMORY_RUNNING_LEVEL = "com.microsoft.appcenter.crashes.memory";

/**
* Group for sending logs.
*/
Expand Down Expand Up @@ -152,6 +166,11 @@ public class Crashes extends AbstractAppCenterService {
*/
private CrashesListener mCrashesListener;

/**
* Memory warning listener.
*/
private ComponentCallbacks2 mMemoryWarningListener;

/**
* ErrorReport for the last session.
*/
Expand All @@ -167,6 +186,11 @@ public class Crashes extends AbstractAppCenterService {
*/
private boolean mAutomaticProcessing = true;

/**
* Indicates if the app received a low memory warning in the last session.
*/
private boolean mHasReceivedMemoryWarningInLastSession = false;

/**
* Init.
*/
Expand Down Expand Up @@ -308,6 +332,16 @@ public static AppCenterFuture<ErrorReport> getLastSessionCrashReport() {
return getInstance().getInstanceLastSessionCrashReport();
}

/**
* Check whether there was a memory warning in the last session.
*
* @return future with result being <code>true</code> if memory running was critical, <code>false</code> otherwise.
* @see AppCenterFuture
*/
public static AppCenterFuture<Boolean> hasReceivedMemoryWarningInLastSession() {
return getInstance().hasInstanceReceivedMemoryWarningInLastSession();
}

/**
* Implements {@link #getMinidumpDirectory()} at instance level.
*/
Expand Down Expand Up @@ -338,6 +372,21 @@ public void run() {
return future;
}

/**
* Implements {@link #hasReceivedMemoryWarningInLastSession()} at instance level.
*/
private synchronized AppCenterFuture<Boolean> hasInstanceReceivedMemoryWarningInLastSession() {
final DefaultAppCenterFuture<Boolean> future = new DefaultAppCenterFuture<>();
postAsyncGetter(new Runnable() {

@Override
public void run() {
future.complete(mHasReceivedMemoryWarningInLastSession);
}
}, future, false);
return future;
}

/**
* Implements {@link #getLastSessionCrashReport()} at instance level.
*/
Expand All @@ -357,7 +406,9 @@ public void run() {
@Override
protected synchronized void applyEnabledState(boolean enabled) {
initialize();
if (!enabled) {
if (enabled) {
mContext.registerComponentCallbacks(mMemoryWarningListener);
} else {

/* Delete all files. */
for (File file : ErrorLogHelper.getErrorStorageDirectory().listFiles()) {
Expand All @@ -371,12 +422,30 @@ protected synchronized void applyEnabledState(boolean enabled) {
/* Delete cache and in memory last session report. */
mErrorReportCache.clear();
mLastSessionErrorReport = null;
mContext.unregisterComponentCallbacks(mMemoryWarningListener);
SharedPreferencesManager.remove(PREF_KEY_MEMORY_RUNNING_LEVEL);
}
}

@Override
public synchronized void onStarted(@NonNull Context context, @NonNull Channel channel, String appSecret, String transmissionTargetToken, boolean startedFromApp) {
mContext = context;
mMemoryWarningListener = new ComponentCallbacks2() {

@Override
public void onTrimMemory(int level) {
saveMemoryRunningLevel(level);
}

@Override
public void onConfigurationChanged(Configuration newConfig) {
}

@Override
public void onLowMemory() {
saveMemoryRunningLevel(TRIM_MEMORY_COMPLETE);
}
};
super.onStarted(context, channel, appSecret, transmissionTargetToken, startedFromApp);
if (isInstanceEnabled()) {
processPendingErrors();
Expand Down Expand Up @@ -693,6 +762,8 @@ private void processPendingErrors() {
}
}
}
mHasReceivedMemoryWarningInLastSession = isMemoryRunningLevelWasReceived(SharedPreferencesManager.getInt(PREF_KEY_MEMORY_RUNNING_LEVEL, -1));
SharedPreferencesManager.remove(PREF_KEY_MEMORY_RUNNING_LEVEL);

/* If automatic processing is enabled. */
if (mAutomaticProcessing) {
Expand All @@ -702,6 +773,13 @@ private void processPendingErrors() {
}
}

private static boolean isMemoryRunningLevelWasReceived(int memoryLevel) {
return memoryLevel == TRIM_MEMORY_RUNNING_MODERATE
|| memoryLevel == TRIM_MEMORY_RUNNING_LOW
|| memoryLevel == TRIM_MEMORY_RUNNING_CRITICAL
|| memoryLevel == TRIM_MEMORY_COMPLETE;
}

/**
* Send crashes or wait for user confirmation (either via callback or explicit call in manual processing).
*
Expand Down Expand Up @@ -1096,6 +1174,12 @@ public void run() {
});
}

@WorkerThread
private static void saveMemoryRunningLevel(int level) {
SharedPreferencesManager.putInt(PREF_KEY_MEMORY_RUNNING_LEVEL, level);
AppCenterLog.debug(LOG_TAG, String.format("The memory running level (%s) was saved.", level));
}

/**
* Interface to use a template method to build exception model since it's a complex operation
* and should be called only after all enabled checks have been done.
Expand Down
Expand Up @@ -5,7 +5,9 @@

package com.microsoft.appcenter.crashes;

import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Looper;
import android.os.SystemClock;

Expand Down Expand Up @@ -75,10 +77,17 @@
import java.util.Map;
import java.util.UUID;

import static android.content.ComponentCallbacks2.TRIM_MEMORY_COMPLETE;
import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL;
import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE;
import static android.content.ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN;
import static com.microsoft.appcenter.Flags.CRITICAL;
import static com.microsoft.appcenter.Flags.DEFAULTS;
import static com.microsoft.appcenter.crashes.Crashes.PREF_KEY_MEMORY_RUNNING_LEVEL;
import static com.microsoft.appcenter.crashes.ingestion.models.ErrorAttachmentLog.attachmentWithBinary;
import static com.microsoft.appcenter.test.TestUtils.generateString;
import static com.microsoft.appcenter.utils.PrefStorageConstants.KEY_ENABLED;
import static java.util.Collections.singletonList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
Expand All @@ -97,6 +106,7 @@
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isA;
import static org.mockito.Matchers.isNull;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
Expand All @@ -108,6 +118,7 @@
import static org.powermock.api.mockito.PowerMockito.doAnswer;
import static org.powermock.api.mockito.PowerMockito.doThrow;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.spy;
import static org.powermock.api.mockito.PowerMockito.verifyNoMoreInteractions;
import static org.powermock.api.mockito.PowerMockito.verifyStatic;
import static org.powermock.api.mockito.PowerMockito.whenNew;
Expand Down Expand Up @@ -1647,4 +1658,107 @@ public boolean matches(Object argument) {
verifyStatic();
FileManager.write(any(File.class), eq(jsonCrash));
}

@Test
public void handlerMemoryWarning() {

/* Mock classes. */
Context mockContext = mock(Context.class);
ArgumentCaptor<ComponentCallbacks2> componentCallbacks2Captor = ArgumentCaptor.forClass(ComponentCallbacks2.class);
doNothing().when(mockContext).registerComponentCallbacks(componentCallbacks2Captor.capture());

/* Instance crash module. */
Crashes crashes = Crashes.getInstance();
crashes.onStarted(mockContext, mock(Channel.class), "", null, true);
crashes.applyEnabledState(true);
componentCallbacks2Captor.getValue().onConfigurationChanged(mock(Configuration.class));

/* Invoke callback onTrimMemory. */
componentCallbacks2Captor.getValue().onTrimMemory(TRIM_MEMORY_RUNNING_CRITICAL);

/* Verify put data to preferences. */
verifyStatic();
SharedPreferencesManager.putInt(eq(PREF_KEY_MEMORY_RUNNING_LEVEL), eq(TRIM_MEMORY_RUNNING_CRITICAL));

/* Invoke callback onLowMemory. */
componentCallbacks2Captor.getValue().onLowMemory();

/* Verify put data to preferences. */
verifyStatic();
SharedPreferencesManager.putInt(eq(PREF_KEY_MEMORY_RUNNING_LEVEL), eq(TRIM_MEMORY_COMPLETE));
}

@Test
public void registerAndUnregisterComponentCallbacks() {

/* Mock classes. */
Context mockContext = mock(Context.class);
mockStatic(ErrorLogHelper.class);
when(ErrorLogHelper.getErrorStorageDirectory()).thenReturn(errorStorageDirectory.getRoot());
when(ErrorLogHelper.getStoredErrorLogFiles()).thenReturn(new File[0]);
when(ErrorLogHelper.getNewMinidumpFiles()).thenReturn(new File[0]);

/* Instance crash module. */
Crashes crashes = Crashes.getInstance();
crashes.setInstanceEnabled(false);
crashes.onStarted(mockContext, mock(Channel.class), "", null, true);

/* Verify register callback. */
verify(mockContext, never()).registerComponentCallbacks(any(ComponentCallbacks2.class));
verifyStatic();
SharedPreferencesManager.remove(eq(PREF_KEY_MEMORY_RUNNING_LEVEL));

/* Enable crashes. */
crashes.setInstanceEnabled(true);
verify(mockContext).registerComponentCallbacks(any(ComponentCallbacks2.class));

/* Disable crashes. */
crashes.setInstanceEnabled(false);

/* Verify unregister callback. */
verify(mockContext, (times(2))).unregisterComponentCallbacks(any(ComponentCallbacks2.class));

/* Verify clear preferences. */
verifyStatic(times(2));
SharedPreferencesManager.remove(eq(PREF_KEY_MEMORY_RUNNING_LEVEL));
}

@Test
public void setReceiveMemoryWarningInLastSession() {
mockStatic(ErrorLogHelper.class);
when(ErrorLogHelper.getErrorStorageDirectory()).thenReturn(errorStorageDirectory.getRoot());
when(ErrorLogHelper.getStoredErrorLogFiles()).thenReturn(new File[0]);
when(ErrorLogHelper.getNewMinidumpFiles()).thenReturn(new File[0]);
when(FileManager.read(any(File.class))).thenReturn("");

when(SharedPreferencesManager.getInt(eq(PREF_KEY_MEMORY_RUNNING_LEVEL), anyInt()))
.thenReturn(TRIM_MEMORY_UI_HIDDEN);
checkHasReceivedMemoryWarningInLastSession(false);

when(SharedPreferencesManager.getInt(eq(PREF_KEY_MEMORY_RUNNING_LEVEL), anyInt()))
.thenReturn(TRIM_MEMORY_RUNNING_LOW);
checkHasReceivedMemoryWarningInLastSession(true);

when(SharedPreferencesManager.getInt(eq(PREF_KEY_MEMORY_RUNNING_LEVEL), anyInt()))
.thenReturn(TRIM_MEMORY_RUNNING_CRITICAL);
checkHasReceivedMemoryWarningInLastSession(true);

when(SharedPreferencesManager.getInt(eq(PREF_KEY_MEMORY_RUNNING_LEVEL), anyInt()))
.thenReturn(TRIM_MEMORY_COMPLETE);
checkHasReceivedMemoryWarningInLastSession(true);

when(SharedPreferencesManager.getInt(eq(PREF_KEY_MEMORY_RUNNING_LEVEL), anyInt()))
.thenReturn(TRIM_MEMORY_RUNNING_MODERATE);
checkHasReceivedMemoryWarningInLastSession(true);
}

private void checkHasReceivedMemoryWarningInLastSession(boolean expected) {
Crashes crashes = Crashes.getInstance();
crashes.onStarting(mAppCenterHandler);
crashes.onStarted(mock(Context.class), mock(Channel.class), "", null, true);
crashes.setInstanceEnabled(true);
crashes.onStarted(mock(Context.class), mock(Channel.class), "", null, true);
assertEquals(expected, Crashes.hasReceivedMemoryWarningInLastSession().get());
crashes.setInstanceEnabled(false);
}
}

0 comments on commit 5a9ef5a

Please sign in to comment.