Android 廣播監聽USB插拔

  • AndroidManifest.xml
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />

<receiver android:name=".receiver.UsbReceiver">
    <intent-filter>
        <action android:name="android.intent.action.MEDIA_MOUNTED" />
        <action android:name="android.intent.action.MEDIA_UNMOUNTED" />

        <data android:scheme="file" />
    </intent-filter>
</receiver>
  • UsbReceiver.java
public class UsbReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        UsbManager.getInstance().usbObserveReceive(context, intent, Intent.ACTION_MEDIA_MOUNTED.equals(intent.getAction())
                ? Constants.USB_TYPE_ATTACH : Constants.USB_TYPE_DETACH);
    }
}
  • UsbInfo.java
public class UsbInfo {
    // uuid
    public String uuid;
    // uri
    public Uri uri;
    // u盤類型
    public String fsType;
    // u盤名稱
    public String fsLabel;
    // u盤路徑
    public String path;
    // u盤路徑
    public String internalPath;
    // 已格式化顯示的可用空間大小
    public String availableSize;
    // 已格式化顯示的總空間大小
    public String totalSize;

    @Override
    public String toString() {
        return "UsbInfo{" +
                "uuid='" + uuid + '\'' +
                ", uri=" + uri +
                ", fsType='" + fsType + '\'' +
                ", fsLabel='" + fsLabel + '\'' +
                ", path='" + path + '\'' +
                ", internalPath='" + internalPath + '\'' +
                ", availableSize='" + availableSize + '\'' +
                ", totalSize='" + totalSize + '\'' +
                '}';
    }
}
  • UsbObserveType .java
public class Constants {
	public static final int USB_TYPE_ATTACH = 0x3001;
	public static final int USB_TYPE_DETACH = 0x3002;
}



@IntDef(flag = true, value = {
        Constants.USB_TYPE_ATTACH, Constants.USB_TYPE_DETACH
})
public @interface UsbObserveType {
}
  • UsbManager.java
public class UsbManager {
    private static final String TAG = "UsbManager";
    private Set<UsbInfo> mUsbInfos = new LinkedHashSet<>();
    private List<OnUsbReceiveListener> mOnUsbReceiveListeners = new LinkedList<>();

    private static class UsbManagerHolder {
        private static final UsbManager INSTANCE = new UsbManager();
    }

    public static UsbManager getInstance() {
        return UsbManagerHolder.INSTANCE;
    }

    public Set<UsbInfo> getUsbInfos(Context context) {
        if (mUsbInfos.size() == 0)
            return queryUsbInfos(context);
        return mUsbInfos;
    }

    public void registerUsbReceiveListener(OnUsbReceiveListener listener) {
        mOnUsbReceiveListeners.add(listener);
    }

    public void unregisterUsbReceiveListener(OnUsbReceiveListener listener) {
        mOnUsbReceiveListeners.remove(listener);
    }

    public void usbObserveReceive(Context context, Intent intent, @UsbObserveType int usbObserveType) {
        Set<UsbInfo> tUsbInfos = queryUsbInfos(context);
        UsbInfo attachUsbInfo = new UsbInfo();
        if (usbObserveType == Constants.USB_TYPE_ATTACH) {
            for (UsbInfo usbInfo : tUsbInfos) {
                if (intent.getData().getPath().equals(usbInfo.path)) {
                    usbInfo.uri = intent.getData();
                    attachUsbInfo = usbInfo;
                    mUsbInfos.add(attachUsbInfo);
                    break;
                }
            }
        } else {
            for (UsbInfo usbInfo : mUsbInfos) {
                if (usbInfo.uri.equals(intent.getData())) {
                    attachUsbInfo = usbInfo;
                    mUsbInfos.remove(attachUsbInfo);
                    break;
                }
            }
        }
        Log.i(TAG, "intent = " + intent + ", usbInfos = " + mUsbInfos);
        for (OnUsbReceiveListener listener : mOnUsbReceiveListeners) {
            if (listener != null) {
                listener.onUsbReceive(usbObserveType, mUsbInfos, attachUsbInfo);
            }
        }
    }

    public Set<UsbInfo> queryUsbInfos(Context context) {
        Set<UsbInfo> usbInfos = new LinkedHashSet<>();
        try {
            StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);

            Class clazz = Class.forName("android.os.storage.StorageManager");
            Method methodGetVolumes = clazz.getMethod("getVolumes");
            List<?> volumeInfos = (List<?>) methodGetVolumes.invoke(sm);
            @SuppressLint("PrivateApi") Class volumeInfoClazz = Class.forName("android.os.storage.VolumeInfo");
            Method methodGetFsUUID = volumeInfoClazz.getMethod("getFsUuid");

            Field fieldFsType = volumeInfoClazz.getDeclaredField("fsType");
            Field fieldFsLabelField = volumeInfoClazz.getDeclaredField("fsLabel");
            Field fieldPath = volumeInfoClazz.getDeclaredField("path");
            Field fieldInternalPath = volumeInfoClazz.getDeclaredField("internalPath");

            if (volumeInfos != null) {
                for (Object volumeInfo : volumeInfos) {
                    String uuid = (String) methodGetFsUUID.invoke(volumeInfo);
                    if (uuid != null) {
                        UsbInfo usbInfo = new UsbInfo();
                        usbInfo.uuid = uuid;
                        usbInfo.fsType = (String) fieldFsType.get(volumeInfo);
                        usbInfo.fsLabel = (String) fieldFsLabelField.get(volumeInfo);
                        usbInfo.path = (String) fieldPath.get(volumeInfo);
                        usbInfo.internalPath = (String) fieldInternalPath.get(volumeInfo);
                        try {
                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
                                StatFs statFs = new StatFs(usbInfo.path);
                                usbInfo.availableSize = Formatter.formatFileSize(context, statFs.getAvailableBytes());
                                usbInfo.totalSize = Formatter.formatFileSize(context, statFs.getTotalBytes());
                            }
                        } catch (IllegalArgumentException e) {
                            e.printStackTrace();
                        }
                        usbInfos.add(usbInfo);
                    }
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return usbInfos;
    }

    public interface OnUsbReceiveListener {
        void onUsbReceive(@UsbObserveType int usbObserveType, Set<UsbInfo> usbInfos, UsbInfo currUsbInfo);
    }
}
  • 使用
public class TestActivity extends AppCompatActivity implements UsbManager.OnUsbReceiveListener {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        UsbManager.getInstance().registerUsbReceiveListener(this);
    }

 	@Override
    protected void onDestroy() {
        UsbManager.getInstance().unregisterUsbReceiveListener(this);
        super.onDestroy();
    }
	
	@Override
    public void onUsbReceive(int usbObserveType, Set<UsbInfo> usbInfos, UsbInfo currUsbInfo) {
    	// 接收處理
    }
}

注意事項:

在實際項目開發過程中,比如在TV機頂盒開發,使用以上方法是可以較爲準確的監聽到USB的插拔,但是如果嘗試將數據寫入USB,將會出現無法寫入提示 permission denied,這個時候需要去修改源碼。

從源碼中定位到 frameworks\base\services\core\java\com\android\server\pm\PackageManagerService.java,搜索到 systemReady() 這個方法,加入以下幾句代碼:

StorageManagerInternal StorageManagerInternal = LocalServices.getService(StorageManagerInternal.class);
StorageManagerInternal.addExternalStoragePolicy(
        new StorageManagerInternal.ExternalStorageMountPolicy() {
    @Override
    public int getMountMode(int uid, String packageName) {
        if (Process.isIsolated(uid)) {
            return Zygote.MOUNT_EXTERNAL_NONE;
        }
        // 添加允許寫權限---開始
		if (checkUidPermission(WRITE_MEDIA_STORAGE, uid) == PERMISSION_GRANTED) {
            return Zygote.MOUNT_EXTERNAL_DEFAULT;
        }
        // 添加允許寫權限---結束
        if (checkUidPermission(READ_EXTERNAL_STORAGE, uid) == PERMISSION_DENIED) {
            return Zygote.MOUNT_EXTERNAL_DEFAULT;
        }
        if (checkUidPermission(WRITE_EXTERNAL_STORAGE, uid) == PERMISSION_DENIED) {
            return Zygote.MOUNT_EXTERNAL_READ;
        }
        return Zygote.MOUNT_EXTERNAL_WRITE;
    }

    @Override
    public boolean hasExternalStorage(int uid, String packageName) {
        return true;
    }
});

然後在app的 AndroidManifest.xml 加入權限即可:

<uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" />
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章