Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c0cbfda0 authored by Bjorn Bringert's avatar Bjorn Bringert Committed by The Android Open Source Project
Browse files

am 963cd006: Allow creating AssetFileDescriptors for MemoryFiles.

Merge commit '963cd006'

* commit '963cd006':
  Allow creating AssetFileDescriptors for MemoryFiles.
parents ae8c5b86 963cd006
Loading
Loading
Loading
Loading
+88 −0
Original line number Original line Diff line number Diff line
@@ -16,6 +16,7 @@


package android.content.res;
package android.content.res;


import android.os.MemoryFile;
import android.os.Parcel;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.os.Parcelable;
@@ -24,6 +25,8 @@ import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.FileChannel;


/**
/**
 * File descriptor of an entry in the AssetManager.  This provides your own
 * File descriptor of an entry in the AssetManager.  This provides your own
@@ -123,6 +126,13 @@ public class AssetFileDescriptor implements Parcelable {
        mFd.close();
        mFd.close();
    }
    }
    
    
    /**
     * Checks whether this file descriptor is for a memory file.
     */
    private boolean isMemoryFile() throws IOException {
        return MemoryFile.isMemoryFile(mFd.getFileDescriptor());
    }

    /**
    /**
     * Create and return a new auto-close input stream for this asset.  This
     * Create and return a new auto-close input stream for this asset.  This
     * will either return a full asset {@link AutoCloseInputStream}, or
     * will either return a full asset {@link AutoCloseInputStream}, or
@@ -132,6 +142,12 @@ public class AssetFileDescriptor implements Parcelable {
     * should only call this once for a particular asset.
     * should only call this once for a particular asset.
     */
     */
    public FileInputStream createInputStream() throws IOException {
    public FileInputStream createInputStream() throws IOException {
        if (isMemoryFile()) {
            if (mLength > Integer.MAX_VALUE) {
                throw new IOException("File length too large for a memory file: " + mLength);
            }
            return new AutoCloseMemoryFileInputStream(mFd, (int)mLength);
        }
        if (mLength < 0) {
        if (mLength < 0) {
            return new ParcelFileDescriptor.AutoCloseInputStream(mFd);
            return new ParcelFileDescriptor.AutoCloseInputStream(mFd);
        }
        }
@@ -261,6 +277,66 @@ public class AssetFileDescriptor implements Parcelable {
        }
        }
    }
    }
    
    
    /**
     * An input stream that reads from a MemoryFile and closes it when the stream is closed.
     * This extends FileInputStream just because {@link #createInputStream} returns
     * a FileInputStream. All the FileInputStream methods are
     * overridden to use the MemoryFile instead.
     */
    private static class AutoCloseMemoryFileInputStream extends FileInputStream {
        private ParcelFileDescriptor mParcelFd;
        private MemoryFile mFile;
        private InputStream mStream;

        public AutoCloseMemoryFileInputStream(ParcelFileDescriptor fd, int length)
                throws IOException {
            super(fd.getFileDescriptor());
            mParcelFd = fd;
            mFile = new MemoryFile(fd.getFileDescriptor(), length, "r");
            mStream = mFile.getInputStream();
        }

        @Override
        public int available() throws IOException {
            return mStream.available();
        }

        @Override
        public void close() throws IOException {
            mParcelFd.close();  // must close ParcelFileDescriptor, not just the file descriptor,
                                // since it could be a subclass of ParcelFileDescriptor.
                                // E.g. ContentResolver.ParcelFileDescriptorInner.close() releases
                                // a content provider
            mFile.close();      // to unmap the memory file from the address space.
            mStream.close();    // doesn't actually do anything
        }

        @Override
        public FileChannel getChannel() {
            return null;
        }

        @Override
        public int read() throws IOException {
            return mStream.read();
        }

        @Override
        public int read(byte[] buffer, int offset, int count) throws IOException {
            return mStream.read(buffer, offset, count);
        }

        @Override
        public int read(byte[] buffer) throws IOException {
            return mStream.read(buffer);
        }

        @Override
        public long skip(long count) throws IOException {
            return mStream.skip(count);
        }
    }

    /**
    /**
     * An OutputStream you can create on a ParcelFileDescriptor, which will
     * An OutputStream you can create on a ParcelFileDescriptor, which will
     * take care of calling {@link ParcelFileDescriptor#close
     * take care of calling {@link ParcelFileDescriptor#close
@@ -345,4 +421,16 @@ public class AssetFileDescriptor implements Parcelable {
            return new AssetFileDescriptor[size];
            return new AssetFileDescriptor[size];
        }
        }
    };
    };

    /**
     * Creates an AssetFileDescriptor from a memory file.
     *
     * @hide
     */
    public static AssetFileDescriptor fromMemoryFile(MemoryFile memoryFile)
            throws IOException {
        ParcelFileDescriptor fd = memoryFile.getParcelFileDescriptor();
        return new AssetFileDescriptor(fd, 0, memoryFile.length());
    }

}
}
+112 −5
Original line number Original line Diff line number Diff line
@@ -37,9 +37,14 @@ public class MemoryFile
{
{
    private static String TAG = "MemoryFile";
    private static String TAG = "MemoryFile";


    // mmap(2) protection flags from <sys/mman.h>
    private static final int PROT_READ = 0x1;
    private static final int PROT_WRITE = 0x2;

    private static native FileDescriptor native_open(String name, int length) throws IOException;
    private static native FileDescriptor native_open(String name, int length) throws IOException;
    // returns memory address for ashmem region
    // returns memory address for ashmem region
    private static native int native_mmap(FileDescriptor fd, int length) throws IOException;
    private static native int native_mmap(FileDescriptor fd, int length, int mode)
            throws IOException;
    private static native void native_munmap(int addr, int length) throws IOException;
    private static native void native_munmap(int addr, int length) throws IOException;
    private static native void native_close(FileDescriptor fd);
    private static native void native_close(FileDescriptor fd);
    private static native int native_read(FileDescriptor fd, int address, byte[] buffer,
    private static native int native_read(FileDescriptor fd, int address, byte[] buffer,
@@ -47,14 +52,16 @@ public class MemoryFile
    private static native void native_write(FileDescriptor fd, int address, byte[] buffer,
    private static native void native_write(FileDescriptor fd, int address, byte[] buffer,
            int srcOffset, int destOffset, int count, boolean isUnpinned) throws IOException;
            int srcOffset, int destOffset, int count, boolean isUnpinned) throws IOException;
    private static native void native_pin(FileDescriptor fd, boolean pin) throws IOException;
    private static native void native_pin(FileDescriptor fd, boolean pin) throws IOException;
    private static native boolean native_is_ashmem_region(FileDescriptor fd) throws IOException;


    private FileDescriptor mFD;        // ashmem file descriptor
    private FileDescriptor mFD;        // ashmem file descriptor
    private int mAddress;   // address of ashmem memory
    private int mAddress;   // address of ashmem memory
    private int mLength;    // total length of our ashmem region
    private int mLength;    // total length of our ashmem region
    private boolean mAllowPurging = false;  // true if our ashmem region is unpinned
    private boolean mAllowPurging = false;  // true if our ashmem region is unpinned
    private final boolean mOwnsRegion;  // false if this is a ref to an existing ashmem region


    /**
    /**
     * MemoryFile constructor.
     * Allocates a new ashmem region. The region is initially not purgable.
     *
     *
     * @param name optional name for the file (can be null).
     * @param name optional name for the file (can be null).
     * @param length of the memory file in bytes.
     * @param length of the memory file in bytes.
@@ -63,11 +70,43 @@ public class MemoryFile
    public MemoryFile(String name, int length) throws IOException {
    public MemoryFile(String name, int length) throws IOException {
        mLength = length;
        mLength = length;
        mFD = native_open(name, length);
        mFD = native_open(name, length);
        mAddress = native_mmap(mFD, length);
        mAddress = native_mmap(mFD, length, PROT_READ | PROT_WRITE);
        mOwnsRegion = true;
    }

    /**
     * Creates a reference to an existing memory file. Changes to the original file
     * will be available through this reference.
     * Calls to {@link #allowPurging(boolean)} on the returned MemoryFile will fail.
     *
     * @param fd File descriptor for an existing memory file, as returned by
     *        {@link #getFileDescriptor()}. This file descriptor will be closed
     *        by {@link #close()}.
     * @param length Length of the memory file in bytes.
     * @param mode File mode. Currently only "r" for read-only access is supported.
     * @throws NullPointerException if <code>fd</code> is null.
     * @throws IOException If <code>fd</code> does not refer to an existing memory file,
     *         or if the file mode of the existing memory file is more restrictive
     *         than <code>mode</code>.
     *
     * @hide
     */
    public MemoryFile(FileDescriptor fd, int length, String mode) throws IOException {
        if (fd == null) {
            throw new NullPointerException("File descriptor is null.");
        }
        if (!isMemoryFile(fd)) {
            throw new IllegalArgumentException("Not a memory file.");
        }
        mLength = length;
        mFD = fd;
        mAddress = native_mmap(mFD, length, modeToProt(mode));
        mOwnsRegion = false;
    }
    }


    /**
    /**
     * Closes and releases all resources for the memory file.
     * Closes the memory file. If there are no other open references to the memory
     * file, it will be deleted.
     */
     */
    public void close() {
    public void close() {
        deactivate();
        deactivate();
@@ -76,7 +115,14 @@ public class MemoryFile
        }
        }
    }
    }


    private void deactivate() {
    /**
     * Unmaps the memory file from the process's memory space, but does not close it.
     * After this method has been called, read and write operations through this object
     * will fail, but {@link #getFileDescriptor()} will still return a valid file descriptor.
     *
     * @hide
     */
    public void deactivate() {
        if (!isDeactivated()) {
        if (!isDeactivated()) {
            try {
            try {
                native_munmap(mAddress, mLength);
                native_munmap(mAddress, mLength);
@@ -135,6 +181,9 @@ public class MemoryFile
     * @return previous value of allowPurging
     * @return previous value of allowPurging
     */
     */
    synchronized public boolean allowPurging(boolean allowPurging) throws IOException {
    synchronized public boolean allowPurging(boolean allowPurging) throws IOException {
        if (!mOwnsRegion) {
            throw new IOException("Only the owner can make ashmem regions purgable.");
        }
        boolean oldValue = mAllowPurging;
        boolean oldValue = mAllowPurging;
        if (oldValue != allowPurging) {
        if (oldValue != allowPurging) {
            native_pin(mFD, !allowPurging);
            native_pin(mFD, !allowPurging);
@@ -210,6 +259,64 @@ public class MemoryFile
        native_write(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging);
        native_write(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging);
    }
    }


    /**
     * Gets a ParcelFileDescriptor for the memory file. See {@link #getFileDescriptor()}
     * for caveats. This must be here to allow classes outside <code>android.os</code< to
     * make ParcelFileDescriptors from MemoryFiles, as
     * {@link ParcelFileDescriptor#ParcelFileDescriptor(FileDescriptor)} is package private.
     *
     *
     * @return The file descriptor owned by this memory file object.
     *         The file descriptor is not duplicated.
     * @throws IOException If the memory file has been closed.
     *
     * @hide
     */
    public ParcelFileDescriptor getParcelFileDescriptor() throws IOException {
        return new ParcelFileDescriptor(getFileDescriptor());
    }

    /**
     * Gets a FileDescriptor for the memory file. Note that this file descriptor
     * is only safe to pass to {@link #MemoryFile(FileDescriptor,int)}). It
     * should not be used with file descriptor operations that expect a file descriptor
     * for a normal file.
     *
     * The returned file descriptor is not duplicated.
     *
     * @throws IOException If the memory file has been closed.
     *
     * @hide
     */
    public FileDescriptor getFileDescriptor() throws IOException {
        return mFD;
    }

    /**
     * Checks whether the given file descriptor refers to a memory file.
     *
     * @throws IOException If <code>fd</code> is not a valid file descriptor.
     *
     * @hide
     */
    public static boolean isMemoryFile(FileDescriptor fd) throws IOException {
        return native_is_ashmem_region(fd);
    }

    /**
     * Converts a file mode string to a <code>prot</code> value as expected by
     * native_mmap().
     *
     * @throws IllegalArgumentException if the file mode is invalid.
     */
    private static int modeToProt(String mode) {
        if ("r".equals(mode)) {
            return PROT_READ;
        } else {
            throw new IllegalArgumentException("Unsupported file mode: '" + mode + "'");
        }
    }

    private class MemoryInputStream extends InputStream {
    private class MemoryInputStream extends InputStream {


        private int mMark = 0;
        private int mMark = 0;
+26 −4
Original line number Original line Diff line number Diff line
@@ -46,10 +46,10 @@ static jobject android_os_MemoryFile_open(JNIEnv* env, jobject clazz, jstring na
}
}


static jint android_os_MemoryFile_mmap(JNIEnv* env, jobject clazz, jobject fileDescriptor,
static jint android_os_MemoryFile_mmap(JNIEnv* env, jobject clazz, jobject fileDescriptor,
        jint length)
        jint length, jint prot)
{
{
    int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
    int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
    jint result = (jint)mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
    jint result = (jint)mmap(NULL, length, prot, MAP_SHARED, fd, 0);
    if (!result)
    if (!result)
        jniThrowException(env, "java/io/IOException", "mmap failed");
        jniThrowException(env, "java/io/IOException", "mmap failed");
    return result;
    return result;
@@ -118,14 +118,36 @@ static void android_os_MemoryFile_pin(JNIEnv* env, jobject clazz, jobject fileDe
    }
    }
}
}


static jboolean android_os_MemoryFile_is_ashmem_region(JNIEnv* env, jobject clazz,
        jobject fileDescriptor) {
    int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
    // Use ASHMEM_GET_SIZE to find out if the fd refers to an ashmem region.
    // ASHMEM_GET_SIZE should succeed for all ashmem regions, and the kernel
    // should return ENOTTY for all other valid file descriptors
    int result = ashmem_get_size_region(fd);
    if (result < 0) {
        if (errno == ENOTTY) {
            // ENOTTY means that the ioctl does not apply to this object,
            // i.e., it is not an ashmem region.
            return JNI_FALSE;
        }
        // Some other error, throw exception
        jniThrowIOException(env, errno);
        return JNI_FALSE;
    }
    return JNI_TRUE;
}

static const JNINativeMethod methods[] = {
static const JNINativeMethod methods[] = {
    {"native_open",  "(Ljava/lang/String;I)Ljava/io/FileDescriptor;", (void*)android_os_MemoryFile_open},
    {"native_open",  "(Ljava/lang/String;I)Ljava/io/FileDescriptor;", (void*)android_os_MemoryFile_open},
    {"native_mmap",  "(Ljava/io/FileDescriptor;I)I", (void*)android_os_MemoryFile_mmap},
    {"native_mmap",  "(Ljava/io/FileDescriptor;II)I", (void*)android_os_MemoryFile_mmap},
    {"native_munmap", "(II)V", (void*)android_os_MemoryFile_munmap},
    {"native_munmap", "(II)V", (void*)android_os_MemoryFile_munmap},
    {"native_close", "(Ljava/io/FileDescriptor;)V", (void*)android_os_MemoryFile_close},
    {"native_close", "(Ljava/io/FileDescriptor;)V", (void*)android_os_MemoryFile_close},
    {"native_read",  "(Ljava/io/FileDescriptor;I[BIIIZ)I", (void*)android_os_MemoryFile_read},
    {"native_read",  "(Ljava/io/FileDescriptor;I[BIIIZ)I", (void*)android_os_MemoryFile_read},
    {"native_write", "(Ljava/io/FileDescriptor;I[BIIIZ)V", (void*)android_os_MemoryFile_write},
    {"native_write", "(Ljava/io/FileDescriptor;I[BIIIZ)V", (void*)android_os_MemoryFile_write},
    {"native_pin",   "(Ljava/io/FileDescriptor;Z)V", (void*)android_os_MemoryFile_pin},
    {"native_pin",   "(Ljava/io/FileDescriptor;Z)V", (void*)android_os_MemoryFile_pin},
    {"native_is_ashmem_region", "(Ljava/io/FileDescriptor;)Z",
            (void*)android_os_MemoryFile_is_ashmem_region}
};
};


static const char* const kClassPathName = "android/os/MemoryFile";
static const char* const kClassPathName = "android/os/MemoryFile";
+6 −0
Original line number Original line Diff line number Diff line
@@ -206,6 +206,12 @@
            <meta-data android:name="com.android.unit_tests.reference" android:resource="@xml/metadata" />
            <meta-data android:name="com.android.unit_tests.reference" android:resource="@xml/metadata" />
        </provider>
        </provider>


        <!-- Application components used for content tests -->
        <provider android:name=".content.MemoryFileProvider"
                android:authorities="com.android.unit_tests.content.MemoryFileProvider"
                android:process=":MemoryFileProvider">
        </provider>

        <!-- Application components used for os tests -->
        <!-- Application components used for os tests -->


        <service android:name=".os.MessengerService"
        <service android:name=".os.MessengerService"
+211 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2009 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.unit_tests.content;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.os.MemoryFile;
import android.os.ParcelFileDescriptor;
import android.util.Log;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;

/** Simple test provider that runs in the local process. */
public class MemoryFileProvider extends ContentProvider {
    private static final String TAG = "MemoryFileProvider";

    private static final String DATA_FILE = "data.bin";

    // some random data
    public static final byte[] TEST_BLOB = new byte[] {
        -12,  127, 0, 3, 1, 2, 3, 4, 5, 6, 1, -128, -1, -54, -65, 35,
        -53, -96, -74, -74, -55, -43, -69, 3, 52, -58,
        -121, 127, 87, -73, 16, -13, -103, -65, -128, -36,
        107, 24, 118, -17, 97, 97, -88, 19, -94, -54,
        53, 43, 44, -27, -124, 28, -74, 26, 35, -36,
        16, -124, -31, -31, -128, -79, 108, 116, 43, -17 };

    private SQLiteOpenHelper mOpenHelper;

    private static final int DATA_ID_BLOB = 1;
    private static final int HUGE = 2;
    private static final int FILE = 3;

    private static final UriMatcher sURLMatcher = new UriMatcher(
            UriMatcher.NO_MATCH);

    static {
        sURLMatcher.addURI("*", "data/#/blob", DATA_ID_BLOB);
        sURLMatcher.addURI("*", "huge", HUGE);
        sURLMatcher.addURI("*", "file", FILE);
    }

    private static class DatabaseHelper extends SQLiteOpenHelper {
        private static final String DATABASE_NAME = "local.db";
        private static final int DATABASE_VERSION = 1;

        public DatabaseHelper(Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
            db.execSQL("CREATE TABLE data (" +
                       "_id INTEGER PRIMARY KEY," +
                       "_blob TEXT, " +
                       "integer INTEGER);");

            // insert alarms
            ContentValues values = new ContentValues();
            values.put("_id", 1);
            values.put("_blob", TEST_BLOB);
            values.put("integer", 100);
            db.insert("data", null, values);
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
            Log.w(TAG, "Upgrading test database from version " +
                  oldVersion + " to " + currentVersion +
                  ", which will destroy all old data");
            db.execSQL("DROP TABLE IF EXISTS data");
            onCreate(db);
        }
    }


    public MemoryFileProvider() {
    }

    @Override
    public boolean onCreate() {
        mOpenHelper = new DatabaseHelper(getContext());
        try {
            OutputStream out = getContext().openFileOutput(DATA_FILE, Context.MODE_PRIVATE);
            out.write(TEST_BLOB);
            out.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        return true;
    }

    @Override
    public Cursor query(Uri url, String[] projectionIn, String selection,
            String[] selectionArgs, String sort) {
        throw new UnsupportedOperationException("query not supported");
    }

    @Override
    public String getType(Uri url) {
        int match = sURLMatcher.match(url);
        switch (match) {
            case DATA_ID_BLOB:
                return "application/octet-stream";
            case FILE:
                return "application/octet-stream";
            default:
                throw new IllegalArgumentException("Unknown URL");
        }
    }

    @Override
    public AssetFileDescriptor openAssetFile(Uri url, String mode) throws FileNotFoundException {
        int match = sURLMatcher.match(url);
        switch (match) {
            case DATA_ID_BLOB:
                String sql = "SELECT _blob FROM data WHERE _id=" + url.getPathSegments().get(1);
                return getBlobColumnAsAssetFile(url, mode, sql);
            case HUGE:
                try {
                    MemoryFile memoryFile = new MemoryFile(null, 5000000);
                    memoryFile.writeBytes(TEST_BLOB, 0, 1000000, TEST_BLOB.length);
                    memoryFile.deactivate();
                    return AssetFileDescriptor.fromMemoryFile(memoryFile);
                } catch (IOException ex) {
                    throw new FileNotFoundException("Error reading " + url + ":" + ex.toString());
                }
            case FILE:
                File file = getContext().getFileStreamPath(DATA_FILE);
                ParcelFileDescriptor fd =
                        ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
                return new AssetFileDescriptor(fd, 0, AssetFileDescriptor.UNKNOWN_LENGTH);
            default:
                throw new FileNotFoundException("No files supported by provider at " + url);
        }
    }

    private AssetFileDescriptor getBlobColumnAsAssetFile(Uri url, String mode, String sql)
            throws FileNotFoundException {
        if (!"r".equals(mode)) {
            throw new FileNotFoundException("Mode " + mode + " not supported for " + url);
        }
        try {
            SQLiteDatabase db = mOpenHelper.getReadableDatabase();
            MemoryFile file = simpleQueryForBlobMemoryFile(db, sql);
            if (file == null) throw new FileNotFoundException("No such entry: " + url);
            AssetFileDescriptor afd = AssetFileDescriptor.fromMemoryFile(file);
            file.deactivate();
            // need to dup and then close? openFileHelper() doesn't do that though
            return afd;
        } catch (IOException ex) {
            throw new FileNotFoundException("Error reading " + url + ":" + ex.toString());
        }
    }

    private MemoryFile simpleQueryForBlobMemoryFile(SQLiteDatabase db, String sql) throws IOException {
        Cursor cursor = db.rawQuery(sql, null);
        try {
            if (!cursor.moveToFirst()) {
                return null;
            }
            byte[] bytes = cursor.getBlob(0);
            MemoryFile file = new MemoryFile(null, bytes.length);
            file.writeBytes(bytes, 0, 0, bytes.length);
            return file;
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
    }

    @Override
    public int update(Uri url, ContentValues values, String where, String[] whereArgs) {
        throw new UnsupportedOperationException("update not supported");
    }

    @Override
    public Uri insert(Uri url, ContentValues initialValues) {
        throw new UnsupportedOperationException("insert not supported");
    }

    @Override
    public int delete(Uri url, String where, String[] whereArgs) {
        throw new UnsupportedOperationException("delete not supported");
    }
}
Loading