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:
2026-09-28 23:44:47 +09:00
co-authored by Cursor
parent 74fb80f783
commit 47d735be2c
19 changed files with 209 additions and 107 deletions
+1
View File
@@ -41,6 +41,7 @@ android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
signingConfig signingConfigs.release
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
rootProject.file('proguard-release.flags'),
+1
View File
@@ -91,6 +91,7 @@
android:allowBackup="false"
android:appCategory="social"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:localeConfig="@xml/locales_config"
android:requestLegacyExternalStorage="true"
@@ -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,
@@ -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);
@@ -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
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

@@ -1,18 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
SPDX-FileCopyrightText: 2022 The LineageOS Project
SPDX-License-Identifier: Apache-2.0
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:pathData="M0,0h108v108h-108z"
android:fillColor="#359944"/>
<path
android:pathData="M54,76C66.15,76 76,66.15 76,54C76,49.769 74.806,45.817 72.736,42.463L74.327,36.098C74.693,34.634 73.366,33.307 71.902,33.673L65.537,35.264C62.183,33.194 58.231,32 54,32C41.85,32 32,41.85 32,54C32,66.15 41.85,76 54,76Z"
android:fillColor="#ffffff"
android:fillType="evenOdd"/>
<path android:pathData="M0,0h108v108h-108z">
<aapt:attr name="android:fillColor">
<gradient
android:type="linear"
android:startX="54"
android:startY="0"
android:endX="54"
android:endY="108">
<item android:offset="0" android:color="#8E2DEE"/>
<item android:offset="1" android:color="#530CCA"/>
</gradient>
</aapt:attr>
</path>
</vector>
@@ -1,34 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
SPDX-FileCopyrightText: 2022 The LineageOS Project
SPDX-License-Identifier: Apache-2.0
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group>
<clip-path
android:pathData="M0,0h108v108h-108z"/>
<path
android:pathData="M54,66C60.75,66 66,63.627 66,57C66,47.373 60.75,42 54,42C47.25,42 42,47.373 42,57C42,63.627 47.25,66 54,66ZM54,57.5C57.057,57.5 60.055,56.942 62,56C61.125,58.603 59.333,62 54,62C48.667,62 46.875,58.603 46,56C47.945,56.942 50.943,57.5 54,57.5Z"
android:fillColor="#143721"
android:fillType="evenOdd"/>
<path
android:pathData="M28.54,28.54m-72,0a72,72 0,1 1,144 0a72,72 0,1 1,-144 0"
android:fillAlpha="0.6">
<aapt:attr name="android:fillColor">
<gradient
android:gradientRadius="72"
android:centerX="28.54"
android:centerY="28.54"
android:type="radial">
<item android:offset="0" android:color="#19FFFFFF"/>
<item android:offset="1" android:color="#00FFFFFF"/>
</gradient>
</aapt:attr>
</path>
</group>
</vector>
@@ -1,19 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
SPDX-FileCopyrightText: 2022 The LineageOS Project
SPDX-License-Identifier: Apache-2.0
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:pathData="M69.465,42.887L70.916,37.083L65.113,38.534L64.033,37.861C61.123,36.048 57.688,35 54,35C43.507,35 35,43.507 35,54C35,64.493 43.507,73 54,73C64.493,73 73,64.493 73,54C73,50.311 71.952,46.876 70.138,43.966L69.465,42.887ZM76,54C76,66.15 66.15,76 54,76C41.85,76 32,66.15 32,54C32,41.85 41.85,32 54,32C58.265,32 62.248,33.214 65.619,35.315L71.805,33.769C73.27,33.403 74.597,34.729 74.23,36.194L72.684,42.38C74.786,45.752 76,49.734 76,54Z"
android:fillColor="#ffffffff"
android:fillType="evenOdd"/>
<path
android:pathData="M54,65.733C60.6,65.733 65.733,63.414 65.733,56.933C65.733,47.52 60.6,42.267 54,42.267C47.4,42.267 42.267,47.52 42.267,56.933C42.267,63.414 47.4,65.733 54,65.733ZM54,56.933C56.802,56.933 59.551,56.387 61.333,55.467C60.532,58.012 58.889,61.333 54,61.333C49.111,61.333 47.468,58.012 46.667,55.467C48.449,56.387 51.198,56.933 54,56.933Z"
android:fillColor="#ffffffff"
android:fillType="evenOdd"/>
</vector>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/ic_launcher_foreground"
android:tint="#FF000000" />
@@ -275,8 +275,8 @@
<include
android:id="@+id/home_category_add"
layout="@layout/home_category_add_tab"
android:layout_width="32dp"
android:layout_height="32dp" />
android:layout_width="24dp"
android:layout_height="24dp" />
</com.android.messaging.ui.home.HomeCategoryStrip>
<LinearLayout
@@ -274,7 +274,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|start"
android:layout_marginTop="2dp"
android:visibility="gone" />
</FrameLayout>
</LinearLayout>
@@ -2,9 +2,12 @@
<ImageButton xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
style="@style/Home.AddButton"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginTop="6dp"
android:layout_marginEnd="4dp"
android:layout_marginBottom="8dp"
android:padding="6dp"
android:contentDescription="@string/home_category_add_content_description"
app:srcCompat="@drawable/ic_home_automation_plus"
app:tint="@color/home_add_icon_tint" />
-2
View File
@@ -12,8 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-keep class com.android.messaging.ui.contact.* { *; }
-assumenosideeffects public class com.android.messaging.util.Trace {
public void beginSection(...);
public void endSection(...);
+15 -24
View File
@@ -22,54 +22,45 @@
RuntimeVisibleTypeAnnotations,
AnnotationDefault
-keep class com.android.messaging.* { *; }
-keep class com.android.messaging.*.* { *; }
-keep class com.android.messaging.*.*.* { *; }
# TODO(b/373579455): Evaluate if <init> needs to be kept.
# Manifest / XML entry points. Do not keep the whole app or androidx packages —
# Play requires 25% shrink / optimize / obfuscate when uncompressed DEX > 10 MB.
-keep public class * extends android.app.Activity {
void <init>();
}
# TODO(b/373579455): Evaluate if <init> needs to be kept.
-keep public class * extends android.app.Application {
void <init>();
}
# TODO(b/373579455): Evaluate if <init> needs to be kept.
-keep public class * extends android.app.Service {
void <init>();
}
# TODO(b/373579455): Evaluate if <init> needs to be kept.
-keep public class * extends android.content.BroadcastReceiver {
void <init>();
}
# TODO(b/373579455): Evaluate if <init> needs to be kept.
-keep public class * extends android.content.ContentProvider {
void <init>();
}
# TODO(b/373579455): Evaluate if <init> needs to be kept.
-keep public class * extends android.app.backup.BackupAgentHelper {
void <init>();
}
# TODO(b/373579455): Evaluate if <init> needs to be kept.
-keep public class * extends android.preference.Preference {
void <init>();
}
# TODO(b/373579455): Evaluate if <init> needs to be kept.
-keep public class * extends androidx.preference.Preference {
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keep public class * extends androidx.fragment.app.Fragment {
void <init>();
}
-keep public class com.android.vcard.* { *; }
-keep class androidx.collection.* { *; }
-keep class androidx.core.* { *; }
-keep class androidx.core.*.* { *; }
-keep class androidx.appcompat.* { *; }
-keep class androidx.appcompat.*.* { *; }
-keep class androidx.recyclerview.widget.* { *; }
-keep class androidx.viewpager.widget.* { *; }
# Keep rastermill classes that need to be accessed from native code (JNI)
-keep class android.support.rastermill.** { *; }
# Actions cross the ActionService / BackgroundWorker via Parcelable extras.
-keep class com.android.messaging.datamodel.action.Action {
void <init>();
}
-keep class * extends com.android.messaging.datamodel.action.Action {
public static android.os.Parcelable$Creator CREATOR;
<init>(...);
}
# Preserve the name of the getCaller method so it can find itself in the stack trace it generates
-keepclassmembers public class com.android.messaging.util.DebugUtils {
@@ -77,7 +68,7 @@
}
# Keep the static fields of referenced inner classes of auto-generated R classes, in case we
# access those fields by reflection (e.g. EmojiMarkup)
# access those fields by reflection
-keepclassmembers class **.R$* {
public static <fields>;
}