- vừa được xem lúc

[Android] Hiển thị Activity trên màn hình khóa - Show Activity over lock screen

0 0 93

Người đăng: Đoan Trung

Theo Viblo Asia

Xin chào các bạn, Hôm nay là 30 tết rồi, ngồi ngắm trời chờ đón giao thừa, trong lúc rảnh rỗi mình quyết định ngồi viết bài sau 1 thời gian vắng bóng.
Tiện gần đây mình được hỏi về vấn đề "Làm thế nào để hiển thị 1 Activity trên màn hình khóa" như các ứng dụng Zalo, Whatsapp làm để hiển thị Activity khi có cuộc gọi đến. Dưới đây là ảnh InComingCall Activity của Zalo



Trong các version android trước đây, để hiển thị 1 thứ gì đó trên màn hình khóa, các bạn có thể nghĩ đến cách hiển thị 1 view lên màn hình bằng WindowManager với các type và flags phù hợp. Nhưng với các phiên bản Android gần đây, ví dụ android 10(Android Q) hoặc android 11(Android R) thì hướng đi đó không còn hoạt động, Hôm nay mình sẽ hướng dẫn các bạn 1 cách để hiển thị Activity trên màn hình khóa bằng cách dùng Notification với High priority, Call category cùng với "Full-Screen intent".
Để hiển thị được 1 Activity trên màn hình khóa, mình sẽ làm các bước sau:

1. Tạo 1 ForegroundService để hiển thị 1 notification, service này sẽ được khởi chạy khi có cuộc gọi đến

2. Tạo notification với High priority, Call Category và Full-Screen intent.

3. Gửi broadcast để kích hoạt full screen intent.

4. Tạo Full screen Activity, đây chính là màn hình sẽ được hiển thị trên màn hình khóa

5. Khai báo Activity, Service trong file AndroidManifest, đồng thời khai báo các quyền cần sử dụng.

6. Test ứng dụng.

Cùng đi vào chi tiết nào

B1: Tạo 1 ForegroundService để hiển thị 1 notification, service này sẽ được khởi chạy khi có cuộc gọi đến

Để tạo Service ta sẽ tạo 1 class có tên là IncomingCallService và extends Service, sau đó trong phương thức onStartCommand chúng ta sẽ viết code như sau:

 public class IncomingCallService extends Service { public int onStartCommand(Intent intent, int flags, int startId) { Notification notification = buildNotification(); startForeground(1, notification); return START_NOT_STICKY; } }

Trong phương thức onStartCommand, các bạn nhìn thấy mình tạo 1 notification và gọi phương thức startForeground để hiển thị notification đó với id =1, Service là 1 thành phần chính của android, nên nếu bạn nào chưa biết rõ về Service thì có thể tham khảo tại [Đây](https://developer.android.com/guide/components/services)

B2: Tạo notification với High priority, Call category và Full screen intent

Mình sẽ implement phương thức buildNotification, phương thức này sẽ trả về 1 notification với High priority, Call category và Full-Screen intent.

 private Notification buildNotification() { Intent fullScreenIntent = new Intent(this, IncomingCallActivity.class); PendingIntent fullScreenPendingIntent = PendingIntent.getActivity(this, 0, fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT); NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_notification_icon) .setContentTitle("Incoming call") .setContentText("(919) 555-1234") .setPriority(NotificationCompat.PRIORITY_HIGH) .setCategory(NotificationCompat.CATEGORY_CALL) // Use a full-screen intent only for the highest-priority alerts where you // have an associated activity that you would like to launch after the user // interacts with the notification. Also, if your app targets Android 10 // or higher, you need to request the USE_FULL_SCREEN_INTENT permission in // order for the platform to invoke this notification. .setFullScreenIntent(fullScreenPendingIntent, true); notificationBuilder.setAutoCancel(true); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { notificationManager.createNotificationChannel(new NotificationChannel("123", "123", NotificationManager.IMPORTANCE_HIGH)); notificationBuilder.setChannelId("123"); } return notificationBuilder.build(); }

Mình cùng đi chi tiết về phương thức build notification nào. Đầu tiên mình tạo 1 PendingIntent, PendingIntent này sẽ start InComingCallActivity khi người dùng click notification. bạn có thể đọc thêm về PendingIntent tại [đây](https://developer.android.com/reference/android/app/PendingIntent) .
Tiếp theo đó mình tạo notification bằng NotifcationCompat.Buider, chúng ta cung cấp icon, title, text..vv. Quan trọng nhất ở đây là bạn phải có 3 dùng code dưới đây để set priority, category và full screen intent cho notification.
setPriority(NotificationCompat.PRIORITY_HIGH)
setCategory(NotificationCompat.CATEGORY_CALL)
setFullScreenIntent(fullScreenPendingIntent, true);

Từ android O trở đi, bạn phải thêm channel để hiển thị notification, nếu không có notification channel thì notification của bạn sẽ không được hiển thị tới người dùng. Bạn có thể đọc thêm về notification channel tại [đây](https://developer.android.com/training/notify-user/channels). Xong, tiếp theo mình sẽ đi vào bước tiếp theo.

B3: gửi broadcast để kích hoạt FullScreen-Intent..

Sau khi Service được chạy, nó sẽ hiển thị notification, nhưng mục đích của mình là hiển thị Full Screen Activity chứ không phải notification. Ở đây mình sẽ send ACTION_CLOSE_SYSTEM_DIALOGS broadcast, Với broadcast này, nó sẽ close các system dialog, và notifiation của mình sẽ gọi start PendingIntent, Pending Intent của mình sẽ gọi start Activity như ở B2. Để làm việc này mình chỉ cần thêm 1 dòng code để sendBroadcast trong method onStartCommand như code dưới đây :

 public int onStartCommand(Intent intent, int flags, int startId) { Notification notification = buildNotification(); startForeground(1, notification); sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); return START_NOT_STICKY; }

B4: Tạo 1 Activity để hiển thị trên màn hình khóa

Ở bước này, mình sẽ tạo 1 class có tên là InComingCallActivity và cho nó extends AppCompatActivity. Code Activity của mình như sau:

 public class IncomingCallActivity extends AppCompatActivity { protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_explore); setShowWhenLocked(true); setTurnScreenOn(true); getWindow().addFlags( WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON); } }

Trong bài này mình chỉ muốn hiển thị được activity trên màn hình khóa nên code Activity của mình rất ngắn gọn, sau này bạn sẽ phải thêm logic của ứng dụng của bạn vào đây. Trong Activity bạn sẽ phải thêm mấy phương thức như:

setShowWhenLocked : Phương thức này cho phép Activity của bạn hiển thị ngay cả khi màn hình khóa.
setTurnScreenOn : Phương thức này sẽ turn on screen của bạn lên.
Mình cũng sẽ thêm code giao diện của mình ở đây, bạn có thể thay đổi giao diện theo ý bạn muốn.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/teal_700"> <androidx.cardview.widget.CardView android:id="@+id/cardView" android:layout_width="150dp" android:layout_height="150dp" app:cardCornerRadius="75dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.13999999"> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" android:src="@drawable/download" /> </androidx.cardview.widget.CardView> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="0999999999" android:textColor="@color/white" android:textSize="34sp" android:textStyle="bold" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/cardView" app:layout_constraintVertical_bias="0.1" /> <TextView android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Đang gọi..." android:textColor="@color/white" android:textSize="20sp" android:textStyle="bold" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView" app:layout_constraintVertical_bias="0.1" /> <ImageView android:id="@+id/imb_reject" android:layout_width="60dp" android:layout_height="60dp" android:src="@drawable/bg_reject" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/imb_accept" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView" app:layout_constraintVertical_bias="0.75" /> <ImageView android:id="@+id/imb_accept" android:layout_width="60dp" android:layout_height="60dp" android:src="@drawable/bg_accept" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toEndOf="@+id/imb_reject" app:layout_constraintTop_toBottomOf="@+id/textView" app:layout_constraintVertical_bias="0.75" />
</androidx.constraintlayout.widget.ConstraintLayout>

B5: Khai báo Activity, Service và các quyền cần thiết.

Như các bạn đã biết, tất cả các thành phần quan trọng và các quyền mà ứng dụng sử dụng đều cần khai báo trong file AndroidManifest. Do ở trên mình đã tạo 1 Activity và 1 Service nên mình sẽ khai báo chúng trong AndroidManifest và ứng dụng của mình cũng cần 2 quyền là USE_FULL_SCREEN_INTENT và FOREGROUND_SERVICE.

 <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <application> <activity android:name=".IncomingCallActivity" /> <service android:name=".IncomingCallService" android:enabled="true" android:exported="true" /> </application>

B6: Test ứng dụng.

Trong thực để, InComingCallService sẽ cần được start khi có cuộc gọi đến. để start service, bạn chỉ cần xác định chỗ sẽ gọi phương thức start vào thêm đoạn code dưới đây :

 public void startCallService() { Intent intent = new Intent(context, IncomingCallService.class); startForegroundService(intent); }

Nhưng trên ví dụ này, mình không tập trung vào logic của ứng dụng call, mà mình chỉ muốn hướng dẫn các bạn phần hiển thị phần hiển thị Activity trên màn hình khóa, Nên sau khi các bạn triển khai code xong, các bạn cài đặt ứng dụng vào máy, sau đó các bạn khóa màn hình điện thoại vào và sử dụng command line dưới đây trên terminal hoặc command line window để test ứng dụng:
adb shell am start-foreground-service com.ddona.call/.IncomingCallService

Các bạn nhớ thay "com.ddona.call" thành package name của ứng dụng bạn đang viết nhá.
Lưu ý: Với điện thoại Xaomi, bạn cần cấp thêm quyền cho ứng dụng bằng các bước dưới đây thì ứng dụng mới hoạt động được:

  1. Long click to you app icon
  2. Open app info
  3. Click to "Other permission" item
  4. Allow show on Lock screen

Bài viết của mình đến đây là kết thúc. Hy vọng rằng các bạn sẽ áp dụng được kỹ thuật này vào ứng dụng của các bạn. Một năm mới cũng sắp tới. Tiện đây mình cũng xin chúc các bạn cùng gia đình 1 năm mới sức khỏe, thành công và hạnh phúc. HAPPY NEW YEAR!!

Các bạn có thể tham khảo source code demo tại Đây. Ngoài ra các bạn có thể đọc thêm về full screen intent tại đây

Bình luận

Bài viết tương tự

- vừa được xem lúc

Giới thiệu Typescript - Sự khác nhau giữa Typescript và Javascript

Typescript là gì. TypeScript là một ngôn ngữ giúp cung cấp quy mô lớn hơn so với JavaScript.

0 0 495

- vừa được xem lúc

Cài đặt WSL / WSL2 trên Windows 10 để code như trên Ubuntu

Sau vài ba năm mình chuyển qua code trên Ubuntu thì thật không thể phủ nhận rằng mình đã yêu em nó. Cá nhân mình sử dụng Ubuntu để code web thì thật là tuyệt vời.

0 0 371

- vừa được xem lúc

Đặt tên commit message sao cho "tình nghĩa anh em chắc chắn bền lâu"????

. Lời mở đầu. .

1 1 685

- vừa được xem lúc

Tìm hiểu về Resource Controller trong Laravel

Giới thiệu. Trong laravel, việc sử dụng các route post, get, group để gọi đến 1 action của Controller đã là quá quen đối với các bạn sử dụng framework này.

0 0 331

- vừa được xem lúc

Phân quyền đơn giản với package Laravel permission

Như các bạn đã biết, phân quyền trong một ứng dụng là một phần không thể thiếu trong việc phát triển phần mềm, dù đó là ứng dụng web hay là mobile. Vậy nên, hôm nay mình sẽ giới thiệu một package có thể giúp các bạn phân quyền nhanh và đơn giản trong một website được viết bằng PHP với framework là L

0 0 417

- vừa được xem lúc

Bạn đã biết các tips này khi làm việc với chuỗi trong JavaScript chưa ?

Hi xin chào các bạn, tiếp tục chuỗi chủ đề về cái thằng JavaScript này, hôm nay mình sẽ giới thiệu cho các bạn một số thủ thuật hay ho khi làm việc với chuỗi trong JavaScript có thể bạn đã hoặc chưa từng dùng. Cụ thể như nào thì hãy cùng mình tìm hiểu trong bài viết này nhé (go).

0 0 413