代碼其實沒有幾行,這裡簡單記錄下學習的過程.
Android系統啟動時會掃描系統與SD卡中的對媒體檔案,分別存入資料庫sqlite中,以contentProvider的形式對外提供服務
路徑:/data/data/com.android.providers.media/databases/XXX...
可以看到有2個db檔案, 一個是系統的,一個是sd卡裡的
用SQLite Expert開啟internal.db,部分截圖如下:
這裡面記錄了音頻audio、視頻video、圖片images的相關資料資訊,我們以音頻audio為例,藍色部分audio_meta就是audio資料表,開啟之後就可以看到詳細資料了,裡面列出了系統內部的所有音頻檔案,各個欄位在android.provider.MediaStore中都定義有相應的常量,如id --- MediaStore.Audio.Media._ID.
而這裡面有想說下這四個欄位
含義在源碼裡都有說明,看了一遍資料,發現這四個欄位同時有且僅有一個欄位為1,也就是對於一個多媒體檔案只能是這四種中的一種,預設為0,如果是某種類型,則android系統預設置為1,所以也就明白了為什麼很多掃描系統通知或者來電鈴聲的範例程式碼中,都會有一個類似的條件陳述式:is_notification = 1.
如:
/**
* 掃描系統內部通知鈴聲
*/
| 代碼如下 |
複製代碼 |
private void scannerMediaFile() { ContentResolver cr = this.getContentResolver(); Cursor cursor = cr.query(MediaStore.Audio.Media.INTERNAL_CONTENT_URI, new String[] { MediaStore.Audio.Media._ID, MediaStore.Audio.Media.DATA, MediaStore.Audio.Media.TITLE }, "is_notification != ?", new String[] { "0" }, "_id asc"); if (cursor == null) { return; } while (cursor.moveToNext()) { data.add(cursor.getString(1)); } } |
這裡 is_notification != 0,效果是一樣的,除非哪天google再定義個2, 3 ......
上面扯了些其他的,關於設定鈴聲的方法,系統提供了一個鈴聲管理器android.provider.RingtoneManager,其中提供了擷取與設定鈴聲的API
如:
| 代碼如下 |
複製代碼 |
| Uri uri = RingtoneManager.getActualDefaultRingtoneUri(MediaActivity.this, RingtoneManager.TYPE_NOTIFICATION);可以擷取到當前系統的通知鈴聲uri |
第二個參數可以指定擷取的鈴聲類型,還有其他的TYPE_RINGTONE,TYPE_ALARM, TYPE_ALL
設定鈴聲的API:
| 代碼如下 |
複製代碼 |
RingtoneManager.setActualDefaultRingtoneUri(MediaActivity.this, RingtoneManager.TYPE_NOTIFICATION, Uri.parse(data.get(position)));
|
第二個參數同上,最後一個是指定一個新的Uri, 這裡的data.get(position)就是在上面的掃描碼掃描出的所有通知鈴聲path路徑中選澤一個,然後在解析成一個URI對象傳入即可
那麼android是如何擷取指定類型的系統鈴聲呢?
這涉及到另一個類android.provider.Settings
相關源碼如下:
| 代碼如下 |
複製代碼 |
public static Uri getActualDefaultRingtoneUri(Context context, int type) { //根據指定的類型擷取Settings類中對應的類型,這裡RingtoneManager.TYPE_NOTIFICATION對應的為Settings.System.NOTIFICATION_SOUND,其實也就是下面所說的system表中的一個name欄位名 String setting = getSettingForType(type); if (setting == null) return null; //調用Settings類中靜態內部類System中的相應方法 final String uriString = Settings.System.getString(context.getContentResolver(), setting); return uriString != null ? Uri.parse(uriString) : null; } |
| 代碼如下 |
複製代碼 |
public synchronized static String getString(ContentResolver resolver, String name) { //MOVED_TO_SECURE是System類中定義的一個hashSet集合,在Android系統啟動時,會初始化30(目前是30)條涉及系統安全的設定資料(如果http代理設定,wifi相關設定),並且存入資料庫中,與多媒體的db不同,系統預設存放在settings.db中,路徑為/data/data/com.android.providers.settings/databases,具體是存放在settings.db資料庫執行個體的secure表中,用工具開啟,可以看到此表中恰好有30條資料。說了那麼多,其實這裡是檢查你所指定的類型也就是db中的欄位在不在這個集合中,如果在,則會調用Settings類中的另一個靜態內部類Secure中的getString(...)方法 if (MOVED_TO_SECURE.contains(name)) { Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System" + " to android.provider.Settings.Secure, returning read-only value."); return Secure.getString(resolver, name); } //如果不在那個涉及系統安全的設定集合中,則調用Settings中定義的一個緩衝類NameValueCache中的getString(...) if (sNameValueCache == null) { sNameValueCache = new NameValueCache(SYS_PROP_SETTING_VERSION, CONTENT_URI, CALL_METHOD_GET_SYSTEM); } return sNameValueCache.getString(resolver, name); } //NameValueCache中getString()方法部分代碼 Cursor c = null; try { //mUri == "content://settings/system"在NameValueCache初始化時賦值,指定查詢的是settings.db中的system表,同理上面提到的Secure類的getString(...)中調用的也是這個緩衝類的同名方法,只不過mUri被指定為查詢secure表(這2個表中除了id,只有name與value2個欄位,分別指定設定的類型與對應的值) c = cp.query(mUri, SELECT_VALUE, NAME_EQ_PLACEHOLDER, new String[]{name}, null); if (c == null) { Log.w(TAG, "Can't get key " + name + " from " + mUri); return null; } String value = c.moveToNext() ? c.getString(0) : null; synchronized (this) { //查詢完講name/value索引值對放入mValues集合中,當然如果這個集合中已經存在這個索引值對,那麼也就不會執行這段操作db的代碼了 mValues.put(name, value); } |
settings.db結構如下:
上面樣本中指定的TYPE_NOTIFICATION的資料如下(藍色部分):
最後返回的就是file:///..........這個String資料,再轉化成URI返回給調用者
OK,那麼設定鈴聲的API, setAc.......執行的過程也類似:
| 代碼如下 |
複製代碼 |
public static boolean putString(ContentResolver resolver, String name, String value) { //這裡依然是檢查設定的類型是否涉及到系統預置的安全設定集合,如果是,則直接返回false if (MOVED_TO_SECURE.contains(name)) { Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System" + " to android.provider.Settings.Secure, value is unchanged."); return false; } //這裡執行的是另一個靜態內部類NameValueTable中的方法 return putString(resolver, CONTENT_URI, name, value); } protected static boolean putString(ContentResolver resolver, Uri uri, String name, String value) { // The database will take care of replacing duplicates. try { ContentValues values = new ContentValues(); values.put(NAME, name); values.put(VALUE, value); //指定類型name與相應value插入db的system表中,如果表中已經存在指定的類型欄位怎麼辦? 請看上面的源碼注釋... resolver.insert(uri, values); return true; } catch (SQLException e) { Log.w(TAG, "Can't set key " + name + " in " + uri, e); return false; } } |
1,設定鈴聲之前,要Crowdsourced Security Testing道有哪些系統鈴聲,所以需要掃描,android提供了xxx.media這個contentProvider為此服務,對應的資料庫為internal.db/external-xx.db
2,拿到鈴聲,真正需要設定的時候,提供了Setting類管理這個過程,其對應的資料庫為settings.db
2.1 首先檢查是否涉及到系統的一些安全設定參數,這裡定義了Secure類來管理,如果涉及到系統安全,那麼又分為兩種情況:
2.1.1 如果是查詢,則操作secure 表查詢
2.1.2 如果是寫操作,則直接return
2.2 不涉及到系統安全,就屬於正常設定,接著定義了System類管理
3,查詢操作的實際操作類NameValueCache, 其中定義了
緩衝name/value索引值對的集合,避免每次操作都去操作資料庫
可以由調用者指定的uri,便於根據uri決定去操作哪張表
以及寫操作的NameValueTable類,因為寫操作涉及到id, 所以繼承了BaseColumns類