MediaProvider Process Analysis

Keywords: Java Database Android Mobile

MediaProvider Process Analysis

1. Summary

Android Media Provider uses the SQLite database to store the information of multimedia files such as pictures, videos, audio, documents, etc. for use by video player, music player, gallery and document editor. According to Google's requirements, other Android applications need to be read through content providers when they get a list of files. Similarly, Telephone Provider, Calendar Provider, Contacts Provider, and it's not hard to understand how to master one of them.

2. Introduction to MediaProvider Foundation

2.1. MediaProvider Location in Android

MediaProvider's role in Android can be seen in the following figure:

2.2. MediaProvider Scanning Process

The scanning flow chart of MediaProvider is as follows

[External Link Picture Transfer Failure (img-a)

2.3. Mddia Provider database correlation

2.3.1. Location and viewing method of database files

The database files involved by MediaProvicer are as follows:

/data/data/com.android.providers.media/databases # ls

external.db external.db-shm external.db-wal internal.db

How to view the database file:

Access the adb shell with root privilege and open a database on the mobile phone / data/data/com.android.providers.media/databases with sqlite3. The database that begins with external stores SD card media information, one card corresponds to another, so if the mobile phone uses too many cards, there will be multiple databases. The database, which starts with internal, stores the media information in the internal memory of the mobile phone. Because the general user can not access the internal memory of the mobile phone, and the two database structures are basically the same, so only need to pay attention to external database.

Note: The database is named in a form similar to external-ffffffff.db. The following eight hexadecimal characters are the Volume ID of the SD card FAT partition. The ID is determined when partitioning. It can only be changed if partitioning is re-partitioned or manually changed, which can prevent database conflicts when inserting different SD cards. For a brief understanding of FAT filesystems, see  Understanding FAT Filesystems

2.3.2. Database tables, views, indexes, trigger lists

The list of tables, views, indexes and triggers in sqlite3 external.db is as follows:

I use SQLite database software to connect directly to DB files for viewing, similar sqlite, mysql viewing tools are very many, such as navicat can also directly generate ER diagrams. Also visio reverse engineering and mysql workbench can generate ER diagrams.

2.3.3. Brief introduction of data tables

The following is analyzed with external.db.

Table:files is a table that stores all file/folder information, while other tables store their own targeted information. For example, screenshot image files are stored in both the files table and the images view. The association between various tables is basically managed by TRIGGER. To view the table structure, please make sure that For direct viewing with. schema, these tables are somewhat complex and are created in the. packages/providers/MediaProvider/src/com/android/providers/media/MediaProvider.java

Picture database

images: picture information

Thumbnails: thumbnails

Video database

video: video Information

Video thumbnails: Video thumbnails

Audio database

album_art: album cover

albums: album information

android_metadata: Current character encoding

artists: artists

audio_genres: genres

audio_genres_map: Audio genre mapping

audio_meta: Audio Information

audio_playlists: Playlists

audio_playlists_map: Audio Playlist Mapping

The fields in each table can be viewed with the following commands:

Method 1: select * from sqlite_master where type= "table"

Method 2: schema

3. Introduction of important documents in MediaProvider

3.1. MediaProvider.java for direct database management

File path:

MediaProvider/src/com/android/providers/media/MediaProvider.java

MediaProvider.java is to create a database and provide URIs to the outside world to realize the functions of adding, deleting, checking and URI management of the database. Deal directly with databases. If you want to modify the fields in the database, you can modify them in this file. For example, when it is popular to record city information when taking photos, the following modifications can be made:

db.execSQL("CREATE VIEW images AS SELECT _id,_data,_size,_display_name,mime_type,title,"
- + "date_added,date_modified,description,picasa_id,isprivate,latitude,longitude,"
+ + "date_added,date_modified,description,picasa_id,isprivate,city,latitude,longitude,"
+ "datetaken,orientation,mini_thumb_magic,bucket_id,bucket_display_name,width,"
+ "height FROM files WHERE media_type=1");
...
private static final String IMAGE_COLUMNS =
"_data,_size,_display_name,mime_type,title,date_added," +
- "date_modified,description,picasa_id,isprivate,latitude,longitude," +
+ "date_modified,description,picasa_id,isprivate,city,latitude,longitude," +
"datetaken,orientation,mini_thumb_magic,bucket_id,bucket_display_name," +
"width,height";

private static final String IMAGE_COLUMNSv407 =
"_data,_size,_display_name,mime_type,title,date_added," +
- "date_modified,description,picasa_id,isprivate,latitude,longitude," +
+ "date_modified,description,picasa_id,isprivate,city,latitude,longitude," +
"datetaken,orientation,mini_thumb_magic,bucket_id,bucket_display_name";

Another very important function of Media Provider is to provide data to other applications through URI s. Then the url provided by MediaProvider is as follows:

URI_MATCHER.addURI("media", "*/images/media", IMAGES_MEDIA);   // * Can be replaced by external or internal, the same as below
URI_MATCHER.addURI("media", "*/images/media/#", IMAGES_MEDIA_ID);
URI_MATCHER.addURI("media", "*/images/thumbnails", IMAGES_THUMBNAILS);
URI_MATCHER.addURI("media", "*/images/thumbnails/#", IMAGES_THUMBNAILS_ID);
URI_MATCHER.addURI("media", "*/audio/media", AUDIO_MEDIA);
URI_MATCHER.addURI("media", "*/audio/media/#", AUDIO_MEDIA_ID);
URI_MATCHER.addURI("media", "*/audio/media/#/genres", AUDIO_MEDIA_ID_GENRES);
URI_MATCHER.addURI("media", "*/audio/media/#/genres/#", AUDIO_MEDIA_ID_GENRES_ID);
URI_MATCHER.addURI("media", "*/audio/media/#/playlists", AUDIO_MEDIA_ID_PLAYLISTS);
URI_MATCHER.addURI("media", "*/audio/media/#/playlists/#", AUDIO_MEDIA_ID_PLAYLISTS_ID);
URI_MATCHER.addURI("media", "*/audio/genres", AUDIO_GENRES);
URI_MATCHER.addURI("media", "*/audio/genres/#", AUDIO_GENRES_ID);
URI_MATCHER.addURI("media", "*/audio/genres/#/members", AUDIO_GENRES_ID_MEMBERS);
URI_MATCHER.addURI("media", "*/audio/genres/all/members", AUDIO_GENRES_ALL_MEMBERS);
URI_MATCHER.addURI("media", "*/audio/playlists", AUDIO_PLAYLISTS);
URI_MATCHER.addURI("media", "*/audio/playlists/#", AUDIO_PLAYLISTS_ID);
URI_MATCHER.addURI("media", "*/audio/playlists/#/members", AUDIO_PLAYLISTS_ID_MEMBERS);
URI_MATCHER.addURI("media", "*/audio/playlists/#/members/#", AUDIO_PLAYLISTS_ID_MEMBERS_ID);
URI_MATCHER.addURI("media", "*/audio/artists", AUDIO_ARTISTS);
URI_MATCHER.addURI("media", "*/audio/artists/#", AUDIO_ARTISTS_ID);
URI_MATCHER.addURI("media", "*/audio/artists/#/albums", AUDIO_ARTISTS_ID_ALBUMS);
URI_MATCHER.addURI("media", "*/audio/albums", AUDIO_ALBUMS);
URI_MATCHER.addURI("media", "*/audio/albums/#", AUDIO_ALBUMS_ID);
URI_MATCHER.addURI("media", "*/audio/albumart", AUDIO_ALBUMART);
URI_MATCHER.addURI("media", "*/audio/albumart/#", AUDIO_ALBUMART_ID);
URI_MATCHER.addURI("media", "*/audio/media/#/albumart", AUDIO_ALBUMART_FILE_ID);
URI_MATCHER.addURI("media", "*/video/media", VIDEO_MEDIA);
URI_MATCHER.addURI("media", "*/video/media/#", VIDEO_MEDIA_ID);
URI_MATCHER.addURI("media", "*/video/thumbnails", VIDEO_THUMBNAILS);
URI_MATCHER.addURI("media", "*/video/thumbnails/#", VIDEO_THUMBNAILS_ID);
URI_MATCHER.addURI("media", "*/media_scanner", MEDIA_SCANNER);
URI_MATCHER.addURI("media", "*/fs_id", FS_ID);
URI_MATCHER.addURI("media", "*/version", VERSION);
URI_MATCHER.addURI("media", "*/mtp_connected", MTP_CONNECTED);
URI_MATCHER.addURI("media", "*", VOLUMES_ID);
URI_MATCHER.addURI("media", null, VOLUMES);
// Used by MTP implementation
URI_MATCHER.addURI("media", "*/file", FILES);
URI_MATCHER.addURI("media", "*/file/#", FILES_ID);
URI_MATCHER.addURI("media", "*/object", MTP_OBJECTS);
URI_MATCHER.addURI("media", "*/object/#", MTP_OBJECTS_ID);
URI_MATCHER.addURI("media", "*/object/#/references", MTP_OBJECT_REFERENCES);
// Used only to trigger special logic for directories
URI_MATCHER.addURI("media", "*/dir", FILES_DIRECTORY);
/**
* @deprecated use the 'basic' or 'fancy' search Uris instead
*/
URI_MATCHER.addURI("media", "*/audio/" + SearchManager.SUGGEST_URI_PATH_QUERY,
AUDIO_SEARCH_LEGACY);
URI_MATCHER.addURI("media", "*/audio/" + SearchManager.SUGGEST_URI_PATH_QUERY + "/*",
AUDIO_SEARCH_LEGACY);
// used for search suggestions
URI_MATCHER.addURI("media", "*/audio/search/" + SearchManager.SUGGEST_URI_PATH_QUERY,
AUDIO_SEARCH_BASIC);
URI_MATCHER.addURI("media", "*/audio/search/" + SearchManager.SUGGEST_URI_PATH_QUERY +
"/*", AUDIO_SEARCH_BASIC);

// used by the music app's search activity
URI_MATCHER.addURI("media", "*/audio/search/fancy", AUDIO_SEARCH_FANCY);
URI_MATCHER.addURI("media", "*/audio/search/fancy/*", AUDIO_SEARCH_FANCY);

In the command line, you can query data directly through content query, such as commonly used:

adb shell content query --uri content://media/external/file//query list of file details scanned by external memory

adb shell content query --uri content://media/external/dir//refer to the previous article

Adb shell content query -- URI content://media/internal/audio/media/-- where "_id=7"// queries audio files in internal memory with ID of 7

adb shell content query --uri content://media/external/images/media/query how many image stores are on external memory

Adb shell content query -- URI content://media/external/images/media/--user 0//query only the list of pictures of user's external memory with user id 0

Tips: adb shell content query is very helpful for analyzing why some applications can't display pictures, audio files, doc files, etc.

For example, in some cases, APP cannot display a list of DOCX/PPTX/XLSX files. In fact, when mediaprovider scans this type of file, it does not identify it as the specified format, but it identifies its mime_type as null. So when APP reads a file of a specified type, it will not be listed.

Important member variables and functions:

private void ensureDefaultFolders(DatabaseHelper helper, SQLiteDatabase db) {
public static int getDatabaseVersion(Context context) {
private static void createLatestSchema(SQLiteDatabase db, boolean internal) {
private static void updateDatabase(Context context, SQLiteDatabase db, boolean internal,
private boolean queryThumbnail(SQLiteQueryBuilder qb, Uri uri, String table,String column, boolean hasThumbnailId) {
public Cursor query(Uri uri, String[] projectionIn, String selection,String[] selectionArgs, String sort) {
public String getType(Uri url)
public Uri insert(Uri uri, ContentValues initialValues) {
private long insertDirectory(DatabaseHelper helper, SQLiteDatabase db, String path) {
private long insertFile(DatabaseHelper helper, Uri uri, ContentValues initialValues, int mediaType,boolean notify, ArrayList<Long> notifyRowIds) {
public int delete(Uri uri, String userWhere, String[] whereArgs) {
public int update(Uri uri, ContentValues initialValues, String userWhere,String[] whereArgs) {
private DatabaseHelper getDatabaseForUri(Uri uri) {
private Uri attachVolume(String volume) {
private static String getVolumeName(Uri uri) {

3.2. Entry to scan files MediaScanner Receiver. Java

File path:

packages/providers/MediaProvider/src/com/android/providers/media/MediaScannerService.java

MediaScanner Receiver. Java mainly listens to four broadcasts:

ACTION_BOOT_COMPLETED// Boot to Complete Broadcasting

ACTION_LOCALE_CHANGED//Area Change Broadcasting

ACTION_MEDIA_MOUNTED//Disk mount completes broadcast

ACTION_MEDIA_SCANNER_SCAN_FILE// / Specifies the broadcasting of a file to be scanned

MediaScanner Receiver receives the broadcast, collects and collates the data, and then starts the MediaScanner Service.

The key codes and related explanations are as follows:

public class MediaScannerReceiver extends BroadcastReceiver {
    private final static String TAG = "MediaScannerReceiver";

    @Override
    public void onReceive(Context context, Intent intent) {
        final String action = intent.getAction();
        final Uri uri = intent.getData();
        if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
            // Scan internal only.
            scan(context, MediaProvider.INTERNAL_VOLUME);  // Scan the internal storage area when booting
        } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
            scanTranslatable(context);      // When the area changes, the data is updated by region and so on.
        } else {
            if (uri.getScheme().equals("file")) {
                Log.d(TAG, "action: " + action + " path: " + path);
                if (Intent.ACTION_MEDIA_MOUNTED.equals(action)) {
                    // scan whenever any volume is mounted
                    scan(context, MediaProvider.EXTERNAL_VOLUME);   //Scanning external partitions is actually SD cards (internal SD cards and external SD cards)
                } else if (Intent.ACTION_MEDIA_SCANNER_SCAN_FILE.equals(action) &&
                        path != null && path.startsWith(externalStoragePath + "/")) {
                    scanFile(context, path);  //Scanning a Media File on External Memory
                }
            }
        }
}
    private void scan(Context context, String volume) {
        Bundle args = new Bundle();
        args.putString("volume", volume);
        context.startService(
                new Intent(context, MediaScannerService.class).putExtras(args));  //Start the MediaScanner Service for the actual action, as is the case with scanFilescanTranslatable.
    }

    private void scanFile(Context context, String path) {
    }

    private void scanTranslatable(Context context) {
    }

3.3. The core class of scanned files, MediaScannerService.java

MediaScanner Service is a service that provides two ways of calling. All operations related to scanning files call this file function. For example, MediaScanner Receiver randomly starts the MediaScanner Service for scanning after receiving a broadcast requiring scanning. This situation is mainly aimed at full scan, such as boot-up, after mounting SD/U disk.

Another way is to bind the service directly to scan a file, which needs to be passed

File MediaScannerConnection.java to cooperate. MediaScanner Connection. Java mainly implements bind MediaScanner Service, while other applications can scan directly through the following ways.

packages/apps/Gallery2/src/com/android/gallery3d/ingest/IngestService.java

mScannerConnection = new MediaScannerConnection(context, this);

mScannerConnection.scanFile(path, null);

packages/apps/Bluetooth/src/com/android/bluetooth/opp/BluetoothOppService.java

mConnection = new MediaScannerConnection(mContext, this);

mConnection.scanFile(mInfo.mFilename, mInfo.mMimetype);

Responsible for scanning media files, and then insert the scanned information into the media database.

The key core code is as follows:

public class MediaScannerService extends Service implements Runnable {
private static final String TAG = "MediaScannerService";
...
    public void onCreate() {
        PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
...
        Thread thr = new Thread(null, this, "MediaScannerService");  //MediaScannerService itself is a runnable, where a thread is created to implement the functions of MediaScannerService.
        thr.start();
}
    public void run() {
...
        mServiceLooper = Looper.myLooper();
        mServiceHandler = new ServiceHandler();

        Looper.loop();
}
    private final class ServiceHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            Bundle arguments = (Bundle) msg.obj;
....
                    String volume = arguments.getString("volume");
                    String[] directories = null;

                    if (MediaProvider.INTERNAL_VOLUME.equals(volume)) {
                        // scan internal media storage
                        directories = new String[] {
                                Environment.getRootDirectory() + "/media",
                                Environment.getOemDirectory() + "/media",
                                Environment.getProductDirectory() + "/media",
                        }; //directories for building internal memory 
                    }
                    else if (MediaProvider.EXTERNAL_VOLUME.equals(volume)) {
                        // scan external storage volumes
                        if (getSystemService(UserManager.class).isDemoUser()) {
                            directories = ArrayUtils.appendElement(String.class,
                                    mExternalStoragePaths,
                                    Environment.getDataPreloadsMediaDirectory().getAbsolutePath());
                        } else {
                            directories = mExternalStoragePaths; //If an external sd card is inserted here, the directories value is:
                        }
                    }

                    if (directories != null) {
                        if (false) Log.d(TAG, "start scanning volume " + volume + ": "
                                + Arrays.toString(directories));
                        scan(directories, volume);   //Scan the specified path.
                        if (false) Log.d(TAG, "done scanning volume " + volume);
                    }
                }
...
    private void scan(String[] directories, String volumeName) {
...
            sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_STARTED, uri));  //Send a broadcast starting with the scan.

            try {
                if (volumeName.equals(MediaProvider.EXTERNAL_VOLUME)) {
                    openDatabase(volumeName);
                }

                try (MediaScanner scanner = new MediaScanner(this, volumeName)) {
                    scanner.scanDirectories(directories);  //Call MediaScanner to scan.
                }
        } finally {
            sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_FINISHED, uri));   //Send the broadcasts that have been scanned.
            mWakeLock.release();
        }
    }

3.4.MTP transaction processing entry MtpReceiver.java

File path:
packages/providers/MediaProvider/src/com/android/providers/media/MtpReceiver.java
This broadcast receiver can monitor the state of USB as long as it monitors the broadcast ACTION_USB_STATE. ACTION_USB_STATE was dynamically registered again immediately after listening to the boot broadcast. To achieve the change of USB state. After receiving the broadcast, MtpReceiver collects and collates the data and then starts the MtpService.

The key codes and related explanations are as follows:

public class MtpReceiver extends BroadcastReceiver {
    private static final String TAG = MtpReceiver.class.getSimpleName();
    private static final boolean DEBUG = false;

    @Override
    public void onReceive(Context context, Intent intent) {
        final String action = intent.getAction();
        if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
            final Intent usbState = context.registerReceiver(
                    null, new IntentFilter(UsbManager.ACTION_USB_STATE));  //Dynamic Registration of ACTION_USB_STATE Broadcasting
            if (usbState != null) {
                handleUsbState(context, usbState);
            }
        } else if (UsbManager.ACTION_USB_STATE.equals(action)) {
            handleUsbState(context, intent);
        }
    }
    private void handleUsbState(Context context, Intent intent) {
        Bundle extras = intent.getExtras();
        boolean configured = extras.getBoolean(UsbManager.USB_CONFIGURED);
        boolean connected = extras.getBoolean(UsbManager.USB_CONNECTED);
        boolean mtpEnabled = extras.getBoolean(UsbManager.USB_FUNCTION_MTP);//Whether mtp is enabled
        boolean ptpEnabled = extras.getBoolean(UsbManager.USB_FUNCTION_PTP);//Whether to enable ptp
        boolean unlocked = extras.getBoolean(UsbManager.USB_DATA_UNLOCKED);//Whether to unlock data
        boolean isCurrentUser = UserHandle.myUserId() == ActivityManager.getCurrentUser();
//Check whether the current mobile phone is MTP enabled, adb, ptp, etc. can be viewed through the system properties (persist.sys.usb.config). For example, some models are equipped with getprop persist.sys.usb.config: adb, which means that mtp, PTP are not opened.
        if (configured && (mtpEnabled || ptpEnabled)) {
            if (!isCurrentUser)
                return;
            context.getContentResolver().insert(Uri.parse(
"content://media/none/mtp_connected"), null);
            intent = new Intent(context, MtpService.class);
            intent.putExtra(UsbManager.USB_DATA_UNLOCKED, unlocked);  //If unlocked is false, the device can be seen on the computer, but the specific content can not be seen. If true, the specific content can be seen in the mobile phone.
            if (ptpEnabled) {
                intent.putExtra(UsbManager.USB_FUNCTION_PTP, true);
            }
            if (DEBUG) { Log.d(TAG, "handleUsbState startService"); }
            context.startService(intent);   //After the parameter organization is completed, start the MtpService.
        } else if (!connected || !(mtpEnabled || ptpEnabled)) {
            // Only unbind if disconnected or disabled.
            boolean status = context.stopService(new Intent(context, MtpService.class));
            if (DEBUG) { Log.d(TAG, "handleUsbState stopService status=" + status); }
            // tell MediaProvider MTP is disconnected so it can unbind from the service
            context.getContentResolver().delete(Uri.parse(
"content://media/none/mtp_connected"), null, null);
        }
    }

As you can see from the function handleUsbState (), it is actually listening for usb state broadcasting to organize parameters for starting MtpService.

3.5. Key Class of Communication with PC MTPService.java

3.5.1. MTP-related codes

packages/providers/MediaProvider/src/com/android/providers/media/

MediaProvider.java

MediaScannerReceiver.java

MediaScannerService.java

MediaUpgradeReceiver.java

MtpReceiver.java

MtpService.java

frameworks/base/media/java/android/media/

MediaScanner.java

./frameworks/base/media/java/android/mtp/

MtpDatabase.java

MtpServer.java

MtpStorage.java

MtpStorageInfo.java

frameworks/base/media/jni/

android_media_MediaScanner.cpp

android_mtp_MtpDatabase.cpp

android_mtp_MtpServer.cpp

frameworks/av/media/mtp/

MtpServer.h

MtpServer.cpp

3.5.2. MTP Protocol

After the connection between mobile phone and computer, data can be transmitted through MTP mode, which is due to the support of MTP protocol between Android and computer. The MTP protocol is briefly introduced as follows (copied from the network):

Media Transfer Protocol (MTP) is a self-defined extension protocol based on Picture Transfer Protocol (PTP). This protocol allows users to access media files linearly on mobile devices. PTP is only designed to download photos from digital cameras, while MTP can support music files on digital audio players and media files on portable media players, as well as personal information transmission of personal digital assistants. MTP is a key part of WMDRM10-PD, and WMDRM10-PD is a digital rights management (DRM) service of Windows Media.

Media Transfer Protocol (MTP) is a part of the "Windows Media" framework, which is closely related to Windows Media Player. Windows systems have supported MTP since Windows XPSP2. Windows XP needs to install Windows Media Player 10 or higher to get MTP support. Subsequent systems support MTP natively. Microsoft also provides MTP driver packages to older operating systems after Windows 98. OSX and Linux each have upgrade packages that support MTP.

The USB Developer Forum standardized MTP as a mature USB class in May 2008. Since then MTP has become PTP officer.

Square extension, both have the same class code.

3.5.3. Introduction to Android MTP

As you can see from the previous section, MTPReceiver receives ACTION_USB_STATE when the phone is connected to USB and starts MTPService after checking the parameters.

Then the key code of MTPService startup is as follows:

packages/providers/MediaProvider/src/com/android/providers/media/MtpService.java
    @Override
    public void onCreate() {
        mVolumes = StorageManager.getVolumeList(getUserId(), 0);
        mVolumeMap = new HashMap<>();//map prepares for subsequent storage of Mount partitions.

        mStorageManager = this.getSystemService(StorageManager.class);
        mStorageManager.registerListener(mStorageEventListener); //Registered storage monitors can be fed back to the PC through MTP protocol after any changes in the storage of mobile phones.
    }


    private synchronized void startServer(StorageVolume primary, String[] subdirs) {
            final MtpDatabase database = new MtpDatabase(this, MediaProvider.EXTERNAL_VOLUME, subdirs);   //Create MTPDataBase
...
            final MtpServer server =
                    new MtpServer(database, fd, mPtpMode,
                            new OnServerTerminated(), Build.MANUFACTURER,
                            Build.MODEL, "1.0", deviceSerialNumber);
            database.setServer(server);
            sServerHolder = new ServerHolder(server, database);
            // Add currently mounted and enabled storages to the server
            if (mUnlocked) {  //Only in the case of data unlock will the storage volume in the mobile phone be mounted on the MTP protocol.
                if (mPtpMode) {
                    addStorage(primary);
                } else {
                    for (StorageVolume v : mVolumeMap.values()) {
                        addStorage(v);
                    }
                }
            }
            server.start();    // MTPServer startup
        }
}
    private void addStorage(StorageVolume volume) {
        Log.v(TAG, "Adding MTP storage:" + volume.getPath());
.....
         sServerHolder.database.addStorage(volume);  //Call MtpDatabase to add storage volumes to MTP protocol
    }
    public synchronized int onStartCommand(Intent intent, int flags, int startId) {
        mUnlocked = intent.getBooleanExtra(UsbManager.USB_DATA_UNLOCKED, false); //Read intent parameters, which is related to whether the mtp mode can be successfully opened.
        mPtpMode = intent.getBooleanExtra(UsbManager.USB_FUNCTION_PTP, false);

        for (StorageVolume v : mVolumes) {
            if (v.getState().equals(Environment.MEDIA_MOUNTED)) {
                mVolumeMap.put(v.getPath(), v); //Read all disk volumes
            }
        }
.....
      final StorageVolume primary = StorageManager.getPrimaryVolume(mVolumes);
        startServer(primary, subdirs);  //Start the key service in MTP mode: MTP Server.
        return START_REDELIVER_INTENT;
    }

The flow chart of adding storage volume at MTP startup level is as follows:

frameworks/base/media/java/android/mtp/MtpStorageManager.java listens, code as follows:

45  public class MtpStorageManager {
55      private class MtpObjectObserver extends FileObserver {
65          public void onEvent(int event, String path) {
66              synchronized (MtpStorageManager.this) {
71                  MtpObject obj = mObject.getChild(path);
72                  if ((event & MOVED_TO) != 0 || (event & CREATE) != 0) {
75                      handleAddedObject(mObject, path, (event & IN_ISDIR) != 0);//Adding File Operations
76                  } else if ((event & MOVED_FROM) != 0 || (event & DELETE) != 0) {
83                      handleRemovedObject(obj);  //Delete File Operation
84                  } else if ((event & IN_IGNORED) != 0) {
85                      if (sDebug)
86                          Log.i(TAG, "inotify for " + mObject.getPath() + " deleted");
87                      if (mObject.mObserver != null)
88                          mObject.mObserver.stopWatching();
89                      mObject.mObserver = null;
90                  } 

The file saved in MTP mode calls mMediaProvider insert data immediately after the file has been saved to realize the record sharing between MTP and MediaProvider.

frameworks/base/media/java/android/mtp/MtpDatabase.java
373      private int beginSendObject(String path, int format, int parent, int storageId) {
....
382      }
384      private void endSendObject(int handle, boolean succeeded) {
385          MtpStorageManager.MtpObject obj = mManager.getObject(handle);
390          // Add the new file to MediaProvider
391          if (succeeded) {
413                  Uri uri = mMediaProvider.insert(mObjectsUri, values);//After receiving the command for the end of file transfer, the insert command is called directly to save the relevant information to the Media Provider.

3.6. Database upgrade class MediaUpgrade Receiver

File path:

packages/providers/MediaProvider/src/com/android/providers/media/MediaUpgradeReceiver.java

In the system startup phase, after the core module is started, and before the startup is completed, the version of apk will be read, which is actually Android: version Code. If the two versions are the same, no operation will be performed, and if they are different from previous versions, data upgrade operations will be performed. For the OSV upgrade process, when the field of the database needs to be upgraded, it can be operated here to achieve the perfect inheritance of data. MediaUpgrade Receiver will be activated at boot time to upgrade and update the database.

4. Common MediaProvider Problems

4.1. How to store screenshot files in MediaProvider database

MediaProvider provides the MediaScannerService class to scan files, but there is another more widely used way to store image information directly in the MediaProvider database with ContentProvider. Screen capture is achieved in this way, the relevant code is as follows:

frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
1806      private class ScreenshotRunnable implements Runnable {   //Screenshot Runnable is an asynchronous execution class, which is relatively simple in what circumstances and how to invoke it. It is not described here that it directly enters the link of storing data.
1807          private int mScreenshotType = TAKE_SCREENSHOT_FULLSCREEN;
1808  
1809          public void setScreenshotType(int screenshotType) {
1810              mScreenshotType = screenshotType;
1811          }
1812  
1813          @Override
1814          public void run() {
1815              mScreenshotHelper.takeScreenshot(mScreenshotType,
1816                      mStatusBar != null && mStatusBar.isVisibleLw(),
1817                      mNavigationBar != null && mNavigationBar.isVisibleLw(), mHandler);
1818          }
PhoneWindowManager Called after receiving a screenshot event mScreenshotHelper Prepare for screenshots.

frameworks/base/core/java/com/android/internal/util/ScreenshotHelper.java
19      private static final String SYSUI_PACKAGE = "com.android.systemui";
20      private static final String SYSUI_SCREENSHOT_SERVICE =
21              "com.android.systemui.screenshot.TakeScreenshotService";
...
52              final ComponentName serviceComponent = new ComponentName(SYSUI_PACKAGE,
53                      SYSUI_SCREENSHOT_SERVICE);
54              final Intent serviceIntent = new Intent();
55  //The binding service TakeScreenshot Service implements screenshots.
56              final Runnable mScreenshotTimeout = new Runnable() {
57                  @Override public void run() {
58                      synchronized (mScreenshotLock) {
59                          if (mScreenshotConnection != null) {
60                              mContext.unbindService(mScreenshotConnection);
61                              mScreenshotConnection = null;
62                              notifyScreenshotError();
63                          }
64                      }
65                  }
66              };

frameworks/base/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
59              if (mScreenshot == null) {
60                  mScreenshot = new GlobalScreenshot(TakeScreenshotService.this);
61              }
62  
63              switch (msg.what) {
64                  case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
65                      mScreenshot.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0);  //TakeScreenshot Service receives a screenshot request and calls Global Screenshot for screenshot

frameworks/base/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
545      private void saveScreenshotInWorkerThread(Runnable finisher) {
546          SaveImageInBackgroundData data = new SaveImageInBackgroundData();
547          data.context = mContext;
548          data.image = mScreenBitmap;
549          data.iconSize = mNotificationIconSize;
550          data.finisher = finisher;
551          data.previewWidth = mPreviewWidth;
552          data.previewheight = mPreviewHeight;
553          if (mSaveInBgTask != null) {
554              mSaveInBgTask.cancel(false);
555          }
556          mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data, mNotificationManager)
557                  .execute();   //Start an asynchronous call to SaveImageInBackgroundTask to save the relevant data.
558      }
...
114  class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
115      private static final String TAG = "SaveImageInBackgroundTask";
...
234      protected Void doInBackground(Void... params) {
262              ContentValues values = new ContentValues();  //Start by organizing the parameters of the data that need to be saved
263              ContentResolver resolver = context.getContentResolver();
264              values.put(MediaStore.Images.ImageColumns.DATA, mImageFilePath);
265              values.put(MediaStore.Images.ImageColumns.TITLE, mImageFileName);
266              values.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, mImageFileName);
267              values.put(MediaStore.Images.ImageColumns.DATE_TAKEN, mImageTime);
268              values.put(MediaStore.Images.ImageColumns.DATE_ADDED, dateSeconds);
269              values.put(MediaStore.Images.ImageColumns.DATE_MODIFIED, dateSeconds);
270              values.put(MediaStore.Images.ImageColumns.MIME_TYPE, "image/png");
271              values.put(MediaStore.Images.ImageColumns.WIDTH, mImageWidth);
272              values.put(MediaStore.Images.ImageColumns.HEIGHT, mImageHeight);
273              values.put(MediaStore.Images.ImageColumns.SIZE, new File(mImageFilePath).length());
274              Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);    //Finally, the data is stored in the database.


5. Summary

The whole MediaProvider process is relatively simple, a little more complex is the MediaScan under the base library, the part calling native, and the MTP part. As can be seen from the previous flow chart, the native layer mainly realizes the traversal search of the path, while the java layer realizes the identification of imie type of the checked results and then inserts them into the database.

MTP is the most popular MTP protocol. When sending and deleting a file on a mobile phone through MTP, it will be called to Mtp Database through the protocol to update the database. The MTP protocol is still vague, and further analysis is needed if there is a need for this aspect.

Posted by inkfish on Sun, 04 Aug 2019 05:20:03 -0700