From 5bd5767768a9dc0a0d2972239fbfd2f68258cfd9 Mon Sep 17 00:00:00 2001 From: Priyank Singh Date: Fri, 10 May 2019 17:12:13 -0700 Subject: [PATCH] Adding the privacy chip to the CarStatusBar All the files are copied from P.car. It cannot be cherrypicked as P.car contains more code in th ecl that is not needed in Q. Bug: 123355120 Test: Manual Change-Id: I9529c0c9ffd5b317870ee85ff8f81ee126bb6e5c --- .../res/layout/car_ongoing_privacy_chip.xml | 37 +++ .../res/layout/car_top_navigation_bar.xml | 2 + packages/CarSystemUI/res/values/dimens.xml | 20 ++ .../car/privacy/OngoingPrivacyChip.java | 228 ++++++++++++++++++ .../car/privacy/PrivacyApplication.java | 62 +++++ .../car/privacy/PrivacyDialogBuilder.java | 59 +++++ .../statusbar/car/privacy/PrivacyItem.java | 46 ++++ .../statusbar/car/privacy/PrivacyType.java | 53 ++++ 8 files changed, 507 insertions(+) create mode 100644 packages/CarSystemUI/res/layout/car_ongoing_privacy_chip.xml create mode 100644 packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/OngoingPrivacyChip.java create mode 100644 packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyApplication.java create mode 100644 packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyDialogBuilder.java create mode 100644 packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyItem.java create mode 100644 packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyType.java diff --git a/packages/CarSystemUI/res/layout/car_ongoing_privacy_chip.xml b/packages/CarSystemUI/res/layout/car_ongoing_privacy_chip.xml new file mode 100644 index 0000000000000..918abd962dc2f --- /dev/null +++ b/packages/CarSystemUI/res/layout/car_ongoing_privacy_chip.xml @@ -0,0 +1,37 @@ + + + + + + + \ No newline at end of file diff --git a/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml index 925ccb4a162a1..cae89c16a03ee 100644 --- a/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml +++ b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml @@ -65,6 +65,8 @@ /> + + 24dp 96dp 128dp + 36dp + 0dp + 0dp + 0dp + 0dp + + 0dp + 0dp + 0dp + 0dp 100dp @@ -76,6 +86,16 @@ 4dp 12dp + + 40dp + + 40dp + + 20dp + + 10dp + + 10dp @*android:dimen/car_single_line_list_item_height diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/OngoingPrivacyChip.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/OngoingPrivacyChip.java new file mode 100644 index 0000000000000..ead1de2bd352e --- /dev/null +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/OngoingPrivacyChip.java @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.car.privacy; + +import android.app.ActivityManager; +import android.app.AppOpsManager; +import android.content.Context; +import android.content.Intent; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.os.UserManager; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; + +import com.android.systemui.Dependency; +import com.android.systemui.R; +import com.android.systemui.appops.AppOpItem; +import com.android.systemui.appops.AppOpsController; +import com.android.systemui.plugins.ActivityStarter; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * Layout defining the privacy chip that will be displayed in CarStatusRar with the information for + * which applications are using AppOpps permission fpr camera, mic and location. + */ +public class OngoingPrivacyChip extends LinearLayout implements View.OnClickListener { + + private Context mContext; + + private LinearLayout mIconsContainer; + private List mPrivacyItems; + private static AppOpsController sAppOpsController; + private UserManager mUserManager; + private int mCurrentUser; + private List mCurrentUserIds; + private boolean mListening = false; + PrivacyDialogBuilder mPrivacyDialogBuilder; + private LinearLayout mPrivacyChip; + private ActivityStarter mActivityStarter; + + protected static final int[] OPS = new int[]{ + AppOpsManager.OP_CAMERA, + AppOpsManager.OP_RECORD_AUDIO, + AppOpsManager.OP_COARSE_LOCATION, + AppOpsManager.OP_FINE_LOCATION + }; + + public OngoingPrivacyChip(Context context) { + super(context, null); + init(context); + } + + public OngoingPrivacyChip(Context context, AttributeSet attr) { + super(context, attr); + init(context); + } + + public OngoingPrivacyChip(Context context, AttributeSet attr, int defStyle) { + super(context, attr, defStyle); + init(context); + } + + public OngoingPrivacyChip(Context context, AttributeSet attr, int defStyle, int a) { + super(context, attr, defStyle, a); + init(context); + } + + private void init(Context context) { + mContext = context; + mPrivacyItems = new ArrayList<>(); + sAppOpsController = Dependency.get(AppOpsController.class); + mUserManager = mContext.getSystemService(UserManager.class); + mActivityStarter = Dependency.get(ActivityStarter.class); + mCurrentUser = ActivityManager.getCurrentUser(); + mCurrentUserIds = mUserManager.getProfiles(mCurrentUser).stream().map( + userInfo -> userInfo.id).collect(Collectors.toList()); + + mPrivacyDialogBuilder = new PrivacyDialogBuilder(context, mPrivacyItems); + } + + private AppOpsController.Callback mCallback = new AppOpsController.Callback() { + + @Override + public void onActiveStateChanged(int code, int uid, String packageName, boolean active) { + int userId = UserHandle.getUserId(uid); + if (mCurrentUserIds.contains(userId)) { + updatePrivacyList(); + } + } + }; + + @Override + public void onFinishInflate() { + mIconsContainer = findViewById(R.id.icons_container); + mPrivacyChip = (LinearLayout) findViewById(R.id.car_privacy_chip); + if (mPrivacyChip != null) { + mPrivacyChip.setOnClickListener(this); + setListening(true); + } + } + + @Override + public void onDetachedFromWindow() { + if (mPrivacyChip != null) { + setListening(false); + } + super.onDetachedFromWindow(); + } + + @Override + public void onClick(View v) { + updatePrivacyList(); + Handler mUiHandler = new Handler(Looper.getMainLooper()); + mUiHandler.post(() -> { + mActivityStarter.postStartActivityDismissingKeyguard( + new Intent(Intent.ACTION_REVIEW_ONGOING_PERMISSION_USAGE), 0); + }); + } + + private void setListening(boolean listen) { + if (mListening == listen) { + return; + } + mListening = listen; + if (mListening) { + sAppOpsController.addCallback(OPS, mCallback); + updatePrivacyList(); + } else { + sAppOpsController.removeCallback(OPS, mCallback); + } + } + + private void updatePrivacyList() { + mPrivacyItems = mCurrentUserIds.stream() + .flatMap(item -> sAppOpsController.getActiveAppOpsForUser(item).stream()) + .filter(Objects::nonNull) + .map(item -> toPrivacyItem(item)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + mPrivacyDialogBuilder = new PrivacyDialogBuilder(mContext, mPrivacyItems); + + Handler refresh = new Handler(Looper.getMainLooper()); + refresh.post(new Runnable() { + @Override + public void run() { + updateView(); + } + }); + } + + private PrivacyItem toPrivacyItem(AppOpItem appOpItem) { + PrivacyType type; + switch (appOpItem.getCode()) { + case AppOpsManager.OP_CAMERA: + type = PrivacyType.TYPE_CAMERA; + break; + case AppOpsManager.OP_COARSE_LOCATION: + type = PrivacyType.TYPE_LOCATION; + break; + case AppOpsManager.OP_FINE_LOCATION: + type = PrivacyType.TYPE_LOCATION; + break; + case AppOpsManager.OP_RECORD_AUDIO: + type = PrivacyType.TYPE_MICROPHONE; + break; + default: + return null; + } + PrivacyApplication app = new PrivacyApplication(appOpItem.getPackageName(), mContext); + return new PrivacyItem(type, app, appOpItem.getTimeStarted()); + } + + // Should only be called if the mPrivacyDialogBuilder icons or app changed + private void updateView() { + if (mPrivacyItems.isEmpty()) { + mPrivacyChip.setVisibility(GONE); + return; + } + mPrivacyChip.setVisibility(VISIBLE); + setIcons(mPrivacyDialogBuilder); + + requestLayout(); + } + + private void setIcons(PrivacyDialogBuilder dialogBuilder) { + mIconsContainer.removeAllViews(); + dialogBuilder.generateIcons().forEach(item -> { + int size = mContext.getResources().getDimensionPixelSize( + R.dimen.privacy_chip_icon_height); + ImageView image = new ImageView(mContext); + image.setImageDrawable(item); + LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(size, size); + + int leftPadding = mContext.getResources().getDimensionPixelSize( + R.dimen.privacy_chip_icon_padding_left); + int topPadding = mContext.getResources().getDimensionPixelSize( + R.dimen.privacy_chip_icon_padding_top); + int rightPadding = mContext.getResources().getDimensionPixelSize( + R.dimen.privacy_chip_icon_padding_right); + int bottomPadding = mContext.getResources().getDimensionPixelSize( + R.dimen.privacy_chip_icon_padding_bottom); + image.setLayoutParams(layoutParams); + image.setPadding(leftPadding, topPadding, rightPadding, bottomPadding); + mIconsContainer.addView(image); + }); + } +} diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyApplication.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyApplication.java new file mode 100644 index 0000000000000..5ec7a77cb305e --- /dev/null +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyApplication.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.car.privacy; + +import android.car.userlib.CarUserManagerHelper; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.util.Log; + +/** + * Class to hold the data for the applications that are using the AppOps permissions. + */ +public class PrivacyApplication { + private static final String TAG = "PrivacyApplication"; + + private Drawable mIcon; + private String mApplicationName; + + public PrivacyApplication(String packageName, Context context) { + try { + CarUserManagerHelper carUserManagerHelper = new CarUserManagerHelper(context); + ApplicationInfo app = context.getPackageManager() + .getApplicationInfoAsUser(packageName, 0, + carUserManagerHelper.getCurrentForegroundUserId()); + mIcon = context.getPackageManager().getApplicationIcon(app); + mApplicationName = context.getPackageManager().getApplicationLabel(app).toString(); + } catch (PackageManager.NameNotFoundException e) { + mApplicationName = packageName; + Log.e(TAG, "Failed to to find package name", e); + } + } + + /** + * Gets the application name. + */ + public Drawable getIcon() { + return mIcon; + } + + /** + * Gets the application name. + */ + public String getApplicationName() { + return mApplicationName; + } +} diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyDialogBuilder.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyDialogBuilder.java new file mode 100644 index 0000000000000..3b83e7cc0623c --- /dev/null +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyDialogBuilder.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.car.privacy; + +import android.content.Context; +import android.graphics.drawable.Drawable; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * Helper class to build the {@link OngoingPrivacyDialog} + */ +public class PrivacyDialogBuilder { + + private Map> mItemsByType; + private PrivacyApplication mApplication; + private Context mContext; + + public PrivacyDialogBuilder(Context context, List itemsList) { + mContext = context; + mItemsByType = itemsList.stream().filter(Objects::nonNull).collect( + Collectors.groupingBy(PrivacyItem::getPrivacyType)); + List apps = itemsList.stream().filter(Objects::nonNull).map( + PrivacyItem::getPrivacyApplication).distinct().collect(Collectors.toList()); + mApplication = apps.size() == 1 ? apps.get(0) : null; + } + + /** + * Gets the icon id for all the {@link PrivacyItem} in the same order as of itemList. + */ + public List generateIcons() { + return mItemsByType.keySet().stream().map(item -> item.getIconId(mContext)).collect( + Collectors.toList()); + } + + /** + * Gets the application object. + */ + public PrivacyApplication getApplication() { + return mApplication; + } +} diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyItem.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyItem.java new file mode 100644 index 0000000000000..fca137392d741 --- /dev/null +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyItem.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.car.privacy; + +/** + * Class for holding the data of each privacy item displayed in {@link OngoingPrivacyDialog} + */ +public class PrivacyItem { + + private PrivacyType mPrivacyType; + private PrivacyApplication mPrivacyApplication; + + public PrivacyItem(PrivacyType privacyType, PrivacyApplication privacyApplication, + long timeStarted) { + this.mPrivacyType = privacyType; + this.mPrivacyApplication = privacyApplication; + } + + /** + * Gets the application object. + */ + public PrivacyApplication getPrivacyApplication() { + return mPrivacyApplication; + } + + /** + * Gets the privacy type for the application. + */ + public PrivacyType getPrivacyType() { + return mPrivacyType; + } +} diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyType.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyType.java new file mode 100644 index 0000000000000..8955c87bbff4f --- /dev/null +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyType.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.car.privacy; + +import android.content.Context; +import android.graphics.drawable.Drawable; + +import com.android.systemui.R; + +/** + * Enum for storing data for camera, mic and location. + */ +public enum PrivacyType { + TYPE_CAMERA(R.string.privacy_type_camera, com.android.internal.R.drawable.ic_camera), + TYPE_LOCATION(R.string.privacy_type_location, R.drawable.stat_sys_location), + TYPE_MICROPHONE(R.string.privacy_type_microphone, R.drawable.ic_mic_white); + + private int mNameId; + private int mIconId; + + PrivacyType(int nameId, int iconId) { + mNameId = nameId; + mIconId = iconId; + } + + /** + * Get the icon Id. + */ + public Drawable getIconId(Context context) { + return context.getResources().getDrawable(mIconId, null); + } + + /** + * Get the name Id. + */ + public String getNameId(Context context) { + return context.getResources().getString(mNameId); + } +}