Merge "Adding the privacy chip to the CarStatusBar" into qt-dev

This commit is contained in:
TreeHugger Robot
2019-05-21 23:00:41 +00:00
committed by Android (Google) Code Review
8 changed files with 507 additions and 0 deletions

View 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>

View File

@@ -65,6 +65,8 @@
/>
</FrameLayout>
<include layout="@layout/car_ongoing_privacy_chip"/>
<FrameLayout
android:id="@+id/clock_container"
android:layout_width="wrap_content"

View File

@@ -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>

View File

@@ -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);
});
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}