Messaging: Remove unused code

Change-Id: I8093bb3e305ae323f7069bf42f9e83f8c8647cc7
This commit is contained in:
Michael W
2024-12-17 12:38:18 +01:00
parent 0069df879a
commit e29b158fba
69 changed files with 42 additions and 3228 deletions

View File

@@ -46,17 +46,7 @@
<string name="wireless_alerts_key" translatable="false">buglesub_wireless_alerts_key</string>
<string name="apn_list_pref_key" translatable="false">buglesub_apn_list</string>
<!-- SMS/MMS settings keys -->
<!--
TODO: Several of these are currently unused but are expected to be needed to
implement SMS/MMS delivery and basic settings. Once we have the core functionality in place
we should do a pass to remove any unused values here.
-->
<string name="use_local_apn_pref_key" translatable="false">use_local_apn_pref_key</string>
<bool name="use_local_apn_pref_default" translatable="false">false</bool>
<integer name="mediapicker_transition_duration">600</integer><!-- ms -->
<integer name="asyncimage_transition_duration">300</integer><!-- ms -->
<integer name="compose_transition_duration">300</integer><!-- ms -->
<integer name="camera_shutter_duration">200</integer><!-- ms -->
<fraction name="camera_shutter_max_alpha">70%</fraction>

View File

@@ -116,7 +116,6 @@
<dimen name="vcard_detail_group_indicator_width">40dp</dimen>
<dimen name="mms_indicator_size">12sp</dimen>
<dimen name="conversation_fast_fling_threshold">10dp</dimen>
<dimen name="list_empty_text_size">14sp</dimen>
<dimen name="list_empty_text_top_margin">20dp</dimen>
<dimen name="list_empty_text_left_right_margin">60dp</dimen>

View File

@@ -480,24 +480,4 @@ public class MmsHttpClient {
}
return null;
}
/**
* Get NAI using hidden SystemProperties.get(String)
*
* @return the NAI string as system property
*/
private static String getNaiBySystemProperty() {
try {
final Class systemPropertiesClass = Class.forName("android.os.SystemProperties");
if (systemPropertiesClass != null) {
final Method method = systemPropertiesClass.getMethod("get", String.class);
if (method != null) {
return (String) method.invoke(null, NAI_PROPERTY);
}
}
} catch (Exception e) {
Log.w(MmsService.TAG, "SystemProperties.get failed " + e);
}
return null;
}
}

View File

@@ -36,32 +36,6 @@ public class MmsManager {
// Cached computed overrides for carrier configuration values
private static final SparseArray<Bundle> sConfigOverridesMap = new SparseArray<>();
/**
* Set the size of thread pool for request execution.
*
* Default is 4
*
* Note: if system MMS API is used, this has no effect
*
* @param size thread pool size
*/
public static void setThreadPoolSize(int size) {
MmsService.setThreadPoolSize(size);
}
/**
* Set whether to use wake lock while sending or downloading MMS.
*
* Default value is true
*
* Note: if system MMS API is used, this has no effect
*
* @param useWakeLock true to use wake lock, false otherwise
*/
public static void setUseWakeLock(final boolean useWakeLock) {
MmsService.setUseWakeLock(useWakeLock);
}
/**
* Set the optional carrier config values loader
*

View File

@@ -98,23 +98,11 @@ abstract class MmsRequest implements Parcelable {
// Thread pool for transferring PDU with MMS apps
protected final ExecutorService mPduTransferExecutor = Executors.newCachedThreadPool();
// Whether this request should acquire wake lock
private boolean mUseWakeLock;
protected MmsRequest(final String locationUrl, final Uri pduUri,
final PendingIntent pendingIntent) {
mLocationUrl = locationUrl;
mPduUri = pduUri;
mPendingIntent = pendingIntent;
mUseWakeLock = true;
}
void setUseWakeLock(final boolean useWakeLock) {
mUseWakeLock = useWakeLock;
}
boolean getUseWakeLock() {
return mUseWakeLock;
}
/**
@@ -376,7 +364,6 @@ abstract class MmsRequest implements Parcelable {
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeByte((byte) (mUseWakeLock ? 1 : 0));
parcel.writeString(mLocationUrl);
parcel.writeParcelable(mPduUri, 0);
parcel.writeParcelable(mPendingIntent, 0);
@@ -384,7 +371,6 @@ abstract class MmsRequest implements Parcelable {
protected MmsRequest(final Parcel in) {
final ClassLoader classLoader = MmsRequest.class.getClassLoader();
mUseWakeLock = in.readByte() != 0;
mLocationUrl = in.readString();
mPduUri = in.readParcelable(classLoader);
mPendingIntent = in.readParcelable(classLoader);

View File

@@ -22,7 +22,6 @@ import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.Process;
import android.telephony.SmsManager;
import android.util.Log;
@@ -45,20 +44,11 @@ public class MmsService extends Service {
private static final String EXTRA_REQUEST = "request";
private static final String EXTRA_MYPID = "mypid";
private static final String WAKELOCK_ID = "mmslib_wakelock";
/**
* Thread pool size for each request queue
*/
private static volatile int sThreadPoolSize = DEFAULT_THREAD_POOL_SIZE;
/**
* Optional wake lock to use
*/
private static volatile boolean sUseWakeLock = true;
private static volatile PowerManager.WakeLock sWakeLock = null;
private static final Object sWakeLockLock = new Object();
/**
* Carrier configuration values loader
*/
@@ -74,25 +64,6 @@ public class MmsService extends Service {
*/
private static volatile UserAgentInfoLoader sUserAgentInfoLoader = null;
/**
* Set the size of thread pool for request execution.
* Default is DEFAULT_THREAD_POOL_SIZE
*
* @param size thread pool size
*/
static void setThreadPoolSize(final int size) {
sThreadPoolSize = size;
}
/**
* Set whether to use wake lock
*
* @param useWakeLock true to use wake lock, false otherwise
*/
static void setUseWakeLock(final boolean useWakeLock) {
sUseWakeLock = useWakeLock;
}
/**
* Set the optional carrier config values
*
@@ -164,52 +135,6 @@ public class MmsService extends Service {
}
}
/**
* Acquire the wake lock
*
* @param context the context to use
*/
private static void acquireWakeLock(final Context context) {
synchronized (sWakeLockLock) {
if (sWakeLock == null) {
final PowerManager pm =
(PowerManager) context.getSystemService(Context.POWER_SERVICE);
sWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_ID);
}
sWakeLock.acquire();
}
}
/**
* Release the wake lock
*/
private static void releaseWakeLock() {
boolean releasedEmptyWakeLock = false;
synchronized (sWakeLockLock) {
if (sWakeLock != null) {
sWakeLock.release();
} else {
releasedEmptyWakeLock = true;
}
}
if (releasedEmptyWakeLock) {
Log.w(TAG, "Releasing empty wake lock");
}
}
/**
* Check if wake lock is not held (e.g. when service stops)
*/
private static void verifyWakeLockNotHeld() {
boolean wakeLockHeld = false;
synchronized (sWakeLockLock) {
wakeLockHeld = sWakeLock != null && sWakeLock.isHeld();
}
if (wakeLockHeld) {
Log.e(TAG, "Wake lock still held!");
}
}
// Remember my PID to discard restarted intent
private static volatile int sMyPid = -1;
@@ -253,28 +178,6 @@ public class MmsService extends Service {
// Service stop task
private final Runnable mServiceStopRunnable = this::tryStopService;
/**
* Start the service with a request
*
* @param context the Context to use
* @param request the request to start
*/
public static void startRequest(final Context context, final MmsRequest request) {
final boolean useWakeLock = sUseWakeLock;
request.setUseWakeLock(useWakeLock);
final Intent intent = new Intent(context, MmsService.class);
intent.putExtra(EXTRA_REQUEST, request);
intent.putExtra(EXTRA_MYPID, getMyPid());
if (useWakeLock) {
acquireWakeLock(context);
}
if (context.startService(intent) == null) {
if (useWakeLock) {
releaseWakeLock();
}
}
}
@Override
public void onCreate() {
super.onCreate();
@@ -331,9 +234,6 @@ public class MmsService extends Service {
} catch (Exception e) {
Log.w(TAG, "Unexpected execution failure", e);
} finally {
if (request.getUseWakeLock()) {
releaseWakeLock();
}
releaseService();
}
});
@@ -344,9 +244,6 @@ public class MmsService extends Service {
Log.w(TAG, "Executing request failed " + e);
request.returnResult(this, SmsManager.MMS_ERROR_UNSPECIFIED,
null/*response*/, 0/*httpStatusCode*/);
if (request.getUseWakeLock()) {
releaseWakeLock();
}
}
} else {
Log.w(TAG, "Empty request");
@@ -434,7 +331,6 @@ public class MmsService extends Service {
if (stopped != null) {
if (stopped) {
Log.i(TAG, "Service successfully stopped");
verifyWakeLockNotHeld();
} else {
Log.i(TAG, "Service stopping cancelled");
}

View File

@@ -26,7 +26,6 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
/**
* Encoded-string-value = Text-string | Value-length Char-set Text-string
@@ -224,43 +223,6 @@ public class EncodedStringValue implements Cloneable {
return ret;
}
/**
* Extract an EncodedStringValue[] from a given String.
*/
public static EncodedStringValue[] extract(String src) {
String[] values = src.split(";");
ArrayList<EncodedStringValue> list = new ArrayList<>();
for (int i = 0; i < values.length; i++) {
if (values[i].length() > 0) {
list.add(new EncodedStringValue(values[i]));
}
}
int len = list.size();
if (len > 0) {
return list.toArray(new EncodedStringValue[len]);
} else {
return null;
}
}
/**
* Concatenate an EncodedStringValue[] into a single String.
*/
public static String concat(EncodedStringValue[] addr) {
StringBuilder sb = new StringBuilder();
int maxIndex = addr.length - 1;
for (int i = 0; i <= maxIndex; i++) {
sb.append(addr[i].getString());
if (i < maxIndex) {
sb.append(";");
}
}
return sb.toString();
}
public static EncodedStringValue copy(EncodedStringValue value) {
if (value == null) {
return null;

View File

@@ -116,7 +116,6 @@ public class BugleApplication extends Application implements UncaughtExceptionHa
MmsManager.setApnSettingsLoader(new BugleApnSettingsLoader(context));
MmsManager.setCarrierConfigValuesLoader(carrierConfigValuesLoader);
MmsManager.setUserAgentInfoLoader(new BugleUserAgentInfoLoader(context));
MmsManager.setUseWakeLock(true);
}
public static void updateAppConfig(final Context context) {

View File

@@ -57,7 +57,6 @@ class FactoryImpl extends Factory {
private MediaResourceManager mMediaResourceManager;
private MediaCacheManager mMediaCacheManager;
private ContactContentObserver mContactContentObserver;
private PhoneUtils mPhoneUtils;
private MediaUtil mMediaUtil;
private SparseArray<BugleSubscriptionPrefs> mSubscriptionPrefs;
private BugleCarrierConfigValuesLoader mCarrierConfigValuesLoader;

View File

@@ -1,365 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
* Copyright (C) 2024 The LineageOS Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.messaging.datamodel;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import androidx.annotation.NonNull;
import android.text.TextUtils;
import android.util.SparseArray;
import com.android.messaging.datamodel.MemoryCacheManager.MemoryCache;
import com.android.messaging.util.Assert;
import com.android.messaging.util.LogUtil;
import java.io.InputStream;
/**
* Class for creating / loading / reusing bitmaps. This class allow the user to create a new bitmap,
* reuse an bitmap from the pool and to return a bitmap for future reuse. The pool of bitmaps
* allows for faster decode and more efficient memory usage.
* Note: consumers should not create BitmapPool directly, but instead get the pool they want from
* the BitmapPoolManager.
*/
public class BitmapPool implements MemoryCache {
public static final int MAX_SUPPORTED_IMAGE_DIMENSION = 0xFFFF;
protected static final boolean VERBOSE = false;
/**
* Number of reuse failures to skip before reporting.
*/
private static final int FAILED_REPORTING_FREQUENCY = 100;
/**
* Count of reuse failures which have occurred.
*/
private static volatile int sFailedBitmapReuseCount = 0;
/**
* Overall pool data structure which currently only supports rectangular bitmaps. The size of
* one of the sides is used to index into the SparseArray.
*/
private final SparseArray<SingleSizePool> mPool;
private final Object mPoolLock = new Object();
private final String mPoolName;
private final int mMaxSize;
/**
* Inner structure which holds a pool of bitmaps all the same size (i.e. all have the same
* width as each other and height as each other, but not necessarily the same).
*/
private class SingleSizePool {
int mNumItems;
final Bitmap[] mBitmaps;
SingleSizePool(final int maxPoolSize) {
mNumItems = 0;
mBitmaps = new Bitmap[maxPoolSize];
}
}
/**
* Creates a pool of reused bitmaps with helper decode methods which will attempt to use the
* reclaimed bitmaps. This will help speed up the creation of bitmaps by using already allocated
* bitmaps.
* @param maxSize The overall max size of the pool. When the pool exceeds this size, all calls
* to reclaimBitmap(Bitmap) will result in recycling the bitmap.
* @param name Name of the bitmap pool and only used for logging. Can not be null.
*/
BitmapPool(final int maxSize, @NonNull final String name) {
Assert.isTrue(maxSize > 0);
Assert.isTrue(!TextUtils.isEmpty(name));
mPoolName = name;
mMaxSize = maxSize;
mPool = new SparseArray<>();
}
@Override
public void reclaim() {
synchronized (mPoolLock) {
for (int p = 0; p < mPool.size(); p++) {
final SingleSizePool singleSizePool = mPool.valueAt(p);
for (int i = 0; i < singleSizePool.mNumItems; i++) {
singleSizePool.mBitmaps[i].recycle();
singleSizePool.mBitmaps[i] = null;
}
singleSizePool.mNumItems = 0;
}
mPool.clear();
}
}
/**
* Creates a new BitmapFactory.Options.
*/
public static BitmapFactory.Options getBitmapOptionsForPool(final boolean scaled,
final int inputDensity, final int targetDensity) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = scaled;
options.inDensity = inputDensity;
options.inTargetDensity = targetDensity;
options.inSampleSize = 1;
options.inJustDecodeBounds = false;
options.inMutable = true;
return options;
}
/**
* @return The pool key for the provided image dimensions or 0 if either width or height is
* greater than the max supported image dimension.
*/
private int getPoolKey(final int width, final int height) {
if (width > MAX_SUPPORTED_IMAGE_DIMENSION || height > MAX_SUPPORTED_IMAGE_DIMENSION) {
return 0;
}
return (width << 16) | height;
}
/**
*
* @return A bitmap in the pool with the specified dimensions or null if no bitmap with the
* specified dimension is available.
*/
private Bitmap findPoolBitmap(final int width, final int height) {
final int poolKey = getPoolKey(width, height);
if (poolKey != 0) {
synchronized (mPoolLock) {
// Take a bitmap from the pool if one is available
final SingleSizePool singlePool = mPool.get(poolKey);
if (singlePool != null && singlePool.mNumItems > 0) {
singlePool.mNumItems--;
final Bitmap foundBitmap = singlePool.mBitmaps[singlePool.mNumItems];
singlePool.mBitmaps[singlePool.mNumItems] = null;
return foundBitmap;
}
}
}
return null;
}
/**
* Internal function to try and find a bitmap in the pool which matches the desired width and
* height and then set that in the bitmap options properly.
*
* TODO: Why do we take a width/height? Shouldn't this already be in the
* BitmapFactory.Options instance? Can we assert that they match?
* @param optionsTmp The BitmapFactory.Options to update with the bitmap for the system to try
* to reuse.
* @param width The width of the reusable bitmap.
* @param height The height of the reusable bitmap.
*/
private void assignPoolBitmap(final BitmapFactory.Options optionsTmp, final int width,
final int height) {
if (optionsTmp.inJustDecodeBounds) {
return;
}
optionsTmp.inBitmap = findPoolBitmap(width, height);
}
/**
* Load a resource into a bitmap. Uses a bitmap from the pool if possible to reduce memory
* turnover.
* @param resourceId Resource id to load.
* @param resources Application resources. Cannot be null.
* @param optionsTmp Should be the same options returned from getBitmapOptionsForPool(). Cannot
* be null.
* @param width The width of the bitmap.
* @param height The height of the bitmap.
* @return The decoded Bitmap with the resource drawn in it.
*/
public Bitmap decodeSampledBitmapFromResource(final int resourceId,
@NonNull final Resources resources, @NonNull final BitmapFactory.Options optionsTmp,
final int width, final int height) {
Assert.notNull(resources);
Assert.notNull(optionsTmp);
Assert.isTrue(width > 0);
Assert.isTrue(height > 0);
assignPoolBitmap(optionsTmp, width, height);
Bitmap b = null;
try {
b = BitmapFactory.decodeResource(resources, resourceId, optionsTmp);
} catch (final IllegalArgumentException e) {
// BitmapFactory couldn't decode the file, try again without an inputBufferBitmap.
if (optionsTmp.inBitmap != null) {
optionsTmp.inBitmap = null;
b = BitmapFactory.decodeResource(resources, resourceId, optionsTmp);
sFailedBitmapReuseCount++;
if (sFailedBitmapReuseCount % FAILED_REPORTING_FREQUENCY == 0) {
LogUtil.w(LogUtil.BUGLE_TAG,
"Pooled bitmap consistently not being reused count = " +
sFailedBitmapReuseCount);
}
}
} catch (final OutOfMemoryError e) {
LogUtil.w(LogUtil.BUGLE_TAG, "Oom decoding resource " + resourceId);
reclaim();
}
return b;
}
/**
* Load an input stream into a bitmap. Uses a bitmap from the pool if possible to reduce memory
* turnover.
* @param inputStream InputStream load. Cannot be null.
* @param optionsTmp Should be the same options returned from getBitmapOptionsForPool(). Cannot
* be null.
* @param width The width of the bitmap.
* @param height The height of the bitmap.
* @return The decoded Bitmap with the resource drawn in it.
*/
public Bitmap decodeSampledBitmapFromInputStream(@NonNull final InputStream inputStream,
@NonNull final BitmapFactory.Options optionsTmp,
final int width, final int height) {
Assert.notNull(inputStream);
Assert.isTrue(width > 0);
Assert.isTrue(height > 0);
assignPoolBitmap(optionsTmp, width, height);
Bitmap b = null;
try {
b = BitmapFactory.decodeStream(inputStream, null, optionsTmp);
} catch (final IllegalArgumentException e) {
// BitmapFactory couldn't decode the file, try again without an inputBufferBitmap.
if (optionsTmp.inBitmap != null) {
optionsTmp.inBitmap = null;
b = BitmapFactory.decodeStream(inputStream, null, optionsTmp);
sFailedBitmapReuseCount++;
if (sFailedBitmapReuseCount % FAILED_REPORTING_FREQUENCY == 0) {
LogUtil.w(LogUtil.BUGLE_TAG,
"Pooled bitmap consistently not being reused count = " +
sFailedBitmapReuseCount);
}
}
} catch (final OutOfMemoryError e) {
LogUtil.w(LogUtil.BUGLE_TAG, "Oom decoding inputStream");
reclaim();
}
return b;
}
/**
* Turn encoded bytes into a bitmap. Uses a bitmap from the pool if possible to reduce memory
* turnover.
* @param bytes Encoded bytes to draw on the bitmap. Cannot be null.
* @param optionsTmp The bitmap will set here and the input should be generated from
* getBitmapOptionsForPool(). Cannot be null.
* @param width The width of the bitmap.
* @param height The height of the bitmap.
* @return A Bitmap with the encoded bytes drawn in it.
*/
public Bitmap decodeByteArray(@NonNull final byte[] bytes,
@NonNull final BitmapFactory.Options optionsTmp, final int width,
final int height) throws OutOfMemoryError {
Assert.notNull(bytes);
Assert.notNull(optionsTmp);
Assert.isTrue(width > 0);
Assert.isTrue(height > 0);
assignPoolBitmap(optionsTmp, width, height);
Bitmap b = null;
try {
b = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, optionsTmp);
} catch (final IllegalArgumentException e) {
if (VERBOSE) {
LogUtil.v(LogUtil.BUGLE_TAG, "BitmapPool(" + mPoolName +
") Unable to use pool bitmap");
}
// BitmapFactory couldn't decode the file, try again without an inputBufferBitmap.
// (i.e. without the bitmap from the pool)
if (optionsTmp.inBitmap != null) {
optionsTmp.inBitmap = null;
b = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, optionsTmp);
sFailedBitmapReuseCount++;
if (sFailedBitmapReuseCount % FAILED_REPORTING_FREQUENCY == 0) {
LogUtil.w(LogUtil.BUGLE_TAG,
"Pooled bitmap consistently not being reused count = " +
sFailedBitmapReuseCount);
}
}
}
return b;
}
/**
* Creates a bitmap with the given size, this will reuse a bitmap in the pool, if one is
* available, otherwise this will create a new one.
* @param width The desired width of the bitmap.
* @param height The desired height of the bitmap.
* @return A bitmap with the desired width and height, this maybe a reused bitmap from the pool.
*/
public Bitmap createOrReuseBitmap(final int width, final int height) {
Bitmap b = findPoolBitmap(width, height);
if (b == null) {
b = createBitmap(width, height);
}
return b;
}
/**
* This will create a new bitmap regardless of pool state.
* @param width The desired width of the bitmap.
* @param height The desired height of the bitmap.
* @return A bitmap with the desired width and height.
*/
private Bitmap createBitmap(final int width, final int height) {
return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
}
/**
* Called when a bitmap is finished being used so that it can be used for another bitmap in the
* future or recycled. Any bitmaps returned should not be used by the caller again.
* @param b The bitmap to return to the pool for future usage or recycled. This cannot be null.
*/
public void reclaimBitmap(@NonNull final Bitmap b) {
Assert.notNull(b);
final int poolKey = getPoolKey(b.getWidth(), b.getHeight());
if (poolKey == 0 || !b.isMutable()) {
// Unsupported image dimensions or a immutable bitmap.
b.recycle();
return;
}
synchronized (mPoolLock) {
SingleSizePool singleSizePool = mPool.get(poolKey);
if (singleSizePool == null) {
singleSizePool = new SingleSizePool(mMaxSize);
mPool.append(poolKey, singleSizePool);
}
if (singleSizePool.mNumItems < singleSizePool.mBitmaps.length) {
singleSizePool.mBitmaps[singleSizePool.mNumItems] = b;
singleSizePool.mNumItems++;
} else {
b.recycle();
}
}
}
/**
* @return whether the pool is full for a given width and height.
*/
public boolean isFull(final int width, final int height) {
final int poolKey = getPoolKey(width, height);
synchronized (mPoolLock) {
final SingleSizePool singleSizePool = mPool.get(poolKey);
if (singleSizePool != null &&
singleSizePool.mNumItems >= singleSizePool.mBitmaps.length) {
return true;
}
return false;
}
}
}

View File

@@ -835,17 +835,6 @@ public class BugleDatabaseOperations {
return null;
}
/**
* Frees up memory associated with phone number to participant id matching.
*/
@DoesNotRunOnMainThread
public static void clearParticipantIdCache() {
Assert.isNotMainThread();
synchronized (sNormalizedPhoneNumberToParticipantIdCache) {
sNormalizedPhoneNumberToParticipantIdCache.clear();
}
}
@DoesNotRunOnMainThread
public static ArrayList<String> getRecipientsForConversation(final DatabaseWrapper dbWrapper,
final String conversationId) {
@@ -940,21 +929,6 @@ public class BugleDatabaseOperations {
return message;
}
@VisibleForTesting
static MessagePartData readMessagePartData(final DatabaseWrapper dbWrapper,
final String partId) {
MessagePartData messagePartData = null;
try (Cursor cursor = dbWrapper.query(DatabaseHelper.PARTS_TABLE,
MessagePartData.getProjection(), PartColumns._ID + "=?",
new String[]{partId}, null, null, null)) {
Assert.inRange(cursor.getCount(), 0, 1);
if (cursor.moveToFirst()) {
messagePartData = MessagePartData.createFromCursor(cursor);
}
}
return messagePartData;
}
@DoesNotRunOnMainThread
public static MessageData readMessageData(final DatabaseWrapper dbWrapper,
final Uri smsMessageUri) {
@@ -1726,17 +1700,6 @@ public class BugleDatabaseOperations {
}
}
/**
* Refresh conversation names/avatars based on a changed participant.
*/
@DoesNotRunOnMainThread
public static void refreshConversationsForParticipant(final String participantId) {
Assert.isNotMainThread();
final ArrayList<String> participantList = new ArrayList<>(1);
participantList.add(participantId);
refreshConversationsForParticipants(participantList);
}
/**
* Refresh one conversation.
*/

View File

@@ -1,101 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.messaging.datamodel;
public class DataModelException extends Exception {
private static final long serialVersionUID = 1L;
private static final int FIRST = 100;
// ERRORS GENERATED INTERNALLY BY DATA MODEL.
// ERRORS RELATED WITH SMS.
public static final int ERROR_SMS_TEMPORARY_FAILURE = 116;
public static final int ERROR_SMS_PERMANENT_FAILURE = 117;
public static final int ERROR_MMS_TEMPORARY_FAILURE = 118;
public static final int ERROR_MMS_PERMANENT_UNKNOWN_FAILURE = 119;
// Request expired.
public static final int ERROR_EXPIRED = 120;
// Request canceled by user.
public static final int ERROR_CANCELED = 121;
public static final int ERROR_MOBILE_DATA_DISABLED = 123;
public static final int ERROR_MMS_SERVICE_BLOCKED = 124;
public static final int ERROR_MMS_INVALID_ADDRESS = 125;
public static final int ERROR_MMS_NETWORK_PROBLEM = 126;
public static final int ERROR_MMS_MESSAGE_NOT_FOUND = 127;
public static final int ERROR_MMS_MESSAGE_FORMAT_CORRUPT = 128;
public static final int ERROR_MMS_CONTENT_NOT_ACCEPTED = 129;
public static final int ERROR_MMS_MESSAGE_NOT_SUPPORTED = 130;
public static final int ERROR_MMS_REPLY_CHARGING_ERROR = 131;
public static final int ERROR_MMS_ADDRESS_HIDING_NOT_SUPPORTED = 132;
public static final int ERROR_MMS_LACK_OF_PREPAID = 133;
public static final int ERROR_MMS_CAN_NOT_PERSIST = 134;
public static final int ERROR_MMS_NO_AVAILABLE_APN = 135;
public static final int ERROR_MMS_INVALID_MESSAGE_TO_SEND = 136;
public static final int ERROR_MMS_INVALID_MESSAGE_RECEIVED = 137;
public static final int ERROR_MMS_NO_CONFIGURATION = 138;
private static final int LAST = 138;
private final boolean mIsInjection;
private final int mErrorCode;
private final String mMessage;
private final long mBackoff;
public DataModelException(final int errorCode, final Exception innerException,
final long backoff, final boolean injection, final String message) {
// Since some of the exceptions passed in may not be serializable, only record message
// instead of setting inner exception for Exception class. Otherwise, we will get
// serialization issues when we pass ServerRequestException as intent extra later.
if (errorCode < FIRST || errorCode > LAST) {
throw new IllegalArgumentException("error code out of range: " + errorCode);
}
mIsInjection = injection;
mErrorCode = errorCode;
if (innerException != null) {
mMessage = innerException.getMessage() + " -- " +
(mIsInjection ? "[INJECTED] -- " : "") + message;
} else {
mMessage = (mIsInjection ? "[INJECTED] -- " : "") + message;
}
mBackoff = backoff;
}
public DataModelException(final int errorCode) {
this(errorCode, null, 0, false, null);
}
public DataModelException(final int errorCode, final Exception innerException) {
this(errorCode, innerException, 0, false, null);
}
public DataModelException(final int errorCode, final String message) {
this(errorCode, null, 0, false, message);
}
@Override
public String getMessage() {
return mMessage;
}
public int getErrorCode() {
return mErrorCode;
}
}

View File

@@ -1102,29 +1102,6 @@ public abstract class MessageNotificationState extends NotificationState {
}
}
/*
private static void updateAlertStatusMessages(final long thresholdDeltaMs) {
// TODO may need this when supporting error notifications
final EsDatabaseHelper helper = EsDatabaseHelper.getDatabaseHelper();
final ContentValues values = new ContentValues();
final long nowMicros = System.currentTimeMillis() * 1000;
values.put(MessageColumns.ALERT_STATUS, "1");
final String selection =
MessageColumns.ALERT_STATUS + "=0 AND (" +
MessageColumns.STATUS + "=" + EsProvider.MESSAGE_STATUS_FAILED_TO_SEND + " OR (" +
MessageColumns.STATUS + "!=" + EsProvider.MESSAGE_STATUS_ON_SERVER + " AND " +
MessageColumns.TIMESTAMP + "+" + thresholdDeltaMs*1000 + "<" + nowMicros + ")) ";
final int updateCount = helper.getWritableDatabaseWrapper().update(
EsProvider.MESSAGES_TABLE,
values,
selection,
null);
if (updateCount > 0) {
EsConversationsData.notifyConversationsChanged();
}
}*/
static CharSequence applyWarningTextColor(final Context context,
final CharSequence text) {
if (text == null) {
@@ -1166,7 +1143,6 @@ public abstract class MessageNotificationState extends NotificationState {
final ArrayList<Integer> failedMessages = new ArrayList<>();
int cursorPosition = -1;
final long when = 0;
messageDataCursor.moveToPosition(-1);
while (messageDataCursor.moveToNext()) {
@@ -1195,7 +1171,6 @@ public abstract class MessageNotificationState extends NotificationState {
CharSequence line1;
CharSequence line2;
final boolean isRichContent = false;
ConversationIdSet conversationIds = null;
PendingIntent destinationIntent;
if (failedMessages.size() == 1) {
@@ -1222,12 +1197,6 @@ public abstract class MessageNotificationState extends NotificationState {
}
line1 = resources.getString(failureStringId);
line2 = failedMessgeSnippet;
// Set rich text for non-SMS messages or MMS push notification messages
// which we generate locally with rich text
// TODO- fix this
// if (messageData.isMmsInd()) {
// isRichContent = true;
// }
} else {
// We have notifications for multiple conversation, go to the conversation
// list.
@@ -1265,29 +1234,18 @@ public abstract class MessageNotificationState extends NotificationState {
builder
.setContentTitle(line1)
.setTicker(line1)
.setWhen(when > 0 ? when : System.currentTimeMillis())
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.drawable.ic_failed_light)
.setDeleteIntent(pendingIntentForDelete)
.setContentIntent(destinationIntent)
.setSound(UriUtil.getUriForResourceId(context, R.raw.message_failure));
if (isRichContent && !TextUtils.isEmpty(line2)) {
final NotificationCompat.InboxStyle inboxStyle =
new NotificationCompat.InboxStyle(builder);
if (line2 != null) {
inboxStyle.addLine(Html.fromHtml(line2.toString()));
}
builder.setStyle(inboxStyle);
} else {
builder.setContentText(line2);
}
if (builder != null) {
notificationManager.notify(
BugleNotifications.buildNotificationTag(
PendingIntentConstants.MSG_SEND_ERROR, null),
PendingIntentConstants.MSG_SEND_ERROR,
builder.build());
}
} else {
notificationManager.cancel(
BugleNotifications.buildNotificationTag(

View File

@@ -254,15 +254,6 @@ public class MessagingContentProvider extends ContentProvider {
@Override
public Cursor query(@NonNull final Uri uri, final String[] projection, String selection,
final String[] selectionArgs, String sortOrder) {
// Processes other than self are allowed to temporarily access the media
// scratch space; we grant uri read access on a case-by-case basis. Dialer app and
// contacts app would doQuery() on the vCard uri before trying to open the inputStream.
// There's nothing that we need to return for this uri so just No-Op.
//if (isMediaScratchSpaceUri(uri)) {
// return null;
//}
final SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
String[] queryArgs = selectionArgs;

View File

@@ -23,7 +23,6 @@ import android.os.Parcelable;
import android.text.TextUtils;
import com.android.messaging.datamodel.DataModel;
import com.android.messaging.datamodel.DataModelException;
import com.android.messaging.datamodel.action.ActionMonitor.ActionCompletedListener;
import com.android.messaging.datamodel.action.ActionMonitor.ActionExecutedListener;
import com.android.messaging.util.LogUtil;
@@ -96,11 +95,10 @@ public abstract class Action implements Parcelable {
/**
* Do work in a long running background worker thread.
* {@link #requestBackgroundWork} needs to be called for this method to
* be called. {@link #processBackgroundFailure} will be called on the Action service thread
* if this method throws {@link DataModelException}.
* be called.
* @return response that is to be passed to {@link #processBackgroundResponse}
*/
protected Bundle doBackgroundWork() throws DataModelException {
protected Bundle doBackgroundWork() {
return null;
}

View File

@@ -26,7 +26,6 @@ import androidx.core.app.JobIntentService;
import com.android.messaging.Factory;
import com.android.messaging.datamodel.DataModel;
import com.android.messaging.datamodel.DataModelException;
import com.android.messaging.util.Assert;
import com.android.messaging.util.LogUtil;
import com.android.messaging.util.LoggingTimer;
@@ -140,18 +139,9 @@ public class BackgroundWorkerService extends JobIntentService {
} catch (final Exception exception) {
final boolean retry = false;
LogUtil.e(TAG, "Error in background worker", exception);
if (!(exception instanceof DataModelException)) {
// DataModelException is expected (sort-of) and handled in handleFailureFromWorker
// below, but other exceptions should crash ENG builds
Assert.fail("Unexpected error in background worker - abort");
}
if (retry) {
action.markBackgroundWorkQueued();
startServiceWithAction(action, attempt + 1);
} else {
action.markBackgroundCompletionQueued();
mHost.handleFailureFromBackgroundWorker(action, exception);
}
}
}
}

View File

@@ -114,9 +114,6 @@ public class BugleActionToasts {
}
}
public static void onConversationDeleted() {
}
private static void showToast(final int messageResId) {
ThreadUtil.getMainThreadHandler().post(() -> Toast.makeText(getApplicationContext(),
getApplicationContext().getString(messageResId), Toast.LENGTH_LONG).show());

View File

@@ -30,7 +30,6 @@ import com.android.messaging.Factory;
import com.android.messaging.datamodel.BugleDatabaseOperations;
import com.android.messaging.datamodel.BugleNotifications;
import com.android.messaging.datamodel.DataModel;
import com.android.messaging.datamodel.DataModelException;
import com.android.messaging.datamodel.DatabaseHelper;
import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
import com.android.messaging.datamodel.DatabaseWrapper;
@@ -71,7 +70,7 @@ public class DeleteConversationAction extends Action implements Parcelable {
// telephony database can sometimes be quite slow to delete conversations, so we delete from
// the local DB first, notify the UI, and then delete from telephony.
@Override
protected Bundle doBackgroundWork() throws DataModelException {
protected Bundle doBackgroundWork() {
final DatabaseWrapper db = DataModel.get().getDatabase();
final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID);
@@ -85,8 +84,6 @@ public class DeleteConversationAction extends Action implements Parcelable {
LogUtil.i(TAG, "DeleteConversationAction: Deleted local conversation "
+ conversationId);
BugleActionToasts.onConversationDeleted();
// Remove notifications if necessary
BugleNotifications.update(true /* silent */, null /* conversationId */,
BugleNotifications.UPDATE_MESSAGES);

View File

@@ -34,7 +34,6 @@ import com.android.messaging.Factory;
import com.android.messaging.datamodel.BugleDatabaseOperations;
import com.android.messaging.datamodel.BugleNotifications;
import com.android.messaging.datamodel.DataModel;
import com.android.messaging.datamodel.DataModelException;
import com.android.messaging.datamodel.DatabaseWrapper;
import com.android.messaging.datamodel.MessagingContentProvider;
import com.android.messaging.datamodel.MmsFileProvider;
@@ -211,7 +210,7 @@ public class ProcessDownloadedMmsAction extends Action {
}
@Override
protected Bundle doBackgroundWork() throws DataModelException {
protected Bundle doBackgroundWork() {
final Context context = Factory.get().getApplicationContext();
final int subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID);
final String messageId = actionParameters.getString(KEY_MESSAGE_ID);

View File

@@ -28,7 +28,6 @@ import com.android.messaging.Factory;
import com.android.messaging.datamodel.BugleDatabaseOperations;
import com.android.messaging.datamodel.BugleNotifications;
import com.android.messaging.datamodel.DataModel;
import com.android.messaging.datamodel.DataModelException;
import com.android.messaging.datamodel.DatabaseWrapper;
import com.android.messaging.datamodel.MessagingContentProvider;
import com.android.messaging.datamodel.SyncManager;
@@ -161,7 +160,7 @@ public class ReceiveMmsMessageAction extends Action implements Parcelable {
}
@Override
protected Bundle doBackgroundWork() throws DataModelException {
protected Bundle doBackgroundWork() {
final Context context = Factory.get().getApplicationContext();
final int subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID);
final String transactionId = actionParameters.getString(KEY_TRANSACTION_ID);

View File

@@ -55,10 +55,7 @@ import com.android.messaging.util.PhoneUtils;
import com.android.messaging.widget.WidgetConversationProvider;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class ConversationData extends BindableData {
@@ -67,40 +64,6 @@ public class ConversationData extends BindableData {
private static final long LAST_MESSAGE_TIMESTAMP_NaN = -1;
private static final int MESSAGE_COUNT_NaN = -1;
/**
* Takes a conversation id and a list of message ids and computes the positions
* for each message.
*/
public List<Integer> getPositions(final String conversationId, final List<Long> ids) {
final ArrayList<Integer> result = new ArrayList<>();
if (ids.isEmpty()) {
return result;
}
final Cursor c = new ConversationData.ReversedCursor(
DataModel.get().getDatabase().rawQuery(
ConversationMessageData.getConversationMessageIdsQuerySql(),
new String [] { conversationId }));
if (c != null) {
try {
final Set<Long> idsSet = new HashSet<>(ids);
if (c.moveToLast()) {
do {
final long messageId = c.getLong(0);
if (idsSet.contains(messageId)) {
result.add(c.getPosition());
}
} while (c.moveToPrevious());
}
} finally {
c.close();
}
}
Collections.sort(result);
return result;
}
public interface ConversationDataListener {
void onConversationMessagesCursorUpdated(ConversationData data, Cursor cursor,
@Nullable ConversationMessageData newestMessage, boolean isSync);

View File

@@ -477,10 +477,6 @@ public class ConversationMessageData {
return mProtocol == (MessageData.PROTOCOL_SMS);
}
final int getProtocol() {
return mProtocol;
}
public final int getStatus() {
return mStatus;
}

View File

@@ -639,14 +639,6 @@ public class MessageData implements Parcelable {
|| mProtocol == MessageData.PROTOCOL_MMS_PUSH_NOTIFICATION;
}
public static boolean getIsMmsNotification(final int protocol) {
return (protocol == MessageData.PROTOCOL_MMS_PUSH_NOTIFICATION);
}
public final boolean getIsMmsNotification() {
return getIsMmsNotification(mProtocol);
}
public static boolean getIsSms(final int protocol) {
return protocol == (MessageData.PROTOCOL_SMS);
}
@@ -801,10 +793,6 @@ public class MessageData implements Parcelable {
}
}
public final void setRetryStartTimestamp(final long timestamp) {
mRetryStartTimestamp = timestamp;
}
public final void setRawTelephonyStatus(final int rawStatus) {
mRawStatus = rawStatus;
}

View File

@@ -1,5 +1,6 @@
/*
* Copyright (C) 2015 The Android Open Source Project
* Copyright (C) 2024 The LineageOS Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,7 +26,6 @@ import com.android.messaging.util.ImageUtils;
import com.android.messaging.util.UriUtil;
public class AvatarRequestDescriptor extends UriImageRequestDescriptor {
final boolean isWearBackground;
public AvatarRequestDescriptor(final Uri uri, final int desiredWidth,
final int desiredHeight) {
@@ -45,7 +45,6 @@ public class AvatarRequestDescriptor extends UriImageRequestDescriptor {
ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */);
Assert.isTrue(uri == null || UriUtil.isLocalResourceUri(uri) ||
AvatarUriUtil.isAvatarUri(uri));
this.isWearBackground = isWearBackground;
}
@Override

View File

@@ -79,12 +79,10 @@ public class NetworkUriImageRequest<D extends UriImageRequestDescriptor> extends
return false;
}
@SuppressWarnings("deprecation")
@Override
public Bitmap loadBitmapInternal() throws IOException {
public Bitmap loadBitmapInternal() {
Assert.isNotMainThread();
InputStream inputStream = null;
Bitmap bitmap = null;
HttpURLConnection connection = null;
try {
@@ -109,9 +107,6 @@ public class NetworkUriImageRequest<D extends UriImageRequestDescriptor> extends
"IOException trying to get inputStream for image with url: "
+ mDescriptor.uri, e);
} finally {
if (inputStream != null) {
inputStream.close();
}
if (connection != null) {
connection.disconnect();
}

View File

@@ -16,8 +16,6 @@
package com.android.messaging.mmslib;
import android.app.DownloadManager;
import android.content.Context;
import android.net.Uri;
import android.provider.BaseColumns;
@@ -39,49 +37,8 @@ public final class Downloads {
* @hide
*/
public static final class Impl implements BaseColumns {
private Impl() {}
/**
* The permission to access the download manager
*/
public static final String PERMISSION_ACCESS = "android.permission.ACCESS_DOWNLOAD_MANAGER";
/**
* The permission to access the download manager's advanced functions
*/
public static final String PERMISSION_ACCESS_ADVANCED =
"android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED";
/**
* The permission to access the all the downloads in the manager.
*/
public static final String PERMISSION_ACCESS_ALL =
"android.permission.ACCESS_ALL_DOWNLOADS";
/**
* The permission to directly access the download manager's cache
* directory
*/
public static final String PERMISSION_CACHE = "android.permission.ACCESS_CACHE_FILESYSTEM";
/**
* The permission to send broadcasts on download completion
*/
public static final String PERMISSION_SEND_INTENTS =
"android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS";
/**
* The permission to download files to the cache partition that won't be automatically
* purged when space is needed.
*/
public static final String PERMISSION_CACHE_NON_PURGEABLE =
"android.permission.DOWNLOAD_CACHE_NON_PURGEABLE";
/**
* The permission to download files without any system notification being shown.
*/
public static final String PERMISSION_NO_NOTIFICATION =
"android.permission.DOWNLOAD_WITHOUT_NOTIFICATION";
private Impl() {
}
/**
* The content:// URI to access downloads owned by the caller's UID.
@@ -89,521 +46,6 @@ public final class Downloads {
public static final Uri CONTENT_URI =
Uri.parse("content://downloads/my_downloads");
/**
* The content URI for accessing all downloads across all UIDs (requires the
* ACCESS_ALL_DOWNLOADS permission).
*/
public static final Uri ALL_DOWNLOADS_CONTENT_URI =
Uri.parse("content://downloads/all_downloads");
/** URI segment to access a publicly accessible downloaded file */
public static final String PUBLICLY_ACCESSIBLE_DOWNLOADS_URI_SEGMENT = "public_downloads";
/**
* The content URI for accessing publicly accessible downloads (i.e., it requires no
* permissions to access this downloaded file)
*/
public static final Uri PUBLICLY_ACCESSIBLE_DOWNLOADS_URI =
Uri.parse("content://downloads/" + PUBLICLY_ACCESSIBLE_DOWNLOADS_URI_SEGMENT);
/**
* Broadcast Action: this is sent by the download manager to the app
* that had initiated a download when that download completes. The
* download's content: uri is specified in the intent's data.
*/
public static final String ACTION_DOWNLOAD_COMPLETED =
"android.intent.action.DOWNLOAD_COMPLETED";
/**
* Broadcast Action: this is sent by the download manager to the app
* that had initiated a download when the user selects the notification
* associated with that download. The download's content: uri is specified
* in the intent's data if the click is associated with a single download,
* or Downloads.CONTENT_URI if the notification is associated with
* multiple downloads.
* Note: this is not currently sent for downloads that have completed
* successfully.
*/
public static final String ACTION_NOTIFICATION_CLICKED =
"android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED";
/**
* The name of the column containing the URI of the data being downloaded.
* <P>Type: TEXT</P>
* <P>Owner can Init/Read</P>
*/
public static final String COLUMN_URI = "uri";
/**
* The name of the column containing application-specific data.
* <P>Type: TEXT</P>
* <P>Owner can Init/Read/Write</P>
*/
public static final String COLUMN_APP_DATA = "entity";
/**
* The name of the column containing the flags that indicates whether
* the initiating application is capable of verifying the integrity of
* the downloaded file. When this flag is set, the download manager
* performs downloads and reports success even in some situations where
* it can't guarantee that the download has completed (e.g. when doing
* a byte-range request without an ETag, or when it can't determine
* whether a download fully completed).
* <P>Type: BOOLEAN</P>
* <P>Owner can Init</P>
*/
public static final String COLUMN_NO_INTEGRITY = "no_integrity";
/**
* The name of the column containing the filename that the initiating
* application recommends. When possible, the download manager will attempt
* to use this filename, or a variation, as the actual name for the file.
* <P>Type: TEXT</P>
* <P>Owner can Init</P>
*/
public static final String COLUMN_FILE_NAME_HINT = "hint";
/**
* The name of the column containing the filename where the downloaded data
* was actually stored.
* <P>Type: TEXT</P>
* <P>Owner can Read</P>
*/
public static final String _DATA = "_data";
/**
* The name of the column containing the MIME type of the downloaded data.
* <P>Type: TEXT</P>
* <P>Owner can Init/Read</P>
*/
public static final String COLUMN_MIME_TYPE = "mimetype";
/**
* The name of the column containing the flag that controls the destination
* of the download. See the DESTINATION_* constants for a list of legal values.
* <P>Type: INTEGER</P>
* <P>Owner can Init</P>
*/
public static final String COLUMN_DESTINATION = "destination";
/**
* The name of the column containing the flags that controls whether the
* download is displayed by the UI. See the VISIBILITY_* constants for
* a list of legal values.
* <P>Type: INTEGER</P>
* <P>Owner can Init/Read/Write</P>
*/
public static final String COLUMN_VISIBILITY = "visibility";
/**
* The name of the column containing the current control state of the download.
* Applications can write to this to control (pause/resume) the download.
* the CONTROL_* constants for a list of legal values.
* <P>Type: INTEGER</P>
* <P>Owner can Read</P>
*/
public static final String COLUMN_CONTROL = "control";
/**
* The name of the column containing the current status of the download.
* Applications can read this to follow the progress of each download. See
* the STATUS_* constants for a list of legal values.
* <P>Type: INTEGER</P>
* <P>Owner can Read</P>
*/
public static final String COLUMN_STATUS = "status";
/**
* The name of the column containing the date at which some interesting
* status changed in the download. Stored as a System.currentTimeMillis()
* value.
* <P>Type: BIGINT</P>
* <P>Owner can Read</P>
*/
public static final String COLUMN_LAST_MODIFICATION = "lastmod";
/**
* The name of the column containing the package name of the application
* that initiating the download. The download manager will send
* notifications to a component in this package when the download completes.
* <P>Type: TEXT</P>
* <P>Owner can Init/Read</P>
*/
public static final String COLUMN_NOTIFICATION_PACKAGE = "notificationpackage";
/**
* The name of the column containing the component name of the class that
* will receive notifications associated with the download. The
* package/class combination is passed to
* Intent.setClassName(String,String).
* <P>Type: TEXT</P>
* <P>Owner can Init/Read</P>
*/
public static final String COLUMN_NOTIFICATION_CLASS = "notificationclass";
/**
* If extras are specified when requesting a download they will be provided in the intent
* that is sent to the specified class and package when a download has finished.
* <P>Type: TEXT</P>
* <P>Owner can Init</P>
*/
public static final String COLUMN_NOTIFICATION_EXTRAS = "notificationextras";
/**
* The name of the column contain the values of the cookie to be used for
* the download. This is used directly as the value for the Cookie: HTTP
* header that gets sent with the request.
* <P>Type: TEXT</P>
* <P>Owner can Init</P>
*/
public static final String COLUMN_COOKIE_DATA = "cookiedata";
/**
* The name of the column containing the user agent that the initiating
* application wants the download manager to use for this download.
* <P>Type: TEXT</P>
* <P>Owner can Init</P>
*/
public static final String COLUMN_USER_AGENT = "useragent";
/**
* The name of the column containing the referer (sic) that the initiating
* application wants the download manager to use for this download.
* <P>Type: TEXT</P>
* <P>Owner can Init</P>
*/
public static final String COLUMN_REFERER = "referer";
/**
* The name of the column containing the total size of the file being
* downloaded.
* <P>Type: INTEGER</P>
* <P>Owner can Read</P>
*/
public static final String COLUMN_TOTAL_BYTES = "total_bytes";
/**
* The name of the column containing the size of the part of the file that
* has been downloaded so far.
* <P>Type: INTEGER</P>
* <P>Owner can Read</P>
*/
public static final String COLUMN_CURRENT_BYTES = "current_bytes";
/**
* The name of the column where the initiating application can provide the
* UID of another application that is allowed to access this download. If
* multiple applications share the same UID, all those applications will be
* allowed to access this download. This column can be updated after the
* download is initiated. This requires the permission
* android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED.
* <P>Type: INTEGER</P>
* <P>Owner can Init</P>
*/
public static final String COLUMN_OTHER_UID = "otheruid";
/**
* The name of the column where the initiating application can provided the
* title of this download. The title will be displayed ito the user in the
* list of downloads.
* <P>Type: TEXT</P>
* <P>Owner can Init/Read/Write</P>
*/
public static final String COLUMN_TITLE = "title";
/**
* The name of the column where the initiating application can provide the
* description of this download. The description will be displayed to the
* user in the list of downloads.
* <P>Type: TEXT</P>
* <P>Owner can Init/Read/Write</P>
*/
public static final String COLUMN_DESCRIPTION = "description";
/**
* The name of the column indicating whether the download was requesting through the public
* API. This controls some differences in behavior.
* <P>Type: BOOLEAN</P>
* <P>Owner can Init/Read</P>
*/
public static final String COLUMN_IS_PUBLIC_API = "is_public_api";
/**
* The name of the column holding a bitmask of allowed network types. This is only used for
* public API downloads.
* <P>Type: INTEGER</P>
* <P>Owner can Init/Read</P>
*/
public static final String COLUMN_ALLOWED_NETWORK_TYPES = "allowed_network_types";
/**
* The name of the column indicating whether roaming connections can be used. This is only
* used for public API downloads.
* <P>Type: BOOLEAN</P>
* <P>Owner can Init/Read</P>
*/
public static final String COLUMN_ALLOW_ROAMING = "allow_roaming";
/**
* The name of the column indicating whether metered connections can be used. This is only
* used for public API downloads.
* <P>Type: BOOLEAN</P>
* <P>Owner can Init/Read</P>
*/
public static final String COLUMN_ALLOW_METERED = "allow_metered";
/**
* Whether or not this download should be displayed in the system's Downloads UI. Defaults
* to true.
* <P>Type: INTEGER</P>
* <P>Owner can Init/Read</P>
*/
public static final String COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI = "is_visible_in_downloads_ui";
/**
* If true, the user has confirmed that this download can proceed over the mobile network
* even though it exceeds the recommended maximum size.
* <P>Type: BOOLEAN</P>
*/
public static final String COLUMN_BYPASS_RECOMMENDED_SIZE_LIMIT =
"bypass_recommended_size_limit";
/**
* Set to true if this download is deleted. It is completely removed from the database
* when MediaProvider database also deletes the metadata asociated with this downloaded
* file.
* <P>Type: BOOLEAN</P>
* <P>Owner can Read</P>
*/
public static final String COLUMN_DELETED = "deleted";
/**
* The URI to the corresponding entry in MediaProvider for this downloaded entry. It is
* used to delete the entries from MediaProvider database when it is deleted from the
* downloaded list.
* <P>Type: TEXT</P>
* <P>Owner can Read</P>
*/
public static final String COLUMN_MEDIAPROVIDER_URI = "mediaprovider_uri";
/**
* The column that is used to remember whether the media scanner was invoked.
* It can take the values: null or 0(not scanned), 1(scanned), 2 (not scannable).
* <P>Type: TEXT</P>
*/
public static final String COLUMN_MEDIA_SCANNED = "scanned";
/**
* The column with errorMsg for a failed downloaded.
* Used only for debugging purposes.
* <P>Type: TEXT</P>
*/
public static final String COLUMN_ERROR_MSG = "errorMsg";
/**
* This column stores the source of the last update to this row.
* This column is only for internal use.
* Valid values are indicated by LAST_UPDATESRC_* constants.
* <P>Type: INT</P>
*/
public static final String COLUMN_LAST_UPDATESRC = "lastUpdateSrc";
/** The column that is used to count retries */
public static final String COLUMN_FAILED_CONNECTIONS = "numfailed";
/**
* default value for {@link #COLUMN_LAST_UPDATESRC}.
* This value is used when this column's value is not relevant.
*/
public static final int LAST_UPDATESRC_NOT_RELEVANT = 0;
/**
* One of the values taken by {@link #COLUMN_LAST_UPDATESRC}.
* This value is used when the update is NOT to be relayed to the DownloadService
* (and thus spare DownloadService from scanning the database when this change occurs)
*/
public static final int LAST_UPDATESRC_DONT_NOTIFY_DOWNLOADSVC = 1;
/*
* Lists the destinations that an application can specify for a download.
*/
/**
* This download will be saved to the external storage. This is the
* default behavior, and should be used for any file that the user
* can freely access, copy, delete. Even with that destination,
* unencrypted DRM files are saved in secure internal storage.
* Downloads to the external destination only write files for which
* there is a registered handler. The resulting files are accessible
* by filename to all applications.
*/
public static final int DESTINATION_EXTERNAL = 0;
/**
* This download will be saved to the download manager's private
* partition. This is the behavior used by applications that want to
* download private files that are used and deleted soon after they
* get downloaded. All file types are allowed, and only the initiating
* application can access the file (indirectly through a content
* provider). This requires the
* android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED permission.
*/
public static final int DESTINATION_CACHE_PARTITION = 1;
/**
* This download will be saved to the download manager's private
* partition and will be purged as necessary to make space. This is
* for private files (similar to CACHE_PARTITION) that aren't deleted
* immediately after they are used, and are kept around by the download
* manager as long as space is available.
*/
public static final int DESTINATION_CACHE_PARTITION_PURGEABLE = 2;
/**
* This download will be saved to the download manager's private
* partition, as with DESTINATION_CACHE_PARTITION, but the download
* will not proceed if the user is on a roaming data connection.
*/
public static final int DESTINATION_CACHE_PARTITION_NOROAMING = 3;
/**
* This download will be saved to the location given by the file URI in
* {@link #COLUMN_FILE_NAME_HINT}.
*/
public static final int DESTINATION_FILE_URI = 4;
/**
* This download will be saved to the system cache ("/cache")
* partition. This option is only used by system apps and so it requires
* android.permission.ACCESS_CACHE_FILESYSTEM permission.
*/
public static final int DESTINATION_SYSTEMCACHE_PARTITION = 5;
/**
* This download was completed by the caller (i.e., NOT downloadmanager)
* and caller wants to have this download displayed in Downloads App.
*/
public static final int DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD = 6;
/**
* This download is allowed to run.
*/
public static final int CONTROL_RUN = 0;
/**
* This download must pause at the first opportunity.
*/
public static final int CONTROL_PAUSED = 1;
/*
* Lists the states that the download manager can set on a download
* to notify applications of the download progress.
* The codes follow the HTTP families:<br>
* 1xx: informational<br>
* 2xx: success<br>
* 3xx: redirects (not used by the download manager)<br>
* 4xx: client errors<br>
* 5xx: server errors
*/
/**
* Returns whether the status is informational (i.e. 1xx).
*/
public static boolean isStatusInformational(int status) {
return (status >= 100 && status < 200);
}
/**
* Returns whether the status is a success (i.e. 2xx).
*/
public static boolean isStatusSuccess(int status) {
return (status >= 200 && status < 300);
}
/**
* Returns whether the status is an error (i.e. 4xx or 5xx).
*/
public static boolean isStatusError(int status) {
return (status >= 400 && status < 600);
}
/**
* Returns whether the status is a client error (i.e. 4xx).
*/
public static boolean isStatusClientError(int status) {
return (status >= 400 && status < 500);
}
/**
* Returns whether the status is a server error (i.e. 5xx).
*/
public static boolean isStatusServerError(int status) {
return (status >= 500 && status < 600);
}
/**
* this method determines if a notification should be displayed for a
* given {@link #COLUMN_VISIBILITY} value
* @param visibility the value of {@link #COLUMN_VISIBILITY}.
* @return true if the notification should be displayed. false otherwise.
*/
public static boolean isNotificationToBeDisplayed(int visibility) {
return visibility == DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED ||
visibility == DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION;
}
/**
* Returns whether the download has completed (either with success or
* error).
*/
public static boolean isStatusCompleted(int status) {
return (status >= 200 && status < 300) || (status >= 400 && status < 600);
}
/**
* This download hasn't stated yet
*/
public static final int STATUS_PENDING = 190;
/**
* This download has started
*/
public static final int STATUS_RUNNING = 192;
/**
* This download has been paused by the owning app.
*/
public static final int STATUS_PAUSED_BY_APP = 193;
/**
* This download encountered some network error and is waiting before retrying the request.
*/
public static final int STATUS_WAITING_TO_RETRY = 194;
/**
* This download is waiting for network connectivity to proceed.
*/
public static final int STATUS_WAITING_FOR_NETWORK = 195;
/**
* This download exceeded a size limit for mobile networks and is waiting for a Wi-Fi
* connection to proceed.
*/
public static final int STATUS_QUEUED_FOR_WIFI = 196;
/**
* This download couldn't be completed due to insufficient storage
* space. Typically, this is because the SD card is full.
*/
public static final int STATUS_INSUFFICIENT_SPACE_ERROR = 198;
/**
* This download couldn't be completed because no external storage
* device was found. Typically, this is because the SD card is not
* mounted.
*/
public static final int STATUS_DEVICE_NOT_FOUND_ERROR = 199;
/**
* This download has successfully completed.
* Warning: there might be other status values that indicate success
@@ -612,56 +54,12 @@ public final class Downloads {
*/
public static final int STATUS_SUCCESS = 200;
/**
* This request couldn't be parsed. This is also used when processing
* requests with unknown/unsupported URI schemes.
*/
public static final int STATUS_BAD_REQUEST = 400;
/**
* This download can't be performed because the content type cannot be
* handled.
*/
public static final int STATUS_NOT_ACCEPTABLE = 406;
/**
* This download cannot be performed because the length cannot be
* determined accurately. This is the code for the HTTP error "Length
* Required", which is typically used when making requests that require
* a content length but don't have one, and it is also used in the
* client when a response is received whose length cannot be determined
* accurately (therefore making it impossible to know when a download
* completes).
*/
public static final int STATUS_LENGTH_REQUIRED = 411;
/**
* This download was interrupted and cannot be resumed.
* This is the code for the HTTP error "Precondition Failed", and it is
* also used in situations where the client doesn't have an ETag at all.
*/
public static final int STATUS_PRECONDITION_FAILED = 412;
/**
* The lowest-valued error status that is not an actual HTTP status code.
*/
public static final int MIN_ARTIFICIAL_ERROR_STATUS = 488;
/**
* The requested destination file already exists.
*/
public static final int STATUS_FILE_ALREADY_EXISTS_ERROR = 488;
/**
* Some possibly transient error occurred, but we can't resume the download.
*/
public static final int STATUS_CANNOT_RESUME = 489;
/**
* This download was canceled
*/
public static final int STATUS_CANCELED = 490;
/**
* This download has completed with an error.
* Warning: there will be other status values that indicate errors in
@@ -672,136 +70,7 @@ public final class Downloads {
/**
* This download couldn't be completed because of a storage issue.
* Typically, that's because the filesystem is missing or full.
* Use the more specific {@link #STATUS_INSUFFICIENT_SPACE_ERROR}
* and {@link #STATUS_DEVICE_NOT_FOUND_ERROR} when appropriate.
*/
public static final int STATUS_FILE_ERROR = 492;
/**
* This download couldn't be completed because of an HTTP
* redirect response that the download manager couldn't
* handle.
*/
public static final int STATUS_UNHANDLED_REDIRECT = 493;
/**
* This download couldn't be completed because of an
* unspecified unhandled HTTP code.
*/
public static final int STATUS_UNHANDLED_HTTP_CODE = 494;
/**
* This download couldn't be completed because of an
* error receiving or processing data at the HTTP level.
*/
public static final int STATUS_HTTP_DATA_ERROR = 495;
/**
* This download couldn't be completed because of an
* HttpException while setting up the request.
*/
public static final int STATUS_HTTP_EXCEPTION = 496;
/**
* This download couldn't be completed because there were
* too many redirects.
*/
public static final int STATUS_TOO_MANY_REDIRECTS = 497;
/**
* This download has failed because requesting application has been
* blocked by {@link NetworkPolicyManager}.
*
* @hide
* @deprecated since behavior now uses
* {@link #STATUS_WAITING_FOR_NETWORK}
*/
@Deprecated
public static final int STATUS_BLOCKED = 498;
/** {@hide} */
public static String statusToString(int status) {
switch (status) {
case STATUS_PENDING: return "PENDING";
case STATUS_RUNNING: return "RUNNING";
case STATUS_PAUSED_BY_APP: return "PAUSED_BY_APP";
case STATUS_WAITING_TO_RETRY: return "WAITING_TO_RETRY";
case STATUS_WAITING_FOR_NETWORK: return "WAITING_FOR_NETWORK";
case STATUS_QUEUED_FOR_WIFI: return "QUEUED_FOR_WIFI";
case STATUS_INSUFFICIENT_SPACE_ERROR: return "INSUFFICIENT_SPACE_ERROR";
case STATUS_DEVICE_NOT_FOUND_ERROR: return "DEVICE_NOT_FOUND_ERROR";
case STATUS_SUCCESS: return "SUCCESS";
case STATUS_BAD_REQUEST: return "BAD_REQUEST";
case STATUS_NOT_ACCEPTABLE: return "NOT_ACCEPTABLE";
case STATUS_LENGTH_REQUIRED: return "LENGTH_REQUIRED";
case STATUS_PRECONDITION_FAILED: return "PRECONDITION_FAILED";
case STATUS_FILE_ALREADY_EXISTS_ERROR: return "FILE_ALREADY_EXISTS_ERROR";
case STATUS_CANNOT_RESUME: return "CANNOT_RESUME";
case STATUS_CANCELED: return "CANCELED";
case STATUS_UNKNOWN_ERROR: return "UNKNOWN_ERROR";
case STATUS_FILE_ERROR: return "FILE_ERROR";
case STATUS_UNHANDLED_REDIRECT: return "UNHANDLED_REDIRECT";
case STATUS_UNHANDLED_HTTP_CODE: return "UNHANDLED_HTTP_CODE";
case STATUS_HTTP_DATA_ERROR: return "HTTP_DATA_ERROR";
case STATUS_HTTP_EXCEPTION: return "HTTP_EXCEPTION";
case STATUS_TOO_MANY_REDIRECTS: return "TOO_MANY_REDIRECTS";
case STATUS_BLOCKED: return "BLOCKED";
default: return Integer.toString(status);
}
}
/**
* This download is visible but only shows in the notifications
* while it's in progress.
*/
public static final int VISIBILITY_VISIBLE = DownloadManager.Request.VISIBILITY_VISIBLE;
/**
* This download is visible and shows in the notifications while
* in progress and after completion.
*/
public static final int VISIBILITY_VISIBLE_NOTIFY_COMPLETED =
DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED;
/**
* This download doesn't show in the UI or in the notifications.
*/
public static final int VISIBILITY_HIDDEN = DownloadManager.Request.VISIBILITY_HIDDEN;
/**
* Constants related to HTTP request headers associated with each download.
*/
public static class RequestHeaders {
public static final String HEADERS_DB_TABLE = "request_headers";
public static final String COLUMN_DOWNLOAD_ID = "download_id";
public static final String COLUMN_HEADER = "header";
public static final String COLUMN_VALUE = "value";
/**
* Path segment to add to a download URI to retrieve request headers
*/
public static final String URI_SEGMENT = "headers";
/**
* Prefix for ContentValues keys that contain HTTP header lines, to be passed to
* DownloadProvider.insert().
*/
public static final String INSERT_KEY_PREFIX = "http_header_";
}
}
/**
* Query where clause for general querying.
*/
private static final String QUERY_WHERE_CLAUSE = Impl.COLUMN_NOTIFICATION_PACKAGE + "=? AND "
+ Impl.COLUMN_NOTIFICATION_CLASS + "=?";
/**
* Delete all the downloads for a package/class pair.
*/
public static final void removeAllDownloadsByPackage(
Context context, String notificationPackage, String notificationClass) {
context.getContentResolver().delete(Impl.CONTENT_URI, QUERY_WHERE_CLAUSE,
new String[] { notificationPackage, notificationClass });
}
}

View File

@@ -26,7 +26,6 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
/**
* Encoded-string-value = Text-string | Value-length Char-set Text-string
@@ -239,43 +238,6 @@ public class EncodedStringValue implements Cloneable {
return ret;
}
/**
* Extract an EncodedStringValue[] from a given String.
*/
public static EncodedStringValue[] extract(String src) {
String[] values = src.split(";");
ArrayList<EncodedStringValue> list = new ArrayList<>();
for (int i = 0; i < values.length; i++) {
if (values[i].length() > 0) {
list.add(new EncodedStringValue(values[i]));
}
}
int len = list.size();
if (len > 0) {
return list.toArray(new EncodedStringValue[len]);
} else {
return null;
}
}
/**
* Concatenate an EncodedStringValue[] into a single String.
*/
public static String concat(EncodedStringValue[] addr) {
StringBuilder sb = new StringBuilder();
int maxIndex = addr.length - 1;
for (int i = 0; i <= maxIndex; i++) {
sb.append(addr[i].getString());
if (i < maxIndex) {
sb.append(";");
}
}
return sb.toString();
}
public static EncodedStringValue copy(EncodedStringValue value) {
if (value == null) {
return null;

View File

@@ -207,13 +207,6 @@ public class PduParser {
// or "application/vnd.wap.multipart.related"
// or "application/vnd.wap.multipart.alternative"
return retrieveConf;
} else if (ctTypeStr.equals(ContentType.MMS_MULTIPART_ALTERNATIVE)) {
// "application/vnd.wap.multipart.alternative"
// should take only the first part.
PduPart firstPart = mBody.getPart(0);
mBody.removeAll();
mBody.addPart(0, firstPart);
return retrieveConf;
}
return null;
case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:

View File

@@ -23,7 +23,6 @@ import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.provider.MediaStore;
@@ -77,21 +76,6 @@ public class PduPersister {
public static final String TEMPORARY_DRM_OBJECT_URI =
"content://mms/" + Long.MAX_VALUE + "/part";
/**
* Indicate that we transiently failed to process a MM.
*/
public static final int PROC_STATUS_TRANSIENT_FAILURE = 1;
/**
* Indicate that we permanently failed to process a MM.
*/
public static final int PROC_STATUS_PERMANENTLY_FAILURE = 2;
/**
* Indicate that we have successfully processed a MM.
*/
public static final int PROC_STATUS_COMPLETED = 3;
public static final String BEGIN_VCARD = "BEGIN:VCARD";
private static PduPersister sPersister;
@@ -1046,244 +1030,6 @@ public class PduPersister {
return path;
}
private void updateAddress(
final long msgId, final int type, final EncodedStringValue[] array) {
// Delete old address information and then insert new ones.
SqliteWrapper.delete(mContext, mContentResolver,
Uri.parse("content://mms/" + msgId + "/addr"),
Addr.TYPE + "=" + type, null);
persistAddress(msgId, type, array);
}
/**
* Update headers of a SendReq.
*
* @param uri The PDU which need to be updated.
* @param pdu New headers.
* @throws MmsException Bad URI or updating failed.
*/
public void updateHeaders(final Uri uri, final SendReq sendReq) {
synchronized (PDU_CACHE_INSTANCE) {
// If the cache item is getting updated, wait until it's done updating before
// purging it.
if (PDU_CACHE_INSTANCE.isUpdating(uri)) {
if (LOCAL_LOGV) {
LogUtil.v(TAG, "updateHeaders: " + uri + " blocked by isUpdating()");
}
try {
PDU_CACHE_INSTANCE.wait();
} catch (final InterruptedException e) {
Log.e(TAG, "updateHeaders: ", e);
}
}
}
PDU_CACHE_INSTANCE.purge(uri);
final ContentValues values = new ContentValues(10);
final byte[] contentType = sendReq.getContentType();
if (contentType != null) {
values.put(Mms.CONTENT_TYPE, toIsoString(contentType));
}
final long date = sendReq.getDate();
if (date != -1) {
values.put(Mms.DATE, date);
}
final int deliveryReport = sendReq.getDeliveryReport();
if (deliveryReport != 0) {
values.put(Mms.DELIVERY_REPORT, deliveryReport);
}
final long expiry = sendReq.getExpiry();
if (expiry != -1) {
values.put(Mms.EXPIRY, expiry);
}
final byte[] msgClass = sendReq.getMessageClass();
if (msgClass != null) {
values.put(Mms.MESSAGE_CLASS, toIsoString(msgClass));
}
final int priority = sendReq.getPriority();
if (priority != 0) {
values.put(Mms.PRIORITY, priority);
}
final int readReport = sendReq.getReadReport();
if (readReport != 0) {
values.put(Mms.READ_REPORT, readReport);
}
final byte[] transId = sendReq.getTransactionId();
if (transId != null) {
values.put(Mms.TRANSACTION_ID, toIsoString(transId));
}
final EncodedStringValue subject = sendReq.getSubject();
if (subject != null) {
values.put(Mms.SUBJECT, toIsoString(subject.getTextString()));
values.put(Mms.SUBJECT_CHARSET, subject.getCharacterSet());
} else {
values.put(Mms.SUBJECT, "");
}
final long messageSize = sendReq.getMessageSize();
if (messageSize > 0) {
values.put(Mms.MESSAGE_SIZE, messageSize);
}
final PduHeaders headers = sendReq.getPduHeaders();
final HashSet<String> recipients = new HashSet<>();
for (final int addrType : ADDRESS_FIELDS) {
EncodedStringValue[] array = null;
if (addrType == PduHeaders.FROM) {
final EncodedStringValue v = headers.getEncodedStringValue(addrType);
if (v != null) {
array = new EncodedStringValue[1];
array[0] = v;
}
} else {
array = headers.getEncodedStringValues(addrType);
}
if (array != null) {
final long msgId = ContentUris.parseId(uri);
updateAddress(msgId, addrType, array);
if (addrType == PduHeaders.TO) {
for (final EncodedStringValue v : array) {
if (v != null) {
recipients.add(v.getString());
}
}
}
}
}
if (!recipients.isEmpty()) {
final long threadId = MmsSmsUtils.Threads.getOrCreateThreadId(mContext, recipients);
values.put(Mms.THREAD_ID, threadId);
}
SqliteWrapper.update(mContext, mContentResolver, uri, values, null, null);
}
private void updatePart(final Uri uri, final PduPart part,
final Map<Uri, InputStream> preOpenedFiles)
throws MmsException {
final ContentValues values = new ContentValues(7);
final int charset = part.getCharset();
if (charset != 0) {
values.put(Part.CHARSET, charset);
}
String contentType = null;
if (part.getContentType() != null) {
contentType = toIsoString(part.getContentType());
values.put(Part.CONTENT_TYPE, contentType);
} else {
throw new MmsException("MIME type of the part must be set.");
}
getValues(part, values);
SqliteWrapper.update(mContext, mContentResolver, uri, values, null, null);
// Only update the data when:
// 1. New binary data supplied or
// 2. The Uri of the part is different from the current one.
if ((part.getData() != null)
|| (!uri.equals(part.getDataUri()))) {
persistData(part, uri, contentType, preOpenedFiles);
}
}
/**
* Update all parts of a PDU.
*
* @param uri The PDU which need to be updated.
* @param body New message body of the PDU.
* @param preOpenedFiles if not null, a map of preopened InputStreams for the parts.
* @throws MmsException Bad URI or updating failed.
*/
public void updateParts(final Uri uri, final PduBody body,
final Map<Uri, InputStream> preOpenedFiles)
throws MmsException {
try {
PduCacheEntry cacheEntry;
synchronized (PDU_CACHE_INSTANCE) {
if (PDU_CACHE_INSTANCE.isUpdating(uri)) {
if (LOCAL_LOGV) {
LogUtil.v(TAG, "updateParts: " + uri + " blocked by isUpdating()");
}
try {
PDU_CACHE_INSTANCE.wait();
} catch (final InterruptedException e) {
Log.e(TAG, "updateParts: ", e);
}
cacheEntry = PDU_CACHE_INSTANCE.get(uri);
if (cacheEntry != null) {
((MultimediaMessagePdu) cacheEntry.getPdu()).setBody(body);
}
}
// Tell the cache to indicate to other callers that this item
// is currently being updated.
PDU_CACHE_INSTANCE.setUpdating(uri, true);
}
final ArrayList<PduPart> toBeCreated = new ArrayList<>();
final ArrayMap<Uri, PduPart> toBeUpdated = new ArrayMap<>();
final int partsNum = body.getPartsNum();
final StringBuilder filter = new StringBuilder().append('(');
for (int i = 0; i < partsNum; i++) {
final PduPart part = body.getPart(i);
final Uri partUri = part.getDataUri();
if ((partUri == null) || TextUtils.isEmpty(partUri.getAuthority())
|| !partUri.getAuthority().startsWith("mms")) {
toBeCreated.add(part);
} else {
toBeUpdated.put(partUri, part);
// Don't use 'i > 0' to determine whether we should append
// 'AND' since 'i = 0' may be skipped in another branch.
if (filter.length() > 1) {
filter.append(" AND ");
}
filter.append(Part._ID);
filter.append("!=");
DatabaseUtils.appendEscapedSQLString(filter, partUri.getLastPathSegment());
}
}
filter.append(')');
final long msgId = ContentUris.parseId(uri);
// Remove the parts which doesn't exist anymore.
SqliteWrapper.delete(mContext, mContentResolver,
Uri.parse(Mms.CONTENT_URI + "/" + msgId + "/part"),
filter.length() > 2 ? filter.toString() : null, null);
// Create new parts which didn't exist before.
for (final PduPart part : toBeCreated) {
persistPart(part, msgId, preOpenedFiles);
}
// Update the modified parts.
for (final Map.Entry<Uri, PduPart> e : toBeUpdated.entrySet()) {
updatePart(e.getKey(), e.getValue(), preOpenedFiles);
}
} finally {
synchronized (PDU_CACHE_INSTANCE) {
PDU_CACHE_INSTANCE.setUpdating(uri, false);
PDU_CACHE_INSTANCE.notifyAll();
}
}
}
/**
* Persist a PDU object to specific location in the storage.
*

View File

@@ -1,5 +1,6 @@
/*
* Copyright (C) 2012 The Android Open Source Project
* Copyright (C) 2024 The LineageOS Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,46 +18,11 @@
package com.android.messaging.mmslib.util;
import android.content.Context;
import android.drm.DrmManagerClient;
import android.util.Log;
public class DownloadDrmHelper {
private static final String TAG = "DownloadDrmHelper";
/** The MIME type of special DRM files */
public static final String MIMETYPE_DRM_MESSAGE = "application/vnd.oma.drm.message";
/** The extensions of special DRM files */
public static final String EXTENSION_DRM_MESSAGE = ".dm";
public static final String EXTENSION_INTERNAL_FWDL = ".fl";
/**
* Checks if the Media Type is a DRM Media Type
*
* @param drmManagerClient A DrmManagerClient
* @param mimetype Media Type to check
* @return True if the Media Type is DRM else false
*/
public static boolean isDrmMimeType(Context context, String mimetype) {
boolean result = false;
if (context != null) {
try {
DrmManagerClient drmClient = new DrmManagerClient(context);
if (drmClient != null && mimetype != null && mimetype.length() > 0) {
result = drmClient.canHandle("", mimetype);
}
} catch (IllegalArgumentException e) {
Log.w(TAG,
"DrmManagerClient instance could not be created, context is Illegal.");
} catch (IllegalStateException e) {
Log.w(TAG, "DrmManagerClient didn't initialize properly.");
}
}
return result;
}
/**
* Checks if the Media Type needs to be DRM converted
*
@@ -66,46 +32,4 @@ public class DownloadDrmHelper {
public static boolean isDrmConvertNeeded(String mimetype) {
return MIMETYPE_DRM_MESSAGE.equals(mimetype);
}
/**
* Modifies the file extension for a DRM Forward Lock file NOTE: This
* function shouldn't be called if the file shouldn't be DRM converted
*/
public static String modifyDrmFwLockFileExtension(String filename) {
if (filename != null) {
int extensionIndex;
extensionIndex = filename.lastIndexOf(".");
if (extensionIndex != -1) {
filename = filename.substring(0, extensionIndex);
}
filename = filename.concat(EXTENSION_INTERNAL_FWDL);
}
return filename;
}
/**
* Gets the original mime type of DRM protected content.
*
* @param context The context
* @param path Path to the file
* @param containingMime The current mime type of of the file i.e. the
* containing mime type
* @return The original mime type of the file if DRM protected else the
* currentMime
*/
public static String getOriginalMimeType(Context context, String path, String containingMime) {
String result = containingMime;
DrmManagerClient drmClient = new DrmManagerClient(context);
try {
if (drmClient.canHandle(path, null)) {
result = drmClient.getOriginalMimeType(path);
}
} catch (IllegalArgumentException ex) {
Log.w(TAG,
"Can't get original mime type since path is null or empty string.");
} catch (IllegalStateException ex) {
Log.w(TAG, "DrmManagerClient didn't initialize properly.");
}
return result;
}
}

View File

@@ -39,7 +39,6 @@ import java.util.regex.Pattern;
import com.android.messaging.Factory;
import com.android.messaging.R;
import com.android.messaging.datamodel.BugleNotifications;
import com.android.messaging.datamodel.MessageNotificationState;
import com.android.messaging.datamodel.NoConfirmationSmsSendService;
import com.android.messaging.datamodel.action.ReceiveSmsMessageAction;
import com.android.messaging.sms.MmsUtils;
@@ -164,17 +163,6 @@ public final class SmsReceiver extends BroadcastReceiver {
}
}
private static class SecondaryUserNotificationState extends MessageNotificationState {
SecondaryUserNotificationState() {
super(null);
}
@Override
protected Style build(Builder builder) {
return null;
}
}
public static void postNewMessageSecondaryUserNotification() {
final Context context = Factory.get().getApplicationContext();
final Resources resources = context.getResources();

View File

@@ -17,27 +17,19 @@
package com.android.messaging.sms;
import android.content.ContentValues;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.provider.Telephony;
import android.text.TextUtils;
import android.util.Log;
import com.android.messaging.R;
import com.android.messaging.datamodel.data.ParticipantData;
import com.android.messaging.util.LogUtil;
import com.android.messaging.util.PhoneUtils;
import com.google.common.collect.Lists;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/*
* Database helper class for looking up APNs. This database has a single table
@@ -86,61 +78,7 @@ public class ApnDatabase extends SQLiteOpenHelper {
Telephony.Carriers.MVNO_MATCH_DATA + " TEXT," +
Telephony.Carriers.SUBSCRIPTION_ID + " INTEGER DEFAULT " +
ParticipantData.DEFAULT_SELF_SUB_ID + ");";
public static final String[] APN_PROJECTION = {
Telephony.Carriers.TYPE, // 0
Telephony.Carriers.MMSC, // 1
Telephony.Carriers.MMSPROXY, // 2
Telephony.Carriers.MMSPORT, // 3
Telephony.Carriers._ID, // 4
Telephony.Carriers.CURRENT, // 5
Telephony.Carriers.NUMERIC, // 6
Telephony.Carriers.NAME, // 7
Telephony.Carriers.MCC, // 8
Telephony.Carriers.MNC, // 9
Telephony.Carriers.APN, // 10
Telephony.Carriers.SUBSCRIPTION_ID // 11
};
public static final int COLUMN_TYPE = 0;
public static final int COLUMN_MMSC = 1;
public static final int COLUMN_MMSPROXY = 2;
public static final int COLUMN_MMSPORT = 3;
public static final int COLUMN_ID = 4;
public static final int COLUMN_CURRENT = 5;
public static final int COLUMN_NUMERIC = 6;
public static final int COLUMN_NAME = 7;
public static final int COLUMN_MCC = 8;
public static final int COLUMN_MNC = 9;
public static final int COLUMN_APN = 10;
public static final int COLUMN_SUB_ID = 11;
public static final String[] APN_FULL_PROJECTION = {
Telephony.Carriers.NAME,
Telephony.Carriers.MCC,
Telephony.Carriers.MNC,
Telephony.Carriers.APN,
Telephony.Carriers.USER,
Telephony.Carriers.SERVER,
Telephony.Carriers.PASSWORD,
Telephony.Carriers.PROXY,
Telephony.Carriers.PORT,
Telephony.Carriers.MMSC,
Telephony.Carriers.MMSPROXY,
Telephony.Carriers.MMSPORT,
Telephony.Carriers.AUTH_TYPE,
Telephony.Carriers.TYPE,
Telephony.Carriers.PROTOCOL,
Telephony.Carriers.ROAMING_PROTOCOL,
Telephony.Carriers.CARRIER_ENABLED,
Telephony.Carriers.BEARER,
Telephony.Carriers.MVNO_TYPE,
Telephony.Carriers.MVNO_MATCH_DATA,
Telephony.Carriers.CURRENT,
Telephony.Carriers.SUBSCRIPTION_ID,
};
private static final String CURRENT_SELECTION = Telephony.Carriers.CURRENT + " NOT NULL";
/**
* ApnDatabase is initialized asynchronously from the application.onCreate
@@ -178,105 +116,6 @@ public class ApnDatabase extends SQLiteOpenHelper {
rebuildTables(db);
}
/**
* Get a copy of user changes in the old table
*
* @return The list of user changed apns
*/
public static List<ContentValues> loadUserDataFromOldTable(final SQLiteDatabase db) {
try (Cursor cursor = db.query(APN_TABLE,
APN_FULL_PROJECTION, CURRENT_SELECTION,
null/*selectionArgs*/,
null/*groupBy*/, null/*having*/, null/*orderBy*/)) {
if (cursor != null) {
final List<ContentValues> result = Lists.newArrayList();
while (cursor.moveToNext()) {
final ContentValues row = cursorToValues(cursor);
if (row != null) {
result.add(row);
}
}
return result;
}
} catch (final SQLiteException e) {
LogUtil.w(TAG, "ApnDatabase.loadUserDataFromOldTable: no old user data: " + e, e);
}
return null;
}
private static final String[] ID_PROJECTION = new String[]{Telephony.Carriers._ID};
private static final String ID_SELECTION = Telephony.Carriers._ID + "=?";
/**
* Store use changes of old table into the new apn table
*
* @param data The user changes
*/
public static void saveUserDataFromOldTable(
final SQLiteDatabase db, final List<ContentValues> data) {
if (data == null || data.size() < 1) {
return;
}
for (final ContentValues row : data) {
// Build query from the row data. It is an exact match, column by column,
// except the CURRENT column
final StringBuilder selectionBuilder = new StringBuilder();
final ArrayList<String> selectionArgs = Lists.newArrayList();
for (final String key : row.keySet()) {
if (!Telephony.Carriers.CURRENT.equals(key)) {
if (selectionBuilder.length() > 0) {
selectionBuilder.append(" AND ");
}
final String value = row.getAsString(key);
if (TextUtils.isEmpty(value)) {
selectionBuilder.append(key).append(" IS NULL");
} else {
selectionBuilder.append(key).append("=?");
selectionArgs.add(value);
}
}
}
try (Cursor cursor = db.query(APN_TABLE,
ID_PROJECTION,
selectionBuilder.toString(),
selectionArgs.toArray(new String[0]),
null/*groupBy*/, null/*having*/, null/*orderBy*/)) {
/*groupBy*/
/*having*/
/*orderBy*/
if (cursor != null && cursor.moveToFirst()) {
db.update(APN_TABLE, row, ID_SELECTION, new String[]{cursor.getString(0)});
} else {
// User APN does not exist, insert into the new table
row.put(Telephony.Carriers.NUMERIC,
PhoneUtils.canonicalizeMccMnc(
row.getAsString(Telephony.Carriers.MCC),
row.getAsString(Telephony.Carriers.MNC))
);
db.insert(APN_TABLE, null/*nullColumnHack*/, row);
}
} catch (final SQLiteException e) {
LogUtil.e(TAG, "ApnDatabase.saveUserDataFromOldTable: query error " + e, e);
}
}
}
// Convert Cursor to ContentValues
private static ContentValues cursorToValues(final Cursor cursor) {
final int columnCount = cursor.getColumnCount();
if (columnCount > 0) {
final ContentValues result = new ContentValues();
for (int i = 0; i < columnCount; i++) {
final String name = cursor.getColumnName(i);
final String value = cursor.getString(i);
result.put(name, value);
}
return result;
}
return null;
}
@Override
public void onOpen(final SQLiteDatabase db) {
super.onOpen(db);
@@ -350,13 +189,4 @@ public class ApnDatabase extends SQLiteOpenHelper {
loadApnTable(db);
}
/**
* Clear all tables
*/
public static void clearTables() {
final SQLiteDatabase db = getApnDatabase().getWritableDatabase();
db.execSQL("DROP TABLE IF EXISTS " + APN_TABLE);
db.execSQL(APN_TABLE_SQL);
}
}

View File

@@ -595,19 +595,4 @@ public class BugleApnSettingsLoader implements ApnSettingsLoader {
}
return false;
}
/**
* Get the ID of first APN to try
*/
public static String getFirstTryApn(final SQLiteDatabase database, final String mccMnc) {
String key = null;
try (Cursor cursor = queryLocalDatabase(database, mccMnc, null/*apnName*/)) {
if (cursor.moveToFirst()) {
key = cursor.getString(ApnDatabase.COLUMN_ID);
}
} catch (final Exception e) {
// Nothing to do
}
return key;
}
}

View File

@@ -20,7 +20,6 @@ package com.android.messaging.sms;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@@ -734,32 +733,6 @@ public class DatabaseMessages {
}
}
/**
* Get media file size
*/
private long getMediaFileSize() {
final Context context = Factory.get().getApplicationContext();
final Uri uri = getDataUri();
AssetFileDescriptor fd = null;
try {
fd = context.getContentResolver().openAssetFileDescriptor(uri, "r");
if (fd != null) {
return fd.getParcelFileDescriptor().getStatSize();
}
} catch (final FileNotFoundException e) {
LogUtil.e(TAG, "DatabaseMessages.MmsPart: cound not find media file: " + e, e);
} finally {
if (fd != null) {
try {
fd.close();
} catch (final IOException e) {
LogUtil.e(TAG, "DatabaseMessages.MmsPart: failed to close " + e, e);
}
}
}
return 0L;
}
/**
* @return If the type is a text type that stores text embedded (i.e. in db table)
*/

View File

@@ -25,7 +25,6 @@ import android.content.Intent;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
@@ -38,8 +37,6 @@ import android.provider.Telephony.Threads;
import android.telephony.SmsManager;
import android.telephony.SmsMessage;
import android.text.TextUtils;
import android.text.util.Rfc822Token;
import android.text.util.Rfc822Tokenizer;
import com.android.messaging.Factory;
import com.android.messaging.R;
@@ -1379,45 +1376,6 @@ public class MmsUtils {
null/*selectionArgs*/);
}
/**
* Update the read status of a single MMS message by its URI
*
* @param mmsUri
* @param read
*/
public static void updateReadStatusForMmsMessage(final Uri mmsUri, final boolean read) {
final ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver();
final ContentValues values = new ContentValues();
values.put(Mms.READ, read ? 1 : 0);
resolver.update(mmsUri, values, null/*where*/, null/*selectionArgs*/);
}
public static class AttachmentInfo {
public String mUrl;
public String mContentType;
public int mWidth;
public int mHeight;
}
/**
* Convert byte array to Java String using a charset name
*
* @param bytes
* @param charsetName
* @return
*/
public static String bytesToString(final byte[] bytes, final String charsetName) {
if (bytes == null) {
return null;
}
try {
return new String(bytes, charsetName);
} catch (final UnsupportedEncodingException e) {
LogUtil.e(TAG, "MmsUtils.bytesToString: " + e, e);
return new String(bytes);
}
}
/**
* Convert a Java String to byte array using a charset name
*
@@ -1510,25 +1468,6 @@ public class MmsUtils {
return sUseSystemApn;
}
// For the internal debugger only
public static void setUseSystemApnTable(final boolean turnOn) {
if (!turnOn) {
// We're turning on local APNs on a device where we wouldn't normally have the
// local APN table. Build it here.
final SQLiteDatabase database = ApnDatabase.getApnDatabase().getWritableDatabase();
// Do we already have the table?
try (Cursor cursor = database.query(ApnDatabase.APN_TABLE,
ApnDatabase.APN_PROJECTION,
null, null, null, null, null, null)) {
} catch (final Exception e) {
// Apparently there's no table, create it now.
ApnDatabase.forceBuildAndLoadApnTables();
}
}
sUseSystemApn = turnOn;
}
public static final Uri MMS_PART_CONTENT_URI = Uri.parse("content://mms/part");
/**
@@ -2045,28 +1984,6 @@ public class MmsUtils {
return null;
}
/**
* Try parse the address using RFC822 format. If it fails to parse, then return the
* original address
*
* @param address The MMS ind sender address to parse
* @return The real address. If in RFC822 format, returns the correct email.
*/
private static String parsePotentialRfc822EmailAddress(final String address) {
if (address == null || !address.contains("@") || !address.contains("<")) {
return address;
}
final Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(address);
if (tokens != null && tokens.length > 0) {
for (final Rfc822Token token : tokens) {
if (token != null && !TextUtils.isEmpty(token.getAddress())) {
return token.getAddress();
}
}
}
return address;
}
public static DatabaseMessages.MmsMessage processReceivedPdu(final Context context,
final byte[] pushData, final int subId, final String subPhoneNumber) {
// Parse data
@@ -2089,20 +2006,6 @@ public class MmsUtils {
switch (type) {
case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND: {
// TODO: Should this be commented out?
// threadId = findThreadId(context, pdu, type);
// if (threadId == -1) {
// // The associated SendReq isn't found, therefore skip
// // processing this PDU.
// break;
// }
// Uri uri = p.persist(pdu, Inbox.CONTENT_URI, true,
// MessagingPreferenceActivity.getIsGroupMmsEnabled(mContext), null);
// // Update thread ID for ReadOrigInd & DeliveryInd.
// ContentValues values = new ContentValues(1);
// values.put(Mms.THREAD_ID, threadId);
// SqliteWrapper.update(mContext, cr, uri, values, null, null);
LogUtil.w(TAG, "Received unsupported WAP Push, type=" + type);
break;
}
@@ -2125,24 +2028,6 @@ public class MmsUtils {
}
final String[] dups = getDupNotifications(context, nInd);
if (dups == null) {
// TODO: Do we handle Rfc822 Email Addresses?
//final String contentLocation =
// MmsUtils.bytesToString(nInd.getContentLocation(), "UTF-8");
//final byte[] transactionId = nInd.getTransactionId();
//final long messageSize = nInd.getMessageSize();
//final long expiry = nInd.getExpiry();
//final String transactionIdString =
// MmsUtils.bytesToString(transactionId, "UTF-8");
//final EncodedStringValue fromEncoded = nInd.getFrom();
// An mms ind received from email address will have from address shown as
// "John Doe <johndoe@foobar.com>" but the actual received message will only
// have the email address. So let's try to parse the RFC822 format to get the
// real email. Otherwise we will create two conversations for the MMS
// notification and the actual MMS message if auto retrieve is disabled.
//final String from = parsePotentialRfc822EmailAddress(
// fromEncoded != null ? fromEncoded.getString() : null);
Uri inboxUri = null;
try {
inboxUri = p.persist(pdu, Mms.Inbox.CONTENT_URI, subId, subPhoneNumber,
@@ -2441,12 +2326,6 @@ public class MmsUtils {
switch (rawStatus) {
case PduHeaders.RESPONSE_STATUS_ERROR_SERVICE_DENIED:
case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_SERVICE_DENIED:
//case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_LIMITATIONS_NOT_MET:
//case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_REQUEST_NOT_ACCEPTED:
//case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_FORWARDING_DENIED:
//case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_NOT_SUPPORTED:
//case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_ADDRESS_HIDING_NOT_SUPPORTED:
//case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_LACK_OF_PREPAID:
stringResId = R.string.mms_failure_outgoing_service;
break;
case PduHeaders.RESPONSE_STATUS_ERROR_SENDING_ADDRESS_UNRESOLVED:
@@ -2463,8 +2342,6 @@ public class MmsUtils {
stringResId = R.string.mms_failure_outgoing_content;
break;
case PduHeaders.RESPONSE_STATUS_ERROR_UNSUPPORTED_MESSAGE:
//case PduHeaders.RESPONSE_STATUS_ERROR_MESSAGE_NOT_FOUND:
//case PduHeaders.RESPONSE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND:
stringResId = R.string.mms_failure_outgoing_unsupported;
break;
case MessageData.RAW_TELEPHONY_STATUS_MESSAGE_TOO_BIG:

View File

@@ -1,59 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.messaging.sms;
/**
* A generic Exception for errors in sending SMS
*/
class SmsException extends Exception {
private static final long serialVersionUID = 1L;
/**
* Creates a new SmsException.
*/
public SmsException() {
super();
}
/**
* Creates a new SmsException with the specified detail message.
*
* @param message the detail message.
*/
public SmsException(String message) {
super(message);
}
/**
* Creates a new SmsException with the specified cause.
*
* @param cause the cause.
*/
public SmsException(Throwable cause) {
super(cause);
}
/**
* Creates a new SmsException with the specified detail message and cause.
*
* @param message the detail message.
* @param cause the cause.
*/
public SmsException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -182,7 +182,7 @@ public class SmsSender {
// This should be called from a RequestWriter queue thread
public static SendResult sendMessage(final Context context, final int subId, String dest,
String message, final String serviceCenter, final boolean requireDeliveryReport,
final Uri messageUri) throws SmsException {
final Uri messageUri) throws Exception {
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
LogUtil.v(TAG, "SmsSender: sending message. " +
"dest=" + dest + " message=" + message +
@@ -191,7 +191,7 @@ public class SmsSender {
" requestId=" + messageUri);
}
if (TextUtils.isEmpty(message)) {
throw new SmsException("SmsSender: empty text message");
throw new Exception("SmsSender: empty text message");
}
// Get the real dest and message for email or alias if dest is email or alias
// Or sanitize the dest if dest is a number
@@ -208,13 +208,13 @@ public class SmsSender {
dest = PhoneNumberUtils.stripSeparators(dest);
}
if (TextUtils.isEmpty(dest)) {
throw new SmsException("SmsSender: empty destination address");
throw new Exception("SmsSender: empty destination address");
}
// Divide the input message by SMS length limit
final SmsManager smsManager = PhoneUtils.get(subId).getSmsManager();
final ArrayList<String> messages = smsManager.divideMessage(message);
if (messages == null || messages.size() < 1) {
throw new SmsException("SmsSender: fails to divide message");
throw new Exception("SmsSender: fails to divide message");
}
// Prepare the send result, which collects the send status for each part
final SendResult pendingResult = new SendResult(messages.size());
@@ -252,7 +252,7 @@ public class SmsSender {
// Actually sending the message using SmsManager
private static void sendInternal(final Context context, final int subId, String dest,
final ArrayList<String> messages, final String serviceCenter,
final boolean requireDeliveryReport, final Uri messageUri) throws SmsException {
final boolean requireDeliveryReport, final Uri messageUri) throws Exception {
Assert.notNull(context);
final SmsManager smsManager = PhoneUtils.get(subId).getSmsManager();
final int messageCount = messages.size();
@@ -295,7 +295,7 @@ public class SmsSender {
dest, serviceCenter, messages, sentIntents, deliveryIntents);
}
} catch (final Exception e) {
throw new SmsException("SmsSender: caught exception in sending " + e);
throw new Exception("SmsSender: caught exception in sending " + e);
}
}

View File

@@ -1,38 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.messaging.ui;
/**
* Defines a list of animation tags used as android:transitionName properties used for L's
* hero transitions.
*/
public class BugleAnimationTags {
/**
* The tag for the FAB in the conversation list activity.
*/
public static final String TAG_FABICON = "bugle:fabicon";
/**
* The tag for the content view of a conversation list item view.
*/
public static final String TAG_CLIVCONTENT = "bugle:clivcontent";
/**
* The tag for the action bar.
*/
public static final String TAG_ACTIONBAR = "bugle:actionbar";
}

View File

@@ -33,7 +33,6 @@ import android.view.ViewOverlay;
import android.widget.FrameLayout;
import com.android.messaging.R;
import com.android.messaging.util.ImageUtils;
import com.android.messaging.util.UiUtils;
/**
@@ -192,9 +191,9 @@ public class ViewGroupItemVerticalExplodeAnimation {
// Strip the view of its background when taking a snapshot so that things like touch
// feedback don't get accidentally snapshotted.
final Drawable viewBackground = view.getBackground();
ImageUtils.setBackgroundDrawableOnView(view, null);
view.setBackground(null);
view.draw(new Canvas(viewBitmap));
ImageUtils.setBackgroundDrawableOnView(view, viewBackground);
view.setBackground(viewBackground);
return viewBitmap;
}
}

View File

@@ -30,7 +30,6 @@ import androidx.fragment.app.FragmentTransaction;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreferenceCompat;
import com.android.messaging.R;
import com.android.messaging.ui.BugleActionBarActivity;
@@ -82,14 +81,10 @@ public class ApplicationSettingsActivity extends BugleActionBarActivity {
public static class ApplicationSettingsFragment extends PreferenceFragmentCompat {
private String mNotificationsPreferenceKey;
private Preference mNotificationsPreference;
private String mSmsDisabledPrefKey;
private Preference mSmsDisabledPreference;
private String mSmsEnabledPrefKey;
private Preference mSmsEnabledPreference;
private boolean mIsSmsPreferenceClicked;
private String mSwipeRightToDeleteConversationkey;
private SwitchPreferenceCompat mSwipeRightToDeleteConversationPreference;
public ApplicationSettingsFragment() {
// Required empty constructor
@@ -103,16 +98,10 @@ public class ApplicationSettingsActivity extends BugleActionBarActivity {
mNotificationsPreferenceKey =
getString(R.string.notifications_pref_key);
mNotificationsPreference = findPreference(mNotificationsPreferenceKey);
mSmsDisabledPrefKey = getString(R.string.sms_disabled_pref_key);
mSmsDisabledPreference = findPreference(mSmsDisabledPrefKey);
mSmsEnabledPrefKey = getString(R.string.sms_enabled_pref_key);
mSmsEnabledPreference = findPreference(mSmsEnabledPrefKey);
mSwipeRightToDeleteConversationkey = getString(
R.string.swipe_right_deletes_conversation_key);
mSwipeRightToDeleteConversationPreference =
(SwitchPreferenceCompat) findPreference(mSwipeRightToDeleteConversationkey);
mIsSmsPreferenceClicked = false;
final PreferenceScreen advancedScreen = (PreferenceScreen) findPreference(
getString(R.string.advanced_pref_key));
@@ -135,10 +124,6 @@ public class ApplicationSettingsActivity extends BugleActionBarActivity {
intent.putExtra(Settings.EXTRA_APP_PACKAGE, getContext().getPackageName());
startActivity(intent);
}
if (preference.getKey() == mSmsDisabledPrefKey ||
preference.getKey() == mSmsEnabledPrefKey) {
mIsSmsPreferenceClicked = true;
}
return super.onPreferenceTreeClick(preference);
}
@@ -152,7 +137,6 @@ public class ApplicationSettingsActivity extends BugleActionBarActivity {
getPreferenceScreen().removePreference(mSmsEnabledPreference);
mSmsDisabledPreference.setSummary(defaultSmsAppLabel);
}
mIsSmsPreferenceClicked = false;
}
@Override

View File

@@ -152,7 +152,7 @@ public class ContactPickerFragment extends Fragment implements ContactPickerData
mRecipientTextView.setContactChipsListener(this);
mRecipientTextView.setDropdownChipLayouter(new ContactDropdownLayouter(inflater,
getActivity(), this));
mRecipientTextView.setAdapter(new ContactRecipientAdapter(getActivity(), this));
mRecipientTextView.setAdapter(new ContactRecipientAdapter(getActivity()));
mRecipientTextView.addTextChangedListener(new TextWatcher() {
@Override
public void onTextChanged(final CharSequence s, final int start, final int before,

View File

@@ -68,15 +68,14 @@ public final class ContactRecipientAdapter extends BaseRecipientAdapter {
*/
private static final int ENTRY_TYPE_DIRECTORY = RecipientEntry.ENTRY_TYPE_SIZE;
public ContactRecipientAdapter(final Context context,
final ContactListItemView.HostInterface clivHost) {
this(context, Integer.MAX_VALUE, QUERY_TYPE_PHONE, clivHost);
public ContactRecipientAdapter(final Context context) {
this(context, Integer.MAX_VALUE, QUERY_TYPE_PHONE);
}
public ContactRecipientAdapter(final Context context, final int preferredMaxResultCount,
final int queryMode, final ContactListItemView.HostInterface clivHost) {
final int queryMode) {
super(context, preferredMaxResultCount, queryMode);
setPhotoManager(new ContactRecipientPhotoManager(context, clivHost));
setPhotoManager(new ContactRecipientPhotoManager(context));
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}

View File

@@ -59,7 +59,6 @@ public class ContactRecipientAutoCompleteView extends RecipientEditTextView {
void onEntryComplete();
}
private final int mTextHeight;
private ContactChipsChangeListener mChipsChangeListener;
/**
@@ -110,7 +109,6 @@ public class ContactRecipientAutoCompleteView extends RecipientEditTextView {
final Rect textBounds = new Rect(0, 0, 0, 0);
final TextPaint paint = getPaint();
paint.getTextBounds(TEXT_HEIGHT_SAMPLE, 0, TEXT_HEIGHT_SAMPLE.length(), textBounds);
mTextHeight = textBounds.height();
setTokenizer(new Rfc822Tokenizer());
addTextChangedListener(new ContactChipsWatcher());

View File

@@ -42,15 +42,12 @@ public class ContactRecipientPhotoManager implements PhotoManager {
private static final String IMAGE_BYTES_REQUEST_STATIC_BINDING_ID = "imagebytes";
private final Context mContext;
private final int mIconSize;
private final ContactListItemView.HostInterface mClivHostInterface;
public ContactRecipientPhotoManager(final Context context,
final ContactListItemView.HostInterface clivHostInterface) {
public ContactRecipientPhotoManager(final Context context) {
mContext = context;
mIconSize = context.getResources().getDimensionPixelSize(
R.dimen.compose_message_chip_height) - context.getResources().getDimensionPixelSize(
R.dimen.compose_message_chip_padding) * 2;
mClivHostInterface = clivHostInterface;
}
/**

View File

@@ -90,7 +90,6 @@ public class ComposeMessageView extends LinearLayout
void sendMessage(MessageData message);
void onComposeEditTextFocused();
void onAttachmentsCleared();
void onAttachmentsChanged(final boolean haveAttachments);
void displayPhoto(Uri photoUri, Rect imageBounds, boolean isDraft);
void promptForSelfPhoneNumber();
boolean isReadyForAction();
@@ -301,10 +300,8 @@ public class ComposeMessageView extends LinearLayout
}
final boolean haveAttachments = mBinding.getData().hasAttachments();
if (simPickerVisible && haveAttachments) {
mHost.onAttachmentsChanged(false);
mAttachmentPreview.hideAttachmentPreview();
} else {
mHost.onAttachmentsChanged(haveAttachments);
mAttachmentPreview.onAttachmentsChanged(mBinding.getData());
}
}
@@ -474,8 +471,7 @@ public class ComposeMessageView extends LinearLayout
if ((changeFlags & DraftMessageData.ATTACHMENTS_CHANGED) ==
DraftMessageData.ATTACHMENTS_CHANGED) {
final boolean haveAttachments = mAttachmentPreview.onAttachmentsChanged(data);
mHost.onAttachmentsChanged(haveAttachments);
mAttachmentPreview.onAttachmentsChanged(data);
hasAttachmentsChanged = true;
}

View File

@@ -165,13 +165,6 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
private ConversationFragmentHost mHost;
protected List<Integer> mFilterResults;
// The minimum scrolling distance between RecyclerView's scroll change event beyong which
// a fling motion is considered fast, in which case we'll delay load image attachments for
// perf optimization.
private int mFastFlingThreshold;
// ConversationMessageView that is currently selected
private ConversationMessageView mSelectedMessage;
@@ -412,8 +405,6 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mFastFlingThreshold = getResources().getDimensionPixelOffset(
R.dimen.conversation_fast_fling_threshold);
mAdapter = new ConversationMessageAdapter(getActivity(), null, this,
null,
// Sets the item click listener on the Recycler item views.
@@ -1579,11 +1570,6 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
return -1; // don't override the color
}
@Override
public void onAttachmentsChanged(final boolean haveAttachments) {
// no-op for now
}
@Override
public void onDraftChanged(final DraftMessageData data, final int changeFlags) {
mDraftMessageDataModel.ensureBound(data);

View File

@@ -40,7 +40,6 @@ import com.android.messaging.ui.mediapicker.MediaPicker.MediaPickerListener;
import com.android.messaging.util.Assert;
import com.android.messaging.util.ImeUtil;
import com.android.messaging.util.ImeUtil.ImeStateHost;
import com.google.common.annotations.VisibleForTesting;
import java.util.Collection;
@@ -236,26 +235,6 @@ public class ConversationInputManager implements ConversationInput.ConversationI
return false;
}
@VisibleForTesting
boolean isMediaPickerVisible() {
return mMediaInput.mShowing;
}
@VisibleForTesting
boolean isSimSelectorVisible() {
return mSimInput.mShowing;
}
@VisibleForTesting
boolean isImeKeyboardVisible() {
return mImeInput.mShowing;
}
@VisibleForTesting
void testNotifyImeStateChanged(final boolean imeOpen) {
mImeStateObserver.onImeStateChanged(imeOpen);
}
/**
* returns true if the state of the visibility was actually changed
*/

View File

@@ -737,7 +737,7 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick
R.dimen.message_metadata_top_padding);
// Update the message text/info views
ImageUtils.setBackgroundDrawableOnView(mMessageTextAndInfoView, textBackground);
mMessageTextAndInfoView.setBackground(textBackground);
mMessageTextAndInfoView.setMinimumHeight(textMinHeight);
final LinearLayout.LayoutParams textAndInfoLayoutParams =
(LinearLayout.LayoutParams) mMessageTextAndInfoView.getLayoutParams();

View File

@@ -36,7 +36,6 @@ import android.widget.AbsListView;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.core.view.ViewCompat;
import androidx.core.view.ViewGroupCompat;
import androidx.fragment.app.Fragment;
import androidx.loader.app.LoaderManager;
@@ -51,7 +50,6 @@ import com.android.messaging.datamodel.binding.BindingBase;
import com.android.messaging.datamodel.data.ConversationListData;
import com.android.messaging.datamodel.data.ConversationListData.ConversationListDataListener;
import com.android.messaging.datamodel.data.ConversationListItemData;
import com.android.messaging.ui.BugleAnimationTags;
import com.android.messaging.ui.ListEmptyView;
import com.android.messaging.ui.SnackBarInteraction;
import com.android.messaging.ui.UIIntents;
@@ -232,7 +230,6 @@ public class ConversationListFragment extends Fragment implements ConversationLi
mStartNewConversationButton.setOnClickListener(clickView ->
mHost.onCreateConversationClick());
}
ViewCompat.setTransitionName(mStartNewConversationButton, BugleAnimationTags.TAG_FABICON);
// The root view has a non-null background, which by default is deemed by the framework
// to be a "transition group," where all child views are animated together during an
@@ -417,15 +414,6 @@ public class ConversationListFragment extends Fragment implements ConversationLi
});
}
public View getHeroElementForTransition() {
return mArchiveMode ? null : mStartNewConversationButton;
}
@VisibleForAnimation
public RecyclerView getRecyclerView() {
return mRecyclerView;
}
@Override
public void startFullScreenPhotoViewer(
final Uri initialPhoto, final Rect initialPhotoBounds, final Uri photosUri) {

View File

@@ -79,8 +79,6 @@ public class ConversationListItemView extends FrameLayout implements OnClickList
private static String sPlusOneString;
private static String sPlusNString;
private static final int SWIPE_DIRECTION_RIGHT = 2;
public interface HostInterface {
boolean isConversationSelected(final String conversationId);
void onConversationClicked(final ConversationListItemData conversationListItemData,

View File

@@ -1,5 +1,6 @@
/*
* Copyright (C) 2015 The Android Open Source Project
* Copyright (C) 2024 The LineageOS Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,47 +28,16 @@ import javax.annotation.concurrent.ThreadSafe;
@ThreadSafe
public class AudioLevelSource {
private volatile int mSpeechLevel;
private volatile Listener mListener;
public static final int LEVEL_UNKNOWN = -1;
public interface Listener {
void onSpeechLevel(int speechLevel);
}
public void setSpeechLevel(int speechLevel) {
Preconditions.checkArgument(speechLevel >= 0 && speechLevel <= 100 ||
speechLevel == LEVEL_UNKNOWN);
mSpeechLevel = speechLevel;
maybeNotify();
}
public int getSpeechLevel() {
return mSpeechLevel;
}
public void reset() {
setSpeechLevel(LEVEL_UNKNOWN);
}
public boolean isValid() {
return mSpeechLevel > 0;
}
private void maybeNotify() {
final Listener l = mListener;
if (l != null) {
l.onSpeechLevel(mSpeechLevel);
}
}
public synchronized void setListener(Listener listener) {
mListener = listener;
}
public synchronized void clearListener(Listener listener) {
if (mListener == listener) {
mListener = null;
}
}
}

View File

@@ -111,11 +111,6 @@ public class AudioRecordView extends FrameLayout implements
mHostInterface = hostInterface;
}
@VisibleForTesting
public void testSetMediaRecorder(final LevelTrackingMediaRecorder recorder) {
mMediaRecorder = recorder;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();

View File

@@ -17,7 +17,6 @@
package com.android.messaging.ui.mediapicker.camerafocus;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.drawable.Drawable;
@@ -47,7 +46,6 @@ public class PieItem {
private List<PieItem> mItems;
private Path mPath;
private OnClickListener mOnClickListener;
private float mAlpha;
// Gray out the view when disabled
private static final float ENABLED_ALPHA = 1;
@@ -87,12 +85,7 @@ public class PieItem {
return mPath;
}
public void setChangeAlphaWhenDisabled (boolean enable) {
mChangeAlphaWhenDisabled = enable;
}
public void setAlpha(float alpha) {
mAlpha = alpha;
mDrawable.setAlpha((int) (255 * alpha));
}
@@ -138,11 +131,6 @@ public class PieItem {
outer = outside;
}
public void setFixedSlice(float center, float sweep) {
mCenter = center;
this.sweep = sweep;
}
public float getCenter() {
return mCenter;
}
@@ -192,12 +180,4 @@ public class PieItem {
public void draw(Canvas canvas) {
mDrawable.draw(canvas);
}
public void setImageResource(Context context, int resId) {
Drawable d = context.getResources().getDrawable(resId).mutate();
d.setBounds(mDrawable.getBounds());
mDrawable = d;
setAlpha(mAlpha);
}
}

View File

@@ -38,19 +38,6 @@ public class AccessibilityUtil {
return accessibilityManager.isTouchExplorationEnabled();
}
public static StringBuilder appendContentDescription(final Context context,
final StringBuilder contentDescription, final String val) {
if (sContentDescriptionDivider == null) {
sContentDescriptionDivider =
context.getResources().getString(R.string.enumeration_comma);
}
if (contentDescription.length() != 0) {
contentDescription.append(sContentDescriptionDivider);
}
contentDescription.append(val);
return contentDescription;
}
public static void announceForAccessibilityCompat(
final View view, @Nullable final AccessibilityManager accessibilityManager,
final int textResourceId) {

View File

@@ -18,8 +18,6 @@ package com.android.messaging.util;
import android.os.Looper;
import java.util.Arrays;
public final class Assert {
public @interface RunsOnMainThread {}
public @interface DoesNotRunOnMainThread {}
@@ -46,17 +44,6 @@ public final class Assert {
setIfEngBuild();
}
/**
* Halt execution if this is not an eng build.
* <p>Intended for use in code paths that should be run only for tests and never on
* a real build.
* <p>Note that this will crash on a user build even though asserts don't normally
* crash on a user build.
*/
public static void isEngBuild() {
isTrueReleaseCheck(sIsEngBuild);
}
/**
* Halt execution if this isn't the case.
*/
@@ -75,15 +62,6 @@ public final class Assert {
}
}
/**
* Halt execution even in release builds if this isn't the case.
*/
public static void isTrueReleaseCheck(final boolean condition) {
if (!condition) {
fail("Expected condition to be true", true);
}
}
public static void equals(final int expected, final int actual) {
if (expected != actual) {
fail("Expected " + expected + " but got " + actual, false);
@@ -103,15 +81,6 @@ public final class Assert {
}
}
public static void oneOf(final int actual, final int ...expected) {
for (int value : expected) {
if (actual == value) {
return;
}
}
fail("Expected value to be one of " + Arrays.toString(expected) + " but was " + actual);
}
public static void inRange(
final int val, final int rangeMinInclusive, final int rangeMaxInclusive) {
if (val < rangeMinInclusive || val > rangeMaxInclusive) {

View File

@@ -16,7 +16,6 @@
*/
package com.android.messaging.util;
import android.graphics.Color;
import android.net.Uri;
import android.net.Uri.Builder;
import androidx.annotation.NonNull;
@@ -85,11 +84,6 @@ public class AvatarUriUtil {
public static final Uri DEFAULT_BACKGROUND_AVATAR = new Uri.Builder().scheme(SCHEME)
.authority(AUTHORITY).appendPath(TYPE_DEFAULT_BACKGROUND_URI).build();
private static final Uri BLANK_SIM_INDICATOR_INCOMING_URI = createSimIconUri("",
false /* selected */, Color.TRANSPARENT, true /* incoming */);
private static final Uri BLANK_SIM_INDICATOR_OUTGOING_URI = createSimIconUri("",
false /* selected */, Color.TRANSPARENT, false /* incoming */);
/**
* Creates an avatar uri based on a list of ParticipantData. The list of participants may not
* be null or empty. Depending on the size of the list either a group avatar uri will be create
@@ -217,10 +211,6 @@ public class AvatarUriUtil {
return builder.build();
}
public static Uri getBlankSimIndicatorUri(final boolean incoming) {
return incoming ? BLANK_SIM_INDICATOR_INCOMING_URI : BLANK_SIM_INDICATOR_OUTGOING_URI;
}
/**
* Creates an avatar uri from the given local resource Uri, followed by a fallback Uri in case
* the local resource one could not be loaded.

View File

@@ -36,8 +36,6 @@ import com.android.messaging.ui.conversationlist.ConversationListActivity;
*/
public class BugleActivityUtil {
private static final int REQUEST_GOOGLE_PLAY_SERVICES = 0;
/**
* Determine if the requirements for the app to run are met. Log any Activity startup
* analytics.

View File

@@ -1,112 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
* Copyright (C) 2024 The LineageOS Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.messaging.util;
/**
* Very simple circular array implementation.
*
* @param <E> The element type of this list.
* @LibraryInternal
*/
public class CircularArray<E> {
private int mNextWriter;
private boolean mHasWrapped;
private final int mMaxCount;
Object mList[];
/**
* Constructor for CircularArray.
*
* @param count Max elements to hold in the list.
*/
public CircularArray(int count) {
mMaxCount = count;
clear();
}
/**
* Reset the list.
*/
public void clear() {
mNextWriter = 0;
mHasWrapped = false;
mList = new Object[mMaxCount];
}
/**
* Add an element to the end of the list.
*
* @param object The object to add.
*/
public void add(E object) {
mList[mNextWriter] = object;
++mNextWriter;
if (mNextWriter == mMaxCount) {
mNextWriter = 0;
mHasWrapped = true;
}
}
/**
* Get the number of elements in the list. This will be 0 <= returned count <= max count
*
* @return Elements in the circular list.
*/
public int count() {
if (mHasWrapped) {
return mMaxCount;
} else {
return mNextWriter;
}
}
/**
* Return null if the list hasn't wrapped yet. Otherwise return the next object that would be
* overwritten. Can be useful to avoid extra allocations.
*
* @return
*/
@SuppressWarnings("unchecked")
public E getFree() {
if (!mHasWrapped) {
return null;
} else {
return (E) mList[mNextWriter];
}
}
/**
* Get the object at index. Index 0 is the oldest item inserted into the list. Index (count() -
* 1) is the newest.
*
* @param index Index to retrieve.
* @return Object at index.
*/
@SuppressWarnings("unchecked")
public E get(int index) {
if (mHasWrapped) {
int wrappedIndex = index + mNextWriter;
if (wrappedIndex >= mMaxCount) {
wrappedIndex -= mMaxCount;
}
return (E) mList[wrappedIndex];
} else {
return (E) mList[index];
}
}
}

View File

@@ -45,10 +45,6 @@ public class ConnectivityUtil {
.createForSubscriptionId(subId);
}
public int getCurrentServiceState() {
return mCurrentServiceState;
}
private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
@Override
public void onServiceStateChanged(final ServiceState serviceState) {

View File

@@ -1,5 +1,6 @@
/*
* Copyright (C) 2015 The Android Open Source Project
* Copyright (C) 2024 The LineageOS Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,7 +24,6 @@ import com.android.ex.chips.RecipientEntry;
import com.android.messaging.Factory;
import com.android.messaging.R;
import com.android.messaging.datamodel.BugleRecipientEntry;
import com.android.messaging.datamodel.data.ParticipantData;
/**
* Provides utility methods around creating RecipientEntry instance specific to Bugle's needs.
@@ -105,11 +105,4 @@ public class ContactRecipientEntryUtils {
public static boolean isSendToDestinationContact(final RecipientEntry entry) {
return entry.getContactId() == CONTACT_ID_SENDTO_DESTINATION;
}
/**
* Returns true if the given participant is a special send to number item.
*/
public static boolean isSendToDestinationContact(final ParticipantData participant) {
return participant.getContactId() == CONTACT_ID_SENDTO_DESTINATION;
}
}

View File

@@ -58,30 +58,19 @@ public final class ContentType {
public static final String IMAGE_PNG = "image/png";
public static final String IMAGE_X_MS_BMP = "image/x-ms-bmp";
public static final String AUDIO_UNSPECIFIED = "audio/*";
public static final String AUDIO_AAC = "audio/aac";
public static final String AUDIO_AMR = "audio/amr";
public static final String AUDIO_IMELODY = "audio/imelody";
public static final String AUDIO_MID = "audio/mid";
public static final String AUDIO_MIDI = "audio/midi";
public static final String AUDIO_MP3 = "audio/mp3";
public static final String AUDIO_MPEG3 = "audio/mpeg3";
public static final String AUDIO_MPEG = "audio/mpeg";
public static final String AUDIO_MPG = "audio/mpg";
public static final String AUDIO_MP4 = "audio/mp4";
public static final String AUDIO_MP4_LATM = "audio/mp4-latm";
public static final String AUDIO_X_MID = "audio/x-mid";
public static final String AUDIO_X_MIDI = "audio/x-midi";
public static final String AUDIO_X_MP3 = "audio/x-mp3";
public static final String AUDIO_X_MPEG3 = "audio/x-mpeg3";
public static final String AUDIO_X_MPEG = "audio/x-mpeg";
public static final String AUDIO_X_MPG = "audio/x-mpg";
public static final String AUDIO_3GPP = "audio/3gpp";
public static final String AUDIO_X_WAV = "audio/x-wav";
public static final String AUDIO_OGG = "application/ogg";
public static final String MULTIPART_MIXED = "multipart/mixed";
public static final String VIDEO_UNSPECIFIED = "video/*";
public static final String VIDEO_3GP = "video/3gp";
public static final String VIDEO_3GPP = "video/3gpp";
@@ -95,10 +84,6 @@ public final class ContentType {
public static final String APP_SMIL = "application/smil";
public static final String APP_WAP_XHTML = "application/vnd.wap.xhtml+xml";
public static final String APP_XHTML = "application/xhtml+xml";
public static final String APP_DRM_CONTENT = "application/vnd.oma.drm.content";
public static final String APP_DRM_MESSAGE = "application/vnd.oma.drm.message";
// This class should never be instantiated.
private ContentType() {
@@ -136,16 +121,6 @@ public final class ContentType {
|| contentType.equalsIgnoreCase(TEXT_VCARD));
}
public static boolean isDrmType(final String contentType) {
return (null != contentType)
&& (contentType.equals(APP_DRM_CONTENT)
|| contentType.equals(APP_DRM_MESSAGE));
}
public static boolean isUnspecified(final String contentType) {
return (null != contentType) && contentType.endsWith("*");
}
/**
* If the content type is a type which can be displayed in the conversation list as a preview.
*/

View File

@@ -25,7 +25,6 @@ import android.text.TextUtils;
import com.android.messaging.Factory;
import com.android.messaging.R;
import com.google.common.io.Files;
import java.io.File;
import java.io.IOException;
@@ -71,55 +70,6 @@ public class FileUtil {
return getNewFile(directory, fileExtension, fileNameFormat);
}
/** Delete everything below and including root */
public static void removeFileOrDirectory(File root) {
removeFileOrDirectoryExcept(root, null);
}
/** Delete everything below and including root except for the given file */
public static void removeFileOrDirectoryExcept(File root, File exclude) {
if (root.exists()) {
if (root.isDirectory()) {
for (File file : root.listFiles()) {
if (exclude == null || !file.equals(exclude)) {
removeFileOrDirectoryExcept(file, exclude);
}
}
root.delete();
} else if (root.isFile()) {
root.delete();
}
}
}
/**
* Move all files and folders under a directory into the target.
*/
public static void moveAllContentUnderDirectory(File sourceDir, File targetDir) {
if (sourceDir.isDirectory() && targetDir.isDirectory()) {
if (isSameOrSubDirectory(sourceDir, targetDir)) {
LogUtil.e(LogUtil.BUGLE_TAG, "Can't move directory content since the source " +
"directory is a parent of the target");
return;
}
for (File file : sourceDir.listFiles()) {
if (file.isDirectory()) {
final File dirTarget = new File(targetDir, file.getName());
dirTarget.mkdirs();
moveAllContentUnderDirectory(file, dirTarget);
} else {
try {
final File fileTarget = new File(targetDir, file.getName());
Files.move(file, fileTarget);
} catch (IOException e) {
LogUtil.e(LogUtil.BUGLE_TAG, "Failed to move files", e);
// Try proceed with the next file.
}
}
}
}
}
// Checks if the file is in /data, and don't allow any app to send personal information.
// We're told it's possible to create world readable hardlinks to other apps private data
// so we ban all /data file uris.

View File

@@ -35,7 +35,6 @@ import android.net.Uri;
import android.provider.MediaStore;
import androidx.annotation.Nullable;
import android.text.TextUtils;
import android.view.View;
import com.android.messaging.Factory;
import com.android.messaging.datamodel.MediaScratchFileProvider;
@@ -169,15 +168,6 @@ public class ImageUtils {
}
}
/**
* Sets a drawable to the background of a view. setBackgroundDrawable() is deprecated since
* JB and replaced by setBackground().
*/
@SuppressWarnings("deprecation")
public static void setBackgroundDrawableOnView(final View view, final Drawable drawable) {
view.setBackground(drawable);
}
/**
* Based on the input bitmap bounds given by BitmapFactory.Options, compute the required
* sub-sampling size for loading a scaled down version of the bitmap to the required size

View File

@@ -1,60 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
* Copyright (C) 2024 The LineageOS Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.messaging.util;
import androidx.collection.LongSparseArray;
/**
* A space saving set for long values using v4 compat LongSparseArray
*/
public class LongSparseSet {
private static final Object THE_ONLY_VALID_VALUE = new Object();
private final LongSparseArray<Object> mSet = new LongSparseArray<>();
public LongSparseSet() {
}
/**
* @param key The element to check
* @return True if the element is in the set, false otherwise
*/
public boolean contains(long key) {
if (mSet.get(key, null/*default*/) == THE_ONLY_VALID_VALUE) {
return true;
}
return false;
}
/**
* Add an element to the set
*
* @param key The element to add
*/
public void add(long key) {
mSet.put(key, THE_ONLY_VALID_VALUE);
}
/**
* Remove an element from the set
*
* @param key The element to remove
*/
public void remove(long key) {
mSet.delete(key);
}
}

View File

@@ -1,27 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.messaging.util;
public class MaterialPalette{
public final int mPrimaryColor;
public final int mSecondaryColor;
public MaterialPalette(final int primaryColor, final int secondaryColor) {
mPrimaryColor = primaryColor;
mSecondaryColor = secondaryColor;
}
}

View File

@@ -23,18 +23,4 @@ public class PendingIntentConstants {
public static final int SMS_NOTIFICATION_ID = 0;
public static final int SMS_SECONDARY_USER_NOTIFICATION_ID = 1;
public static final int MSG_SEND_ERROR = 2;
// Request codes
public static final int UPDATE_NOTIFICATIONS_ALARM_ACTION_ID = 100;
public static final int MIN_ASSIGNED_REQUEST_CODE = 1001;
// Logging
private static final String TAG = LogUtil.BUGLE_TAG;
private static final boolean VERBOSE = false;
// Internal Constants
private static final String NOTIFICATION_REQUEST_CODE_PREFS = "notificationRequestCodes.v1";
private static final String REQUEST_CODE_DELIMITER = "|";
private static final String MAX_REQUEST_CODE_KEY = "maxRequestCode";
}

View File

@@ -105,15 +105,6 @@ public class PhoneUtils {
return null;
}
/**
* Get number of SIM slots
*
* @return the SIM slot count
*/
public int getSimSlotCount() {
return mSubscriptionManager.getActiveSubscriptionInfoCountMax();
}
/**
* Get SIM's carrier name
*
@@ -134,15 +125,6 @@ public class PhoneUtils {
return null;
}
/**
* Check if there is SIM inserted on the device
*
* @return true if there is SIM inserted, false otherwise
*/
public boolean hasSim() {
return mSubscriptionManager.getActiveSubscriptionInfoCount() > 0;
}
/**
* Check if the SIM is roaming
*
@@ -168,16 +150,6 @@ public class PhoneUtils {
return new int[]{mcc, mnc};
}
/**
* Get the mcc/mnc string
*
* @return the text of mccmnc string
*/
public String getSimOperatorNumeric() {
// For L_MR1 we return the canonicalized (xxxxxx) string
return getMccMncString(getMccMnc());
}
/**
* Get the SIM's self raw number, i.e. not canonicalized
*
@@ -581,39 +553,6 @@ public class PhoneUtils {
return getCanonicalBySimLocale(selfNumber);
}
/**
* Get the SIM's phone number in NATIONAL format with only digits, used in sending
* as LINE1NOCOUNTRYCODE macro in mms_config
*
* @return all digits national format number of the SIM
*/
public String getSimNumberNoCountryCode() {
String selfNumber = null;
try {
selfNumber = getSelfRawNumber(false/*allowOverride*/);
} catch (IllegalStateException e) {
// continue
}
if (selfNumber == null) {
selfNumber = "";
}
final String country = getSimCountry();
final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance();
try {
final PhoneNumber phoneNumber = phoneNumberUtil.parse(selfNumber, country);
if (phoneNumber != null && phoneNumberUtil.isValidNumber(phoneNumber)) {
return phoneNumberUtil
.format(phoneNumber, PhoneNumberFormat.NATIONAL)
.replaceAll("\\D", "");
}
} catch (final NumberParseException e) {
LogUtil.e(TAG, "PhoneUtils.getSimNumberNoCountryCode(): Not able to parse phone number "
+ LogUtil.sanitizePII(selfNumber) + " for country " + country);
}
return selfNumber;
}
/**
* Format a phone number for displaying, using system locale country.
* If the country code matches between the system locale and the input phone number,

View File

@@ -1,129 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.messaging.util;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import androidx.appcompat.graphics.drawable.DrawableWrapperCompat;
import androidx.appcompat.widget.SwitchCompat;
import android.util.TypedValue;
/* Most methods in this file are copied from
* v7/appcompat/src/androidx.appcompat.internal/widget/TintManager.java. It would be better if
* we could have just extended the TintManager but this is a final class that we do not have
* access to. */
/**
* Util methods for the SwitchCompat widget
*/
public class SwitchCompatUtils {
/**
* Given a color and a SwitchCompat view, updates the SwitchCompat to appear with the appropiate
* color when enabled and checked
*/
public static void updateSwitchCompatColor(SwitchCompat switchCompat, final int color) {
final Context context = switchCompat.getContext();
final TypedValue typedValue = new TypedValue();
switchCompat.setThumbDrawable(getColorTintedDrawable(switchCompat.getThumbDrawable(),
getSwitchThumbColorStateList(context, color, typedValue),
PorterDuff.Mode.MULTIPLY));
switchCompat.setTrackDrawable(getColorTintedDrawable(switchCompat.getTrackDrawable(),
getSwitchTrackColorStateList(context, color, typedValue), PorterDuff.Mode.SRC_IN));
}
private static Drawable getColorTintedDrawable(Drawable oldDrawable,
final ColorStateList colorStateList, final PorterDuff.Mode mode) {
final int[] thumbState = oldDrawable.isStateful() ? oldDrawable.getState() : null;
if (oldDrawable instanceof DrawableWrapperCompat) {
oldDrawable = ((DrawableWrapperCompat) oldDrawable).getDrawable();
}
final Drawable newDrawable = new TintDrawableWrapper(oldDrawable, colorStateList, mode);
if (thumbState != null) {
newDrawable.setState(thumbState);
}
return newDrawable;
}
private static ColorStateList getSwitchThumbColorStateList(final Context context,
final int color, final TypedValue typedValue) {
final int[][] states = new int[3][];
final int[] colors = new int[3];
int i = 0;
// Disabled state
states[i] = new int[] { -android.R.attr.state_enabled };
colors[i] = getColor(Color.parseColor("#ffbdbdbd"), 1f);
i++;
states[i] = new int[] { android.R.attr.state_checked };
colors[i] = color;
i++;
// Default enabled state
states[i] = new int[0];
colors[i] = getThemeAttrColor(context, typedValue,
androidx.appcompat.R.attr.colorSwitchThumbNormal);
i++;
return new ColorStateList(states, colors);
}
private static ColorStateList getSwitchTrackColorStateList(final Context context,
final int color, final TypedValue typedValue) {
final int[][] states = new int[3][];
final int[] colors = new int[3];
int i = 0;
// Disabled state
states[i] = new int[] { -android.R.attr.state_enabled };
colors[i] = getThemeAttrColor(context, typedValue, android.R.attr.colorForeground, 0.1f);
i++;
states[i] = new int[] { android.R.attr.state_checked };
colors[i] = getColor(color, 0.3f);
i++;
// Default enabled state
states[i] = new int[0];
colors[i] = getThemeAttrColor(context, typedValue, android.R.attr.colorForeground, 0.3f);
i++;
return new ColorStateList(states, colors);
}
private static int getThemeAttrColor(final Context context, final TypedValue typedValue,
final int attr) {
if (context.getTheme().resolveAttribute(attr, typedValue, true)) {
if (typedValue.type >= TypedValue.TYPE_FIRST_INT
&& typedValue.type <= TypedValue.TYPE_LAST_INT) {
return typedValue.data;
} else if (typedValue.type == TypedValue.TYPE_STRING) {
return context.getResources().getColor(typedValue.resourceId);
}
}
return 0;
}
private static int getThemeAttrColor(final Context context, final TypedValue typedValue,
final int attr, final float alpha) {
final int color = getThemeAttrColor(context, typedValue, attr);
return getColor(color, alpha);
}
private static int getColor(int color, float alpha) {
final int originalAlpha = Color.alpha(color);
// Return the color, multiplying the original alpha by the disabled value
return (color & 0x00ffffff) | (Math.round(originalAlpha * alpha) << 24);
}
}

View File

@@ -20,22 +20,12 @@ package com.android.messaging.util;
import android.app.Activity;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import android.text.Html;
import android.text.Spanned;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.style.URLSpan;
import android.view.Gravity;
import android.view.Surface;
import android.view.View;
import android.view.View.OnLayoutChangeListener;
import android.view.animation.Animation;
@@ -45,16 +35,17 @@ import android.view.animation.ScaleAnimation;
import android.widget.RemoteViews;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.messaging.Factory;
import com.android.messaging.R;
import com.android.messaging.ui.SnackBar;
import com.android.messaging.ui.SnackBar.Placement;
import com.android.messaging.ui.conversationlist.ConversationListActivity;
import com.android.messaging.ui.SnackBarInteraction;
import com.android.messaging.ui.SnackBarManager;
import com.android.messaging.ui.UIIntents;
import java.lang.reflect.Field;
import java.util.List;
public class UiUtils {
@@ -62,10 +53,6 @@ public class UiUtils {
public static final int MEDIAPICKER_TRANSITION_DURATION =
getApplicationContext().getResources().getInteger(
R.integer.mediapicker_transition_duration);
/** Short transition duration in ms */
public static final int ASYNCIMAGE_TRANSITION_DURATION =
getApplicationContext().getResources().getInteger(
R.integer.asyncimage_transition_duration);
/** Compose transition duration in ms */
public static final int COMPOSE_TRANSITION_DURATION =
getApplicationContext().getResources().getInteger(
@@ -272,34 +259,6 @@ public class UiUtils {
Color.rgb(blendedRed, blendedGreen, blendedBlue));
}
public static void lockOrientation(final Activity activity) {
final int orientation = activity.getResources().getConfiguration().orientation;
final int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
// rotation tracks the rotation of the device from its natural orientation
// orientation tracks whether the screen is landscape or portrait.
// It is possible to have a rotation of 0 (device in its natural orientation) in portrait
// (phone), or in landscape (tablet), so we have to check both values to determine what to
// pass to setRequestedOrientation.
if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_90) {
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
} else if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
} else if (rotation == Surface.ROTATION_180 || rotation == Surface.ROTATION_270) {
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT);
} else if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
}
}
}
public static void unlockOrientation(final Activity activity) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
}
public static boolean isRtlMode() {
return Factory.get().getApplicationContext().getResources()
.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
@@ -350,67 +309,6 @@ public class UiUtils {
return phoneUtils.isDefaultSmsApp();
}
/*
* Removes all html markup from the text and replaces links with the the text and a text version
* of the href.
* @param htmlText HTML markup text
* @return Sanitized string with link hrefs inlined
*/
public static String stripHtml(final String htmlText) {
final StringBuilder result = new StringBuilder();
final Spanned markup = Html.fromHtml(htmlText);
final String strippedText = markup.toString();
final URLSpan[] links = markup.getSpans(0, markup.length() - 1, URLSpan.class);
int currentIndex = 0;
for (final URLSpan link : links) {
final int spanStart = markup.getSpanStart(link);
final int spanEnd = markup.getSpanEnd(link);
if (spanStart > currentIndex) {
result.append(strippedText, currentIndex, spanStart);
}
final String displayText = strippedText.substring(spanStart, spanEnd);
final String linkText = link.getURL();
result.append(getApplicationContext().getString(R.string.link_display_format,
displayText, linkText));
currentIndex = spanEnd;
}
if (strippedText.length() > currentIndex) {
result.append(strippedText, currentIndex, strippedText.length());
}
return result.toString();
}
public static void setActionBarShadowVisibility(final AppCompatActivity activity, final boolean visible) {
final ActionBar actionBar = activity.getSupportActionBar();
actionBar.setElevation(visible ?
activity.getResources().getDimensionPixelSize(R.dimen.action_bar_elevation) :
0);
final View actionBarView = activity.getWindow().getDecorView().findViewById(
androidx.appcompat.R.id.decor_content_parent);
if (actionBarView != null) {
// AppCompatActionBar has one drawable Field, which is the shadow for the action bar
// set the alpha on that drawable manually
final Field[] fields = actionBarView.getClass().getDeclaredFields();
try {
for (final Field field : fields) {
if (field.getType().equals(Drawable.class)) {
field.setAccessible(true);
final Drawable shadowDrawable = (Drawable) field.get(actionBarView);
if (shadowDrawable != null) {
shadowDrawable.setAlpha(visible ? 255 : 0);
actionBarView.invalidate();
return;
}
}
}
} catch (final IllegalAccessException ex) {
// Not expected, we should avoid this via field.setAccessible(true) above
LogUtil.e(LogUtil.BUGLE_TAG, "Error setting shadow visibility", ex);
}
}
}
/**
* Get the activity that's hosting the view, typically casting view.getContext() as an Activity
* is sufficient, but sometimes the context is a context wrapper, in which case we need to case