Showing posts with label android. Show all posts
Showing posts with label android. Show all posts

Nov 22, 2016

Remapping hardware buttons on Android

I own a Nexus 5 (several, actually) for a while now, and every few months the power button starts to fail (due to usage)? Sometimes it gives off a signal by itself, like it's stuck. And the screen starts to flicker.

A video posted by Jean Caffou (@jeancaffou) on

But recently the power button just died. For two days I had to press it really hard for it to make contact, but now, nothing works. I'm not a hardware guy myself, so to replace the button physically I need to visit my geeky electronics hacker friend. And it sucks begging people for help. So to workaround it for as long as possible let's try software solutions.
Apps - they work without root, sure. But all of the ones I tested were kind of buggy. One worked on proximity sensor, the other one tried to remap the volume button. But the problem with all of them was they were sending the WAKE command to turn the screen on. And I sometimes had no way to turn the screen off. Except for, you know, using another app. The WAKE command was causing a weird bug when I was ending a call.
When calling, the system turns the screen off and on based on proximity (if you have your phone on your face), so sometimes the device didn't go to sleep, EVER, but the screen was off. So WAKE command did nothing, because the phone thinks that it's still on. So I was stuck.
To get out of this loop, I powered up the adb and sent the POWER command instead of WAKE.

adb shell input keyevent KEYCODE_POWER

The first time I sent this, nothing happened, but internally, the phone went to sleep. The second time I sent it, the phone powered back on. So now I know the problem. And do I really need an app to remap the power buttons?

Turns out, I don't.

There is a system file (unfortunately you need root to edit it) which defines all of the keyboard layout including hardware buttons. It is located in:

/system/usr/keylayout/

Inside of this folder there are a bunch of .kl files - you need to find the right one and open it up.
There you will find the definitions such as:

key 114 VOLUME_DOWN
key 115 VOLUME_UP
key 116 POWER

Just change the "VOLUME_" ones to POWER.

Reboot to apply changes.

I'm not sure if I'll be able to power on the device if the battery dies, but it works for locking/unlocking.

Because this file is only read on boot, it might work if the device is plugged in a charger so that it boots and reads the definitions.

May 17, 2014

HttpsURLConnection POST example with NameValuePair and UrlEncodedFormEntity

try {
 String u = "https://...";
 
 // Add your data
 List nameValuePairs = new ArrayList(2);
 nameValuePairs.add(new BasicNameValuePair("id", "1"));
 // ...
 UrlEncodedFormEntity entity = new UrlEncodedFormEntity(nameValuePairs);
 
 URL url = new URL(u);
 HttpsURLConnection request = (HttpsURLConnection) url.openConnection();

 request.setUseCaches(false);
 request.setDoOutput(true);
 request.setDoInput(true);

 request.setRequestMethod("POST");
 OutputStream post = request.getOutputStream();
 entity.writeTo(post);
 post.flush();

 BufferedReader in = new BufferedReader(new InputStreamReader(request.getInputStream()));
 String inputLine, response = "";
 while ((inputLine = in.readLine()) != null) {
  response += inputLine;
 }
 post.close();
 in.close();
} catch (Exception e) {
 Log.e("Your app", "error", e);
}

May 16, 2014

javax.net.ssl.SSLPeerUnverifiedException: No peer certificate

Dealing with the above mentioned Exception has sent me into a wild-goose chase for a quick fix.
Unfortunately the depth of this problem is much larger than I anticipated.

A quick web search suggests that there is a problem with the SSL chain, therefore this would mean that there is a SSL configuration problem on the server, that it's not serving the certificates correctly, eg. in the correct order, or using a self-signed certificate in which case the server also acts as the Certificate Authority.

Some mentioned solutions were to implement your own SSL Client, with a Trust Manager that does absolutely nothing. This is VERY VERY BAD. Don't do it.

Other suggestions were to use your own Trust Keystore (when using self signed certificates, or using "untrusted CAs", like, for example, a university CA).

But if you're using a trusted CA this shouldn't be a problem, right?

For a SSL Server Test you can use Qualys' tool here:
https://www.ssllabs.com/ssltest/analyze.html?d=example.com

Using this test I found no errors on the domain, but there was a notice that the site only works in browsers with SNI support.

SNI (Server Name Indication) is kind of like a Virtual Host for SSL. Usually SSL certificates were issued for an IP address, but having multiple virtual hosts on the same server can cause problems like different browsers getting different SSL certificates from the same server.

You can test this if you have Android <= 2.3.7. Open the browser and navigate to the site. If you get a certificate warning, check the certificate.

So, obviously, if old browsers have this problems, there are going to be problems in the code too.
The answer here summarizes it. The best workaround seems to be to use another library, which supports SNI.
http://stackoverflow.com/questions/21956663/why-does-android-get-the-wrong-ssl-certificate-two-domains-one-server

The most likely cause for this problem is that the server uses Server Name Indication to choose which certificate to send. If the client doesn't support SNI, the server cannot choose which certificate to send during the SSL/TLS handshake (before any HTTP traffic is sent). SNI is required when you want to use multiple certificates on the same IP address and port, but not all clients support it (notoriously, IE on any version of Windows XP, and a number of mobile browsers).

You're also visibly using the Apache HTTP Client library (not HttpsURLConnection, for which there can be SNI support with some Android versions. Support for SNI in the Apache HTTP Client library is quite recent, and certainly hasn't made it into the Android stack.You may find the workaround described in this article useful (although it seems only to work for Android 4.2+).

Another two options would be:
  • to use a distinct IP address for each host (so as not to need SNI), if you're in control of server, or
  • to use another HTTP Client library (e.g. HttpsURLConnection).





Other notes:

Installing the INTERMEDIATE certificate into Apache on Ubuntu/Debian systems
Credits: https://gist.github.com/mgedmin/7124635
    SSLCertificateFile /etc/ssl/certs/subdomain.example.com.crt
    SSLCertificateKeyFile /etc/ssl/private/subdomain.example.com.pem
    SSLCertificateChainFile /etc/ssl/certs/startssl-class1-intermediate.crt

Mar 25, 2013

Java, PHP :: RSA Asymmetric Encryption

Information Security and Privacy was a pretty fun and interesting class I had in college, I learned quite a lot of interesting new stuff, but when it came down to it, in practice, I had some problems implementing encrypted communication between Android (Java) frontend and PHP backend because of a tiny little detail.
Algorithm of choice was RSA and the most important part is to use "RSA/ECB/PKCS1PADDING" algorithm when calling Cipher instance in Java, other stuff is pretty straightforward.

RSA works like this.
Imagine a scenario where Alice sends an encrypted message to Bob.
As you can see, Alice encrypts data with Bob's public key, and only Bob can decrypt it, because only he has his private key.

Here's the Java code (Warning, some Android elements ahead)
public class Encryption {
 private static Key  privKey;
 private static PublicKey pubKey;
 private static PublicKey servPub;
 
 private static String  tag  = "Encryption";
 private static String[]  alg  = {"RSA","RSA/ECB/PKCS1PADDING"};
 private static String  hash  = "SHA1";
 private static String  serverPubKeyB64 = ""; //Bob's public key here
 
 
 public static String encrypt(String data) {
  return b64(encrpyt(data.getBytes()));
 }
 
 public static byte[] encrpyt(byte[] data) {
  genKey();
  try {
   Cipher c1 = Cipher.getInstance(alg[1]);
         c1.init(Cipher.ENCRYPT_MODE, getServPub());
         return c1.doFinal(data);
  } catch(Exception e) {
   Log.e(tag, "encrpyt", e);
  }
  return new byte[0];
 }
 
 public static void genKey() {
  if (privKey == null || pubKey == null) {
   Log.i(tag, "generating key");
   try {
    KeyPairGenerator kpg = KeyPairGenerator.getInstance(alg[0]);
    kpg.initialize(1024);
    KeyPair kp = kpg.generateKeyPair();
    privKey = kp.getPrivate();
    pubKey = kp.getPublic();
   } catch (Exception e) {
    Log.e(tag, "genKey", e);
   }
  }
 }
 
 public static PublicKey getServPub() {
  if(servPub == null) {
   try {
    byte[] encodedPublicKey = b64decode(serverPubKeyB64);
    
    KeyFactory keyFactory = KeyFactory.getInstance(alg[0]);
    X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(encodedPublicKey);
    PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
    
    return publicKey;
   } catch(Exception e) {
    Log.e(tag, "getServPub", e);
   }
  }
  return servPub;
 }
 
 public static PublicKey getPubKey() {
  genKey();
  return pubKey;
 }
 
 public static String getPubKeyB64() {
  PublicKey key = getPubKey();
  if(key != null) {
   return b64(getPubKey().getEncoded());
  } else {
   Log.e(tag, "getPubKeyB64 - key is null");
   return "";
  }
 }
 
 public static String b64(byte[] b) {
  return Base64.encodeToString(b, Base64.DEFAULT);
 }
 
 public static byte[] b64decode(String str) {
  return Base64.decode(str, Base64.DEFAULT);
 }
 
 public static String sha1(String data) {
  return sha1(data.getBytes());
 }
 
 
 public static String sha1(byte[] data) {
  return formatString(sha1bytes(data));
 }
 
 public static byte[] sha1bytes(byte[] data) {
  try {
   MessageDigest md = MessageDigest.getInstance(hash);
   return md.digest(data);
  } catch(Exception e) {
   Log.e(tag, "sha1", e);
   return new byte[0];
  }
 }
 
 @SuppressLint("DefaultLocale")
 public static String formatString(byte[] data) {
  StringBuilder sb = new StringBuilder();

  for (byte b : data) {
      sb.append(String.format("%02X", b));
  }
  
  return sb.toString().toLowerCase();
 }
}
And the PHP code:
class Encryption {

    public $pubkey = '...';

    public $privkey;
 
 public function __construct() {
  $this->privkey = openssl_pkey_get_private(file_get_contents('....pem'));
 }

    public function encrypt($data) {
        if (openssl_public_encrypt($data, $encrypted, $this->pubkey))
            $data = base64_encode($encrypted);
        else
            throw new Exception('Unable to encrypt data.');

        return $data;
    }

    public function decrypt($data) {
        if (openssl_private_decrypt(base64_decode($data), $decrypted, $this->privkey))
            $data = $decrypted;
        else
            $data = '';

        return $data;
    }
}

Oct 6, 2012

Android Facebook SDK login error #1118578

The error is caused because the activity has set the launch mode to singleInstance.

From StackOverflow where I posted the question:


Everything was working fine, until I wanted to check what happens if the user clicks the Login with Facebook button with no internet connection available.
A toast showed up saying:
Login failed. Please contact the maker of this app and ask them to report issue #1118578 to Facebook.
In the logcat I have:
D/Facebook-authorize(2824): Login canceled by user.
I tried to get rid of this toast and display my own error message in the onCancel() method - but I can't find a way to disable that toast. When I enabled WiFi again, single sign on didn't work anymore!
What could be the cause of this?
    private void fbLogin() {
    facebook.authorize(this, new String[] { "email" }, new DialogListener() {
        @Override
        public void onComplete(Bundle values) {
            app.save("fb_token", facebook.getAccessToken());
            app.save("fb_expire", facebook.getAccessExpires());
            mAsyncRunner.request("me", new meRequestListener());
        }
        @Override
        public void onFacebookError(FacebookError error) {}
        @Override
        public void onError(DialogError e) {}
        @Override
        public void onCancel() {}
    });
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    facebook.authorizeCallback(requestCode, resultCode, data);
}
EDIT:
To answer my own question, removing android:launchMode="singleInstance" From the manifest in the  resolved the #1118578 issue.
However, I do need the android:launchMode="singleInstance" for the twitter OAuth login, as the browser passes the data back to the activity via new Intent. If I do not provideandroid:launchMode="singleInstance" in the manifest, a new activity is launched and it does not recieve the OAuth verifier.
Is there any way around this, to use Facebook and Twitter login in the same activity? The only solution I think of is to use a dummy activity for Twitter4j with singleInstance.


See original thread here:
http://stackoverflow.com/questions/12759261/android-facebook-sdk-login-error-1118578

Apr 14, 2012

Google Maps API Key for Android (Eclipse, Windows)

There are two main types of keystores in Eclipse - one is for debugging (a keystore with no password) and the other one is when you create a new one yourself, with a password.

To make Google Maps work on your Android app, you need to enter a MD5 fingerprint of your keystore in Google Maps API signup: http://code.google.com/android/maps-api-signup.html

And here's how you get your keystore's MD5 fingerprint in Windows.

  1. You probably already have JDK installed. Find out your Java bin directory. Mine is:
    C:\Program Files\Java\jdk1.6.0_24\bin
  2. Enter this directory path in your PATH Environment variable (see picture).
  3. Start "cmd" and navigate to your
    C:\Users\<yourusername>\.android
    directory
  4. Type this command:
    keytool -list -keystore debug.keystore
  5. The password is empty, just hit enter
  6. You're done. Copy the MD5 fingerprint in that url, you get an API key, which you use in your Android MapView Layout file.
    android:apiKey="your api key here"

Nov 19, 2011

Android :: wrong image / data in ListView rows

Android's ListView reuses list items when they aren't need anymore. For this reason, you need to make sure all views that should change, will actually get changed. Your problem is that if you don't find a drawable for the current list item, you don't empty nor hide the ImageView. You should do thumb.setImageDrawable(null) in that case, or thumb.setVisibility(View.GONE).
- From stackoverflow

Example code:


 @Override
     public View getView(int pos, View v, ViewGroup parent) {
      TimetableViewHolder timetableHolder;
         if (v == null) {
             LayoutInflater vi = ((Activity)context).getLayoutInflater();
             v = vi.inflate(R.layout.timetable_item, parent, false);
             timetableHolder = new TimetableViewHolder();
             timetableHolder.rel = (TextView) v.findViewById(R.id.rel);
             timetableHolder.time = (TextView) v.findViewById(R.id.time);
             timetableHolder.icon = (ImageView) v.findViewById(R.id.icon);
             v.setTag(timetableHolder);
         } else {
          timetableHolder = (TimetableViewHolder) v.getTag(); 
         }

         TimetableItem item = items.get(pos);

         if(item != null) {
          timetableHolder.rel.setText(item.rel);
          timetableHolder.time.setText(item.time);
          if(item.url.equals("ERROR")) {
           timetableHolder.icon.setImageResource(android.R.drawable.ic_menu_close_clear_cancel);
          } else if(item.url.equals("PRESTOP")) {
           timetableHolder.icon.setImageResource(android.R.drawable.ic_menu_directions);
          } else { // THIS IS IMPORTANT
           timetableHolder.icon.setImageResource(android.R.drawable.ic_dialog_info);
          }
         }

         return v;
     }

Android :: google maps (MapView) hacks, tricks, workarounds

MapView isn't very developer friendly now is it?

Here are some hacks I've had to work with:

1) When tapping on the overlay, the app crashes if you're trying to display the dialog.
Unable to add window -- token null is not for an application
Yes, this is due to context. Especially if you're trying to do this in a seperate thread or AsyncTask.
Turns out the context you need to pass to your dialog is mapView.getContext();
AlertDialog.Builder dialog = new AlertDialog.Builder(mapView.getContext());




2) Displaying only the overlays which are within map bounds
Oh yeah, several problems here. You've probably come across at least one of these:

  • Wrong map bounds in onCreate (0, 360000000),
    Yeah, in onCreate those haven't been calculated yet. onStart doesn't help either. Try this:
Runnable waitForMapTimeTask = new Runnable() {
  public void run() {
    if(mapView.getLatitudeSpan()==0||mapView.getLongitudeSpan()== 360000000) {
      mapView.postDelayed(this, 100);
    } else {
      redrawMarkers(); // draw here
    }
  }
};
mapView.postDelayed(waitForMapTimeTask, 100);

You create a new thread and wait until you get the right bounds. Recursively call it again.
  • Map bounds ???
    Don't worry, it's simple.
public Rect getMapBounds() {
return new Rect(
mapView.getMapCenter().getLongitudeE6() - mapView.getLongitudeSpan()/2,
mapView.getMapCenter().getLatitudeE6() - mapView.getLatitudeSpan()/2,
mapView.getMapCenter().getLongitudeE6() + mapView.getLongitudeSpan()/2,
mapView.getMapCenter().getLatitudeE6() + mapView.getLatitudeSpan()/2
);
}

...
if(!s.drawn && rect.contains(point.getLongitudeE6(), point.getLatitudeE6())) {


  • Yeah, okay, but what about panning / zooming?
    Well, there are no methods, like onPan or onZoom, but some people found their way around this problem. There is no perfect solution, you'll see.
    Check these links out:
  1. http://stackoverflow.com/questions/2328650/how-can-i-detect-if-an-android-mapview-has-been-panned-or-zoomed
  2. http://bricolsoftconsulting.com/2011/10/31/extending-mapview-to-add-a-change-event/
  3. http://stackoverflow.com/questions/3567420/how-to-catch-that-map-panning-and-zoom-are-really-finished
  • Zoom in on double tap?
    Click here: http://dev.kafol.net/2011/11/how-hard-is-it-to-make-simple-zoom-in.html.
  • Overlays don't get drawn immediately!
    Try this:
    mapView.postInvalidate();
    or this:
    mapView.invalidate();

    But keep in mind, that invalidate() needs to be called from an UI! If you're trying to get it working from a thread, use postInvalidate()!
  • MapView java.util.ConcurrentModificationException when adding new overlays
    Not sure if I solved this one, but it seems to work now. I read somewhere that this could happen if you add overlays in a non UI thread. I moved the 
    Nope, sorry, this one was my bad. I was doing some crazy async sorting and all hell broke loose.
itemizedOverlay.populateNow();
mapOverlays.add(itemizedOverlay);
mapView.postInvalidate();

From doInBackground to  onPostExecute in  AsyncTask.


You could also run something in a UI thread like this:

runOnUiThread(new Runnable() {
      @Override
       public void run() {
           //do stuff here
       }
});



Nov 18, 2011

Android :: google maps on double tap zoom in

How hard is it to make a simple zoom in call on double tap in MapView in Android?

Not very.

How hard is it to get the information on how to do it?

Very.

Here's what you probably didn't know:
You need to extend the MapView and use this extended class in the Android XML layout file.
In the extended class you instantiate the gesture detector and set on double tap listener.
In the Map Activity you implement OnGestureListener and OnDoubleTapListener.

Example:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:gravity="center_horizontal" >
    
 <net.kafol.vlaki.ExtMapView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/mapview"
     android:layout_width="fill_parent"
     android:layout_height="fill_parent"
     android:clickable="true"
     android:enabled="true"
     android:apiKey=""
 />

</RelativeLayout>
package net.kafol.vlaki;

import android.content.Context;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.GestureDetector.OnDoubleTapListener;
import android.view.MotionEvent;
import android.view.GestureDetector.OnGestureListener;
import com.google.android.maps.MapView;

public class ExtMapView extends MapView {
 private Context context;
 private GestureDetector gestureDetector;

 public ExtMapView(Context c, AttributeSet attrs) {
  super(c, attrs);
  context = c;

  gestureDetector = new GestureDetector((OnGestureListener) context);
  gestureDetector.setOnDoubleTapListener((OnDoubleTapListener) context);
 }

 public boolean onTouchEvent(MotionEvent ev) {
  if (this.gestureDetector.onTouchEvent(ev))
   return true;
  else
   return super.onTouchEvent(ev);
 }
}
public class Map extends MapActivity implements OnGestureListener, OnDoubleTapListener {
...
 @Override
 public boolean onDoubleTap(MotionEvent e) {
     int x = (int)e.getX(), y = (int)e.getY();;
     Projection p = mapView.getProjection();
     mapView.getController().animateTo(p.fromPixels(x, y)); // zoom in to a point you tapped 
     mapView.getController().zoomIn();
  return true;
 }

Android : Checkbox ListView - (un) check all

This recycling of views in android is pretty insane. Pain to work with.

So the problem is how to check all check boxes in a list view, if a list view only contains the visible items.

Iterating through the adapter or array of holders was pretty unreliable, some checkboxes weren't affected.

What I did was actually add an attribute to the data object and iterate through this object array (the same way it get's added in the adapter)-

Here are some functions:
 public void toggleCheck(Boolean val) {
  
  for(Station s : stations.list) {
   s.checked = val;
   editor.putBoolean("PF_"+s.getID(), val);
  }
  
  for(int i=0 ; i < lv.getChildCount() ; i++) {
   CheckBox cb = (CheckBox) lv.getChildAt(i).findViewById(R.id.cb);
   cb.setChecked(val);
  }

  editor.apply();
 }
 
    private class StationCBViewHolder {
        public CheckBox cb;
        public Station s;
    }
 
 private class StationListAdapter extends ArrayAdapter {
     private ArrayList items;
     private Context context;
     
     public StationListAdapter(Context context, int tvResId, ArrayList items) {
         super(context, tvResId, items);
         this.items = items;
         this.context = context;
     }

     @Override
     public View getView(int pos, View v, ViewGroup parent) {
      final StationCBViewHolder holder;
      final Station item = items.get(pos);
      
         if (v == null) {
             LayoutInflater vi = ((Activity)context).getLayoutInflater();
             v = vi.inflate(R.layout.stationcheckboxitem, parent, false);
             
             holder = new StationCBViewHolder();
             holder.cb = (CheckBox) v.findViewById(R.id.cb);
             holder.s = item;
             
             item.checked = prefs.getBoolean("PF_"+item.getID(),true);
             
             //holder.cb.setTag(holder);
             holder.cb.setTag(item);
             holder.cb.setChecked(item.checked);
             holder.cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
     @Override
     public void onCheckedChanged(CompoundButton v, boolean isChecked) {
      Station s = (Station) v.getTag();
      editor.putBoolean("PF_"+s.getID(), isChecked);
      editor.apply();
     }
    });
                        
             v.setTag(holder);
         } else {
          holder = (StationCBViewHolder) v.getTag(); 
         }

         holder.cb.setText(item.toString());
         holder.cb.setChecked(item.checked);

         return v;
     }

 }


Full code here: http://pastebin.com/8NMbHqRV


The code still has some bugs related to SharedPreferences and SharedPreferences.Editor, but at least it checks and unchecks all checkboxes.