Поиск по этому блогу

вторник, 24 декабря 2013 г.

Создание экрана блокировки для Android

Всем привет, сегодня я хотел бы рассказать вам как делать экран блокировки для Android, я думаю многие задумывались как это делается, оказывается что не сильно то и сложно. (: Всего лишь нужно создать сервис, бродкаст ресивер и активность которая это будет отображать. Звучит конечно устрашающе но на самом деле все намного проще.
И так приступим, для начала мы создадим разметку в которой у нас будет два объекта (две картинки), первая будет объектом который будет перетаскиваться, а второй объектом на который будут перетаскивать что бы разблокировать экран, ну и еще у нас будут часы которые будут показывать время, но это не сильно важный элемент. (:

Выглядеть все в купе буде вот так:


И так, разметка!

main.xml

<?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:background="#161616"
    android:orientation="vertical">

    <RelativeLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >

        <ImageView
            android:id="@+id/apple"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/apple" />

        <TextView
            android:id="@+id/textView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_alignParentTop="true"
            android:textSize="30dp"
            android:text="TextView" />

    </RelativeLayout>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:id="@+id/homelinearlayout">

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/droid"
            android:visibility="visible"
            android:id="@+id/droid"
            />
   </LinearLayout>
</RelativeLayout>


Что у нас там такое в этой разметке, внутри RelativeLayout у нас два LinearLayout которые хранят два разных объекта, в первом у нас объект который будет перемещаться по экрану, а во втором статичный объект, это мы сделали что бы при перемещении яблока дроид у нас не перемещался вместе с яблоком.

Собственно тут у нас имеется два ImageView и TextView, пока вроде бы все просто и понятно, продолжим.

Дальше нам нужно создать сервис для отслеживания состояния телефона, если включен экран то мы не отображаем наш лок скрин, так как если мы это не будем проверять то он у нас будет отображаться постоянно и мешать. 

MyService.java
import android.app.KeyguardManager;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.IBinder;
import receiver.lockScreenReeiver;
public class MyService extends Service {

        BroadcastReceiver mReceiver;

        @Override
        public IBinder onBind(Intent arg0) {

                // TODO Auto-generated method stub
                return null;
        }

        @Override
        public void onCreate() {

                KeyguardManager.KeyguardLock k1;
                KeyguardManager km = (KeyguardManager) getSystemService(KEYGUARD_SERVICE);
                k1 = km.newKeyguardLock("IN");
                k1.disableKeyguard();
                // проверяем в фоне все время работы экрана
                IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
                // фильтруем на появление флага выключения экрана
                filter.addAction(Intent.ACTION_SCREEN_OFF);
                // если это так то запускаем рессивер
                mReceiver = new lockScreenReeiver();
                registerReceiver(mReceiver, filter);
                super.onCreate();
        }

        @Override
        public void onDestroy() {

                unregisterReceiver(mReceiver);
                super.onDestroy();
        }
}


Из комментариев ясно что тут мы проверяем флаг состояния телефона, и при помощи метода .filter() проверяем флаг ACTION_SCREEN_OFF который присылает или true или false соответственно. По изменению true для этого флага у нас запускается Broadcast Receiver.

BroadcastReceiver — это объект, который начинает выполнять действия, по получению какого нибудь сигнала (Intent). Service в отличии от BroadcastReciever работает сразу после того как его запустили.

Но для начала мы создадим активность которая будет запускать наш сервис при старте программы. Как вы читали выше сервис работает постоянно, соответственно если вы запустили его раз и не выключили то он будет работать до самого удаления программы. 

StartLockScreen.java
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
public class StartLockScreen extends Activity {

        @Override
        protected void onCreate(Bundle savedInstanceState) {

                super.onCreate(savedInstanceState);
                // запускаем сервис
                startService(new Intent(this, MyService.class));
                // убиваем активность
                finish();
        }
}


Тут все просто, при старте программы у нас изначально запускается этот класс и начинается отслеживание состояния телефона. Она запускается первой как скрытая активность, это вы увидите в самом конце когда я покажу вам AndroidManifest.

Теперь нам нужен Broadcast Receiver так как без него ничего не будет происходить. Выглядит он вот так:

lockScreenReciver.java
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import com.lockscreen.LockScreenAppActivity;
public class lockScreenReeiver extends BroadcastReceiver {

        public static boolean wasScreenOn = true;

        public static boolean wasScreenOn = true;

        @Override
        public void onReceive(Context context, Intent intent) {

                // если экран выключен то запускаем наш лок скрин
                if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
                        wasScreenOn = false;
                        Intent intent11 = new Intent(context, LockScreenAppActivity.class);
                        intent11.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        context.startActivity(intent11);
                }
        }
}


В этом классе мы постоянно проверяем что приходит нам с сервиса, и вот тут собственно мы ждем когда придет в рессивер тот флаг (ACTION_SCREEN_OFF), если он приходит то мы запускаем нашу активность с самим экраном, в котором мы обрабатываем поведение картинок. Давайте же посмотрим как выглядит этот класс.

Это самый большой класс во всем проекте, в нем мы инициализируем все переменные, обрабатываем все касания к экрану, собственно все что должно происходить на экране для нашего приложения.

LockScreenAppActivity.java
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup.MarginLayoutParams;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.RelativeLayout.LayoutParams;
import android.widget.TextView;
import java.util.Calendar;
public class LockScreenAppActivity extends Activity {

        // наши часы
        private TextView clock;
        // наши картинки
        private ImageView droid, home;
        // отслеживаем в этот массив координаты пальца
        private int[] droidpos;
        // и передаем в этим переменные
        private int home_x, home_y;
        // лайот на котором находится весь лок скрин
        private LayoutParams layoutParams;
        // размеры экрана
        private int windowheight;
        private int windowwidth;

        @Override
        public void onCreate(Bundle savedInstanceState) {

                super.onCreate(savedInstanceState);
                // делаем наше экран полностью залоченым
                getWindow().addFlags(
                                WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                                                | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                                                | WindowManager.LayoutParams.FLAG_FULLSCREEN);
                setContentView(R.layout.main);
                // идентифицируем наще яблоко
                droid = (ImageView) findViewById(R.id.apple);
                if ((getIntent() != null) && getIntent().hasExtra("kill")
                                && (getIntent().getExtras().getInt("kill") == 1)) {
                        finish();
                }
                try {
                        // инициализация рисивера
                        startService(new Intent(this, MyService.class));
                        // стартуем отслеживание состояния телефона
                        StateListener phoneStateListener = new StateListener();
                        // узнаем все сервисы которые есть
                        TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
                        // слушаем когда телефон уходит в сон и включаем нашего блокировщика
                        telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
                        // хватаем размеры экрана и растягиваем под него локскрин
                        windowwidth = getWindowManager().getDefaultDisplay().getWidth();
                        windowheight = getWindowManager().getDefaultDisplay().getHeight();
                        // выставляем позицию нашего яблока, что бы оно было по центру экрана
                        MarginLayoutParams marginParams2 = new MarginLayoutParams(droid.getLayoutParams());
                        marginParams2.setMargins((windowwidth / 24) * 10, ((windowheight / 32) * 8), 0, 0);
                        // ставим по центру релайтив лайота
                        RelativeLayout.LayoutParams layoutdroid = new RelativeLayout.LayoutParams(marginParams2);
                        droid.setLayoutParams(layoutdroid);
                        // дальше инициализируем наш текствью в который будем выводить время
                        clock = (TextView) findViewById(R.id.textView1);
                        // инициализируем линейр в котором будет дроид
                        LinearLayout homelinear = (LinearLayout) findViewById(R.id.homelinearlayout);
                        homelinear.setPadding(0, 0, 0, 0);
                        // инициализируем самого дроида
                        home = (ImageView) findViewById(R.id.droid);
                        // выставляем его положение, в нашем случае отступ от низа экрана
                        MarginLayoutParams marginParams1 = new MarginLayoutParams(home.getLayoutParams());
                        marginParams1.setMargins((windowwidth / 24) * 10, 0, (windowheight / 32) * 8, 0);
                        // отслеживаем его перемещение
                        LinearLayout.LayoutParams layout = new LinearLayout.LayoutParams(marginParams1);
                        home.setLayoutParams(layout);
                        // вложеная функция обработки перемещения имейджвью яблока по экрану
                        droid.setOnTouchListener(new View.OnTouchListener() {

                                @Override
                                public boolean onTouch(View v, MotionEvent event) {

                                        layoutParams = (LayoutParams) v.getLayoutParams();
                                        // обрабатываем позиции
                                        switch (event.getAction()) {
                                        // нажал на экран
                                                case MotionEvent.ACTION_DOWN: {
                                                        int[] hompos = new int[2];
                                                        droidpos = new int[2];
                                                        home.getLocationOnScreen(hompos);
                                                        home_x = hompos[0];
                                                        home_y = hompos[1];
                                                }
                                                        break;
                                                // перемещаем по экрану яблоко за пальцем
                                                case MotionEvent.ACTION_MOVE: {
                                                        int x_cord = (int) event.getRawX();
                                                        int y_cord = (int) event.getRawY();
                                                        if (x_cord > (windowwidth - (windowwidth / 24))) {
                                                                x_cord = windowwidth - ((windowwidth / 24) * 2);
                                                        }
                                                        if (y_cord > (windowheight - (windowheight / 32))) {
                                                                y_cord = windowheight - ((windowheight / 32) * 2);
                                                        }
                                                        layoutParams.leftMargin = x_cord;
                                                        layoutParams.topMargin = y_cord;
                                                        droid.getLocationOnScreen(droidpos);
                                                        v.setLayoutParams(layoutParams);
                                                        if ((((x_cord - home_x) <= ((windowwidth / 24) * 5)) && ((home_x - x_cord) <= ((windowwidth / 24) * 4)))
                                                                        && ((home_y - y_cord) <= ((windowheight / 32) * 5))) {
                                                                v.setVisibility(View.GONE);
                                                                finish();
                                                        }
                                                }
                                                        break;
                                                // убрал палец с экрана, возаращаем на стартовую позицию
                                                case MotionEvent.ACTION_UP: {
                                                        int x_cord1 = (int) event.getRawX();
                                                        int y_cord2 = (int) event.getRawY();
                                                        if ((((x_cord1 - home_x) <= ((windowwidth / 24) * 5)) && ((home_x - x_cord1) <= ((windowwidth / 24) * 4)))
                                                                        && ((home_y - y_cord2) <= ((windowheight / 32) * 5))) {
                                                                System.out.println("home overlapps");
                                                                System.out.println("homeee" + home_x + "  " + (int) event.getRawX() + "  "
                                                                                + x_cord1 + " " + droidpos[0]);
                                                                System.out.println("homeee" + home_y + "  " + (int) event.getRawY() + "  "
                                                                                + y_cord2 + " " + droidpos[1]);
                                                        } else {
                                                                layoutParams.leftMargin = (windowwidth / 24) * 10;
                                                                layoutParams.topMargin = (windowheight / 32) * 8;
                                                                v.setLayoutParams(layoutParams);
                                                        }
                                                }
                                        }
                                        return true;
                                }
                        });
                        // узнаем который час
                        Calendar c = Calendar.getInstance();
                        // выводим время
                        clock.setText("Time: " + c.get(Calendar.HOUR_OF_DAY) + " : " + c.get(Calendar.MINUTE));
                } catch (Exception e) {
                        // TODO: handle exception
                }
        }

        // класс слушатель тач евентов
        class StateListener extends PhoneStateListener {

                @Override
                public void onCallStateChanged(int state, String incomingNumber) {

                        super.onCallStateChanged(state, incomingNumber);
                        switch (state) {
                        // если яблоко коснулось дроида то выключаем лок скрин
                                case TelephonyManager.CALL_STATE_OFFHOOK: {
                                        finish();
                                }
                                        break;
                        }
                }
        }
}


В шапке мы инициализируем все наши переменные, в onCreate() мы присваиваем им нужные параметры, а так же вызываем рессивер и класс слушатель для отслеживания касаний к экрану. В том же onCreate() мы отслеживаем нажатия на экран вложенным методом onTouch() который обрабатывает все касания (у нас оно одно). 

Так же в методе onCreate() мы задали разметку для нашего лейаута как и где будут располагаться картинки, и задали показ времени. И сделали так что бы все элементы экрана кроме самого блокировщика экрана были скрыты.

Я постарался прокомментировать каждую строчку кода, надеюсь что сильно детального описания не нужно.

Осталось только немного изменить наш AndroidManifest, в нем мы добавим наши активности, так же определим когда запускать BroadcastReceiver и Service. И нужно не забыть про разрешения для девайса на запуск всего этого зоопарка классов.

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.lockscreen"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="8" />

    <application
        android:icon="@drawable/lockicon"
        android:label="@string/app_name">
        <activity android:label="@string/app_name"
          android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
          android:screenOrientation="portrait"
            android:name=".LockScreenAppActivity"
           >
            <intent-filter >
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>


        <activity
            android:name=".StartLockScreen"
            android:theme="@style/Theme.Transparent" >
        </activity>

        <service android:name=".MyService" >
        </service>

        <receiver
            android:enabled="true"
            android:name="receiver.lockScreenReeiver" >
            <intent-filter >
                <action android:name="android.intent.action.BOOT_COMPLETED" />

            </intent-filter>
        </receiver>
    </application>

    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>

</manifest>


Ну вот вроде бы и все, теперь когда вы нажмете кнопку Compile, у вас должен запуститься ваш лок скрин который после выключения экрана будет появляться каждый раз вместо стандартного.

Да я забыл сказать что, что бы разблокировать нада провести яблоко к андроиду, и тогда разблокируется экран, а то мало ли, не хочется что бы потом люди кричали что заблокировали экран и не смогли разблокировать и пришлось перепрошивать телефон (:

Исходники
DropBox GitHub