Commit 3c2c834a authored by android-build-team Robot's avatar android-build-team Robot

Merge cherrypicks of [4741663, 4741664, 4741665, 4741666, 4743080, 4743081,...

Merge cherrypicks of [4741663, 4741664, 4741665, 4741666, 4743080, 4743081, 4743082, 4743083, 4741262, 4741263, 4741264, 4741265, 4741266, 4741667, 4743084, 4741242, 4741243, 4741741, 4741742, 4741743, 4741744, 4741822, 4743085, 4741668, 4741338, 4743055, 4743056, 4743070, 4743073, 4743075, 4743076, 4743078, 4743079, 4743161, 4743162, 4743164, 4743165, 4743167, 4743168, 4743169, 4743170, 4741681, 4741682, 4741683, 4741684, 4741685, 4741686, 4741687, 4741688, 4741689, 4741690, 4741691, 4741692, 4741693, 4741694, 4741695, 4741696, 4741697, 4741698, 4741699, 4743240, 4743241, 4743242, 4743243, 4741745, 4741823, 4741824, 4741825, 4741267, 4741268, 4743244, 4743280, 4743281, 4743224, 4743203, 4743204, 4743205, 4741746, 4741747, 4743245, 4741826, 4741827, 4741828, 4741829, 4741748, 4741749, 4741750, 4743233, 4743282, 4741244, 4741245, 4741246, 4741247, 4743206, 4743207, 4743208, 4743209, 4743210, 4743211, 4743212, 4743213, 4743214, 4743215, 4743216, 4743217, 4743218, 4743219, 4743360, 4743361, 4743362, 4743363, 4743364, 4743365, 4743366, 4743367, 4743368, 4743369, 4743370, 4743371, 4743372, 4743373, 4743374, 4743375, 4743376, 4743377, 4743283, 4743284, 4741830, 4742501, 4743246, 4743086, 4743087, 4743378, 4743379, 4741751] into sparse-4749909-L04200000199131547

Change-Id: I1492186998ee5230a67cd2efaf8c68d8b008cb7e
parents 1477a4f6 f28568c9
......@@ -35,23 +35,23 @@ interface IAccessibilityServiceConnection {
void setServiceInfo(in AccessibilityServiceInfo info);
boolean findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
String[] findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
long accessibilityNodeId, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags, long threadId,
in Bundle arguments);
boolean findAccessibilityNodeInfosByText(int accessibilityWindowId, long accessibilityNodeId,
String[] findAccessibilityNodeInfosByText(int accessibilityWindowId, long accessibilityNodeId,
String text, int interactionId, IAccessibilityInteractionConnectionCallback callback,
long threadId);
boolean findAccessibilityNodeInfosByViewId(int accessibilityWindowId,
String[] findAccessibilityNodeInfosByViewId(int accessibilityWindowId,
long accessibilityNodeId, String viewId, int interactionId,
IAccessibilityInteractionConnectionCallback callback, long threadId);
boolean findFocus(int accessibilityWindowId, long accessibilityNodeId, int focusType,
String[] findFocus(int accessibilityWindowId, long accessibilityNodeId, int focusType,
int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId);
boolean focusSearch(int accessibilityWindowId, long accessibilityNodeId, int direction,
String[] focusSearch(int accessibilityWindowId, long accessibilityNodeId, int direction,
int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId);
boolean performAccessibilityAction(int accessibilityWindowId, long accessibilityNodeId,
......
......@@ -5871,6 +5871,16 @@ public class Activity extends ContextThemeWrapper
return mComponent;
}
/**
* Temporary method on O-MR1 only.
*
* @hide
*/
@Override
public ComponentName getComponentNameForAutofill() {
return mComponent;
}
/**
* Retrieve a {@link SharedPreferences} object for accessing preferences
* that are private to this activity. This simply calls the underlying
......
......@@ -268,4 +268,9 @@ public abstract class ActivityManagerInternal {
* @param token The IApplicationToken for the activity
*/
public abstract void setFocusedActivity(IBinder token);
/**
* Returns {@code true} if {@code uid} is running an activity from {@code packageName}.
*/
public abstract boolean hasRunningActivity(int uid, @Nullable String packageName);
}
......@@ -2058,6 +2058,16 @@ public class AssistStructure implements Parcelable {
return mActivityComponent;
}
/**
* Called by Autofill server when app forged a different value.
*
* @hide
*/
public void setActivityComponent(ComponentName componentName) {
ensureData();
mActivityComponent = componentName;
}
/** @hide */
public int getFlags() {
return mFlags;
......
/*
* Copyright (C) 2018 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 android.appwidget;
import android.annotation.Nullable;
import android.util.ArraySet;
/**
* App widget manager local system service interface.
*
* @hide Only for use within the system server.
*/
public abstract class AppWidgetManagerInternal {
/**
* Gets the packages from which the uid hosts widgets.
*
* @param uid The potential host UID.
* @return Whether the UID hosts widgets from the package.
*/
public abstract @Nullable ArraySet<String> getHostedWidgetPackages(int uid);
}
......@@ -808,7 +808,11 @@ public final class BluetoothDevice implements Parcelable {
return null;
}
try {
return service.getRemoteName(this);
String name = service.getRemoteName(this);
if (name != null) {
return name.replaceAll("[\\t\\n\\r]+", " ");
}
return null;
} catch (RemoteException e) {
Log.e(TAG, "", e);
}
......
......@@ -42,6 +42,9 @@ import java.util.Comparator;
*/
public class PackageItemInfo {
private static final float MAX_LABEL_SIZE_PX = 500f;
/** The maximum length of a safe label, in characters */
private static final int MAX_SAFE_LABEL_LENGTH = 50000;
/**
* Public name of this item. From the "android:name" attribute.
*/
......@@ -169,7 +172,8 @@ public class PackageItemInfo {
// If the label contains new line characters it may push the UI
// down to hide a part of it. Labels shouldn't have new line
// characters, so just truncate at the first time one is seen.
final int labelLength = labelStr.length();
final int labelLength = Math.min(labelStr.length(), MAX_SAFE_LABEL_LENGTH);
final StringBuffer sb = new StringBuffer(labelLength);
int offset = 0;
while (offset < labelLength) {
final int codePoint = labelStr.codePointAt(offset);
......@@ -181,14 +185,19 @@ public class PackageItemInfo {
break;
}
// replace all non-break space to " " in order to be trimmed
final int charCount = Character.charCount(codePoint);
if (type == Character.SPACE_SEPARATOR) {
labelStr = labelStr.substring(0, offset) + " " + labelStr.substring(offset +
Character.charCount(codePoint));
sb.append(' ');
} else {
sb.append(labelStr.charAt(offset));
if (charCount == 2) {
sb.append(labelStr.charAt(offset + 1));
}
}
offset += Character.charCount(codePoint);
offset += charCount;
}
labelStr = labelStr.trim();
labelStr = sb.toString().trim();
if (labelStr.isEmpty()) {
return packageName;
}
......
......@@ -83,7 +83,7 @@ public class NanoAppFilter {
mAppId = in.readLong();
mAppVersion = in.readInt();
mVersionRestrictionMask = in.readInt();
mAppIdVendorMask = in.readInt();
mAppIdVendorMask = in.readLong();
}
public int describeContents() {
......@@ -91,7 +91,6 @@ public class NanoAppFilter {
}
public void writeToParcel(Parcel out, int flags) {
out.writeLong(mAppId);
out.writeInt(mAppVersion);
out.writeInt(mVersionRestrictionMask);
......
......@@ -2504,6 +2504,28 @@ public class ConnectivityManager {
}
}
/**
* Returns if the active data network for the given UID is metered. A network
* is classified as metered when the user is sensitive to heavy data usage on
* that connection due to monetary costs, data limitations or
* battery/performance issues. You should check this before doing large
* data transfers, and warn the user or delay the operation until another
* network is available.
*
* @return {@code true} if large transfers should be avoided, otherwise
* {@code false}.
*
* @hide
*/
@RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL)
public boolean isActiveNetworkMeteredForUid(int uid) {
try {
return mService.isActiveNetworkMeteredForUid(uid);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* If the LockdownVpn mechanism is enabled, updates the vpn
* with a reload of its profile.
......
......@@ -66,6 +66,7 @@ interface IConnectivityManager
NetworkQuotaInfo getActiveNetworkQuotaInfo();
boolean isActiveNetworkMetered();
boolean isActiveNetworkMeteredForUid(int uid);
boolean requestRouteToHostAddress(int networkType, in byte[] hostAddress);
......
......@@ -1340,6 +1340,13 @@ public final class Parcel {
* @see Parcelable
*/
public final <T extends Parcelable> void writeTypedList(List<T> val) {
writeTypedList(val, 0);
}
/**
* @hide
*/
public <T extends Parcelable> void writeTypedList(List<T> val, int parcelableFlags) {
if (val == null) {
writeInt(-1);
return;
......@@ -1348,13 +1355,7 @@ public final class Parcel {
int i=0;
writeInt(N);
while (i < N) {
T item = val.get(i);
if (item != null) {
writeInt(1);
item.writeToParcel(this, 0);
} else {
writeInt(0);
}
writeTypedObject(val.get(i), parcelableFlags);
i++;
}
}
......
......@@ -694,8 +694,8 @@ public final class MediaStore {
// Log.v(TAG, "getThumbnail: origId="+origId+", kind="+kind+", isVideo="+isVideo);
// If the magic is non-zero, we simply return thumbnail if it does exist.
// querying MediaProvider and simply return thumbnail.
MiniThumbFile thumbFile = new MiniThumbFile(isVideo ? Video.Media.EXTERNAL_CONTENT_URI
: Images.Media.EXTERNAL_CONTENT_URI);
MiniThumbFile thumbFile = MiniThumbFile.instance(
isVideo ? Video.Media.EXTERNAL_CONTENT_URI : Images.Media.EXTERNAL_CONTENT_URI);
Cursor c = null;
try {
long magic = thumbFile.getMagic(origId);
......
......@@ -316,8 +316,8 @@ public final class Dataset implements Parcelable {
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeParcelable(mPresentation, flags);
parcel.writeTypedArrayList(mFieldIds, flags);
parcel.writeTypedArrayList(mFieldValues, flags);
parcel.writeTypedList(mFieldIds, flags);
parcel.writeTypedList(mFieldValues, flags);
parcel.writeParcelableList(mFieldPresentations, flags);
parcel.writeParcelable(mAuthentication, flags);
parcel.writeString(mId);
......@@ -333,8 +333,9 @@ public final class Dataset implements Parcelable {
final Builder builder = (presentation == null)
? new Builder()
: new Builder(presentation);
final ArrayList<AutofillId> ids = parcel.readTypedArrayList(null);
final ArrayList<AutofillValue> values = parcel.readTypedArrayList(null);
final ArrayList<AutofillId> ids = parcel.createTypedArrayList(AutofillId.CREATOR);
final ArrayList<AutofillValue> values =
parcel.createTypedArrayList(AutofillValue.CREATOR);
final ArrayList<RemoteViews> presentations = new ArrayList<>();
parcel.readParcelableList(presentations, null);
final int idCount = (ids != null) ? ids.size() : 0;
......
......@@ -19,9 +19,9 @@ package android.service.autofill;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
......@@ -45,7 +45,7 @@ public final class SaveRequest implements Parcelable {
}
private SaveRequest(@NonNull Parcel parcel) {
this(parcel.readTypedArrayList(null), parcel.readBundle());
this(parcel.createTypedArrayList(FillContext.CREATOR), parcel.readBundle());
}
/**
......@@ -57,7 +57,7 @@ public final class SaveRequest implements Parcelable {
/**
* Gets the extra client state returned from the last {@link
* AutofillService#onFillRequest(FillRequest, CancellationSignal, FillCallback)}
* AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)}
* fill request}.
*
* @return The client state.
......@@ -73,7 +73,7 @@ public final class SaveRequest implements Parcelable {
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeTypedArrayList(mFillContexts, flags);
parcel.writeTypedList(mFillContexts, flags);
parcel.writeBundle(mClientState);
}
......
......@@ -7733,6 +7733,7 @@ public final class ViewRootImpl implements ViewParent,
if (!registered) {
mAttachInfo.mAccessibilityWindowId =
mAccessibilityManager.addAccessibilityInteractionConnection(mWindow,
mContext.getPackageName(),
new AccessibilityInteractionConnection(ViewRootImpl.this));
}
}
......
......@@ -610,6 +610,11 @@ public interface WindowManagerPolicy {
*/
void notifyKeyguardTrustedChanged();
/**
* The keyguard showing state has changed
*/
void onKeyguardShowingAndNotOccludedChanged();
/**
* Notifies the window manager that screen is being turned off.
*
......
......@@ -885,7 +885,7 @@ public final class AccessibilityManager {
* @hide
*/
public int addAccessibilityInteractionConnection(IWindow windowToken,
IAccessibilityInteractionConnection connection) {
String packageName, IAccessibilityInteractionConnection connection) {
final IAccessibilityManager service;
final int userId;
synchronized (mLock) {
......@@ -896,7 +896,8 @@ public final class AccessibilityManager {
userId = mUserId;
}
try {
return service.addAccessibilityInteractionConnection(windowToken, connection, userId);
return service.addAccessibilityInteractionConnection(windowToken, connection,
packageName, userId);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re);
}
......
......@@ -45,7 +45,7 @@ interface IAccessibilityManager {
List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType, int userId);
int addAccessibilityInteractionConnection(IWindow windowToken,
in IAccessibilityInteractionConnection connection, int userId);
in IAccessibilityInteractionConnection connection, String packageName, int userId);
void removeAccessibilityInteractionConnection(IWindow windowToken);
......
......@@ -24,6 +24,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemService;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
......@@ -44,6 +45,7 @@ import android.view.View;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.Preconditions;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
......@@ -384,13 +386,20 @@ public final class AutofillManager {
* Runs the specified action on the UI thread.
*/
void runOnUiThread(Runnable action);
/**
* Gets the complete component name of this client.
*
* <p>Temporary method on O-MR1 only.
*/
ComponentName getComponentNameForAutofill();
}
/**
* @hide
*/
public AutofillManager(Context context, IAutoFillManager service) {
mContext = context;
mContext = Preconditions.checkNotNull(context, "context cannot be null");
mService = service;
}
......@@ -940,6 +949,10 @@ public final class AutofillManager {
return mContext.getAutofillClient();
}
private ComponentName getComponentNameFromContext(AutofillClient client) {
return client == null ? null : client.getComponentNameForAutofill();
}
/** @hide */
public void onAuthenticationResult(int authenticationId, Intent data) {
if (!hasAutofillFeature()) {
......@@ -990,13 +1003,18 @@ public final class AutofillManager {
return;
}
try {
final AutofillClient client = getClientLocked();
final ComponentName componentName = getComponentNameFromContext(client);
if (componentName == null) {
Log.w(TAG, "startSessionLocked(): context is not activity: " + mContext);
return;
}
mSessionId = mService.startSession(mContext.getActivityToken(),
mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
mCallback != null, flags, mContext.getOpPackageName());
mCallback != null, flags, componentName);
if (mSessionId != NO_SESSION) {
mState = STATE_ACTIVE;
}
final AutofillClient client = getClientLocked();
if (client != null) {
client.autofillCallbackResetableStateAvailable();
}
......@@ -1050,14 +1068,19 @@ public final class AutofillManager {
try {
if (restartIfNecessary) {
final AutofillClient client = getClientLocked();
final ComponentName componentName = getComponentNameFromContext(client);
if (componentName == null) {
Log.w(TAG, "startSessionLocked(): context is not activity: " + mContext);
return;
}
final int newId = mService.updateOrRestartSession(mContext.getActivityToken(),
mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
mCallback != null, flags, mContext.getOpPackageName(), mSessionId, action);
mCallback != null, flags, componentName, mSessionId, action);
if (newId != mSessionId) {
if (sDebug) Log.d(TAG, "Session restarted: " + mSessionId + "=>" + newId);
mSessionId = newId;
mState = (mSessionId == NO_SESSION) ? STATE_UNKNOWN : STATE_ACTIVE;
final AutofillClient client = getClientLocked();
if (client != null) {
client.autofillCallbackResetableStateAvailable();
}
......
......@@ -16,6 +16,7 @@
package android.view.autofill;
import android.content.ComponentName;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
......@@ -34,14 +35,15 @@ interface IAutoFillManager {
int addClient(in IAutoFillManagerClient client, int userId);
int startSession(IBinder activityToken, in IBinder appCallback, in AutofillId autoFillId,
in Rect bounds, in AutofillValue value, int userId, boolean hasCallback, int flags,
String packageName);
in ComponentName componentName);
FillEventHistory getFillEventHistory();
boolean restoreSession(int sessionId, in IBinder activityToken, in IBinder appCallback);
void updateSession(int sessionId, in AutofillId id, in Rect bounds,
in AutofillValue value, int action, int flags, int userId);
int updateOrRestartSession(IBinder activityToken, in IBinder appCallback,
in AutofillId autoFillId, in Rect bounds, in AutofillValue value, int userId,
boolean hasCallback, int flags, String packageName, int sessionId, int action);
boolean hasCallback, int flags, in ComponentName componentName, int sessionId,
int action);
void finishSession(int sessionId, int userId);
void cancelSession(int sessionId, int userId);
void setAuthenticationResult(in Bundle data, int sessionId, int authenticationId, int userId);
......
......@@ -457,6 +457,22 @@ status_t ResStringPool::setTo(const void* data, size_t size, bool copyData)
uninit();
// The chunk must be at least the size of the string pool header.
if (size < sizeof(ResStringPool_header)) {
ALOGW("Bad string block: data size %zu is too small to be a string block", size);
return (mError=BAD_TYPE);
}
// The data is at least as big as a ResChunk_header, so we can safely validate the other
// header fields.
// `data + size` is safe because the source of `size` comes from the kernel/filesystem.
if (validate_chunk(reinterpret_cast<const ResChunk_header*>(data), sizeof(ResStringPool_header),
reinterpret_cast<const uint8_t*>(data) + size,
"ResStringPool_header") != NO_ERROR) {
ALOGW("Bad string block: malformed block dimensions");
return (mError=BAD_TYPE);
}
const bool notDeviceEndian = htods(0xf0) != 0xf0;
if (copyData || notDeviceEndian) {
......@@ -468,6 +484,8 @@ status_t ResStringPool::setTo(const void* data, size_t size, bool copyData)
data = mOwnedData;
}
// The size has been checked, so it is safe to read the data in the ResStringPool_header
// data structure.
mHeader = (const ResStringPool_header*)data;
if (notDeviceEndian) {
......@@ -6558,8 +6576,16 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg,
}
} else if (ctype == RES_TABLE_LIBRARY_TYPE) {
if (group->dynamicRefTable.entries().size() == 0) {
status_t err = group->dynamicRefTable.load((const ResTable_lib_header*) chunk);
const ResTable_lib_header* lib = (const ResTable_lib_header*) chunk;
status_t err = validate_chunk(&lib->header, sizeof(*lib),
endPos, "ResTable_lib_header");
if (err != NO_ERROR) {
return (mError=err);
}
err = group->dynamicRefTable.load(lib);
if (err != NO_ERROR) {
return (mError=err);
}
......
......@@ -2363,10 +2363,10 @@ public class MediaPlayer extends PlayerBase
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mTrackType);
dest.writeString(mFormat.getString(MediaFormat.KEY_MIME));
dest.writeString(getLanguage());
if (mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) {
dest.writeString(mFormat.getString(MediaFormat.KEY_MIME));
dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_AUTOSELECT));
dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_DEFAULT));
dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE));
......
......@@ -323,7 +323,6 @@ public class MediaScanner implements AutoCloseable {
private final Uri mAudioUri;
private final Uri mVideoUri;
private final Uri mImagesUri;
private final Uri mThumbsUri;
private final Uri mPlaylistsUri;
private final Uri mFilesUri;
private final Uri mFilesUriNoNotify;
......@@ -419,7 +418,6 @@ public class MediaScanner implements AutoCloseable {
mAudioUri = Audio.Media.getContentUri(volumeName);
mVideoUri = Video.Media.getContentUri(volumeName);
mImagesUri = Images.Media.getContentUri(volumeName);
mThumbsUri = Images.Thumbnails.getContentUri(volumeName);
mFilesUri = Files.getContentUri(volumeName);
mFilesUriNoNotify = mFilesUri.buildUpon().appendQueryParameter("nonotify", "1").build();
......@@ -1283,53 +1281,6 @@ public class MediaScanner implements AutoCloseable {
}
}
private void pruneDeadThumbnailFiles() {
HashSet<String> existingFiles = new HashSet<String>();
String directory = "/sdcard/DCIM/.thumbnails";
String [] files = (new File(directory)).list();
Cursor c = null;
if (files == null)
files = new String[0];
for (int i = 0; i < files.length; i++) {
String fullPathString = directory + "/" + files[i];
existingFiles.add(fullPathString);
}
try {
c = mMediaProvider.query(
mThumbsUri,
new String [] { "_data" },
null,
null,
null, null);
Log.v(TAG, "pruneDeadThumbnailFiles... " + c);
if (c != null && c.moveToFirst()) {
do {
String fullPathString = c.getString(0);
existingFiles.remove(fullPathString);
} while (c.moveToNext());
}
for (String fileToDelete : existingFiles) {
if (false)
Log.v(TAG, "fileToDelete is " + fileToDelete);
try {
(new File(fileToDelete)).delete();
} catch (SecurityException ex) {
}
}
Log.v(TAG, "/pruneDeadThumbnailFiles... " + c);
} catch (RemoteException e) {
// We will soon be killed...
} finally {
if (c != null) {
c.close();
}
}
}
static class MediaBulkDeleter {
StringBuilder whereClause = new StringBuilder();
ArrayList<String> whereArgs = new ArrayList<String>(100);
......@@ -1373,9 +1324,6 @@ public class MediaScanner implements AutoCloseable {
processPlayLists();
}
if (mOriginalCount == 0 && mImagesUri.equals(Images.Media.getContentUri("external")))
pruneDeadThumbnailFiles();
// allow GC to clean up
mPlayLists.clear();
}
......
......@@ -44,13 +44,14 @@ import java.util.Hashtable;
*/
public class MiniThumbFile {
private static final String TAG = "MiniThumbFile";
private static final int MINI_THUMB_DATA_FILE_VERSION = 3;
private static final int MINI_THUMB_DATA_FILE_VERSION = 4;
public static final int BYTES_PER_MINTHUMB = 10000;
private static final int HEADER_SIZE = 1 + 8 + 4;
private Uri mUri;
private RandomAccessFile mMiniThumbFile;
private FileChannel mChannel;
private ByteBuffer mBuffer;
private ByteBuffer mEmptyBuffer;
private static final Hashtable<String, MiniThumbFile> sThumbFiles =
new Hashtable<String, MiniThumbFile>();
......@@ -127,9 +128,10 @@ public class MiniThumbFile {
return mMiniThumbFile;
}
public MiniThumbFile(Uri uri) {
private MiniThumbFile(Uri uri) {
mUri = uri;
mBuffer = ByteBuffer.allocateDirect(BYTES_PER_MINTHUMB);
mEmptyBuffer = ByteBuffer.allocateDirect(BYTES_PER_MINTHUMB);
}
public synchronized void deactivate() {
......@@ -184,6 +186,54 @@ public class MiniThumbFile {
return 0;
}
public synchronized void eraseMiniThumb(long id) {
RandomAccessFile r = miniThumbDataFile();
if (r != null) {
long pos = id * BYTES_PER_MINTHUMB;
FileLock lock = null;
try {
mBuffer.clear();
mBuffer.limit(1 + 8);
lock = mChannel.lock(pos, BYTES_PER_MINTHUMB, false);
// check that we can read the following 9 bytes
// (1 for the "status" and 8 for the long)
if (mChannel.read(mBuffer, pos) == 9) {
mBuffer.position(0);
if (mBuffer.get() == 1) {
long currentMagic = mBuffer.getLong();
if (currentMagic == 0) {