文章目錄
轉自http://www.learningandroid.net/blog/advance/programmable-toggle-gps/
手機應用中最酷的可能就是位置服務相關的了,如何讀取GPS資訊,在官方文檔上有相當詳細的說明,後面如果有機會,我也會專門寫例子來介紹(教程已完成,請參見:教程:實現Android的不同精度的定位(基於網路和GPS))。但今天,我們先來看下如何以編程的方式來開啟或關閉GPS。
官方的API中,android.provider.Settings.Secure類有2個靜態方法:
public static final void setLocationProviderEnabled (ContentResolver cr, String provider, boolean enabled)
和
public static final boolean isLocationProviderEnabled (ContentResolver cr, String provider)
不過遺憾的是,這2個方法都註明了從API Level 8(即Android 2.2)才開始提供,那麼在2.2之前又該如何編程實現GPS的開關呢?
山重水複疑無路
首先,我們要知道,Android系統的設定畫面中就可以進行GPS的開關,那麼它是如何?的呢?
由於我的機器上的android source是2.3版本的,所以直接啟動了一個2.1的模擬器,用adb pull將Settings.apk抓下來,反編譯之後,在SecuritySettings類中找到如下代碼:
package,com.android.settings.SecuritySettings.java
CheckBoxPreference localCheckBoxPreference3 = this.mGps; if (paramPreference == localCheckBoxPreference3) { ContentResolver localContentResolver3 = getContentResolver(); boolean bool6 = this.mGps.isChecked(); Settings.Secure.setLocationProviderEnabled(localContentResolver3, "gps", bool6); continue; }
可以看到2.1系統中已經存在有Settings.Secure.setLocationProviderEnabled方法了,只是該方法沒有開放而已,事實上讀過Android源碼的人都對/*hide*/很反感吧,看得到,摸不到!
既然Setting畫面中的用法,我們不能使用,那麼再換1種方法,我們去看一下Settings.Secure.setLocationProviderEnabled的寫法,然後直接套用。
這次,我們直接去看Android 2.3的源碼,找到Setting.java之後,找到相關的方法,代碼如下:
core, android.provider.Setting.java
public static final void setLocationProviderEnabled(ContentResolver cr, String provider, boolean enabled) { // to ensure thread safety, we write the provider name with a '+' or '-' // and let the SettingsProvider handle it rather than reading and modifying // the list of enabled providers. if (enabled) { provider = "+" + provider; } else { provider = "-" + provider; } putString(cr, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, provider); }
原來這個方法只是1個封裝,事實上調用的還是Settings.Secure中的putString方法,我們直接借用過來:
在自己的onClick事件中寫上
Settings.Secure.putString(getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED, “network,gps”);
然後執行,WOW,發生了什麼,需要android.permission.WRITE_SETTINGS許可權?在Manifest檔案中加上,再運行,還是出錯,不過這次需要的是android.permission.WRITE_SECURE_SETTINGS,再次加上。
滿懷希望的再次運行,結果還是一樣的問題:
java.lang.SecurityException: Permission denial: writing to secure settings requires android.permission.WRITE_SECURE_SETTINGS
看來,Google封死了直接調用Settings的路了,事實上我又試著使用反射來直接調用setLocationProviderEnabled方法,結果也是一樣的告訴我需要許可權。
柳暗花明又一村
難道沒有別的辦法了嗎?那些2.1中可以啟動並執行App Widget是如何做到的呢?
再次檢視2.1的Settings.apk中的代碼,發現有1個widget包,裡面有1個類叫SettingsAppWidgetProvider,這就是用來提供App Widget的類,繼續研究這裡的代碼,發現構造RemoteView的代碼中用到如下方法:
private static PendingIntent getLaunchPendingIntent(Context paramContext, int paramInt1, int paramInt2) { Intent localIntent1 = new Intent(); Intent localIntent2 = localIntent1.setClass(paramContext, SettingsAppWidgetProvider.class); Intent localIntent3 = localIntent1.addCategory("android.intent.category.ALTERNATIVE"); Uri localUri = Uri.parse("custom:" + paramInt2); Intent localIntent4 = localIntent1.setData(localUri); return PendingIntent.getBroadcast(paramContext, 0, localIntent1, 0); }
由於這是反編譯的結果,略微有點混亂,但還是可以看出思路,目的是通過PendingIntent來扔出1個Intent,接受者是SettingsAppWidgetProvider.class,接受的參數有2個,1個是Category:SettingsAppWidgetProvider.class(正是這個類自身),另1個是Data:Uri.parse(“custom:” + paramInt2),這個paramInt2是Widget中的表徵圖按鈕的序號。所有的序號在類的首部都有定義:
private static final int BUTTON_BLUETOOTH = 4; private static final int BUTTON_BRIGHTNESS = 1; private static final int BUTTON_GPS = 3; private static final int BUTTON_SYNC = 2; private static final int BUTTON_WIFI = 0;
那麼我們只要送出custom:3的Uri給SettingsAppWidgetProvider.class,就應該可以由SettingsAppWidgetProvider類來幫我們調用Settings.Secure.setLocationProviderEnabled方法了。
說到做到,在Activity中添加如下方法:
private void toggleGPS() {Intent gpsIntent = new Intent();gpsIntent.setClassName("com.android.settings","com.android.settings.widget.SettingsAppWidgetProvider");gpsIntent.addCategory("android.intent.category.ALTERNATIVE");gpsIntent.setData(Uri.parse("custom:3"));try {PendingIntent.getBroadcast(this, 0, gpsIntent, 0).send();}catch (CanceledException e) {e.printStackTrace();}}
這個方法是1個純開關,如果當前是開啟的,那麼就會關閉它,反之亦然。
檢查GPS開關狀態
那麼,如何查看當前的GPS開關狀態呢?可以用上面提到的反射方式調用isLocationProviderEnabled,代碼片斷如下:
secureClass = cl.loadClass("android.provider.Settings$Secure");isMethod = secureClass.getMethod("isLocationProviderEnabled",ContentResolver.class, String.class);Boolean ret = (Boolean) isMethod.invoke(secureClass, this.getContentResolver(), "gps");
也可以直接用下面的方法:
private void isGPSEnable() {/* 用Setting.System來讀取也可以,只是這是更舊的用法String str = Settings.System.getString(getContentResolver(),Settings.Secure.LOCATION_PROVIDERS_ALLOWED);*/String str = Settings.Secure.getString(getContentResolver(),Settings.Secure.LOCATION_PROVIDERS_ALLOWED);Log.v("GPS", str);if (str != null) {return str.contains("gps");}else{return false;}}
這2種方法的原理都是一樣的,方法2其實也就是isLocationProviderEnabled實際調用的代碼,只是Google未對讀取操作進行許可權限制。
總結
如果目標手機是運行Android 2.2的話,那麼最好還是使用2.2開放的Settings.Secure類中的2個方法來操作。但如果目標手機啟動並執行版本是2.1或以下的話,那麼就只能使用變通的方法來實現了。這1方法在Android官方的Wiki上已經有人提出了,詳情請見:Issue 7890。但可能是2.1版本已經古舊不再維護的原因,官方並未進行任何的Fix。