Pull to refresh

Android-приложение поверх других приложений

Reading time3 min
Views44K
В 2013-году Facebook выпустил функцию Chat Heads для своего мессенджера, который позволяет вести переписку, не открывая сам мессенджер путем нажатия на маленькое круглое окошко, которое всегда висит на дисплее даже поверх других приложений:

image

Facebook одним из первых продемонстрировал реализацию «Drawing over other apps». Данную возможность разработчики сейчас используют в различных типах приложений — от боковых смарт-меню до записи экрана. В этой статье, хочу продеменстрировать процесс написания приложения-поверх-других-приложений на примере «анти-шпионской» программы Khameleon.

Для начала обозначу цели приложения, который будет реализован как пример приложения-поверх-других-приложений. Допустим нужно, чтобы рядом стоящие люди не могли с легкостью видеть контент моего смарфтона, пока я им пользуюсь. То есть необходима возможность:

  1. Скрывать часть дисплея, чтобы видеть только необходимую область
  2. Вместо скрытой области, показывать любой желаемый контент (например заданную веб страницу)

Примерно это может выглядеть так:

image

С функционалом приложения определились — теперь приступим к самому туториалу. Для написания приложения-поверх-других-приложений есть две самых главных составляющих:

  • Service, через который ведется основной контроль и логика приложения
  • Layout, который собственно и является GUI

Перед тем как реализовывать эти два компонента, необходимо получить разрешение для приложения-поверх-других-приложений. Для этого в AndroidManifest.xml необходимо добавить:

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

А для API > 23, т.е. Android M, нужно запросить разрешение в главном Activity:
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, ЛЮБОЙ_ЗАДАННЫЙ_INT);

После того как разрешение получено, укажем Layout, который нужно отобразвить на экране (многие элементы убраны для демонстрации):

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/black">
    <View
        android:id="@+id/grab"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:layout_marginRight="10dp"/>
</RelativeLayout>

В корневой RelativeLayout (по вашему усмотрению) можно поместить любые View'шки как обычно. View с id="@+id/grab" нам нужен для того, чтобы показать как динамично менять размеры Layout. То есть через эту View'шку можно будет расстягивать и сжимать корневой RelativeLayout.

Как только у нас есть минимальный Layout, необходимо реализовать самый обычный Service. Единственная особенность сервиса в добавлении ранее созданного Layout:

    @Override
    public void onCreate(){
        super.onCreate();
        manager = (WindowManager) getSystemService(WINDOW_SERVICE);
        params = new WindowManager.LayoutParams(
                screenWidth, // Ширина экрана
                screenHeight, // Высота экрана
                WindowManager.LayoutParams.TYPE_PHONE, // Говорим, что приложение будет поверх других. В поздних API > 26, данный флаг перенесен на TYPE_APPLICATION_OVERLAY
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, // Необходимо для того чтобы TouchEvent'ы в пустой области передавались на другие приложения
                PixelFormat.TRANSLUCENT); // Само окно прозрачное

        // Задаем позиции для нашего Layout
        topParams.gravity = Gravity.TOP | Gravity.RIGHT;
        params.x = 0;
        params.y = 0;

        // Отображаем наш Layout
        rootView = (RelativeLayout) LayoutInflater.from(this).inflate(R.layout.наш_лэйаут, null);
        windowManager.addView(rootView, params);

На данном этапе наш Layout успешно отображается поверх других приложении. Теперь рассмотрим как можно динамично менять размеры нашего Layout:

rootView.findViewById(R.id.grab).setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                if (motionEvent.getAction() == MotionEvent.ACTION_MOVE) {
                        // Обрабатываем позицию касания и обноваляем размер Layout'а
                        params.height = (int) motionEvent.getRawY()
                        manager.updateView(topView, topParams);
                }
                return true;
            }
        });

Если потянуть за край Layout вверх или вниз, его высота соответственно сжимается и расширяется.

Мы рассмотрели минимальные необходимые шаги для написания приложения-поверх-других-приложений. Полный код для приложенения Khameleon, продемонстрированный выше, можно найти здесь.
Tags:
Hubs:
Total votes 18: ↑14 and ↓4+10
Comments3

Articles