魅族手机文件删除-通知栏警告流程分析(下)

魅族手机文件删除后通知栏警告流程分析上

先将魅族手机中的关键jar包pull出来贴这里

MediaProvider.apk
链接: https://pan.baidu.com/s/1HT4FETwg9KS_8ewctSNEiA 提取码: hwme

/system/framework/framework.jar
链接: https://pan.baidu.com/s/16RgA2XGlunzSxjjr8NpX7Q 提取码: meek

/system/framework/services.jar
链接: https://pan.baidu.com/s/12jytAxe2Ej6BShrQgZ0rAA 提取码: d8b9

将MediaProvider.smali代码逆向还原

只需要关注关键函数deleteInternal


    public final Pattern PATTERN_RELATIVE_PATH = Pattern.compile("(?i)^/storage/(?:emulated/[0-9]+/|[^/]+/)(Android/sandbox/([^/]+)/)?");;

    public static final Pattern PATTERN_OWNED_PATH = Pattern.compile(
            "(?i)^/storage/[^/]+/(?:[0-9]+/)?Android/(?:data|media|obb|sandbox)/([^/]+)(/?.*)?");


    //FileUtils.getContentUriForPath("/storage/emulated/0/libs/3.txt")
    //content://media/external_primary/file
    private int deleteInternal(@NonNull Uri uri, @Nullable Bundle extras)
            throws MediaProvider.FallbackException {
        extras = (extras != null) ? extras : new Bundle();

        // INCLUDED_DEFAULT_DIRECTORIES extra should only be set inside MediaProvider.
        extras.remove(INCLUDED_DEFAULT_DIRECTORIES);

        uri = safeUncanonicalize(uri);
        final boolean allowHidden = isCallingPackageAllowedHidden();
        final int match = matchUri(uri, allowHidden);

        switch(match) {
            case AUDIO_MEDIA_ID:
            case AUDIO_PLAYLISTS_ID:
            case VIDEO_MEDIA_ID:
            case IMAGES_MEDIA_ID:
            case DOWNLOADS_ID:
            case FILES_ID: {
                if (!isFuseThread() && getCachedCallingIdentityForFuse(Binder.getCallingUid()).
                        removeDeletedRowId(Long.parseLong(uri.getLastPathSegment()))) {
                    // Apps sometimes delete the file via filePath and then try to delete the db row
                    // using MediaProvider#delete. Since we would have already deleted the db row
                    // during the filePath operation, the latter will result in a security
                    // exception. Apps which don't expect an exception will break here. Since we
                    // have already deleted the db row, silently return zero as deleted count.
                    return 0;
                }
            }
            break;
            default:
                // For other match types, given uri will not correspond to a valid file.
                break;
        }

        final String userWhere = extras.getString(QUERY_ARG_SQL_SELECTION);
        final String[] userWhereArgs = extras.getStringArray(QUERY_ARG_SQL_SELECTION_ARGS);

        int count = 0;

        final String volumeName = getVolumeName(uri);

        //魅族手机-------begin--------
        if (!volumeName.equals("internal")) {
            StringBuilder builder = new StringBuilder();
            builder.append("delete: uri=");
            builder.append(volumeName);
            builder.append(", userWhere=");
            builder.append(userWhere);
            builder.append(", userWhereArgs:");
            builder.append(Arrays.toString(userWhereArgs));
            saveLogToFile(context, builder.toString());
        }
        //魅族手机-------end--------
        final int targetSdkVersion = getCallingPackageTargetSdkVersion();

        // handle MEDIA_SCANNER before calling getDatabaseForUri()
        if (match == MEDIA_SCANNER) {
            if (mMediaScannerVolume == null) {
                return 0;
            }

            final DatabaseHelper helper = getDatabaseForUri(
                    MediaStore.Files.getContentUri(mMediaScannerVolume));

            helper.mScanStopTime = SystemClock.elapsedRealtime();

            mMediaScannerVolume = null;
            return 1;
        }

        if (match == VOLUMES_ID) {
            detachVolume(uri);
            count = 1;
        }

        switch (match) {
            case AUDIO_PLAYLISTS_ID_MEMBERS_ID:
                extras.putString(QUERY_ARG_SQL_SELECTION,
                        BaseColumns._ID + "=" + uri.getPathSegments().get(5));
                // fall-through
            case AUDIO_PLAYLISTS_ID_MEMBERS: {
                final long playlistId = Long.parseLong(uri.getPathSegments().get(3));
                final Uri playlistUri = ContentUris.withAppendedId(
                        MediaStore.Audio.Playlists.getContentUri(volumeName), playlistId);

                // Playlist contents are always persisted directly into playlist
                // files on disk to ensure that we can reliably migrate between
                // devices and recover from database corruption
                return removePlaylistMembers(playlistUri, extras);
            }
        }

        //魅族手机-----begin---------
        ArrayList delArrays = new ArrayList();
        boolean needDelProtection = isNeedDeletionProtection();
        //一般情况三方app均返回true
        //魅族手机-------end----------


        final DatabaseHelper helper = getDatabaseForUri(uri);
        final com.android.providers.media.util.SQLiteQueryBuilder qb = getQueryBuilder(TYPE_DELETE, match, uri, extras, null);

        {
            // Give callers interacting with a specific media item a chance to
            // escalate access if they don't already have it
            switch (match) {
                case AUDIO_MEDIA_ID:
                case VIDEO_MEDIA_ID:
                case IMAGES_MEDIA_ID:
                    enforceCallingPermission(uri, extras, true);
            }

            /**
             *
             *  5676     const-string v16, "media_type"
             *  5677
             *  5678     const-string v17, "_data"
             *  5679
             *  5680     const-string v18, "_id"
             *  5681
             *  5682     const-string v19, "is_download"
             *  5683
             *  5684     const-string v20, "mime_type"
             *  5685
             *  5686     const-string v21, "is_trashed"
             *  5687
             *  5688     const-string v22, "_size"
             */

            final String[] projection = new String[] {
                    MediaStore.Files.FileColumns.MEDIA_TYPE,
                    MediaStore.Files.FileColumns.DATA,
                    MediaStore.Files.FileColumns._ID,
                    MediaStore.Files.FileColumns.IS_DOWNLOAD,
                    MediaStore.Files.FileColumns.MIME_TYPE,
                    "is_trashed",/*魅族手机*/
                    "_size",/*魅族手机*/
            };
            final boolean isFilesTable = qb.getTables().equals("files");
            final LongSparseArray<String> deletedDownloadIds = new LongSparseArray<>();
            if (isFilesTable) {
                String deleteparam = uri.getQueryParameter(MediaStore.PARAM_DELETE_DATA);
                if (deleteparam == null || ! deleteparam.equals("false")) {
                    Cursor c = qb.query(helper, projection, userWhere, userWhereArgs,
                            null, null, null, null, null);
                    try {
                        while (c.moveToNext()) {
                            final int mediaType = c.getInt(0);
                            final String data = c.getString(1);
                            final long id = c.getLong(2);
                            final int isDownload = c.getInt(3);
                            final String mimeType = c.getString(4);

                            //魅族手机--------begin-------------
                            final int isTrashed = c.getInt(5);
                            final long size = c.getInt(6);
                            boolean checkWriteRead = checkPermissonForMediaStore(data, false);

                            //一般应用申请了sdcard读写权限,legacy模式,checkWriteRead就会为true,代表data路径可读写
                            if (!checkWriteRead) {
                                StringBuilder builder = new StringBuilder();
                                builder.append("is not allowed to delete :");
                                builder.append(data);
                                Log.w(TAG, builder.toString());
                                continue;
                            }
                            if (needDelProtection && isTrashed == 1) {
                                int cc = count + 1;
                                StringBuilder builder = new StringBuilder();
                                builder.append("@_@ deleteInternal file has trashed already count = ");
                                builder.append(cc);
                                Log.d(TAG, builder.toString());
                            }

                            final String callingPkg = getCallingPackageOrSelf();
                            File ff = new File(data);
                            boolean isDir = ff.isDirectory();
                            final String topDir = getTopLevelDir(data, isDir);
                            boolean isSelfDir = isAppSelfFile(callingPkg, topDir, data);
                            if (!isSelfDir) {
                                int i = handleTrashOneItem(data, id, mediaType, mimeType, size, delArrays);
                                count += i;
                            }
                            //魅族手机--------end--------------

                            // Forget that caller is owner of this item
                            mCallingIdentity.get().setOwned(id, false);

                            deleteIfAllowed(uri, extras, data);
                            count += qb.delete(helper, BaseColumns._ID + "=" + id, null);

                            // Only need to inform DownloadProvider about the downloads deleted on
                            // external volume.
                            if (isDownload == 1) {
                                deletedDownloadIds.put(id, mimeType);
                            }

                            // Update any playlists that reference this item
                            if ((mediaType == MediaStore.Files.FileColumns.MEDIA_TYPE_AUDIO)
                                    && helper.isExternal()) {
                                helper.runWithTransaction((db) -> {
                                    try (Cursor cc = db.query("audio_playlists_map",
                                            new String[] { "playlist_id" }, "audio_id=" + id,
                                            null, "playlist_id", null, null)) {
                                        while (cc.moveToNext()) {
                                            final Uri playlistUri = ContentUris.withAppendedId(
                                                    MediaStore.Audio.Playlists.getContentUri(volumeName),
                                                    cc.getLong(0));
                                            resolvePlaylistMembers(playlistUri);
                                        }
                                    }
                                    return null;
                                });
                            }
                        }
                    } finally {
                        com.android.providers.media.util.FileUtils.closeQuietly(c);
                    }
                    // Do not allow deletion if the file/object is referenced as parent
                    // by some other entries. It could cause database corruption.
                    appendWhereStandalone(qb, ID_NOT_PARENT_CLAUSE);
                }
            }

            switch (match) {
                case AUDIO_GENRES_ID_MEMBERS:
                    throw new MediaProvider.FallbackException("Genres are read-only", Build.VERSION_CODES.R);

                case IMAGES_THUMBNAILS_ID:
                case IMAGES_THUMBNAILS:
                case VIDEO_THUMBNAILS_ID:
                case VIDEO_THUMBNAILS:
                    // Delete the referenced files first.
                    Cursor c = qb.query(helper, sDataOnlyColumn, userWhere, userWhereArgs, null,
                            null, null, null, null);
                    if (c != null) {
                        try {
                            while (c.moveToNext()) {
                                deleteIfAllowed(uri, extras, c.getString(0));
                            }
                        } finally {
                            com.android.providers.media.util.FileUtils.closeQuietly(c);
                        }
                    }
                    count += deleteRecursive(qb, helper, userWhere, userWhereArgs);
                    break;

                default:
                    count += deleteRecursive(qb, helper, userWhere, userWhereArgs);
                    break;
            }

            if (deletedDownloadIds.size() > 0) {
                // Do this on a background thread, since we don't want to make binder
                // calls as part of a FUSE call.
                helper.postBackground(() -> {
                    getContext().getSystemService(DownloadManager.class)
                            .onMediaStoreDownloadsDeleted(deletedDownloadIds);
                });
            }

            if (isFilesTable && !isCallingPackageSelf()) {
                com.android.providers.media.util.Metrics.logDeletion(volumeName, mCallingIdentity.get().uid,
                        getCallingPackageOrSelf(), count);

                //魅族手机----begin
                if (delArrays.size() > 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    final String callingPkg = getCallingPackageOrSelf();
                    ArrayList<ContentValues> pkgDelArrays = sMapDeleteList.get(callingPkg);
                    if (pkgDelArrays == null) {
                        ArrayList<ContentValues> clonedArr = (ArrayList<ContentValues>) delArrays.clone();
                        sMapDeleteList.put(callingPkg, clonedArr);
                    } else {
                        ArrayList<ContentValues> arrays = pkgDelArrays;
                        //删除重复数据,更新sMapDeleteList
                    }

                    Message msg = mHandler.obtainMessage();
                    msg.what = 10;
                    msg.obj = callingPkg;
                    mHandler.sendMessageDelayed(msg, 100);
                }
                //魅族手机----end
            }
        }

        return count;
    }


    private static int handleTrashOneItem(String path, long id, int mediaType, String mimeType, long size, ArrayList<ContentValues> paramArrayList) {
        Uri uri = ContentUris.withAppendedId(FileUtils.getContentUriForPath(path), id);
        ContentValues contentValues1 = new ContentValues();
        contentValues1.put("is_trashed", Integer.valueOf(1));
        ContentValues contentValues2 = new ContentValues();
        contentValues2.put("date_deleted", Long.valueOf(System.currentTimeMillis() / 1000L));
        int i = update(uri, contentValues1, null, null);
        if (i > 0) {
            contentValues2.put("_id", Long.valueOf(id));
            contentValues2.put("_data", path);
            contentValues2.put("_display_name", FileUtils.extractDisplayName(path));
            contentValues2.put("media_type", Integer.valueOf(mediaType));
            contentValues2.put("mime_type", mimeType);
            contentValues2.put("_size", Long.valueOf(size));
            paramArrayList.add(contentValues2);
        }
        return i;
    }

    private static boolean isAppSelfFile(String paramString1, String paramString2, String paramString3) {
        StringBuilder stringBuilder2 = new StringBuilder();
        stringBuilder2.append("@_@ isAppSelfFile pkg: ");
        stringBuilder2.append(paramString1);
        stringBuilder2.append(", topName: ");
        stringBuilder2.append(paramString2);
        stringBuilder2.append(", path: ");
        stringBuilder2.append(paramString3);
        Log.d("MediaProvider", stringBuilder2.toString());
        boolean bool = false;
        try {
            Class<?> clazz = Class.forName("meizu.security.FlymePermissionManager");
            if (checkPkgForExclusiveDirectoryMethod == null)
                checkPkgForExclusiveDirectoryMethod = clazz.getMethod("checkPkgForExclusiveDirectory", new Class[] { String.class, String.class, String.class });
            int i = ((Integer)checkPkgForExclusiveDirectoryMethod.invoke((Object)null, new Object[] { paramString1, paramString2, paramString3 })).intValue();
            if (i == 0)
                bool = true;
        } catch (Exception exception) {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append("MZSTORAGE-isAppSelfFile ");
            stringBuilder.append(exception.getMessage());
            Log.d("MediaProvider", stringBuilder.toString());
        }
        StringBuilder stringBuilder1 = new StringBuilder();
        stringBuilder1.append("@_@ isAppSelfFile appSelf = ");
        stringBuilder1.append(bool);
        Log.d("MediaProvider", stringBuilder1.toString());
        return bool;
    }
    public static @Nullable String extractPathOwnerPackageName(@Nullable String path) {
        if (path == null) return null;
        final Matcher m = PATTERN_OWNED_PATH.matcher(path);
        if (m.matches()) {
            return m.group(1);
        } else {
            return null;
        }
    }

    private boolean checkPermissonForPath(String filePath, int paramInt1, int paramInt2, boolean paramBoolean) {
        if (isCallingPackageManager() || FileUtils.extractPathOwnerPackageName(filePath) != null || paramInt1 < 10000 || isCtsRunning())
            return true;
        boolean bool = checkPermissonForTopDir(getTopLevelDir(filePath, paramBoolean), paramInt1, paramInt2, filePath);
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("check:");
        stringBuilder.append(bool);
        stringBuilder.append(", TopDir:");
        stringBuilder.append(getTopLevelDir(filePath, paramBoolean));
        stringBuilder.append(", uid:");
        stringBuilder.append(paramInt1);
        stringBuilder.append(", path");
        stringBuilder.append(filePath);
        stringBuilder.append(", pathIsDir:");
        stringBuilder.append(paramBoolean);
        Log.d("MediaProvider", stringBuilder.toString());
        return bool;
    }

    private String getTopLevelDir(String path, boolean isDir) {
        boolean bool;
        String str = null;
        if (path == null)
            return null;
        if (path.equals("/storage/emulated/0") || path.equals("/storage/emulated/999"))
            return "/";
        String[] arrayOfString = FileUtils.sanitizePath(FileUtils.extractRelativePath(path));
        if (isDir && arrayOfString.length == 1 && TextUtils.isEmpty(arrayOfString[0])) {
            bool = true;
        } else {
            bool = false;
        }
        if (bool)
            return FileUtils.extractDisplayName(path);
        path = str;
        if (arrayOfString.length >= 1)
            path = arrayOfString[0];
        return path;
    }


    private boolean checkPermissonForMediaStore(String path, boolean paramBoolean) {
        return checkPermissonForPath(path, Binder.getCallingUid(), Binder.getCallingPid(), paramBoolean);
    }

    private boolean isNeedDeletionProtection() {
        String str = getCallingPackageOrSelf();
        return (!isCtsRunning() && !"com.android.providers.media.module".equals(str)
                && !"com.android.providers.media".equals(str))
                ? (isSystemApp(str) ^ true) : false;
    }

    private static void saveLogToFile(Context paramContext, String paramString) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(paramString);
        int i = Binder.getCallingPid();
        String[] arrayOfString = getPackageNames(paramContext, i);//其实就是获取删除文件的app包名
        stringBuilder.append("\ncaller pid = ");
        stringBuilder.append(i);
        stringBuilder.append("\ncaller packageList:");
        stringBuilder.append(Arrays.toString((Object[])arrayOfString));
        stringBuilder.append('\n');
    }

    private static Map<String, ArrayList<ContentValues>> sMapDeleteList =
            new HashMap<String, ArrayList<ContentValues>>();

    private static Handler mHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(@NonNull Message message) {
            if (message != null) {
                StringBuilder stringBuilder = new StringBuilder();
                stringBuilder.append("handleMessage what = ");
                stringBuilder.append(message.what);
                Log.d("MediaProvider", stringBuilder.toString());
                int i = message.what;
                if (i != 0) {
                    if (i == 10) {
                        MediaProvider mediaProvider = MediaProvider.this;
                        mediaProvider.sendDeleteBroadcasts(mediaProvider.getContext(), (String)message.obj);
                    }
                }
//                else {
//                    try {
//                        if (MediaApplication.getInstance() != null) {
//                            HashMap<Object, Object> hashMap = new HashMap<Object, Object>();
//                            this();
//                            hashMap.put("packageName", "com.android.providers.media.module");
//                            hashMap.put("className", "MediaProvider");
//                            UsageStatsHelper.onEvent("media_provider_called", null, (Map)hashMap);
//                            Log.i("MediaProvider", "Register report scan ...");
//                        } else {
//                            Log.w("MediaProvider", "MediaApplication is null");
//                        }
//                    } catch (Exception exception) {
//                        exception.printStackTrace();
//                    }
//                }
            }
            super.handleMessage(message);
        }
    };
}

deleteInternal拆分讲解

private int deleteInternal(@NonNull Uri uri, @Nullable Bundle extras)
            throws MediaProvider.FallbackException {
   
        .........
        .........
        
        //魅族手机-----begin---------
        ArrayList delArrays = new ArrayList();
        boolean needDelProtection = isNeedDeletionProtection();
        //一般情况三方app均返回true
        //魅族手机-------end----------        
            
isNeedDeletionProtection函数对于第三方app一般返回true
delArrays非常关键,魅族手机会将删除的文件放到这个list中,后面会分析到
private int deleteInternal(@NonNull Uri uri, @Nullable Bundle extras)
            throws MediaProvider.FallbackException {
   
        .......
        .......
        
        ......
        final int match = matchUri(uri, allowHidden);
        if (match == MEDIA_SCANNER) {
   
            if (mMediaScannerVolume == null) {
   
                return 0;
            }</
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值