Merge "Give SliceManagerService a concept of pinned slices."
This commit is contained in:
committed by
Android (Google) Code Review
commit
4b1557d3a0
@@ -99,6 +99,7 @@ java_library {
|
||||
"core/java/android/app/backup/IRestoreSession.aidl",
|
||||
"core/java/android/app/backup/ISelectBackupTransportCallback.aidl",
|
||||
"core/java/android/app/slice/ISliceManager.aidl",
|
||||
"core/java/android/app/slice/ISliceListener.aidl",
|
||||
"core/java/android/app/timezone/ICallback.aidl",
|
||||
"core/java/android/app/timezone/IRulesManager.aidl",
|
||||
"core/java/android/app/usage/ICacheQuotaService.aidl",
|
||||
|
||||
@@ -97,6 +97,7 @@ aidl_files := \
|
||||
frameworks/base/core/java/android/app/admin/SystemUpdatePolicy.aidl \
|
||||
frameworks/base/core/java/android/app/admin/PasswordMetrics.aidl \
|
||||
frameworks/base/core/java/android/app/slice/ISliceManager.aidl \
|
||||
frameworks/base/core/java/android/app/slice/ISliceListener.aidl \
|
||||
frameworks/base/core/java/android/print/PrintDocumentInfo.aidl \
|
||||
frameworks/base/core/java/android/print/PageRange.aidl \
|
||||
frameworks/base/core/java/android/print/PrintAttributes.aidl \
|
||||
|
||||
25
core/java/android/app/slice/ISliceListener.aidl
Normal file
25
core/java/android/app/slice/ISliceListener.aidl
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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 android.app.slice;
|
||||
|
||||
import android.app.slice.ISliceManager;
|
||||
import android.app.slice.Slice;
|
||||
|
||||
/** @hide */
|
||||
oneway interface ISliceListener {
|
||||
void onSliceUpdated(in Slice s);
|
||||
}
|
||||
@@ -16,6 +16,17 @@
|
||||
|
||||
package android.app.slice;
|
||||
|
||||
import android.app.slice.ISliceListener;
|
||||
import android.app.slice.SliceSpec;
|
||||
import android.net.Uri;
|
||||
|
||||
/** @hide */
|
||||
interface ISliceManager {
|
||||
void addSliceListener(in Uri uri, String pkg, in ISliceListener listener,
|
||||
in SliceSpec[] specs);
|
||||
void removeSliceListener(in Uri uri, String pkg, in ISliceListener listener);
|
||||
void pinSlice(String pkg, in Uri uri, in SliceSpec[] specs);
|
||||
void unpinSlice(String pkg, in Uri uri);
|
||||
boolean hasSliceAccess(String pkg);
|
||||
SliceSpec[] getPinnedSpecs(in Uri uri, String pkg);
|
||||
}
|
||||
|
||||
19
core/java/android/app/slice/Slice.aidl
Normal file
19
core/java/android/app/slice/Slice.aidl
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* 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 android.app.slice;
|
||||
|
||||
parcelable Slice;
|
||||
@@ -17,8 +17,11 @@
|
||||
package android.app.slice;
|
||||
|
||||
import android.annotation.SystemService;
|
||||
import android.app.slice.ISliceListener.Stub;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.ServiceManager.ServiceNotFoundException;
|
||||
|
||||
@@ -36,4 +39,93 @@ public class SliceManager {
|
||||
mService = ISliceManager.Stub.asInterface(
|
||||
ServiceManager.getServiceOrThrow(Context.SLICE_SERVICE));
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public void addSliceListener(Uri uri, SliceListener listener, SliceSpec[] specs) {
|
||||
try {
|
||||
mService.addSliceListener(uri, mContext.getPackageName(), listener.mStub, specs);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public void removeSliceListener(Uri uri, SliceListener listener) {
|
||||
try {
|
||||
mService.removeSliceListener(uri, mContext.getPackageName(), listener.mStub);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public void pinSlice(Uri uri, SliceSpec[] specs) {
|
||||
try {
|
||||
mService.pinSlice(mContext.getPackageName(), uri, specs);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public void unpinSlice(Uri uri) {
|
||||
try {
|
||||
mService.unpinSlice(mContext.getPackageName(), uri);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public boolean hasSliceAccess() {
|
||||
try {
|
||||
return mService.hasSliceAccess(mContext.getPackageName());
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public SliceSpec[] getPinnedSpecs(Uri uri) {
|
||||
try {
|
||||
return mService.getPinnedSpecs(uri, mContext.getPackageName());
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public abstract static class SliceListener {
|
||||
private final Handler mHandler;
|
||||
|
||||
/**
|
||||
*/
|
||||
public SliceListener() {
|
||||
this(Handler.getMain());
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public SliceListener(Handler h) {
|
||||
mHandler = h;
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public abstract void onSliceUpdated(Slice s);
|
||||
|
||||
private final ISliceListener.Stub mStub = new Stub() {
|
||||
@Override
|
||||
public void onSliceUpdated(Slice s) throws RemoteException {
|
||||
mHandler.post(() -> SliceListener.this.onSliceUpdated(s));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +102,14 @@ public abstract class SliceProvider extends ContentProvider {
|
||||
* @hide
|
||||
*/
|
||||
public static final String METHOD_MAP_INTENT = "map_slice";
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public static final String METHOD_PIN = "pin";
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public static final String METHOD_UNPIN = "unpin";
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@@ -142,6 +150,18 @@ public abstract class SliceProvider extends ContentProvider {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public void onSlicePinned(Uri sliceUri) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public void onSliceUnpinned(Uri sliceUri) {
|
||||
}
|
||||
|
||||
/**
|
||||
* This method must be overridden if an {@link IntentFilter} is specified on the SliceProvider.
|
||||
* In that case, this method can be called and is expected to return a non-null Uri representing
|
||||
@@ -221,6 +241,7 @@ public abstract class SliceProvider extends ContentProvider {
|
||||
getContext().enforceCallingPermission(permission.BIND_SLICE,
|
||||
"Slice binding requires the permission BIND_SLICE");
|
||||
Intent intent = extras.getParcelable(EXTRA_INTENT);
|
||||
if (intent == null) return null;
|
||||
Uri uri = onMapIntentToUri(intent);
|
||||
List<SliceSpec> supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS);
|
||||
Bundle b = new Bundle();
|
||||
@@ -231,10 +252,62 @@ public abstract class SliceProvider extends ContentProvider {
|
||||
b.putParcelable(EXTRA_SLICE, null);
|
||||
}
|
||||
return b;
|
||||
} else if (method.equals(METHOD_PIN)) {
|
||||
Uri uri = extras.getParcelable(EXTRA_BIND_URI);
|
||||
if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.myUid())) {
|
||||
getContext().enforceUriPermission(uri, permission.BIND_SLICE,
|
||||
permission.BIND_SLICE, Binder.getCallingPid(), Binder.getCallingUid(),
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
|
||||
"Slice binding requires the permission BIND_SLICE");
|
||||
}
|
||||
handlePinSlice(uri);
|
||||
} else if (method.equals(METHOD_UNPIN)) {
|
||||
Uri uri = extras.getParcelable(EXTRA_BIND_URI);
|
||||
if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.myUid())) {
|
||||
getContext().enforceUriPermission(uri, permission.BIND_SLICE,
|
||||
permission.BIND_SLICE, Binder.getCallingPid(), Binder.getCallingUid(),
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
|
||||
"Slice binding requires the permission BIND_SLICE");
|
||||
}
|
||||
handleUnpinSlice(uri);
|
||||
}
|
||||
return super.call(method, arg, extras);
|
||||
}
|
||||
|
||||
private void handlePinSlice(Uri sliceUri) {
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
onSlicePinned(sliceUri);
|
||||
} else {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
Handler.getMain().post(() -> {
|
||||
onSlicePinned(sliceUri);
|
||||
latch.countDown();
|
||||
});
|
||||
try {
|
||||
latch.await();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleUnpinSlice(Uri sliceUri) {
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
onSliceUnpinned(sliceUri);
|
||||
} else {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
Handler.getMain().post(() -> {
|
||||
onSliceUnpinned(sliceUri);
|
||||
latch.countDown();
|
||||
});
|
||||
try {
|
||||
latch.await();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Slice handleBindSlice(Uri sliceUri, List<SliceSpec> supportedSpecs) {
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
return onBindSliceStrict(sliceUri, supportedSpecs);
|
||||
|
||||
19
core/java/android/app/slice/SliceSpec.aidl
Normal file
19
core/java/android/app/slice/SliceSpec.aidl
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* 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 android.app.slice;
|
||||
|
||||
parcelable SliceSpec;
|
||||
@@ -103,6 +103,11 @@ public final class SliceSpec implements Parcelable {
|
||||
return mType.equals(other.mType) && mRevision == other.mRevision;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("SliceSpec{%s,%d}", mType, mRevision);
|
||||
}
|
||||
|
||||
public static final Creator<SliceSpec> CREATOR = new Creator<SliceSpec>() {
|
||||
@Override
|
||||
public SliceSpec createFromParcel(Parcel source) {
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* 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.server.slice;
|
||||
|
||||
import android.app.slice.ISliceListener;
|
||||
import android.app.slice.Slice;
|
||||
import android.app.slice.SliceProvider;
|
||||
import android.app.slice.SliceSpec;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Manages the state of a pinned slice.
|
||||
*/
|
||||
public class PinnedSliceState {
|
||||
|
||||
private static final long SLICE_TIMEOUT = 5000;
|
||||
private static final String TAG = "PinnedSliceState";
|
||||
|
||||
private final Object mLock;
|
||||
|
||||
private final SliceManagerService mService;
|
||||
private final Uri mUri;
|
||||
@GuardedBy("mLock")
|
||||
private final ArraySet<String> mPinnedPkgs = new ArraySet<>();
|
||||
@GuardedBy("mLock")
|
||||
private final ArraySet<ISliceListener> mListeners = new ArraySet<>();
|
||||
@GuardedBy("mLock")
|
||||
private SliceSpec[] mSupportedSpecs = null;
|
||||
|
||||
public PinnedSliceState(SliceManagerService service, Uri uri) {
|
||||
mService = service;
|
||||
mUri = uri;
|
||||
mService.getHandler().post(this::handleSendPinned);
|
||||
mLock = mService.getLock();
|
||||
}
|
||||
|
||||
public SliceSpec[] getSpecs() {
|
||||
return mSupportedSpecs;
|
||||
}
|
||||
|
||||
public void mergeSpecs(SliceSpec[] supportedSpecs) {
|
||||
synchronized (mLock) {
|
||||
if (mSupportedSpecs == null) {
|
||||
mSupportedSpecs = supportedSpecs;
|
||||
} else {
|
||||
List<SliceSpec> specs = Arrays.asList(mSupportedSpecs);
|
||||
mSupportedSpecs = specs.stream().map(s -> {
|
||||
SliceSpec other = findSpec(supportedSpecs, s.getType());
|
||||
if (other == null) return null;
|
||||
if (other.getRevision() < s.getRevision()) {
|
||||
return other;
|
||||
}
|
||||
return s;
|
||||
}).filter(s -> s != null).toArray(SliceSpec[]::new);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private SliceSpec findSpec(SliceSpec[] specs, String type) {
|
||||
for (SliceSpec spec : specs) {
|
||||
if (Objects.equals(spec.getType(), type)) {
|
||||
return spec;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Uri getUri() {
|
||||
return mUri;
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
mService.getHandler().post(this::handleSendUnpinned);
|
||||
}
|
||||
|
||||
public void onChange() {
|
||||
mService.getHandler().post(this::handleBind);
|
||||
}
|
||||
|
||||
public void addSliceListener(ISliceListener listener, SliceSpec[] specs) {
|
||||
synchronized (mLock) {
|
||||
if (mListeners.add(listener) && mListeners.size() == 1) {
|
||||
mService.listen(mUri);
|
||||
}
|
||||
mergeSpecs(specs);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean removeSliceListener(ISliceListener listener) {
|
||||
synchronized (mLock) {
|
||||
if (mListeners.remove(listener) && mListeners.size() == 0) {
|
||||
mService.unlisten(mUri);
|
||||
}
|
||||
}
|
||||
return !isPinned();
|
||||
}
|
||||
|
||||
public void pin(String pkg, SliceSpec[] specs) {
|
||||
synchronized (mLock) {
|
||||
mPinnedPkgs.add(pkg);
|
||||
mergeSpecs(specs);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean unpin(String pkg) {
|
||||
synchronized (mLock) {
|
||||
mPinnedPkgs.remove(pkg);
|
||||
}
|
||||
return !isPinned();
|
||||
}
|
||||
|
||||
public boolean isListening() {
|
||||
synchronized (mLock) {
|
||||
return !mListeners.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public boolean isPinned() {
|
||||
synchronized (mLock) {
|
||||
return !mPinnedPkgs.isEmpty() || !mListeners.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
ContentProviderClient getClient() {
|
||||
ContentProviderClient client =
|
||||
mService.getContext().getContentResolver().acquireContentProviderClient(mUri);
|
||||
client.setDetectNotResponding(SLICE_TIMEOUT);
|
||||
return client;
|
||||
}
|
||||
|
||||
private void handleBind() {
|
||||
Slice s;
|
||||
try (ContentProviderClient client = getClient()) {
|
||||
Bundle extras = new Bundle();
|
||||
extras.putParcelable(SliceProvider.EXTRA_BIND_URI, mUri);
|
||||
extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS,
|
||||
new ArrayList<>(Arrays.asList(mSupportedSpecs)));
|
||||
final Bundle res;
|
||||
try {
|
||||
res = client.call(SliceProvider.METHOD_SLICE, null, extras);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Unable to bind slice " + mUri, e);
|
||||
return;
|
||||
}
|
||||
if (res == null) return;
|
||||
Bundle.setDefusable(res, true);
|
||||
s = res.getParcelable(SliceProvider.EXTRA_SLICE);
|
||||
}
|
||||
synchronized (mLock) {
|
||||
mListeners.removeIf(l -> {
|
||||
try {
|
||||
l.onSliceUpdated(s);
|
||||
return false;
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Unable to notify slice " + mUri, e);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
if (!isPinned()) {
|
||||
// All the listeners died, remove from pinned state.
|
||||
mService.removePinnedSlice(mUri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSendPinned() {
|
||||
try (ContentProviderClient client = getClient()) {
|
||||
Bundle b = new Bundle();
|
||||
b.putParcelable(SliceProvider.EXTRA_BIND_URI, mUri);
|
||||
try {
|
||||
client.call(SliceProvider.METHOD_PIN, null, b);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Unable to contact " + mUri, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSendUnpinned() {
|
||||
try (ContentProviderClient client = getClient()) {
|
||||
Bundle b = new Bundle();
|
||||
b.putParcelable(SliceProvider.EXTRA_BIND_URI, mUri);
|
||||
try {
|
||||
client.call(SliceProvider.METHOD_UNPIN, null, b);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Unable to contact " + mUri, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,21 +16,307 @@
|
||||
|
||||
package com.android.server.slice;
|
||||
|
||||
import android.app.slice.ISliceManager;
|
||||
import android.content.Context;
|
||||
import static android.content.ContentProvider.getUserIdFromUri;
|
||||
import static android.content.ContentProvider.maybeAddUserId;
|
||||
|
||||
import android.Manifest.permission;
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.slice.ISliceListener;
|
||||
import android.app.slice.ISliceManager;
|
||||
import android.app.slice.SliceSpec;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.database.ContentObserver;
|
||||
import android.net.Uri;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.app.AssistUtils;
|
||||
import com.android.internal.util.Preconditions;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.ServiceThread;
|
||||
import com.android.server.SystemService;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class SliceManagerService extends ISliceManager.Stub {
|
||||
|
||||
public SliceManagerService(Context context) {
|
||||
private static final String TAG = "SliceManagerService";
|
||||
private final Object mLock = new Object();
|
||||
|
||||
private final Context mContext;
|
||||
private final PackageManagerInternal mPackageManagerInternal;
|
||||
private final AppOpsManager mAppOps;
|
||||
private final AssistUtils mAssistUtils;
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private final ArrayMap<Uri, PinnedSliceState> mPinnedSlicesByUri = new ArrayMap<>();
|
||||
private final Handler mHandler;
|
||||
private final ContentObserver mObserver;
|
||||
|
||||
public SliceManagerService(Context context) {
|
||||
this(context, createHandler().getLooper());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
SliceManagerService(Context context, Looper looper) {
|
||||
mContext = context;
|
||||
mPackageManagerInternal = Preconditions.checkNotNull(
|
||||
LocalServices.getService(PackageManagerInternal.class));
|
||||
mAppOps = context.getSystemService(AppOpsManager.class);
|
||||
mAssistUtils = new AssistUtils(context);
|
||||
mHandler = new Handler(looper);
|
||||
|
||||
mObserver = new ContentObserver(mHandler) {
|
||||
@Override
|
||||
public void onChange(boolean selfChange, Uri uri) {
|
||||
try {
|
||||
getPinnedSlice(uri).onChange();
|
||||
} catch (IllegalStateException e) {
|
||||
Log.e(TAG, "Received change for unpinned slice " + uri, e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// ----- Lifecycle stuff -----
|
||||
private void systemReady() {
|
||||
}
|
||||
|
||||
private void onUnlockUser(int userHandle) {
|
||||
private void onUnlockUser(int userId) {
|
||||
}
|
||||
|
||||
private void onStopUser(int userId) {
|
||||
synchronized (mLock) {
|
||||
mPinnedSlicesByUri.values().removeIf(s -> getUserIdFromUri(s.getUri()) == userId);
|
||||
}
|
||||
}
|
||||
|
||||
/// ----- ISliceManager stuff -----
|
||||
@Override
|
||||
public void addSliceListener(Uri uri, String pkg, ISliceListener listener, SliceSpec[] specs)
|
||||
throws RemoteException {
|
||||
verifyCaller(pkg);
|
||||
uri = maybeAddUserId(uri, Binder.getCallingUserHandle().getIdentifier());
|
||||
enforceAccess(pkg, uri);
|
||||
getOrCreatePinnedSlice(uri).addSliceListener(listener, specs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSliceListener(Uri uri, String pkg, ISliceListener listener)
|
||||
throws RemoteException {
|
||||
verifyCaller(pkg);
|
||||
uri = maybeAddUserId(uri, Binder.getCallingUserHandle().getIdentifier());
|
||||
enforceAccess(pkg, uri);
|
||||
if (getPinnedSlice(uri).removeSliceListener(listener)) {
|
||||
removePinnedSlice(uri);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pinSlice(String pkg, Uri uri, SliceSpec[] specs) throws RemoteException {
|
||||
verifyCaller(pkg);
|
||||
enforceFullAccess(pkg, "pinSlice", uri);
|
||||
uri = maybeAddUserId(uri, Binder.getCallingUserHandle().getIdentifier());
|
||||
getOrCreatePinnedSlice(uri).pin(pkg, specs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unpinSlice(String pkg, Uri uri) throws RemoteException {
|
||||
verifyCaller(pkg);
|
||||
enforceFullAccess(pkg, "unpinSlice", uri);
|
||||
uri = maybeAddUserId(uri, Binder.getCallingUserHandle().getIdentifier());
|
||||
if (getPinnedSlice(uri).unpin(pkg)) {
|
||||
removePinnedSlice(uri);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSliceAccess(String pkg) throws RemoteException {
|
||||
verifyCaller(pkg);
|
||||
return hasFullSliceAccess(pkg, Binder.getCallingUserHandle().getIdentifier());
|
||||
}
|
||||
|
||||
@Override
|
||||
public SliceSpec[] getPinnedSpecs(Uri uri, String pkg) throws RemoteException {
|
||||
verifyCaller(pkg);
|
||||
enforceAccess(pkg, uri);
|
||||
return getPinnedSlice(uri).getSpecs();
|
||||
}
|
||||
|
||||
/// ----- internal code -----
|
||||
void removePinnedSlice(Uri uri) {
|
||||
synchronized (mLock) {
|
||||
mPinnedSlicesByUri.remove(uri).destroy();
|
||||
}
|
||||
}
|
||||
|
||||
private PinnedSliceState getPinnedSlice(Uri uri) {
|
||||
synchronized (mLock) {
|
||||
PinnedSliceState manager = mPinnedSlicesByUri.get(uri);
|
||||
if (manager == null) {
|
||||
throw new IllegalStateException(String.format("Slice %s not pinned",
|
||||
uri.toString()));
|
||||
}
|
||||
return manager;
|
||||
}
|
||||
}
|
||||
|
||||
private PinnedSliceState getOrCreatePinnedSlice(Uri uri) {
|
||||
synchronized (mLock) {
|
||||
PinnedSliceState manager = mPinnedSlicesByUri.get(uri);
|
||||
if (manager == null) {
|
||||
manager = createPinnedSlice(uri);
|
||||
mPinnedSlicesByUri.put(uri, manager);
|
||||
}
|
||||
return manager;
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
PinnedSliceState createPinnedSlice(Uri uri) {
|
||||
return new PinnedSliceState(this, uri);
|
||||
}
|
||||
|
||||
public Object getLock() {
|
||||
return mLock;
|
||||
}
|
||||
|
||||
public Context getContext() {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
public Handler getHandler() {
|
||||
return mHandler;
|
||||
}
|
||||
|
||||
private void enforceAccess(String pkg, Uri uri) {
|
||||
getContext().enforceUriPermission(uri, permission.BIND_SLICE,
|
||||
permission.BIND_SLICE, Binder.getCallingPid(), Binder.getCallingUid(),
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
|
||||
"Slice binding requires the permission BIND_SLICE");
|
||||
int user = Binder.getCallingUserHandle().getIdentifier();
|
||||
if (getUserIdFromUri(uri, user) != user) {
|
||||
getContext().enforceCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS_FULL,
|
||||
"Slice interaction across users requires INTERACT_ACROSS_USERS_FULL");
|
||||
}
|
||||
}
|
||||
|
||||
private void enforceFullAccess(String pkg, String name, Uri uri) {
|
||||
int user = Binder.getCallingUserHandle().getIdentifier();
|
||||
if (!hasFullSliceAccess(pkg, user)) {
|
||||
throw new SecurityException(String.format("Call %s requires full slice access", name));
|
||||
}
|
||||
if (getUserIdFromUri(uri, user) != user) {
|
||||
getContext().enforceCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS_FULL,
|
||||
"Slice interaction across users requires INTERACT_ACROSS_USERS_FULL");
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyCaller(String pkg) {
|
||||
mAppOps.checkPackage(Binder.getCallingUid(), pkg);
|
||||
}
|
||||
|
||||
private boolean hasFullSliceAccess(String pkg, int userId) {
|
||||
return isDefaultHomeApp(pkg, userId) || isAssistant(pkg, userId)
|
||||
|| isGrantedFullAccess(pkg, userId);
|
||||
}
|
||||
|
||||
private boolean isAssistant(String pkg, int userId) {
|
||||
final ComponentName cn = mAssistUtils.getAssistComponentForUser(userId);
|
||||
if (cn == null) {
|
||||
return false;
|
||||
}
|
||||
return cn.getPackageName().equals(pkg);
|
||||
}
|
||||
|
||||
public void listen(Uri uri) {
|
||||
mContext.getContentResolver().registerContentObserver(uri, true, mObserver);
|
||||
}
|
||||
|
||||
public void unlisten(Uri uri) {
|
||||
mContext.getContentResolver().unregisterContentObserver(mObserver);
|
||||
synchronized (mLock) {
|
||||
mPinnedSlicesByUri.forEach((u, s) -> {
|
||||
if (s.isListening()) {
|
||||
listen(u);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isDefaultHomeApp(String pkg, int userId) {
|
||||
String defaultHome = getDefaultHome(userId);
|
||||
return Objects.equals(pkg, defaultHome);
|
||||
}
|
||||
|
||||
// Based on getDefaultHome in ShortcutService.
|
||||
// TODO: Unify if possible
|
||||
@VisibleForTesting
|
||||
String getDefaultHome(int userId) {
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
final List<ResolveInfo> allHomeCandidates = new ArrayList<>();
|
||||
|
||||
// Default launcher from package manager.
|
||||
final ComponentName defaultLauncher = mPackageManagerInternal
|
||||
.getHomeActivitiesAsUser(allHomeCandidates, userId);
|
||||
|
||||
ComponentName detected = null;
|
||||
if (defaultLauncher != null) {
|
||||
detected = defaultLauncher;
|
||||
}
|
||||
|
||||
if (detected == null) {
|
||||
// If we reach here, that means it's the first check since the user was created,
|
||||
// and there's already multiple launchers and there's no default set.
|
||||
// Find the system one with the highest priority.
|
||||
// (We need to check the priority too because of FallbackHome in Settings.)
|
||||
// If there's no system launcher yet, then no one can access slices, until
|
||||
// the user explicitly sets one.
|
||||
final int size = allHomeCandidates.size();
|
||||
|
||||
int lastPriority = Integer.MIN_VALUE;
|
||||
for (int i = 0; i < size; i++) {
|
||||
final ResolveInfo ri = allHomeCandidates.get(i);
|
||||
if (!ri.activityInfo.applicationInfo.isSystemApp()) {
|
||||
continue;
|
||||
}
|
||||
if (ri.priority < lastPriority) {
|
||||
continue;
|
||||
}
|
||||
detected = ri.activityInfo.getComponentName();
|
||||
lastPriority = ri.priority;
|
||||
}
|
||||
}
|
||||
return detected.getPackageName();
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isGrantedFullAccess(String pkg, int userId) {
|
||||
// TODO: This will be user granted access, if we allow this through a prompt.
|
||||
return false;
|
||||
}
|
||||
|
||||
private static ServiceThread createHandler() {
|
||||
ServiceThread handlerThread = new ServiceThread(TAG,
|
||||
Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
|
||||
handlerThread.start();
|
||||
return handlerThread;
|
||||
}
|
||||
|
||||
public static class Lifecycle extends SystemService {
|
||||
@@ -57,5 +343,10 @@ public class SliceManagerService extends ISliceManager.Stub {
|
||||
public void onUnlockUser(int userHandle) {
|
||||
mService.onUnlockUser(userHandle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopUser(int userHandle) {
|
||||
mService.onStopUser(userHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@ LOCAL_MODULE_TAGS := tests
|
||||
|
||||
# Include test java files and source from notifications package.
|
||||
LOCAL_SRC_FILES := $(call all-java-files-under, src) \
|
||||
$(call all-java-files-under, ../../core/java/com/android/server/notification)
|
||||
$(call all-java-files-under, ../../core/java/com/android/server/notification) \
|
||||
$(call all-java-files-under, ../../core/java/com/android/server/slice) \
|
||||
|
||||
LOCAL_STATIC_JAVA_LIBRARIES := \
|
||||
frameworks-base-testutils \
|
||||
|
||||
@@ -28,6 +28,9 @@
|
||||
|
||||
<application>
|
||||
<uses-library android:name="android.test.runner" />
|
||||
|
||||
<provider android:name=".DummyProvider"
|
||||
android:authorities="com.android.services.uitests" />
|
||||
</application>
|
||||
|
||||
<instrumentation
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.frameworks.tests.uiservices;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
|
||||
public class DummyProvider extends ContentProvider {
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
|
||||
String sortOrder) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType(Uri uri) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.server;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.testing.TestableContext;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
|
||||
|
||||
public class UiServiceTestCase {
|
||||
@Rule
|
||||
public final TestableContext mContext =
|
||||
new TestableContext(InstrumentationRegistry.getContext(), null);
|
||||
|
||||
protected TestableContext getContext() {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
// Share classloader to allow package access.
|
||||
System.setProperty("dexmaker.share_classloader", "true");
|
||||
}
|
||||
}
|
||||
@@ -22,13 +22,16 @@ import static junit.framework.Assert.assertTrue;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import com.android.server.UiServiceTestCase;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class AlertRateLimiterTest extends NotificationTestCase {
|
||||
public class AlertRateLimiterTest extends UiServiceTestCase {
|
||||
|
||||
private long mTestStartTime;
|
||||
private
|
||||
|
||||
@@ -27,13 +27,13 @@ import android.app.ActivityManager;
|
||||
import android.app.Notification;
|
||||
import android.app.Notification.Builder;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings.Secure;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import com.android.server.UiServiceTestCase;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -42,7 +42,7 @@ import org.mockito.MockitoAnnotations;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class BadgeExtractorTest extends NotificationTestCase {
|
||||
public class BadgeExtractorTest extends UiServiceTestCase {
|
||||
|
||||
@Mock RankingConfig mConfig;
|
||||
|
||||
|
||||
@@ -58,13 +58,13 @@ import android.provider.Settings;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.util.Slog;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
import android.view.accessibility.IAccessibilityManager;
|
||||
import android.view.accessibility.IAccessibilityManagerClient;
|
||||
|
||||
import com.android.internal.util.IntPair;
|
||||
import com.android.server.UiServiceTestCase;
|
||||
import com.android.server.lights.Light;
|
||||
|
||||
import org.junit.Before;
|
||||
@@ -74,12 +74,10 @@ import org.mockito.ArgumentMatcher;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class BuzzBeepBlinkTest extends NotificationTestCase {
|
||||
public class BuzzBeepBlinkTest extends UiServiceTestCase {
|
||||
|
||||
@Mock AudioManager mAudioManager;
|
||||
@Mock Vibrator mVibrator;
|
||||
|
||||
@@ -28,6 +28,8 @@ import android.service.notification.StatusBarNotification;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import com.android.server.UiServiceTestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@@ -37,7 +39,7 @@ import java.util.List;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class GlobalSortKeyComparatorTest extends NotificationTestCase {
|
||||
public class GlobalSortKeyComparatorTest extends UiServiceTestCase {
|
||||
|
||||
private final String PKG = "PKG";
|
||||
private final int UID = 1111111;
|
||||
|
||||
@@ -38,6 +38,8 @@ import android.service.notification.StatusBarNotification;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import com.android.server.UiServiceTestCase;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
@@ -45,7 +47,7 @@ import java.util.Map;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class GroupHelperTest extends NotificationTestCase {
|
||||
public class GroupHelperTest extends UiServiceTestCase {
|
||||
private @Mock GroupHelper.Callback mCallback;
|
||||
|
||||
private GroupHelper mGroupHelper;
|
||||
|
||||
@@ -30,7 +30,6 @@ import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
@@ -39,9 +38,11 @@ import static org.mockito.Mockito.when;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import com.android.server.UiServiceTestCase;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ImportanceExtractorTest extends NotificationTestCase {
|
||||
public class ImportanceExtractorTest extends UiServiceTestCase {
|
||||
|
||||
@Mock RankingConfig mConfig;
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ import android.util.ArrayMap;
|
||||
import android.util.Xml;
|
||||
|
||||
import com.android.internal.util.FastXmlSerializer;
|
||||
import com.android.server.UiServiceTestCase;
|
||||
|
||||
import com.google.android.collect.Lists;
|
||||
|
||||
@@ -68,7 +69,7 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ManagedServicesTest extends NotificationTestCase {
|
||||
public class ManagedServicesTest extends UiServiceTestCase {
|
||||
|
||||
@Mock
|
||||
private IPackageManager mIpm;
|
||||
|
||||
@@ -31,12 +31,14 @@ import android.service.notification.Adjustment;
|
||||
import android.service.notification.SnoozeCriterion;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
|
||||
import com.android.server.UiServiceTestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Objects;
|
||||
|
||||
public class NotificationAdjustmentExtractorTest extends NotificationTestCase {
|
||||
public class NotificationAdjustmentExtractorTest extends UiServiceTestCase {
|
||||
|
||||
@Test
|
||||
public void testExtractsAdjustment() {
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.android.server.notification;
|
||||
|
||||
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
|
||||
import static android.app.NotificationManager.IMPORTANCE_HIGH;
|
||||
import static android.app.NotificationManager.IMPORTANCE_LOW;
|
||||
|
||||
@@ -31,17 +30,17 @@ import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.os.UserHandle;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
|
||||
import com.android.server.UiServiceTestCase;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
public class NotificationChannelExtractorTest extends NotificationTestCase {
|
||||
public class NotificationChannelExtractorTest extends UiServiceTestCase {
|
||||
|
||||
@Mock RankingConfig mConfig;
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import com.android.internal.util.FastXmlSerializer;
|
||||
import com.android.server.UiServiceTestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -36,7 +37,7 @@ import java.io.ByteArrayOutputStream;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class NotificationChannelTest extends NotificationTestCase {
|
||||
public class NotificationChannelTest extends UiServiceTestCase {
|
||||
|
||||
@Test
|
||||
public void testWriteToParcel() {
|
||||
|
||||
@@ -38,6 +38,8 @@ import android.telecom.TelecomManager;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import com.android.server.UiServiceTestCase;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -50,7 +52,7 @@ import java.util.List;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class NotificationComparatorTest extends NotificationTestCase {
|
||||
public class NotificationComparatorTest extends UiServiceTestCase {
|
||||
@Mock Context mContext;
|
||||
@Mock TelecomManager mTm;
|
||||
@Mock RankingHandler handler;
|
||||
|
||||
@@ -32,9 +32,11 @@ import android.content.Intent;
|
||||
import android.os.UserHandle;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
|
||||
import com.android.server.UiServiceTestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class NotificationIntrusivenessExtractorTest extends NotificationTestCase {
|
||||
public class NotificationIntrusivenessExtractorTest extends UiServiceTestCase {
|
||||
|
||||
@Test
|
||||
public void testNonIntrusive() {
|
||||
|
||||
@@ -37,6 +37,8 @@ import android.service.notification.SnoozeCriterion;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import com.android.server.UiServiceTestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@@ -45,7 +47,7 @@ import java.util.List;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class NotificationListenerServiceTest extends NotificationTestCase {
|
||||
public class NotificationListenerServiceTest extends UiServiceTestCase {
|
||||
|
||||
private String[] mKeys = new String[] { "key", "key1", "key2", "key3"};
|
||||
|
||||
|
||||
@@ -83,6 +83,7 @@ import android.util.ArrayMap;
|
||||
import android.util.AtomicFile;
|
||||
|
||||
import com.android.internal.statusbar.NotificationVisibility;
|
||||
import com.android.server.UiServiceTestCase;
|
||||
import com.android.server.lights.Light;
|
||||
import com.android.server.lights.LightsManager;
|
||||
import com.android.server.notification.NotificationManagerService.NotificationAssistants;
|
||||
@@ -110,7 +111,7 @@ import java.util.Map;
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@RunWithLooper
|
||||
public class NotificationManagerServiceTest extends NotificationTestCase {
|
||||
public class NotificationManagerServiceTest extends UiServiceTestCase {
|
||||
private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId";
|
||||
private final int mUid = Binder.getCallingUid();
|
||||
private NotificationManagerService mService;
|
||||
|
||||
@@ -53,6 +53,7 @@ import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
|
||||
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
|
||||
import com.android.server.UiServiceTestCase;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -65,7 +66,7 @@ import java.util.Objects;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class NotificationRecordTest extends NotificationTestCase {
|
||||
public class NotificationRecordTest extends UiServiceTestCase {
|
||||
|
||||
private final Context mMockContext = Mockito.mock(Context.class);
|
||||
@Mock PackageManager mPm;
|
||||
|
||||
@@ -11,12 +11,14 @@ import android.service.notification.NotificationStats;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import com.android.server.UiServiceTestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class NotificationStatsTest extends NotificationTestCase {
|
||||
public class NotificationStatsTest extends UiServiceTestCase {
|
||||
|
||||
@Test
|
||||
public void testConstructor() {
|
||||
|
||||
@@ -33,6 +33,8 @@ import android.os.Build;
|
||||
import android.support.test.filters.SmallTest;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.server.UiServiceTestCase;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -41,7 +43,7 @@ import org.mockito.MockitoAnnotations;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class NotificationTest extends NotificationTestCase {
|
||||
public class NotificationTest extends UiServiceTestCase {
|
||||
|
||||
@Mock
|
||||
ActivityManager mAm;
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* 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.server.notification;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.testing.TestableContext;
|
||||
|
||||
import org.junit.Rule;
|
||||
|
||||
|
||||
public class NotificationTestCase {
|
||||
@Rule
|
||||
public final TestableContext mContext =
|
||||
new TestableContext(InstrumentationRegistry.getContext(), null);
|
||||
|
||||
protected TestableContext getContext() {
|
||||
return mContext;
|
||||
}
|
||||
}
|
||||
@@ -65,6 +65,7 @@ import android.util.ArrayMap;
|
||||
import android.util.Xml;
|
||||
|
||||
import com.android.internal.util.FastXmlSerializer;
|
||||
import com.android.server.UiServiceTestCase;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
@@ -90,7 +91,7 @@ import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class RankingHelperTest extends NotificationTestCase {
|
||||
public class RankingHelperTest extends UiServiceTestCase {
|
||||
private static final String PKG = "com.android.server.notification";
|
||||
private static final int UID = 0;
|
||||
private static final UserHandle USER = UserHandle.of(0);
|
||||
|
||||
@@ -24,9 +24,11 @@ import org.junit.runner.RunWith;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
import com.android.server.UiServiceTestCase;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class RateEstimatorTest extends NotificationTestCase {
|
||||
public class RateEstimatorTest extends UiServiceTestCase {
|
||||
private long mTestStartTime;
|
||||
private RateEstimator mEstimator;
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ import android.support.test.filters.FlakyTest;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import com.android.server.UiServiceTestCase;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -36,7 +38,7 @@ import java.util.GregorianCalendar;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ScheduleCalendarTest extends NotificationTestCase {
|
||||
public class ScheduleCalendarTest extends UiServiceTestCase {
|
||||
|
||||
private ScheduleCalendar mScheduleCalendar;
|
||||
private ZenModeConfig.ScheduleInfo mScheduleInfo;
|
||||
|
||||
@@ -13,6 +13,8 @@ import android.service.notification.ZenModeConfig;
|
||||
import android.support.test.filters.SmallTest;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.server.UiServiceTestCase;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -23,7 +25,7 @@ import java.util.GregorianCalendar;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class ScheduleConditionProviderTest extends NotificationTestCase {
|
||||
public class ScheduleConditionProviderTest extends UiServiceTestCase {
|
||||
|
||||
ScheduleConditionProvider mService;
|
||||
|
||||
|
||||
@@ -32,7 +32,6 @@ import android.os.UserHandle;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.util.Slog;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
@@ -46,10 +45,12 @@ import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.android.server.UiServiceTestCase;
|
||||
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class SnoozeHelperTest extends NotificationTestCase {
|
||||
public class SnoozeHelperTest extends UiServiceTestCase {
|
||||
private static final String TEST_CHANNEL_ID = "test_channel_id";
|
||||
|
||||
@Mock SnoozeHelper.Callback mCallback;
|
||||
|
||||
@@ -30,9 +30,11 @@ import org.junit.runner.RunWith;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import com.android.server.UiServiceTestCase;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ValidateNotificationPeopleTest extends NotificationTestCase {
|
||||
public class ValidateNotificationPeopleTest extends UiServiceTestCase {
|
||||
|
||||
@Test
|
||||
public void testNoExtra() throws Exception {
|
||||
|
||||
@@ -32,6 +32,8 @@ import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
|
||||
import com.android.server.UiServiceTestCase;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -41,7 +43,7 @@ import org.mockito.MockitoAnnotations;
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@TestableLooper.RunWithLooper
|
||||
public class ZenModeHelperTest extends NotificationTestCase {
|
||||
public class ZenModeHelperTest extends UiServiceTestCase {
|
||||
|
||||
@Mock ConditionProviders mConditionProviders;
|
||||
private TestableLooper mTestableLooper;
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
package com.android.server.slice;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.clearInvocations;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.slice.ISliceListener;
|
||||
import android.app.slice.Slice;
|
||||
import android.app.slice.SliceProvider;
|
||||
import android.app.slice.SliceSpec;
|
||||
import android.content.ContentProvider;
|
||||
import android.content.IContentProvider;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.RemoteException;
|
||||
import android.support.test.filters.SmallTest;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
import android.testing.TestableLooper.RunWithLooper;
|
||||
|
||||
import com.android.server.UiServiceTestCase;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@RunWithLooper
|
||||
public class PinnedSliceStateTest extends UiServiceTestCase {
|
||||
|
||||
private static final String AUTH = "my.authority";
|
||||
private static final Uri TEST_URI = Uri.parse("content://" + AUTH + "/path");
|
||||
|
||||
private static final SliceSpec[] FIRST_SPECS = new SliceSpec[]{
|
||||
new SliceSpec("spec1", 3),
|
||||
new SliceSpec("spec2", 3),
|
||||
new SliceSpec("spec3", 2),
|
||||
new SliceSpec("spec4", 1),
|
||||
};
|
||||
|
||||
private static final SliceSpec[] SECOND_SPECS = new SliceSpec[]{
|
||||
new SliceSpec("spec2", 1),
|
||||
new SliceSpec("spec3", 2),
|
||||
new SliceSpec("spec4", 3),
|
||||
new SliceSpec("spec5", 4),
|
||||
};
|
||||
|
||||
private SliceManagerService mSliceService;
|
||||
private PinnedSliceState mPinnedSliceManager;
|
||||
private IContentProvider mIContentProvider;
|
||||
private ContentProvider mContentProvider;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
mSliceService = mock(SliceManagerService.class);
|
||||
when(mSliceService.getLock()).thenReturn(new Object());
|
||||
when(mSliceService.getContext()).thenReturn(mContext);
|
||||
when(mSliceService.getHandler()).thenReturn(new Handler(TestableLooper.get(this).getLooper()));
|
||||
mContentProvider = mock(ContentProvider.class);
|
||||
mIContentProvider = mock(IContentProvider.class);
|
||||
when(mContentProvider.getIContentProvider()).thenReturn(mIContentProvider);
|
||||
mContext.getContentResolver().addProvider(AUTH, mContentProvider);
|
||||
mPinnedSliceManager = new PinnedSliceState(mSliceService, TEST_URI);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMergeSpecs() {
|
||||
// No annotations to start.
|
||||
assertNull(mPinnedSliceManager.getSpecs());
|
||||
|
||||
mPinnedSliceManager.mergeSpecs(FIRST_SPECS);
|
||||
assertArrayEquals(FIRST_SPECS, mPinnedSliceManager.getSpecs());
|
||||
|
||||
mPinnedSliceManager.mergeSpecs(SECOND_SPECS);
|
||||
assertArrayEquals(new SliceSpec[]{
|
||||
// spec1 is gone because it's not in the second set.
|
||||
new SliceSpec("spec2", 1), // spec2 is 1 because it's smaller in the second set.
|
||||
new SliceSpec("spec3", 2), // spec3 is the same in both sets
|
||||
new SliceSpec("spec4", 1), // spec4 is 1 because it's smaller in the first set.
|
||||
// spec5 is gone because it's not in the first set.
|
||||
}, mPinnedSliceManager.getSpecs());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendPinnedOnCreate() throws RemoteException {
|
||||
// When created, a pinned message should be sent.
|
||||
TestableLooper.get(this).processAllMessages();
|
||||
|
||||
verify(mIContentProvider).call(anyString(), eq(SliceProvider.METHOD_PIN), eq(null),
|
||||
argThat(b -> {
|
||||
assertEquals(TEST_URI, b.getParcelable(SliceProvider.EXTRA_BIND_URI));
|
||||
return true;
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendUnpinnedOnDestroy() throws RemoteException {
|
||||
TestableLooper.get(this).processAllMessages();
|
||||
clearInvocations(mIContentProvider);
|
||||
|
||||
mPinnedSliceManager.destroy();
|
||||
TestableLooper.get(this).processAllMessages();
|
||||
|
||||
verify(mIContentProvider).call(anyString(), eq(SliceProvider.METHOD_UNPIN), eq(null),
|
||||
argThat(b -> {
|
||||
assertEquals(TEST_URI, b.getParcelable(SliceProvider.EXTRA_BIND_URI));
|
||||
return true;
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPkgPin() {
|
||||
assertFalse(mPinnedSliceManager.isPinned());
|
||||
|
||||
mPinnedSliceManager.pin("pkg", FIRST_SPECS);
|
||||
assertTrue(mPinnedSliceManager.isPinned());
|
||||
|
||||
assertTrue(mPinnedSliceManager.unpin("pkg"));
|
||||
assertFalse(mPinnedSliceManager.isPinned());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiPkgPin() {
|
||||
assertFalse(mPinnedSliceManager.isPinned());
|
||||
|
||||
mPinnedSliceManager.pin("pkg", FIRST_SPECS);
|
||||
assertTrue(mPinnedSliceManager.isPinned());
|
||||
mPinnedSliceManager.pin("pkg2", FIRST_SPECS);
|
||||
|
||||
assertFalse(mPinnedSliceManager.unpin("pkg"));
|
||||
assertTrue(mPinnedSliceManager.unpin("pkg2"));
|
||||
assertFalse(mPinnedSliceManager.isPinned());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListenerPin() {
|
||||
ISliceListener listener = mock(ISliceListener.class);
|
||||
assertFalse(mPinnedSliceManager.isPinned());
|
||||
|
||||
mPinnedSliceManager.addSliceListener(listener, FIRST_SPECS);
|
||||
assertTrue(mPinnedSliceManager.isPinned());
|
||||
|
||||
assertTrue(mPinnedSliceManager.removeSliceListener(listener));
|
||||
assertFalse(mPinnedSliceManager.isPinned());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiListenerPin() {
|
||||
ISliceListener listener = mock(ISliceListener.class);
|
||||
ISliceListener listener2 = mock(ISliceListener.class);
|
||||
assertFalse(mPinnedSliceManager.isPinned());
|
||||
|
||||
mPinnedSliceManager.addSliceListener(listener, FIRST_SPECS);
|
||||
assertTrue(mPinnedSliceManager.isPinned());
|
||||
mPinnedSliceManager.addSliceListener(listener2, FIRST_SPECS);
|
||||
|
||||
assertFalse(mPinnedSliceManager.removeSliceListener(listener));
|
||||
assertTrue(mPinnedSliceManager.removeSliceListener(listener2));
|
||||
assertFalse(mPinnedSliceManager.isPinned());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPkgListenerPin() {
|
||||
ISliceListener listener = mock(ISliceListener.class);
|
||||
assertFalse(mPinnedSliceManager.isPinned());
|
||||
|
||||
mPinnedSliceManager.addSliceListener(listener, FIRST_SPECS);
|
||||
assertTrue(mPinnedSliceManager.isPinned());
|
||||
mPinnedSliceManager.pin("pkg", FIRST_SPECS);
|
||||
|
||||
assertFalse(mPinnedSliceManager.removeSliceListener(listener));
|
||||
assertTrue(mPinnedSliceManager.unpin("pkg"));
|
||||
assertFalse(mPinnedSliceManager.isPinned());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBind() throws RemoteException {
|
||||
TestableLooper.get(this).processAllMessages();
|
||||
clearInvocations(mIContentProvider);
|
||||
|
||||
ISliceListener listener = mock(ISliceListener.class);
|
||||
Slice s = new Slice.Builder(TEST_URI).build();
|
||||
Bundle b = new Bundle();
|
||||
b.putParcelable(SliceProvider.EXTRA_SLICE, s);
|
||||
when(mIContentProvider.call(anyString(), eq(SliceProvider.METHOD_SLICE), eq(null),
|
||||
any())).thenReturn(b);
|
||||
|
||||
assertFalse(mPinnedSliceManager.isPinned());
|
||||
|
||||
mPinnedSliceManager.addSliceListener(listener, FIRST_SPECS);
|
||||
|
||||
mPinnedSliceManager.onChange();
|
||||
TestableLooper.get(this).processAllMessages();
|
||||
|
||||
verify(mIContentProvider).call(anyString(), eq(SliceProvider.METHOD_SLICE), eq(null),
|
||||
argThat(bundle -> {
|
||||
assertEquals(TEST_URI, bundle.getParcelable(SliceProvider.EXTRA_BIND_URI));
|
||||
return true;
|
||||
}));
|
||||
verify(listener).onSliceUpdated(eq(s));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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.server.slice;
|
||||
|
||||
import static android.content.ContentProvider.maybeAddUserId;
|
||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.slice.ISliceListener;
|
||||
import android.app.slice.SliceSpec;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
import android.net.Uri;
|
||||
import android.os.RemoteException;
|
||||
import android.support.test.filters.SmallTest;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
import android.testing.TestableLooper.RunWithLooper;
|
||||
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.UiServiceTestCase;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@RunWithLooper
|
||||
public class SliceManagerServiceTest extends UiServiceTestCase {
|
||||
|
||||
private static final String AUTH = "com.android.services.uitests";
|
||||
private static final Uri TEST_URI = maybeAddUserId(Uri.parse("content://" + AUTH + "/path"), 0);
|
||||
|
||||
private static final SliceSpec[] EMPTY_SPECS = new SliceSpec[]{
|
||||
};
|
||||
|
||||
private SliceManagerService mService;
|
||||
private PinnedSliceState mCreatedSliceState;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
LocalServices.addService(PackageManagerInternal.class, mock(PackageManagerInternal.class));
|
||||
mContext.addMockSystemService(AppOpsManager.class, mock(AppOpsManager.class));
|
||||
mContext.getTestablePermissions().setPermission(TEST_URI, PERMISSION_GRANTED);
|
||||
|
||||
mService = spy(new SliceManagerService(mContext, TestableLooper.get(this).getLooper()));
|
||||
mCreatedSliceState = mock(PinnedSliceState.class);
|
||||
doReturn(mCreatedSliceState).when(mService).createPinnedSlice(eq(TEST_URI));
|
||||
}
|
||||
|
||||
@After
|
||||
public void teardown() {
|
||||
LocalServices.removeServiceForTest(PackageManagerInternal.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddListenerCreatesPinned() throws RemoteException {
|
||||
mService.addSliceListener(TEST_URI, "pkg", mock(ISliceListener.class), EMPTY_SPECS);
|
||||
verify(mService, times(1)).createPinnedSlice(eq(TEST_URI));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddListenerCreatesOnePinned() throws RemoteException {
|
||||
mService.addSliceListener(TEST_URI, "pkg", mock(ISliceListener.class), EMPTY_SPECS);
|
||||
mService.addSliceListener(TEST_URI, "pkg", mock(ISliceListener.class), EMPTY_SPECS);
|
||||
verify(mService, times(1)).createPinnedSlice(eq(TEST_URI));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveListenerDestroysPinned() throws RemoteException {
|
||||
ISliceListener listener = mock(ISliceListener.class);
|
||||
mService.addSliceListener(TEST_URI, "pkg", listener, EMPTY_SPECS);
|
||||
|
||||
when(mCreatedSliceState.removeSliceListener(eq(listener))).thenReturn(false);
|
||||
mService.removeSliceListener(TEST_URI, "pkg", listener);
|
||||
verify(mCreatedSliceState, never()).destroy();
|
||||
|
||||
when(mCreatedSliceState.removeSliceListener(eq(listener))).thenReturn(true);
|
||||
mService.removeSliceListener(TEST_URI, "pkg", listener);
|
||||
verify(mCreatedSliceState).destroy();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testUnrecognizedThrows() throws RemoteException {
|
||||
mService.removeSliceListener(TEST_URI, "pkg", mock(ISliceListener.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddPinCreatesPinned() throws RemoteException {
|
||||
doReturn("pkg").when(mService).getDefaultHome(anyInt());
|
||||
|
||||
mService.pinSlice("pkg", TEST_URI, EMPTY_SPECS);
|
||||
mService.pinSlice("pkg", TEST_URI, EMPTY_SPECS);
|
||||
verify(mService, times(1)).createPinnedSlice(eq(TEST_URI));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemovePinDestroysPinned() throws RemoteException {
|
||||
doReturn("pkg").when(mService).getDefaultHome(anyInt());
|
||||
|
||||
mService.pinSlice("pkg", TEST_URI, EMPTY_SPECS);
|
||||
|
||||
when(mCreatedSliceState.unpin(eq("pkg"))).thenReturn(false);
|
||||
mService.unpinSlice("pkg", TEST_URI);
|
||||
verify(mCreatedSliceState, never()).destroy();
|
||||
|
||||
when(mCreatedSliceState.unpin(eq("pkg"))).thenReturn(true);
|
||||
mService.unpinSlice("pkg", TEST_URI);
|
||||
verify(mCreatedSliceState).destroy();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user