摸摸嚕嚕

從 Hello World 開始

我今天想要使用gps 幫我定位的時候,
跳出了System.err
java.lang.SecurityException: “gps” location provider requires ACCESS_FINE_LOCATION permission.

04-20 15:34:38.461 11949 12416 W System.err: java.lang.SecurityException: "gps" location provider requires ACCESS_FINE_LOCATION permission.
04-20 15:34:38.461 11949 12416 W System.err: 	at android.os.Parcel.readException(Parcel.java:1620)
04-20 15:34:38.461 11949 12416 W System.err: 	at android.os.Parcel.readException(Parcel.java:1573)
04-20 15:34:38.461 11949 12416 W System.err: 	at android.location.ILocationManager$Stub$Proxy.getLastLocation(ILocationManager.java:717)
04-20 15:34:38.461 11949 12416 W System.err: 	at android.location.LocationManager.getLastKnownLocation(LocationManager.java:1200)
04-20 15:34:38.461 11949 12416 W System.err: 	at com.cei.httpposttowebservice.MainActivity.locationServiceInitial(MainActivity.java:126)
04-20 15:34:38.461 11949 12416 W System.err: 	at com.cei.httpposttowebservice.MainActivity.getWeather(MainActivity.java:112)
04-20 15:34:38.461 11949 12416 W System.err: 	at com.cei.httpposttowebservice.MainActivity.access$200(MainActivity.java:35)
04-20 15:34:38.461 11949 12416 W System.err: 	at com.cei.httpposttowebservice.MainActivity$4.run(MainActivity.java:100)
04-20 15:34:38.461 11949 12416 W System.err: 	at java.lang.Thread.run(Thread.java:818)

除了在AndroidManaifests.xml要加入user-permission之外, 還需要主動需求permission.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.cei.httpposttowebservice">
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

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

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

在java程式中主動提出需求 需要permission

    private static int PERMISSION_REQUEST_CODE = 1;
    //private Location mLocation;
    private static final String[] INITIAL_PERMS = {
        Manifest.permission.ACCESS_FINE_LOCATION,
        Manifest.permission.ACCESS_COARSE_LOCATION
    };
    
       ----------    
       ----------
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();

        if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) ==
                PackageManager.PERMISSION_GRANTED &&
                checkSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) ==
                        PackageManager.PERMISSION_GRANTED) {
            Toast.makeText(this, "BBBB", Toast.LENGTH_LONG).show();

        } else {
            Toast.makeText(this, "AAAAAA", Toast.LENGTH_LONG).show();
            requestPermissions(INITIAL_PERMS,PERMISSION_REQUEST_CODE);
        }
    }

不一定要在onCreate requestpermission

JSON(JavaScript Object Notation)是一種由道格拉斯·克羅克福特構想設計、輕量級的資料交換語言,以文字為基礎,且易於讓人閱讀。儘管JSON是Javascript的一個子集,但JSON是獨立於語言的文字格式,並且採用了類似於C語言家族的一些習慣。
以上採自Wikipedia.

因為我還沒寫parser, 所以這篇只有介紹該怎麼都出一個你心中那SPEC的Json array

{
  "Inputs": {
    "input1": {
      "ColumnNames": [
        "symboling",
        "normalized-losses",
        "make",
        "fuel-type",
        "aspiration",
        "num-of-doors",
        "body-style",
        "drive-wheels",
        "engine-location",
        "wheel-base",
        "length",
        "width",
        "height",
        "curb-weight",
        "engine-type",
        "num-of-cylinders",
        "engine-size",
        "fuel-system",
        "bore",
        "stroke",
        "compression-ratio",
        "horsepower",
        "peak-rpm",
        "city-mpg",
        "highway-mpg",
        "price"
      ],
      "Values": [
        [
          "0",
          "0",
          "value",
          "value",
          "value",
          "value",
          "value",
          "value",
          "value",
          "0",
          "0",
          "0",
          "0",
          "0",
          "value",
          "value",
          "0",
          "value",
          "0",
          "0",
          "0",
          "0",
          "0",
          "0",
          "0",
          "0"
        ],
        [
          "0",
          "0",
          "value",
          "value",
          "value",
          "value",
          "value",
          "value",
          "value",
          "0",
          "0",
          "0",
          "0",
          "0",
          "value",
          "value",
          "0",
          "value",
          "0",
          "0",
          "0",
          "0",
          "0",
          "0",
          "0",
          "0"
        ]
      ]
    }
  },
  "GlobalParameters": {}
}

謹記
{} 這個叫做JSONObeject
[] 這個叫做JSONArray
所以上述的案例大概就是

String[] columeName = {"symboling", "normalized-losses", "make", "fuel-type", "aspiration", "num-of-doors", "body-style", "drive-wheels", "engine-location", "wheel-base", "length", "width", "height", "curb-weight", "engine-type", "num-of-cylinders", "engine-size", "fuel-system", "bore", "stroke", "compression-ratio", "horsepower", "peak-rpm", "city-mpg", "highway-mpg", "price"};
            String[] mValues = {"2", "164", "audi", "gas", "std", "four", "sedan", "fwd", "front", "99.8", "176.6", "66.2", "54.3", "2337", "ohc", "four", "109", "mpfi", "3.19", "3.4", "10", "102", "5500", "24", "30", "13950"};
            String[] _Values = {"0", "0", "value", "value", "value", "value", "value", "value", "value", "0", "0", "0", "0", "0", "value", "value", "0", "value", "0", "0", "0", "0", "0", "0", "0", "0"};
            JSONArray colume = new JSONArray();
            JSONArray values_1 = new JSONArray();
            JSONArray values_2 = new JSONArray();
            for (int i = 0; i < columeName.length; i++) {
                colume.put(columeName[i]);
                values_1.put(mValues[i]);
                values_2.put(mValues[i]);
            }
            JSONArray valuesArray = new JSONArray();
            valuesArray.put(values_1);
            valuesArray.put(values_2);


            JSONObject arrInput1 = new JSONObject();
            arrInput1.put("ColumnNames", colume);
            arrInput1.put("Values", valuesArray);
            JSONObject input1 = new JSONObject();
            input1.put("input1", arrInput1);

            JSONObject mInput = new JSONObject();

            mInput.put("Inputs", input1);
            JSONObject mGlobalParameters = new JSONObject();
            mInput.put("GlobalParameters", mGlobalParameters);

[掃除蜘蛛網]
因為轉型作別的案子.. 開始要碰很久沒碰的APP
所以 照舊 先把新看的舊東西寫出來

Android using Http GET & POST:

First: AndroidManaifests.xml
Please add user-permission Internet in Manifests.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.xxx.httpposttowebservice">
    <uses-permission android:name="android.permission.INTERNET" />

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

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

In android M, using org.apache.http.legacy

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"
    useLibrary 'org.apache.http.legacy'

Before, Make sure your device has already connecting to wifi or internet.

GET

  1. import import java.net.HttpURLConnection;
  2. check yout web url String mWeatherUrl = "http://api.openweathermap.org/data/2.5/find?lat=55.5&lon=37.5&cnt=10"; //openweathermap.org
  3. open connect & read information.
HttpURLConnection conn = null;
try {
    String mWeatherUrl = "http://api.openweathermap.org/data/2.5/find?";//"lat=55.5&lon=37.5&cnt=10" openweathermap.org
    
    URL url = new URL(mWeatherUrl);
    conn = (HttpURLConnection) url.openConnection(); // open connect
    conn.setRequestMethod("GET");										 // using GET method
    int responseCode = conn.getResponseCode();       // responseCode will return what eror.
    BufferedReader in = new BufferedReader(
    new InputStreamReader(conn.getInputStream()));   //read your data
    String inputLine = "";
    StringBuffer response = new StringBuffer();
    while ((inputLine = in.readLine()) != null) {
           response.append(inputLine);
    }
    in.close();
    Log.d(TAG, response.toString());
} cache (Exception e) {
    e.printStackTrace();
} finally {
    if (conn != null) conn.disconnect();               //make sure you disconnnect
}

POST

import javax.net.ssl.HttpsURLConnection;         // import 


String webService = "https://webservice.com.tw;  //replace your web url
        String ret = "";
        String apikey = "api_key";							 // if your service need api, replace it.
        HttpsURLConnection conn = null;
        try {
            URL url = new URL(webService);
            JSONObject query = getJsonObj();
            conn = (HttpsURLConnection) url.openConnection();
            Log.d(TAG, query.toString());

            conn.setRequestMethod("POST");					//using POST

            conn.setRequestProperty("Content-Type", "application/json");	//check with your service
            conn.setRequestProperty("Accept", "application/json");      //check with your service
            conn.setFixedLengthStreamingMode(query.toString().getBytes().length);
            conn.setRequestProperty("Authorization", apikey);  //check with your service
            conn.connect();
            OutputStream os = conn.getOutputStream();
            os.write(query.toString().getBytes());
            os.flush();

            InputStream in = new BufferedInputStream(conn.getInputStream());		//get information
            BufferedReader reader = new BufferedReader(new InputStreamReader(in));
            StringBuilder result = new StringBuilder();
            String line = "";
            while ((line = reader.readLine()) != null) {
                result.append(line);
                Log.d(TAG, line);
            }
            Log.d(TAG, in.toString());

        } catch (Exception e) {
            Log.d(TAG, "Log = " + e.toString());
            e.printStackTrace();
        }
        finally {
            if (conn != null) conn.disconnect();
        }

Genymotion 是一套強大的android emulator.
不管是自己在電腦上玩,或是用為開發App都是相當快速且便利的

魯小我是為了沒事寫個App可以玩,想說安裝一下。而且感覺很好玩 xD

這邊分享的是Linux安裝的過程 (因為實在太不順了)

事先準備工作:

  1. 請先確認電腦的CPU為何!這個非常重要. -> 重開機 -> 進BIOS
    Intel CPU, 請到 Advanced -> CPU Configuration -> enable Intel Virtualization Technology
    AMDCPU, 請到 Advanced -> CPU Configuration -> enable AMD Secure Virtual Machine or SVM Mode
    Reason: 因為 Genymotion emulator 啟動時需要使用 virtual box. 必須enable 上述的功能.
    如果BIOS找不到這兩項, 那麼你可以…… 換一台電腦 (我也沒招)

  2. 請安裝 Virtual box (如果已經安裝virtual box. 請跳過這個步驟)
    安裝完 Virtual box 後請記得裝VirtualBox 5.0.10 Oracle VM VirtualBox Extension Pack

  3. 都安裝好後,請先run sudo /etc/init.d/vboxdrv status
    如果都設定好了,應該會出現以下的message ```
    You should get the message VirtualBox kernel modules (vboxdrv, vboxnetflt, vboxnetadp, vboxpci) are loaded

    如果不是...  印象中應該出現 not load or not ready之類的訊息. 
    此時請在 terminal 下 `sudo /etc/init.d/vboxdrv setup`
    
  4. 請修改你的Virtual box 使用者群組: sudo usermod -a -G vboxusers <login>

  5. 請到 Genymotion 網站下載 Genymotion.
    如果是公司行號也可以直接購買;如果跟阿魯一樣,請選擇Free ~

  6. ./genymotion-2.5.2_x64_debian 直接安裝,或是可以選擇目錄

  7. 安裝好後,請直接執行 ./<genymotion path>/genymotion 確定可以執行.

基本上Genymotion 安裝步驟如上, 那前言提到的很不順…
首先是Virtual box 的安裝, 因為之前亂裝了一些套件跟Lubuntu, 導致很多virtual box 的元件depend on lubuntu,
然後就….. sudo apt-get remove virtualbox-dkms
然後在重新安裝 virtual box …

後來遇到這個問題 Starting VirtualBox kernel modules ...failed! (modprobe vboxdrv failed. Please use 'dmesg' to find out why) , Google了一下….
gcc 版本不對!!!! (gcc -v) , Ubuntu 請裝gcc 4.8

virtual box裝好了. 開不起來, 原因就是第一步. BIOS的設定沒弄好. Orz …

大致上就這些不順

Genymotion裝好了, 當然會想把他套用在Android studio上.
- Launch android studio
- File -> Settings -> Plugin -> Browse repostories -> Search “genymotion”
- Install
- Restart android studio

Q&A
Q1: Android sutdio 不是就有emulator了嘛?為什麼要用Genymotion
A : Android studio 的emulator 開啟時相當耗費時間. 使用genymotion相對減少開啟時間

Q2: enymotion 缺點?
A : 目前 genymotion 只有 手機跟平板的emulator, 原生的有wearable的emulator

Q3:有想到再補充

在 linux 與 Mac OS X 都可以在
.bashprofile.bashrc 設定環境變數與 alias,但是這兩個檔案到底差別為何?

當你經由 console 登入(輸入帳號密碼),或是ssh登入時 ,系統透過 .bash_profile 執行相關的設定動作。
而 .bashrc 則是讓非登入的操作命令使用

原則是,當你想要只有登入之後才使用的設定,請放在 .bash_profile
在 Mac OS X 則是用 .bash_profile 取代 .bashrc 不需要使用 .bashrc

  1. Create bash_profile at ~/
sudo vim ~/.bash_profile
if [ -f ~/.bashrc ]; then
    source ~/.bashrc
fi
  1. 單純導入 ll 指令
#alias
alias ll='ls -lG'
  1. 以後想到什麼還可以加進去嚕

參考連結:[sendStickyBroadcastAsUser](http://developer.android.com/reference/android/content/ContextWrapper.html#sendStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle))

Issue:
遇到一個詭異的issue.
前鎮子同事在Service寫了一個Broadcast receiver, 專門收耳機拔插的intent.
收到此intent時,便會跳出一個dialog. 提示使用者 ooxx

客戶回報:淋背沒差耳機他也會跳出來
同事反應:謀哩洗勒工瞎小
但是實際測試: dialog會一直跳出來 跳出來 跳出來
客戶就會覺得:你很煩 你很煩 你很煩
廣告一樣:一直玩 一直玩 一直玩

分析Log:
Broadcast receiver 確實有收到intent
但是耳機未插拔: root cause -> 七月到了.(誤)

看其他的Log, 發現該Service時常被kill 然後又重啟.(LowMemoryKiller)

改從其他地方trace, 發現插拔耳機 最後會到 AudioService.java

private void sendStickyBroadcastToAll(Intent intent) {
    final long ident = Binder.clearCallingIdentity();
    try {
        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
    } finally {
        Binder.restoreCallingIdentity(ident);
    }
}

我把 sendStickyBroadcastAsUser 換成 sendBroadcast
issue 就不會在產生了

sendStickyBroadcastToAll 是一種android 為了預防app 或是 service漏聽了重要的intent所設計機制
當intent 透過這種方式送出後, intent 內容會keep在某個記憶體位址中.
一旦有新的 receiver register, intent就會直接送到該receiver.
這種方式大部分是語系統相關的intent Ex:Battery ,earPhone ….

所以可以推斷:當OOM發生時,該service 被 LowMemoryKiller 殺了, restart -> 重新註冊receiver, ->收到intent
然後就一直跳一直跳一直跳 我是說Dialog.

解決的辦法很簡單,插旗子確認intent有收過就好了

launch camera:

INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE 可以在有 SECURE LOCK的情況下Launch Camera
不過按 back key 就會在回到 LockScreen


private final Intent SECURE_CAMERA_INTENT =	
            new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE)	
                    .addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);	
private final Intent INSECURE_CAMERA_INTENT =	
            new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);


private Intent getCameraIntent() {	
        LockPatternUtils mLockPatternUtils = new LockPatternUtils(mContext);	
        return mLockPatternUtils.isSecure()	
                ? SECURE_CAMERA_INTENT : INSECURE_CAMERA_INTENT;	
        }	    }

最近公司在跑自動化測試的internal project.

android 系統本身有提供仿真人點擊 Ui 的 class, UiAutomator
使用java , 程式化的模擬

Android Studio

我是使用android studio 作為開發的IDE.

  1. 先 New 一個project, 要哪一種Activity 都沒差
  2. 修改 在 Project 根目錄 下的build.gradle
    修改如下:
apply plugin: 'com.android.application'

android {
    compileSdkVersion 19
    buildToolsVersion "22.0.1"
    
    defaultConfig {
        applicationId "com.example.alumincan.mtbf"
        minSdkVersion 19         // Notice: 最少是18, (android4.3)
        targetSdkVersion 19
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    packagingOptions {
        exclude 'LICENSE.txt'   //Duplicate files copied
    }
}
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    // Testing-only dependencies
    androidTestCompile('com.android.support.test:testing-support-lib:0.1')
    // UiAutomator Testing
    androidTestCompile('com.android.support.test.uiautomator:uiautomator-v18:2.0.0')
}

我是在AndroidTrst中建立一個新的test class.

public class MediaTester extends InstrumentationTestCase {
    private final String TAG = "MediaTester";
    private Context mContext;
    private UiDevice mDevice;
    
    @Override
    protected void setUp() {
        try {
            super.setUp();
            mContext = this.getInstrumentation().getContext();
            mDevice = UiDevice.getInstance(getInstrumentation());

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public void testRecordAudio() {
        mDevice.pressHome();
        Intent intent = new Intent();
        intent.setClassName("com.android.soundrecorder", 
                        "com.android.soundrecorder.SoundRecorder");
        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
        mContext.startActivity(intent);

        UiObject recorder = new UiObject(new UiSelector().
                       resourceId("com.android.soundrecorder:id/recordButton"));
        UiObject stop = new UiObject(new UiSelector().
                       resourceId("com.android.soundrecorder:id/stopButton"));
        try {
            recorder.click();
            Thread.sleep(1000 * 5);
            stop.click();
            Thread.sleep(1000 * 1);
        } catch (UiObjectNotFoundException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

上述是sample,
簡單的說就是讓程式找到目前screen上UI元件, 取得並且簡單的控制.
Ex:

UiObject recorder = new UiObject(new UiSelector().
                       resourceId("com.android.soundrecorder:id/recordButton"));```
可以透過UiSelector 找尋元件的 text, resourceID and content-desc.
找到後就可以使用Object控制

如何取得元件的text? resourceID? or content-desc?
請到sdk/tools
執行 **uiautomatorviewer**

![UiAutomatorViewer](http://imgur.com/ZEjI9SS.png)
點擊 左上方紅色框框處
你會得到下圖
![UiSeletor](http://i.imgur.com/0AkPuOb.png)

此時就可以知道該元件的個參數值為何.

Using PackageManager to get all application that uses’ installed and system native app.

Please import PackageManager and ResolveInfo first:

import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;

New an array-list to store app list:

private PackageManager mPm;

mPm = mContext.getPackageManager();
Intent mIntent = new Intent(Intent.ACTION_MAIN, null);
mIntent.addCategory(Intent.CATEGORY_LAUNCHER);
List<ResolveInfo> mResolveInfos = mPm.queryIntentActivities(mIntent, 0);

maybe we can get apps’ icon, packageName, className or other information

for (ResolveInfo rlf : mResolveInfos) {
            try {
                Log.d(TAG, rlf.activityInfo.packageName);
                KeyInformation mInf = new KeyInformation();
                ApplicationInfo app = mPm.getApplicationInfo(rlf.activityInfo.packageName, 0);

                mInf.lable = mPm.getApplicationLabel(app).toString();
                mInf.icon = mPm.getApplicationIcon(rlf.activityInfo.packageName);
                mInf.packageName = rlf.activityInfo.packageName;
                mInf.className = app.className;

                mList.add(mInf);
            } catch (PackageManager.NameNotFoundException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
}

using adapter to draw a listview will be like this:
擷取選取區域_001.png

是為了case 需求
希望能聽到某種訊息之後,變更預設的鈴聲.

已知:預設鈴聲的 檔名 + 路徑

  1. 因為依舊是在Settings的程式,以及必須與SettingsProvider 互動,所以先取得provider
ContentResolver cr = mContext.getContentResolver();
  1. 定義取得的欄位名稱
String[] cols = new String[] {
                MediaStore.Audio.Media._ID,
                MediaStore.Audio.Media.DATA,
                MediaStore.Audio.Media.TITLE,
                MediaStore.Audio.Media.ALBUM
            };
  1. 產生 query string, 就是向資料庫 query 資料跟數據
String q = MediaStore.Audio.Media.DISPLAY_NAME + "='some.mp3' and IS_MUSIC=1";
  1. new 一個物件Cusor, 作為query 後資料擺放的object
Cursor cursor = cr.query(MediaStore.Audio.Media.INTERNAL_CONTENT_URI,
                 cols, q, null, null);
  1. 此時就可以使用 cusor 物件查看取得的訊息
EX: cursor.getString(0);

當然程式碼有經過部份的修改,如果想直接copy paste 可能要再做稍微的修正才能使用