Messaging: Meet Play memory/R8 rules, ship Q icon, and polish home chrome
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -128,6 +128,18 @@ public class BugleApplication extends Application implements UncaughtExceptionHa
|
||||
Trace.endSection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrimMemory(final int level) {
|
||||
super.onTrimMemory(level);
|
||||
if (level < TRIM_MEMORY_UI_HIDDEN) {
|
||||
return;
|
||||
}
|
||||
if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
|
||||
LogUtil.d(TAG, "BugleApplication.onTrimMemory " + level);
|
||||
}
|
||||
reclaimCachedBitmaps();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLowMemory() {
|
||||
super.onLowMemory();
|
||||
@@ -135,7 +147,14 @@ public class BugleApplication extends Application implements UncaughtExceptionHa
|
||||
if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
|
||||
LogUtil.d(TAG, "BugleApplication.onLowMemory");
|
||||
}
|
||||
Factory.get().reclaimMemory();
|
||||
reclaimCachedBitmaps();
|
||||
}
|
||||
|
||||
private static void reclaimCachedBitmaps() {
|
||||
final Factory factory = Factory.get();
|
||||
if (factory != null) {
|
||||
factory.reclaimMemory();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -211,7 +211,16 @@ public class DecodedImageResource extends ImageResource {
|
||||
}
|
||||
}
|
||||
byte[] encodedBytes = ImageUtils.bitmapToBytes(bitmap, COMPRESS_QUALITY);
|
||||
return new EncodedImageResource(getKey(), encodedBytes, getOrientation());
|
||||
int desiredWidth = ImageRequest.UNSPECIFIED_SIZE;
|
||||
int desiredHeight = ImageRequest.UNSPECIFIED_SIZE;
|
||||
if (mOriginalImageRequest instanceof ImageRequest<?>) {
|
||||
final ImageRequestDescriptor descriptor =
|
||||
((ImageRequest<?>) mOriginalImageRequest).getDescriptor();
|
||||
desiredWidth = descriptor.desiredWidth;
|
||||
desiredHeight = descriptor.desiredHeight;
|
||||
}
|
||||
return new EncodedImageResource(getKey(), encodedBytes, getOrientation(),
|
||||
desiredWidth, desiredHeight);
|
||||
} catch (Exception ex) {
|
||||
// Something went wrong during bitmap compression, fall back to just using the
|
||||
// original bitmap.
|
||||
|
||||
@@ -17,11 +17,11 @@ package com.android.messaging.datamodel.media;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
|
||||
import com.android.messaging.util.ImageUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -41,10 +41,20 @@ import java.util.List;
|
||||
*/
|
||||
public class EncodedImageResource extends ImageResource {
|
||||
private final byte[] mImageBytes;
|
||||
private final int mDesiredWidth;
|
||||
private final int mDesiredHeight;
|
||||
|
||||
public EncodedImageResource(String key, byte[] imageBytes, int orientation) {
|
||||
this(key, imageBytes, orientation, ImageRequest.UNSPECIFIED_SIZE,
|
||||
ImageRequest.UNSPECIFIED_SIZE);
|
||||
}
|
||||
|
||||
public EncodedImageResource(String key, byte[] imageBytes, int orientation,
|
||||
final int desiredWidth, final int desiredHeight) {
|
||||
super(key, orientation);
|
||||
mImageBytes = imageBytes;
|
||||
mDesiredWidth = desiredWidth;
|
||||
mDesiredHeight = desiredHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -54,7 +64,7 @@ public class EncodedImageResource extends ImageResource {
|
||||
try {
|
||||
// This should only be called during the decode request.
|
||||
Assert.isNotMainThread();
|
||||
return BitmapFactory.decodeByteArray(mImageBytes, 0, mImageBytes.length);
|
||||
return decodeSampledBitmap();
|
||||
} finally {
|
||||
releaseLock();
|
||||
}
|
||||
@@ -99,11 +109,15 @@ public class EncodedImageResource extends ImageResource {
|
||||
return true;
|
||||
}
|
||||
|
||||
private Bitmap decodeSampledBitmap() {
|
||||
return ImageUtils.decodeByteArraySampled(mImageBytes, mDesiredWidth, mDesiredHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
MediaRequest<? extends RefCountedMediaResource> getMediaDecodingRequest(
|
||||
final MediaRequest<? extends RefCountedMediaResource> originalRequest) {
|
||||
Assert.isTrue(isEncoded());
|
||||
return new DecodeImageRequest();
|
||||
return new DecodeImageRequest(originalRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,7 +126,22 @@ public class EncodedImageResource extends ImageResource {
|
||||
* properties such as binding.
|
||||
*/
|
||||
private class DecodeImageRequest implements MediaRequest<ImageResource> {
|
||||
public DecodeImageRequest() {
|
||||
private final int mRequestDesiredWidth;
|
||||
private final int mRequestDesiredHeight;
|
||||
|
||||
public DecodeImageRequest(
|
||||
final MediaRequest<? extends RefCountedMediaResource> originalRequest) {
|
||||
int desiredWidth = mDesiredWidth;
|
||||
int desiredHeight = mDesiredHeight;
|
||||
if (originalRequest != null) {
|
||||
final MediaRequestDescriptor<?> descriptor = originalRequest.getDescriptor();
|
||||
if (descriptor instanceof ImageRequestDescriptor) {
|
||||
desiredWidth = ((ImageRequestDescriptor) descriptor).desiredWidth;
|
||||
desiredHeight = ((ImageRequestDescriptor) descriptor).desiredHeight;
|
||||
}
|
||||
}
|
||||
mRequestDesiredWidth = desiredWidth;
|
||||
mRequestDesiredHeight = desiredHeight;
|
||||
// Hold a ref onto the encoded resource before the request finishes.
|
||||
addRef();
|
||||
}
|
||||
@@ -129,8 +158,11 @@ public class EncodedImageResource extends ImageResource {
|
||||
Assert.isNotMainThread();
|
||||
acquireLock();
|
||||
try {
|
||||
final Bitmap decodedBitmap = BitmapFactory.decodeByteArray(mImageBytes, 0,
|
||||
mImageBytes.length);
|
||||
final Bitmap decodedBitmap = ImageUtils.decodeByteArraySampled(mImageBytes,
|
||||
mRequestDesiredWidth, mRequestDesiredHeight);
|
||||
if (decodedBitmap == null) {
|
||||
throw new RuntimeException("failed decoding encoded bitmap");
|
||||
}
|
||||
return new DecodedImageResource(getKey(), decodedBitmap, getOrientation());
|
||||
} finally {
|
||||
releaseLock();
|
||||
|
||||
@@ -18,11 +18,11 @@ package com.android.messaging.datamodel.media;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.support.v7.mms.pdu.ContentType;
|
||||
|
||||
import com.android.messaging.Factory;
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.ImageUtils;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
@@ -91,7 +91,11 @@ public class NetworkUriImageRequest<D extends UriImageRequestDescriptor> extends
|
||||
connection.setDoInput(true);
|
||||
connection.connect();
|
||||
if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
|
||||
bitmap = BitmapFactory.decodeStream(connection.getInputStream());
|
||||
try (InputStream inputStream = connection.getInputStream()) {
|
||||
final byte[] bytes = ImageUtils.readStreamToByteArray(inputStream);
|
||||
bitmap = ImageUtils.decodeByteArraySampled(bytes,
|
||||
mDescriptor.desiredWidth, mDescriptor.desiredHeight);
|
||||
}
|
||||
}
|
||||
} catch (MalformedURLException e) {
|
||||
LogUtil.e(LogUtil.BUGLE_TAG,
|
||||
|
||||
+2
@@ -19,6 +19,7 @@ package com.android.messaging.ui.conversationlist;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.ActionMode;
|
||||
import android.window.SplashScreenView;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -34,6 +35,7 @@ public class ConversationListActivity extends AbstractConversationListActivity {
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
Trace.beginSection("ConversationListActivity.onCreate");
|
||||
getSplashScreen().setOnExitAnimationListener(SplashScreenView::remove);
|
||||
setTheme(R.style.BugleTheme_ConversationListActivity);
|
||||
super.onCreate(savedInstanceState);
|
||||
mConversationListFragment = ConversationListFragment.createConversationListFragment(null);
|
||||
|
||||
+42
-4
@@ -20,10 +20,12 @@ import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Typeface;
|
||||
import android.net.Uri;
|
||||
import android.support.v7.mms.pdu.ContentType;
|
||||
import android.text.Layout;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.StaticLayout;
|
||||
@@ -221,6 +223,9 @@ public class ConversationListItemView extends FrameLayout implements OnClickList
|
||||
mListDividerView = findViewById(R.id.conversation_list_divider);
|
||||
mConversationNameView.addOnLayoutChangeListener(this);
|
||||
mSnippetTextView.addOnLayoutChangeListener(this);
|
||||
if (mCategoryChipView != null) {
|
||||
mCategoryChipView.addOnLayoutChangeListener(this);
|
||||
}
|
||||
|
||||
final Resources resources = getContext().getResources();
|
||||
final Resources.Theme theme = getContext().getTheme();
|
||||
@@ -243,6 +248,8 @@ public class ConversationListItemView extends FrameLayout implements OnClickList
|
||||
setSnippet();
|
||||
} else if (v == mSubjectTextView) {
|
||||
setSubject();
|
||||
} else if (v == mCategoryChipView) {
|
||||
alignSimBadgeToFirstSnippetLine();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,10 +265,12 @@ public class ConversationListItemView extends FrameLayout implements OnClickList
|
||||
if (!SimNameUtil.shouldShowSimName(getContext())) {
|
||||
mCategoryChipView.setText(null);
|
||||
mCategoryChipView.setVisibility(GONE);
|
||||
mCategoryChipView.setTranslationY(0f);
|
||||
return;
|
||||
}
|
||||
mCategoryChipView.setText(SimNameUtil.getSimDisplayName(getContext(), mData.getSelfId()));
|
||||
mCategoryChipView.setVisibility(VISIBLE);
|
||||
alignSimBadgeToFirstSnippetLine();
|
||||
}
|
||||
|
||||
private void setConversationName() {
|
||||
@@ -344,12 +353,41 @@ public class ConversationListItemView extends FrameLayout implements OnClickList
|
||||
final int indent = getSnippetChipIndent();
|
||||
if (indent <= 0) {
|
||||
mSnippetTextView.setText(snippetText);
|
||||
} else {
|
||||
final SpannableString spanned = new SpannableString(snippetText == null ? "" : snippetText);
|
||||
spanned.setSpan(new LeadingMarginSpan.Standard(indent, 0),
|
||||
0, spanned.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
mSnippetTextView.setText(spanned);
|
||||
}
|
||||
alignSimBadgeToFirstSnippetLine();
|
||||
}
|
||||
|
||||
private void alignSimBadgeToFirstSnippetLine() {
|
||||
if (mCategoryChipView == null) {
|
||||
return;
|
||||
}
|
||||
final SpannableString spanned = new SpannableString(snippetText == null ? "" : snippetText);
|
||||
spanned.setSpan(new LeadingMarginSpan.Standard(indent, 0),
|
||||
0, spanned.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
mSnippetTextView.setText(spanned);
|
||||
if (mCategoryChipView.getVisibility() != VISIBLE || mSnippetTextView == null) {
|
||||
mCategoryChipView.setTranslationY(0f);
|
||||
return;
|
||||
}
|
||||
int chipHeight = mCategoryChipView.getHeight();
|
||||
if (chipHeight <= 0) {
|
||||
mCategoryChipView.measure(
|
||||
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
|
||||
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
|
||||
chipHeight = mCategoryChipView.getMeasuredHeight();
|
||||
}
|
||||
if (chipHeight <= 0) {
|
||||
return;
|
||||
}
|
||||
final Paint.FontMetricsInt fm = mSnippetTextView.getPaint().getFontMetricsInt();
|
||||
final int textHeight = fm.descent - fm.ascent;
|
||||
int textTop = mSnippetTextView.getPaddingTop();
|
||||
final Layout layout = mSnippetTextView.getLayout();
|
||||
if (layout != null && layout.getLineCount() > 0) {
|
||||
textTop = mSnippetTextView.getPaddingTop() + layout.getLineBaseline(0) + fm.ascent;
|
||||
}
|
||||
mCategoryChipView.setTranslationY(textTop + (textHeight - chipHeight) / 2f);
|
||||
}
|
||||
|
||||
private int getSnippetChipIndent() {
|
||||
|
||||
@@ -22,14 +22,20 @@ public final class HomeCategoryAddModal {
|
||||
void onCategoryCreated(HomeCategoryStore.Category created);
|
||||
}
|
||||
|
||||
private static boolean sShowing;
|
||||
|
||||
private HomeCategoryAddModal() {
|
||||
}
|
||||
|
||||
public static void show(@NonNull final Context context, @Nullable final Listener listener) {
|
||||
if (sShowing) {
|
||||
return;
|
||||
}
|
||||
sShowing = true;
|
||||
final View content = LayoutInflater.from(context)
|
||||
.inflate(R.layout.home_category_name_dialog, null, false);
|
||||
final EditText field = content.findViewById(R.id.home_category_name_field);
|
||||
HomeModal.builder(context)
|
||||
final HomeModal.Handle handle = HomeModal.builder(context)
|
||||
.icon(R.drawable.ic_home_category)
|
||||
.title(R.string.home_category_add)
|
||||
.message(R.string.home_category_name_message)
|
||||
@@ -44,6 +50,11 @@ public final class HomeCategoryAddModal {
|
||||
}
|
||||
})
|
||||
.show();
|
||||
if (handle == null || handle.dialog == null) {
|
||||
sShowing = false;
|
||||
return;
|
||||
}
|
||||
handle.dialog.setOnDismissListener(dialog -> sShowing = false);
|
||||
if (field != null) {
|
||||
field.requestFocus();
|
||||
ImeUtil.get().showImeKeyboard(context, field);
|
||||
|
||||
@@ -202,6 +202,46 @@ public class ImageUtils {
|
||||
return inSampleSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cap for full-resolution decodes when the caller did not give a target size.
|
||||
* 2048px keeps a 12 MP photo from becoming a ~48 MB ARGB_8888 bitmap.
|
||||
*/
|
||||
public static final int MAX_DECODE_EDGE = 2048;
|
||||
|
||||
public static Bitmap decodeByteArraySampled(final byte[] bytes, final int reqWidth,
|
||||
final int reqHeight) {
|
||||
if (bytes == null || bytes.length == 0) {
|
||||
return null;
|
||||
}
|
||||
final BitmapFactory.Options bounds = new BitmapFactory.Options();
|
||||
bounds.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeByteArray(bytes, 0, bytes.length, bounds);
|
||||
int targetWidth = reqWidth;
|
||||
int targetHeight = reqHeight;
|
||||
if (targetWidth == ImageRequest.UNSPECIFIED_SIZE
|
||||
&& targetHeight == ImageRequest.UNSPECIFIED_SIZE) {
|
||||
targetWidth = MAX_DECODE_EDGE;
|
||||
targetHeight = MAX_DECODE_EDGE;
|
||||
}
|
||||
final BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.outWidth = bounds.outWidth;
|
||||
options.outHeight = bounds.outHeight;
|
||||
options.inSampleSize = get().calculateInSampleSize(options, targetWidth, targetHeight);
|
||||
options.inJustDecodeBounds = false;
|
||||
options.inMutable = true;
|
||||
return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
|
||||
}
|
||||
|
||||
public static byte[] readStreamToByteArray(final InputStream inputStream) throws IOException {
|
||||
final ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
final byte[] buffer = new byte[16 * 1024];
|
||||
int read;
|
||||
while ((read = inputStream.read(buffer)) != -1) {
|
||||
output.write(buffer, 0, read);
|
||||
}
|
||||
return output.toByteArray();
|
||||
}
|
||||
|
||||
private static final String[] MEDIA_CONTENT_PROJECTION = new String[] {
|
||||
MediaStore.MediaColumns.MIME_TYPE
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user