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
This commit is contained in:
37
packages/CarSystemUI/res/layout/car_ongoing_privacy_chip.xml
Normal file
37
packages/CarSystemUI/res/layout/car_ongoing_privacy_chip.xml
Normal file
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
<com.android.systemui.statusbar.car.privacy.OngoingPrivacyChip
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/car_privacy_chip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="@dimen/ongoing_appops_chip_margin"
|
||||
android:layout_toStartOf="@+id/clock_container"
|
||||
android:focusable="true"
|
||||
android:gravity="center_vertical|end"
|
||||
android:orientation="horizontal"
|
||||
android:paddingEnd="@dimen/ongoing_appops_chip_side_padding"
|
||||
android:paddingStart="@dimen/ongoing_appops_chip_side_padding"
|
||||
android:visibility="visible">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/icons_container"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_vertical|start"/>
|
||||
</com.android.systemui.statusbar.car.privacy.OngoingPrivacyChip>
|
||||
@@ -65,6 +65,8 @@
|
||||
/>
|
||||
</FrameLayout>
|
||||
|
||||
<include layout="@layout/car_ongoing_privacy_chip"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/clock_container"
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
@@ -59,6 +59,16 @@
|
||||
<dimen name="car_keyline_1">24dp</dimen>
|
||||
<dimen name="car_keyline_2">96dp</dimen>
|
||||
<dimen name="car_keyline_3">128dp</dimen>
|
||||
<dimen name="privacy_chip_icon_height">36dp</dimen>
|
||||
<dimen name="privacy_chip_icon_padding_left">0dp</dimen>
|
||||
<dimen name="privacy_chip_icon_padding_right">0dp</dimen>
|
||||
<dimen name="privacy_chip_icon_padding_top">0dp</dimen>
|
||||
<dimen name="privacy_chip_icon_padding_bottom">0dp</dimen>
|
||||
|
||||
<dimen name="privacy_chip_text_padding_left">0dp</dimen>
|
||||
<dimen name="privacy_chip_text_padding_right">0dp</dimen>
|
||||
<dimen name="privacy_chip_text_padding_top">0dp</dimen>
|
||||
<dimen name="privacy_chip_text_padding_bottom">0dp</dimen>
|
||||
|
||||
<dimen name="privacy_chip_icon_max_height">100dp</dimen>
|
||||
|
||||
@@ -76,6 +86,16 @@
|
||||
<dimen name="ongoing_appops_chip_bg_padding">4dp</dimen>
|
||||
<!-- Radius of Ongoing App Ops chip corners -->
|
||||
<dimen name="ongoing_appops_chip_bg_corner_radius">12dp</dimen>
|
||||
<!-- Start padding for the app icon displayed in the dialog -->
|
||||
<dimen name="privacy_dialog_app_icon_padding_start">40dp</dimen>
|
||||
<!-- End padding for the app opps icon displayed in the dialog -->
|
||||
<dimen name="privacy_dialog_app_ops_icon_padding_end">40dp</dimen>
|
||||
<!-- Top padding for the list of application displayed in the dialog -->
|
||||
<dimen name="privacy_dialog_app_list_padding_top">20dp</dimen>
|
||||
<!-- Top padding for the dialog container-->
|
||||
<dimen name="privacy_dialog_container_padding_top">10dp</dimen>
|
||||
<!-- Top padding for the dialog title-->
|
||||
<dimen name="privacy_dialog_title_padding_start">10dp</dimen>
|
||||
|
||||
<!-- Car volume dimens. -->
|
||||
<dimen name="car_volume_item_height">@*android:dimen/car_single_line_list_item_height</dimen>
|
||||
|
||||
@@ -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<PrivacyItem> mPrivacyItems;
|
||||
private static AppOpsController sAppOpsController;
|
||||
private UserManager mUserManager;
|
||||
private int mCurrentUser;
|
||||
private List<Integer> 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<PrivacyType, List<PrivacyItem>> mItemsByType;
|
||||
private PrivacyApplication mApplication;
|
||||
private Context mContext;
|
||||
|
||||
public PrivacyDialogBuilder(Context context, List<PrivacyItem> itemsList) {
|
||||
mContext = context;
|
||||
mItemsByType = itemsList.stream().filter(Objects::nonNull).collect(
|
||||
Collectors.groupingBy(PrivacyItem::getPrivacyType));
|
||||
List<PrivacyApplication> 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<Drawable> generateIcons() {
|
||||
return mItemsByType.keySet().stream().map(item -> item.getIconId(mContext)).collect(
|
||||
Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the application object.
|
||||
*/
|
||||
public PrivacyApplication getApplication() {
|
||||
return mApplication;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user