API for notification listener for Companioon apps
Test: 1. Trigger the confitrmation dialog. Ensure it looks exactly like the one from settings. 2. Call an API without associating the appa first Ensure exception is thrown with a message mentioning the need to associate 1st Change-Id: I94d4116e1988db869ed445ae3fd018c50590e3f4
This commit is contained in:
@@ -8262,6 +8262,8 @@ package android.companion {
|
||||
method public void associate(android.companion.AssociationRequest, android.companion.CompanionDeviceManager.Callback, android.os.Handler);
|
||||
method public void disassociate(java.lang.String);
|
||||
method public java.util.List<java.lang.String> getAssociations();
|
||||
method public boolean hasNotificationAccess(android.content.ComponentName);
|
||||
method public void requestNotificationAccess(android.content.ComponentName);
|
||||
field public static final java.lang.String EXTRA_DEVICE = "android.companion.extra.DEVICE";
|
||||
}
|
||||
|
||||
|
||||
@@ -8756,6 +8756,8 @@ package android.companion {
|
||||
method public void associate(android.companion.AssociationRequest, android.companion.CompanionDeviceManager.Callback, android.os.Handler);
|
||||
method public void disassociate(java.lang.String);
|
||||
method public java.util.List<java.lang.String> getAssociations();
|
||||
method public boolean hasNotificationAccess(android.content.ComponentName);
|
||||
method public void requestNotificationAccess(android.content.ComponentName);
|
||||
field public static final java.lang.String EXTRA_DEVICE = "android.companion.extra.DEVICE";
|
||||
}
|
||||
|
||||
|
||||
@@ -8293,6 +8293,8 @@ package android.companion {
|
||||
method public void associate(android.companion.AssociationRequest, android.companion.CompanionDeviceManager.Callback, android.os.Handler);
|
||||
method public void disassociate(java.lang.String);
|
||||
method public java.util.List<java.lang.String> getAssociations();
|
||||
method public boolean hasNotificationAccess(android.content.ComponentName);
|
||||
method public void requestNotificationAccess(android.content.ComponentName);
|
||||
field public static final java.lang.String EXTRA_DEVICE = "android.companion.extra.DEVICE";
|
||||
}
|
||||
|
||||
|
||||
@@ -22,11 +22,13 @@ import static com.android.internal.util.Preconditions.checkNotNull;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.IntentSender;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Handler;
|
||||
import android.os.RemoteException;
|
||||
import android.service.notification.NotificationListenerService;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Collections;
|
||||
@@ -195,22 +197,47 @@ public final class CompanionDeviceManager {
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public void requestNotificationAccess() {
|
||||
/**
|
||||
* Request notification access for the given component.
|
||||
*
|
||||
* The given component must follow the protocol specified in {@link NotificationListenerService}
|
||||
*
|
||||
* Only components from the same {@link ComponentName#getPackageName package} as the calling app
|
||||
* are allowed.
|
||||
*
|
||||
* Your app must have an association with a device before calling this API
|
||||
*/
|
||||
public void requestNotificationAccess(ComponentName component) {
|
||||
if (!checkFeaturePresent()) {
|
||||
return;
|
||||
}
|
||||
//TODO implement
|
||||
throw new UnsupportedOperationException("Not yet implemented");
|
||||
try {
|
||||
mService.requestNotificationAccess(component).send();
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
} catch (PendingIntent.CanceledException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public boolean haveNotificationAccess() {
|
||||
/**
|
||||
* Check whether the given component can access the notifications via a
|
||||
* {@link NotificationListenerService}
|
||||
*
|
||||
* Your app must have an association with a device before calling this API
|
||||
*
|
||||
* @param component the name of the component
|
||||
* @return whether the given component has the notification listener permission
|
||||
*/
|
||||
public boolean hasNotificationAccess(ComponentName component) {
|
||||
if (!checkFeaturePresent()) {
|
||||
return false;
|
||||
}
|
||||
//TODO implement
|
||||
throw new UnsupportedOperationException("Not yet implemented");
|
||||
try {
|
||||
return mService.hasNotificationAccess(component);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkFeaturePresent() {
|
||||
|
||||
@@ -16,8 +16,10 @@
|
||||
|
||||
package android.companion;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.companion.IFindDeviceCallback;
|
||||
import android.companion.AssociationRequest;
|
||||
import android.content.ComponentName;
|
||||
|
||||
/**
|
||||
* Interface for communication with the core companion device manager service.
|
||||
@@ -32,7 +34,6 @@ interface ICompanionDeviceManager {
|
||||
List<String> getAssociations(String callingPackage, int userId);
|
||||
void disassociate(String deviceMacAddress, String callingPackage);
|
||||
|
||||
//TODO add these
|
||||
// boolean haveNotificationAccess(String packageName);
|
||||
// oneway void requestNotificationAccess(String packageName);
|
||||
boolean hasNotificationAccess(in ComponentName component);
|
||||
PendingIntent requestNotificationAccess(in ComponentName component);
|
||||
}
|
||||
|
||||
@@ -16,9 +16,15 @@
|
||||
|
||||
package android.os;
|
||||
|
||||
import android.util.ExceptionUtils;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.util.FastPrintWriter;
|
||||
import com.android.internal.util.FunctionalUtils;
|
||||
import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
|
||||
import com.android.internal.util.FunctionalUtils.ThrowingSupplier;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
@@ -26,7 +32,6 @@ import java.io.FileOutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Base class for a remotable object, the core part of a lightweight
|
||||
@@ -251,14 +256,23 @@ public class Binder implements IBinder {
|
||||
* Convenience method for running the provided action enclosed in
|
||||
* {@link #clearCallingIdentity}/{@link #restoreCallingIdentity}
|
||||
*
|
||||
* Any exception thrown by the given action will be caught and rethrown after the call to
|
||||
* {@link #restoreCallingIdentity}
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final void withCleanCallingIdentity(Runnable action) {
|
||||
public static final void withCleanCallingIdentity(ThrowingRunnable action) {
|
||||
long callingIdentity = clearCallingIdentity();
|
||||
Throwable throwableToPropagate = null;
|
||||
try {
|
||||
action.run();
|
||||
} catch (Throwable throwable) {
|
||||
throwableToPropagate = throwable;
|
||||
} finally {
|
||||
restoreCallingIdentity(callingIdentity);
|
||||
if (throwableToPropagate != null) {
|
||||
throw ExceptionUtils.propagate(throwableToPropagate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,14 +280,24 @@ public class Binder implements IBinder {
|
||||
* Convenience method for running the provided action enclosed in
|
||||
* {@link #clearCallingIdentity}/{@link #restoreCallingIdentity} returning the result
|
||||
*
|
||||
* Any exception thrown by the given action will be caught and rethrown after the call to
|
||||
* {@link #restoreCallingIdentity}
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final <T> T withCleanCallingIdentity(Supplier<T> action) {
|
||||
public static final <T> T withCleanCallingIdentity(ThrowingSupplier<T> action) {
|
||||
long callingIdentity = clearCallingIdentity();
|
||||
Throwable throwableToPropagate = null;
|
||||
try {
|
||||
return action.get();
|
||||
} catch (Throwable throwable) {
|
||||
throwableToPropagate = throwable;
|
||||
return null; // overridden by throwing in finally block
|
||||
} finally {
|
||||
restoreCallingIdentity(callingIdentity);
|
||||
if (throwableToPropagate != null) {
|
||||
throw ExceptionUtils.propagate(throwableToPropagate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.text.TextUtils;
|
||||
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.function.Function;
|
||||
@@ -80,6 +81,12 @@ public class SettingsStringUtil {
|
||||
return s;
|
||||
}
|
||||
|
||||
public static String addAll(String delimitedElements, Collection<String> elements) {
|
||||
final ColonDelimitedSet<String> set
|
||||
= new ColonDelimitedSet.OfStrings(delimitedElements);
|
||||
return set.addAll(elements) ? set.toString() : delimitedElements;
|
||||
}
|
||||
|
||||
public static String add(String delimitedElements, String element) {
|
||||
final ColonDelimitedSet<String> set
|
||||
= new ColonDelimitedSet.OfStrings(delimitedElements);
|
||||
|
||||
@@ -67,10 +67,15 @@ public abstract class AlertActivity extends Activity implements DialogInterface
|
||||
|
||||
@Override
|
||||
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
|
||||
event.setClassName(Dialog.class.getName());
|
||||
event.setPackageName(getPackageName());
|
||||
return dispatchPopulateAccessibilityEvent(this, event);
|
||||
}
|
||||
|
||||
ViewGroup.LayoutParams params = getWindow().getAttributes();
|
||||
public static boolean dispatchPopulateAccessibilityEvent(Activity act,
|
||||
AccessibilityEvent event) {
|
||||
event.setClassName(Dialog.class.getName());
|
||||
event.setPackageName(act.getPackageName());
|
||||
|
||||
ViewGroup.LayoutParams params = act.getWindow().getAttributes();
|
||||
boolean isFullScreen = (params.width == ViewGroup.LayoutParams.MATCH_PARENT) &&
|
||||
(params.height == ViewGroup.LayoutParams.MATCH_PARENT);
|
||||
event.setFullScreen(isFullScreen);
|
||||
@@ -86,8 +91,7 @@ public abstract class AlertActivity extends Activity implements DialogInterface
|
||||
* @see #mAlertParams
|
||||
*/
|
||||
protected void setupAlert() {
|
||||
mAlertParams.apply(mAlert);
|
||||
mAlert.installContent();
|
||||
mAlert.installContent(mAlertParams);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -247,6 +247,11 @@ public class AlertController {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void installContent(AlertParams params) {
|
||||
params.apply(this);
|
||||
installContent();
|
||||
}
|
||||
|
||||
public void installContent() {
|
||||
int contentView = selectContentView();
|
||||
mWindow.setContentView(contentView);
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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.internal.notification;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
|
||||
public final class NotificationAccessConfirmationActivityContract {
|
||||
private static final ComponentName COMPONENT_NAME = new ComponentName(
|
||||
"com.android.settings",
|
||||
"com.android.settings.notification.NotificationAccessConfirmationActivity");
|
||||
public static final String EXTRA_USER_ID = "user_id";
|
||||
public static final String EXTRA_COMPONENT_NAME = "component_name";
|
||||
public static final String EXTRA_PACKAGE_TITLE = "package_title";
|
||||
|
||||
public static Intent launcherIntent(int userId, ComponentName component, String packageTitle) {
|
||||
return new Intent()
|
||||
.setComponent(COMPONENT_NAME)
|
||||
.putExtra(EXTRA_USER_ID, userId)
|
||||
.putExtra(EXTRA_COMPONENT_NAME, component)
|
||||
.putExtra(EXTRA_PACKAGE_TITLE, packageTitle);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.internal.util;
|
||||
|
||||
import static com.android.internal.util.ArrayUtils.isEmpty;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
|
||||
@@ -64,7 +66,7 @@ public class CollectionUtils {
|
||||
*/
|
||||
public static @NonNull <I, O> List<O> map(@Nullable List<I> cur,
|
||||
Function<? super I, ? extends O> f) {
|
||||
if (cur == null || cur.isEmpty()) return Collections.emptyList();
|
||||
if (isEmpty(cur)) return Collections.emptyList();
|
||||
final ArrayList<O> result = new ArrayList<>();
|
||||
for (int i = 0; i < cur.size(); i++) {
|
||||
result.add(f.apply(cur.get(i)));
|
||||
@@ -72,6 +74,30 @@ public class CollectionUtils {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link #map(List, Function)} + {@link #filter(List, java.util.function.Predicate)}
|
||||
*
|
||||
* Calling this is equivalent (but more memory efficient) to:
|
||||
*
|
||||
* {@code
|
||||
* filter(
|
||||
* map(cur, f),
|
||||
* i -> { i != null })
|
||||
* }
|
||||
*/
|
||||
public static @NonNull <I, O> List<O> mapNotNull(@Nullable List<I> cur,
|
||||
Function<? super I, ? extends O> f) {
|
||||
if (isEmpty(cur)) return Collections.emptyList();
|
||||
final ArrayList<O> result = new ArrayList<>();
|
||||
for (int i = 0; i < cur.size(); i++) {
|
||||
O transformed = f.apply(cur.get(i));
|
||||
if (transformed != null) {
|
||||
result.add(transformed);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given list, or an immutable empty list if the provided list is null
|
||||
*
|
||||
@@ -94,7 +120,7 @@ public class CollectionUtils {
|
||||
* Returns the elements of the given list that are of type {@code c}
|
||||
*/
|
||||
public static @NonNull <T> List<T> filter(@Nullable List<?> list, Class<T> c) {
|
||||
if (ArrayUtils.isEmpty(list)) return Collections.emptyList();
|
||||
if (isEmpty(list)) return Collections.emptyList();
|
||||
ArrayList<T> result = null;
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
final Object item = list.get(i);
|
||||
@@ -120,7 +146,7 @@ public class CollectionUtils {
|
||||
*/
|
||||
public static @Nullable <T> T find(@Nullable List<T> items,
|
||||
java.util.function.Predicate<T> predicate) {
|
||||
if (ArrayUtils.isEmpty(items)) return null;
|
||||
if (isEmpty(items)) return null;
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
final T item = items.get(i);
|
||||
if (predicate.test(item)) return item;
|
||||
@@ -145,11 +171,17 @@ public class CollectionUtils {
|
||||
* {@link Collections#emptyList}
|
||||
*/
|
||||
public static @NonNull <T> List<T> remove(@Nullable List<T> cur, T val) {
|
||||
if (cur == null || cur == Collections.emptyList()) {
|
||||
return Collections.emptyList();
|
||||
if (isEmpty(cur)) {
|
||||
return emptyIfNull(cur);
|
||||
}
|
||||
cur.remove(val);
|
||||
return cur;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a list that will not be affected by mutations to the given original list.
|
||||
*/
|
||||
public static @NonNull <T> List<T> copyOf(@Nullable List<T> cur) {
|
||||
return isEmpty(cur) ? Collections.emptyList() : new ArrayList<>(cur);
|
||||
}
|
||||
}
|
||||
|
||||
48
core/java/com/android/internal/util/FunctionalUtils.java
Normal file
48
core/java/com/android/internal/util/FunctionalUtils.java
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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.internal.util;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Utilities specific to functional programming
|
||||
*/
|
||||
public class FunctionalUtils {
|
||||
private FunctionalUtils() {}
|
||||
|
||||
/**
|
||||
* An equivalent of {@link Runnable} that allows throwing checked exceptions
|
||||
*
|
||||
* This can be used to specify a lambda argument without forcing all the checked exceptions
|
||||
* to be handled within it
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ThrowingRunnable {
|
||||
void run() throws Exception;
|
||||
}
|
||||
|
||||
/**
|
||||
* An equivalent of {@link Supplier} that allows throwing checked exceptions
|
||||
*
|
||||
* This can be used to specify a lambda argument without forcing all the checked exceptions
|
||||
* to be handled within it
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ThrowingSupplier<T> {
|
||||
T get() throws Exception;
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,23 @@ public class Preconditions {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that an expression checking an argument is true.
|
||||
*
|
||||
* @param expression the expression to check
|
||||
* @param messageTemplate a printf-style message template to use if the check fails; will
|
||||
* be converted to a string using {@link String#format(String, Object...)}
|
||||
* @param messageArgs arguments for {@code messageTemplate}
|
||||
* @throws IllegalArgumentException if {@code expression} is false
|
||||
*/
|
||||
public static void checkArgument(boolean expression,
|
||||
final String messageTemplate,
|
||||
final Object... messageArgs) {
|
||||
if (!expression) {
|
||||
throw new IllegalArgumentException(String.format(messageTemplate, messageArgs));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that an string reference passed as a parameter to the calling
|
||||
* method is not empty.
|
||||
|
||||
@@ -3556,6 +3556,11 @@
|
||||
android:process=":ui">
|
||||
</activity>
|
||||
|
||||
<activity android:name="com.android.settings.notification.NotificationAccessConfirmationActivity"
|
||||
android:theme="@android:style/Theme.DeviceDefault.Light.Dialog.Alert"
|
||||
android:excludeFromRecents="true">
|
||||
</activity>
|
||||
|
||||
<receiver android:name="com.android.server.BootReceiver"
|
||||
android:systemUserOnly="true">
|
||||
<intent-filter android:priority="1000">
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
<!-- Private symbols that we need to reference from framework code. See
|
||||
frameworks/base/core/res/MakeJavaSymbols.sed for how to easily generate
|
||||
this.
|
||||
|
||||
Can be referenced in java code as: com.android.internal.R.<type>.<name>
|
||||
and in layout xml as: "@*android:<type>/<name>"
|
||||
-->
|
||||
<java-symbol type="id" name="account_name" />
|
||||
<java-symbol type="id" name="account_row_icon" />
|
||||
|
||||
@@ -20,10 +20,12 @@ package com.android.server.print;
|
||||
import static com.android.internal.util.CollectionUtils.size;
|
||||
import static com.android.internal.util.Preconditions.checkArgument;
|
||||
import static com.android.internal.util.Preconditions.checkNotNull;
|
||||
import static com.android.internal.util.Preconditions.checkState;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.CheckResult;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.PendingIntent;
|
||||
import android.companion.AssociationRequest;
|
||||
import android.companion.CompanionDeviceManager;
|
||||
import android.companion.ICompanionDeviceDiscoveryService;
|
||||
@@ -47,13 +49,18 @@ import android.os.Parcel;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.provider.SettingsStringUtil.ComponentNameSet;
|
||||
import android.text.BidiFormatter;
|
||||
import android.util.AtomicFile;
|
||||
import android.util.ExceptionUtils;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
import android.util.Xml;
|
||||
|
||||
import com.android.internal.app.IAppOpsService;
|
||||
import com.android.internal.content.PackageMonitor;
|
||||
import com.android.internal.notification.NotificationAccessConfirmationActivityContract;
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.internal.util.CollectionUtils;
|
||||
import com.android.server.FgThread;
|
||||
@@ -80,6 +87,7 @@ import java.util.function.Function;
|
||||
//TODO schedule stopScan on activity destroy(except if configuration change)
|
||||
//TODO on associate called again after configuration change -> replace old callback with new
|
||||
//TODO avoid leaking calling activity in IFindDeviceCallback (see PrintManager#print for example)
|
||||
//TODO check user-feature present in manifest on API calls
|
||||
/** @hide */
|
||||
public class CompanionDeviceManagerService extends SystemService implements Binder.DeathRecipient {
|
||||
|
||||
@@ -217,6 +225,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
|
||||
a -> a.deviceAddress);
|
||||
}
|
||||
|
||||
//TODO also revoke notification access
|
||||
@Override
|
||||
public void disassociate(String deviceMacAddress, String callingPackage)
|
||||
throws RemoteException {
|
||||
@@ -237,11 +246,49 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
|
||||
|
||||
checkArgument(getCallingUserId() == userId,
|
||||
"Must be called by either same user or system");
|
||||
|
||||
mAppOpsManager.checkPackage(Binder.getCallingUid(), pkg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PendingIntent requestNotificationAccess(ComponentName component)
|
||||
throws RemoteException {
|
||||
String callingPackage = component.getPackageName();
|
||||
checkCanCallNotificationApi(callingPackage);
|
||||
int userId = getCallingUserId();
|
||||
String packageTitle = BidiFormatter.getInstance().unicodeWrap(
|
||||
getPackageInfo(callingPackage, userId)
|
||||
.applicationInfo
|
||||
.loadSafeLabel(getContext().getPackageManager())
|
||||
.toString());
|
||||
long identity = Binder.clearCallingIdentity();
|
||||
try {
|
||||
return PendingIntent.getActivity(getContext(),
|
||||
0 /* request code */,
|
||||
NotificationAccessConfirmationActivityContract.launcherIntent(
|
||||
userId, component, packageTitle),
|
||||
PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT
|
||||
| PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(identity);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNotificationAccess(ComponentName component) throws RemoteException {
|
||||
checkCanCallNotificationApi(component.getPackageName());
|
||||
String setting = Settings.Secure.getString(getContext().getContentResolver(),
|
||||
Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
|
||||
return new ComponentNameSet(setting).contains(component);
|
||||
}
|
||||
|
||||
private void checkCanCallNotificationApi(String callingPackage) throws RemoteException {
|
||||
checkCallerIsSystemOr(callingPackage);
|
||||
checkState(!ArrayUtils.isEmpty(readAllAssociations(getCallingUserId(), callingPackage)),
|
||||
"App must have an association before calling this API");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private int getCallingUserId() {
|
||||
return UserHandle.getUserId(Binder.getCallingUid());
|
||||
}
|
||||
@@ -290,6 +337,17 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
|
||||
private ICompanionDeviceDiscoveryServiceCallback.Stub getServiceCallback() {
|
||||
return new ICompanionDeviceDiscoveryServiceCallback.Stub() {
|
||||
|
||||
@Override
|
||||
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
|
||||
throws RemoteException {
|
||||
try {
|
||||
return super.onTransact(code, data, reply, flags);
|
||||
} catch (Throwable e) {
|
||||
Slog.e(LOG_TAG, "Error during IPC", e);
|
||||
throw ExceptionUtils.propagate(e, RemoteException.class);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceSelected(String packageName, int userId, String deviceAddress) {
|
||||
updateSpecialAccessPermissionForAssociatedPackage(packageName, userId);
|
||||
@@ -301,7 +359,6 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
|
||||
public void onDeviceSelectionCancel() {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@@ -351,8 +408,13 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
|
||||
}
|
||||
|
||||
private void recordAssociation(String priviledgedPackage, String deviceAddress) {
|
||||
if (DEBUG) {
|
||||
Log.i(LOG_TAG, "recordAssociation(priviledgedPackage = " + priviledgedPackage
|
||||
+ ", deviceAddress = " + deviceAddress + ")");
|
||||
}
|
||||
int userId = getCallingUserId();
|
||||
updateAssociations(associations -> CollectionUtils.add(associations,
|
||||
new Association(getCallingUserId(), deviceAddress, priviledgedPackage)));
|
||||
new Association(userId, deviceAddress, priviledgedPackage)));
|
||||
}
|
||||
|
||||
private void updateAssociations(Function<List<Association>, List<Association>> update) {
|
||||
@@ -364,7 +426,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
|
||||
final AtomicFile file = getStorageFileForUser(userId);
|
||||
synchronized (file) {
|
||||
List<Association> associations = readAllAssociations(userId);
|
||||
final ArrayList<Association> old = new ArrayList<>(associations);
|
||||
final List<Association> old = CollectionUtils.copyOf(associations);
|
||||
associations = update.apply(associations);
|
||||
if (size(old) == size(associations)) return;
|
||||
|
||||
@@ -394,15 +456,6 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
//TODO Show dialog before recording notification access
|
||||
// final SettingStringHelper setting =
|
||||
// new SettingStringHelper(
|
||||
// getContext().getContentResolver(),
|
||||
// Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
|
||||
// getUserId());
|
||||
// setting.write(ColonDelimitedSet.OfStrings.add(setting.read(), priviledgedPackage));
|
||||
}
|
||||
|
||||
private AtomicFile getStorageFileForUser(int uid) {
|
||||
|
||||
Reference in New Issue
Block a user