2015年12月27日 星期日

Center text on a bitmap

Reference:

http://www.skoumal.net/en/android-how-draw-text-bitmap/

Code snippet:

Bitmap cloneMarkerBitmap = oriMarkerBitmap.copy(Bitmap.Config.ARGB_8888,true);
Canvas canvas = new Canvas(cloneMarkerBitmap);
Paint textPaint = new Paint();
textPaint.setColor(mResources.getColor(R.color.white));
textPaint.setTextSize(convertDpToPixel(12));
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setTypeface(Typeface.DEFAULT_BOLD);

// draw text to the Canvas center
String markerNumber = Integer.toString(number);
Rect bounds = new Rect();
textPaint.getTextBounds(markerNumber, 0, markerNumber.length(), bounds);
int x = (cloneMarkerBitmap.getWidth())/2;
int y = (cloneMarkerBitmap.getHeight() + bounds.height())/2;
canvas.drawText(markerNumber, x, y, textPaint);

...............

    public float convertDpToPixel(float dp) {
        DisplayMetrics metrics = mResources.getDisplayMetrics();
        float px = dp * metrics.density;
        return px;
    }

Multi-line in EditText

Reference:
http://stackoverflow.com/questions/4233626/allow-multi-line-in-edittext-view-in-android

Code snippet:
<EditText
    android:inputType="textMultiLine" <!-- Multiline input -->
    android:lines="8" <!-- Total Lines prior display -->
    android:minLines="6" <!-- Minimum lines -->
    android:gravity="top|left" <!-- Cursor Position -->
    android:maxLines="10" <!-- Maximum Lines -->
    android:layout_height="wrap_content" <!-- Height determined by content -->
    android:layout_width="fill_parent" <!-- Fill entire width -->
    android:scrollbars="vertical" <!-- Vertical Scroll Bar -->
/>

Send SMS message and get the result

Code snippet:
public void sendSmsMessage(String phoneNumber,String message){
        SmsManager smsManager = SmsManager.getDefault();
        ArrayList<String> messageList = smsManager.divideMessage(message);
        PendingIntent mPI = PendingIntent.getBroadcast(MainEntryActivity.this, 0, new Intent("SMS_SENT"), PendingIntent.FLAG_UPDATE_CURRENT);
        ArrayList<PendingIntent> sentIntents = new ArrayList<PendingIntent>();
        for(int i=0;i<messageList.size();i++){
            sentIntents.add(mPI);
        }
        registerReceiver(smsSentReceiver, new IntentFilter("SMS_SENT"));
        smsManager.sendMultipartTextMessage (phoneNumber, null, messageList, sentIntents, null);
}

private BroadcastReceiver smsSentReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String result = "";
            switch(getResultCode()) {
                case Activity.RESULT_OK:
                    result = "Transmission successful";
                    break;
                case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
                    result = "Transmission failed";
                    break;
                case SmsManager.RESULT_ERROR_RADIO_OFF:
                    result = "Radio off";
                    break;
                case SmsManager.RESULT_ERROR_NULL_PDU:
                    result = "No PDU defined";
                    break;
                case SmsManager.RESULT_ERROR_NO_SERVICE:
                    result = "No service";
                    break;
            }
            Log.d(TAG, "send sms result: " + result);
        }
};

@Override
protected void onDestroy() {
        try{
            unregisterReceiver(smsSentReceiver);
        } catch(IllegalArgumentException iae){
            Log.d(TAG,"Receiver not registered");
            iae.printStackTrace();
        }
}

2015年12月16日 星期三

Install fail: Failure [INSTALL_FAILED_OLDER_SDK] for KitKat device

Reference:

https://www.reddit.com/r/androiddev/comments/297xli/howto_use_the_v21_support_libs_on_older_versions/
http://moonlightbox.logdown.com/posts/2015/09/01/android-finished-with-non-zero-exit-value-3
http://stackoverflow.com/questions/31605291/gradle-finished-with-non-zero-exit-value-3
https://www.dotblogs.com.tw/newmonkey48/2015/11/02/153771

Steps:


1. Change minSdkVersion to 19 (for KitKat) in all build.gradle files
2. If Studio show some error messages like below one, you can add dexOptions in the app/build.gradle to fix the error.
Error message:
java.lang.OutOfMemoryError: GC overhead limit exceeded​

Error:org.gradle.process.internal.ExecException: Process 'command 'C:\Program Files\Java\jdk1.7.0_67\bin\java.exe'' finished with non-zero exit value 3

Solution:
app/build.gradle

android {
   ............
    dexOptions {
        javaMaxHeapSize "4g"
    }
}

2015年12月11日 星期五

How to stop a thread

Reference:

http://sp-tech.blogspot.tw/2012/11/java-java.html

Code snippet:

public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
        private boolean isRunning = true;
private Thread mThread;

@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);

mThread = new Thread(new MyTask());
                mThread.start();
        }

private class MyTask implements Runnable{

        @Override
        public void run() {
Log.d(TAG,"Thread starts");
            while(isRunning){
try{
//do something
Log.d(TAG,"Sleeping...");
Thread.sleep(5000);
} catch(InterruptedException e){
Log.d(TAG,"Thread was inturrupted");
} catch(Exception e) {
//handle error
e.printStackTrace();
}  
}
Log.d(TAG,"Thread ends");
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        isRunning = false; // make the thread leave
        mThread.interrupt();
    }
}

2015年12月8日 星期二

Get boundary coordinates of visible region on Google Map

Reference:

http://stackoverflow.com/questions/14700498/android-google-maps-get-boundary-co-ordinates

Code snippet:

LatLngBounds curScreen = googleMap.getProjection().getVisibleRegion().latLngBounds;

2015年12月3日 星期四

Display the keyboard automatically on AlertDialog with EditText

Reference:

http://stackoverflow.com/questions/12997273/alertdialog-with-edittext-open-soft-keyboard-automatically-with-focus-on-editte
Updated 2016/1/15:
http://stackoverflow.com/questions/8785023/how-to-close-android-soft-keyboard-programatically

Code snippet:

Builder builder = new Builder(this);
final EditText input = new EditText(this);
final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
builder.setTitle(R.string.dialog_title_addsubject)
                .setMessage(R.string.dialog_addsubject)
                .setView(input)
                .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {

                    public void onClick(DialogInterface dialog, int which) {
                        .......
                        imm.hideSoftInputFromWindow(input.getWindowToken(), 0);
                        /* updated 2016/1/15
                         * to hide it, call the method again
                         * imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
                         */
                        
                    }
                })
                .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {

                    public void onClick(DialogInterface dialog, int which) {
                        imm.hideSoftInputFromWindow(input.getWindowToken(), 0);
                        /* updated 2016/1/15
                         * to hide it, call the method again
                         * imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
                         */
                    }

                });
builder.show();
input.requestFocus();
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);

2015年12月2日 星期三

Create an AlertDialog with custom layout/view

Reference:
http://android--code.blogspot.tw/2015/08/android-alertdialog-custom-view.html

Code snippet:
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
View dialogView = getLayoutInflater().inflate(R.layout.alertdialog_custom_view,null);
builder.setView(dialogView);
builder.setTitle(R.string.dialog_title);
// Use AlertDialog default button instead of customized button
builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                    ......
            }
});
builder.setNegativeButton(R.string.cancel,new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                ......
            }
});
builder.show();

2015年11月25日 星期三

[Exception] The specified child already has a parent. You must call removeView() on the child's parent first.

Reference:
http://www.cnblogs.com/over140/archive/2013/06/06/3121354.html

Pre-condition:
Activity + Fragment

Solution:
View v = inflater.inflate(R.layout.preference_fragment, container, false);


Set the focus and display the keyboard on EditText

Reference:

http://stackoverflow.com/questions/8991522/how-can-i-set-the-focus-and-display-the-keyboard-on-my-edittext-programmatic

Code snippet:

EditText editText = (EditText) findViewById(R.id.myTextViewId);
editText.requestFocus();
InputMethodManager imm = (InputMethodManager)                     getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT);

2015年11月13日 星期五

Get screen shot bitmap

Code snippet:

public Bitmap getScreenShot(int x,int y,int width,int height){
        View v1 = getWindow().getDecorView().getRootView();
        v1.setDrawingCacheEnabled(true);
        final Bitmap screenShot = Bitmap.createBitmap(v1.getDrawingCache());
        v1.setDrawingCacheEnabled(false);
        final Bitmap cropBitmap = Bitmap.createBitmap(screenShot,x,y,width,height);
        return cropBitmap.copy(cropBitmap.getConfig(),true);
}

2015年11月11日 星期三

Check if Google Play Service is available

Reference:

http://stackoverflow.com/questions/18737632/how-to-check-google-play-services-version
http://stackoverflow.com/questions/28479849/when-does-isgoogleplayservicesavailable-return-service-version-update-required
http://stackoverflow.com/questions/25989727/isgoogleplayservicesavailable-always-returns-2
http://www.4byte.cn/question/135275/googlemap-v2-crash-if-first-use-android.html

Code snippet:

public static void checkGooglePlayServiceAvailability(Activity activity){
        Log.d(TAG,"sdk gms versionCode: " + GooglePlayServicesUtil.GOOGLE_PLAY_SERVICES_VERSION_CODE);
        try{
            int deviceGmsVersion = activity.getPackageManager().getPackageInfo("com.google.android.gms",0).versionCode;
            Log.d(TAG,"" + "device gms versionCode: " + deviceGmsVersion);
        } catch(Exception e){
            e.printStackTrace();
        }

        int statusCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(activity);
        Log.e(TAG,"statuscode: " + statusCode);
        Log.e(TAG,"error string: " + GooglePlayServicesUtil.getErrorString(statusCode));

        if(statusCode != ConnectionResult.SUCCESS){
            if(GooglePlayServicesUtil.isUserRecoverableError(statusCode)){
                Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog(statusCode, activity, 0);
                // If Google Play services can provide an error dialog
                if (errorDialog != null) {
                    errorDialog.show();
                }
            }
        }
    }

2015年11月10日 星期二

Handle back key event

Reference:

http://stackoverflow.com/questions/5312334/how-to-handle-back-button-in-activity

Code snippet:

For API level 5

@Override
public void onBackPressed() {
    // your code.
}

For older then API 5 use this:

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK) {
        // your code
        return true;
    }

    return super.onKeyDown(keyCode, event);
}

2015年11月9日 星期一

Get the view's height when the views are not built in onCreate(), onStart(), or onResume()

Reference:
http://stackoverflow.com/questions/4142090/how-do-you-to-retrieve-dimensions-of-a-view-getheight-and-getwidth-always-r/4406090#4406090
http://stackoverflow.com/questions/8170915/getheight-returns-0-for-all-android-ui-objects
http://www.eoeandroid.com/thread-240677-1-1.html?_dsign=f73765c1

Code snippet:
private ViewTreeObserver mViewTreeObserver;
private TextView mTextView;
protected void onCreate(Bundle savedInstanceState) {
    .........
    mTextView = (TextView) findViewById(R.id.my_text_view);
    mViewTreeObserver = mTextView.getViewTreeObserver();
    mViewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                    @Override
                    public void onGlobalLayout() {                      
                        int height = mTextView.getHeight();
                        if(!mViewTreeObserver.isAlive()){
                            mViewTreeObserver = mNewTrackingRelativeLayout.getViewTreeObserver();
                        }
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                            mViewTreeObserver.removeOnGlobalLayoutListener(this);
                        } else {
                            mViewTreeObserver.removeGlobalOnLayoutListener(this);
                        }
                    }
                });
    .........
}


2015年11月6日 星期五

Add search button on keyboard and close the keyboard when click the search button

Reference:
http://stackoverflow.com/questions/3205339/android-how-to-make-keyboard-enter-button-say-search-and-handle-its-click
http://stackoverflow.com/questions/23528644/edittext-ignores-the-imeactionlabel
http://stackoverflow.com/questions/3400028/close-virtual-keyboard-on-button-press
http://stackoverflow.com/questions/13593069/androidhide-keyboard-after-button-click

Code snippet:
layout.xml

<EditText android:imeOptions="actionSearch"
    android:inputType="text"/>

class.java

editText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
    @Override
    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
        if (actionId == EditorInfo.IME_ACTION_SEARCH) {
            performSearch();

            // close the keyboard
            InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
            imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(),
                                      InputMethodManager.RESULT_UNCHANGED_SHOWN);
            return true;
        }
        return false;
    }
});

2015年11月5日 星期四

How to solve "adb server is out of date.killing" problem

Reference:
http://yeeapps.com/11/news_detial.php?news_id=adb%20server%20is%20out%20of%20date.killing%E7%9A%84%E8%A7%A3%E6%B1%BA%E6%96%B9%E6%B3%95

Step:
1. 在cmd中執行adb nodaemon server,查看adb的埠號是多少,一般情況下是5037
2. 再執行netstat -ano | findstr "5037",找出佔據埠號5037的程序,並記錄其 PID
3. 到工作管理員中,將同一個 PID的程序刪除

2015年11月4日 星期三

Changing layout height programmatically

Reference:

http://www.cnblogs.com/wanqieddy/p/4040601.html

Code snippet:

layout.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:map="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <RelativeLayout
        android:id="@+id/my_relative_layout_id"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        ................


class.java:

RelativeLayout mRelativeLayout = (RelativeLayout) findViewById(R.id.my_relative_layout_id);
mRelativeLayout.setLayoutParams(new LinearLayout.LayoutParams(200,600));


Note:

setLayoutParams裡的參數型態不是要改變高度的 layout型態,而是要和外面一層layout的型態相同(如上面紅字所標註),否則會出現 ClassCastException
以上面的範例來說,要改變高度的 layout型態是 RelativeLayout,如果 setLayoutParams裡面帶入的參數型態為 RelativeLayout.LayoutParams,就會出現下面的 exception

java.lang.ClassCastException: android.widget.RelativeLayout$LayoutParams cannot be cast to android.widget.LinearLayout$LayoutParams

因為要改變高度的 layout,外面一層的 layout型態為 LinearLayout,所以setLayoutParams裡面帶入的參數型態要是 LinearLayout.LayoutParams

2015年10月15日 星期四

如何在 App 畫面上顯示 Google map

Reference:

https://developers.google.com/maps/documentation/android-api/map#indoor_maps
https://developer.android.com/guide/topics/location/index.html

Illustration:

按照“取得 Google map的 API key”一文,取得了 Google Map API key之後,之後在下列檔案新增紅字的部分,就可以在你的 app裡面顯示 Google map了

1. AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="your_package_name" >

<permission android:name="your_package_name.permission.MAPS_RECEIVE" android:protectionLevel="signature"/>
    <uses-permission android:name="your_package_name.permission.MAPS_RECEIVE"/>
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
    <!--
 The ACCESS_COARSE/FINE_LOCATION permissions are not required to use
         Google Maps Android API v2, but are recommended.
    -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

<application
        android:allowBackup="true"
        ...... >
        <meta-data
            android:name="com.google.android.gms.version"
            android:value="6587000" />
        <meta-data
            android:name="com.google.android.maps.v2.API_KEY"
            android:value="@string/google_maps_key" />

        .......
    </application>
    <!--
              如果使用OpenGL ES version 2來顯示
              避免 Google Play Store 認為我們的 App 不支援 OpenGL ES version 2
    -->
    <uses-feature android:glEsVersion="0x00020000" android:required="true" />
</manifest>

2. res/values/google_maps_key.xml

<resources>
......
    <string name="google_maps_key" translatable="false" templateMergeStrategy="preserve">
        YOUR_KEY_HERE
    </string>
</resources>

取得 Google map的 API key

Reference:

http://developer.android.com/tools/publishing/app-signing.html
https://developers.google.com/maps/documentation/android-api/signup
http://wazai.net/1916/%E7%B0%BD%E7%BD%B2%E4%BD%A0%E7%9A%84android%E6%87%89%E7%94%A8%E7%A8%8B%E5%BC%8F-keystore

Step:

1. 打開命令提示字元視窗,輸入下列的指令:
keytool -genkey -v -keystore yourkeyname.keystore -alias yourkeyname -keyalg RSA -keysize 2048 -validity 10000

Note:
如果這邊出現「keytool: command not found」,請把$JAVA_HOME\bin加入環境變數$PATH中,或是直接到$JAVA_HOME\bin\目錄下執行 keytool,例如 $JAVA_HOME\bin是 C:\Program Files\Java\jdk1.7.0_02\bin,就先cd C:\Program Files\Java\jdk1.7.0_02\bin之後開始輸入上面的指令。
將上述指令 -keystore yourkeyname.keystore以及 -alias yourkeyname中的 yourkeyname改成自己要的名字,其他東西照著貼上就好,-validity官方說明文件建議設定10000以上的值
Ex: 
keytool -genkey -v -keystore jasonrelease.keystore -alias jasonrelease -keyalg RSA -keysize 2048 -validity 10000
2. 將產生的 yourkeyname.keystore移到 $JAVA_HOME\bin\目錄下
3. 在命令提示字元視窗輸入下列指令
keytool -list -v -keystore jasonrelease.keystore

android debug key:
keytool -list -v -keystore debug.keystore
password:android
4. 將 SHA1編碼複製起來,等一下在 Google API Console網頁會使用到
5. 利用 Google搜尋 Google API Console,找到Google API註冊網站
https://console.developers.google.com/
6.  API -> Google Maps Android API -> 啟用
7. 憑證 -> 新增憑證 -> API金鑰 -> 「Android」金鑰
8. 新增「套件名稱和指紋」
9. 輸入開發的 app的套件名稱和剛剛複製起來的 SHA1編碼,點擊建立按鈕後完成

2015年10月14日 星期三

複製 Studio project到新的位置

將 Studio project直接複製一份到新的位置,開啟新位置的 project時,有出現異常的情況,按照下面的步驟操作 Studio,就可以排除異常了

Tools -> Android -> Sync Project with Gradle Files

Hide the statusbar and navigation bar permanently

Reference:

http://stackoverflow.com/questions/21724420/how-to-hide-navigation-bar-permanently-in-android-activity

Code snippet:

private int currentApiVersion;

@Override
@SuppressLint("NewApi")
protected void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);

    currentApiVersion = android.os.Build.VERSION.SDK_INT;

    final int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
        | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
        | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
        | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
        | View.SYSTEM_UI_FLAG_FULLSCREEN
        | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;

    // This work only for android 4.4+
    if(currentApiVersion >= Build.VERSION_CODES.KITKAT)
    {

        getWindow().getDecorView().setSystemUiVisibility(flags);

        // Code below is to handle presses of Volume up or Volume down.
        // Without this, after pressing volume buttons, the navigation bar will
        // show up and won't hide
        final View decorView = getWindow().getDecorView();
        decorView
            .setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener()
            {

                @Override
                public void onSystemUiVisibilityChange(int visibility)
                {
                    if((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0)
                    {
                        decorView.setSystemUiVisibility(flags);
                    }
                }
            });
    }

}


@SuppressLint("NewApi")
@Override
public void onWindowFocusChanged(boolean hasFocus)
{
    super.onWindowFocusChanged(hasFocus);
    if(currentApiVersion >= Build.VERSION_CODES.KITKAT && hasFocus)
    {
        getWindow().getDecorView().setSystemUiVisibility(
            View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                | View.SYSTEM_UI_FLAG_FULLSCREEN
                | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
    }
}

2015年10月13日 星期二

ActionBar 顯示返回鍵

Reference:

http://yuzhou2.blogspot.tw/2015/08/android-actionbar-homeasup.html
http://developer.android.com/guide/topics/ui/actionbar.html
https://developer.android.com/training/implementing-navigation/ancestral.html

Code snippet:

AndroidManifest.xml
<application ...>
    <activity android:name=".MainActivity" ...>
    </activity>
    <activity android:name=".SecondActivity"
              android:parentActivityName=".MainActivity" ...>
    </activity>
</application>


SecondActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
    ActionBar actionBar = getSupportActionBar();
    // change the icon of key
    actionBar.setHomeAsUpIndicator(R.drawable.icon);
    actionBar.setDisplayHomeAsUpEnabled(true);
}

Note:

This is appropriate when the parent activity may be different depending on how the user arrived at the current screen.

SecondActivity.java
@Override
public Intent getParentActivityIntent() {
    finish();
    return null;
}

2015年9月4日 星期五

Uninstall app programmatically

Code snippet:

private static final int REQUEST_UNINSTALL = 0;
public void uninstallPkg(String packageName, boolean allUsers) {
// Create new intent to launch Uninstaller activity
Uri packageURI = Uri.parse("package:" + packageName);
Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageURI);
uninstallIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, allUsers);
  uninstallIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
startActivityForResult(uninstallIntent, REQUEST_UNINSTALL);
}
 
// The result code is not Activity.RESULT_OK
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
  // TODO Auto-generated method stub
    boolean result = false;
    if(data != null){
    result = data.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, true);
    }
    if ((requestCode == REQUEST_UNINSTALL) && result) {
// TODO
  }
}

2015年8月27日 星期四

ContentProvider限制查詢的回傳資料數量

Reference:

http://blog.csdn.net/dalancon/article/details/12618315

說明:

query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)

sortOrder
LIMIT <count> OFFSET <skip>
count: 回傳的資料數量
skip: 跳過的資料數量
Ex:
String sortOrder = "name DESC LIMIT 3 OFFSET 2";
按照 name的字母降冪排序,最多只回傳3筆資料,略過首兩筆符合的資料,從第三筆開始回傳

2015年8月19日 星期三

ViewPager

簡介:

透過滑動螢幕,達到切換不同頁面的效果。只要實作 PagerAdapter,就能顯示各個頁面要顯示的內容,每一頁是由一個 Fragment來呈現。
實作 PagerAdapter方面,通常會實作繼承自 PagerAdapter的 FragmentPagerAdapter和 FragmentStatePagerAdapter。Pager的頁面少使用 FragmentPagerAdapter,反之,則使用FragmentStatePagerAdapter。

Code snippet:

@Override
public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // add view pager
        mSectionsPagerAdapter = new SectionsPagerAdapter(getFragmentManager());
        // Set up the ViewPager with the sections adapter.
        mViewPager = (ViewPager) v.findViewById(R.id.pager);
        mViewPager.setAdapter(mSectionsPagerAdapter);
        .........
}

public class SectionsPagerAdapter extends FragmentPagerAdapter{
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}

@Override
public Fragment getItem(int position) {
Fragment fragment = null;
switch (position){
case 0:
fragment = FirstFragment.newInstance();
break;
case 1:
fragment = SecondFragment.newInstance();
break;
case 2:
fragment = ThirdFragment.newInstance();
break;
default:
break;
}
return fragment;
}

@Override
public int getCount() {
return NUMBERS_OF_PAGER;
}

@Override
public CharSequence getPageTitle(int position) {
Locale l = Locale.getDefault();
Resources res = getActivity().getResources();
switch (position) {
case 0:
return res.getText(R.string.tab_title_showcase).toString().toUpperCase(l);
case 1:
return res.getText(R.string.tab_title_favorite).toString().toUpperCase(l);
case 2:
return res.getText(R.string.tab_title_recent).toString().toUpperCase(l);
                                ...................
default:
break;
}
return null;
}
}

Critical issue:

https://code.google.com/p/android/issues/detail?id=55068
ViewPager doesn't refresh child fragments when back navigation via backstack

當 FragmentA實作一個 ViewPager,在設定 PagerAdapter的建構子時,要使用 getChildFragmentManager() 取代 getFragmentManager()
官方文件說明如下:

public final FragmentManager getChildFragmentManager ()

Added in API level 17
Return a private FragmentManager for placing and managing Fragments inside of this Fragment.
若使用 getFragmentManager(),當 FragmentA透過 FragmentTransaction跳到 FragmentB,再按 Back鍵回到 Fragment時,ViewPager裡的 Fragment會發生顯示異常
(若要按 Back鍵讓 FragmentB能回到 FragmentA,FragmentTransaction要執行下面程式localFragmentTransaction.addToBackStack(null))
若是由 ViewPager裡的 Fragment利用 FragmentTransaction跳到 FragmentB,要透過 getActivity()取得的 Activity object,來執行 replace(),否則會因為找不到 FragmentA、B容器的 container的resource id而發生 IllegalArgumentException

08-17 16:22:02.328: D/AndroidRuntime(1605): Shutting down VM
08-17 16:22:02.329: E/AndroidRuntime(1605): FATAL EXCEPTION: main
08-17 16:22:02.329: E/AndroidRuntime(1605): Process: org.cyanogenmod.theme.chooser, PID: 1605
08-17 16:22:02.329: E/AndroidRuntime(1605): java.lang.IllegalArgumentException: No view found for id 0x7f0d0003 (org.cyanogenmod.theme.chooser:id/content) for fragment ChooserDetailFragment{25b7e683 #2 id=0x7f0d0003 class org.cyanogenmod.theme.chooser.ChooserDetailFragment}

2015年8月4日 星期二

Eclipse更新 support library後出現了「Jar mismatch! Fix your dependencies」

Reference:

http://www.dotblogs.com.tw/cheng/archive/2014/07/18/r-java-android-jar-mismatch-include-library.aspx

徵狀:

使用 SDK Manager更新 Android Support Library,Eclipse出現下面錯誤訊息
Found 2 versions of android-support-v4.jar in the dependency list,
but not all the versions are identical (check is based on SHA-1 only at this time).
All versions of the libraries must be the same at this time.

原因:

Library專案(Ex: appcompat_v7)和原本專案的「android-support-v4.jar」版本不同,造成的錯誤(Eclipse會自動做sha1來比對是否一致),但不一定是Android的support library,也有可能是其他衝突的jar檔

解決方法:

如果是 support library的話,將 library專案和原本專案的 libs目錄下的「android-support-v4.jar」刪除掉,之後一併在 library專案和原本專案上按右鍵 -> Android Tools -> Add Support Library,將最新的 support library加入,讓兩邊 include的 library一致

2015年8月1日 星期六

Theme and Style

Reference

https://developer.android.com/training/material/theme.html#StatusBar

精通 Android程式介面設計
作者:孫宏明

定義

Theme和 Style都是格式的定義
Theme應用的對象是 Application或 Activity
Style應用的對象是介面元件

Theme

Theme的系列

Theme、Theme.Light、Theme.Holo、Theme.Holo.Light、Theme.Translucent

每個系列可再分為下列五種:
Theme                                              基本型態 (深色系列)
Theme.Dialog                                  對話盒型態
Theme.Panel                                    小窗格型態 (背景透明)
Theme.NoTitleBar                           沒有程式上方的標題列
Theme.NoTitleBar.Fullscreen         讓程式畫面佔滿螢幕

如何讓程式在不同版本的Android使用不同的Theme

在 res下建立 valuse-XX資料夾,並建立 styles.xml資源檔
例如:Android 4.2的 API編號為17,就建立 values-17資料夾


使用 parent屬性指定一個系統內建的 theme,另外可將一些屬性放在 <item>標籤裡,修改 theme的外觀

<resources>

   <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="android:windowFullscreen">true</item>
        <item name="android:windowNoTitle">true</item>
        <item name="android:textSize">25sp</item>
        <item name="android:textColor">#00FF00</item>
    </style>

</resources>

Style

如何建立 Style

建立 Style和建立 Theme的過程類似,因為 Theme就是一種 Style

<style name="MyTextStyle" parent="android:TextAppearance">
        <item name="android:textSize">25sp</item>
        <item name="android:textColor">#FF0000</item>
</style>

<TextView
        style="@style/MyTextStyle"
        android:id="@+id/question_label"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"      
        android:text="@string/question_label"
        android:textSize="25sp"/>

也可以套用系統預設的 style
<TextView
        style="@android:style/TextAppearance"
        android:id="@+id/question_label"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"      
        android:text="@string/question_label"
        android:textSize="25sp"/>

在使用系統預設的 style時,可以用?代替@,是說當沒有指定特定style時使用系統預設值
style="?android:textAppearance"


Style的繼承

style提供繼承的功能
當style名稱加上「.」,表示這個 style名稱繼承「.」符號前面一個的 style名稱
例如:
CentHori繼承 MyTextStyle,同時擁有三個屬性
<style name="MyTextStyle" parent="android:TextAppearance">
        <item name="android:textSize">25sp</item>
        <item name="android:textColor">#FF0000</item>
</style>

<style name="MyTextStyle.CentHori">
        <item name="android:layout_gravity">center_horizontal</item>
</style>

這種繼承方式可以系續延伸
<style name="MyTextStyle.CentHori.GreyBack">
        <item name="android:background">#aaaaaa</item>
</style>

2015年7月21日 星期二

Read an image file into bitmap and set it to the imageview

AndroidManifest.xml
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

如果沒有加 Permission,image file會無法被讀取,導致 ImageView無法正常顯示

Java
private static final String INTERNAL_STORAGE_PATH = "/storage/sdcard0";
private static final String FILE_NAME = "image.jpg";
private ImageView mImageView;
protected void onCreate(Bundle savedInstanceState) {
......
        mImageView = (ImageView)findViewById(R.id.origin_view);

        // From internal storage
        File imageFile = new File(INTERNAL_STORAGE_PATH + "/" + FILE_NAME);       
        Bitmap imageBitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath());

        // From external storage (SD card)
        File imageFile = new File(Environment.getExternalStorageDirectory() + "/" + FILE_NAME);
        Bitmap imageBitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath());

        // From app resource
        Bitmap imageBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.ic_launcher);

        mImageView.setImageBitmap(imageBitmap);      
}
 

2015年7月20日 星期一

在 ActionBar顯示進度圖示

Reference:

http://androidbiancheng.blogspot.tw/2011/07/windowfeatureindeterminateprogress.html

步驟:


1. requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
2. 呼叫 setProgressBarIndeterminateVisibility(true/false) 方法來顯示/隱藏進度圖示

Send message by handler

Reference:

http://blog.csdn.net/ahuier/article/details/17012923
http://blog.csdn.net/ahuier/article/details/17013647

Handler sendMessage:

1. sendEmptyMessage(int what)
Example:
new Thread(new Runnable() { 
    @Override 
    public void run() { 
        mHandler.sendEmptyMessage(3); 
    } 
}).start();

protected static Handler mHandler = new Handler() {
     @Override 
     public void handleMessage(android.os.Message msg) { 
         System.out.println("--> what: " + msg.what); 
     } 
}; 

2. sendEmptyMessageAtTime(int what,long uptimeMillis)

3. sendEmptyMessageDelayed (int what, long delayMillis)

4. sendMessage (Message msg)
Example:
new Thread(new Runnable() { 
    @Override 
    public void run() { 
        Message msg = mHandler.obtainMessage();  
        msg.arg1 = 1; 
        msg.what = 3; 
        msg.obj = "AHuier"; 
        mHandler.sendMessage(msg); 
    } 
}).start(); 

Handler obtainMessage:

obtainMessage(int what, int arg1, int arg2, Object obj)
Ex:
mHandler.obtainMessage(Constants.MESSAGE_READ, bytes, -1, buffer).sendToTarget();

Message obtain:

1. obtain()
Ex:
Message message = Message.obtain(); 
message.what = 1; 
message.arg1 = 1; 
message.arg2 = 3; 
message.obj = "AHuier"; 
handler.sendMessage(message);

2. obtain(Handler h)
Ex:
Message message = Message.obtain(handler); 
message.what = 1; 
message.arg1 = 1; 
message.arg2 = 3; 
message.obj = "AHuier"; 
message.sendToTarget();

3. obtain(Handler h, int what)

4. obtain(Handler h, int what, int arg1, int arg2, Object obj)

5.使用 Bundle來傳遞複雜的資料型態
Ex:
Message message = Message.obtain(handler, 1, 1, 3, "AHuier"); 
Bundle data = new Bundle(); 
data.putStringArray("str", new String[]{"AHui", "AHui1", "AHui2"}); 
message.setData(data); 
message.sendToTarget();


Relative reference:

http://givemepass-blog.logdown.com/posts/296606-how-to-use-a-handler
http://j796160836.pixnet.net/blog/post/28766165




Bluetooth

Reference:

http://developer.android.com/guide/topics/connectivity/bluetooth.html

Get BluetoothAdapter:

BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
   
// Device does not support Bluetooth
}

Enable Bluetooth:


if (!mBluetoothAdapter.isEnabled()) {
   
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult
(enableBtIntent, REQUEST_ENABLE_BT);
}


Quering paired devices:

MAC address可以用來初始化BluetoothDevice物件

Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
// If there are paired devices
if (pairedDevices.size() > 0) {
   
// Loop through paired devices
   
for (BluetoothDevice device : pairedDevices) {
       
// Add the name and address to an array adapter to show in a ListView
        mArrayAdapter
.add(device.getName() + "\n" + device.getAddress());
   
}
}


Discovering devices:

只要呼叫 startDiscovery()就可以開始尋找device
應用程式需要註冊一個broadcase receiver來接收搜尋到的device的資訊

// Create a BroadcastReceiver for ACTION_FOUND
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
   
public void onReceive(Context context, Intent intent) {
       
String action = intent.getAction();
       
// When discovery finds a device
       
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
           
// Get the BluetoothDevice object from the Intent
           
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
           
// Add the name and address to an array adapter to show in a ListView
            mArrayAdapter
.add(device.getName() + "\n" + device.getAddress());
       
}
   
}
};
// Register the BroadcastReceiver
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver
(mReceiver, filter); // Don't forget to unregister during onDestroy


注意:
執行搜尋device會消耗很多資源,所以當找到一個要連接的device,在連接前要先呼叫cancelDiscovery(),停止搜尋device。
另外,當已經在連接中的情況下進行搜尋device,會減少已連接的連線的頻寬,所以在已連接的情況下不要再搜尋device

Get BluetoothDevice:

BluetoothAdapter
public BluetoothDevice  getRemoteDevice (String address)

Valid Bluetooth hardware addresses must be upper case, in a format such as "00:11:22:33:AA:BB". The helper checkBluetoothAddress(String) is available to validate a Bluetooth address.

Parameters

address
valid Bluetooth MAC address

Throws

IllegalArgumentException
if address is invalid 
Ex:
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);

BluetoothSocket
public BluetoothDevice  getRemoteDevice ()

Get the remote device this socket is connecting, or connected, to.

Returns
remote device

Ex:
socket.getRemoteDevice()

Enabling discoverability:

要讓你的Device成為Discoverable,要呼叫StartActivityForResult(Intent,int),Intent要使用ACTION_REQUEST_DISCOVERABLE

Discoverable時間:
Default: 120s
Maximum: 3600s
Set to 0: always
< 0 或 > 3600: 120s

Intent discoverableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent
.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity
(discoverableIntent);


無論Discoverable成功或失敗,onActivityResult callback function都會收到呼叫

如果Device的Bluetooth還沒開啟,當Device變成Discoverable時,會自動開啟Bluetooth

若要知道Discoverable mode是否發生改變,可以註冊一個Broadcase receiver監聽ACTION_SCAN_MODE_CHANGED的intent,intent裡面會包含EXTRA_SCAN_MODE 和EXTRA_PREVIOUS_SCAN_MODE
mode分為以下三種:
SCAN_MODE_CONNECTABLE_DISCOVERABLE: 在Discoverable mode
SCAN_MODE_CONNECTABLE: 不在Discoverable mode,但仍可以接收connection
SCAN_MODE_NONE: 不在Disoverable mode,並且也不能接收connection

Connecting Devices:

要在兩台Device之間建立連線,一台Device(Server side)要先打開server socket,另外一台Device(Client side)要初始化connection(使用server device的MAC address)。當兩台Device在同一個RFCOMM channel上都有了已連接的BluetoothSocket,就可以取得input和output stream,進行資料交換

Server和Client取得Bluetooth的方法如下:
Server: when an incoming connection is accepted
Client: when it opens an RFCOMM channel to the server

實作方法:
法一:每一台device都打開server socket,等待device接收connection,當有一台server初始化connection,就變成client
法二:某一台device打開server socket,負責管理connection,其他device只要負責初始化connection

注意:
RFCOMM connection attempt會block住直到配對成功、使用者拒絕配對、配對失敗或time out

Connecting as a server:

server socket的目的是監聽connection request,當接收到request後,就會提供一個BluetoothSocket,當從BlutoothServerSocket取得BluetoothSocket後,除非想要接收更多的connection,否則就應該把BlutoothServerSocket關閉

設定和接受connection的步驟如下:
1. 取得 BluetoothServerSocket:
    呼叫 listenUsingRfcommWithServiceRecord(String, UUID)

    BluetoothAdapter
    public BluetoothServerSocket  listenUsingRfcommWithServiceRecord (String name, UUID uuid)

    Parameters


    name
    service name for SDP record


    uuid
    uuid for SDP record

    Returns
    a listening RFCOMM BluetoothServerSocket

    Throws

    IOException
    on error, for example Bluetooth not available, or insufficient permissions, or channel in use. 

2. 監聽connection request:
    呼叫 accept()

    BluetoothServerSocket
    public BluetoothSocket  accept ()

    Returns
    a connected BluetoothSocket

    Throws

    IOException
    on error, for example this call was aborted, or timeout 

3. 釋放 server socket (除非想要接收額外的connection)
    呼叫 close()

    BluetoothServerSocket
    public void  close ()

    Throws
    IOException

注意:
不應該在 Activity main UI thread呼叫 accept(),因為它是個 blocking call,會把 Application給block住。在使用BluetoothServerSocket和 BluetoothSocket時,應該在新的thread裡執行。

Connecting as a client

要初始化和remote device(擁有 open server socket)之間的connection,先要取得 BluetoothDevice物件代表這個 remote device

步驟:
1. 取得 BluetoothSocket
    呼叫createRfcommSocketToServiceRecord(UUID uuid)
    uuid必須符合server service在開啟BluetoothServerSocket (with   listenUsingRfcommWithServiceRecord(String, UUID))時所用uuid

    BluetoothDevice
    public BluetoothSocket  createRfcommSocketToServiceRecord (UUID uuid)

   
Parameters

    uuid
    service record uuid to lookup RFCOMM channel

    Returns
    a RFCOMM BluetoothServerSocket ready for an outgoing connection

    Throws

    IOException
    on error, for example Bluetooth not available, or insufficient permissions

2. 初始化connection
    呼叫connect()

    BluetoothSocket

    public void  connect ()

    Throws

    IOException
    on error, for example connection failure

    呼叫了connect(),system會查詢 remote device是否有符合的uuid,如果查詢成功,remote
    device會接受這個connection,並分享RFCOMM channel。
    connect()是一個blocking call,當connection fail或 connect()發生time out(約12秒),就會丟出
    exception

注意:
當呼叫 connect時,避免 device正在執行device discovery,否則 connection attempt會變得很慢,並且可能會fail

Managing a connction:

傳送任意資料的步驟:
1. 取得 InputStream和 OutputStream來處理資料傳輸
     呼叫 getInputStream()和 getOutputStream
2. 讀取 steam的資料
    呼叫 read(byte[])和 write(byte[])

注意:
因為 read(byte[])和 write(byte[])是 blocking call,所以應該create新的thread來讀取stream。
read(byte[])會 block住直到 stream有資料可以讀取;只有當device的讀取速度不夠快,導致bufer滿了,write(byte[])才會 block住