Skip to content

Commit 707fc94

Browse files
caitlinshkAndroid Build Coastguard Worker
authored andcommitted
[SB][Privacy] Fetch current active appops on startup.
This also updates SysUI's chip animation scheduler to ignore an `isTooEarly` check if the chip animation is forced to be visible (which is true for privacy events). Bug: 294104969 Test: start recording, then kill systemui via adb-> verify privacy chip reappears after restart. Pull down shade and verify chip is correctly attributed. Stop recording and verify chip/dot disappears. Test: open camera, then kill systemui via adb -> verify privacy chip reappears after restart. Pull down shade and verify chip is correctly attributed. Close camera and verify chip/dot disappears. Test: smoke test of privacy chip and dot Test: atest AppOpsControllerTest SystemStatusAnimationSchedulerImplTest (cherry picked from commit 084a7afb4bb41e0cdfdbe67bdd60728d940b4331) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:dac02d61f8cf755f733ef6c2fbd0f939ea13ee23) Merged-In: I664bb3003a2f6871113406e3257b7118bbdf2ab5 Change-Id: I664bb3003a2f6871113406e3257b7118bbdf2ab5
1 parent 9532b69 commit 707fc94

3 files changed

Lines changed: 248 additions & 2 deletions

File tree

packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import java.io.PrintWriter;
5252
import java.util.ArrayList;
5353
import java.util.List;
54+
import java.util.Map;
5455
import java.util.Set;
5556

5657
import javax.inject.Inject;
@@ -144,6 +145,10 @@ protected void setBGHandler(H handler) {
144145
protected void setListening(boolean listening) {
145146
mListening = listening;
146147
if (listening) {
148+
// System UI could be restarted while ops are active, so fetch the currently active ops
149+
// once System UI starts listening again.
150+
fetchCurrentActiveOps();
151+
147152
mAppOps.startWatchingActive(OPS, this);
148153
mAppOps.startWatchingNoted(OPS, this);
149154
mAudioManager.registerAudioRecordingCallback(mAudioRecordingCallback, mBGHandler);
@@ -176,6 +181,29 @@ protected void setListening(boolean listening) {
176181
}
177182
}
178183

184+
private void fetchCurrentActiveOps() {
185+
List<AppOpsManager.PackageOps> packageOps = mAppOps.getPackagesForOps(OPS);
186+
for (AppOpsManager.PackageOps op : packageOps) {
187+
for (AppOpsManager.OpEntry entry : op.getOps()) {
188+
for (Map.Entry<String, AppOpsManager.AttributedOpEntry> attributedOpEntry :
189+
entry.getAttributedOpEntries().entrySet()) {
190+
if (attributedOpEntry.getValue().isRunning()) {
191+
onOpActiveChanged(
192+
entry.getOpStr(),
193+
op.getUid(),
194+
op.getPackageName(),
195+
/* attributionTag= */ attributedOpEntry.getKey(),
196+
/* active= */ true,
197+
// AppOpsManager doesn't have a way to fetch attribution flags or
198+
// chain ID given an op entry, so default them to none.
199+
AppOpsManager.ATTRIBUTION_FLAGS_NONE,
200+
AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
201+
}
202+
}
203+
}
204+
}
205+
}
206+
179207
/**
180208
* Adds a callback that will get notifified when an AppOp of the type the controller tracks
181209
* changes

packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,9 @@ class SystemStatusAnimationScheduler @Inject constructor(
8888
}
8989

9090
fun onStatusEvent(event: StatusEvent) {
91-
// Ignore any updates until the system is up and running
92-
if (isTooEarly() || !isImmersiveIndicatorEnabled()) {
91+
// Ignore any updates until the system is up and running. However, for important events that
92+
// request to be force visible (like privacy), ignore whether it's too early.
93+
if ((isTooEarly() && !event.forceVisible) || !isImmersiveIndicatorEnabled()) {
9394
return
9495
}
9596

packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
2020
import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
2121

22+
import static com.google.common.truth.Truth.assertThat;
23+
2224
import static junit.framework.TestCase.assertFalse;
2325

2426
import static org.junit.Assert.assertEquals;
@@ -66,6 +68,7 @@
6668

6769
import java.util.Collections;
6870
import java.util.List;
71+
import java.util.Map;
6972

7073
@SmallTest
7174
@RunWith(AndroidTestingRunner.class)
@@ -157,6 +160,204 @@ public void testStopListening() {
157160
verify(mSensorPrivacyController, times(1)).removeCallback(mController);
158161
}
159162

163+
@Test
164+
public void startListening_fetchesCurrentActive_none() {
165+
when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
166+
.thenReturn(List.of());
167+
168+
mController.setListening(true);
169+
170+
assertThat(mController.getActiveAppOps()).isEmpty();
171+
}
172+
173+
/** Regression test for b/294104969. */
174+
@Test
175+
public void startListening_fetchesCurrentActive_oneActive() {
176+
AppOpsManager.PackageOps packageOps = createPackageOp(
177+
"package.test",
178+
/* packageUid= */ 2,
179+
AppOpsManager.OPSTR_FINE_LOCATION,
180+
/* isRunning= */ true);
181+
when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
182+
.thenReturn(List.of(packageOps));
183+
184+
// WHEN we start listening
185+
mController.setListening(true);
186+
187+
// THEN the active list has the op
188+
List<AppOpItem> list = mController.getActiveAppOps();
189+
assertEquals(1, list.size());
190+
AppOpItem first = list.get(0);
191+
assertThat(first.getPackageName()).isEqualTo("package.test");
192+
assertThat(first.getUid()).isEqualTo(2);
193+
assertThat(first.getCode()).isEqualTo(AppOpsManager.OP_FINE_LOCATION);
194+
}
195+
196+
@Test
197+
public void startListening_fetchesCurrentActive_multiplePackages() {
198+
AppOpsManager.PackageOps packageOps1 = createPackageOp(
199+
"package.one",
200+
/* packageUid= */ 1,
201+
AppOpsManager.OPSTR_FINE_LOCATION,
202+
/* isRunning= */ true);
203+
AppOpsManager.PackageOps packageOps2 = createPackageOp(
204+
"package.two",
205+
/* packageUid= */ 2,
206+
AppOpsManager.OPSTR_FINE_LOCATION,
207+
/* isRunning= */ false);
208+
AppOpsManager.PackageOps packageOps3 = createPackageOp(
209+
"package.three",
210+
/* packageUid= */ 3,
211+
AppOpsManager.OPSTR_FINE_LOCATION,
212+
/* isRunning= */ true);
213+
when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
214+
.thenReturn(List.of(packageOps1, packageOps2, packageOps3));
215+
216+
// WHEN we start listening
217+
mController.setListening(true);
218+
219+
// THEN the active list has the ops
220+
List<AppOpItem> list = mController.getActiveAppOps();
221+
assertEquals(2, list.size());
222+
223+
AppOpItem item0 = list.get(0);
224+
assertThat(item0.getPackageName()).isEqualTo("package.one");
225+
assertThat(item0.getUid()).isEqualTo(1);
226+
assertThat(item0.getCode()).isEqualTo(AppOpsManager.OP_FINE_LOCATION);
227+
228+
AppOpItem item1 = list.get(1);
229+
assertThat(item1.getPackageName()).isEqualTo("package.three");
230+
assertThat(item1.getUid()).isEqualTo(3);
231+
assertThat(item1.getCode()).isEqualTo(AppOpsManager.OP_FINE_LOCATION);
232+
}
233+
234+
@Test
235+
public void startListening_fetchesCurrentActive_multipleEntries() {
236+
AppOpsManager.PackageOps packageOps = mock(AppOpsManager.PackageOps.class);
237+
when(packageOps.getUid()).thenReturn(1);
238+
when(packageOps.getPackageName()).thenReturn("package.one");
239+
240+
// Entry 1
241+
AppOpsManager.OpEntry entry1 = mock(AppOpsManager.OpEntry.class);
242+
when(entry1.getOpStr()).thenReturn(AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE);
243+
AppOpsManager.AttributedOpEntry attributed1 = mock(AppOpsManager.AttributedOpEntry.class);
244+
when(attributed1.isRunning()).thenReturn(true);
245+
when(entry1.getAttributedOpEntries()).thenReturn(Map.of("tag", attributed1));
246+
// Entry 2
247+
AppOpsManager.OpEntry entry2 = mock(AppOpsManager.OpEntry.class);
248+
when(entry2.getOpStr()).thenReturn(AppOpsManager.OPSTR_CAMERA);
249+
AppOpsManager.AttributedOpEntry attributed2 = mock(AppOpsManager.AttributedOpEntry.class);
250+
when(attributed2.isRunning()).thenReturn(true);
251+
when(entry2.getAttributedOpEntries()).thenReturn(Map.of("tag", attributed2));
252+
// Entry 3
253+
AppOpsManager.OpEntry entry3 = mock(AppOpsManager.OpEntry.class);
254+
when(entry3.getOpStr()).thenReturn(AppOpsManager.OPSTR_FINE_LOCATION);
255+
AppOpsManager.AttributedOpEntry attributed3 = mock(AppOpsManager.AttributedOpEntry.class);
256+
when(attributed3.isRunning()).thenReturn(false);
257+
when(entry3.getAttributedOpEntries()).thenReturn(Map.of("tag", attributed3));
258+
259+
when(packageOps.getOps()).thenReturn(List.of(entry1, entry2, entry3));
260+
when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
261+
.thenReturn(List.of(packageOps));
262+
263+
// WHEN we start listening
264+
mController.setListening(true);
265+
266+
// THEN the active list has the ops
267+
List<AppOpItem> list = mController.getActiveAppOps();
268+
assertEquals(2, list.size());
269+
270+
AppOpItem first = list.get(0);
271+
assertThat(first.getPackageName()).isEqualTo("package.one");
272+
assertThat(first.getUid()).isEqualTo(1);
273+
assertThat(first.getCode()).isEqualTo(AppOpsManager.OP_PHONE_CALL_MICROPHONE);
274+
275+
AppOpItem second = list.get(1);
276+
assertThat(second.getPackageName()).isEqualTo("package.one");
277+
assertThat(second.getUid()).isEqualTo(1);
278+
assertThat(second.getCode()).isEqualTo(AppOpsManager.OP_CAMERA);
279+
}
280+
281+
@Test
282+
public void startListening_fetchesCurrentActive_multipleAttributes() {
283+
AppOpsManager.PackageOps packageOps = mock(AppOpsManager.PackageOps.class);
284+
when(packageOps.getUid()).thenReturn(1);
285+
when(packageOps.getPackageName()).thenReturn("package.one");
286+
AppOpsManager.OpEntry entry = mock(AppOpsManager.OpEntry.class);
287+
when(entry.getOpStr()).thenReturn(AppOpsManager.OPSTR_RECORD_AUDIO);
288+
289+
AppOpsManager.AttributedOpEntry attributed1 = mock(AppOpsManager.AttributedOpEntry.class);
290+
when(attributed1.isRunning()).thenReturn(false);
291+
AppOpsManager.AttributedOpEntry attributed2 = mock(AppOpsManager.AttributedOpEntry.class);
292+
when(attributed2.isRunning()).thenReturn(true);
293+
AppOpsManager.AttributedOpEntry attributed3 = mock(AppOpsManager.AttributedOpEntry.class);
294+
when(attributed3.isRunning()).thenReturn(true);
295+
when(entry.getAttributedOpEntries()).thenReturn(
296+
Map.of("attr1", attributed1, "attr2", attributed2, "attr3", attributed3));
297+
298+
when(packageOps.getOps()).thenReturn(List.of(entry));
299+
when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
300+
.thenReturn(List.of(packageOps));
301+
302+
// WHEN we start listening
303+
mController.setListening(true);
304+
305+
// THEN the active list has the ops
306+
List<AppOpItem> list = mController.getActiveAppOps();
307+
// Multiple attributes get merged into one entry in the active ops
308+
assertEquals(1, list.size());
309+
310+
AppOpItem first = list.get(0);
311+
assertThat(first.getPackageName()).isEqualTo("package.one");
312+
assertThat(first.getUid()).isEqualTo(1);
313+
assertThat(first.getCode()).isEqualTo(AppOpsManager.OP_RECORD_AUDIO);
314+
}
315+
316+
/** Regression test for b/294104969. */
317+
@Test
318+
public void addCallback_existingCallbacksNotifiedOfCurrentActive() {
319+
AppOpsManager.PackageOps packageOps1 = createPackageOp(
320+
"package.one",
321+
/* packageUid= */ 1,
322+
AppOpsManager.OPSTR_FINE_LOCATION,
323+
/* isRunning= */ true);
324+
AppOpsManager.PackageOps packageOps2 = createPackageOp(
325+
"package.two",
326+
/* packageUid= */ 2,
327+
AppOpsManager.OPSTR_RECORD_AUDIO,
328+
/* isRunning= */ true);
329+
AppOpsManager.PackageOps packageOps3 = createPackageOp(
330+
"package.three",
331+
/* packageUid= */ 3,
332+
AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE,
333+
/* isRunning= */ true);
334+
when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
335+
.thenReturn(List.of(packageOps1, packageOps2, packageOps3));
336+
337+
// WHEN we start listening
338+
mController.addCallback(
339+
new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_FINE_LOCATION},
340+
mCallback);
341+
mTestableLooper.processAllMessages();
342+
343+
// THEN the callback is notified of the current active ops it cares about
344+
verify(mCallback).onActiveStateChanged(
345+
AppOpsManager.OP_FINE_LOCATION,
346+
/* uid= */ 1,
347+
"package.one",
348+
true);
349+
verify(mCallback).onActiveStateChanged(
350+
AppOpsManager.OP_RECORD_AUDIO,
351+
/* uid= */ 2,
352+
"package.two",
353+
true);
354+
verify(mCallback, never()).onActiveStateChanged(
355+
AppOpsManager.OP_PHONE_CALL_MICROPHONE,
356+
/* uid= */ 3,
357+
"package.three",
358+
true);
359+
}
360+
160361
@Test
161362
public void addCallback_includedCode() {
162363
mController.addCallback(
@@ -673,6 +874,22 @@ public void testPhoneCallCameraFilteredWhenCameraDisabled() {
673874
assertEquals(AppOpsManager.OP_PHONE_CALL_CAMERA, list.get(cameraIdx).getCode());
674875
}
675876

877+
private AppOpsManager.PackageOps createPackageOp(
878+
String packageName, int packageUid, String opStr, boolean isRunning) {
879+
AppOpsManager.PackageOps packageOps = mock(AppOpsManager.PackageOps.class);
880+
when(packageOps.getPackageName()).thenReturn(packageName);
881+
when(packageOps.getUid()).thenReturn(packageUid);
882+
AppOpsManager.OpEntry entry = mock(AppOpsManager.OpEntry.class);
883+
when(entry.getOpStr()).thenReturn(opStr);
884+
AppOpsManager.AttributedOpEntry attributed = mock(AppOpsManager.AttributedOpEntry.class);
885+
when(attributed.isRunning()).thenReturn(isRunning);
886+
887+
when(packageOps.getOps()).thenReturn(Collections.singletonList(entry));
888+
when(entry.getAttributedOpEntries()).thenReturn(Map.of("tag", attributed));
889+
890+
return packageOps;
891+
}
892+
676893
private class TestHandler extends AppOpsControllerImpl.H {
677894
TestHandler(Looper looper) {
678895
mController.super(looper);

0 commit comments

Comments
 (0)