How to generate status bar icons from text for an Android App

In the Android framework, the status bar icons have to be packaged with the APK and can’t be created dynamically. It’s ok if you need just a few icons, but what if you need a lot of icons? Here you run into a problem. In my case, I needed a set of icons created from text. Think for example that you would want to create an app that put a small icon on the status bar with the current temp and weather state. something like “45 rainy”. In order to accomplish that you need to combine all the combinations for temp + state. How can you do that?

One nice way to do that is to run code that executes a command-line script of an Image editor program called “ImageMagick”. With that program, you can produce automatically icons from a text and do a lot of other manipulations. Of course, the program must be installed on your computer.

In my case, I run this code from Unit test code, this way I didn’t have to deal with build a traditional Java program with a main method.

@Test
public void printIcons() throws IOException, InterruptedException {
    String iconContent;
    String fileName;
    String[] folders = {"/res/drawable-mdpi/", "/res/drawable-hdpi/", "/res/drawable-xhdpi/", "/res/drawable-xxhdpi/"};
    String[] sizes = {"24x24", "36x36", "48x48", "72x72"};
    String[] weatherStates = {"sunny", "rainy", "clear", "snow"};
    for (String state : weatherStates) {
        for (int i = -30; i < 25; i++) {
            iconContent = String.format("label:%d\n%s", i, state);
            for (int j = 0; j < 4; j++) {
                String path = String.format("/Users/******/Documents/test%s", folders[j]);
                Runtime.getRuntime().exec("mkdir -p " + path).waitFor();
                fileName = String.format("/Users/********/Documents/test%s%s_%d.gif", folders[j], state, (i));
                ProcessBuilder pb = new ProcessBuilder("/opt/local/bin/convert", "-background", "transparent", "-fill", "white", "-font", "Times-Bold-Italic",
                        "-size", sizes[j], "-gravity", "center", iconContent, fileName);
                pb.redirectErrorStream(true);
                Process process = pb.start();
                process.waitFor();
            }

        }
    }
}

In windows, the path for the “convert” may be different. Maybe something with “_full_path_/Magick convert”.

You can get the right R.drawable for the icon in this way:

int id = context.getResources().getIdentifier("clear_18", "drawable", context.getPackageName());
NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
        .setContentTitle("title")
        .setContentText("content")
        .setSmallIcon(id);

Animator didn’t stop after calling end() method

In the last days I run into a problem: In my layout  I have some Views. To each View I used an Animator object in order to perform infinite animation with them. The animation is pretty simple, and this how I created it:

private static AnimatorSet createLeftTiltAnimator(final View v) {
    AnimatorSet animatorSet = new AnimatorSet();
    final ObjectAnimator scaleX = new ObjectAnimator();
    final ObjectAnimator rotateLeft = new ObjectAnimator();
    scaleX.setPropertyName("scaleX");
    scaleX.setRepeatMode(ValueAnimator.REVERSE);
    scaleX.setRepeatCount(ValueAnimator.INFINITE);
    scaleX.setFloatValues(1f, 0.92f);
    scaleX.setTarget(v);
    rotateLeft.setPropertyName("rotation");
    rotateLeft.setRepeatMode(ValueAnimator.REVERSE);
    rotateLeft.setRepeatCount(ValueAnimator.INFINITE);
    rotateLeft.setFloatValues(0, -7.5f);
    rotateLeft.setTarget(v);
    animatorSet.playTogether(scaleX, rotateLeft);
    animatorSet.setDuration(400);
    return animatorSet;
}

Inside my logic, whenever I needed I just called on that Animator object with:

    animatorSet.start();

Well, it worked well, but the problem was when I wanted to end the animation, after I called:

    animatorSet.end();

Some of the Views just stayed with the animation, and I didn’t realise why it happens.
After some analysing, I figured that on some of the View Animators I called twice:

    animatorSet.start();

In this situation, calling end() just didn’t help. So what the solution to prevent such incidents? Well, actually it is very simple, before calling the start() I just added a check if the Animator not already running:

if (!animatoreSet.isRunning())
    viewHolder.iconAnimatoreSet.start();

Android : Draw View on top of other application

Let’s say that you want to write an app that waiting in the background of the device and listening to something that going to happen, in order to pop-up some window offering information or button to perform a specific action.  It can be even something simple like URL namespace that the user just copied into the clipboard and you want to offer him to do something with this URL.

In order to do this, you need to use  WindowManager inside a Service . The WindowManager have the ability to draw a View on top of everything else, with the right LayoutParams, and specifically, the right WindowManager.LayoutParams Flags.

Let’s show how we can do this. First thing you have to use this permission:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

And your target API need to be 22 or below, or you will have to deal with permission issues.
Now, you don’t have to have any Activity at all, but for this tutorial, we will use very simple MainActivity:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        startService(new Intent(this, OverlayButtonService.class));
        finish();
    }
}

Inside the OverlayButtonService (don’t forget to register your Service in the manifest) we have the interesting code. We will use very simple View that we will define in R.layout.button.xml file:

<?xml version="1.0" encoding="utf-8"?>
<ImageView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:padding="16dp"
    android:background="@drawable/btn_bg"
    android:src="@android:drawable/ic_input_add"/>

And the OverlayButtonService code is:

public class OverlayButtonService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.TYPE_PHONE,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                PixelFormat.TRANSLUCENT);
        layoutParams.gravity = Gravity.TOP | Gravity.START;
        layoutParams.x = 50;
        layoutParams.y = 50;
        @SuppressLint("InflateParams")
        View btn = ((LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE))
                .inflate(R.layout.button, null);
        final WindowManager windowManager = ((WindowManager) getSystemService(WINDOW_SERVICE));
        windowManager.addView(btn, layoutParams);
        btn.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                //here you can do whatever action you want after click
                close(v);
                return false;
            }
        });

    }


    public void close(View view){
        try {
            final WindowManager windowManager = ((WindowManager) getSystemService(WINDOW_SERVICE));
            windowManager.removeView(view);
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            stopSelf();
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

What we do is very simple. We define the LayoutParams to draw the Button in the top left corner, and we use FLAG_NOT_FOCUSABLE as we want that the user will be able to keep communicate the screen without we interrupt it.

The output of this code can be something like that:

The orange button is our button and in the moment the user will touch it, the button will disappear.

The full code in this post can be found here:
https://github.com/yshahak/OverlayButton/tree/master

 

How to write simple Android Lock Screen

Hello everyone,

I will start this blog by explaining how you can write simple Lock Screen for Android.

First, it important to understand that officially, Android have built it Lock Screen mechanism, and there isn’t any official API for bypass or replace this Lock Screen.
However, if you want to do that nevertheless, you can use some tricks that can help you achieve your purpose. I hope this post will help other programmers as I know I wasn’t able to find too much information when I tried to build Lock Screen from scratch.

The Lock Screen I’m going to show is quite a real one, as it disables completely the user ability to communicate with the device as long as the Lock Screen is displayed.
In order to do that, I needed to find a way to bypass Home Screen button, and also hide the Status Bar.
We will use very simple UI for this demo:


The basic concept for building your own Lock Screen is to be able to draw your layout on top of any other component in the user device so the user won’t be able to use the device unless he will follow your own security demands, like type in a PIN or swipe left, or any other measure you choose to implement.

So all you need to do is run your Service that will listen to any Screen Off events, and each time this event happens you will push your UI on top of everything.

 

First, there is two Permission must be including in the Manifest.xml file:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

android.permission.SYSTEM_ALERT_WINDOW permission enables you to draw your layout on top of everything in the device.

android.permission.RECEIVE_BOOT_COMPLETED permission needed in order to run your service right after user switch on his device.

Now, the Service need to listen to Intent.ACTION_SCREEN_OFF event. So after the Service will start we will create and register simple BroadcastReciever inside the Service:

BroadcastReceiver screenReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF) && linearLayout == null) {
            init();
        }
    }
};

linearLayout == null is the indicator to check if our layout is already displayed, as we don’t want to draw duplicate layouts again and again.

We will register this receiver in the Service onCreate() method, and will define the LayoutParams we will use for drawing our layout on top of everything:

private LinearLayout linearLayout;
private WindowManager.LayoutParams layoutParams;
private WindowManager windowManager;

@Override
public void onCreate() {
 super.onCreate();
 IntentFilter intentFilter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
 registerReceiver(screenReceiver, intentFilter);

 windowManager = ((WindowManager) getSystemService(WINDOW_SERVICE));
 layoutParams = new WindowManager.LayoutParams(
 WindowManager.LayoutParams.MATCH_PARENT,
 WindowManager.LayoutParams.MATCH_PARENT,
 WindowManager.LayoutParams.TYPE_SYSTEM_ERROR,
 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 
 | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
 | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION,
 PixelFormat.TRANSLUCENT);
}

WindowManager.LayoutParams.TYPE_SYSTEM_ERROR is a window flag that gives us the ability to put our layout on top of everything.
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN simply makes our layout to be drawn on the entire screen.
WindowManager.LayoutParams.SYSTEM_UI_FLAG_HIDE_NAVIGATION hide the device navigation buttons.

Pay attention: we must target targetSdkVersion not higher than 22! otherwise will get Permission exception.

So right now, each time screen off event will be intercepted by our screenReceiver and linearlayout will be null, we will call our init() method:

private void init() {
    linearLayout = new LinearLayout(this);
    windowManager.addView(linearLayout, layoutParams);
    ((LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE)).inflate(R.layout.lock_screen, linearLayout);
    View btnClose = linearLayout.findViewById(R.id.btn_close);
    btnClose.setOnClickListener(this);
}

lock_screen.xml layout is very simple and contains just a simple button:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/colorAccent">

    <Button
        android:id="@+id/btn_close"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="close"
        android:layout_gravity="center"/>

</FrameLayout>

Now, all left is to remove all UI component after the user clicked our botton:

@Override
public void onClick(View view) {
    windowManager.removeView(linearLayout);
    linearLayout = null;
}

MainActivity.class will be super simple. It will just trigger the service.

public class MainActivity extends Activity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        startService(new Intent(this, LockScreenService.class));
        finish();
    }
}

Hope you enjoyed! The whole code for this demo can be found here:
http://github.com/yshahak/LockScreenDemo