blob: 36e360da857f67a298eb8fe429c85dfd460f45d9 [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;
Sergey Nikolaienkov8dfc49b2020-06-22 11:38:35 +0200102 private View mBgEnd;
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100103 private View mTextsContainers;
104 private TextView mTextView;
Sergey Nikolaienkov8dfc49b2020-06-22 11:38:35 +0200105 private boolean mIsLtr;
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100106
107 @State private int mState = STATE_NOT_SHOWN;
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100108
Sergey Nikolaienkovfa016df2020-03-05 07:27:58 +0100109 /**
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100110 * Array of the observers that monitor different aspects of the system, such as AppOps and
111 * microphone foreground services
Sergey Nikolaienkovfa016df2020-03-05 07:27:58 +0100112 */
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100113 private final AudioActivityObserver[] mAudioActivityObservers;
Sergey Nikolaienkovfa016df2020-03-05 07:27:58 +0100114 /**
115 * Set of applications that we've notified the user about since the indicator came up. Meaning
116 * that if an application is in this list then at some point since the indicator came up, it
117 * was expanded showing this application's title.
118 * Used not to notify the user about the same application again while the indicator is shown.
119 * We empty this set every time the indicator goes off the screen (we always call {@code
120 * mSessionNotifiedPackages.clear()} before calling {@link #hide()}).
121 */
122 private final Set<String> mSessionNotifiedPackages = new ArraySet<>();
123 /**
124 * If an application starts recording while the TV indicator is neither in {@link
125 * #STATE_NOT_SHOWN} nor in {@link #STATE_MINIMIZED}, then we add the application's package
126 * name to the queue, from which we take packages names one by one to disclose the
127 * corresponding applications' titles to the user, whenever the indicator eventually comes to
128 * one of the two aforementioned states.
129 */
130 private final Queue<String> mPendingNotificationPackages = new LinkedList<>();
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100131 /**
132 * Set of applications for which we make an exception and do not show the indicator. This gets
133 * populated once - in {@link #AudioRecordingDisclosureBar(Context)}.
134 */
135 private final Set<String> mExemptPackages;
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +0200136
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100137 public AudioRecordingDisclosureBar(Context context) {
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +0200138 mContext = context;
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +0200139
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100140 mExemptPackages = new ArraySet<>(
141 Arrays.asList(mContext.getResources().getStringArray(
142 R.array.audio_recording_disclosure_exempt_apps)));
Robin Leeb7f16fd2020-06-09 14:15:58 +0200143 mExemptPackages.addAll(Arrays.asList(getGlobalStringArray(EXEMPT_PACKAGES_LIST)));
144 mExemptPackages.removeAll(Arrays.asList(getGlobalStringArray(FORCED_PACKAGES_LIST)));
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100145
146 mAudioActivityObservers = new AudioActivityObserver[]{
147 new RecordAudioAppOpObserver(mContext, this),
148 new MicrophoneForegroundServicesObserver(mContext, this),
149 };
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +0200150 }
151
Robin Leeb7f16fd2020-06-09 14:15:58 +0200152 private String[] getGlobalStringArray(String setting) {
153 String result = Settings.Global.getString(mContext.getContentResolver(), setting);
154 return TextUtils.isEmpty(result) ? new String[0] : result.split(",");
155 }
156
Sergey Nikolaienkov224e9622020-03-10 13:58:08 +0100157 @UiThread
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100158 @Override
159 public void onAudioActivityStateChange(boolean active, String packageName) {
160 if (DEBUG) {
161 Log.d(TAG,
162 "onAudioActivityStateChange, packageName=" + packageName + ", active="
163 + active);
164 }
165
166 if (mExemptPackages.contains(packageName)) {
167 if (DEBUG) Log.d(TAG, " - exempt package: ignoring");
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100168 return;
169 }
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100170
171 if (active) {
172 showIndicatorForPackageIfNeeded(packageName);
173 } else {
174 hideIndicatorIfNeeded();
175 }
176 }
177
178 @UiThread
179 private void showIndicatorForPackageIfNeeded(String packageName) {
180 if (DEBUG) Log.d(TAG, "showIndicatorForPackageIfNeeded, packageName=" + packageName);
Sergey Nikolaienkovfa016df2020-03-05 07:27:58 +0100181 if (!mSessionNotifiedPackages.add(packageName)) {
182 // We've already notified user about this app, no need to do it again.
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100183 if (DEBUG) Log.d(TAG, " - already notified");
Sergey Nikolaienkovfa016df2020-03-05 07:27:58 +0100184 return;
185 }
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100186
187 switch (mState) {
188 case STATE_NOT_SHOWN:
189 show(packageName);
190 break;
191
192 case STATE_MINIMIZED:
193 expand(packageName);
194 break;
195
196 case STATE_DISAPPEARING:
197 case STATE_APPEARING:
198 case STATE_MAXIMIZING:
199 case STATE_SHOWN:
200 case STATE_MINIMIZING:
201 // Currently animating or expanded. Thus add to the pending notifications, and it
202 // will be picked up once the indicator comes to the STATE_MINIMIZED.
Sergey Nikolaienkovfa016df2020-03-05 07:27:58 +0100203 mPendingNotificationPackages.add(packageName);
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100204 break;
205 }
206 }
207
Sergey Nikolaienkov224e9622020-03-10 13:58:08 +0100208 @UiThread
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100209 private void hideIndicatorIfNeeded() {
210 if (DEBUG) Log.d(TAG, "hideIndicatorIfNeeded");
211 // If not MINIMIZED, will check whether the indicator should be hidden when the indicator
212 // comes to the STATE_MINIMIZED eventually.
213 if (mState != STATE_MINIMIZED) return;
214
215 // If is in the STATE_MINIMIZED, but there are other active recorders - simply ignore.
216 for (int index = mAudioActivityObservers.length - 1; index >= 0; index--) {
217 for (String activePackage : mAudioActivityObservers[index].getActivePackages()) {
218 if (mExemptPackages.contains(activePackage)) continue;
219 if (DEBUG) Log.d(TAG, " - there are still ongoing activities");
220 return;
221 }
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100222 }
223
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100224 // Clear the state and hide the indicator.
225 mSessionNotifiedPackages.clear();
226 hide();
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100227 }
228
Sergey Nikolaienkov224e9622020-03-10 13:58:08 +0100229 @UiThread
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100230 private void show(String packageName) {
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100231 final String label = getApplicationLabel(packageName);
232 if (DEBUG) {
233 Log.d(TAG, "Showing indicator for " + packageName + " (" + label + ")...");
234 }
235
Sergey Nikolaienkov8dfc49b2020-06-22 11:38:35 +0200236 mIsLtr = mContext.getResources().getConfiguration().getLayoutDirection()
237 == View.LAYOUT_DIRECTION_LTR;
238
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100239 // Inflate the indicator view
240 mIndicatorView = LayoutInflater.from(mContext).inflate(
241 R.layout.tv_audio_recording_indicator,
242 null);
243 mIconTextsContainer = mIndicatorView.findViewById(R.id.icon_texts_container);
244 mIconContainerBg = mIconTextsContainer.findViewById(R.id.icon_container_bg);
245 mIcon = mIconTextsContainer.findViewById(R.id.icon_mic);
246 mTextsContainers = mIconTextsContainer.findViewById(R.id.texts_container);
247 mTextView = mTextsContainers.findViewById(R.id.text);
Sergey Nikolaienkov8dfc49b2020-06-22 11:38:35 +0200248 mBgEnd = mIndicatorView.findViewById(R.id.bg_end);
249
250 // Swap background drawables depending on layout directions (both drawables have rounded
251 // corners only on one side)
252 if (mIsLtr) {
253 mBgEnd.setBackgroundResource(R.drawable.tv_rect_dark_right_rounded);
254 mIconContainerBg.setBackgroundResource(R.drawable.tv_rect_dark_left_rounded);
255 } else {
256 mBgEnd.setBackgroundResource(R.drawable.tv_rect_dark_left_rounded);
257 mIconContainerBg.setBackgroundResource(R.drawable.tv_rect_dark_right_rounded);
258 }
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100259
260 // Set up the notification text
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100261 mTextView.setText(mContext.getString(R.string.app_accessed_mic, label));
262
263 // Initially change the visibility to INVISIBLE, wait until and receives the size and
264 // then animate it moving from "off" the screen correctly
265 mIndicatorView.setVisibility(View.INVISIBLE);
266 mIndicatorView
267 .getViewTreeObserver()
268 .addOnGlobalLayoutListener(
269 new ViewTreeObserver.OnGlobalLayoutListener() {
270 @Override
271 public void onGlobalLayout() {
272 // Remove the observer
273 mIndicatorView.getViewTreeObserver().removeOnGlobalLayoutListener(
274 this);
275
276 // Now that the width of the indicator has been assigned, we can
277 // move it in from off the screen.
Sergey Nikolaienkov8dfc49b2020-06-22 11:38:35 +0200278 final int initialOffset =
279 (mIsLtr ? 1 : -1) * mIndicatorView.getWidth();
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100280 final AnimatorSet set = new AnimatorSet();
281 set.setDuration(ANIMATION_DURATION);
282 set.playTogether(
283 ObjectAnimator.ofFloat(mIndicatorView,
284 View.TRANSLATION_X, initialOffset, 0),
285 ObjectAnimator.ofFloat(mIndicatorView, View.ALPHA, 0f,
286 1f));
287 set.addListener(
288 new AnimatorListenerAdapter() {
289 @Override
290 public void onAnimationStart(Animator animation,
291 boolean isReverse) {
292 // Indicator is INVISIBLE at the moment, change it.
293 mIndicatorView.setVisibility(View.VISIBLE);
294 }
295
296 @Override
297 public void onAnimationEnd(Animator animation) {
298 startPulsatingAnimation();
299 onExpanded();
300 }
301 });
302 set.start();
303 }
304 });
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +0200305
306 final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100307 WRAP_CONTENT,
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +0200308 WRAP_CONTENT,
309 WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
310 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
311 PixelFormat.TRANSLUCENT);
Sergey Nikolaienkov8dfc49b2020-06-22 11:38:35 +0200312 layoutParams.gravity = Gravity.TOP | (mIsLtr ? Gravity.RIGHT : Gravity.LEFT);
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +0200313 layoutParams.setTitle(LAYOUT_PARAMS_TITLE);
314 layoutParams.packageName = mContext.getPackageName();
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +0200315 final WindowManager windowManager = (WindowManager) mContext.getSystemService(
316 Context.WINDOW_SERVICE);
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100317 windowManager.addView(mIndicatorView, layoutParams);
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +0200318
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100319 mState = STATE_APPEARING;
320 }
321
Sergey Nikolaienkov224e9622020-03-10 13:58:08 +0100322 @UiThread
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100323 private void expand(String packageName) {
324 final String label = getApplicationLabel(packageName);
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100325 if (DEBUG) {
326 Log.d(TAG, "Expanding for " + packageName + " (" + label + ")...");
327 }
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100328 mTextView.setText(mContext.getString(R.string.app_accessed_mic, label));
329
330 final AnimatorSet set = new AnimatorSet();
331 set.playTogether(
332 ObjectAnimator.ofFloat(mIconTextsContainer, View.TRANSLATION_X, 0),
333 ObjectAnimator.ofFloat(mIconContainerBg, View.ALPHA, 1f),
334 ObjectAnimator.ofFloat(mTextsContainers, View.ALPHA, 1f),
Sergey Nikolaienkov8dfc49b2020-06-22 11:38:35 +0200335 ObjectAnimator.ofFloat(mBgEnd, View.ALPHA, 1f));
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100336 set.setDuration(ANIMATION_DURATION);
337 set.addListener(
338 new AnimatorListenerAdapter() {
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +0200339 @Override
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100340 public void onAnimationEnd(Animator animation) {
341 onExpanded();
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +0200342 }
343 });
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100344 set.start();
345
346 mState = STATE_MAXIMIZING;
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +0200347 }
348
Sergey Nikolaienkov224e9622020-03-10 13:58:08 +0100349 @UiThread
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100350 private void minimize() {
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100351 if (DEBUG) Log.d(TAG, "Minimizing...");
Sergey Nikolaienkov8dfc49b2020-06-22 11:38:35 +0200352 final int targetOffset = (mIsLtr ? 1 : -1) * mTextsContainers.getWidth();
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100353 final AnimatorSet set = new AnimatorSet();
354 set.playTogether(
355 ObjectAnimator.ofFloat(mIconTextsContainer, View.TRANSLATION_X, targetOffset),
356 ObjectAnimator.ofFloat(mIconContainerBg, View.ALPHA, 0f),
357 ObjectAnimator.ofFloat(mTextsContainers, View.ALPHA, 0f),
Sergey Nikolaienkov8dfc49b2020-06-22 11:38:35 +0200358 ObjectAnimator.ofFloat(mBgEnd, View.ALPHA, 0f));
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100359 set.setDuration(ANIMATION_DURATION);
360 set.addListener(
361 new AnimatorListenerAdapter() {
362 @Override
363 public void onAnimationEnd(Animator animation) {
364 onMinimized();
365 }
366 });
367 set.start();
368
369 mState = STATE_MINIMIZING;
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +0200370 }
371
Sergey Nikolaienkov224e9622020-03-10 13:58:08 +0100372 @UiThread
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100373 private void hide() {
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100374 if (DEBUG) Log.d(TAG, "Hiding...");
Sergey Nikolaienkov8dfc49b2020-06-22 11:38:35 +0200375 final int targetOffset = (mIsLtr ? 1 : -1) * (mIndicatorView.getWidth()
376 - (int) mIconTextsContainer.getTranslationX());
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100377 final AnimatorSet set = new AnimatorSet();
378 set.playTogether(
379 ObjectAnimator.ofFloat(mIndicatorView, View.TRANSLATION_X, targetOffset),
380 ObjectAnimator.ofFloat(mIcon, View.ALPHA, 0f));
381 set.setDuration(ANIMATION_DURATION);
382 set.addListener(
383 new AnimatorListenerAdapter() {
384 @Override
385 public void onAnimationEnd(Animator animation) {
386 onHidden();
387 }
388 });
389 set.start();
390
391 mState = STATE_DISAPPEARING;
392 }
393
Sergey Nikolaienkov224e9622020-03-10 13:58:08 +0100394 @UiThread
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100395 private void onExpanded() {
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100396 if (DEBUG) Log.d(TAG, "Expanded");
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100397 mState = STATE_SHOWN;
398
399 mIndicatorView.postDelayed(this::minimize, MAXIMIZED_DURATION);
400 }
401
Sergey Nikolaienkov224e9622020-03-10 13:58:08 +0100402 @UiThread
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100403 private void onMinimized() {
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100404 if (DEBUG) Log.d(TAG, "Minimized");
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100405 mState = STATE_MINIMIZED;
406
Sergey Nikolaienkovfa016df2020-03-05 07:27:58 +0100407 if (!mPendingNotificationPackages.isEmpty()) {
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100408 // There is a new application that started recording, tell the user about it.
Sergey Nikolaienkovfa016df2020-03-05 07:27:58 +0100409 expand(mPendingNotificationPackages.poll());
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100410 } else {
411 hideIndicatorIfNeeded();
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100412 }
413 }
414
Sergey Nikolaienkov224e9622020-03-10 13:58:08 +0100415 @UiThread
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100416 private void onHidden() {
Sergey Nikolaienkovd07dc4d2020-03-26 17:57:40 +0100417 if (DEBUG) Log.d(TAG, "Hidden");
418
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100419 final WindowManager windowManager = (WindowManager) mContext.getSystemService(
420 Context.WINDOW_SERVICE);
421 windowManager.removeView(mIndicatorView);
422
423 mIndicatorView = null;
424 mIconTextsContainer = null;
425 mIconContainerBg = null;
426 mIcon = null;
427 mTextsContainers = null;
428 mTextView = null;
Sergey Nikolaienkov8dfc49b2020-06-22 11:38:35 +0200429 mBgEnd = null;
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100430
431 mState = STATE_NOT_SHOWN;
Sergey Nikolaienkovfa016df2020-03-05 07:27:58 +0100432
433 // Check if anybody started recording while we were in STATE_DISAPPEARING
434 if (!mPendingNotificationPackages.isEmpty()) {
435 // There is a new application that started recording, tell the user about it.
436 show(mPendingNotificationPackages.poll());
437 }
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100438 }
439
Sergey Nikolaienkov224e9622020-03-10 13:58:08 +0100440 @UiThread
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100441 private void startPulsatingAnimation() {
442 final View pulsatingView = mIconTextsContainer.findViewById(R.id.pulsating_circle);
443 final ObjectAnimator animator =
444 ObjectAnimator.ofPropertyValuesHolder(
445 pulsatingView,
446 PropertyValuesHolder.ofFloat(View.SCALE_X, PULSE_SCALE),
447 PropertyValuesHolder.ofFloat(View.SCALE_Y, PULSE_SCALE));
448 animator.setDuration(PULSE_BIT_DURATION);
449 animator.setRepeatCount(ObjectAnimator.INFINITE);
450 animator.setRepeatMode(ObjectAnimator.REVERSE);
451 animator.start();
452 }
453
454 private String getApplicationLabel(String packageName) {
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +0200455 final PackageManager pm = mContext.getPackageManager();
456 final ApplicationInfo appInfo;
457 try {
458 appInfo = pm.getApplicationInfo(packageName, 0);
459 } catch (PackageManager.NameNotFoundException e) {
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100460 return packageName;
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +0200461 }
Sergey Nikolaienkov5b514ee2019-12-06 10:42:59 +0100462 return pm.getApplicationLabel(appInfo).toString();
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +0200463 }
Sergey Nikolaienkovc7a95ac2019-08-29 07:23:11 +0200464}