系統工程師之系統升級研究
前一陣子,一直忙專案並負責了系統升級這一塊。簡單總結和分析系統升級模組的處理。
首先明確系統升級UI以及大概思路:
1.系統升級單獨作為一個apk進行編寫。啟動系統升級apk可以用:
Intent intent = new Intent ();
intent.setClassName(arg1,arg2); //arg1為要啟動的apk的包名,arg2為要啟動apk的具體activity名
startActivity(intent);
2.系統升級apk顯示的方式。這裡我設定成dialog樣式。
說到這裡就解釋一下系統自帶的一些theme
•android:theme=”@android:style/Theme.Dialog” 將一個Activity顯示為對話方塊模式
•android:theme=”@android:style/Theme.NoTitleBar” 不顯示應用程式標題欄
•android:theme=”@android:style/Theme.NoTitleBar.Fullscreen” 不顯示應用程式標題欄,並全屏
•android:theme=”@android:style/Theme.Light” 背景為白色
•android:theme=”@android:style/Theme.Light.NoTitleBar” 白色背景並無標題欄
•android:theme=”@android:style/Theme.Light.NoTitleBar.Fullscreen” 白色背景,無標題欄,全屏
•android:theme=”@android:style/Theme.Black” 背景黑色
•android:theme=”@android:style/Theme.Black.NoTitleBar” 黑色背景並無標題欄
•android:theme=”@android:style/Theme.Black.NoTitleBar.Fullscreen” 黑色背景,無標題欄,全屏
•android:theme=”@android:style/Theme.Wallpaper” 用系統桌面為應用程式背景
•android:theme=”@android:style/Theme.Wallpaper.NoTitleBar” 用系統桌面為應用程式背景,且無標題欄
•android:theme=”@android:style/Theme.Wallpaper.NoTitleBar.Fullscreen” 用系統桌面為應用程式背景,無標題欄,全屏
•android:theme=”@android:style/Translucent”半透明效果
•android:theme=”@android:style/Theme.Translucent.NoTitleBar” 半透明並無標題欄
•android:theme=”@android:style/Theme.Translucent.NoTitleBar.Fullscreen” 半透明效果,無標題欄
3.考慮升級的時間點,這裡升級檢測的時刻,我選擇wifi開啟的時候.
所以我需要註冊一個廣播,來接收wifi 的改變。
<receiver android:name=”com.xiaoshou.systemupdate.WifiReceiver” >
<intent-filter>
<action android:name=”android.net.wifi.RSSI_CHANGED” />
<action android:name=”android.net.wifi.STATE_CHANGE” />
<action android:name=”android.net.wifi.WIFI_STATE_CHANGED” />
</intent-filter>
</receiver>
wifiReceiver是負責處理當wifi 狀態發生改變工作。我處理了三種狀態也就是對應的上面三個action.
在onreceiver 中做相應的處理。
if (intent.getAction().equals(WifiManager.RSSI_CHANGED_ACTION)) { //訊號強度變化後的處理
} else if (intent.getAction().equals(
WifiManager.NETWORK_STATE_CHANGED_ACTION)) {// wifi連線上與否
System.out.println(“網路狀態改變”);
NetworkInfo info = intent
.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
if (info.getState().equals(NetworkInfo.State.DISCONNECTED)) {
System.out.println(“wifi網路連線斷開”);
} else if (info.getState().equals(NetworkInfo.State.CONNECTED)) {
Log.i(TAG, “wifi 連線成功”);
//這裡做檢測新版本,並將檢測到的新版本資訊插入到資料庫,如果希望別的應用能訪問到,需要用contentprovider進行封裝。
//contentprovider只儲存最新版本資訊,這個由伺服器中返回。這裡單獨寫個工具類檢測做檢測更新的響應的操作。CheckUpdate
}
} else if (intent.getAction().equals(
WifiManager.WIFI_STATE_CHANGED_ACTION)) {// wifi開啟與否
int wifistate = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
WifiManager.WIFI_STATE_DISABLED);
if (wifistate == WifiManager.WIFI_STATE_DISABLED) {
System.out.println(“系統關閉wifi”);
} else if (wifistate == WifiManager.WIFI_STATE_ENABLED) {
System.out.println(“系統開啟wifi”);
}
}
checkupdate 類
/**
* check the update Inner
*
* @param strSN
* @param strVer
* @param checkUrl
* @return
*/
private static int checkUpdateInner(String strSN, String strVer, String checkUrl)
{
String sn = "sn=" + strSN;
String version = "version=" + strVer;
String modelClient = SystemFacade.getModelClient();
HttpPost httpRequest = new HttpPost(checkUrl);
HttpClient httpClient = AndroidHttpClient.newInstance("CheckUpdate");
try
{
httpRequest.setEntity(new ByteArrayEntity(buildRequest(strSN, strVer, modelClient).toByteArray()));
HttpResponse httpResponse = httpClient.execute(httpRequest);
// httpResponse.getEntity().getContentLength();
SystemFacade.LOGD("--dela--", "--CheckUpdate.java--checkUpdateInner()--content length == " + httpResponse.getEntity().getContentLength());
SystemFacade.LOGD(TAG,
"Flash test : ++++ httpResponse.getStatusLine().getStatusCode() = "
+ httpResponse.getStatusLine().getStatusCode());
int responseCode = httpResponse.getStatusLine().getStatusCode();
if (isStatusSuccess(responseCode))
{
SystemFacade.LOGD(TAG, "responseCode status success");
UpdateMsg msg = buildResponseMsg(httpResponse);
SystemFacade.LOGD("--dela--", "--responseCode status success--00--");
if (msg != null)
{
SystemFacade.LOGD(TAG, msg.toString());
if (msg.isEmptyMsg())
{
SystemFacade.LOGD(TAG, "no update");
returnCode = CHECK_ALREADY_NEWEST;
}
else
{
Log.d(
TAG,
"!-------type----------!" + msg.getType()
+ "!-------id----------!" + msg.getId()
+ "!--------des---------!" + msg.getDes()
+ "!-------isnecessary----------!"
+ msg.getIsNecessary()
+ "!-------path----------!" + msg.getPath()
+ "!-------version----------!"
+ msg.getVersion()
+ "!-------mark----------!"
+ msg.getCheckMark()
+ "!--------total size---------!"
+ msg.getTotalSize()
+ "!------save path-------!"
+ msg.getSavePath());
UpdatePackageManager manager = UpdatePackageManager.getInstance(UserApplication.getApplication());
manager.insertUpdateMsg(msg);
returnCode = CHECK_HAS_UPDATE;
}
}
else
{
SystemFacade.LOGD(TAG, "---------------" + "no update");
returnCode = CHECK_ALREADY_NEWEST;
}
}
else
{
SystemFacade.LOGD(TAG, "not Status Success " + httpResponse.getStatusLine().getStatusCode());
returnCode = CHECK_ERROR;
}
} catch (Exception e) {
// return CHECK_HAS_UPDATE;
SystemFacade.LOGD(TAG, " isUpdateEnable exception");
e.printStackTrace();
returnCode = CHECK_ERROR;
} finally {
if (httpClient instanceof AndroidHttpClient)
((AndroidHttpClient) httpClient).close();
else
httpClient.getConnectionManager().shutdown();
}
return returnCode;
}
這裡值得注意的是,經過測試,客戶端傳送當前裝置的ID和當前裝置的版本號,以及伺服器請求地址。伺服器會判斷改裝置是否是最新版本,如果是最新版本,返回一個值都是空的UpdateMsg,UpdateMsg類的定義如下:
package com.xiaoshou.systemupdate.msg.model;
import com.xiaoshou.systemupdate.msg.model.ProtocolMsg.ClientVersion;
public class UpdateMsg {
//update package id in database
private long id;
//update package version
private String version;
//update package whole version type
private String type;
//update package descripe
private String des;
//update package request path
private String path;
//update package save path
private String savePath;
//update package priority
private String isNecessary;
//update package check mark
private String checkMark = "";
//update package total size
private long totalSize;
//update package current size
private int currentSize;
public UpdateMsg(ClientVersion version) {
this.version = version.getVersionCode();
this.type = version.getType();
this.des = version.getDes();
this.path = version.getPath();
this.isNecessary = version.getIsNecessary();
this.checkMark = version.getId();
try {
// this.totalSize = Integer.parseInt(version.getFileSize());
this.totalSize = Long.parseLong(version.getFileSize());
} catch (NumberFormatException e) {
this.totalSize = 0;
}
}
public UpdateMsg(){
}
public boolean isEmptyMsg(){
return (this.path == null || "".equals(this.path));
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getDes() {
return des;
}
public void setDes(String des) {
this.des = des;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getSavePath() {
return savePath;
}
public void setSavePath(String savePath) {
this.savePath = savePath;
}
public long getTotalSize() {
return totalSize;
}
public void setTotalSize(long totalSize) {
this.totalSize = totalSize;
}
public int getCurrentSize() {
return currentSize;
}
public void setCurrentSize(int currentSize) {
this.currentSize = currentSize;
}
public String getIsNecessary() {
return isNecessary;
}
public void setIsNecessary(String isNecessary) {
this.isNecessary = isNecessary;
}
public String getCheckMark() {
return checkMark;
}
public void setCheckMark(String checkMark) {
this.checkMark = checkMark;
}
@Override
public String toString() {
return "UpdateMsg [id=" + id + ", version=" + version + ", type=" + type + ", des=" + des
+ ", path=" + path + ", savePath=" + savePath + ", isNecessary=" + isNecessary
+ ", checkMark=" + checkMark + ", totalSize=" + totalSize + ", currentSize="
+ currentSize + "]";
}
}
回到檢測跟新checkUpdateInner方法,前面已經分析通過傳送裝置ID 和裝置version 以及請求伺服器的地址,伺服器會返回相應的新版本資訊對應UpdateMsg各個屬性。如果沒有最新版本會返回一個數據為空的UpdateMsg物件。
如果沒有新版本,這裡不做處理,結束檢測。如果有新版本,將新版本的資訊插入到Contentprovider中,ContentProvider中就存放了伺服器中的最新版本資訊。到此檢測更新的操作基本完成。小結一下:
wifi開啟檢測伺服器上是否有新版本,有新版本插入到Contentprovider中,ContentProvider的實現下篇做介紹。現在只需要知道Contentprovider中是存有最新版本資訊的。
至此完成了初步檢測更新,並將檢測後的結果儲存到ContentProvider中。
接下來我們跟據已經獲得的伺服器版本資訊,做邏輯處理。我以我的案例來說:設定中版本號這一欄如果有新版本需要顯示有個圖示標識。這裡的實現方案有兩種:
1.在檢視建立之前,通過網路請求判斷。
2.前面我們已經將新的版本資訊存入了Contentprovider中,更據contentProvicer中的資料進行判斷顯示。
從時間效率上我選擇第二種。
首先獲取當前系統資訊(版本號)
private static final String DEV_VERSION = "ro.boeye.version"; //這個系統變數儲存了在編譯完成後會的系統版本資訊
CharSequence summary = SystemProperties.get(DEV_VERSION, null);
接下來我們只需要將當前的版本號與contentprovider中的版本資訊作大小比較,判斷當前版本是否未最新版本。如果當前版本大於或等於contentprovider中的資訊的版本號,則沒有最新版本。
public boolean isLatestVersion() {
String selection = UpdateColumn.UPDATE_VERSION + " > ?";
Cursor cursor = cr.query(UpdateColumn.CONTENT_URI,
new String[] { UpdateColumn.UPDATE_ID }, selection,
new String[] { version }, UpdateColumn.DEFAULT_SORT);
int count = 0;
if (cursor != null) {
count = cursor.getCount();
cursor.close();
}
if (count <= 0) {
return true;
} else {
return false;
}
}
其他的邏輯就跟據自己需要進行編寫了。這裡基本完成了當前版本是否是最新版本的驗證工作。
接 下來我們看下載,有了最新版本,我們就要進行下載的邏輯編寫了。前面已經知道如果有新版本,我們會將新版本資訊儲存到contentprovider中。 對新資訊可以從contentprovider中獲取。其他簡單邏輯我這裡就不詳細講了,直接進行下載邏輯。關於下載對於使用者的幾個介面:
1.取消下載。
2.下載。
3.後臺下載。
首先明確我們下載採用的是系統提供的下載框架DownloadManager。
DownloadManager是Android 2.3引入的,基於http協議,用於處理長時間下載。下面是官方的對DownloadManaager的解釋
**
* The download manager is a system service that handles long-running HTTP downloads. Clients may
* request that a URI be downloaded to a particular destination file. The download manager will
* conduct the download in the background, taking care of HTTP interactions and retrying downloads
* after failures or across connectivity changes and system reboots.
*
* Instances of this class should be obtained through
* {@link android.content.Context#getSystemService(String)} by passing
* {@link android.content.Context#DOWNLOAD_SERVICE}.
*
* Apps that request downloads through this API should register a broadcast receiver for
* {@link #ACTION_NOTIFICATION_CLICKED} to appropriately handle when the user clicks on a running
* download in a notification or from the downloads UI.
*
* Note that the application must have the {@link android.Manifest.permission#INTERNET}
* permission to use this class.
*/
public class DownloadManager {
我就挑重點說明一下DownLoadManger的使用。
1.第一步要使用DownloadManager需要在app中註冊許可權。
<permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER" />
2.獲取DownloadManager服務
(DownloadManager)getSystemService(DOWNLOAD_SERVICE);
3.啟動下載服務
寫法一:
ContentValues values = new ContentValues();
values.put(Downloads.URI, url);//指定下載地址
values.put(Downloads.COOKIE_DATA,cookie);//如果下載Server需要cookie,設定cookie
values.put(Downloads.VISIBILITY,Downloads.VISIBILITY_HIDDEN);//設定下載提示是否在螢幕頂部顯示
values.put(Downloads.NOTIFICATION_PACKAGE,getPackageName());//設定下載完成之後回撥的包名
values.put(Downloads.NOTIFICATION_CLASS,DownloadCompleteReceiver.class.getName());//設定下載完成之後負責接收的Receiver,這個類要繼承BroadcastReceiver
values.put(Downloads.DESTINATION,save_path);//設定下載到的路徑,這個需要在Receiver裡自行處理
values.put(Downloads.TITLE,title);//設定下載任務的名稱
this.getContentResolver().insert(Downloads.CONTENT_URI,values);//將其插入到DownloadManager的資料庫中,資料庫會觸發修改事件,啟動下載任務
如何為DownloadManager設定代理,比如Wap
values.put(Downloads.PROXY_HOST,"10.0.0.172");
values.put(Downloads.PROXY_PORT,"80");
寫法二:
public void startDownload()
{
// if(!prefs.contains(DL_ID))
{
// String url = "http://42.121.0.14/cms/firmware/Icarus-E653/2014081420/update.zip";
String url = mUpdateMsg.getPath();
Uri resource = Uri.parse(url);
DownloadManager.Request request = new DownloadManager.Request(resource);
request.setAllowedNetworkTypes(Request.NETWORK_MOBILE | Request.NETWORK_WIFI);
request.setAllowedOverRoaming(false);
SystemFacade.LOGD("--dela--", "--BrightnessDialogActivity.java--startDownload()--11--");
MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
String mimeString = mimeTypeMap.getMimeTypeFromExtension(MimeTypeMap.getFileExtensionFromUrl(url));
request.setMimeType(mimeString);
request.setShowRunningNotification(false);
request.setVisibleInDownloadsUi(false);
request.setNotificationVisibility(Request.VISIBILITY_HIDDEN);
File sdCard = Environment.getExternalStorageDirectory();
String folder = sdCard.getAbsolutePath() + mUpdateManager.DOWNLOADS_DIR;
File dir = new File(folder);
if(!dir.exists())
{
if(dir.mkdirs())
{
SystemFacade.LOGD("--dela--", "-------create----dir ======= "+ folder);
}
}
SystemFacade.LOGD("--dela--", "--BrightnessDialogActivity.java--startDownload()--22--");
File downloadFile = new File(mUpdateManager.DOWNLOADS_DIR+mUpdateMsg.getVersion());
if(downloadFile.exists())
downloadFile.delete();
request.setDestinationInExternalPublicDir(mUpdateManager.DOWNLOADS_DIR, mUpdateMsg.getVersion());
request.setTitle("");
long id = downloadManager.enqueue(request);
prefs.edit().putLong(DL_ID, id).commit();
this.lockScreenNotForSleep();
}
// else
{
// queryDownloadStatus();
// Log.i("--dela--", "--restart--()");
}
// registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
}
此時下載任務資訊通過
long id = downloadManager.enqueue(request);
加 入到了DownloadManager的資料庫(contentProvider)中,通過監聽Contentprovider中的資料變化可以獲得當前 的下載狀態。在這裡返回的reference變數是系統為當前的下載請求分配的一個唯一的ID,我們可以通過這個ID重新獲得這個下載任務,進行一些自己 想要進行的操作或者查詢下載的狀態以及取消下載等等。下載任務會在呼叫enqueue()方法後開始,下載完成後系統會發送一個廣播 android.intent.action.DOWNLOAD_COMPLETE,我們可以通過註冊一個BroadcastReceiver來接收系統 發來的廣播訊息,並進行想要的邏輯處理.
回到對downloadManager的資料庫的監聽,通過對downloadManager原始碼你可以知道downloadManager對外提供的contentprovider的URI是
private static final Uri CONTENT_URI = Uri.parse("content://downloads/my_downloads");
這個可以通過追溯enqueue方法找到。接下來就是應用註冊監聽對應的資料庫的變化。在自己繼承的application類中註冊:
downloadManager = (DownloadManager)getSystemService(DOWNLOAD_SERVICE); //獲取downloadManager的服務物件
prefs = PreferenceManager.getDefaultSharedPreferences(this); //為該應用建立自己的preference,用來儲存當前下載任務的ID
downloadChangeObserver = new DownloadChangeObserver(); //建立一個監視contentprovicder物件,繼承contentObserver
getContentResolver().registerContentObserver(CONTENT_URI, true, downloadChangeObserver); //註冊監聽
說到這裡簡單講一下contentobserver- 內容觀察者,目的是觀察(捕捉)特定Uri引起的資料庫的變化,繼而做一些相應的處理,它類似於資料庫技術中的觸發器(Trigger),當 ContentObserver所觀察的Uri發生變化時,便會觸發它。觸發器分為表觸發器、行觸發器,相應地ContentObserver也分為“表 “ContentObserver、“行”ContentObserver,當然這是與它所監聽的Uri MIME Type有關的。 熟悉Content Provider(內容提供者)的應該知道,我們可以通過UriMatcher類註冊不同型別的Uri,我們可以通過這些不同的,Uri來查詢不同的結 果。根據Uri返回的結果,Uri Type可以分為:返回多條資料的Uri、返回單條資料的Uri。會觸發它。觸發器分為表觸發器、行觸發器,相應地ContentObserver也分為 “表“ContentObserver、“行”ContentObserver,當然這是與它所監聽的Uri MIME Type有關的。下面簡單介紹使用的流程:
1、 建立我們特定的ContentObserver派生類,必須過載父類構造方法,必須過載onChange()方法去處理回撥後的功能實現
2、 利用context.getContentResolover()獲得ContentResolove物件,接著呼叫registerContentObserver()方法去註冊內容觀察者
3、 由於ContentObserver的生命週期不同步於Activity和Service等,因此,在不需要時,需要手動的呼叫
unregisterContentObserver()去取消註冊。
下面看看我的內容觀察者做的事項,獲取當前下載進度並通知UI執行緒更新:
class DownloadChangeObserver extends ContentObserver
{
public DownloadChangeObserver()
{
super(null);
}
@Override
public void onChange(boolean selfChange)
{
//to do
int[] status = new int[] {-1, -1, 0};
DownloadManager.Query query = new DownloadManager.Query().setFilterById(prefs.getLong(DL_ID, 0));
Cursor cr = null;
try {
cr = downloadManager.query(query);
if(null != cr && cr.moveToFirst())
{
status[0] = cr.getInt(cr.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
status[1] = cr.getInt(cr.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
status[2] = cr.getInt(cr.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS));
}
} catch (Exception e) {
// TODO: handle exception
}
finally
{
if(null != cr)
{
cr.close();
}
}
switch (status[2]) {
case DownloadManager.STATUS_PAUSED:
SystemFacade.LOGD("--dela--", "--download manager status pause--");
break;
case DownloadManager.STATUS_PENDING:
SystemFacade.LOGD("--dela--", "--download manager status pending--");
break;
case DownloadManager.STATUS_RUNNING:
float f1 = Float.parseFloat(String.valueOf(status[0]));
float f2 = Float.parseFloat(String.valueOf(status[1]));
BigDecimal b = new BigDecimal(f1/f2);
float f3 = b.setScale(2, BigDecimal.ROUND_HALF_UP).floatValue();
int progress = (int)(f3*100);
Intent intent = new Intent("android.rockchip.update.progress");
intent.putExtra("progress", progress);
sendBroadcast(intent);
lockScreenNotForSleep();
SystemFacade.LOGD("--dela--", "--download manager status running--progress == " + progress + "--");
break;
case DownloadManager.STATUS_SUCCESSFUL:
SystemFacade.LOGD("--dela--", "--download manager status success--");
break;
case DownloadManager.STATUS_FAILED:
SystemFacade.LOGD("--dela--", "--download manager status failed--");
break;
default:
break;
}
SystemFacade.LOGD("--dela--", "--BrightnessDialogActivity.java--onChange()--down so far == " + status[0]
+ "--total size == " + status[1]
+ "--status == " + status[2] + "--");
}
};
這裡主要講一下流程首先獲取到我們下載任務的對應的contentResolver物件,我們通過自己下載任務ID,前面儲存 在preference中,獲取到DownloadManager的query物件正如Google解釋一樣他是作為一個過濾 器,downloadManager可以通過呼叫query方法跟進前面獲取的query物件作為過濾條件來得到對應的任務的資訊查詢遊標。獲得遊標 cursor後就可以查詢到我們下載任務的資訊了。並在
DownloadManager.STATUS_RUNNING:
狀態時通過一定的邏輯計算得出進度比例,併發送廣播。在UI執行緒獲取到廣播,及廣播發來的資料就可以更新UI了。
回到下載完成系統傳送廣播:android.intent.action.DOWNLOAD_COMPLETE:使用者註冊接收到廣播的後做的主要操作放到了另一個app中updateService. 下篇介紹當完成下載之後的工作。到這裡基本就介紹完了下載。
二。取消下載。
我們只需要在DownlaodManager的下載佇列中移除掉我們自己請求任務ID。直接上程式碼
long downloadId = prefs.getLong(DL_ID, -1);
if(downloadId >= 0)
downloadManager.remove(downloadId);
三。後臺下載。
明確一個事情就好了只要下載任務一旦加入到downloadManager的請求佇列,只要沒有移除任務,他就會一直儲存下載任務。所以後臺下載,只需要 將當前應用finish() 掉。下次啟動是downloadManager會跟進任務ID查詢到上次的下載點,繼續下載。
上篇介紹了從檢測到升級包的下載。這篇我簡單介紹從下載完成後到系統安裝重啟並開機的整個流程:
上篇說了downloadManager下載完成後會發送一個廣播android.intent.action.DOWNLOAD_COMPLETE;我們通過監聽這個廣播,將系統升級包的任務交給另一個Apk去完成RKUpdateService4.4。上程式碼:
Timer timer = new Timer(true);
timer.schedule(new TimerTask() {
@Override
public void run() {
// TODO Auto-generated method stub
try
{
UpdateMsg msg = UpdatePackageManager.getInstance(UserApplication.getApplication()).getNewestUpdatePackage(null);
File saveFile = new File(msg.getSavePath());
if(saveFile.length() == msg.getTotalSize())
{
if(checkPackgeMd5Code(msg))
{
UserApplication.getApplication().unregisterContentObserver();
Intent intent1 = new Intent("android.rockchip.update.finish");
String path = SystemFacade.getDefaultUpdatePackage(msg.getPath());
File update = new File(path);
if(update.exists())
update.delete();
if(!saveFile.renameTo(update))
{
String cmd = "mv " + msg.getSavePath() + " " + path;
Runtime.getRuntime().exec(cmd);
SystemFacade.LOGD("--dela--", "--UserApplication.java--download finish--cmd == " + cmd + "--");
}
intent1.putExtra("updateImgPath", path);
mContext.sendBroadcast(intent1);
}
}
}
catch (DownloadException e)
{
e.printStackTrace();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}, 1000);
上面的程式碼做了這麼幾件事情:
1.獲取最新版本的資訊,並確定檔案未損壞,下載完成了。
2.取消對download的contentprovider的內容監聽。
3.將下載的檔案//mnt/sdcard//downloads/Boyue-Ereader_V1.0.0-2015121613 複製到/mnt/sdcard/update.zip中。
4.傳送廣播,讓專門的APK來處理安裝事項。
說到這就得轉移到處理系統安裝的APK中。首先APK需要一個廣播接收者來接收上面傳送的廣播
android.rockchip.update.finish
接受到廣播後啟動一個service去處理
if(action.equals("android.rockchip.update.finish"))
{
Bundle bundle = intent.getExtras();
String imgPath = bundle.getString("updateImgPath");
serviceIntent = new Intent("android.rockchip.update.service");
//serviceIntent.setComponent(new ComponentName("android.rockchip.update.service", "android.rockchip.update.service.FirmwareUpdatingActivity"));
//serviceIntent.putExtra("android.rockchip.update.extra.IMAGE_PATH", bundle.getString("updateImgPath"));
//serviceIntent.putExtra("android.rockchip.update.extra.IMAGE_VERSION", "");
//serviceIntent.putExtra("android.rockchip.update.extra.CURRENT_VERSION", "");
serviceIntent.putExtra("command", RKUpdateService.COMMAND_UPDATE_FINISH);
serviceIntent.putExtra("delay", 5000);
serviceIntent.putExtra("updateImgPath", imgPath);
// context.startActivity(serviceIntent);
context.startService(serviceIntent);
}
在處理的service中的onStartCommand中獲取command的值
int command = intent.getIntExtra("command", COMMAND_NULL);
int delayTime = 10;
Message msg = new Message();
msg.what = command;
msg.arg1 = WorkHandler.NOT_NOTIFY_IF_NO_IMG;
mWorkHandler.sendMessageDelayed(msg, delayTime);
return Service.START_REDELIVER_INTENT;
workHandler跟據command的值進行處理:
case COMMAND_UPDATE_FINISH:
{
LOG("WorkHandler::handleMessage() : To perform 'COMMAND_UPDATE_FINISH'.");
if(mWorkHandleLocked){
LOG("WorkHandler::handleMessage() : locked !!!");
return;
}
if ( null != (searchResult = getValidFirmwareImageFile(IMAGE_FILE_DIRS) ) )
{
if ( 1 == searchResult.length )
{
String path = searchResult[0];
String imageFileVersion = null;
String currentVersion = null;
//if it is rkimage, check the image
if(path.endsWith("img")){
if(!checkRKimage(path)){
LOG("WorkHandler::handleMessage() : not a valid rkimage !!");
return;
}
imageFileVersion = getImageVersion(path);
LOG("WorkHandler::handleMessage() : Find a VALID image file : '" + path
+ "'. imageFileVersion is '" + imageFileVersion);
currentVersion = getCurrentFirmwareVersion();
LOG("WorkHandler::handleMessage() : Current system firmware version : '" + currentVersion + "'.");
}
startUpdateActivity(path, imageFileVersion, currentVersion);
return;
}else {
LOG("find more than two package files, so it is invalid!");
return;
}
}
}
break;
以上程式碼做了這麼幾件事:
1. 確保workHandler只有一個執行緒在操作。
if(mWorkHandleLocked){
LOG("WorkHandler::handleMessage() : locked !!!");
return;
}
mWorkHandlerLocked被volatile關鍵字進行了修飾:Volatile關鍵字的理解與使用下篇再做介紹。
2.得到有效的系統升級映象檔案
3.得到映象檔案的版本號和當前系統的版本號。
4.啟動activity.下面是啟動的程式碼
private void startUpdateActivity(String path, String imageVersion, String currentVersion)
{
Intent intent = new Intent(mContext, UpdateAndRebootActivity.class);
// Intent intent = new Intent();
// intent.setComponent(new ComponentName("android.rockchip.update.service", "android.rockchip.update.service.UpdateAndRebootActivity"));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(EXTRA_IMAGE_PATH, path);
intent.putExtra(EXTRA_IMAGE_VERSION, imageVersion);
intent.putExtra(EXTRA_CURRENT_VERSION, currentVersion);
mContext.startActivity(intent);
}
接下來我們去看啟動的UpdateAndRebootActivity。直接定位到關鍵程式碼,我們可以找到onCreate()方法中,程式碼如下:
wakeLock = null;
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK , TAG);
if(null != wakeLock)
wakeLock.acquire();
LOG("onCreate() : start 'work thread'.");
HandlerThread workThread = new HandlerThread("UpdateAndRebootActivity : work thread");
workThread.start();
mWorkHandler = new WorkHandler(workThread.getLooper() );
mUiHandler = new UiHandler();
mContext.bindService(new Intent(mContext, RKUpdateService.class), mConnection, Context.BIND_AUTO_CREATE);
看到這就有必要看一看RKUpdateService。程式碼實現如下
package android.rockchip.update.service;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.GeneralSecurityException;
import java.util.Locale;
import org.apache.http.Header;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Binder;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.widget.Toast;
import android.os.SystemProperties;
public class RKUpdateService extends Service {
public static final String VERSION = "1.7.6";
private static final String TAG = "RKUpdateService";
private static final boolean DEBUG = true;
private static final boolean mIsNotifyDialog = true;
private static final boolean mIsSupportUsbUpdate = true;
private static final boolean mIsDeleteDirect = true;
private Context mContext;
private volatile boolean mIsFirstStartUp = true;
private static void LOG(String msg) {
if ( DEBUG ) {
Log.d(TAG, msg);
}
}
static {
/*
* Load the library. If it's already loaded, this does nothing.
*/
System.loadLibrary("rockchip_update_jni");
}
public static String OTA_PACKAGE_FILE = "update.zip";
public static String RKIMAGE_FILE = "update.img";
public static final int RKUPDATE_MODE = 1;
public static final int OTAUPDATE_MODE = 2;
private static volatile boolean mWorkHandleLocked = false;
private static volatile boolean mIsNeedDeletePackage = false;
public static final String EXTRA_IMAGE_PATH = "android.rockchip.update.extra.IMAGE_PATH";
public static final String EXTRA_IMAGE_VERSION = "android.rockchip.update.extra.IMAGE_VERSION";
public static final String EXTRA_CURRENT_VERSION = "android.rockchip.update.extra.CURRENT_VERSION";
public static String DATA_ROOT = "/data/media/0";
public static String FLASH_ROOT = Environment.getExternalStorageDirectory().getAbsolutePath();
public static String SDCARD_ROOT = "/mnt/external_sd";
// public static String SDCARD_ROOT = "/mnt/sdcard";
public static String USB_ROOT = "/mnt/usb_storage";
public static String CACHE_ROOT = Environment.getDownloadCacheDirectory().getAbsolutePath();
public static final int COMMAND_NULL = 0;
public static final int COMMAND_CHECK_LOCAL_UPDATING = 1;
public static final int COMMAND_CHECK_REMOTE_UPDATING = 2;
public static final int COMMAND_CHECK_REMOTE_UPDATING_BY_HAND = 3;
public static final int COMMAND_DELETE_UPDATEPACKAGE = 4;
public static final int COMMAND_UPDATE_FINISH = 5;
private static final String COMMAND_FLAG_SUCCESS = "success";
private static final String COMMAND_FLAG_UPDATING = "updating";
public static final int UPDATE_SUCCESS = 1;
public static final int UPDATE_FAILED = 2;
private static final String[] IMAGE_FILE_DIRS = {
DATA_ROOT + "/",
FLASH_ROOT + "/",
SDCARD_ROOT + "/",
USB_ROOT + "/",
};
private String mLastUpdatePath;
private WorkHandler mWorkHandler;
private Handler mMainHandler;
private SharedPreferences mAutoCheckSet;
/*----------------------------------------------------------------------------------------------------*/
public static URI mRemoteURI = null;
public static URI mRemoteURIBackup = null;
private String mTargetURI = null;
private boolean mUseBackupHost = false;
private String mOtaPackageVersion = null;
private String mSystemVersion = null;
private String mOtaPackageName = null;
private String mOtaPackageLength = null;
private String mDescription = null;
private volatile boolean mIsOtaCheckByHand = false;
@Override
public IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
return mBinder;
}
private final LocalBinder mBinder = new LocalBinder();
public class LocalBinder extends Binder {
public void updateFirmware(String imagePath, int mode) {
LOG("updateFirmware(): imagePath = " + imagePath);
try {
mWorkHandleLocked = true;
if(mode == OTAUPDATE_MODE){
RecoverySystem.installPackage(mContext, new File(imagePath));
}else if(mode == RKUPDATE_MODE){
RecoverySystem.installRKimage(mContext, imagePath);
}
} catch (IOException e) {
Log.e(TAG, "updateFirmware() : Reboot for updateFirmware() failed", e);
}
}
public boolean doesOtaPackageMatchProduct(String imagePath) {
LOG("doesImageMatchProduct(): start verify package , imagePath = " + imagePath);
try{
RecoverySystem.verifyPackage(new File(imagePath), null, null);
}catch(GeneralSecurityException e){
LOG("doesImageMatchProduct(): verifaPackage faild!");
return false;
}catch(IOException exc) {
LOG("doesImageMatchProduct(): verifaPackage faild!");
return false;
}
return true;
}
public void deletePackage(String path) {
LOG("try to deletePackage...");
File f = new File(path);
if(f.exists()) {
f.delete();
LOG("delete complete! path=" + path);
}else {
LOG("path=" + path + " ,file not exists!");
}
}
public void unLockWorkHandler() {
LOG("unLockWorkHandler...");
mWorkHandleLocked = false;
}
public void LockWorkHandler() {
mWorkHandleLocked = true;
LOG("LockWorkHandler...!");
}
}
@Override
public void onCreate() {
super.onCreate();
mContext = this;
/*-----------------------------------*/
LOG("starting RKUpdateService, version is " + VERSION);
//whether is UMS or m-user
if(getMultiUserState()) {
FLASH_ROOT = DATA_ROOT;
}
String ota_packagename = getOtaPackageFileName();
if(ota_packagename != null) {
OTA_PACKAGE_FILE = ota_packagename;
LOG("get ota package name private is " + OTA_PACKAGE_FILE);
}
String rk_imagename = getRKimageFileName();
if(rk_imagename != null) {
RKIMAGE_FILE = rk_imagename;
LOG("get rkimage name private is " + RKIMAGE_FILE);
}
try {
mRemoteURI = new URI(getRemoteUri());
mRemoteURIBackup = new URI(getRemoteUriBackup());
LOG("remote uri is " + mRemoteURI.toString());
LOG("remote uri backup is " + mRemoteURIBackup.toString());
}catch(URISyntaxException e) {
e.printStackTrace();
}
mAutoCheckSet = getSharedPreferences("auto_check", MODE_PRIVATE);
mMainHandler = new Handler(Looper.getMainLooper());
HandlerThread workThread = new HandlerThread("UpdateService : work thread");
workThread.start();
mWorkHandler = new WorkHandler(workThread.getLooper());
if(mIsFirstStartUp) {
LOG("first startup!!!");
mIsFirstStartUp = false;
String command = RecoverySystem.readFlagCommand();
String path;
if(command != null) {
LOG("command = " + command);
if(command.contains("$path")) {
path = command.substring(command.indexOf('=') + 1);
LOG("last_flag: path = " + path);
if(command.startsWith(COMMAND_FLAG_SUCCESS)) {
if(!mIsNotifyDialog) {
mIsNeedDeletePackage = true;
mLastUpdatePath = path;
return;
}
LOG("now try to start notifydialog activity!");
Intent intent = new Intent(mContext, NotifyDeleteActivity.class);
if(mIsDeleteDirect)
{
File f = new File(path);
if(f.exists())
{
f.delete();
LOG("delete complete! path=" + path);
}
else
{
LOG("path=" + path + " ,file not exists!");
}
intent.putExtra("deleteDirect", true);
}
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra("flag", UPDATE_SUCCESS);
intent.putExtra("path", path);
startActivity(intent);
mWorkHandleLocked = true;
return;
}
if(command.startsWith(COMMAND_FLAG_UPDATING)) {
Intent intent = new Intent(mContext, NotifyDeleteActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra("flag", UPDATE_FAILED);
intent.putExtra("path", path);
startActivity(intent);
mWorkHandleLocked = true;
return;
}
}
}
}
}
@Override
public void onDestroy() {
LOG("onDestroy.......");
super.onDestroy();
}
@Override
public void onStart(Intent intent, int startId) {
LOG("onStart.......");
super.onStart(intent, startId);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
LOG("onStartCommand.......");
if(intent == null) {
return Service.START_NOT_STICKY;
}
int command = intent.getIntExtra("command", COMMAND_NULL);
int delayTime = 10;
// if(COMMAND_UPDATE_FINISH != command)
delayTime = intent.getIntExtra("delay", 2000);
// else {
// Message msg = new Message();
// msg.what = command;
// msg.arg1 = WorkHandler.NOT_NOTIFY_IF_NO_IMG;
// mWorkHandler.sendMessage(msg);
// return Service.START_REDELIVER_INTENT;
// }
LOG("command = " + command + " delaytime = " + delayTime);
if(command == COMMAND_NULL) {
return Service.START_NOT_STICKY;
}
if(command == COMMAND_CHECK_REMOTE_UPDATING) {
mIsOtaCheckByHand = false;
if(!mAutoCheckSet.getBoolean("auto_check", true)) {
LOG("user set not auto check!");
return Service.START_NOT_STICKY;
}
}
if(command == COMMAND_CHECK_REMOTE_UPDATING_BY_HAND) {
mIsOtaCheckByHand = true;
command = COMMAND_CHECK_REMOTE_UPDATING;
}
if(mIsNeedDeletePackage) {
command = COMMAND_DELETE_UPDATEPACKAGE;
delayTime = 20000;
mWorkHandleLocked = true;
}
Message msg = new Message();
msg.what = command;
msg.arg1 = WorkHandler.NOT_NOTIFY_IF_NO_IMG;
mWorkHandler.sendMessageDelayed(msg, delayTime);
return Service.START_REDELIVER_INTENT;
}
/** @see mWorkHandler. */
private class WorkHandler extends Handler {
private static final int NOTIFY_IF_NO_IMG = 1;
private static final int NOT_NOTIFY_IF_NO_IMG = 0;
/*-----------------------------------*/
public WorkHandler(Looper looper) {
super(looper);
}
public void handleMessage(Message msg) {
String[] searchResult = null;
switch (msg.what) {
case COMMAND_CHECK_LOCAL_UPDATING:
LOG("WorkHandler::handleMessage() : To perform 'COMMAND_CHECK_LOCAL_UPDATING'.");
if(mWorkHandleLocked){
LOG("WorkHandler::handleMessage() : locked !!!");
return;
}
if ( null != (searchResult = getValidFirmwareImageFile(IMAGE_FILE_DIRS) ) )
{
if ( 1 == searchResult.length )
{
String path = searchResult[0];
String imageFileVersion = null;
String currentVersion = null;
//if it is rkimage, check the image
if(path.endsWith("img")){
if(!checkRKimage(path)){
LOG("WorkHandler::handleMessage() : not a valid rkimage !!");
return;
}
imageFileVersion = getImageVersion(path);
LOG("WorkHandler::handleMessage() : Find a VALID image file : '" + path
+ "'. imageFileVersion is '" + imageFileVersion);
currentVersion = getCurrentFirmwareVersion();
LOG("WorkHandler::handleMessage() : Current system firmware version : '" + currentVersion + "'.");
}
startProposingActivity(path, imageFileVersion, currentVersion);
return;
}else {
LOG("find more than two package files, so it is invalid!");
return;
}
}
break;
case COMMAND_CHECK_REMOTE_UPDATING:
if(mWorkHandleLocked){
LOG("WorkHandler::handleMessage() : locked !!!");
return;
}
for(int i = 0; i < 2; i++) {
try {
boolean result;
if(i == 0) {
mUseBackupHost = false;
result = requestRemoteServerForUpdate(mRemoteURI);
}else{
mUseBackupHost = true;
result = requestRemoteServerForUpdate(mRemoteURIBackup);
}
if(result) {
LOG("find a remote update package, now start PackageDownloadActivity...");
startNotifyActivity();
}else {
LOG("no find remote update package...");
myMakeToast(mContext.getString(R.string.current_new));
}
break;
}catch(Exception e) {
//e.printStackTrace();
LOG("request remote server error...");
myMakeToast(mContext.getString(R.string.current_new));
}
try{
Thread.sleep(5000);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
break;
case COMMAND_DELETE_UPDATEPACKAGE:
//if mIsNeedDeletePackage == true delete the package
if(mIsNeedDeletePackage) {
LOG("execute COMMAND_DELETE_UPDATEPACKAGE...");
File f = new File(mLastUpdatePath);
if(f.exists()) {
f.delete();
LOG("delete complete! path=" + mLastUpdatePath);
}else {
LOG("path=" + mLastUpdatePath + " ,file not exists!");
}
mIsNeedDeletePackage = false;
mWorkHandleLocked = false;
}
break;
case COMMAND_UPDATE_FINISH:
{
LOG("WorkHandler::handleMessage() : To perform 'COMMAND_UPDATE_FINISH'.");
if(mWorkHandleLocked){
LOG("WorkHandler::handleMessage() : locked !!!");
return;
}
if ( null != (searchResult = getValidFirmwareImageFile(IMAGE_FILE_DIRS) ) )
{
if ( 1 == searchResult.length )
{
String path = searchResult[0];
String imageFileVersion = null;
String currentVersion = null;
//if it is rkimage, check the image
if(path.endsWith("img")){
if(!checkRKimage(path)){
LOG("WorkHandler::handleMessage() : not a valid rkimage !!");
return;
}
imageFileVersion = getImageVersion(path);
LOG("WorkHandler::handleMessage() : Find a VALID image file : '" + path
+ "'. imageFileVersion is '" + imageFileVersion);
currentVersion = getCurrentFirmwareVersion();
LOG("WorkHandler::handleMessage() : Current system firmware version : '" + currentVersion + "'.");
}
startUpdateActivity(path, imageFileVersion, currentVersion);
return;
}else {
LOG("find more than two package files, so it is invalid!");
return;
}
}
}
break;
default:
break;
}
}
}
private String[] getValidFirmwareImageFile(String searchPaths[]) {
for ( String dir_path : searchPaths) {
String filePath = dir_path + OTA_PACKAGE_FILE;
LOG("getValidFirmwareImageFile() : Target image file path : " + filePath);
if ((new File(filePath)).exists()) {
return (new String[] {filePath} );
}
}
//find rkimage
for ( String dir_path : searchPaths) {
String filePath = dir_path + RKIMAGE_FILE;
//LOG("getValidFirmwareImageFile() : Target image file path : " + filePath);
if ( (new File(filePath) ).exists() ) {
return (new String[] {filePath} );
}
}
if(mIsSupportUsbUpdate) {
//find usb device update package
File usbRoot = new File(USB_ROOT);
if(usbRoot.listFiles() == null) {
return null;
}
for(File tmp : usbRoot.listFiles()) {
if(tmp.isDirectory()) {
File[] files = tmp.listFiles(new FileFilter() {
@Override
public boolean accept(File arg0) {
LOG("scan usb files: " + arg0.getAbsolutePath());
if(arg0.isDirectory()) {
return false;
}
if(arg0.getName().equals(RKIMAGE_FILE) || arg0.getName().equals(OTA_PACKAGE_FILE)){
return true;
}
return false;
}
});
if(files != null && files.length > 0) {
return new String[] {files[0].getAbsolutePath()};
}
}
}
}
return null;
}
native private static String getImageVersion(String path);
native private static String getImageProductName(String path);
private void startProposingActivity(String path, String imageVersion, String currentVersion) {
Intent intent = new Intent();
intent.setComponent(new ComponentName("android.rockchip.update.service", "android.rockchip.update.service.FirmwareUpdatingActivity") );
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(EXTRA_IMAGE_PATH, path);
intent.putExtra(EXTRA_IMAGE_VERSION, imageVersion);
intent.putExtra(EXTRA_CURRENT_VERSION, currentVersion);
mContext.startActivity(intent);
}
private void startUpdateActivity(String path, String imageVersion, String currentVersion)
{
Intent intent = new Intent(mContext, UpdateAndRebootActivity.class);
// Intent intent = new Intent();
// intent.setComponent(new ComponentName("android.rockchip.update.service", "android.rockchip.update.service.UpdateAndRebootActivity"));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(EXTRA_IMAGE_PATH, path);
intent.putExtra(EXTRA_IMAGE_VERSION, imageVersion);
intent.putExtra(EXTRA_CURRENT_VERSION, currentVersion);
mContext.startActivity(intent);
}
private boolean checkRKimage(String path){
String imageProductName = getImageProductName(path);
LOG("checkRKimage() : imageProductName = " + imageProductName);
if(imageProductName == null) {
return false;
}
if(imageProductName.equals(getProductName())){
return true;
}else {
return false;
}
}
private String getOtaPackageFileName() {
String str = SystemProperties.get("ro.ota.packagename");
if(str == null || str.length() == 0) {
return null;
}
if(!str.endsWith(".zip")) {
return str + ".zip";
}
return str;
}
private String getRKimageFileName() {
String str = SystemProperties.get("ro.rkimage.name");
if(str == null || str.length() == 0) {
return null;
}
if(!str.endsWith(".img")) {
return str + ".img";
}
return str;
}
private String getCurrentFirmwareVersion() {
return SystemProperties.get("ro.firmware.version");
}
private static String getProductName() {
return SystemProperties.get("ro.product.model");
}
private void notifyInvalidImage(String path) {
Intent intent = new Intent();
intent.setComponent(new ComponentName("android.rockchip.update.service", "android.rockchip.update.service.InvalidFirmwareImageActivity") );
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(EXTRA_IMAGE_PATH, path);
mContext.startActivity(intent);
}
private void makeToast(final CharSequence msg) {
mMainHandler.post(new Runnable(){
public void run(){
Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_LONG).show();
}
});
}
/**********************************************************************************************************************
ota update
***********************************************************************************************************************/
public static String getRemoteUri() {
return "http://" + getRemoteHost() + "/OtaUpdater/android?product=" + getOtaProductName() + "&version=" + getSystemVersion()
+ "&sn=" + getProductSN() + "&country=" + getCountry() + "&language=" + getLanguage();
}
public static String getRemoteUriBackup() {
return "http://" + getRemoteHostBackup() + "/OtaUpdater/android?product=" + getOtaProductName() + "&version=" + getSystemVersion()
+ "&sn=" + getProductSN() + "&country=" + getCountry() + "&language=" + getLanguage();
}
public static String getRemoteHost() {
String remoteHost = SystemProperties.get("ro.product.ota.host");
if(remoteHost == null || remoteHost.length() == 0) {
remoteHost = "192.168.1.143:2300";
}
return remoteHost;
}
public static String getRemoteHostBackup() {
String remoteHost = SystemProperties.get("ro.product.ota.host2");
if(remoteHost == null || remoteHost.length() == 0) {
remoteHost = "192.168.1.143:2300";
}
return remoteHost;
}
public static String getOtaProductName() {
String productName = SystemProperties.get("ro.product.model");
if(productName.contains(" ")) {
productName = productName.replaceAll(" ", "");
}
return productName;
}
public static boolean getMultiUserState() {
String multiUser = SystemProperties.get("ro.factory.hasUMS");
if(multiUser != null && multiUser.length() > 0) {
return !multiUser.equals("true");
}
multiUser = SystemProperties.get("ro.factory.storage_policy");
if(multiUser != null && multiUser.length() > 0) {
return mult