Friday, November 21, 2014

Android: Custom toast example (different text size, different background color)


toast.getView().setBackgroundColor() will change the background color.
toast.getView().findViewById(android.R.id.message).setTextColor() will change the text color.

    private void makeCustomToast(String message) {
        Toast toast = Toast.makeText(mContext, message, Toast.LENGTH_SHORT);
        TextView v = (TextView) toast.getView().findViewById(android.R.id.message);
        toast.getView().setBackgroundColor(mContext.getResources().getColor(R.color.blue));
        v.setTextColor(mContext.getResources().getColor(R.color.white));
        toast.show();
    }

Android: Get app version programmatically example


Get version name & version code

String sVersionName;
int iVersionCode;
try {
    PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
    sVersionName = pInfo.versionName;
    iVersionCode = pInfo.versionCode;
} catch (PackageManager.NameNotFoundException e) {
    sVersionName = "Unknown";
    iVersionCode = 1;
}

Version name & version code is from what you have defined in AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.example1"
    android:versionCode="14"
    android:versionName="1.2.2">





Friday, September 26, 2014

Support Chinese Translation (Support Traditional & Simplified) but also Hong Kong

Up until now I was using:

"values-zh-rCN" -> Simplified Chinese
"values-zh-rTW" -> Traditional Chinese

I thought I covered entire Chinese. I was wrong!
There's 'zh-rHK' for Hong Kong.

Since I didn't cover Hong Kong Traditional Chinese, it showed default language which is English.

Hong Kong Traditional Chinese is same as Taiwan Traditional Chinese.
So, you should change to:

"values-zh-rCN" -> Simplified Chinese
"values-zh" -> Traditional Chinese (Covering Taiwan, Hong Kong, Macau, and other places)

Why I didn't add 'zh-rHK'?
Because if there is some other Chinese languages that I wasn't aware, then values-zh should cover them all.

Thursday, May 1, 2014

[Android] How to code 'android:background="?android:attr/selectableItemBackground"' programmatically example

You can't (umm.. there is but hard). So there's a work around.

layout\textview_selectable.xml
 <TextView xmlns:android="http://schemas.android.com/apk/res/android"  
   android:background="?android:attr/selectableItemBackground"
   android:layout_width="match_parent"  
   android:layout_height="wrap_content"  
   />  

then in your code:
 TextView tv = (TextView)getLayoutInflater().inflate(R.layout.textview_selectable, null);

[Android] Set style programatically example

You can't. So there's a work around.

layout\textview_mystyle.xml
 <TextView xmlns:android="http://schemas.android.com/apk/res/android"  
   style="@style/myStyleText"  
   android:layout_width="match_parent"  
   android:layout_height="wrap_content"  
   />  

then in your code:
 TextView tv = (TextView)getLayoutInflater().inflate(R.layout.textview_mystyle, null);

Wednesday, April 30, 2014

Java Date long to C# Datetime long


java date long -> C# datetime long:
 TimeSpan ts = TimeSpan.FromMilliseconds(javaNum);  
 DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Local);  
 DateTime dateTime2 = dateTime.Add(ts);  
 DateTime final = dateTime2.ToUniversalTime(); // Change to local-time  

C # -> java long
  private static DateTime Jan1st1970 = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);   
     
  public static long CurrentTimeMillis()   
  {   
     return (long) (DateTime.UtcNow - Jan1st1970).TotalMilliseconds;   
  }   

[Android] How to send email(Gmail) programmatically without launching a third party app (new intent)

First you need following "javamail-android" libraries: (download)
  • additionnal.jar 
  • mail.jar
  • activation.jar

Copy them to \libs folder and add copied jars to your project library.

You need following permissions:
   <uses-permission android:name="android.permission.GET_ACCOUNTS" />  
   <uses-permission android:name="android.permission.INTERNET" />  
   <uses-permission android:name="android.permission.USE_CREDENTIALS" />  

OAuth2Authenticator.java:
   
 import android.accounts.Account;  
 import android.accounts.AccountManager;  
 import android.accounts.AccountManagerCallback;  
 import android.accounts.AccountManagerFuture;  
 import android.app.Activity;  
 import android.os.AsyncTask;  
 import android.os.Bundle;  
 import android.util.Log;  
   
 import com.sun.mail.smtp.SMTPTransport;  
 import com.sun.mail.util.BASE64EncoderStream;  
   
 import java.util.Properties;  
   
 import javax.activation.DataHandler;  
 import javax.mail.Message;  
 import javax.mail.Session;  
 import javax.mail.URLName;  
 import javax.mail.internet.InternetAddress;  
 import javax.mail.internet.MimeMessage;  
 import javax.mail.util.ByteArrayDataSource;  
   
   
 public class OAuth2Authenticator {  
   
   private static Session mSession;  
   
   private AccountManager mAccountManager;  
   private String mToken;  
   private Account mAccount = null;  
   private Activity mActivity;  
   private static String TEST_ACCOUNT_EMAIL = "(your_account_that_is_added_to_Android_device)@gmail.com";  
   
   public OAuth2Authenticator(Activity activity) {  
     mActivity = activity;  
     mAccountManager = AccountManager.get(mActivity);  
   
     Account[] accounts = mAccountManager.getAccounts();  
   
     // For this tutorial, we manually set the email account(TEST_ACCOUNT_EMAIL). However, you should create some kind of UI for users to select the Gmail account  
     for (Account a : accounts) {  
       if (a.name.equals(TEST_ACCOUNT_EMAIL) && a.type.equals("com.google")) {  
         mAccount = a;  
       }  
     }  
     if (mAccount != null) {  
       AccountManagerFuture<Bundle> future = mAccountManager.getAuthToken(mAccount, "oauth2:https://mail.google.com/", null, mActivity, new OnTokenAcquired(), null);  
     }  
   
   }  
   
   private class OnTokenAcquired implements AccountManagerCallback<Bundle> {  
     @Override  
     public void run(AccountManagerFuture<Bundle> result) {  
       try {  
         Bundle bundle = result.getResult();  
         mToken = bundle.getString(AccountManager.KEY_AUTHTOKEN);  
         new SendMailTask().execute();  
       } catch (Exception e) {  
         Log.e("OnTokenAcquired", e.getMessage());  
       }  
     }  
   }  
   
   private class SendMailTask extends AsyncTask<Void, Void, Void> {  
   
     @Override  
     protected Void doInBackground(Void... params) {  
       try {  
         sendMail("subject", "body message", TEST_ACCOUNT_EMAIL, mToken, "(recipient_address)@(somemail).com");  
       } catch (Exception e) {  
         Log.e("SendMailTask", "error:" + e.getMessage());  
       }  
       return null;  
     }  
   }  
   
   private SMTPTransport connectToSmtp(String host, int port, String userEmail,  
                     String oauthToken, boolean debug) throws Exception {  
   
     Properties props = new Properties();  
     props.put("mail.smtp.starttls.enable", "true");  
     props.put("mail.smtp.starttls.required", "true");  
     props.put("mail.smtp.sasl.enable", "false");  
     mSession = Session.getInstance(props);  
     mSession.setDebug(debug);  
     final URLName unusedUrlName = null;  
     SMTPTransport transport = new SMTPTransport(mSession, unusedUrlName);  
     // If the password is non-null, SMTP tries to do AUTH LOGIN.  
     final String emptyPassword = null;  
     transport.connect(host, port, userEmail, emptyPassword);  
     byte[] response = String.format("user=%s\1auth=Bearer %s\1\1", userEmail, oauthToken).getBytes();  
     response = BASE64EncoderStream.encode(response);  
     transport.issueCommand("AUTH XOAUTH2 " + new String(response), 235);  
     return transport;  
   }  
   
   public synchronized void sendMail(String subject, String body, String user,  
                    String oauthToken, String recipients) {  
     try {  
   
       SMTPTransport smtpTransport = connectToSmtp("smtp.gmail.com",  
           587,  
           user,  
           oauthToken,  
           true);  
       MimeMessage message = new MimeMessage(mSession);  
       DataHandler handler = new DataHandler(new ByteArrayDataSource(body.getBytes(), "text/plain"));  
       message.setSender(new InternetAddress(user));  
       message.setSubject(subject);  
       message.setDataHandler(handler);  
       if (recipients.indexOf(',') > 0)  
         message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(recipients));  
       else  
         message.setRecipient(Message.RecipientType.TO, new InternetAddress(recipients));  
       smtpTransport.sendMessage(message, message.getAllRecipients());  
     } catch (Exception e) {  
       Log.e("sendMail", e.getMessage());  
     }  
   }  
 }  

MainActivity.java:
 new OAuth2Authenticator(this);  


Friday, April 18, 2014

Android: how to send SMS(text message) programmatically


You need SEND_SMS permission:

Code:
        try {
            SmsManager smsManager = SmsManager.getDefault();
            String sPhoneNumber = "1-222-3333";
            String sMessage = "Hello World";
            smsManager.sendTextMessage(sPhoneNumber, null, sMessage, null, null);
            Toast.makeText(this, "Message has been sent", Toast.LENGTH_LONG).show();
        } catch (Exception e) {
            Toast.makeText(this, "Message send failed", Toast.LENGTH_LONG).show();
        }

Warning: Make sure your app doesn't send SMS without user's knowledge. It will violate the Google's policy and your app will be suspended.

  • Do not send SMS, email, or other messages on behalf of the user without providing the user with the ability to confirm content and intended recipient.


Developer's Guide: What API level should you target for your app? (minSdkVersion)

This is my personal opinion. (Not a correct answer)
(Note: I am writing this on April 18, 2014. Things might be different later)

Please check the latest version distribution dashboard first.

Free app or paid app?
If you are making a paid app, it is OK to target relatively latest versions only. I say version 14 (Android 4.0+) [or version 15 (Android 4.0.3+) since not many devices use version 14 anyways]. There is a big jump from 2.x to 4.x no need an extra work to support 2.x devices when your main income is from the paid app (or in-app-purchase).

Why? I found most paid apps are purchased from the latest devices anyways. People who use older devices are pretty much satisfied with apps they already have and they don't tend to look for paid apps (compared to people who uses the latest devices). I don't make games so I am not sure if above logic applies for games too.

If your income is a free app with ad then you want to cover as much as possible. Still, I believe 2.x devices are not worth it at this time.

Think about testing
You might think the more device support the better. Not always! If your app doesn't work properly on some devices (or some older versions), they will give you bad ratings/reviews and your overall ratings will decrease. One of my apps got more popular when I actually removed the support for older devices for this reason.

Again, this is my personal opinion from my experience.

Wednesday, April 16, 2014

Android: Show notification example


Show notification:

        Intent intent = new Intent(context, ExampleReceiver.class);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
        NotificationManager notificationManager = (NotificationManager) context.getSystemService(context.NOTIFICATION_SERVICE);
        Notification n;
        Bitmap bm = BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_launcher);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            n = new Notification.Builder(context)
                    .setContentTitle("title")
                    .setContentText("Short message\nContent message")
                    .setTicker("ticker message")
                    .setSmallIcon(R.drawable.notification_icon)
                    .setLargeIcon(bm)
                    .setStyle(new Notification.BigTextStyle().bigText("Long message.\nTesting..Testing..\nlooooooooooooooooon message"))
                    .setContentIntent(paramIntent)
                    .setAutoCancel(true).build();
        } else {
            n = new Notification.Builder(context)
                    .setContentTitle("title")
                    .setContentText("Short message\nContent message")
                    .setTicker("ticker message")
                    .setSmallIcon(R.drawable.notification_icon)
                    .setLargeIcon(bm)
                    .setContentIntent(paramIntent)
                    .setAutoCancel(true).getNotification();
        }
        n.flags = n.flags | Notification.FLAG_NO_CLEAR;
        notificationManager.notify(0, n);


Show only the ticker and make it go disappear:

        Intent intent = new Intent(context, ExampleReceiver.class);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
        NotificationManager notificationManager = (NotificationManager) context.getSystemService(context.NOTIFICATION_SERVICE);
        Notification n;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            n = new Notification.Builder(context)
                    .setTicker("ticker message")
                    .setSmallIcon(R.drawable.notification_icon)
                    .setAutoCancel(true)
                    .build();
        } else {
            n = new Notification.Builder(context)
                    .setTicker("ticker message")
                    .setSmallIcon(R.drawable.notification_icon)
                    .setAutoCancel(true)
                    .getNotification();
        }
        notificationManager.notify(0, n);
        notificationManager.cancel(0);