Create local package repository on Ubuntu

23 October 2014


先前有講過如何建立自己的debian package
這裡要講怎麼建立一個local的repository,
讓這repository可以在internet上供人使用.
其實這概念跟apt-mirror有點像,只不過deb source是來自於自己罷了.

1. Create your debian package

這一步驟就去參考這篇文章吧
當然你也可以用ubuntu既有的debian package.(只不過我覺得那不如就用apt-mirror就好)


2. Install apache

既然你要成為一個repository server,
那麼就要安裝web server,這裡就選apache吧.
apt-get install apache2


3. Setup

先create一個folder在apache default目錄下(ex: /var/www),
mkdir -p /var/www/debian

把你要給人使用的debian package搬到上面建立的目錄下,
mv *.deb /var/www/debian

接著就替你的debian package建立index,
cd /var/www
dpkg-scanpackages debian /dev/null | gzip -9c > debian/Packages.gz

這樣就完成了!

4. Test

接著就把你的repo加到list裡面去吧,如果要給別人用,或者在別檯機器上用,
記得把127.0.0.1換成public ip.
echo "deb http://127.0.0.1 debian/" >> /etc/apt/sources.list
apt-update
apt-install your-deb-package





read more »


Android In-app billing (IAB) version 3

16 October 2014


這篇要講怎麼整合IAB,
IAB就是讓user可以在你的app中購買商品.

1. Download billing service library

開啓Android SDK Manager,
找到最下面的 Extras,安裝Google Play Billing Library,
安裝完以後,在您Android SDK 目錄中會有該 Library,位置如下:
$SDK_PATH/extras/google/play_billing/in-app-billing-v03

2. Create new package

接著在這一步要在你的project下建立一個新的package,
點選 File > New > Package, 名稱為com.android.vending.billing

3. Copy necessary files

在這一步驟要複製一些必要的檔案至project中,
1. IInAppBillingService.aidl,
    這個檔案放在$SDK_PATH/extras/google/play_billing/in-app-billing-v03底下,
    把IInAppBillingService.aidl,複製到我們剛剛create的package中,
2. *.java
    Google幫我們寫好了很多的wrapper,可以很方便地使用IAB,
    這wrapper會幫我們處理一些bind service的動作,
    透過這些wrapper可以輕易地整合IAB,
    檔案路徑如下:
$SDK_PATH/extras/google/play_billing/samples/TrivialDrive/src/com/example/android
/trivialdrivesample/util
    把這底下所有的.java複製到你的project當中,並且要把package名稱改成自己的

4. Add permission

接著要在AndroidManifest.xml中加入下面這個權限,
<uses-permission android:name="com.android.vending.BILLING">
</uses-permission>

5. Upload apk

因為要購買一個商品,這商品在新増之前,
你一定要有一個apk是有BILLING權限的,
否則是不能新增一個商品.
所以在這步驟要先上傳上去,你可以上傳以後再把它變成draft就好,
當然!你也可以通通寫完code再丟上去測試.

6. Add product

上傳apk以後,接著就可以去developer console新增product了,
1. 先到自己的developer console
2. 點選自己的app
3. 點選左邊的In-app Products
4. 點選Add new product
5. 輸入Product ID(這id等等code裡面會用到)
6. 最後填寫product的資訊

7. Get public key

由於在跟Google Play溝通時,
需要一把public key,
這把key一樣在developer console上,
1. 先到自己的developer console
2. 點選自己的app
3. 點選左邊的Services & APIs
key就會在畫面之中,如下圖!




8. Implement

8-1. Setup

首先要先setup IabHelper,
在setup時就要把剛剛哪組public_key帶進去,
但由於Google有強烈的建議,不要直接hard code public key,
至少要用個xor encrypt的方式,或者用拆解字串的方式存放public key,
xor encrypt就是你先用xor去encrypt你的public key,
之後把encrypt過後的public key放在你的project中,
要用的時候再拿出來decrypt,decrypt完以後,應該就會是原先的public key了,
接著再判斷一下那組public key是否有包含“某部份”真正public key,
有的話才真的是你的public key,

    private IabHelper mHelper;
@Override
public void onCreate(Bundle savedInstanceState) {

String base64EncodedPublicKey = xorDecrypt(PUBLIC_KEY, "key_password");

if (!base64EncodedPublicKey
.contains("eFESRE6ijsRAp3TgWhY1zDwWwo1EqxQgt+f")) {
throw new RuntimeException("This is not my key");
}

mHelper = new IabHelper(this, base64EncodedPublicKey);

mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result) {
Log.d(TAG, "Setup finished.");
if (!result.isSuccess()) {
Log.d(TAG, "Problem setting up In-app Billing: " + result);
}

if (mHelper == null)
return;

}
});
}

public static String xorDecrypt(String input, String key) {
byte[] inputBytes = Base64.decode(input, Base64.DEFAULT);
int inputSize = inputBytes.length;

byte[] keyBytes = key.getBytes();
int keySize = keyBytes.length - 1;

byte[] outBytes = new byte[inputSize];
for (int i = 0; i < inputSize; i++) {
outBytes[i] = (byte) (inputBytes[i] ^ keyBytes[i % keySize]);
}
return new String(outBytes);
}

8-2 購買商品

購買商品要透過launchPurchaseFlow,
launchPurchaseFlow有五個參數,
1. activity
2. product id,就是剛剛上面在developer console新增的那組
3. request code,onActivityResult時會回傳回來
4. listener,購買狀態的listener,用來接收購買成功與否
5. string,可以是空的,可以想像成是補充說明的意思,一樣在購買之後會回傳回來
    if (mHelper != null) {
mHelper.launchPurchaseFlow(this,
SKU_PREMIUM,
10001,
mPurchaseFinishedListener,
"bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ");
}


IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
if (result.isFailure()) {
Log.e(TAG, "Error purchasing: " + result);
return;
} else if (purchase.getSku().equals(SKU_PREMIUM)) {
Toast.makeText(getApplicationContext(), "buy success",Toast.LENGTH_LONG).show();
Log.d(TAG, "Success purchasing:");
}
}
};



8-3 查詢是否有購買

在購買成功以後,android會把購買的結果cache在機器上,
以便快速地查詢, 查詢要透過queryInventoryAsync,
queryInventoryAsync有二個參數,
1. 是否要query product detail(ex: price)
2. listener
其實第一個參數不一定要,
但是當你在沒有網路的情況下, 你的request一定都會是fail,
以至於你無法判斷這個user是否有購買了商品,
且如果你加了第一個參數,你就無法得到商品的detail資訊.
所以使用者要自己衡量一下何時該用.
    mHelper.queryInventoryAsync(false,mGotInventoryListener);
QueryInventoryFinishedListener mGotInventoryListener = new QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {

if (result.isFailure()) {
// handle error here
} else {
// does the user have the premium upgrade?
boolean mIsPremium = inventory.hasPurchase(SKU_PREMIUM);
if (mIsPremium) {
Toast.makeText(getApplicationContext(), "buy",Toast.LENGTH_LONG).show();
}else {
Toast.makeText(getApplicationContext(), "no buy",Toast.LENGTH_LONG).show();
}
}
}
};
8-4 Error handling

最後一步驟,如果你launchPurchaseFlow了第一次,然後取消!
隨即馬上launchPurchaseFlow第二次,絕對會crash,
原因是因為在onActivityResult中要做點處理,
我覺得這很tricky,因為在官網上並沒有提到!
是去看他的example code才發現的!
最後也要記得destroy這個helper.
    @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.d(TAG, "onActivityResult handled by IABUtil.1");
// Pass on the activity result to the helper for handling
if (mHelper!=null && !mHelper.handleActivityResult(requestCode, resultCode, data)) {
Log.d(TAG, "onActivityResult handled by IABUtil.2");
// not handled, so handle it ourselves (here's where you'd
// perform any handling of activity results not related to in-app
// billing...
super.onActivityResult(requestCode, resultCode, data);
} else {
Log.d(TAG, "onActivityResult handled by IABUtil.");
}
}

@Override
protected void onDestroy() {
super.onDestroy();
if (adView != null) {
adView.destroy();
}

if (mHelper != null)
mHelper.dispose();
mHelper = null;
}


9. Test

要測試IBA真的是件麻煩的事情,
要做兩件事,
1. create test account
    因為你用自己的developer account測,會永遠無法購買, '
    會跟你說publisher cannot purchase this item.
    所以要去到developer console中的setting裡面加一組test account,
2. Export Signed Application
    因為你無法用debug key去build app,
    用debug key build出來的app是無法測試的...
    所以得用production key sign出來的app才可以測試.
    接著再把sign好的apk裝在手機上就可以測試了!




read more »


Android Backup service API (backup internal database)

13 October 2014

這篇要講Android自己的backup service
其實使用起來蠻簡單的,
主要分成下面4個步驟,

1. Register for Android Backup Service

首先要先去註冊一個API Key,先到這個網站
進去以後輸入你的package name,接著就會給你一組API key.
這樣就註冊完成.

2. Configuration

完成上面的註冊以後,接著要對你的project做configuration,
先開啟你的AndroidManifest.xml,
先在application element加上一個android:backupAgent這個attribute,
這個attribute的名字,就是待會你新增的class名稱,
<application android:label="MyApp"
android:backupAgent="ExpenseBackupAgent">

接著在application之中,加入下段的meta data,
記得把xxxxxxxxxx換成你剛剛註冊的API Key,
<meta-data android:name="com.google.android.backup.api_key"
android:value="xxxxxxxxxxxxxxxxxxx" />

3. Backup internal database

接著新增一個Class,這個Class要繼承BackupAgentHelper,
記得這Class名稱要跟你manifest.xml中的一樣!
這裡要題外話一下,其實你也可以繼承BackupAgent,
當你如果需要做到更複雜的backup時,
例如你不想backup整份file,你只想backup某個"部分"的data,
這時候你只能透過BackupAgent去做到,
而BackupAgentHelper其實也只是BackupAgent的一個wrapper,
讓你更輕易的backup和restore罷了.
回到主題,先新增一個class繼承BackupAgentHelper,內容如下,
在onCreate裡面用到了FileBackupHelper,
最原始的FileBackupHelper是真的用來backup某個file,
但因為我們這裡要backup db,
所以我們要override掉getFilesDir,
在getFilesDir中去拿db的路徑,
然後透過FileBackupHelper去做backup!
只要透過下面的code就可以幫你自動backup和restore了!

public class ExpenseBackupAgent extends BackupAgentHelper {
private static final String DB_NAME = "expense"; // db名稱
private static final String TAG = ExpenseBackupAgent.class
.getCanonicalName();

@Override
public void onCreate() {
Log.e(TAG, "backu db");
FileBackupHelper dbs = new FileBackupHelper(this, DB_NAME);
addHelper("dbs", dbs);
}

@Override
public File getFilesDir() {
final File f = getDatabasePath(DB_NAME);
return f.getParentFile();
}
}


雖然說backup和restore都是自動的,
那麼要怎麼主動的去request backup?
那就要用BackupManager了,
透過dataChanged,會去幫你queue這個backup的job,
為什麼是說queue呢?因為backup不會馬上執行的!
都是批次的執行!
BackupManager bm = new BackupManager(this);
bm.dataChanged();

4. Test

上面說到了Backup都是批次執行,不是即時的,那我該怎測試呢?
要透過adb了,
adb shell bmgr backup packagename  # 等於dataChanged
adb shell bmgr run # 立即去trigger backup job
adb uninstall packagename # 先刪掉app
最後再安裝一次你的app,
打開以後應該會發現你的資料都還在!








read more »