2018-11-04

Konva.js + Spot light effect (Masking)

這篇文章提供利用 Konva.js 實現 Spotlight 效果之範例。

++
底圖 + 80%填充摭黑 + Cutout Spotlight

Konva.js 部分之程式碼 :

...

<div id="stage-parent" style="width: 50%;">
  <div id="scene_container">
  </div>
</div>

...

<script>

  var stage = new Konva.Stage({
    container: 'scene_container',
    width: 800,
    height: 600
  })

  var stage_scale =1
  var fitStageIntoParentContainer =function() {
    var container = document.querySelector('#stage-parent')

    // now we need to fit stage into parent
    var containerWidth = container.offsetWidth
    // to do this we need to scale the stage
    stage_scale = containerWidth / 800

    stage.width(800 * stage_scale)
    stage.height(600 * stage_scale)
    stage.scale({ x: stage_scale, y: stage_scale })
    stage.draw()
  }
  window.addEventListener('resize', fitStageIntoParentContainer)
  fitStageIntoParentContainer()

  //
  // background layer
  //
  var layer = new Konva.Layer()
  stage.add(layer)

  var bgImg = new Konva.Image({
    width: 800,
    height: 600
  })
  layer.add(bgImg)
  var bgImgObj = new Image()
  bgImgObj.onload = function() {
    bgImg.image(bgImgObj)
    layer.draw()
  }
  bgImgObj.src='https://4.bp.blogspot.com/-u4PaCZRXNZo/W95l88CNWwI/AAAAAAAAEs4/juPqMGiKoLgKgfEqwWmdGles6jXvCCdzgCPcBGAYYCw/s1600/DSC06624m.jpg'

  //
  // mask layer
  //
  var layer_mask =new Konva.Layer()
  stage.add(layer_mask)
  var opacity_rect = new Konva.Rect({
    x: 0,
    y: 0,
    width: 800,
    height: 600,
    fill: 'black',
    draggable: false,
    opacity: 0.8
  })
  layer_mask.add(opacity_rect)

  var transparent_circle = new Konva.Circle({
    x: 400,
    y: 300,
    radius: 200,
    fill: 'black',
    draggable: true,
    globalCompositeOperation: 'destination-out'
  })
  layer_mask.add(transparent_circle)
  layer_mask.draw()

</script>
Konva.js 執行結果 Demo (Spotlight 可拖曳) :

2018-08-23

Unity 3D + V-HACD

碰撞偵測在物理動力模擬是不可缺少的功能,兩個複雜多邊形物體在執行碰撞偵測時需要做很多幾何運算,因此若要遊戲的畫面能夠流暢顯示,遊戲中的碰撞偵測就必須要能達到 real-time 的等級,目前能夠快速執行碰撞偵測的演算法[1][2]幾乎都是基於 Convex Collider 去計算(Unity 3D 使用的物理引擎 PhysX 也包括在內)。

現實世界裡大部份物體都不屬於 Convex 多邊形物體 (Nonconvex Polyhedra),這些 Nonconvex 多邊形物體也稱為 Concave 多邊形物體,為了解決無法對 Concave Collider 進行碰撞偵測的問題,可以把 Concave 多邊形分解成多個 Convex 多邊形。V-HACD[3]就是一個分解 Concave 多邊形的演算法,它所產生的 Convex 多邊形並非完全等於原始多邊形,它的目標是以少量的 Convex 多邊形去近似原始的多邊形,能在不失真以及產生的 Convex 多邊形數目上取得平衡。


需要 7611 個 Convex 多邊形來組合成原始多邊形 (來源[3])

Unreal Engine 4 也使用 V-HACD 來分解 Concave 多邊形。(來源[4])

底下的步驟敘述如何編譯 V-HACD 並在 Unity 中使用 (macOS/Windows) :
1. V-HACD plugin for Unity macOS
1.1 xcode 編譯 vhacd
這裡取得 V-HACD 程式原始碼並確認已有安裝 python 及 cmake 之後,在 install 資料夾裡執行指令 (它會在 build/mac 底下建立 xcode 專案)
./run.py --cmake

執行成功之後在 build/mac 資料夾開啟專案檔案 VHACD.xcodeproj,然後編譯出 Library (Scheme 選擇 ALL_BUILD>My Mac),編譯完成後會看到靜態連結檔 build/mac/VHACD_Lib/Release/libvhacd.a。

1.2 建立 Bundle 專案
在 xcode 另外建立新的 Bundle 專案。

選擇 macOS > Bundle

Linked Frameworks and Libraries 新增剛產生的 libvhacd.a

General > Linked Frameworks and Libraries 新增 libvhacd.a

ps. 後最編譯時若出現下列錯誤訊息則 Linked Frameworks and Libraries 再新增 OpenCL.framework
Undefined symbols for architecture x86_64:
  "_clBuildProgram", referenced from:
      VHACD::VHACD::OCLInit(void*, VHACD::IVHACD::IUserLogger*) in libvhacd.a(VHACD.o)
...

OpenCL.framework and libvhacd.a

Search Paths 的 Header Search Paths 新增指向 vhacd 原始碼路徑 src/VHACD_Lib/public,Library Search Paths 新增指向 vhacd 產生靜態連結Lib的路徑 build/mac/VHACD_Lib/Release。

指向 Header Search Path 及 Library Search Path 的相對路徑

新增 Header File stdafx.h :
#ifndef stdafx_h
#define stdafx_h

#ifdef _WIN32
  #include "targetver.h"

  #define WIN32_LEAN_AND_MEAN             // Exclude rarely-used stuff from Windows headers
  // Windows Header Files:
  #include <windows.h>
  #define MODULE_API __declspec(dllexport)
  #define STD_CALL __stdcall
#else
  #define MODULE_API
  #define STD_CALL
#endif

// TODO: reference additional headers your program requires here
#include "VHACD_api.h"
#include "VHACD.h"
#include "ConvexDecompTool.h"
#include <string.h>
#include <stdio.h>


#endif /* stdafx_h */

新增 Header File targetver.h :
#ifndef targetver_h
#define targetver_h

#include <SDKDDKVer.h>

#endif /* targetver_h */

新增 Header File ConvexDecompTool.h :
#ifndef ConvexDecompTool_h
#define ConvexDecompTool_h

#include "stdafx.h"

using namespace VHACD;

class FVHACDProgressCallback : public IVHACD::IUserCallback
{
private:
  CBFunc tmp_cb_func;
  
public:
  FVHACDProgressCallback(CBFunc pCBFunc);
  ~FVHACDProgressCallback() {};
  
  void Update(const double overallProgress, const double stageProgress, const double operationProgress, const char * const stage,  const char * const operation);
};

#endif /* ConvexDecompTool_h */

新增 Cpp File ConvexDecompTool.cpp :
#include "stdafx.h"

FVHACDProgressCallback::FVHACDProgressCallback(CBFunc pCBFunc){
  tmp_cb_func =pCBFunc;
}

void FVHACDProgressCallback::Update(const double overallProgress, const double stageProgress, const double operationProgress, const char * const stage,  const char * const operation)
{
  
  //process callback info.
  if (tmp_cb_func !=nullptr){
    char buff[255];
    memset(buff, sizeof(char)*255, 0);
    int strlen =sprintf(buff, "%s", stage);
    
    tmp_cb_func(overallProgress, stageProgress, operationProgress, strlen, buff);
  }
  
};
ConvexDecompTool 的作用是接收 v-hacd 處理時的進度及狀態資料,我們會將它傳回給 Unity 並且在 v-hacd 處理時顯示出來。

新增 Header File VHACD_api.h :
#ifndef VHACD_api_h
#define VHACD_api_h

#include "stdafx.h"

extern "C" {
  typedef void (STD_CALL *CBFunc)(double overallProgress, double stageProgress, double operationProgress, int str_sz, char* str_buff);
}

#endif /* VHACD_api_h */
此處的 CBFunc 對應到 Unity C# 的 delegate,可以利用此 Callback Function 將處理進度及資訊傳給 Unity。

新增 Cpp File VHACD_api.cpp :
#include "stdafx.h"

using namespace VHACD;

static void InitParameters(IVHACD::Parameters &VHACD_Params, unsigned int InHullCount, unsigned int InMaxHullVerts, unsigned int InResolution)
{
  VHACD_Params.m_resolution = InResolution; 
  VHACD_Params.m_maxNumVerticesPerCH = InMaxHullVerts; 
  VHACD_Params.m_concavity = 0;
  VHACD_Params.m_maxConvexHulls = InHullCount;
  VHACD_Params.m_oclAcceleration = false;
  VHACD_Params.m_minVolumePerCH = 0.003f;
  VHACD_Params.m_projectHullVertices = true;
};

struct DecomposedHullData{
  int pts;
  int tris;
  double* pts_arr;
  int* tris_arr;
};

DecomposedHullData* dhd =nullptr;
int num_dhd_sz =0;

extern "C" {
  MODULE_API void DisposeHullData(){
    if (dhd !=nullptr){
      for (int i=0;i<num_dhd_sz;++i){
        delete[] dhd[i].pts_arr;
        delete[] dhd[i].tris_arr;
      }
    }
    delete[] dhd;
    dhd =nullptr;
    num_dhd_sz =0;
  }

  MODULE_API int GetHullSize(){
    return num_dhd_sz;
  }

  MODULE_API bool GetTrianglesData(int idx, int* tri_num, int** tri_data){
    if (idx< num_dhd_sz && dhd !=nullptr){
      *tri_num =dhd[idx].tris;
      *tri_data =dhd[idx].tris_arr;
      return true;
    }
    return false;
  }

  MODULE_API bool GetVerticesData(int idx, int* vertices_num, double** vertices_data){
    if (idx< num_dhd_sz && dhd !=nullptr){
      *vertices_num =dhd[idx].pts;
      *vertices_data =dhd[idx].pts_arr;
      return true;
    }
    return false;
  }

  //hull_count range (2~64)
  //max_tri_count range (6~32)
  //resolution range (10000 ~ 1000000)
  MODULE_API bool DecomposeMeshToHulls(
    unsigned int hull_count, 
    unsigned int max_tri_count, 
    unsigned int res, 
    const float* const Verts, 
    const unsigned int NumVerts, 
    const unsigned int* const Tris, 
    const unsigned int NumTris, 
    CBFunc pcb){
    IVHACD* InterfaceVHACD =CreateVHACD();
    
    IVHACD::Parameters VHACD_Params;
    InitParameters(VHACD_Params, hull_count, max_tri_count, res);
    
    FVHACDProgressCallback* VHACD_Callback =new FVHACDProgressCallback(pcb);
    VHACD_Params.m_callback =VHACD_Callback;
    
    bool bSuccess =InterfaceVHACD->Compute(Verts, NumVerts, Tris, NumTris, VHACD_Params);

    if (bSuccess){
      //iterate over each result hull
      unsigned int NumHulls =InterfaceVHACD->GetNConvexHulls();

      DisposeHullData();
      dhd =new DecomposedHullData[NumHulls];

      for (int HullIdx=0; HullIdx<NumHulls; HullIdx++){
        IVHACD::ConvexHull Hull;
        InterfaceVHACD->GetConvexHull(HullIdx, Hull);

        dhd[HullIdx].pts =(int)Hull.m_nPoints;
        dhd[HullIdx].tris =(int)Hull.m_nTriangles;
        dhd[HullIdx].pts_arr =new double[3*Hull.m_nPoints];
        dhd[HullIdx].tris_arr =new int[3*Hull.m_nTriangles];
        memcpy(dhd[HullIdx].pts_arr, Hull.m_points, sizeof(double)*3*Hull.m_nPoints);
        memcpy(dhd[HullIdx].tris_arr, Hull.m_triangles, sizeof(unsigned int)*3*Hull.m_nTriangles);

      }

      num_dhd_sz =NumHulls;
    }else{
      DisposeHullData();
    }
    
    InterfaceVHACD->Clean();
    InterfaceVHACD->Release();
    
    delete VHACD_Callback;
    VHACD_Callback =nullptr;
    
    return bSuccess;
  }
}
其中 GetHullSize, GetTrianglesData, GetVerticesData 的作用是取得處理完成的資料,DecomposeMeshToHulls 的作用是初始化並呼叫執行 V-HACD。
將上述檔案加入至專案然後執行編譯即可取得 Unity 可呼叫之 bundle 檔案。

2. V-HACD plugin for Unity Windows
2.1 VisualStudio 編譯 vhacd
這裡取得 V-HACD 程式原始碼並確認已有安裝 python 及 cmake 之後,修改在 install 資料夾裡的 run.py 檔案,將第 11 行改成 :
cmd = "cmake -A x64 " + arg + " ../" + src_dir
-A x64 讓 cmake 產生 x64 平台的專案。

cmake 在安裝時選擇 Add CMake to the system PATH for all users 或者 Add CMake to the system PATH for the current user 之後在執行 run.py 時比較不會有問題。

執行 run.py (它會在 build/win32 底下建立 VisualStudio Solution)
py run.py --cmake

執行成功之後在 build/win32 資料夾開啟專案檔案 VHACD.sln,然後編譯出 Library (記得把 vhacd 和 testVHACD 的 C/C++ -> Code Generation -> Runtime Library 設定成 Multi-threaded),編譯完成後會看到靜態連結檔 build/win32/VHACD_Lib/Release/vhacd.lib。

Runtime Library 選擇 Multi-threaded 以避免之後 Unity 載入 DLL 時發生無法載入問題。

2.2 建立 DLL 專案 (Visual C++ -> Windows Desktop -> Dynamic-Link Library (DLL) )
將專案目前的 solution platform 改成 x64。在 C/C++ -> General -> Additional Include Directories 新增路徑指向 V-HACD 資料夾 src\VHACD_Lib\public。

新增路徑指向 V-HACD public header 資料夾 (圖中的路徑要換成你電腦中的路徑)

C/C++ -> Code Generation -> Runtime Library 改成 Multi-threaded。
Linker -> General -> Additional Library Directories 新增路徑指向 V-HACD 資料夾 build\win32\VHACD_Lib\Release。

新增路徑指向 V-HACD lib 檔案資料夾 (圖中的路徑要換成你電腦中的路徑)

Linker -> Input -> Additional Dependencies 新增 lib 名稱 vhacd.lib (此檔名是 2.1 編譯產生的檔案)

新增 vhacd.lib 檔名

將 1.2 列出的檔案 stdafx.h,targetver.h,VHACD_api.h,ConvexDecompTool.h,ConvexDecompTool.cpp,WHACD_api.cpp,加入至專案中,並將檔案 ConvexDecompTool.cpp 的 sprintf 改成 sprintf_s最後執行編譯即可獲得 Unity 可呼叫使用的 DLL 檔案

3. Unity C#
3.1 設定 bundle/dll 檔案
把 VHACD_Bundle.bundle 或 VHACD_DLL.dll 複製到 Unity Assets 資料夾 (bundle 及 dll 檔案是 1.2 及 2.2 編譯產生的檔案,可能因為專案命名不同而產生不同檔名),並且統一命名 VHACD_Lib,其 platform 設定參考如圖


VHACD_Lib.bundle platform 設定

VHACD_Lib.dll platform 設定

3.2 VHACD_Lib.cs 檔案
底下是 VHACD_Lib 在 Unity 裡簡單的使用範例 :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using UnityEditor;
using System.Runtime.InteropServices;

public class VHACD_Lib : MonoBehaviour {

  [DllImport("VHACD_Lib")]
  private static extern void DisposeHullData();

  [DllImport("VHACD_Lib")]
  private static extern bool DecomposeMeshToHulls(
    uint hull_count, 
    uint max_tri_count, 
    uint resolustion, 
    float[] verts, 
    uint num_verts, 
    int[] tris, 
    uint num_tris,
    VHACD_CBFunc_Handler pHandler
    );

  [DllImport("VHACD_Lib")]
  private static extern int GetHullSize();

  [DllImport("VHACD_Lib")]
  private static extern bool GetTrianglesData(int idx, ref int tri_num, ref System.IntPtr tri_data);

  [DllImport("VHACD_Lib")]
  private static extern bool GetVerticesData(int idx, ref int vert_num, ref System.IntPtr vert_data);

  delegate void VHACD_CBFunc_Handler(double overallProgress, double stageProgress, double operationProgress, int str_sz, System.IntPtr str_buff);
  public static void VHACD_CB_Handler(double overallProgress, double stageProgress, double operationProgress, int str_sz, System.IntPtr str_buff){
    string data =Marshal.PtrToStringAnsi(str_buff);
    EditorUtility.DisplayProgressBar("V-HACD", data+" ("+(int)stageProgress+"%)", (float)overallProgress/100f);
  }

  MeshCollider[] _convex_mesh_colliders =null;
  Mesh[] _convex_mesh =null;

  public uint hulls =8;
  public uint tris_per_hull =16;
  public uint resolution =100000;

  float GetMax(Vector3 v){
    return Mathf.Max(Mathf.Max(v.x, v.y), v.z);
  }

  float GetMin(Vector3 v){
    return Mathf.Min(Mathf.Min(v.x, v.y), v.z);
  }
  
  // Use this for initialization
  void Start () {
    Debug.Log((int)(Mathf.Pow(8f, 1f/3f)+0.5f));
    Mesh m =GetComponent<MeshFilter>().sharedMesh;
    m.RecalculateBounds();
    if (GetMax(m.bounds.size)<1f || GetMin(m.bounds.size)<0.1f){
      EditorUtility.DisplayDialog("V-HACD", "Build Failed.\nThe largest dimension length is less then 1 unit, or the smallest length is less then 0.1.", "Ok");
      return;
    }

    float[] verts =new float[m.vertexCount*3];
    for (int i=0;i<m.vertexCount;++i){
      verts[i*3+0] =m.vertices[i].x;
      verts[i*3+1] =m.vertices[i].y;
      verts[i*3+2] =m.vertices[i].z;
    }

    GameObject parent =new GameObject();
    parent.name ="compound_collider";
    parent.transform.SetParent(gameObject.transform, false);

    int result_hull_num =0;
    bool success =DecomposeMeshToHulls(hulls, tris_per_hull, resolution, verts, (uint)m.vertexCount, m.triangles, (uint)(m.triangles.Length/3), VHACD_CB_Handler);
    if (success){
      result_hull_num =GetHullSize();
      _convex_mesh_colliders =new MeshCollider[result_hull_num];
      _convex_mesh =new Mesh[result_hull_num];
      for (int i=0;i<result_hull_num;++i){

        int vert_ct =0;
        System.IntPtr vert_arr_ptr =System.IntPtr.Zero;
        GetVerticesData(i, ref vert_ct, ref vert_arr_ptr);
        double[] vert_arr =new double[vert_ct*3];
        Marshal.Copy(vert_arr_ptr, vert_arr, 0, vert_ct*3);

        int tri_ct =0;
        System.IntPtr tri_arr_ptr =System.IntPtr.Zero;
        GetTrianglesData(i, ref tri_ct, ref tri_arr_ptr);
        int[] tri_arr =new int[tri_ct*3];
        Marshal.Copy(tri_arr_ptr, tri_arr, 0, tri_ct*3);
        
        List<Vector3> vert_list =new List<Vector3>();
      
        for (int j=0;j<vert_arr.Length/3;++j){
          vert_list.Add(new Vector3(
            (float)vert_arr[j*3+0],
            (float)vert_arr[j*3+1],
            (float)vert_arr[j*3+2]
          ));
        }

        Mesh tmp_m =new Mesh();
        tmp_m.name =GetComponent<MeshFilter>().sharedMesh.name+"_"+i;
        tmp_m.SetVertices(vert_list);
        tmp_m.SetTriangles(tri_arr, 0);
        tmp_m.RecalculateNormals();
        
        MeshCollider mc =parent.AddComponent<MeshCollider>();
        mc.sharedMesh =tmp_m;
        mc.convex =true;
        // mc.sharedMaterial =shared_physic_material;
        // mc.isTrigger =isTrigger;

        _convex_mesh_colliders[i] =mc;
        _convex_mesh[i] =tmp_m;
      }

    }else{
      //...

    }
    DisposeHullData();
    EditorUtility.ClearProgressBar();
  }
  
  // Update is called once per frame
  void Update () {
    
  }
}
VHACD_Lib.cs 在程式啟動時從 MeshFilter 取得 Mesh 資料,接著呼叫 DecomposeMeshToHulls 函數將這些資料轉成數個 Convex MeshCollider。其中 hulls 表示最多可產生的 Convex Mesh,tris_per_hull 表示每個 Convex Mesh 最多三角面數,resolution 表示 VHACD 在執行時解析原始 Mesh 使用的方格數量。此外要注意的是原始的 Mesh 有數值大小的限制,若是太小則在 Import 時,將 Model Meshes Scale Factor 設定更大的倍率。

以 bunny 作為例子,使用 Unity 內建 MeshCollider 產生的 ConvexCollider 與 V-HACD 計算產生的 Compound Collider 比較如下圖 :

內建工具產生的 Convex Collider

V-HACD 計算產生的 Colliders

將 V-HACD 計算產生的 Colliders 視覺化 (各別著色)


ref 1: https://en.wikipedia.org/wiki/Hyperplane_separation_theorem
ref 2: https://en.wikipedia.org/wiki/Gilbert–Johnson–Keerthi_distance_algorithm
ref 3: https://github.com/kmammou/v-hacd
ref 4: https://forums.unrealengine.com/unreal-engine/announcements-and-releases/36410-engine-features-preview-4-02-2015

2015-12-25

Facebook BUCK with multidex

程式開發後期通常會遇見 Building Apps with over 65k methods 的問題,底下是利用 BUCK 執行編譯時遇到此問題的解決方法 (啟用 multidex 功能之設定檔的筆記) 。

BUCK :
...

# setup multidex jar
prebuilt_jar(
  name = 'support_multidex',
  binary_jar = 'com.android.support/multidex/1.0.0/jars/classes.jar',
  visibility = ['PUBLIC']
)

...

# include multidex jar
android_library(
  name = 'lib',
  srcs = glob(['src/**/*.java',...]),
  deps = [
    ':support_multidex',
    ...
  ],
  visibility = ['PUBLIC'],
)

...

# build apk with multidex
android_binary(
  name = 'release',
  manifest = 'AndroidManifest.xml',
  use_split_dex = True,
  deps = [':lib'],
  package_type='release',
  ...
)

...


AndroidManifest (在 application tag 設定 android:name 屬性) :
<?xml version="1.0" encoding="utf-8"?>
<manifest 
  ... >
  <application 
    android:name="com.mypackage.name.App"
    ...>

    ...

  </application>
</manifest>
新增 App.java :
package com.mypackage.name;
public class App extends android.support.multidex.MultiDexApplication{
}

2014-10-19

Android DexClassLoader 續

之前介紹的方法讓我們的程式可以在執行時期動態載入其它的程式碼,後來發現 Facebook 的 Buck 當中也有類似的功能 ( Exopackage ),但它實作的方法和之前介紹的方法不同,底下敘述使用 Buck 的方法,並套用在前一篇範例上的流程 :

1. 製作 dex 檔案 :
與前一篇介紹的方法相同

2. 程式執行時期下載 dex 檔案  :
建立一個 com.example.dexclassloader 之 Android Application Project 並將主要 Activity (MainActivity.java ) 替換如下 :
package com.example.dexclassloader;
 
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.view.Menu;
 
public class MainActivity extends Activity {
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
   
  Thread works =new Thread(){
   @Override
   public void run(){
    //download and save the jar
    downloadDexFile("http://localhost/PayloadActivity.dex.jar", "PayloadActivity.dex.jar");
    
    //install dex jar
    List<File> dexJars =new ArrayList<File>();
    File libFile =new File(getFilesDir()+"/PayloadActivity.dex.jar");
    dexJars.add(libFile);
    SystemClassLoaderAdder.installDexJars(getClassLoader(), getDir("outdex", Context.MODE_PRIVATE), dexJars);
     
    //load the jar
    loadAndInvokeMethod();
   }
  };
  works.start();
 }
 
 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  // Inflate the menu; this adds items to the action bar if it is present.
  getMenuInflater().inflate(R.menu.main, menu);
  return true;
 }
  
 private void downloadDexFile(String url, String filename){
     try {
 
         URL u = new URL(url);
         HttpURLConnection c = (HttpURLConnection) u.openConnection();
         c.setRequestMethod("GET");
         c.setDoOutput(true);
         c.connect();
          
         FileOutputStream f =openFileOutput(filename, MODE_PRIVATE);
         InputStream in = c.getInputStream();
 
         byte[] buffer = new byte[1024];
         int len1 = 0;
          
         while ((len1 = in.read(buffer)) > 0) {
             f.write(buffer, 0, len1);
         }
         f.close();
          
     } catch (Exception e) {
         //error dialog
         Log.d("mainactivity", "error -"+e.toString());
 
     }
 }
  
 private void loadAndInvokeMethod(){
  try{  
         //LOAD CLASS
         Class<?> payloadClass =this.getClassLoader().loadClass("com.example.PayloadActivity");
          
         //LOAD METHOD
         Method starterFunc =payloadClass.getMethod("starter", Activity.class);
          
         //INVOKE METHOD
         starterFunc.invoke(null, this);
          
  }catch(Exception e){
    
  }
 }
 
}
觀察得知原本 overrideClassLoader 函數的部分改為呼叫 SystemClassLoaderAdder.installDexJars 函數,此外呼叫載入的 dex 裡的類別函數方法也變得簡單。前一篇的作法是建立一個自己的 ClassLoader 去替換原本的 ClassLoader,這篇的作法是直接去修改現有的 ClassLoader 因此也比上一篇的方法少了 MyClassLoader.java 檔案。

(ps. 程式碼裡呼叫的 installDexJars 函數位於檔案 SystemClassLoaderAdder.java,可以在這裡取得)

3. MyApplication.java

完成步驟1, 2 之後程式就可以運作,但不算真正的完成。有時候啟動程式仍然會得到 java.lang.ClassNotFoundException 的錯誤訊息。

為何說是 "有時候" ,因為這跟使用者操作方式的流程有關,假若現在使用者在程式裡啟動了 Acitivty ,而這個 Activity 是屬於 dex 檔案後來載入的程式碼,啟動後在 Activity 的介面操作到一半切換到其它的 App,此時有可能因為系統記憶體不足,釋放原本在背景執行的 App 的記憶體,之後使用者欲切換到原來的 App,系統會自動重新啟動 App 並呼叫上次啟動的 Activity,此時的 ClassLoader 是系統建立的 ClassLoader 不是我們更改過的 ClassLoader 因此找不到 dex 檔案而發生 Class Not Found Exception。

幸好在系統啟動 Activity 之前我們還有機會讓系統執行我們的程式。那就是 Application 的 attachBaseContext 函數。

底下是新增加的程式 MyApplication.java :
package com.example.dexclassloader;

import android.app.Application;
import android.content.Context;
import android.content.res.Configuration;
import java.util.ArrayList;
import java.util.List;
import java.io.File;

public class MyApplication extends Application
{
  public void installPayloadDexFile(){

    String libPath =getFilesDir()+"/PayloadActivity.dex.jar";
    File libFile =new File(libPath);

    if (libFile.exists()==false)
      return;

    File optimizedDexOutputPath = getDir("outdex", Context.MODE_PRIVATE);
    List<File> dexJars = new ArrayList<File>();
    dexJars.add(libFile);
    SystemClassLoaderAdder.installDexJars(getClassLoader(), optimizedDexOutputPath, dexJars);

  }

  @Override
  public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
  }

  @Override
  protected final void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    installPayloadDexFile();
    
  }

  @Override
  public final void onCreate() {
    super.onCreate();
    installPayloadDexFile();
  }

  @Override
  public final void onTerminate() {
    super.onTerminate();

  }

  @Override
  public final void onLowMemory() {
    super.onLowMemory();

  }

  @Override
  public final void onTrimMemory(int level) {
    super.onTrimMemory(level);

  }

}

觀察可知程式在啟動 Activity 之前首先呼叫 MyApplication.attachBaseContext 然後檢查是否存在檔案 PayloadActivity.dex.jar ,若有則載入 dex 並修改目前的 ClassLoader。

4. AndroidManifest.xml

同前一篇介紹的,除了要預先定義 Activity 到 AndroidManifest.xml 之外,若要讓 Class MyApplication 生效,還須定義 MyApplication 在 application 標籤內 :
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.dexclassloader"
    android:versionCode="1"
    android:versionName="1.0" >
 
    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="18" />
 
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" 
        android:name="com.example.dexclassloader.MyApplication" >
        <activity
            android:name="com.example.dexclassloader.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
 
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name="com.example.PayloadActivity" android:label="@string/app_name" />
    </application>
     
  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
 
</manifest>

ref:http://facebook.github.io/buck/article/exopackage.html
ref:http://developer.android.com/reference/android/app/Application.html
ref:http://stackoverflow.com/questions/9873669/how-do-i-catch-content-provider-initialize


2014-10-16

Android : 建立並安裝 自我簽署憑証 ( Self-Signed Certificate authority )

在安全性的需求之下我們使用 https 代替 http 連線,而 server 端開啟 https 服務需要設定相關的憑証,這些憑証要向具有公信力的機購購買,但在開發時期我們可以自己產生,根據這裡的資料,製作 X.509 version 3 的 self-signed CA certificates 可以避免大多數行動裝置無法安裝的問題,底下步驟是敘述製作以及在 Android 上安裝 self-signed CA certificates 的流程。

A. 製作 self-signed CA certificates

1. 建立一個檔案,例如 openssl.cnf,其內容如下 (openssl.cnf 檔案的重點在於 basicConstraints = CA:true 以及 alt_names) :
[req]
  distinguished_name = req_distinguished_name
  req_extensions = v3_req

[req_distinguished_name]
  countryName = Country Name (2 letter code)
  countryName_default = US
  localityName = Locality Name (eg, city)
  organizationalUnitName = Organizational Unit Name (eg, section)
  commonName = Common Name (eg, YOUR name)
  commonName_max = 64
  emailAddress = Email Address
  emailAddress_max = 40

[v3_req] 
  basicConstraints = CA:true
  keyUsage = keyEncipherment, dataEncipherment
  extendedKeyUsage = serverAuth
  subjectAltName = @alt_names

[alt_names]
  DNS.1   = *.mydomain1.com.tw
  DNS.2   = *.mydomain2.com.tw
  IP.1    = 192.168.11.1
其中 alt_names 裡的 DNS 以及 IP 就輸入這個憑証欲綁定的 FQDN 或 IP。

2. 使用底下的命令建立 key 跟 certificate :
openssl req -x509 -nodes -days 365 -newkey rsa:4096 -keyout ./privatekey.key -out ./certificate.crt -extensions v3_req -config ./openssl.cnf
建立的 privatekey.key 跟 certificate.crt 是給開啟 https 服務的伺服器使用。

B. 將 certificate.crt 轉換成 DER 格式 (供 Android 安裝使用) 

openssl x509 -in ./certificate.crt -outform der -out ./certificate_der.crt
可將 certificate_der.crt 擺在網路上,讓 Android 裝置的瀏覽器將它下載到手機中,直接點擊下載的檔案就可以開始安裝憑証。
(或者也可以擺在自己的 http server 上,將 crt 的 MIME type 設定成 application/x-x509-ca-cert 讓瀏覽器直接安裝憑証,或是將 crt 的 MIME type 設定成 application/octet-stream 讓瀏覽器把它視為檔案下載。)

ref:https://developer.android.com/training/articles/security-ssl.html
ref:http://www-01.ibm.com/support/knowledgecenter/#!/SSZH4A_6.0.0/com.ibm.worklight.help.doc/admin/c_ssl_config.html
ref:http://www.xinotes.net/notes/note/1094/
ref:http://stackoverflow.com/questions/7229361/how-to-force-mime-type-of-file-download
ref:http://davdroid.bitfire.at/faq/entry/importing-a-certificate
ref:http://www.akadia.com/services/ssh_test_certificate.html

2014-06-25

Android webview 處理 Line it intent

最近有個需求是要在 webview 上處理 LINE it 按鈕的連結,這裡做個筆記 :

1. 使用 shouldOverrideUrlLoading 攔截轉跳的 url 連結。
2. 攔截 intent:// 開頭的 url
3. 此時應該會有一個 intent 看起來像這樣 :

intent://msg/text/DUMMY_MESSAGE#Intent;scheme=line;action=android.intent.action.VIEW;category=android.intent.category.BROWSABLE;package=jp.naver.line.android;end

4. 兩行搞定

Intent iuri =Intent.parseUri(url, 0);
startActivity(iuri);

2014-03-10

Android DexClassLoader


本文述敘 DexClassLoader 之使用方法 ,概略內容如下 :

範例程式將在執行時下載 dex 檔案並於載入後呼叫 dex 提供的函數來開啟新的 Activity,此 Activity 將會利用 Toast 顯示一段啟動成功的訊息。

1. 製作 dex 檔案 :
將底下程式碼另存成檔案 PayloadActivity.java :
package com.example;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast;

public class PayloadActivity extends Activity {
  public static void starter(Activity parentActivity){
    Intent tmp_intent =new Intent(parentActivity, PayloadActivity.class);
    parentActivity.startActivity(tmp_intent);
  }
  
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Toast.makeText(this, "Instantiate PayloadAcitivty Successful", Toast.LENGTH_LONG).show();
  }
}
PayloadActivity class 提供靜態函數 starter,此函數被呼叫之後會啟動 Payload Activity。

把 PayloadActivity.java 編譯成 dex 檔案 (Mac OSX 環境)  :
首先將 java 檔案編譯成 class 檔案 (其中 path_to_sdk 指向 android sdk 之路徑):
javac ./PayloadActivity.java -cp /path_to_sdk/adt-bundle-mac-x86_64-20130917/sdk/platforms/android-10/android.jar -d .
將 class 檔案壓縮成 PayloadActivity.jar 檔案 :
jar cvfM ./PayloadActivity.jar com/
刪除執行編譯指令時產生的 com 資料夾 :
rm -rf ./com
將 jar 檔案轉成 dex 檔案 :
/path_to_sdk/adt-bundle-mac-x86_64-20130917/sdk/build-tools/android-4.3/dx --dex --output=PayloadActivity.dex.jar ./PayloadActivity.jar

將完成的 PayloadActivity.dex.jar 擺在網路空間。 ( 此範例假設可利用 url http://localhost/PayloadActivity.dex.jar 取得檔案 )

2. 程式執行時期下載 dex 檔案 :
 建立一個 com.example.dexclassloader 之 Android Application Project

主要啟動的 Activity 設定為 MainActivity

在 Eclipse 裡新創 Android Application Project,將主要 Activity ( MainActivity.java ) 替換如下 :
package com.example.dexclassloader;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.URL;

import dalvik.system.DexClassLoader;

import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.view.Menu;

public class MainActivity extends Activity {
 
 public static ClassLoader payloadClassLoader =null;
 private Smith<ClassLoader> sClassLoader =null;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  Thread works =new Thread(){
   @Override
   public void run(){
    //download and save the jar
    downloadDexFile("http://localhost/PayloadActivity.dex.jar", "PayloadActivity.dex.jar");
    
    //override class loader
    overrideClassLoader();
    
    //load the jar
    loadAndInvokeMethod();
   }
  };
  works.start();
 }

 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  // Inflate the menu; this adds items to the action bar if it is present.
  getMenuInflater().inflate(R.menu.main, menu);
  return true;
 }
 
 private void downloadDexFile(String url, String filename){
     try {

         URL u = new URL(url);
         HttpURLConnection c = (HttpURLConnection) u.openConnection();
         c.setRequestMethod("GET");
         c.setDoOutput(true);
         c.connect();
         
         FileOutputStream f =openFileOutput(filename, MODE_PRIVATE);
         InputStream in = c.getInputStream();

         byte[] buffer = new byte[1024];
         int len1 = 0;
         
         while ((len1 = in.read(buffer)) > 0) {
             f.write(buffer, 0, len1);
         }
         f.close();
         
     } catch (Exception e) {
         //error dialog
         Log.d("mainactivity", "error -"+e.toString());

     }
 }
 
 private void overrideClassLoader(){
  try{
         Context mBase =new Smith<Context>(this, "mBase").get();
         Object mPackageInfo =new Smith<Object>(mBase, "mPackageInfo").get();
         sClassLoader =new Smith<ClassLoader>(mPackageInfo, "mClassLoader");
 
         ClassLoader mClassLoader =sClassLoader.get();
 
         MyClassLoader cl =new MyClassLoader(mClassLoader);
         sClassLoader.set(cl);
  }catch(Exception e){
   
  }
 }
 
 private void loadAndInvokeMethod(){
  try{
         String libPath =getFilesDir()+"/PayloadActivity.dex.jar";
         File optimizedDexOutputPath = getDir("outdex", Context.MODE_PRIVATE);
         DexClassLoader dcl=new DexClassLoader(libPath, optimizedDexOutputPath.getAbsolutePath(), null, getClass().getClassLoader());
         payloadClassLoader =dcl;
 
         //LOAD CLASS
         Class<Object> payloadClass =(Class<Object>)dcl.loadClass("com.example.PayloadActivity");
         
         //LOAD METHOD
         Method starterFunc =payloadClass.getMethod("starter", Activity.class);
         
         //INVOKE METHOD
         starterFunc.invoke(null, this);
         
  }catch(Exception e){
   
  }
 }

}

觀察 MainActivity 的 onCreate 函數可知程式啟動時將從 url ( http://localhost/Payload.dex.jar ) 下載檔案 (downloadDexFile 函數),並將檔案暫存在裝置記憶體裡。

完成後使用 DexClassLoader 載入 dex 檔案並執行檔案裡提供的 starter 函數 (loadAndInvokeMethod 函數)。
下一節解說函數 overrideClassLoader 的功能。

3. 從 dex 檔案啟動 Activity (修改 ClassLoader) :

藉由 DexClassLoader 載入的程式在啟動 Activity 時 (Activity Class) 不會搜尋 DexClassLoader 所載入的 dex 檔案,因此會出現找不到 Class 的 Exception。為解決這個問題,透過修改 ClassLoader ,我們可以讓程式先搜尋 dex 檔案,若找不到目標則再搜尋預設的 jar 檔案。

底下是覆寫 loadClass 函數部分的程式碼 :

package com.example.dexclassloader;

public class MyClassLoader extends ClassLoader {
 public MyClassLoader(ClassLoader parent){
  super(parent);
 }
 
 @Override
 public Class<?> loadClass(String className) throws ClassNotFoundException{
  if (MainActivity.payloadClassLoader !=null){
   try{
                Class<?> c = MainActivity.payloadClassLoader.loadClass(className);
                if (c != null){
                  return c;
                }
   } catch (ClassNotFoundException e) {
   }
  }
  
  return super.loadClass(className);
 }
}

此外 overrideClassLoader 函數中的 Smith Class 可以幫助我們取得 ClassLoader,其程式碼可以參考這裡

4. AndroidManifest.xml

雖然可以透過 dex 檔案更新並且啟動 activity,但注意的是此 activity 在安裝 apk 之前,必須先定義在 apk 的 AndroidManifest.xml 檔案裡,AndroidManifest.xml 檔案內容如下 :
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.dexclassloader"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="18" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.dexclassloader.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name="com.example.PayloadActivity" android:label="@string/app_name" />
    </application>
    
  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

</manifest

ref:http://stackoverflow.com/questions/6857807/is-it-possible-to-dynamically-load-a-library-at-runtime-from-an-android-applicat
ref:http://stackoverflow.com/questions/2519760/android-how-to-use-dexclassloader-to-dynamically-replace-an-activity-or-service


2014-01-17

install build-essential


輸入底下指令即可 :
sudo apt-get install build-essential

底下是在沒有網路情況下的安裝步驟 :
1. 取得 deb 檔案 :
sudo apt-get -qq --print-uris install build-essential linux-headers-$(uname -r) | cut -d\' -f 2 > urls.txt

2. 將 urls.txt 檔案複製到有網路的裝置,輸入底下指令取得所有 deb 檔案 :
wget < urls.txt

3. 將所有 deb 檔案複製到原裝置的 apt-get 暫存資料夾 (假如是利用USB隨身碟) :
sudo cp /media/USERNAME/DRIVER_NAME/* /var/cache/apt/archives/

4. 安裝 build-essential :
sudo apt-get install build-essential linux-headers-$(uname -r)

ref:
http://askubuntu.com/questions/334136/how-do-i-install-build-essential-without-an-internet-connection

2013-12-28

Unity 3D + Local Notification for Android

在 Android 底下使用 Notification Service 配合 Alarm Service 即可使系統在指定時間產生通知訊息。底下敘述如何使用 Android 的 Notification 並且將其編譯成 Plugin 與 Unity 整合。

Plugin 主要只有一個檔案 ( AlarmReceiver.java ) :
package com.macaronics.notification;

import java.util.Calendar;

import android.app.Activity;
import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.util.Log;
import com.unity3d.player.UnityPlayer;

public class AlarmReceiver extends BroadcastReceiver {
    public static void startAlarm(String name, String title, String label, int secondsFromNow){
        Activity act =UnityPlayer.currentActivity;
        Log.i("Unity", "startAlarm...");
      
        Calendar c = Calendar.getInstance();
        c.add(Calendar.SECOND, secondsFromNow);
        long alarmTime = c.getTimeInMillis();
        Log.i("Unity", "alarm time +"+secondsFromNow);
        
        // Schedule the alarm!
        AlarmManager am = (AlarmManager)act.getSystemService(Context.ALARM_SERVICE);
        Intent ii =new Intent(act, AlarmReceiver.class);
        ii.putExtra("name", name);
        ii.putExtra("title", title);
        ii.putExtra("label", label);
        am.set(AlarmManager.RTC_WAKEUP, alarmTime, PendingIntent.getBroadcast(act, 0, ii, 0));
    }
    
    //<receiver android:process=":remote_notification" android:name="AlarmReceiver"></receiver>
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d("Unity", "Alarm Recieved!");
      
        NotificationManager mNM = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
        
        Bundle bb =intent.getExtras();

        Class<?> cc = null;
        try {
          cc = context.getClassLoader().loadClass("com.unity3d.player.UnityPlayerProxyActivity");
        } catch (ClassNotFoundException e1) {
          e1.printStackTrace();
          return;
        }
            
        final PackageManager pm=context.getPackageManager();
        ApplicationInfo applicationInfo = null;
        try {
          applicationInfo = pm.getApplicationInfo(context.getPackageName(),PackageManager.GET_META_DATA);
        } catch (NameNotFoundException e) {
          e.printStackTrace();
          return;
        }
        final int appIconResId=applicationInfo.icon;
        Notification notification = new Notification(appIconResId, (String)bb.get("name"), System.currentTimeMillis());
        
        int id =(int)(Math.random()*10000.0f)+1;
        PendingIntent contentIntent = PendingIntent.getActivity(context, id, new Intent(context, cc), 0);
        notification.setLatestEventInfo(context, (String)bb.get("title"), (String)bb.get("label"), contentIntent);

        Log.i("Unity", "notify("+id+") with "+(String)bb.get("title")+", "+(String)bb.get("label"));
        mNM.notify(id, notification);
    }
}
其中函數 startAlarm 主要負責在 Alarm Service 設定 Alarm 事件的啟動時間,接收 Alarm 事件的對象 (com.macaronics.notification.AlarmReceiver ) ,傳遞的資料 (name, title, label),當 Alarm 事件呼叫啟動 onReceive 函數之後,onReceive 則是依照傳遞過來的資料利用 Notification Service 來產生 Notification。

編譯 AlarmReceiver.java (OSX 環境) :
#!/bin/sh

ANDROID_JAR=/Users/macaronics/Desktop/applications/adt-bundle-mac-x86_64-20130917/sdk/platforms/android-13/android.jar
UNITY_JAR=/Applications/Unity4.2.1/Unity.app/Contents/PlaybackEngines/AndroidPlayer/bin/classes.jar

javac ./*.java -cp $ANDROID_JAR:$UNITY_JAR -d .
jar cvfM ../AlarmReceiver.jar com/
rm -rf ./com

其中 ANDROID_JAR 是 Android SDK 底下的 android.jar 路徑,UNITY_JAR 是 Unity 底下 classes.jar 的路徑,javac 指令將目前資料夾底下所有的 java 檔案 compile 成 class 檔案並置於相對應於 package name 的資料夾路徑底下,jar 指令則是將資料夾打包成 jar 檔案 (值得注意的是 package name 與資料夾路徑必須要一致,例如 package name 為 com.macaronics.notification,則其 class 檔案須置於 ./com/macaronics/notification/ 底下。 )

設定 AndroidManifest.xml,在 AndroidManifest.xml 的 application 底下新增 :
<receiver android:process=":remote" android:name="com.macaronics.notification.AlarmReceiver"></receiver>
底下為 AndroidManifest.xml 完整內容 :
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="preferExternal" android:theme="@android:style/Theme.NoTitleBar" android:versionName="1.0" android:versionCode="10">
  <supports-screens android:smallScreens="true" android:normalScreens="true" android:largeScreens="true" android:xlargeScreens="true" android:anyDensity="true" />
  <application android:icon="@drawable/app_icon" android:label="@string/app_name" android:debuggable="false">  
    <receiver android:process=":remote" android:name="com.macaronics.notification.AlarmReceiver"></receiver>
    <activity android:name="com.unity3d.player.UnityPlayerProxyActivity" android:label="@string/app_name" android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen" >
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>
    <activity android:name="com.unity3d.player.UnityPlayerActivity" android:label="@string/app_name" android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen" >
    </activity>
    <activity android:name="com.unity3d.player.UnityPlayerNativeActivity" android:label="@string/app_name" android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen" >
      <meta-data android:name="android.app.lib_name" android:value="unity" />
      <meta-data android:name="unityplayer.ForwardNativeEventsToDalvik" android:value="true" />
    </activity>
  </application>
  <uses-feature android:glEsVersion="0x00020000" />

  <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="17" />

</manifest>
將編譯完成的 AlarmReceiver.jar 以及 AndroidManifest.xml 置於 Unity 專案資料夾的 Assets/Plugins/Android/ 底下。

建立一個 C# Script 測試結果,其內容如下 :
using UnityEngine;
using System.Collections;

public class AlarmReceiver : MonoBehaviour {

  // Use this for initialization
  void Start () {

  }
 
  // Update is called once per frame
  void Update () {
 
  }

  AndroidJavaObject nativeObj =null;
  void OnGUI(){
    if (GUI.Button(new Rect(Screen.width*0.5f-90.0f, 100.0f, 180.0f, 100.0f), "Create Notification")){
      if (nativeObj ==null)
        nativeObj =new AndroidJavaObject("com.macaronics.notification.AlarmReceiver");

      nativeObj.CallStatic("startAlarm", new object[4]{"THIS IS NAME", "THIS IS TITLE", "THIS IS LABEL", 10});
    }
  }
}
當使用者按下 Create Notification 按鈕之後 Plugin 會建立一個 10 秒後顯示 Notification 的 Alarm 事件。

本範例的完整 Unity 程式碼可以在 http://github.com/phardera/unity3d_notification_android 下載。

2013-11-30

BeagleBone Black with AnTuTu Benchmark

底下敘述在 BeagleBone Black 上執行 Android AnTuTu App (4.1.1) 的流程 。

A. 製作 Android SDCard :
參考這裡提供的 Android 映象檔,只要將它放在 microSD 卡上就可以直接用了,將下載下來的壓縮檔解開,然後在該資料夾底下輸入 :
sudo./mkmmc-android.sh /dev/sdX MLO u-boot.img uImage uEnv_beagleboneblack.txt rootfs.tar.bz2
注意這行指令必須要在 Linux 環境下執行 ! 但本文要敘述在 mac osx mavericks 環境下 ( 利用 MacBook Air 3,2 內建之 SD Card Reader,但值得注意的是,目前 VirtualBox 尚不支援 USB 3.0,因此較新的 Macbook 可能不適用 ) 的製作流程 :
1. 安裝 VirtualBox,並在上面安裝 linux 作業系統,這裡使用的是 ubuntu server 13.10。
2. 安裝 VirtualBox Extension Pack。

3. 在 Settings --> Ports --> USB 底下將 Enable USB 2.0 (EHCI) Controller 打勾。
4. 新增 Empty Filter

5. 關閉 VirtualBox
5. 在 Terminal 底下輸入 :
sudo launchctl list | grep diskarbitrationd
6. 將前面顯示的數字 (如圖為 20) 代入至底下指令 :
sudo kill -SIGSTOP 20

7. 插入 SD 卡 !
8. 啟動 VirtualBox 之 Linux Virtual Machine
9. 點選底下 USB 圖示,然後選擇  Apple Internal Memory Card Reader。
若是安裝正確,在 /dev 底下會出多 sdb

10. 在 linux terminal 底下取得 這裡 提供的檔案 (可用 wget 指令),將檔案解壓縮後在該資料夾底下輸入 :
sudo./mkmmc-android.sh /dev/sdb MLO u-boot.img uImage uEnv_beagleboneblack.txt rootfs.tar.bz2

11. 恢復 mac osx sdcard reader 之運作,在 Terminal 底下輸入 :
sudo kill -SIGCONT 20

B. 將 sd card 安裝至 beaglebone black 然後按住 Boot 按鈕並插上電源後等待 android 字串出現到螢幕上為止。
C. 底下是 Android 4.2.2 畫面擷圖。

D. 系統資訊擷圖。


D. 底下是 Antutu 4.1.1 跑分結果。

E. 底下是 Antutu 4.1.1 顯示的系統資訊。



ref:http://www.ezequielaceto.com.ar/techblog/?page_id=910
ref:http://www.ezequielaceto.com.ar/techblog/?p=958
ref:http://superuser.com/questions/373463/how-to-access-an-sd-card-from-a-virtual-machine