blob: 8b85a096146319c40a646dd30ef75181708dd0e7 [file] [log] [blame]
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +02001/*
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +01002 * Copyright (C) 2020 The Android Open Source Project
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +02003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +010017package com.android.systemui.statusbar.tv.micdisclosure;
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +020018
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +020019import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
20
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +010021import android.animation.Animator;
22import android.animation.AnimatorListenerAdapter;
23import android.animation.AnimatorSet;
24import android.animation.ObjectAnimator;
25import android.animation.PropertyValuesHolder;
26import android.annotation.IntDef;
Sergey Nikolaienkov224e9622020-03-10 13:58:08 +010027import android.annotation.UiThread;
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +020028import android.content.Context;
29import android.content.pm.ApplicationInfo;
30import android.content.pm.PackageManager;
31import android.graphics.PixelFormat;
Robin Leeb7f16fd2020-06-09 14:15:58 +020032import android.provider.Settings;
33import android.text.TextUtils;
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +010034import android.util.ArraySet;
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +020035import android.util.Log;
36import android.view.Gravity;
37import android.view.LayoutInflater;
38import android.view.View;
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +020039import android.view.ViewTreeObserver;
40import android.view.WindowManager;
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +020041import android.widget.TextView;
42
43import com.android.systemui.R;
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +010044import com.android.systemui.statusbar.tv.TvStatusBar;
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +020045
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +010046import java.lang.annotation.Retention;
47import java.lang.annotation.RetentionPolicy;
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +020048import java.util.Arrays;
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +010049import java.util.LinkedList;
50import java.util.Queue;
51import java.util.Set;
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +020052
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +010053/**
54 * A component of {@link TvStatusBar} responsible for notifying the user whenever an application is
55 * recording audio.
56 *
57 * @see TvStatusBar
58 */
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +010059public class AudioRecordingDisclosureBar implements
60 AudioActivityObserver.OnAudioActivityStateChangeListener {
61 private static final String TAG = "AudioRecordingDisclosure";
62 static final boolean DEBUG = false;
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +020063
Sergey Nikolaienkovb45c80f2019-10-23 10:58:49 +020064 // This title is used to test the microphone disclosure indicator in
65 // CtsSystemUiHostTestCases:TvMicrophoneCaptureIndicatorTest
66 private static final String LAYOUT_PARAMS_TITLE = "MicrophoneCaptureIndicator";
67
Robin Leeb7f16fd2020-06-09 14:15:58 +020068 private static final String EXEMPT_PACKAGES_LIST = "sysui_mic_disclosure_exempt";
69 private static final String FORCED_PACKAGES_LIST = "sysui_mic_disclosure_forced";
70
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +010071 @Retention(RetentionPolicy.SOURCE)
72 @IntDef(prefix = {"STATE_"}, value = {
73 STATE_NOT_SHOWN,
74 STATE_APPEARING,
75 STATE_SHOWN,
76 STATE_MINIMIZING,
77 STATE_MINIMIZED,
78 STATE_MAXIMIZING,
79 STATE_DISAPPEARING
80 })
81 public @interface State {}
82
83 private static final int STATE_NOT_SHOWN = 0;
84 private static final int STATE_APPEARING = 1;
85 private static final int STATE_SHOWN = 2;
86 private static final int STATE_MINIMIZING = 3;
87 private static final int STATE_MINIMIZED = 4;
88 private static final int STATE_MAXIMIZING = 5;
89 private static final int STATE_DISAPPEARING = 6;
90
91 private static final int ANIMATION_DURATION = 600;
92 private static final int MAXIMIZED_DURATION = 3000;
93 private static final int PULSE_BIT_DURATION = 1000;
94 private static final float PULSE_SCALE = 1.25f;
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +020095
96 private final Context mContext;
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +010097
98 private View mIndicatorView;
99 private View mIconTextsContainer;
100 private View mIconContainerBg;
101 private View mIcon;
102 private View mBgRight;
103 private View mTextsContainers;
104 private TextView mTextView;
105
106 @State private int mState = STATE_NOT_SHOWN;
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100107
Sergey Nikolaienkovfa016df2020-03-05 07:27:58 +0100108 /**
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100109 * Array of the observers that monitor different aspects of the system, such as AppOps and
110 * microphone foreground services
Sergey Nikolaienkovfa016df2020-03-05 07:27:58 +0100111 */
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100112 private final AudioActivityObserver[] mAudioActivityObservers;
Sergey Nikolaienkovfa016df2020-03-05 07:27:58 +0100113 /**
114 * Set of applications that we've notified the user about since the indicator came up. Meaning
115 * that if an application is in this list then at some point since the indicator came up, it
116 * was expanded showing this application's title.
117 * Used not to notify the user about the same application again while the indicator is shown.
118 * We empty this set every time the indicator goes off the screen (we always call {@code
119 * mSessionNotifiedPackages.clear()} before calling {@link #hide()}).
120 */
121 private final Set<String> mSessionNotifiedPackages = new ArraySet<>();
122 /**
123 * If an application starts recording while the TV indicator is neither in {@link
124 * #STATE_NOT_SHOWN} nor in {@link #STATE_MINIMIZED}, then we add the application's package
125 * name to the queue, from which we take packages names one by one to disclose the
126 * corresponding applications' titles to the user, whenever the indicator eventually comes to
127 * one of the two aforementioned states.
128 */
129 private final Queue<String> mPendingNotificationPackages = new LinkedList<>();
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100130 /**
131 * Set of applications for which we make an exception and do not show the indicator. This gets
132 * populated once - in {@link #AudioRecordingDisclosureBar(Context)}.
133 */
134 private final Set<String> mExemptPackages;
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +0200135
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100136 public AudioRecordingDisclosureBar(Context context) {
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +0200137 mContext = context;
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +0200138
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100139 mExemptPackages = new ArraySet<>(
140 Arrays.asList(mContext.getResources().getStringArray(
141 R.array.audio_recording_disclosure_exempt_apps)));
Robin Leeb7f16fd2020-06-09 14:15:58 +0200142 mExemptPackages.addAll(Arrays.asList(getGlobalStringArray(EXEMPT_PACKAGES_LIST)));
143 mExemptPackages.removeAll(Arrays.asList(getGlobalStringArray(FORCED_PACKAGES_LIST)));
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100144
145 mAudioActivityObservers = new AudioActivityObserver[]{
146 new RecordAudioAppOpObserver(mContext, this),
147 new MicrophoneForegroundServicesObserver(mContext, this),
148 };
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +0200149 }
150
Robin Leeb7f16fd2020-06-09 14:15:58 +0200151 private String[] getGlobalStringArray(String setting) {
152 String result = Settings.Global.getString(mContext.getContentResolver(), setting);
153 return TextUtils.isEmpty(result) ? new String[0] : result.split(",");
154 }
155
Sergey Nikolaienkov224e9622020-03-10 13:58:08 +0100156 @UiThread
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100157 @Override
158 public void onAudioActivityStateChange(boolean active, String packageName) {
159 if (DEBUG) {
160 Log.d(TAG,
161 "onAudioActivityStateChange, packageName=" + packageName + ", active="
162 + active);
163 }
164
165 if (mExemptPackages.contains(packageName)) {
166 if (DEBUG) Log.d(TAG, " - exempt package: ignoring");
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100167 return;
168 }
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100169
170 if (active) {
171 showIndicatorForPackageIfNeeded(packageName);
172 } else {
173 hideIndicatorIfNeeded();
174 }
175 }
176
177 @UiThread
178 private void showIndicatorForPackageIfNeeded(String packageName) {
179 if (DEBUG) Log.d(TAG, "showIndicatorForPackageIfNeeded, packageName=" + packageName);
Sergey Nikolaienkovfa016df2020-03-05 07:27:58 +0100180 if (!mSessionNotifiedPackages.add(packageName)) {
181 // We've already notified user about this app, no need to do it again.
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100182 if (DEBUG) Log.d(TAG, " - already notified");
Sergey Nikolaienkovfa016df2020-03-05 07:27:58 +0100183 return;
184 }
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100185
186 switch (mState) {
187 case STATE_NOT_SHOWN:
188 show(packageName);
189 break;
190
191 case STATE_MINIMIZED:
192 expand(packageName);
193 break;
194
195 case STATE_DISAPPEARING:
196 case STATE_APPEARING:
197 case STATE_MAXIMIZING:
198 case STATE_SHOWN:
199 case STATE_MINIMIZING:
200 // Currently animating or expanded. Thus add to the pending notifications, and it
201 // will be picked up once the indicator comes to the STATE_MINIMIZED.
Sergey Nikolaienkovfa016df2020-03-05 07:27:58 +0100202 mPendingNotificationPackages.add(packageName);
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100203 break;
204 }
205 }
206
Sergey Nikolaienkov224e9622020-03-10 13:58:08 +0100207 @UiThread
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100208 private void hideIndicatorIfNeeded() {
209 if (DEBUG) Log.d(TAG, "hideIndicatorIfNeeded");
210 // If not MINIMIZED, will check whether the indicator should be hidden when the indicator
211 // comes to the STATE_MINIMIZED eventually.
212 if (mState != STATE_MINIMIZED) return;
213
214 // If is in the STATE_MINIMIZED, but there are other active recorders - simply ignore.
215 for (int index = mAudioActivityObservers.length - 1; index >= 0; index--) {
216 for (String activePackage : mAudioActivityObservers[index].getActivePackages()) {
217 if (mExemptPackages.contains(activePackage)) continue;
218 if (DEBUG) Log.d(TAG, " - there are still ongoing activities");
219 return;
220 }
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100221 }
222
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100223 // Clear the state and hide the indicator.
224 mSessionNotifiedPackages.clear();
225 hide();
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100226 }
227
Sergey Nikolaienkov224e9622020-03-10 13:58:08 +0100228 @UiThread
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100229 private void show(String packageName) {
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100230 final String label = getApplicationLabel(packageName);
231 if (DEBUG) {
232 Log.d(TAG, "Showing indicator for " + packageName + " (" + label + ")...");
233 }
234
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100235 // Inflate the indicator view
236 mIndicatorView = LayoutInflater.from(mContext).inflate(
237 R.layout.tv_audio_recording_indicator,
238 null);
239 mIconTextsContainer = mIndicatorView.findViewById(R.id.icon_texts_container);
240 mIconContainerBg = mIconTextsContainer.findViewById(R.id.icon_container_bg);
241 mIcon = mIconTextsContainer.findViewById(R.id.icon_mic);
242 mTextsContainers = mIconTextsContainer.findViewById(R.id.texts_container);
243 mTextView = mTextsContainers.findViewById(R.id.text);
244 mBgRight = mIndicatorView.findViewById(R.id.bg_right);
245
246 // Set up the notification text
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100247 mTextView.setText(mContext.getString(R.string.app_accessed_mic, label));
248
249 // Initially change the visibility to INVISIBLE, wait until and receives the size and
250 // then animate it moving from "off" the screen correctly
251 mIndicatorView.setVisibility(View.INVISIBLE);
252 mIndicatorView
253 .getViewTreeObserver()
254 .addOnGlobalLayoutListener(
255 new ViewTreeObserver.OnGlobalLayoutListener() {
256 @Override
257 public void onGlobalLayout() {
258 // Remove the observer
259 mIndicatorView.getViewTreeObserver().removeOnGlobalLayoutListener(
260 this);
261
262 // Now that the width of the indicator has been assigned, we can
263 // move it in from off the screen.
264 final int initialOffset = mIndicatorView.getWidth();
265 final AnimatorSet set = new AnimatorSet();
266 set.setDuration(ANIMATION_DURATION);
267 set.playTogether(
268 ObjectAnimator.ofFloat(mIndicatorView,
269 View.TRANSLATION_X, initialOffset, 0),
270 ObjectAnimator.ofFloat(mIndicatorView, View.ALPHA, 0f,
271 1f));
272 set.addListener(
273 new AnimatorListenerAdapter() {
274 @Override
275 public void onAnimationStart(Animator animation,
276 boolean isReverse) {
277 // Indicator is INVISIBLE at the moment, change it.
278 mIndicatorView.setVisibility(View.VISIBLE);
279 }
280
281 @Override
282 public void onAnimationEnd(Animator animation) {
283 startPulsatingAnimation();
284 onExpanded();
285 }
286 });
287 set.start();
288 }
289 });
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +0200290
291 final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100292 WRAP_CONTENT,
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +0200293 WRAP_CONTENT,
294 WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
295 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
296 PixelFormat.TRANSLUCENT);
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100297 layoutParams.gravity = Gravity.TOP | Gravity.RIGHT;
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +0200298 layoutParams.setTitle(LAYOUT_PARAMS_TITLE);
299 layoutParams.packageName = mContext.getPackageName();
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +0200300 final WindowManager windowManager = (WindowManager) mContext.getSystemService(
301 Context.WINDOW_SERVICE);
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100302 windowManager.addView(mIndicatorView, layoutParams);
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +0200303
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100304 mState = STATE_APPEARING;
305 }
306
Sergey Nikolaienkov224e9622020-03-10 13:58:08 +0100307 @UiThread
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100308 private void expand(String packageName) {
309 final String label = getApplicationLabel(packageName);
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100310 if (DEBUG) {
311 Log.d(TAG, "Expanding for " + packageName + " (" + label + ")...");
312 }
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100313 mTextView.setText(mContext.getString(R.string.app_accessed_mic, label));
314
315 final AnimatorSet set = new AnimatorSet();
316 set.playTogether(
317 ObjectAnimator.ofFloat(mIconTextsContainer, View.TRANSLATION_X, 0),
318 ObjectAnimator.ofFloat(mIconContainerBg, View.ALPHA, 1f),
319 ObjectAnimator.ofFloat(mTextsContainers, View.ALPHA, 1f),
320 ObjectAnimator.ofFloat(mBgRight, View.ALPHA, 1f));
321 set.setDuration(ANIMATION_DURATION);
322 set.addListener(
323 new AnimatorListenerAdapter() {
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +0200324 @Override
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100325 public void onAnimationEnd(Animator animation) {
326 onExpanded();
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +0200327 }
328 });
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100329 set.start();
330
331 mState = STATE_MAXIMIZING;
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +0200332 }
333
Sergey Nikolaienkov224e9622020-03-10 13:58:08 +0100334 @UiThread
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100335 private void minimize() {
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100336 if (DEBUG) Log.d(TAG, "Minimizing...");
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100337 final int targetOffset = mTextsContainers.getWidth();
338 final AnimatorSet set = new AnimatorSet();
339 set.playTogether(
340 ObjectAnimator.ofFloat(mIconTextsContainer, View.TRANSLATION_X, targetOffset),
341 ObjectAnimator.ofFloat(mIconContainerBg, View.ALPHA, 0f),
342 ObjectAnimator.ofFloat(mTextsContainers, View.ALPHA, 0f),
343 ObjectAnimator.ofFloat(mBgRight, View.ALPHA, 0f));
344 set.setDuration(ANIMATION_DURATION);
345 set.addListener(
346 new AnimatorListenerAdapter() {
347 @Override
348 public void onAnimationEnd(Animator animation) {
349 onMinimized();
350 }
351 });
352 set.start();
353
354 mState = STATE_MINIMIZING;
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +0200355 }
356
Sergey Nikolaienkov224e9622020-03-10 13:58:08 +0100357 @UiThread
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100358 private void hide() {
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100359 if (DEBUG) Log.d(TAG, "Hiding...");
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100360 final int targetOffset =
361 mIndicatorView.getWidth() - (int) mIconTextsContainer.getTranslationX();
362 final AnimatorSet set = new AnimatorSet();
363 set.playTogether(
364 ObjectAnimator.ofFloat(mIndicatorView, View.TRANSLATION_X, targetOffset),
365 ObjectAnimator.ofFloat(mIcon, View.ALPHA, 0f));
366 set.setDuration(ANIMATION_DURATION);
367 set.addListener(
368 new AnimatorListenerAdapter() {
369 @Override
370 public void onAnimationEnd(Animator animation) {
371 onHidden();
372 }
373 });
374 set.start();
375
376 mState = STATE_DISAPPEARING;
377 }
378
Sergey Nikolaienkov224e9622020-03-10 13:58:08 +0100379 @UiThread
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100380 private void onExpanded() {
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100381 if (DEBUG) Log.d(TAG, "Expanded");
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100382 mState = STATE_SHOWN;
383
384 mIndicatorView.postDelayed(this::minimize, MAXIMIZED_DURATION);
385 }
386
Sergey Nikolaienkov224e9622020-03-10 13:58:08 +0100387 @UiThread
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100388 private void onMinimized() {
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100389 if (DEBUG) Log.d(TAG, "Minimized");
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100390 mState = STATE_MINIMIZED;
391
Sergey Nikolaienkovfa016df2020-03-05 07:27:58 +0100392 if (!mPendingNotificationPackages.isEmpty()) {
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100393 // There is a new application that started recording, tell the user about it.
Sergey Nikolaienkovfa016df2020-03-05 07:27:58 +0100394 expand(mPendingNotificationPackages.poll());
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100395 } else {
396 hideIndicatorIfNeeded();
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100397 }
398 }
399
Sergey Nikolaienkov224e9622020-03-10 13:58:08 +0100400 @UiThread
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100401 private void onHidden() {
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100402 if (DEBUG) Log.d(TAG, "Hidden");
403
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100404 final WindowManager windowManager = (WindowManager) mContext.getSystemService(
405 Context.WINDOW_SERVICE);
406 windowManager.removeView(mIndicatorView);
407
408 mIndicatorView = null;
409 mIconTextsContainer = null;
410 mIconContainerBg = null;
411 mIcon = null;
412 mTextsContainers = null;
413 mTextView = null;
414 mBgRight = null;
415
416 mState = STATE_NOT_SHOWN;
Sergey Nikolaienkovfa016df2020-03-05 07:27:58 +0100417
418 // Check if anybody started recording while we were in STATE_DISAPPEARING
419 if (!mPendingNotificationPackages.isEmpty()) {
420 // There is a new application that started recording, tell the user about it.
421 show(mPendingNotificationPackages.poll());
422 }
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100423 }
424
Sergey Nikolaienkov224e9622020-03-10 13:58:08 +0100425 @UiThread
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100426 private void startPulsatingAnimation() {
427 final View pulsatingView = mIconTextsContainer.findViewById(R.id.pulsating_circle);
428 final ObjectAnimator animator =
429 ObjectAnimator.ofPropertyValuesHolder(
430 pulsatingView,
431 PropertyValuesHolder.ofFloat(View.SCALE_X, PULSE_SCALE),
432 PropertyValuesHolder.ofFloat(View.SCALE_Y, PULSE_SCALE));
433 animator.setDuration(PULSE_BIT_DURATION);
434 animator.setRepeatCount(ObjectAnimator.INFINITE);
435 animator.setRepeatMode(ObjectAnimator.REVERSE);
436 animator.start();
437 }
438
439 private String getApplicationLabel(String packageName) {
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +0200440 final PackageManager pm = mContext.getPackageManager();
441 final ApplicationInfo appInfo;
442 try {
443 appInfo = pm.getApplicationInfo(packageName, 0);
444 } catch (PackageManager.NameNotFoundException e) {
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100445 return packageName;
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +0200446 }
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100447 return pm.getApplicationLabel(appInfo).toString();
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +0200448 }
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +0200449}