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.