上色

Code Block

2016年2月1日 星期一

[Android] Android 6.0 (marshmallow) run-time permission 執行期間權限取得

自Android 6.0開始,除了必須在AndroidManifest.xml中宣告權限,在安裝時告知使用者外,執行期間需要用到特殊權限的話也必須告知使用者並取得。

權限可區分為以下兩大類

  1. 一般權限(PROTECTION_NORMAL),亦即在AndroidManifest.xml中宣告即可,在程式安裝時即授予程式該權限,用戶也無法手動取消。在Android 6.0(API 23)中,一般權限列表如下︰
    • ACCESS_LOCATION_EXTRA_COMMANDS
    • ACCESS_NETWORK_STATE
    • ACCESS_NOTIFICATION_POLICY
    • ACCESS_WIFI_STATE
    • BLUETOOTH
    • BLUETOOTH_ADMIN
    • BROADCAST_STICKY
    • CHANGE_NETWORK_STATE
    • CHANGE_WIFI_MULTICAST_STATE
    • CHANGE_WIFI_STATE
    • DISABLE_KEYGUARD
    • EXPAND_STATUS_BAR
    • GET_PACKAGE_SIZE
    • INTERNET
    • KILL_BACKGROUND_PROCESSES
    • MODIFY_AUDIO_SETTINGS
    • NFC
    • READ_SYNC_SETTINGS
    • READ_SYNC_STATS
    • RECEIVE_BOOT_COMPLETED
    • REORDER_TASKS
    • REQUEST_INSTALL_PACKAGES
    • SET_TIME_ZONE
    • SET_WALLPAPER
    • SET_WALLPAPER_HINTS
    • TRANSMIT_IR
    • USE_FINGERPRINT
    • VIBRATE
    • WAKE_LOCK
    • WRITE_SYNC_SETTINGS
    • SET_ALARM
    • INSTALL_SHORTCUT
    • UNINSTALL_SHORTCUT
  2. 需要執行期間授權的權限,用戶也可以在設定中隨時關閉他們,Android將之分為數個類別,當取得類別中的其中一個權限時,也就等於取得該類別所有權限,分類如下︰

若要取得權限,需在程式碼中加入
ActivityCompat.requestPermissions(Activity activity, String[] permissions, int requestCode)

若要確認是否具有權限,可使用
ContextCompat.checkSelfPermission (Context context, String permission)

而當使用者拒絕授予此權限時,開發者可能會需要說明該權限的用途並再次詢問,這時可使用
ActivityCompat.shouldShowRequestPermissionRationale(Activity activity, String permission)
當使用者拒絕授予權限時,下次調用此函式就會返回true,開發者可在此時加入權限說明。

當使用者選擇授予或拒絕此權限時,會呼叫onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)方法,此時override此方法即可處理需要權限的操作。


具體程式碼如下(以讀寫SD卡為例)︰
    

private final int REQUEST_CODE_ASK_PERMISSIONS = 10;

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
 switch (requestCode) {
  case REQUEST_CODE_ASK_PERMISSIONS:
   if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
    // Permission Granted
    //執行需要讀寫SD卡的操作
   } else {
    // Permission Denied
    Toast.makeText(MainActivity.this, getString(R.string.noPermission), Toast.LENGTH_LONG).show();
   }
   break;
  default:
   super.onRequestPermissionsResult(requestCode, permissions, grantResults);
 }
}

private void checkPermission(){
 //讀取關於讀寫外部儲存裝置的權限
 int hasReadExternalStoragePermission = ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
 //若沒有權限則詢問取得
 if (hasReadExternalStoragePermission!= PackageManager.PERMISSION_GRANTED)
 {
  if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,Manifest.permission.WRITE_EXTERNAL_STORAGE)){
   //當被使用者拒絕後再次詢問時,說明該權限用途,用戶可選擇重試(重新詢問是否授予)或取消(維持不授予權限)
   new AlertDialog.Builder(MainActivity.this)
     .setMessage("該權限用以讀寫SD卡")
     .setPositiveButton("重試", new DialogInterface.OnClickListener() {
      @Override
      public void onClick(DialogInterface dialog, int which) {
       ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE_ASK_PERMISSIONS);
      }
     })
     .setNegativeButton("取消", null)
     .create()
     .show();
   return;
  }
  ActivityCompat.requestPermissions(MainActivity.this,new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE},REQUEST_CODE_ASK_PERMISSIONS);
 }
 else
  //已有權限,直接執行需要讀寫SD卡的操作
}

Reference: Android developer documents

2015年12月14日 星期一

[Android] 偵測方向使用不同layout

若要偵測使用者螢幕方向,並使用對應不同xml的layout,可使用下列方式︰

  1. 設計兩種不同layout的xml檔案。
  2. 在AndroidManifest.xml中要改變的activity中新增屬性︰
  3. android:configChanges="orientation|keyboardHidden|screenSize"
  4. 如此一來當方向或螢幕大小改變的時候,就會觸發︰
    onConfigurationChanged(Configuration newConfig)
  5. 依照類似下面的方式判斷長寬,以決定適用的layout file。
    public void onConfigurationChanged(Configuration newConfig) {
            super.onConfigurationChanged(newConfig);
            Display display = getWindowManager().getDefaultDisplay();
            int width;
            int height;
            Point size = new Point();
            display.getSize(size);
            width=size.x;
            height=size.y;
            if (width>height) { //Landscape
                setContentView(R.layout.activity_main_land);
                initView();
            }
            else{
                setContentView(R.layout.activity_main_port);
                initView();
            }
        }
    

2015年12月11日 星期五

[Android] Shell Tool for root commands


import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.Scanner;

import android.content.res.AssetManager;
import android.os.Build;
import android.util.Log;

public class ShellUtils
{
 private static String busyboxPath="/system/xbin/busybox";

 /**
  * Set custom busybox path
  * @param path Path
  */
 public static void setCustomBusybox(String path){
  busyboxPath = path;
 }
 
 public static boolean checkBusyboxPath(){
  try{
   if (!new File("/system/xbin/busybox").exists()){
    if(!new File("/system/bin/busybox").exists())
     return false;
    else busyboxPath="/system/bin/busybox";
   }
            else busyboxPath="/system/xbin/busybox";
  }catch (SecurityException e){
   return false;
  }
  return true;
 }

 /**
  * Try to get root permission
  * @return true means success; false means fail
  */
 public static boolean rootCheck(){
  //List all the files under "/data/data", doing this operation needs root permission
  //CommandResult commandResult = execCommand(false,true, "ls /data/data/");
  //Empty result means there's no root permission
        //return !commandResult.successMsg.isEmpty();
  return execCommand(false,true,"").result==0;
 }

 @SuppressWarnings("deprecation")
 public static boolean initiateBusybox(){
  //Clear old files
  File busybox = new File(GlobalVariable.getContext().getFilesDir()+"/busybox");
  if (busybox.exists())
   busybox.delete();
  //Copy corresponding busybox to internal folder
  String cpuVersion;
  if (android.os.Build.VERSION.SDK_INT <21)
   cpuVersion= Build.CPU_ABI;
  else
   cpuVersion=Build.SUPPORTED_ABIS[0];
  //Determine busybox version
  String filename;
  cpuVersion=cpuVersion.toLowerCase();
  if (cpuVersion.contains("arm"))
   filename="busybox-armv5l";
  else if (cpuVersion.contains("x86"))
   filename="busybox-i686";
  else
   return false;
  AssetManager assetManager = GlobalVariable.getContext().getAssets();
  InputStream in = null;
  OutputStream out = null;
  try {
   in = assetManager.open(filename);
   out = new FileOutputStream(busybox);
   byte[] buffer = new byte[1024];
   int read;
   while ((read = in.read(buffer)) != -1) {
    out.write(buffer, 0, read);
   }
  }
  catch (IOException e) {
   e.printStackTrace();
   return false;
  }
  finally {
   if (in != null) {
    try {
     in.close();
    } catch (IOException e) {
     e.printStackTrace();
    }
   }
   if (out != null) {
    try {
     out.close();
    } catch (IOException e) {
     e.printStackTrace();
    }
   }
  }
  //Make busybox executables
  ShellUtils.execCommand(false, true, "chmod 0755 " + GlobalVariable.getContext().getFilesDir() + "/busybox");
  return true;
 }

 /**
  * Execute commands and get the results
  * @param isBusybox Run cmds via busybox or not
     * @param isRoot Run cmds as root?
  * @param cmds Command(s)
  * @return Results of commands
  */
 public static CommandResult execCommand(boolean isBusybox, boolean isRoot, String...cmds){
        int result=-1;
        StringBuilder successMsg = null;
        StringBuilder errorMsg = null;
        if (cmds==null || cmds.length==0)
            return new CommandResult(result,null,null);
  try{
            Process proc=Runtime.getRuntime().exec(isRoot?"su":"sh");
   DataOutputStream opt = new DataOutputStream(proc.getOutputStream());
   boolean exitCmdFound=false;
   //Write cmds
   for(String cmd:cmds){
                //Using busybox and check its path
    if (isBusybox)
     opt.writeBytes(busyboxPath + " ");
    //Unless make sure cmd is in English, using UTF-8 coding, avoid wrong coding.
    opt.write(cmd.getBytes(Charset.forName("utf-8")));
    if(!exitCmdFound && cmd.equals("exit"))
                    exitCmdFound=true;
    opt.writeBytes("\n");
   }
   if(!exitCmdFound)
                opt.writeBytes("exit\n");
   opt.flush(); 
            //Get results
            result=proc.waitFor();
            successMsg = new StringBuilder();
            errorMsg = new StringBuilder();
            Scanner scanner;
            scanner=new Scanner(new InputStreamReader(proc.getInputStream()));
            while (scanner.hasNextLine())
                successMsg.append(scanner.nextLine());
            scanner=new Scanner(new InputStreamReader(proc.getErrorStream()));
            while (scanner.hasNextLine())
                errorMsg.append(scanner.nextLine());
  } catch (Exception e){
            e.printStackTrace();
        }
        return new CommandResult(result,successMsg==null?null:successMsg.toString(), errorMsg==null?null:errorMsg.toString());
 }

    public static class CommandResult {

        /**
         * Result of command
         * 0: normal
         * else: error
         */
        public int    result;
        public String successMsg;
        public String errorMsg;

        public CommandResult(int result, String successMsg, String errorMsg) {
            this.result = result;
            this.successMsg = successMsg;
            this.errorMsg = errorMsg;
        }
    }
}

2015年7月31日 星期五

[C#] Load custom cursor from resources

Sometime you need a custom cursor for your application, the following step let you use your custom cursor in the resources.


  1. Add cursor files (*.cur) to resources
    • In [Properties] -> [Resources] tab, click [Add Resource] -> [Add Existing File...]
    • Choose your custom cursors and click OK.
  2. Load cursor with following code:
this.Cursor = new Cursor(new System.IO.MemoryStream(Properties.Resources.MyCursor));
, where Mycursor refers to your cursor name.

2015年7月25日 星期六

[C#] 取得picture box在zoom模式時的正確圖片座標

private Point unScale(Point scaledP)
{
 if (picturebox1.SizeMode != PictureBoxSizeMode.Zoom) //only zoom mode need to scale
  return scaledP;
 Point unscaled_p = new Point();
 // image and container dimensions
 int w_i = picturebox1.Image.Width;
 int h_i = picturebox1.Image.Height;
 int w_c = picturebox1.Width;
 int h_c = picturebox1.Height;
 float imageRatio = w_i / (float)h_i; // image W:H ratio
 float containerRatio = w_c / (float)h_c; // container W:H ratio

 if (imageRatio >= containerRatio)
 {
  // horizontal image
  float scaleFactor = w_c / (float)w_i;
  float scaledHeight = h_i * scaleFactor;
  // calculate gap between top of container and top of image
  float filler = Math.Abs(h_c - scaledHeight) / 2;
  unscaled_p.X = (int)(scaledP.X / scaleFactor);
  unscaled_p.Y = (int)((scaledP.Y - filler) / scaleFactor);
 }
 else
 {
  // vertical image
  float scaleFactor = h_c / (float)h_i;
  float scaledWidth = w_i * scaleFactor;
  float filler = Math.Abs(w_c - scaledWidth) / 2;
  unscaled_p.X = (int)((scaledP.X - filler) / scaleFactor);
  unscaled_p.Y = (int)(scaledP.Y / scaleFactor);
 }
 return unscaled_p;
}

2015年7月22日 星期三

[C#] Rotate bitmap with a special angle

public unsafe void Rotate(Bitmap bmp, float angle)
{
  int width=bmp.Width;
  int height=bmp.Height;
 /*
  * right, down = positive
  * p1------------p2
  * |            | 
  * p4------------p3
  * 
  * p1(0,0), p2(width-1,0), p3(width-1,height-1), p4(0,height-1)
  * 
  * In this coordinate system(left-handed coordinate system), clockwise rotation matrix (theta): (cos -sin 
  *                                                                                               sin  cos)
  * 
  */
 //Calculate new vertex
 Point p1 = RotatePoint(new Point(0, 0), angle);
 Point p2 = RotatePoint(new Point(width - 1, 0), angle);
 Point p3 = RotatePoint(new Point(width - 1, height - 1), angle);
 Point p4 = RotatePoint(new Point(0, height - 1), angle);
 //Calculate new size
 int dstWidth = Math.Max(Math.Abs(p3.X - p1.X) + 1, Math.Abs(p4.X - p2.X) + 1);
 int dstHeight = Math.Max(Math.Abs(p3.Y - p1.X) + 1, Math.Abs(p4.Y - p2.Y) + 1);
 /*
  * Calculate offset between old and new coordinate system
  * left-top point in new coordiante system -> (0,0)
  * 
  */
 int offsetX = -new int[4] { p1.X, p2.X, p3.X, p4.X }.Min();
 int offsetY = -new int[4] { p1.Y, p2.Y, p3.Y, p4.Y }.Min();
 //create bmp
 Bitmap dstBitmap = new Bitmap(dstWidth, dstHeight, PixelFormat.Format32bppArgb);
 Rectangle srcRect = new Rectangle(0, 0, width, height);
 Rectangle dstRect = new Rectangle(0, 0, dstWidth, dstHeight);
 BitmapData srcBmpData = bmp.LockBits(srcRect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
 BitmapData dstBmpData = dstBitmap.LockBits(dstRect, ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
 //define sin and cos
 double sin = Math.Sin(angle * Math.PI / 180);
 double cos = Math.Cos(angle * Math.PI / 180);
 int srcStride = srcBmpData.Stride;
 int dstStride = dstBmpData.Stride;
 //define pointer
 byte* srcP = (byte*)srcBmpData.Scan0.ToPointer();
 byte* dstP = (byte*)dstBmpData.Scan0.ToPointer();
 Parallel.For(0,dstHeight,i=>
 {
  Parallel.For(0, dstWidth, j =>
  {
   int k = 4 * j + i * dstStride;
   //Calculate corresponding point in old coordinate system
   Point oldPoint = RotatePoint(new Point(j - offsetX, i - offsetY), -angle);
   if (oldPoint.X >= 0 && oldPoint.X < width && oldPoint.Y >= 0 && oldPoint.Y < height)
   {
    dstP[k] = srcP[4 * oldPoint.X + srcStride * oldPoint.Y];
    dstP[k + 1] = srcP[4 * oldPoint.X + srcStride * oldPoint.Y + 1];
    dstP[k + 2] = srcP[4 * oldPoint.X + srcStride * oldPoint.Y + 2];
    dstP[k + 3] = srcP[4 * oldPoint.X + srcStride * oldPoint.Y + 3];
   }
   else
   {
    dstP[k] = dstP[k + 1] = dstP[k + 2] = 0xff;
    dstP[k + 3] = 0x0;
   }
  });
 });
 bmp.UnlockBits(srcBmpData);
 dstBitmap.UnlockBits(dstBmpData);
 bmp = (Bitmap)dstBitmap.Clone();
 dstBitmap.Dispose();
 width = bmp.Width;
 height = bmp.Height;
}

2015年6月17日 星期三

[Android] commit()與apply()

SharedPreferences.Editor中的commit()與apply()都是向SharedPreferences寫入數據,差別如下︰

commit()︰立刻同步寫入數據到磁碟中的SharedPreferences,並且具有回傳值(boolean)表示成功或失敗

apply()︰API 9之後加入的method,會將欲寫入的數據暫存於記憶體中,進行一個非同步寫入,因此速度較commit()快,但是不具有傳回值

因此假如不需要傳回值的時候,建議使用apply()取代commit()

Android Developer的解釋如下︰
As SharedPreferences instances are singletons within a process, it's safe to replace any instance of commit() with apply() if you were already ignoring the return value.
You don't need to worry about Android component lifecycles and their interaction with apply() writing to disk. The framework makes sure in-flight disk writes from apply() complete before switching states.
 Reference: http://developer.android.com/reference/android/content/SharedPreferences.Editor.html