1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
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 
17 package com.android.systemui.statusbar.notification.row;
18 
19 import android.app.AppOpsManager;
20 import android.content.Context;
21 import android.content.pm.ApplicationInfo;
22 import android.content.pm.PackageManager;
23 import android.graphics.drawable.Drawable;
24 import android.service.notification.StatusBarNotification;
25 import android.util.ArraySet;
26 import android.util.AttributeSet;
27 import android.view.View;
28 import android.view.accessibility.AccessibilityEvent;
29 import android.widget.ImageView;
30 import android.widget.LinearLayout;
31 import android.widget.TextView;
32 
33 import com.android.internal.logging.MetricsLogger;
34 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
35 import com.android.systemui.R;
36 
37 /**
38  * The guts of a notification revealed when performing a long press.
39  */
40 public class AppOpsInfo extends LinearLayout implements NotificationGuts.GutsContent {
41     private static final String TAG = "AppOpsGuts";
42 
43     private PackageManager mPm;
44 
45     private String mPkg;
46     private String mAppName;
47     private int mAppUid;
48     private StatusBarNotification mSbn;
49     private ArraySet<Integer> mAppOps;
50     private MetricsLogger mMetricsLogger;
51     private OnSettingsClickListener mOnSettingsClickListener;
52     private NotificationGuts mGutsContainer;
53 
54     private OnClickListener mOnOk = v -> {
55         closeControls(v);
56     };
57 
AppOpsInfo(Context context, AttributeSet attrs)58     public AppOpsInfo(Context context, AttributeSet attrs) {
59         super(context, attrs);
60     }
61 
62     public interface OnSettingsClickListener {
onClick(View v, String pkg, int uid, ArraySet<Integer> ops)63         void onClick(View v, String pkg, int uid, ArraySet<Integer> ops);
64     }
65 
bindGuts(final PackageManager pm, final OnSettingsClickListener onSettingsClick, final StatusBarNotification sbn, ArraySet<Integer> activeOps)66     public void bindGuts(final PackageManager pm,
67             final OnSettingsClickListener onSettingsClick,
68             final StatusBarNotification sbn,
69             ArraySet<Integer> activeOps) {
70         mPkg = sbn.getPackageName();
71         mSbn = sbn;
72         mPm = pm;
73         mAppName = mPkg;
74         mOnSettingsClickListener = onSettingsClick;
75         mAppOps = activeOps;
76 
77         bindHeader();
78         bindPrompt();
79         bindButtons();
80 
81         mMetricsLogger = new MetricsLogger();
82         mMetricsLogger.visibility(MetricsEvent.APP_OPS_GUTS, true);
83     }
84 
bindHeader()85     private void bindHeader() {
86         // Package name
87         Drawable pkgicon = null;
88         ApplicationInfo info;
89         try {
90             info = mPm.getApplicationInfo(mPkg,
91                     PackageManager.MATCH_UNINSTALLED_PACKAGES
92                             | PackageManager.MATCH_DISABLED_COMPONENTS
93                             | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
94                             | PackageManager.MATCH_DIRECT_BOOT_AWARE);
95             if (info != null) {
96                 mAppUid = mSbn.getUid();
97                 mAppName = String.valueOf(mPm.getApplicationLabel(info));
98                 pkgicon = mPm.getApplicationIcon(info);
99             }
100         } catch (PackageManager.NameNotFoundException e) {
101             // app is gone, just show package name and generic icon
102             pkgicon = mPm.getDefaultActivityIcon();
103         }
104         ((ImageView) findViewById(R.id.pkgicon)).setImageDrawable(pkgicon);
105         ((TextView) findViewById(R.id.pkgname)).setText(mAppName);
106     }
107 
bindPrompt()108     private void bindPrompt() {
109         final TextView prompt = findViewById(R.id.prompt);
110         prompt.setText(getPrompt());
111     }
112 
bindButtons()113     private void bindButtons() {
114         View settings =  findViewById(R.id.settings);
115         settings.setOnClickListener((View view) -> {
116             mOnSettingsClickListener.onClick(view, mPkg, mAppUid, mAppOps);
117         });
118         TextView ok = findViewById(R.id.ok);
119         ok.setOnClickListener(mOnOk);
120     }
121 
getPrompt()122     private String getPrompt() {
123         if (mAppOps == null || mAppOps.size() == 0) {
124             return "";
125         } else if (mAppOps.size() == 1) {
126             if (mAppOps.contains(AppOpsManager.OP_CAMERA)) {
127                 return mContext.getString(R.string.appops_camera);
128             } else if (mAppOps.contains(AppOpsManager.OP_RECORD_AUDIO)) {
129                 return mContext.getString(R.string.appops_microphone);
130             } else {
131                 return mContext.getString(R.string.appops_overlay);
132             }
133         } else if (mAppOps.size() == 2) {
134             if (mAppOps.contains(AppOpsManager.OP_CAMERA)) {
135                 if (mAppOps.contains(AppOpsManager.OP_RECORD_AUDIO)) {
136                     return mContext.getString(R.string.appops_camera_mic);
137                 } else {
138                     return mContext.getString(R.string.appops_camera_overlay);
139                 }
140             } else {
141                 return mContext.getString(R.string.appops_mic_overlay);
142             }
143         } else {
144             return mContext.getString(R.string.appops_camera_mic_overlay);
145         }
146     }
147 
148     @Override
onInitializeAccessibilityEvent(AccessibilityEvent event)149     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
150         super.onInitializeAccessibilityEvent(event);
151         if (mGutsContainer != null &&
152                 event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
153             if (mGutsContainer.isExposed()) {
154                 event.getText().add(mContext.getString(
155                         R.string.notification_channel_controls_opened_accessibility, mAppName));
156             } else {
157                 event.getText().add(mContext.getString(
158                         R.string.notification_channel_controls_closed_accessibility, mAppName));
159             }
160         }
161     }
162 
closeControls(View v)163     private void closeControls(View v) {
164         mMetricsLogger.visibility(MetricsEvent.APP_OPS_GUTS, false);
165         int[] parentLoc = new int[2];
166         int[] targetLoc = new int[2];
167         mGutsContainer.getLocationOnScreen(parentLoc);
168         v.getLocationOnScreen(targetLoc);
169         final int centerX = v.getWidth() / 2;
170         final int centerY = v.getHeight() / 2;
171         final int x = targetLoc[0] - parentLoc[0] + centerX;
172         final int y = targetLoc[1] - parentLoc[1] + centerY;
173         mGutsContainer.closeControls(x, y, false, false);
174     }
175 
176     @Override
setGutsParent(NotificationGuts guts)177     public void setGutsParent(NotificationGuts guts) {
178         mGutsContainer = guts;
179     }
180 
181     @Override
willBeRemoved()182     public boolean willBeRemoved() {
183         return false;
184     }
185 
186     @Override
shouldBeSaved()187     public boolean shouldBeSaved() {
188         return false;
189     }
190 
191     @Override
getContentView()192     public View getContentView() {
193         return this;
194     }
195 
196     @Override
handleCloseControls(boolean save, boolean force)197     public boolean handleCloseControls(boolean save, boolean force) {
198         return false;
199     }
200 
201     @Override
getActualHeight()202     public int getActualHeight() {
203         return getHeight();
204     }
205 }
206