diff options
200 files changed, 8442 insertions, 1570 deletions
diff --git a/api/current.txt b/api/current.txt index 7f4fae542037..d70d1e79e00e 100644 --- a/api/current.txt +++ b/api/current.txt @@ -15211,6 +15211,17 @@ package android.graphics.fonts { method public android.graphics.fonts.Font.Builder setWeight(int); } + public class FontFamily { + method public android.graphics.fonts.Font getFont(int); + method public int getFontCount(); + } + + public static class FontFamily.Builder { + ctor public FontFamily.Builder(android.graphics.fonts.Font); + method public android.graphics.fonts.FontFamily.Builder addFont(android.graphics.fonts.Font); + method public android.graphics.fonts.FontFamily build(); + } + public final class FontVariationAxis { ctor public FontVariationAxis(java.lang.String, float); method public static android.graphics.fonts.FontVariationAxis[] fromFontVariationSettings(java.lang.String); diff --git a/api/test-current.txt b/api/test-current.txt index 1d08da5be957..9dc61ee4c0dd 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -314,9 +314,6 @@ package android.database.sqlite { public final class SQLiteDebug { method public static void dump(android.util.Printer, java.lang.String[]); method public static android.database.sqlite.SQLiteDebug.PagerStats getDatabaseInfo(); - field public static final boolean DEBUG_SQL_LOG; - field public static final boolean DEBUG_SQL_STATEMENTS; - field public static final boolean DEBUG_SQL_TIME; } public static class SQLiteDebug.DbStats { diff --git a/config/hiddenapi-light-greylist.txt b/config/hiddenapi-light-greylist.txt index d7f8628f5dfd..d07c50f5ddf5 100644 --- a/config/hiddenapi-light-greylist.txt +++ b/config/hiddenapi-light-greylist.txt @@ -998,204 +998,6 @@ Landroid/appwidget/AppWidgetManager;->getInstalledProviders(I)Ljava/util/List; Landroid/appwidget/AppWidgetManager;->getInstalledProvidersForProfile(ILandroid/os/UserHandle;Ljava/lang/String;)Ljava/util/List; Landroid/appwidget/AppWidgetManager;->mService:Lcom/android/internal/appwidget/IAppWidgetService; Landroid/appwidget/AppWidgetProviderInfo;->providerInfo:Landroid/content/pm/ActivityInfo; -Landroid/bluetooth/BluetoothA2dp;->ACTION_ACTIVE_DEVICE_CHANGED:Ljava/lang/String; -Landroid/bluetooth/BluetoothA2dp;->ACTION_CODEC_CONFIG_CHANGED:Ljava/lang/String; -Landroid/bluetooth/BluetoothA2dp;->close()V -Landroid/bluetooth/BluetoothA2dp;->connect(Landroid/bluetooth/BluetoothDevice;)Z -Landroid/bluetooth/BluetoothA2dp;->disableOptionalCodecs(Landroid/bluetooth/BluetoothDevice;)V -Landroid/bluetooth/BluetoothA2dp;->disconnect(Landroid/bluetooth/BluetoothDevice;)Z -Landroid/bluetooth/BluetoothA2dp;->enableOptionalCodecs(Landroid/bluetooth/BluetoothDevice;)V -Landroid/bluetooth/BluetoothA2dp;->getActiveDevice()Landroid/bluetooth/BluetoothDevice; -Landroid/bluetooth/BluetoothA2dp;->getCodecStatus(Landroid/bluetooth/BluetoothDevice;)Landroid/bluetooth/BluetoothCodecStatus; -Landroid/bluetooth/BluetoothA2dp;->getOptionalCodecsEnabled(Landroid/bluetooth/BluetoothDevice;)I -Landroid/bluetooth/BluetoothA2dp;->getPriority(Landroid/bluetooth/BluetoothDevice;)I -Landroid/bluetooth/BluetoothA2dp;->OPTIONAL_CODECS_NOT_SUPPORTED:I -Landroid/bluetooth/BluetoothA2dp;->OPTIONAL_CODECS_PREF_DISABLED:I -Landroid/bluetooth/BluetoothA2dp;->OPTIONAL_CODECS_PREF_ENABLED:I -Landroid/bluetooth/BluetoothA2dp;->OPTIONAL_CODECS_PREF_UNKNOWN:I -Landroid/bluetooth/BluetoothA2dp;->OPTIONAL_CODECS_SUPPORTED:I -Landroid/bluetooth/BluetoothA2dp;->OPTIONAL_CODECS_SUPPORT_UNKNOWN:I -Landroid/bluetooth/BluetoothA2dp;->setActiveDevice(Landroid/bluetooth/BluetoothDevice;)Z -Landroid/bluetooth/BluetoothA2dp;->setCodecConfigPreference(Landroid/bluetooth/BluetoothDevice;Landroid/bluetooth/BluetoothCodecConfig;)V -Landroid/bluetooth/BluetoothA2dp;->setOptionalCodecsEnabled(Landroid/bluetooth/BluetoothDevice;I)V -Landroid/bluetooth/BluetoothA2dp;->stateToString(I)Ljava/lang/String; -Landroid/bluetooth/BluetoothA2dp;->supportsOptionalCodecs(Landroid/bluetooth/BluetoothDevice;)I -Landroid/bluetooth/BluetoothA2dpSink;->disconnect(Landroid/bluetooth/BluetoothDevice;)Z -Landroid/bluetooth/BluetoothAdapter;->disable(Z)Z -Landroid/bluetooth/BluetoothAdapter;->factoryReset()Z -Landroid/bluetooth/BluetoothAdapter;->getBluetoothManager()Landroid/bluetooth/IBluetoothManager; -Landroid/bluetooth/BluetoothAdapter;->getBluetoothService(Landroid/bluetooth/IBluetoothManagerCallback;)Landroid/bluetooth/IBluetooth; -Landroid/bluetooth/BluetoothAdapter;->getConnectionState()I -Landroid/bluetooth/BluetoothAdapter;->getDiscoverableTimeout()I -Landroid/bluetooth/BluetoothAdapter;->getLeState()I -Landroid/bluetooth/BluetoothAdapter;->getUuids()[Landroid/os/ParcelUuid; -Landroid/bluetooth/BluetoothAdapter;->listenUsingEncryptedRfcommWithServiceRecord(Ljava/lang/String;Ljava/util/UUID;)Landroid/bluetooth/BluetoothServerSocket; -Landroid/bluetooth/BluetoothAdapter;->listenUsingRfcommOn(IZZ)Landroid/bluetooth/BluetoothServerSocket; -Landroid/bluetooth/BluetoothAdapter;->mService:Landroid/bluetooth/IBluetooth; -Landroid/bluetooth/BluetoothAdapter;->setDiscoverableTimeout(I)V -Landroid/bluetooth/BluetoothAdapter;->setScanMode(I)Z -Landroid/bluetooth/BluetoothAdapter;->setScanMode(II)Z -Landroid/bluetooth/BluetoothClass;-><init>(I)V -Landroid/bluetooth/BluetoothClass;->doesClassMatch(I)Z -Landroid/bluetooth/BluetoothClass;->PROFILE_A2DP:I -Landroid/bluetooth/BluetoothClass;->PROFILE_HEADSET:I -Landroid/bluetooth/BluetoothCodecConfig;-><init>(IIIIIJJJJ)V -Landroid/bluetooth/BluetoothCodecConfig;->BITS_PER_SAMPLE_16:I -Landroid/bluetooth/BluetoothCodecConfig;->BITS_PER_SAMPLE_24:I -Landroid/bluetooth/BluetoothCodecConfig;->BITS_PER_SAMPLE_32:I -Landroid/bluetooth/BluetoothCodecConfig;->BITS_PER_SAMPLE_NONE:I -Landroid/bluetooth/BluetoothCodecConfig;->CHANNEL_MODE_MONO:I -Landroid/bluetooth/BluetoothCodecConfig;->CHANNEL_MODE_NONE:I -Landroid/bluetooth/BluetoothCodecConfig;->CHANNEL_MODE_STEREO:I -Landroid/bluetooth/BluetoothCodecConfig;->CODEC_PRIORITY_DEFAULT:I -Landroid/bluetooth/BluetoothCodecConfig;->CODEC_PRIORITY_DISABLED:I -Landroid/bluetooth/BluetoothCodecConfig;->CODEC_PRIORITY_HIGHEST:I -Landroid/bluetooth/BluetoothCodecConfig;->getBitsPerSample()I -Landroid/bluetooth/BluetoothCodecConfig;->getChannelMode()I -Landroid/bluetooth/BluetoothCodecConfig;->getCodecPriority()I -Landroid/bluetooth/BluetoothCodecConfig;->getCodecSpecific1()J -Landroid/bluetooth/BluetoothCodecConfig;->getCodecSpecific2()J -Landroid/bluetooth/BluetoothCodecConfig;->getCodecSpecific3()J -Landroid/bluetooth/BluetoothCodecConfig;->getCodecSpecific4()J -Landroid/bluetooth/BluetoothCodecConfig;->getCodecType()I -Landroid/bluetooth/BluetoothCodecConfig;->getSampleRate()I -Landroid/bluetooth/BluetoothCodecConfig;->SAMPLE_RATE_176400:I -Landroid/bluetooth/BluetoothCodecConfig;->SAMPLE_RATE_192000:I -Landroid/bluetooth/BluetoothCodecConfig;->SAMPLE_RATE_44100:I -Landroid/bluetooth/BluetoothCodecConfig;->SAMPLE_RATE_48000:I -Landroid/bluetooth/BluetoothCodecConfig;->SAMPLE_RATE_88200:I -Landroid/bluetooth/BluetoothCodecConfig;->SAMPLE_RATE_96000:I -Landroid/bluetooth/BluetoothCodecConfig;->SAMPLE_RATE_NONE:I -Landroid/bluetooth/BluetoothCodecConfig;->setCodecPriority(I)V -Landroid/bluetooth/BluetoothCodecConfig;->SOURCE_CODEC_TYPE_AAC:I -Landroid/bluetooth/BluetoothCodecConfig;->SOURCE_CODEC_TYPE_APTX:I -Landroid/bluetooth/BluetoothCodecConfig;->SOURCE_CODEC_TYPE_APTX_HD:I -Landroid/bluetooth/BluetoothCodecConfig;->SOURCE_CODEC_TYPE_INVALID:I -Landroid/bluetooth/BluetoothCodecConfig;->SOURCE_CODEC_TYPE_LDAC:I -Landroid/bluetooth/BluetoothCodecConfig;->SOURCE_CODEC_TYPE_MAX:I -Landroid/bluetooth/BluetoothCodecConfig;->SOURCE_CODEC_TYPE_SBC:I -Landroid/bluetooth/BluetoothCodecStatus;->EXTRA_CODEC_STATUS:Ljava/lang/String; -Landroid/bluetooth/BluetoothCodecStatus;->getCodecConfig()Landroid/bluetooth/BluetoothCodecConfig; -Landroid/bluetooth/BluetoothCodecStatus;->getCodecsLocalCapabilities()[Landroid/bluetooth/BluetoothCodecConfig; -Landroid/bluetooth/BluetoothCodecStatus;->getCodecsSelectableCapabilities()[Landroid/bluetooth/BluetoothCodecConfig; -Landroid/bluetooth/BluetoothDevice;-><init>(Ljava/lang/String;)V -Landroid/bluetooth/BluetoothDevice;->ACTION_ALIAS_CHANGED:Ljava/lang/String; -Landroid/bluetooth/BluetoothDevice;->ACTION_DISAPPEARED:Ljava/lang/String; -Landroid/bluetooth/BluetoothDevice;->ACTION_PAIRING_CANCEL:Ljava/lang/String; -Landroid/bluetooth/BluetoothDevice;->ACTION_SDP_RECORD:Ljava/lang/String; -Landroid/bluetooth/BluetoothDevice;->cancelPairingUserInput()Z -Landroid/bluetooth/BluetoothDevice;->connectGatt(Landroid/content/Context;ZLandroid/bluetooth/BluetoothGattCallback;IZILandroid/os/Handler;)Landroid/bluetooth/BluetoothGatt; -Landroid/bluetooth/BluetoothDevice;->convertPinToBytes(Ljava/lang/String;)[B -Landroid/bluetooth/BluetoothDevice;->createBond(I)Z -Landroid/bluetooth/BluetoothDevice;->createInsecureRfcommSocket(I)Landroid/bluetooth/BluetoothSocket; -Landroid/bluetooth/BluetoothDevice;->createRfcommSocket(I)Landroid/bluetooth/BluetoothSocket; -Landroid/bluetooth/BluetoothDevice;->createScoSocket()Landroid/bluetooth/BluetoothSocket; -Landroid/bluetooth/BluetoothDevice;->EXTRA_REASON:Ljava/lang/String; -Landroid/bluetooth/BluetoothDevice;->EXTRA_SDP_SEARCH_STATUS:Ljava/lang/String; -Landroid/bluetooth/BluetoothDevice;->getAlias()Ljava/lang/String; -Landroid/bluetooth/BluetoothDevice;->getAliasName()Ljava/lang/String; -Landroid/bluetooth/BluetoothDevice;->getBatteryLevel()I -Landroid/bluetooth/BluetoothDevice;->getMessageAccessPermission()I -Landroid/bluetooth/BluetoothDevice;->getPhonebookAccessPermission()I -Landroid/bluetooth/BluetoothDevice;->getService()Landroid/bluetooth/IBluetooth; -Landroid/bluetooth/BluetoothDevice;->isBluetoothDock()Z -Landroid/bluetooth/BluetoothDevice;->isBondingInitiatedLocally()Z -Landroid/bluetooth/BluetoothDevice;->setAlias(Ljava/lang/String;)Z -Landroid/bluetooth/BluetoothDevice;->setMessageAccessPermission(I)Z -Landroid/bluetooth/BluetoothDevice;->setPasskey(I)Z -Landroid/bluetooth/BluetoothDevice;->setSimAccessPermission(I)Z -Landroid/bluetooth/BluetoothDevice;->UNBOND_REASON_AUTH_FAILED:I -Landroid/bluetooth/BluetoothDevice;->UNBOND_REASON_AUTH_REJECTED:I -Landroid/bluetooth/BluetoothDevice;->UNBOND_REASON_AUTH_TIMEOUT:I -Landroid/bluetooth/BluetoothDevice;->UNBOND_REASON_DISCOVERY_IN_PROGRESS:I -Landroid/bluetooth/BluetoothDevice;->UNBOND_REASON_REMOTE_AUTH_CANCELED:I -Landroid/bluetooth/BluetoothDevice;->UNBOND_REASON_REMOTE_DEVICE_DOWN:I -Landroid/bluetooth/BluetoothDevice;->UNBOND_REASON_REPEATED_ATTEMPTS:I -Landroid/bluetooth/BluetoothGatt;->connect(Ljava/lang/Boolean;Landroid/bluetooth/BluetoothGattCallback;Landroid/os/Handler;)Z -Landroid/bluetooth/BluetoothGatt;->mAuthRetryState:I -Landroid/bluetooth/BluetoothGatt;->mAutoConnect:Z -Landroid/bluetooth/BluetoothGatt;->mCallback:Landroid/bluetooth/BluetoothGattCallback; -Landroid/bluetooth/BluetoothGatt;->mClientIf:I -Landroid/bluetooth/BluetoothGatt;->mDeviceBusy:Ljava/lang/Boolean; -Landroid/bluetooth/BluetoothGatt;->mService:Landroid/bluetooth/IBluetoothGatt; -Landroid/bluetooth/BluetoothGatt;->mTransport:I -Landroid/bluetooth/BluetoothGatt;->refresh()Z -Landroid/bluetooth/BluetoothGatt;->unregisterApp()V -Landroid/bluetooth/BluetoothGattCharacteristic;->mInstance:I -Landroid/bluetooth/BluetoothGattCharacteristic;->mService:Landroid/bluetooth/BluetoothGattService; -Landroid/bluetooth/BluetoothGattCharacteristic;->setKeySize(I)V -Landroid/bluetooth/BluetoothGattCharacteristic;->setService(Landroid/bluetooth/BluetoothGattService;)V -Landroid/bluetooth/BluetoothGattDescriptor;->mCharacteristic:Landroid/bluetooth/BluetoothGattCharacteristic; -Landroid/bluetooth/BluetoothGattDescriptor;->mInstance:I -Landroid/bluetooth/BluetoothGattDescriptor;->setCharacteristic(Landroid/bluetooth/BluetoothGattCharacteristic;)V -Landroid/bluetooth/BluetoothGattService;->mDevice:Landroid/bluetooth/BluetoothDevice; -Landroid/bluetooth/BluetoothGattService;->setAdvertisePreferred(Z)V -Landroid/bluetooth/BluetoothGattService;->setInstanceId(I)V -Landroid/bluetooth/BluetoothHeadset;->ACTION_ACTIVE_DEVICE_CHANGED:Ljava/lang/String; -Landroid/bluetooth/BluetoothHeadset;->close()V -Landroid/bluetooth/BluetoothHeadset;->connectAudio()Z -Landroid/bluetooth/BluetoothHeadset;->disconnectAudio()Z -Landroid/bluetooth/BluetoothHeadset;->getActiveDevice()Landroid/bluetooth/BluetoothDevice; -Landroid/bluetooth/BluetoothHeadset;->getAudioState(Landroid/bluetooth/BluetoothDevice;)I -Landroid/bluetooth/BluetoothHeadset;->getPriority(Landroid/bluetooth/BluetoothDevice;)I -Landroid/bluetooth/BluetoothHeadset;->isEnabled()Z -Landroid/bluetooth/BluetoothHeadset;->setActiveDevice(Landroid/bluetooth/BluetoothDevice;)Z -Landroid/bluetooth/BluetoothHeadset;->startScoUsingVirtualVoiceCall()Z -Landroid/bluetooth/BluetoothHeadset;->stopScoUsingVirtualVoiceCall()Z -Landroid/bluetooth/BluetoothHeadsetClient;->acceptCall(Landroid/bluetooth/BluetoothDevice;I)Z -Landroid/bluetooth/BluetoothHeadsetClient;->connect(Landroid/bluetooth/BluetoothDevice;)Z -Landroid/bluetooth/BluetoothHeadsetClient;->disconnect(Landroid/bluetooth/BluetoothDevice;)Z -Landroid/bluetooth/BluetoothHeadsetClient;->getAudioState(Landroid/bluetooth/BluetoothDevice;)I -Landroid/bluetooth/BluetoothHeadsetClient;->rejectCall(Landroid/bluetooth/BluetoothDevice;)Z -Landroid/bluetooth/BluetoothHeadsetClientCall;->getId()I -Landroid/bluetooth/BluetoothHeadsetClientCall;->getNumber()Ljava/lang/String; -Landroid/bluetooth/BluetoothHeadsetClientCall;->getState()I -Landroid/bluetooth/BluetoothHeadsetClientCall;->isMultiParty()Z -Landroid/bluetooth/BluetoothHeadsetClientCall;->isOutgoing()Z -Landroid/bluetooth/BluetoothHearingAid;->ACTION_ACTIVE_DEVICE_CHANGED:Ljava/lang/String; -Landroid/bluetooth/BluetoothHearingAid;->getActiveDevices()Ljava/util/List; -Landroid/bluetooth/BluetoothHearingAid;->setActiveDevice(Landroid/bluetooth/BluetoothDevice;)Z -Landroid/bluetooth/BluetoothMap;->disconnect(Landroid/bluetooth/BluetoothDevice;)Z -Landroid/bluetooth/BluetoothMapClient;->sendMessage(Landroid/bluetooth/BluetoothDevice;[Landroid/net/Uri;Ljava/lang/String;Landroid/app/PendingIntent;Landroid/app/PendingIntent;)Z -Landroid/bluetooth/BluetoothPan;-><init>(Landroid/content/Context;Landroid/bluetooth/BluetoothProfile$ServiceListener;)V -Landroid/bluetooth/BluetoothPan;->close()V -Landroid/bluetooth/BluetoothPan;->connect(Landroid/bluetooth/BluetoothDevice;)Z -Landroid/bluetooth/BluetoothPan;->disconnect(Landroid/bluetooth/BluetoothDevice;)Z -Landroid/bluetooth/BluetoothPan;->doBind()Z -Landroid/bluetooth/BluetoothPan;->isEnabled()Z -Landroid/bluetooth/BluetoothPan;->isTetheringOn()Z -Landroid/bluetooth/BluetoothPan;->isValidDevice(Landroid/bluetooth/BluetoothDevice;)Z -Landroid/bluetooth/BluetoothPan;->log(Ljava/lang/String;)V -Landroid/bluetooth/BluetoothPan;->setBluetoothTethering(Z)V -Landroid/bluetooth/BluetoothPbap;->disconnect(Landroid/bluetooth/BluetoothDevice;)Z -Landroid/bluetooth/BluetoothProfile;->A2DP_SINK:I -Landroid/bluetooth/BluetoothProfile;->AVRCP_CONTROLLER:I -Landroid/bluetooth/BluetoothProfile;->PAN:I -Landroid/bluetooth/BluetoothProfile;->PRIORITY_AUTO_CONNECT:I -Landroid/bluetooth/BluetoothProfile;->PRIORITY_UNDEFINED:I -Landroid/bluetooth/BluetoothSap;->disconnect(Landroid/bluetooth/BluetoothDevice;)Z -Landroid/bluetooth/BluetoothServerSocket;->mSocket:Landroid/bluetooth/BluetoothSocket; -Landroid/bluetooth/BluetoothSocket;->EADDRINUSE:I -Landroid/bluetooth/BluetoothSocket;->flush()V -Landroid/bluetooth/BluetoothSocket;->mPfd:Landroid/os/ParcelFileDescriptor; -Landroid/bluetooth/BluetoothSocket;->mPort:I -Landroid/bluetooth/BluetoothSocket;->mSocket:Landroid/net/LocalSocket; -Landroid/bluetooth/BluetoothUuid;->AdvAudioDist:Landroid/os/ParcelUuid; -Landroid/bluetooth/BluetoothUuid;->AudioSink:Landroid/os/ParcelUuid; -Landroid/bluetooth/BluetoothUuid;->containsAnyUuid([Landroid/os/ParcelUuid;[Landroid/os/ParcelUuid;)Z -Landroid/bluetooth/BluetoothUuid;->Handsfree:Landroid/os/ParcelUuid; -Landroid/bluetooth/BluetoothUuid;->Hogp:Landroid/os/ParcelUuid; -Landroid/bluetooth/BluetoothUuid;->HSP:Landroid/os/ParcelUuid; -Landroid/bluetooth/BluetoothUuid;->is16BitUuid(Landroid/os/ParcelUuid;)Z -Landroid/bluetooth/BluetoothUuid;->is32BitUuid(Landroid/os/ParcelUuid;)Z -Landroid/bluetooth/BluetoothUuid;->isAdvAudioDist(Landroid/os/ParcelUuid;)Z -Landroid/bluetooth/BluetoothUuid;->isAudioSource(Landroid/os/ParcelUuid;)Z -Landroid/bluetooth/BluetoothUuid;->isAvrcpTarget(Landroid/os/ParcelUuid;)Z -Landroid/bluetooth/BluetoothUuid;->isUuidPresent([Landroid/os/ParcelUuid;Landroid/os/ParcelUuid;)Z -Landroid/bluetooth/BluetoothUuid;->NAP:Landroid/os/ParcelUuid; -Landroid/bluetooth/BluetoothUuid;->ObexObjectPush:Landroid/os/ParcelUuid; -Landroid/bluetooth/BluetoothUuid;->PBAP_PSE:Landroid/os/ParcelUuid; -Landroid/bluetooth/BluetoothUuid;->RESERVED_UUIDS:[Landroid/os/ParcelUuid; Landroid/bluetooth/IBluetooth$Stub$Proxy;->getAddress()Ljava/lang/String; Landroid/bluetooth/IBluetooth$Stub$Proxy;->getConnectionState(Landroid/bluetooth/BluetoothDevice;)I Landroid/bluetooth/IBluetooth$Stub;-><init>()V @@ -1235,7 +1037,6 @@ Landroid/bluetooth/IBluetoothManager;->unregisterStateChangeCallback(Landroid/bl Landroid/bluetooth/IBluetoothManagerCallback$Stub;-><init>()V Landroid/bluetooth/IBluetoothPbap$Stub;->asInterface(Landroid/os/IBinder;)Landroid/bluetooth/IBluetoothPbap; Landroid/bluetooth/IBluetoothStateChangeCallback$Stub;-><init>()V -Landroid/bluetooth/le/ScanRecord;->parseFromBytes([B)Landroid/bluetooth/le/ScanRecord; Landroid/content/AsyncTaskLoader;->mExecutor:Ljava/util/concurrent/Executor; Landroid/content/AsyncTaskLoader;->waitForLoader()V Landroid/content/BroadcastReceiver$PendingResult;-><init>(ILjava/lang/String;Landroid/os/Bundle;IZZLandroid/os/IBinder;II)V @@ -2020,72 +1821,6 @@ Landroid/database/sqlite/SQLiteStatement;-><init>(Landroid/database/sqlite/SQLit Landroid/database/sqlite/SqliteWrapper;->checkSQLiteException(Landroid/content/Context;Landroid/database/sqlite/SQLiteException;)V Landroid/database/sqlite/SqliteWrapper;->delete(Landroid/content/Context;Landroid/content/ContentResolver;Landroid/net/Uri;Ljava/lang/String;[Ljava/lang/String;)I Landroid/database/sqlite/SqliteWrapper;->update(Landroid/content/Context;Landroid/content/ContentResolver;Landroid/net/Uri;Landroid/content/ContentValues;Ljava/lang/String;[Ljava/lang/String;)I -Landroid/filterfw/core/Filter;-><init>(Ljava/lang/String;)V -Landroid/filterfw/core/Filter;->isAvailable(Ljava/lang/String;)Z -Landroid/filterfw/core/Filter;->setInputValue(Ljava/lang/String;Ljava/lang/Object;)V -Landroid/filterfw/core/FilterContext;->getFrameManager()Landroid/filterfw/core/FrameManager; -Landroid/filterfw/core/FilterContext;->getGLEnvironment()Landroid/filterfw/core/GLEnvironment; -Landroid/filterfw/core/FilterGraph;->getFilter(Ljava/lang/String;)Landroid/filterfw/core/Filter; -Landroid/filterfw/core/FilterGraph;->tearDown(Landroid/filterfw/core/FilterContext;)V -Landroid/filterfw/core/Frame;->getBitmap()Landroid/graphics/Bitmap; -Landroid/filterfw/core/Frame;->getFormat()Landroid/filterfw/core/FrameFormat; -Landroid/filterfw/core/Frame;->getTimestamp()J -Landroid/filterfw/core/Frame;->release()Landroid/filterfw/core/Frame; -Landroid/filterfw/core/Frame;->setInts([I)V -Landroid/filterfw/core/Frame;->setTimestamp(J)V -Landroid/filterfw/core/FrameFormat;->getHeight()I -Landroid/filterfw/core/FrameFormat;->getTarget()I -Landroid/filterfw/core/FrameFormat;->getWidth()I -Landroid/filterfw/core/FrameFormat;->mutableCopy()Landroid/filterfw/core/MutableFrameFormat; -Landroid/filterfw/core/FrameManager;->duplicateFrame(Landroid/filterfw/core/Frame;)Landroid/filterfw/core/Frame; -Landroid/filterfw/core/FrameManager;->newBoundFrame(Landroid/filterfw/core/FrameFormat;IJ)Landroid/filterfw/core/Frame; -Landroid/filterfw/core/FrameManager;->newFrame(Landroid/filterfw/core/FrameFormat;)Landroid/filterfw/core/Frame; -Landroid/filterfw/core/GLEnvironment;->activate()V -Landroid/filterfw/core/GLEnvironment;->activateSurfaceWithId(I)V -Landroid/filterfw/core/GLEnvironment;->deactivate()V -Landroid/filterfw/core/GLEnvironment;->isActive()Z -Landroid/filterfw/core/GLEnvironment;->registerSurfaceFromMediaRecorder(Landroid/media/MediaRecorder;)I -Landroid/filterfw/core/GLEnvironment;->setSurfaceTimestamp(J)V -Landroid/filterfw/core/GLEnvironment;->swapBuffers()V -Landroid/filterfw/core/GLEnvironment;->unregisterSurfaceId(I)V -Landroid/filterfw/core/GLFrame;->generateMipMap()V -Landroid/filterfw/core/GLFrame;->getTextureId()I -Landroid/filterfw/core/GLFrame;->setBitmap(Landroid/graphics/Bitmap;)V -Landroid/filterfw/core/GLFrame;->setTextureParameter(II)V -Landroid/filterfw/core/GraphRunner;->getError()Ljava/lang/Exception; -Landroid/filterfw/core/GraphRunner;->getGraph()Landroid/filterfw/core/FilterGraph; -Landroid/filterfw/core/GraphRunner;->run()V -Landroid/filterfw/core/GraphRunner;->setDoneCallback(Landroid/filterfw/core/GraphRunner$OnRunnerDoneListener;)V -Landroid/filterfw/core/GraphRunner;->stop()V -Landroid/filterfw/core/MutableFrameFormat;-><init>(II)V -Landroid/filterfw/core/MutableFrameFormat;->setBytesPerSample(I)V -Landroid/filterfw/core/MutableFrameFormat;->setDimensions(II)V -Landroid/filterfw/core/Program;->process(Landroid/filterfw/core/Frame;Landroid/filterfw/core/Frame;)V -Landroid/filterfw/core/Program;->process([Landroid/filterfw/core/Frame;Landroid/filterfw/core/Frame;)V -Landroid/filterfw/core/Program;->setHostValue(Ljava/lang/String;Ljava/lang/Object;)V -Landroid/filterfw/core/ShaderProgram;-><init>(Landroid/filterfw/core/FilterContext;Ljava/lang/String;)V -Landroid/filterfw/core/ShaderProgram;->createIdentity(Landroid/filterfw/core/FilterContext;)Landroid/filterfw/core/ShaderProgram; -Landroid/filterfw/core/ShaderProgram;->process([Landroid/filterfw/core/Frame;Landroid/filterfw/core/Frame;)V -Landroid/filterfw/core/ShaderProgram;->setHostValue(Ljava/lang/String;Ljava/lang/Object;)V -Landroid/filterfw/core/ShaderProgram;->setMaximumTileSize(I)V -Landroid/filterfw/core/ShaderProgram;->setSourceRect(FFFF)V -Landroid/filterfw/core/ShaderProgram;->setSourceRegion(Landroid/filterfw/geometry/Quad;)V -Landroid/filterfw/format/ImageFormat;->create(I)Landroid/filterfw/core/MutableFrameFormat; -Landroid/filterfw/format/ImageFormat;->create(II)Landroid/filterfw/core/MutableFrameFormat; -Landroid/filterfw/format/ImageFormat;->create(IIII)Landroid/filterfw/core/MutableFrameFormat; -Landroid/filterfw/geometry/Point;-><init>()V -Landroid/filterfw/geometry/Point;-><init>(FF)V -Landroid/filterfw/geometry/Point;->x:F -Landroid/filterfw/geometry/Point;->y:F -Landroid/filterfw/geometry/Quad;-><init>()V -Landroid/filterfw/geometry/Quad;-><init>(Landroid/filterfw/geometry/Point;Landroid/filterfw/geometry/Point;Landroid/filterfw/geometry/Point;Landroid/filterfw/geometry/Point;)V -Landroid/filterfw/geometry/Quad;->p0:Landroid/filterfw/geometry/Point; -Landroid/filterfw/geometry/Quad;->p1:Landroid/filterfw/geometry/Point; -Landroid/filterfw/geometry/Quad;->p2:Landroid/filterfw/geometry/Point; -Landroid/filterfw/geometry/Quad;->p3:Landroid/filterfw/geometry/Point; -Landroid/filterfw/GraphEnvironment;-><init>()V -Landroid/filterfw/GraphEnvironment;->getRunner(II)Landroid/filterfw/core/GraphRunner; -Landroid/filterfw/GraphEnvironment;->loadGraph(Landroid/content/Context;I)I Landroid/hardware/biometrics/BiometricConstants;->BIOMETRIC_ERROR_VENDOR_BASE:I Landroid/hardware/biometrics/BiometricFingerprintConstants;->FINGERPRINT_ERROR_VENDOR_BASE:I Landroid/hardware/Camera$Parameters;->copyFrom(Landroid/hardware/Camera$Parameters;)V @@ -5959,7 +5694,6 @@ Landroid/view/ViewHierarchyEncoder;->addProperty(Ljava/lang/String;Ljava/lang/St Landroid/view/ViewHierarchyEncoder;->addProperty(Ljava/lang/String;Z)V Landroid/view/ViewOverlay;->getOverlayView()Landroid/view/ViewGroup; Landroid/view/ViewOverlay;->isEmpty()Z -Landroid/view/ViewPropertyAnimator;->mRTBackend:Landroid/view/ViewPropertyAnimatorRT; Landroid/view/ViewRootImpl$CalledFromWrongThreadException;-><init>(Ljava/lang/String;)V Landroid/view/ViewRootImpl;->addConfigCallback(Landroid/view/ViewRootImpl$ConfigChangedCallback;)V Landroid/view/ViewRootImpl;->cancelInvalidate(Landroid/view/View;)V diff --git a/config/hiddenapi-vendor-list.txt b/config/hiddenapi-vendor-list.txt index b8c1468da793..66f28a9f1f4f 100644 --- a/config/hiddenapi-vendor-list.txt +++ b/config/hiddenapi-vendor-list.txt @@ -55,7 +55,6 @@ Landroid/app/TaskStackListener;->onTaskRemoved(I)V Landroid/app/TaskStackListener;->onTaskSnapshotChanged(ILandroid/app/ActivityManager$TaskSnapshot;)V Landroid/app/TaskStackListener;->onTaskStackChanged()V Landroid/app/WallpaperColors;-><init>(Landroid/graphics/Color;Landroid/graphics/Color;Landroid/graphics/Color;I)V -Landroid/bluetooth/BluetoothHeadset;->phoneStateChanged(IIILjava/lang/String;I)V Landroid/bluetooth/IBluetooth;->sendConnectionStateChange(Landroid/bluetooth/BluetoothDevice;III)V Landroid/companion/AssociationRequest;->getDeviceFilters()Ljava/util/List; Landroid/companion/AssociationRequest;->isSingleDevice()Z diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java index 398644afd17a..2f18b89d9c69 100644 --- a/core/java/android/app/ActivityTaskManager.java +++ b/core/java/android/app/ActivityTaskManager.java @@ -204,6 +204,19 @@ public class ActivityTaskManager { } /** + * Removes all visible recent tasks from the system. + * @hide + */ + @RequiresPermission(android.Manifest.permission.REMOVE_TASKS) + public void removeAllVisibleRecentTasks() { + try { + getService().removeAllVisibleRecentTasks(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Return the maximum number of recents entries that we will maintain and show. * @hide */ diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index ece7f839381d..46664c61fbb9 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -139,6 +139,7 @@ interface IActivityTaskManager { ComponentName getCallingActivity(in IBinder token); void setFocusedTask(int taskId); boolean removeTask(int taskId); + void removeAllVisibleRecentTasks(); List<ActivityManager.RunningTaskInfo> getTasks(int maxNum); List<ActivityManager.RunningTaskInfo> getFilteredTasks(int maxNum, int ignoreActivityType, int ignoreWindowingMode); diff --git a/core/java/android/app/backup/SharedPreferencesBackupHelper.java b/core/java/android/app/backup/SharedPreferencesBackupHelper.java index 939616b3e47a..302a8efcc9f4 100644 --- a/core/java/android/app/backup/SharedPreferencesBackupHelper.java +++ b/core/java/android/app/backup/SharedPreferencesBackupHelper.java @@ -51,8 +51,8 @@ import java.io.File; * // identify the SharedPreferenceBackupHelper's data. * static final String MY_PREFS_BACKUP_KEY = "myprefs"; * - * // Simply allocate a helper and install it - * void onCreate() { + * // Allocate a helper and install it. + * public void onCreate() { * SharedPreferencesBackupHelper helper = * new SharedPreferencesBackupHelper(this, PREFS_DISPLAY, PREFS_SCORES); * addHelper(MY_PREFS_BACKUP_KEY, helper); @@ -117,7 +117,7 @@ public class SharedPreferencesBackupHelper extends FileBackupHelperBase implemen */ public void restoreEntity(BackupDataInputStream data) { Context context = mContext; - + String key = data.getKey(); if (DEBUG) Log.d(TAG, "got entity '" + key + "' size=" + data.size()); diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java index 94fd138ca6e8..966f9025e8a3 100644 --- a/core/java/android/bluetooth/BluetoothA2dp.java +++ b/core/java/android/bluetooth/BluetoothA2dp.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -117,6 +118,7 @@ public final class BluetoothA2dp implements BluetoothProfile { * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @UnsupportedAppUsage public static final String ACTION_ACTIVE_DEVICE_CHANGED = "android.bluetooth.a2dp.profile.action.ACTIVE_DEVICE_CHANGED"; @@ -137,6 +139,7 @@ public final class BluetoothA2dp implements BluetoothProfile { * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @UnsupportedAppUsage public static final String ACTION_CODEC_CONFIG_CHANGED = "android.bluetooth.a2dp.profile.action.CODEC_CONFIG_CHANGED"; @@ -160,6 +163,7 @@ public final class BluetoothA2dp implements BluetoothProfile { * * @hide */ + @UnsupportedAppUsage public static final int OPTIONAL_CODECS_SUPPORT_UNKNOWN = -1; /** @@ -167,6 +171,7 @@ public final class BluetoothA2dp implements BluetoothProfile { * * @hide */ + @UnsupportedAppUsage public static final int OPTIONAL_CODECS_NOT_SUPPORTED = 0; /** @@ -174,6 +179,7 @@ public final class BluetoothA2dp implements BluetoothProfile { * * @hide */ + @UnsupportedAppUsage public static final int OPTIONAL_CODECS_SUPPORTED = 1; /** @@ -182,6 +188,7 @@ public final class BluetoothA2dp implements BluetoothProfile { * * @hide */ + @UnsupportedAppUsage public static final int OPTIONAL_CODECS_PREF_UNKNOWN = -1; /** @@ -189,6 +196,7 @@ public final class BluetoothA2dp implements BluetoothProfile { * * @hide */ + @UnsupportedAppUsage public static final int OPTIONAL_CODECS_PREF_DISABLED = 0; /** @@ -196,6 +204,7 @@ public final class BluetoothA2dp implements BluetoothProfile { * * @hide */ + @UnsupportedAppUsage public static final int OPTIONAL_CODECS_PREF_ENABLED = 1; private Context mContext; @@ -268,6 +277,7 @@ public final class BluetoothA2dp implements BluetoothProfile { return true; } + @UnsupportedAppUsage /*package*/ void close() { mServiceListener = null; IBluetoothManager mgr = mAdapter.getBluetoothManager(); @@ -315,6 +325,7 @@ public final class BluetoothA2dp implements BluetoothProfile { * @return false on immediate error, true otherwise * @hide */ + @UnsupportedAppUsage public boolean connect(BluetoothDevice device) { if (DBG) log("connect(" + device + ")"); try { @@ -357,6 +368,7 @@ public final class BluetoothA2dp implements BluetoothProfile { * @return false on immediate error, true otherwise * @hide */ + @UnsupportedAppUsage public boolean disconnect(BluetoothDevice device) { if (DBG) log("disconnect(" + device + ")"); try { @@ -460,6 +472,7 @@ public final class BluetoothA2dp implements BluetoothProfile { * @return false on immediate error, true otherwise * @hide */ + @UnsupportedAppUsage public boolean setActiveDevice(@Nullable BluetoothDevice device) { if (DBG) log("setActiveDevice(" + device + ")"); try { @@ -490,6 +503,7 @@ public final class BluetoothA2dp implements BluetoothProfile { */ @RequiresPermission(Manifest.permission.BLUETOOTH) @Nullable + @UnsupportedAppUsage public BluetoothDevice getActiveDevice() { if (VDBG) log("getActiveDevice()"); try { @@ -556,6 +570,7 @@ public final class BluetoothA2dp implements BluetoothProfile { * @hide */ @RequiresPermission(Manifest.permission.BLUETOOTH) + @UnsupportedAppUsage public int getPriority(BluetoothDevice device) { if (VDBG) log("getPriority(" + device + ")"); try { @@ -671,6 +686,7 @@ public final class BluetoothA2dp implements BluetoothProfile { * @return the current codec status * @hide */ + @UnsupportedAppUsage public BluetoothCodecStatus getCodecStatus(BluetoothDevice device) { if (DBG) Log.d(TAG, "getCodecStatus(" + device + ")"); try { @@ -698,6 +714,7 @@ public final class BluetoothA2dp implements BluetoothProfile { * @param codecConfig the codec configuration preference * @hide */ + @UnsupportedAppUsage public void setCodecConfigPreference(BluetoothDevice device, BluetoothCodecConfig codecConfig) { if (DBG) Log.d(TAG, "setCodecConfigPreference(" + device + ")"); @@ -723,6 +740,7 @@ public final class BluetoothA2dp implements BluetoothProfile { * active A2DP Bluetooth device. * @hide */ + @UnsupportedAppUsage public void enableOptionalCodecs(BluetoothDevice device) { if (DBG) Log.d(TAG, "enableOptionalCodecs(" + device + ")"); enableDisableOptionalCodecs(device, true); @@ -735,6 +753,7 @@ public final class BluetoothA2dp implements BluetoothProfile { * active A2DP Bluetooth device. * @hide */ + @UnsupportedAppUsage public void disableOptionalCodecs(BluetoothDevice device) { if (DBG) Log.d(TAG, "disableOptionalCodecs(" + device + ")"); enableDisableOptionalCodecs(device, false); @@ -775,6 +794,7 @@ public final class BluetoothA2dp implements BluetoothProfile { * OPTIONAL_CODECS_SUPPORTED. * @hide */ + @UnsupportedAppUsage public int supportsOptionalCodecs(BluetoothDevice device) { try { mServiceLock.readLock().lock(); @@ -799,6 +819,7 @@ public final class BluetoothA2dp implements BluetoothProfile { * OPTIONAL_CODECS_PREF_DISABLED. * @hide */ + @UnsupportedAppUsage public int getOptionalCodecsEnabled(BluetoothDevice device) { try { mServiceLock.readLock().lock(); @@ -824,6 +845,7 @@ public final class BluetoothA2dp implements BluetoothProfile { * OPTIONAL_CODECS_PREF_DISABLED. * @hide */ + @UnsupportedAppUsage public void setOptionalCodecsEnabled(BluetoothDevice device, int value) { try { if (value != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN @@ -854,6 +876,7 @@ public final class BluetoothA2dp implements BluetoothProfile { * * @hide */ + @UnsupportedAppUsage public static String stateToString(int state) { switch (state) { case STATE_DISCONNECTED: diff --git a/core/java/android/bluetooth/BluetoothA2dpSink.java b/core/java/android/bluetooth/BluetoothA2dpSink.java index 13f0aaf47f0c..fda2f8927535 100755 --- a/core/java/android/bluetooth/BluetoothA2dpSink.java +++ b/core/java/android/bluetooth/BluetoothA2dpSink.java @@ -16,6 +16,7 @@ package android.bluetooth; +import android.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -278,6 +279,7 @@ public final class BluetoothA2dpSink implements BluetoothProfile { * @return false on immediate error, true otherwise * @hide */ + @UnsupportedAppUsage public boolean disconnect(BluetoothDevice device) { if (DBG) log("disconnect(" + device + ")"); final IBluetoothA2dpSink service = mService; diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 1b9e27ca7070..3c22905550ca 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -23,6 +23,7 @@ import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; +import android.annotation.UnsupportedAppUsage; import android.app.ActivityThread; import android.bluetooth.le.BluetoothLeAdvertiser; import android.bluetooth.le.BluetoothLeScanner; @@ -633,6 +634,7 @@ public final class BluetoothAdapter { private static PeriodicAdvertisingManager sPeriodicAdvertisingManager; private final IBluetoothManager mManagerService; + @UnsupportedAppUsage private IBluetooth mService; private final ReentrantReadWriteLock mServiceLock = new ReentrantReadWriteLock(); @@ -988,6 +990,7 @@ public final class BluetoothAdapter { */ @RequiresPermission(Manifest.permission.BLUETOOTH) @AdapterState + @UnsupportedAppUsage public int getLeState() { int state = BluetoothAdapter.STATE_OFF; @@ -1098,6 +1101,7 @@ public final class BluetoothAdapter { * @return true to indicate adapter shutdown has begun, or false on immediate error * @hide */ + @UnsupportedAppUsage public boolean disable(boolean persist) { try { @@ -1149,6 +1153,7 @@ public final class BluetoothAdapter { * @return true to indicate that the config file was successfully cleared * @hide */ + @UnsupportedAppUsage public boolean factoryReset() { try { mServiceLock.readLock().lock(); @@ -1172,6 +1177,7 @@ public final class BluetoothAdapter { * @return the UUIDs supported by the local Bluetooth Adapter. * @hide */ + @UnsupportedAppUsage public ParcelUuid[] getUuids() { if (getState() != STATE_ON) { return null; @@ -1438,6 +1444,7 @@ public final class BluetoothAdapter { * @return true if the scan mode was set, false otherwise * @hide */ + @UnsupportedAppUsage public boolean setScanMode(@ScanMode int mode, int duration) { if (getState() != STATE_ON) { return false; @@ -1456,6 +1463,7 @@ public final class BluetoothAdapter { } /** @hide */ + @UnsupportedAppUsage public boolean setScanMode(int mode) { if (getState() != STATE_ON) { return false; @@ -1465,6 +1473,7 @@ public final class BluetoothAdapter { } /** @hide */ + @UnsupportedAppUsage public int getDiscoverableTimeout() { if (getState() != STATE_ON) { return -1; @@ -1483,6 +1492,7 @@ public final class BluetoothAdapter { } /** @hide */ + @UnsupportedAppUsage public void setDiscoverableTimeout(int timeout) { if (getState() != STATE_ON) { return; @@ -2007,6 +2017,7 @@ public final class BluetoothAdapter { * #STATE_CONNECTING} or {@link #STATE_DISCONNECTED} * @hide */ + @UnsupportedAppUsage public int getConnectionState() { if (getState() != STATE_ON) { return BluetoothAdapter.STATE_DISCONNECTED; @@ -2094,6 +2105,7 @@ public final class BluetoothAdapter { * permissions, or channel in use. * @hide */ + @UnsupportedAppUsage public BluetoothServerSocket listenUsingRfcommOn(int channel, boolean mitm, boolean min16DigitPin) throws IOException { BluetoothServerSocket socket = @@ -2206,6 +2218,7 @@ public final class BluetoothAdapter { * permissions, or channel in use. * @hide */ + @UnsupportedAppUsage public BluetoothServerSocket listenUsingEncryptedRfcommWithServiceRecord(String name, UUID uuid) throws IOException { return createNewRfcommSocketAndRecord(name, uuid, false, true); @@ -2749,6 +2762,7 @@ public final class BluetoothAdapter { return true; } + @UnsupportedAppUsage /*package*/ IBluetoothManager getBluetoothManager() { return mManagerService; } @@ -2756,6 +2770,7 @@ public final class BluetoothAdapter { private final ArrayList<IBluetoothManagerCallback> mProxyServiceStateCallbacks = new ArrayList<IBluetoothManagerCallback>(); + @UnsupportedAppUsage /*package*/ IBluetooth getBluetoothService(IBluetoothManagerCallback cb) { synchronized (mProxyServiceStateCallbacks) { if (cb == null) { diff --git a/core/java/android/bluetooth/BluetoothClass.java b/core/java/android/bluetooth/BluetoothClass.java index f22ea6e88e04..8557f389d913 100755 --- a/core/java/android/bluetooth/BluetoothClass.java +++ b/core/java/android/bluetooth/BluetoothClass.java @@ -16,6 +16,7 @@ package android.bluetooth; +import android.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; @@ -63,6 +64,7 @@ public final class BluetoothClass implements Parcelable { private final int mClass; /** @hide */ + @UnsupportedAppUsage public BluetoothClass(int classInt) { mClass = classInt; } @@ -322,8 +324,10 @@ public final class BluetoothClass implements Parcelable { } /** @hide */ + @UnsupportedAppUsage public static final int PROFILE_HEADSET = 0; /** @hide */ + @UnsupportedAppUsage public static final int PROFILE_A2DP = 1; /** @hide */ public static final int PROFILE_OPP = 2; @@ -346,6 +350,7 @@ public final class BluetoothClass implements Parcelable { * @return True if this device might support specified profile. * @hide */ + @UnsupportedAppUsage public boolean doesClassMatch(int profile) { if (profile == PROFILE_A2DP) { if (hasService(Service.RENDER)) { diff --git a/core/java/android/bluetooth/BluetoothCodecConfig.java b/core/java/android/bluetooth/BluetoothCodecConfig.java index e3a6e064725e..79c0a3a207c4 100644 --- a/core/java/android/bluetooth/BluetoothCodecConfig.java +++ b/core/java/android/bluetooth/BluetoothCodecConfig.java @@ -16,6 +16,7 @@ package android.bluetooth; +import android.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; @@ -32,34 +33,58 @@ public final class BluetoothCodecConfig implements Parcelable { // Add an entry for each source codec here. // NOTE: The values should be same as those listed in the following file: // hardware/libhardware/include/hardware/bt_av.h + @UnsupportedAppUsage public static final int SOURCE_CODEC_TYPE_SBC = 0; + @UnsupportedAppUsage public static final int SOURCE_CODEC_TYPE_AAC = 1; + @UnsupportedAppUsage public static final int SOURCE_CODEC_TYPE_APTX = 2; + @UnsupportedAppUsage public static final int SOURCE_CODEC_TYPE_APTX_HD = 3; + @UnsupportedAppUsage public static final int SOURCE_CODEC_TYPE_LDAC = 4; + @UnsupportedAppUsage public static final int SOURCE_CODEC_TYPE_MAX = 5; + @UnsupportedAppUsage public static final int SOURCE_CODEC_TYPE_INVALID = 1000 * 1000; + @UnsupportedAppUsage public static final int CODEC_PRIORITY_DISABLED = -1; + @UnsupportedAppUsage public static final int CODEC_PRIORITY_DEFAULT = 0; + @UnsupportedAppUsage public static final int CODEC_PRIORITY_HIGHEST = 1000 * 1000; + @UnsupportedAppUsage public static final int SAMPLE_RATE_NONE = 0; + @UnsupportedAppUsage public static final int SAMPLE_RATE_44100 = 0x1 << 0; + @UnsupportedAppUsage public static final int SAMPLE_RATE_48000 = 0x1 << 1; + @UnsupportedAppUsage public static final int SAMPLE_RATE_88200 = 0x1 << 2; + @UnsupportedAppUsage public static final int SAMPLE_RATE_96000 = 0x1 << 3; + @UnsupportedAppUsage public static final int SAMPLE_RATE_176400 = 0x1 << 4; + @UnsupportedAppUsage public static final int SAMPLE_RATE_192000 = 0x1 << 5; + @UnsupportedAppUsage public static final int BITS_PER_SAMPLE_NONE = 0; + @UnsupportedAppUsage public static final int BITS_PER_SAMPLE_16 = 0x1 << 0; + @UnsupportedAppUsage public static final int BITS_PER_SAMPLE_24 = 0x1 << 1; + @UnsupportedAppUsage public static final int BITS_PER_SAMPLE_32 = 0x1 << 2; + @UnsupportedAppUsage public static final int CHANNEL_MODE_NONE = 0; + @UnsupportedAppUsage public static final int CHANNEL_MODE_MONO = 0x1 << 0; + @UnsupportedAppUsage public static final int CHANNEL_MODE_STEREO = 0x1 << 1; private final int mCodecType; @@ -72,6 +97,7 @@ public final class BluetoothCodecConfig implements Parcelable { private final long mCodecSpecific3; private final long mCodecSpecific4; + @UnsupportedAppUsage public BluetoothCodecConfig(int codecType, int codecPriority, int sampleRate, int bitsPerSample, int channelMode, long codecSpecific1, @@ -276,6 +302,7 @@ public final class BluetoothCodecConfig implements Parcelable { * * @return the codec type */ + @UnsupportedAppUsage public int getCodecType() { return mCodecType; } @@ -296,6 +323,7 @@ public final class BluetoothCodecConfig implements Parcelable { * * @return the codec priority */ + @UnsupportedAppUsage public int getCodecPriority() { return mCodecPriority; } @@ -307,6 +335,7 @@ public final class BluetoothCodecConfig implements Parcelable { * * @param codecPriority the codec priority */ + @UnsupportedAppUsage public void setCodecPriority(int codecPriority) { mCodecPriority = codecPriority; } @@ -324,6 +353,7 @@ public final class BluetoothCodecConfig implements Parcelable { * * @return the codec sample rate */ + @UnsupportedAppUsage public int getSampleRate() { return mSampleRate; } @@ -338,6 +368,7 @@ public final class BluetoothCodecConfig implements Parcelable { * * @return the codec bits per sample */ + @UnsupportedAppUsage public int getBitsPerSample() { return mBitsPerSample; } @@ -351,6 +382,7 @@ public final class BluetoothCodecConfig implements Parcelable { * * @return the codec channel mode */ + @UnsupportedAppUsage public int getChannelMode() { return mChannelMode; } @@ -360,6 +392,7 @@ public final class BluetoothCodecConfig implements Parcelable { * * @return a codec specific value1. */ + @UnsupportedAppUsage public long getCodecSpecific1() { return mCodecSpecific1; } @@ -369,6 +402,7 @@ public final class BluetoothCodecConfig implements Parcelable { * * @return a codec specific value2 */ + @UnsupportedAppUsage public long getCodecSpecific2() { return mCodecSpecific2; } @@ -378,6 +412,7 @@ public final class BluetoothCodecConfig implements Parcelable { * * @return a codec specific value3 */ + @UnsupportedAppUsage public long getCodecSpecific3() { return mCodecSpecific3; } @@ -387,6 +422,7 @@ public final class BluetoothCodecConfig implements Parcelable { * * @return a codec specific value4 */ + @UnsupportedAppUsage public long getCodecSpecific4() { return mCodecSpecific4; } diff --git a/core/java/android/bluetooth/BluetoothCodecStatus.java b/core/java/android/bluetooth/BluetoothCodecStatus.java index 3a05e7093d40..78560d2de420 100644 --- a/core/java/android/bluetooth/BluetoothCodecStatus.java +++ b/core/java/android/bluetooth/BluetoothCodecStatus.java @@ -16,6 +16,7 @@ package android.bluetooth; +import android.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; @@ -37,6 +38,7 @@ public final class BluetoothCodecStatus implements Parcelable { * This extra represents the current codec status of the A2DP * profile. */ + @UnsupportedAppUsage public static final String EXTRA_CODEC_STATUS = "android.bluetooth.codec.extra.CODEC_STATUS"; @@ -137,6 +139,7 @@ public final class BluetoothCodecStatus implements Parcelable { * * @return the current codec configuration */ + @UnsupportedAppUsage public BluetoothCodecConfig getCodecConfig() { return mCodecConfig; } @@ -146,6 +149,7 @@ public final class BluetoothCodecStatus implements Parcelable { * * @return an array with the codecs local capabilities */ + @UnsupportedAppUsage public BluetoothCodecConfig[] getCodecsLocalCapabilities() { return mCodecsLocalCapabilities; } @@ -155,6 +159,7 @@ public final class BluetoothCodecStatus implements Parcelable { * * @return an array with the codecs selectable capabilities */ + @UnsupportedAppUsage public BluetoothCodecConfig[] getCodecsSelectableCapabilities() { return mCodecsSelectableCapabilities; } diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 7a6b72e980f5..818a749842f7 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -21,6 +21,7 @@ import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; +import android.annotation.UnsupportedAppUsage; import android.content.Context; import android.os.Handler; import android.os.Parcel; @@ -115,6 +116,7 @@ public final class BluetoothDevice implements Parcelable { * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @UnsupportedAppUsage public static final String ACTION_DISAPPEARED = "android.bluetooth.device.action.DISAPPEARED"; @@ -186,6 +188,7 @@ public final class BluetoothDevice implements Parcelable { * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @UnsupportedAppUsage public static final String ACTION_ALIAS_CHANGED = "android.bluetooth.device.action.ALIAS_CHANGED"; @@ -306,6 +309,7 @@ public final class BluetoothDevice implements Parcelable { * * @hide */ + @UnsupportedAppUsage public static final String EXTRA_REASON = "android.bluetooth.device.extra.REASON"; /** @@ -346,6 +350,7 @@ public final class BluetoothDevice implements Parcelable { /** @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @UnsupportedAppUsage public static final String ACTION_SDP_RECORD = "android.bluetooth.device.action.SDP_RECORD"; @@ -390,6 +395,7 @@ public final class BluetoothDevice implements Parcelable { "android.bluetooth.device.action.PAIRING_REQUEST"; /** @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @UnsupportedAppUsage public static final String ACTION_PAIRING_CANCEL = "android.bluetooth.device.action.PAIRING_CANCEL"; @@ -481,6 +487,7 @@ public final class BluetoothDevice implements Parcelable { * * @hide */ + @UnsupportedAppUsage public static final int UNBOND_REASON_AUTH_FAILED = 1; /** @@ -489,6 +496,7 @@ public final class BluetoothDevice implements Parcelable { * * @hide */ + @UnsupportedAppUsage public static final int UNBOND_REASON_AUTH_REJECTED = 2; /** @@ -503,6 +511,7 @@ public final class BluetoothDevice implements Parcelable { * * @hide */ + @UnsupportedAppUsage public static final int UNBOND_REASON_REMOTE_DEVICE_DOWN = 4; /** @@ -510,6 +519,7 @@ public final class BluetoothDevice implements Parcelable { * * @hide */ + @UnsupportedAppUsage public static final int UNBOND_REASON_DISCOVERY_IN_PROGRESS = 5; /** @@ -517,6 +527,7 @@ public final class BluetoothDevice implements Parcelable { * * @hide */ + @UnsupportedAppUsage public static final int UNBOND_REASON_AUTH_TIMEOUT = 6; /** @@ -524,6 +535,7 @@ public final class BluetoothDevice implements Parcelable { * * @hide */ + @UnsupportedAppUsage public static final int UNBOND_REASON_REPEATED_ATTEMPTS = 7; /** @@ -532,6 +544,7 @@ public final class BluetoothDevice implements Parcelable { * * @hide */ + @UnsupportedAppUsage public static final int UNBOND_REASON_REMOTE_AUTH_CANCELED = 8; /** @@ -610,6 +623,7 @@ public final class BluetoothDevice implements Parcelable { "android.bluetooth.device.extra.SDP_RECORD"; /** @hide */ + @UnsupportedAppUsage public static final String EXTRA_SDP_SEARCH_STATUS = "android.bluetooth.device.extra.SDP_SEARCH_STATUS"; /** @@ -720,6 +734,7 @@ public final class BluetoothDevice implements Parcelable { private final String mAddress; /*package*/ + @UnsupportedAppUsage static IBluetooth getService() { synchronized (BluetoothDevice.class) { if (sService == null) { @@ -763,6 +778,7 @@ public final class BluetoothDevice implements Parcelable { * @throws IllegalArgumentException address is invalid * @hide */ + @UnsupportedAppUsage /*package*/ BluetoothDevice(String address) { getService(); // ensures sService is initialized if (!BluetoothAdapter.checkBluetoothAddress(address)) { @@ -887,6 +903,7 @@ public final class BluetoothDevice implements Parcelable { * @return the Bluetooth alias, or null if no alias or there was a problem * @hide */ + @UnsupportedAppUsage public String getAlias() { final IBluetooth service = sService; if (service == null) { @@ -911,6 +928,7 @@ public final class BluetoothDevice implements Parcelable { * @return true on success, false on error * @hide */ + @UnsupportedAppUsage public boolean setAlias(String alias) { final IBluetooth service = sService; if (service == null) { @@ -934,6 +952,7 @@ public final class BluetoothDevice implements Parcelable { * @see #getAlias() * @see #getName() */ + @UnsupportedAppUsage public String getAliasName() { String name = getAlias(); if (name == null) { @@ -952,6 +971,7 @@ public final class BluetoothDevice implements Parcelable { * @hide */ @RequiresPermission(Manifest.permission.BLUETOOTH) + @UnsupportedAppUsage public int getBatteryLevel() { final IBluetooth service = sService; if (service == null) { @@ -1010,6 +1030,7 @@ public final class BluetoothDevice implements Parcelable { * @throws IllegalArgumentException if an invalid transport was specified * @hide */ + @UnsupportedAppUsage public boolean createBond(int transport) { final IBluetooth service = sService; if (service == null) { @@ -1063,6 +1084,7 @@ public final class BluetoothDevice implements Parcelable { } /** @hide */ + @UnsupportedAppUsage public boolean isBondingInitiatedLocally() { final IBluetooth service = sService; if (service == null) { @@ -1355,6 +1377,7 @@ public final class BluetoothDevice implements Parcelable { } /** @hide */ + @UnsupportedAppUsage public boolean setPasskey(int passkey) { //TODO(BT) /* @@ -1395,6 +1418,7 @@ public final class BluetoothDevice implements Parcelable { } /** @hide */ + @UnsupportedAppUsage public boolean cancelPairingUserInput() { final IBluetooth service = sService; if (service == null) { @@ -1410,6 +1434,7 @@ public final class BluetoothDevice implements Parcelable { } /** @hide */ + @UnsupportedAppUsage public boolean isBluetoothDock() { // TODO(BT) /* @@ -1435,6 +1460,7 @@ public final class BluetoothDevice implements Parcelable { * #ACCESS_UNKNOWN}, {@link #ACCESS_ALLOWED} or {@link #ACCESS_REJECTED}. * @hide */ + @UnsupportedAppUsage public int getPhonebookAccessPermission() { final IBluetooth service = sService; if (service == null) { @@ -1479,6 +1505,7 @@ public final class BluetoothDevice implements Parcelable { * {@link #ACCESS_ALLOWED} or {@link #ACCESS_REJECTED}. * @hide */ + @UnsupportedAppUsage public int getMessageAccessPermission() { final IBluetooth service = sService; if (service == null) { @@ -1501,6 +1528,7 @@ public final class BluetoothDevice implements Parcelable { * @return Whether the value has been successfully set. * @hide */ + @UnsupportedAppUsage public boolean setMessageAccessPermission(int value) { final IBluetooth service = sService; if (service == null) { @@ -1543,6 +1571,7 @@ public final class BluetoothDevice implements Parcelable { * @return Whether the value has been successfully set. * @hide */ + @UnsupportedAppUsage public boolean setSimAccessPermission(int value) { final IBluetooth service = sService; if (service == null) { @@ -1581,6 +1610,7 @@ public final class BluetoothDevice implements Parcelable { * permissions * @hide */ + @UnsupportedAppUsage public BluetoothSocket createRfcommSocket(int channel) throws IOException { if (!isBluetoothEnabled()) { Log.e(TAG, "Bluetooth is not enabled"); @@ -1733,6 +1763,7 @@ public final class BluetoothDevice implements Parcelable { * permissions. * @hide */ + @UnsupportedAppUsage public BluetoothSocket createInsecureRfcommSocket(int port) throws IOException { if (!isBluetoothEnabled()) { Log.e(TAG, "Bluetooth is not enabled"); @@ -1752,6 +1783,7 @@ public final class BluetoothDevice implements Parcelable { * permissions. * @hide */ + @UnsupportedAppUsage public BluetoothSocket createScoSocket() throws IOException { if (!isBluetoothEnabled()) { Log.e(TAG, "Bluetooth is not enabled"); @@ -1769,6 +1801,7 @@ public final class BluetoothDevice implements Parcelable { * @return the pin code as a UTF-8 byte array, or null if it is an invalid Bluetooth pin. * @hide */ + @UnsupportedAppUsage public static byte[] convertPinToBytes(String pin) { if (pin == null) { return null; @@ -1900,6 +1933,7 @@ public final class BluetoothDevice implements Parcelable { * operations. * @hide */ + @UnsupportedAppUsage public BluetoothGatt connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback, int transport, boolean opportunistic, int phy, Handler handler) { diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java index 457119dc5bfe..78248efdd048 100644 --- a/core/java/android/bluetooth/BluetoothGatt.java +++ b/core/java/android/bluetooth/BluetoothGatt.java @@ -16,6 +16,7 @@ package android.bluetooth; +import android.annotation.UnsupportedAppUsage; import android.os.Handler; import android.os.ParcelUuid; import android.os.RemoteException; @@ -41,16 +42,23 @@ public final class BluetoothGatt implements BluetoothProfile { private static final boolean DBG = true; private static final boolean VDBG = false; + @UnsupportedAppUsage private IBluetoothGatt mService; + @UnsupportedAppUsage private volatile BluetoothGattCallback mCallback; private Handler mHandler; + @UnsupportedAppUsage private int mClientIf; private BluetoothDevice mDevice; + @UnsupportedAppUsage private boolean mAutoConnect; + @UnsupportedAppUsage private int mAuthRetryState; private int mConnState; private final Object mStateLock = new Object(); + @UnsupportedAppUsage private Boolean mDeviceBusy = false; + @UnsupportedAppUsage private int mTransport; private int mPhy; private boolean mOpportunistic; @@ -810,6 +818,7 @@ public final class BluetoothGatt implements BluetoothProfile { /** * Unregister the current application and callbacks. */ + @UnsupportedAppUsage private void unregisterApp() { if (DBG) Log.d(TAG, "unregisterApp() - mClientIf=" + mClientIf); if (mService == null || mClientIf == 0) return; @@ -845,6 +854,7 @@ public final class BluetoothGatt implements BluetoothProfile { * automatically connect as soon as the remote device becomes available (true). * @return true, if the connection attempt was initiated successfully */ + @UnsupportedAppUsage /*package*/ boolean connect(Boolean autoConnect, BluetoothGattCallback callback, Handler handler) { if (DBG) { @@ -1407,6 +1417,7 @@ public final class BluetoothGatt implements BluetoothProfile { * * @hide */ + @UnsupportedAppUsage public boolean refresh() { if (DBG) Log.d(TAG, "refresh() - device: " + mDevice.getAddress()); if (mService == null || mClientIf == 0) return false; diff --git a/core/java/android/bluetooth/BluetoothGattCharacteristic.java b/core/java/android/bluetooth/BluetoothGattCharacteristic.java index 243ad359a48f..6d46b3a41867 100644 --- a/core/java/android/bluetooth/BluetoothGattCharacteristic.java +++ b/core/java/android/bluetooth/BluetoothGattCharacteristic.java @@ -15,6 +15,7 @@ */ package android.bluetooth; +import android.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.ParcelUuid; import android.os.Parcelable; @@ -182,6 +183,7 @@ public class BluetoothGattCharacteristic implements Parcelable { * * @hide */ + @UnsupportedAppUsage protected int mInstance; /** @@ -218,6 +220,7 @@ public class BluetoothGattCharacteristic implements Parcelable { * * @hide */ + @UnsupportedAppUsage protected BluetoothGattService mService; /** @@ -381,6 +384,7 @@ public class BluetoothGattCharacteristic implements Parcelable { * * @hide */ + @UnsupportedAppUsage /*package*/ void setService(BluetoothGattService service) { mService = service; } @@ -464,6 +468,7 @@ public class BluetoothGattCharacteristic implements Parcelable { * * @hide */ + @UnsupportedAppUsage public void setKeySize(int keySize) { mKeySize = keySize; } diff --git a/core/java/android/bluetooth/BluetoothGattDescriptor.java b/core/java/android/bluetooth/BluetoothGattDescriptor.java index 217a5abf1fe5..3ffbb9e0c052 100644 --- a/core/java/android/bluetooth/BluetoothGattDescriptor.java +++ b/core/java/android/bluetooth/BluetoothGattDescriptor.java @@ -16,6 +16,7 @@ package android.bluetooth; +import android.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.ParcelUuid; import android.os.Parcelable; @@ -100,6 +101,7 @@ public class BluetoothGattDescriptor implements Parcelable { * * @hide */ + @UnsupportedAppUsage protected int mInstance; /** @@ -114,6 +116,7 @@ public class BluetoothGattDescriptor implements Parcelable { * * @hide */ + @UnsupportedAppUsage protected BluetoothGattCharacteristic mCharacteristic; /** @@ -205,6 +208,7 @@ public class BluetoothGattDescriptor implements Parcelable { * * @hide */ + @UnsupportedAppUsage /*package*/ void setCharacteristic(BluetoothGattCharacteristic characteristic) { mCharacteristic = characteristic; } diff --git a/core/java/android/bluetooth/BluetoothGattService.java b/core/java/android/bluetooth/BluetoothGattService.java index ce1dc1ce6311..8e740ee387d6 100644 --- a/core/java/android/bluetooth/BluetoothGattService.java +++ b/core/java/android/bluetooth/BluetoothGattService.java @@ -15,6 +15,7 @@ */ package android.bluetooth; +import android.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.ParcelUuid; import android.os.Parcelable; @@ -48,6 +49,7 @@ public class BluetoothGattService implements Parcelable { * * @hide */ + @UnsupportedAppUsage protected BluetoothDevice mDevice; /** @@ -265,6 +267,7 @@ public class BluetoothGattService implements Parcelable { * * @hide */ + @UnsupportedAppUsage public void setInstanceId(int instanceId) { mInstanceId = instanceId; } @@ -382,6 +385,7 @@ public class BluetoothGattService implements Parcelable { * * @hide */ + @UnsupportedAppUsage public void setAdvertisePreferred(boolean advertisePreferred) { mAdvertisePreferred = advertisePreferred; } diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java index 0c91a2054b8b..636b1b9b13ca 100644 --- a/core/java/android/bluetooth/BluetoothHeadset.java +++ b/core/java/android/bluetooth/BluetoothHeadset.java @@ -22,6 +22,7 @@ import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; +import android.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; import android.os.Binder; @@ -110,6 +111,7 @@ public final class BluetoothHeadset implements BluetoothProfile { * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @UnsupportedAppUsage public static final String ACTION_ACTIVE_DEVICE_CHANGED = "android.bluetooth.headset.profile.action.ACTIVE_DEVICE_CHANGED"; @@ -401,6 +403,7 @@ public final class BluetoothHeadset implements BluetoothProfile { * results once close() has been called. Multiple invocations of close() * are ok. */ + @UnsupportedAppUsage /*package*/ void close() { if (VDBG) log("close()"); @@ -602,6 +605,7 @@ public final class BluetoothHeadset implements BluetoothProfile { * @return priority of the device * @hide */ + @UnsupportedAppUsage public int getPriority(BluetoothDevice device) { if (VDBG) log("getPriority(" + device + ")"); final IBluetoothHeadset service = mService; @@ -719,6 +723,7 @@ public final class BluetoothHeadset implements BluetoothProfile { * * @hide */ + @UnsupportedAppUsage public int getAudioState(BluetoothDevice device) { if (VDBG) log("getAudioState"); final IBluetoothHeadset service = mService; @@ -846,6 +851,7 @@ public final class BluetoothHeadset implements BluetoothProfile { * @return false if there was some error such as there is no active headset * @hide */ + @UnsupportedAppUsage public boolean connectAudio() { final IBluetoothHeadset service = mService; if (service != null && isEnabled()) { @@ -872,6 +878,7 @@ public final class BluetoothHeadset implements BluetoothProfile { * @return false if audio is not connected, or on error, true otherwise * @hide */ + @UnsupportedAppUsage public boolean disconnectAudio() { final IBluetoothHeadset service = mService; if (service != null && isEnabled()) { @@ -909,6 +916,7 @@ public final class BluetoothHeadset implements BluetoothProfile { * @hide */ @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @UnsupportedAppUsage public boolean startScoUsingVirtualVoiceCall() { if (DBG) log("startScoUsingVirtualVoiceCall()"); final IBluetoothHeadset service = mService; @@ -938,6 +946,7 @@ public final class BluetoothHeadset implements BluetoothProfile { * @hide */ @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @UnsupportedAppUsage public boolean stopScoUsingVirtualVoiceCall() { if (DBG) log("stopScoUsingVirtualVoiceCall()"); final IBluetoothHeadset service = mService; @@ -962,6 +971,7 @@ public final class BluetoothHeadset implements BluetoothProfile { * * @hide */ + @UnsupportedAppUsage public void phoneStateChanged(int numActive, int numHeld, int callState, String number, int type) { final IBluetoothHeadset service = mService; @@ -1060,6 +1070,7 @@ public final class BluetoothHeadset implements BluetoothProfile { * @hide */ @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) + @UnsupportedAppUsage public boolean setActiveDevice(@Nullable BluetoothDevice device) { if (DBG) { Log.d(TAG, "setActiveDevice: " + device); @@ -1089,6 +1100,7 @@ public final class BluetoothHeadset implements BluetoothProfile { * @hide */ @RequiresPermission(android.Manifest.permission.BLUETOOTH) + @UnsupportedAppUsage public BluetoothDevice getActiveDevice() { if (VDBG) { Log.d(TAG, "getActiveDevice"); @@ -1163,6 +1175,7 @@ public final class BluetoothHeadset implements BluetoothProfile { } }; + @UnsupportedAppUsage private boolean isEnabled() { return mAdapter.getState() == BluetoothAdapter.STATE_ON; } diff --git a/core/java/android/bluetooth/BluetoothHeadsetClient.java b/core/java/android/bluetooth/BluetoothHeadsetClient.java index 397b90656f3e..ec18d42698c1 100644 --- a/core/java/android/bluetooth/BluetoothHeadsetClient.java +++ b/core/java/android/bluetooth/BluetoothHeadsetClient.java @@ -16,6 +16,7 @@ package android.bluetooth; +import android.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -476,6 +477,7 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @return <code>true</code> if command has been issued successfully; <code>false</code> * otherwise; upon completion HFP sends {@link #ACTION_CONNECTION_STATE_CHANGED} intent. */ + @UnsupportedAppUsage public boolean connect(BluetoothDevice device) { if (DBG) log("connect(" + device + ")"); final IBluetoothHeadsetClient service = mService; @@ -498,6 +500,7 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @return <code>true</code> if command has been issued successfully; <code>false</code> * otherwise; upon completion HFP sends {@link #ACTION_CONNECTION_STATE_CHANGED} intent. */ + @UnsupportedAppUsage public boolean disconnect(BluetoothDevice device) { if (DBG) log("disconnect(" + device + ")"); final IBluetoothHeadsetClient service = mService; @@ -720,6 +723,7 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @return <code>true</code> if command has been issued successfully; <code>false</code> * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. */ + @UnsupportedAppUsage public boolean acceptCall(BluetoothDevice device, int flag) { if (DBG) log("acceptCall()"); final IBluetoothHeadsetClient service = mService; @@ -766,6 +770,7 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * #EXTRA_AG_FEATURE_REJECT_CALL}. This method invocation will fail silently when feature is not * supported.</p> */ + @UnsupportedAppUsage public boolean rejectCall(BluetoothDevice device) { if (DBG) log("rejectCall()"); final IBluetoothHeadsetClient service = mService; @@ -943,6 +948,7 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * * Note: This is an internal function and shouldn't be exposed */ + @UnsupportedAppUsage public int getAudioState(BluetoothDevice device) { if (VDBG) log("getAudioState"); final IBluetoothHeadsetClient service = mService; diff --git a/core/java/android/bluetooth/BluetoothHeadsetClientCall.java b/core/java/android/bluetooth/BluetoothHeadsetClientCall.java index d46b2e37467b..e02a2f4ae5d3 100644 --- a/core/java/android/bluetooth/BluetoothHeadsetClientCall.java +++ b/core/java/android/bluetooth/BluetoothHeadsetClientCall.java @@ -16,6 +16,7 @@ package android.bluetooth; +import android.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; @@ -143,6 +144,7 @@ public final class BluetoothHeadsetClientCall implements Parcelable { * * @return call id. */ + @UnsupportedAppUsage public int getId() { return mId; } @@ -162,6 +164,7 @@ public final class BluetoothHeadsetClientCall implements Parcelable { * * @return state of this particular phone call. */ + @UnsupportedAppUsage public int getState() { return mState; } @@ -171,6 +174,7 @@ public final class BluetoothHeadsetClientCall implements Parcelable { * * @return string representing phone number. */ + @UnsupportedAppUsage public String getNumber() { return mNumber; } @@ -189,6 +193,7 @@ public final class BluetoothHeadsetClientCall implements Parcelable { * * @return <code>true</code> if call is a multi party call, <code>false</code> otherwise. */ + @UnsupportedAppUsage public boolean isMultiParty() { return mMultiParty; } @@ -198,6 +203,7 @@ public final class BluetoothHeadsetClientCall implements Parcelable { * * @return <code>true</code> if its outgoing call, <code>false</code> otherwise. */ + @UnsupportedAppUsage public boolean isOutgoing() { return mOutgoing; } diff --git a/core/java/android/bluetooth/BluetoothHearingAid.java b/core/java/android/bluetooth/BluetoothHearingAid.java index 159e165d594f..606f00a8239d 100644 --- a/core/java/android/bluetooth/BluetoothHearingAid.java +++ b/core/java/android/bluetooth/BluetoothHearingAid.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -108,6 +109,7 @@ public final class BluetoothHearingAid implements BluetoothProfile { * receive. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @UnsupportedAppUsage public static final String ACTION_ACTIVE_DEVICE_CHANGED = "android.bluetooth.hearingaid.profile.action.ACTIVE_DEVICE_CHANGED"; @@ -401,6 +403,7 @@ public final class BluetoothHearingAid implements BluetoothProfile { * @return false on immediate error, true otherwise * @hide */ + @UnsupportedAppUsage public boolean setActiveDevice(@Nullable BluetoothDevice device) { if (DBG) log("setActiveDevice(" + device + ")"); try { @@ -432,6 +435,7 @@ public final class BluetoothHearingAid implements BluetoothProfile { * @hide */ @RequiresPermission(Manifest.permission.BLUETOOTH) + @UnsupportedAppUsage public List<BluetoothDevice> getActiveDevices() { if (VDBG) log("getActiveDevices()"); try { diff --git a/core/java/android/bluetooth/BluetoothMap.java b/core/java/android/bluetooth/BluetoothMap.java index 0fa1d5d6295f..98c23c600f14 100644 --- a/core/java/android/bluetooth/BluetoothMap.java +++ b/core/java/android/bluetooth/BluetoothMap.java @@ -16,6 +16,7 @@ package android.bluetooth; +import android.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -233,6 +234,7 @@ public final class BluetoothMap implements BluetoothProfile { * @param device Remote Bluetooth Device * @return false on error, true otherwise */ + @UnsupportedAppUsage public boolean disconnect(BluetoothDevice device) { if (DBG) log("disconnect(" + device + ")"); final IBluetoothMap service = mService; diff --git a/core/java/android/bluetooth/BluetoothMapClient.java b/core/java/android/bluetooth/BluetoothMapClient.java index 4f21d936e562..183be5f38bd1 100644 --- a/core/java/android/bluetooth/BluetoothMapClient.java +++ b/core/java/android/bluetooth/BluetoothMapClient.java @@ -16,6 +16,7 @@ package android.bluetooth; +import android.annotation.UnsupportedAppUsage; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; @@ -358,6 +359,7 @@ public final class BluetoothMapClient implements BluetoothProfile { * @param deliveredIntent intent issued when message is delivered * @return true if the message is enqueued, false on error */ + @UnsupportedAppUsage public boolean sendMessage(BluetoothDevice device, Uri[] contacts, String message, PendingIntent sentIntent, PendingIntent deliveredIntent) { if (DBG) Log.d(TAG, "sendMessage(" + device + ", " + contacts + ", " + message); diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java index 9f401eb3cefc..58be73296027 100644 --- a/core/java/android/bluetooth/BluetoothPan.java +++ b/core/java/android/bluetooth/BluetoothPan.java @@ -18,6 +18,7 @@ package android.bluetooth; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -129,6 +130,7 @@ public final class BluetoothPan implements BluetoothProfile { * Create a BluetoothPan proxy object for interacting with the local * Bluetooth Service which handles the Pan profile */ + @UnsupportedAppUsage /*package*/ BluetoothPan(Context context, ServiceListener l) { mContext = context; mServiceListener = l; @@ -142,6 +144,7 @@ public final class BluetoothPan implements BluetoothProfile { doBind(); } + @UnsupportedAppUsage boolean doBind() { Intent intent = new Intent(IBluetoothPan.class.getName()); ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); @@ -154,6 +157,7 @@ public final class BluetoothPan implements BluetoothProfile { return true; } + @UnsupportedAppUsage /*package*/ void close() { if (VDBG) log("close()"); @@ -236,6 +240,7 @@ public final class BluetoothPan implements BluetoothProfile { * @return false on immediate error, true otherwise * @hide */ + @UnsupportedAppUsage public boolean connect(BluetoothDevice device) { if (DBG) log("connect(" + device + ")"); final IBluetoothPan service = mPanService; @@ -276,6 +281,7 @@ public final class BluetoothPan implements BluetoothProfile { * @return false on immediate error, true otherwise * @hide */ + @UnsupportedAppUsage public boolean disconnect(BluetoothDevice device) { if (DBG) log("disconnect(" + device + ")"); final IBluetoothPan service = mPanService; @@ -348,6 +354,7 @@ public final class BluetoothPan implements BluetoothProfile { return BluetoothProfile.STATE_DISCONNECTED; } + @UnsupportedAppUsage public void setBluetoothTethering(boolean value) { if (DBG) log("setBluetoothTethering(" + value + ")"); final IBluetoothPan service = mPanService; @@ -360,6 +367,7 @@ public final class BluetoothPan implements BluetoothProfile { } } + @UnsupportedAppUsage public boolean isTetheringOn() { if (VDBG) log("isTetheringOn()"); final IBluetoothPan service = mPanService; @@ -392,14 +400,17 @@ public final class BluetoothPan implements BluetoothProfile { } }; + @UnsupportedAppUsage private boolean isEnabled() { return mAdapter.getState() == BluetoothAdapter.STATE_ON; } + @UnsupportedAppUsage private static boolean isValidDevice(BluetoothDevice device) { return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); } + @UnsupportedAppUsage private static void log(String msg) { Log.d(TAG, msg); } diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java index c60e9e075c87..ae264e19bb7c 100644 --- a/core/java/android/bluetooth/BluetoothPbap.java +++ b/core/java/android/bluetooth/BluetoothPbap.java @@ -17,6 +17,7 @@ package android.bluetooth; import android.annotation.SdkConstant; +import android.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -281,6 +282,7 @@ public class BluetoothPbap implements BluetoothProfile { */ // TODO: This is currently being used by SettingsLib and will be used in the future. // TODO: Must specify target device. Implement this in the service. + @UnsupportedAppUsage public boolean disconnect(BluetoothDevice device) { log("disconnect()"); final IBluetoothPbap service = mService; diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java index 6aeb94da1175..9777b5cc6cdc 100644 --- a/core/java/android/bluetooth/BluetoothProfile.java +++ b/core/java/android/bluetooth/BluetoothProfile.java @@ -20,6 +20,7 @@ package android.bluetooth; import android.Manifest; import android.annotation.RequiresPermission; import android.annotation.SystemApi; +import android.annotation.UnsupportedAppUsage; import java.util.List; @@ -85,6 +86,7 @@ public interface BluetoothProfile { * * @hide */ + @UnsupportedAppUsage int PAN = 5; /** @@ -122,6 +124,7 @@ public interface BluetoothProfile { * * @hide */ + @UnsupportedAppUsage int A2DP_SINK = 11; /** @@ -129,6 +132,7 @@ public interface BluetoothProfile { * * @hide */ + @UnsupportedAppUsage int AVRCP_CONTROLLER = 12; /** @@ -192,6 +196,7 @@ public interface BluetoothProfile { * * @hide **/ + @UnsupportedAppUsage int PRIORITY_AUTO_CONNECT = 1000; /** @@ -217,6 +222,7 @@ public interface BluetoothProfile { * * @hide */ + @UnsupportedAppUsage int PRIORITY_UNDEFINED = -1; /** diff --git a/core/java/android/bluetooth/BluetoothSap.java b/core/java/android/bluetooth/BluetoothSap.java index c51e39a74186..1b732062f614 100644 --- a/core/java/android/bluetooth/BluetoothSap.java +++ b/core/java/android/bluetooth/BluetoothSap.java @@ -16,6 +16,7 @@ package android.bluetooth; +import android.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -280,6 +281,7 @@ public final class BluetoothSap implements BluetoothProfile { * @return false on error, true otherwise * @hide */ + @UnsupportedAppUsage public boolean disconnect(BluetoothDevice device) { if (DBG) log("disconnect(" + device + ")"); final IBluetoothSap service = mService; diff --git a/core/java/android/bluetooth/BluetoothServerSocket.java b/core/java/android/bluetooth/BluetoothServerSocket.java index ebb7f187aea5..ba4b5a566799 100644 --- a/core/java/android/bluetooth/BluetoothServerSocket.java +++ b/core/java/android/bluetooth/BluetoothServerSocket.java @@ -16,6 +16,7 @@ package android.bluetooth; +import android.annotation.UnsupportedAppUsage; import android.os.Handler; import android.os.ParcelUuid; import android.util.Log; @@ -69,6 +70,7 @@ public final class BluetoothServerSocket implements Closeable { private static final String TAG = "BluetoothServerSocket"; private static final boolean DBG = false; + @UnsupportedAppUsage /*package*/ final BluetoothSocket mSocket; private Handler mHandler; private int mMessage; diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java index 09a5b593e521..780f896139f9 100644 --- a/core/java/android/bluetooth/BluetoothSocket.java +++ b/core/java/android/bluetooth/BluetoothSocket.java @@ -16,6 +16,7 @@ package android.bluetooth; +import android.annotation.UnsupportedAppUsage; import android.net.LocalSocket; import android.os.ParcelFileDescriptor; import android.os.ParcelUuid; @@ -110,6 +111,7 @@ public final class BluetoothSocket implements Closeable { public static final int TYPE_L2CAP_LE = 4; /*package*/ static final int EBADFD = 77; + @UnsupportedAppUsage /*package*/ static final int EADDRINUSE = 98; /*package*/ static final int SEC_FLAG_ENCRYPT = 1; @@ -129,10 +131,13 @@ public final class BluetoothSocket implements Closeable { private boolean mExcludeSdp = false; /* when true no SPP SDP record will be created */ private boolean mAuthMitm = false; /* when true Man-in-the-middle protection will be enabled*/ private boolean mMin16DigitPin = false; /* Minimum 16 digit pin for sec mode 2 connections */ + @UnsupportedAppUsage private ParcelFileDescriptor mPfd; + @UnsupportedAppUsage private LocalSocket mSocket; private InputStream mSocketIS; private OutputStream mSocketOS; + @UnsupportedAppUsage private int mPort; /* RFCOMM channel or L2CAP psm */ private int mFd; private String mServiceName; @@ -517,6 +522,7 @@ public final class BluetoothSocket implements Closeable { * * @throws IOException if an i/o error occurs. */ + @UnsupportedAppUsage /*package*/ void flush() throws IOException { if (mSocketOS == null) throw new IOException("flush is called on null OutputStream"); if (VDBG) Log.d(TAG, "flush: " + mSocketOS); diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java index 605dbd21993f..fdbfec00e627 100644 --- a/core/java/android/bluetooth/BluetoothUuid.java +++ b/core/java/android/bluetooth/BluetoothUuid.java @@ -16,6 +16,7 @@ package android.bluetooth; +import android.annotation.UnsupportedAppUsage; import android.os.ParcelUuid; import java.nio.ByteBuffer; @@ -37,16 +38,20 @@ public final class BluetoothUuid { * The following 128 bit values are calculated as: * uuid * 2^96 + BASE_UUID */ + @UnsupportedAppUsage public static final ParcelUuid AudioSink = ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"); public static final ParcelUuid AudioSource = ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB"); + @UnsupportedAppUsage public static final ParcelUuid AdvAudioDist = ParcelUuid.fromString("0000110D-0000-1000-8000-00805F9B34FB"); + @UnsupportedAppUsage public static final ParcelUuid HSP = ParcelUuid.fromString("00001108-0000-1000-8000-00805F9B34FB"); public static final ParcelUuid HSP_AG = ParcelUuid.fromString("00001112-0000-1000-8000-00805F9B34FB"); + @UnsupportedAppUsage public static final ParcelUuid Handsfree = ParcelUuid.fromString("0000111E-0000-1000-8000-00805F9B34FB"); public static final ParcelUuid Handsfree_AG = @@ -55,20 +60,24 @@ public final class BluetoothUuid { ParcelUuid.fromString("0000110E-0000-1000-8000-00805F9B34FB"); public static final ParcelUuid AvrcpTarget = ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB"); + @UnsupportedAppUsage public static final ParcelUuid ObexObjectPush = ParcelUuid.fromString("00001105-0000-1000-8000-00805f9b34fb"); public static final ParcelUuid Hid = ParcelUuid.fromString("00001124-0000-1000-8000-00805f9b34fb"); + @UnsupportedAppUsage public static final ParcelUuid Hogp = ParcelUuid.fromString("00001812-0000-1000-8000-00805f9b34fb"); public static final ParcelUuid PANU = ParcelUuid.fromString("00001115-0000-1000-8000-00805F9B34FB"); + @UnsupportedAppUsage public static final ParcelUuid NAP = ParcelUuid.fromString("00001116-0000-1000-8000-00805F9B34FB"); public static final ParcelUuid BNEP = ParcelUuid.fromString("0000000f-0000-1000-8000-00805F9B34FB"); public static final ParcelUuid PBAP_PCE = ParcelUuid.fromString("0000112e-0000-1000-8000-00805F9B34FB"); + @UnsupportedAppUsage public static final ParcelUuid PBAP_PSE = ParcelUuid.fromString("0000112f-0000-1000-8000-00805F9B34FB"); public static final ParcelUuid MAP = @@ -92,10 +101,12 @@ public final class BluetoothUuid { /** Length of bytes for 128 bit UUID */ public static final int UUID_BYTES_128_BIT = 16; + @UnsupportedAppUsage public static final ParcelUuid[] RESERVED_UUIDS = { AudioSink, AudioSource, AdvAudioDist, HSP, Handsfree, AvrcpController, AvrcpTarget, ObexObjectPush, PANU, NAP, MAP, MNS, MAS, SAP}; + @UnsupportedAppUsage public static boolean isAudioSource(ParcelUuid uuid) { return uuid.equals(AudioSource); } @@ -104,6 +115,7 @@ public final class BluetoothUuid { return uuid.equals(AudioSink); } + @UnsupportedAppUsage public static boolean isAdvAudioDist(ParcelUuid uuid) { return uuid.equals(AdvAudioDist); } @@ -120,6 +132,7 @@ public final class BluetoothUuid { return uuid.equals(AvrcpController); } + @UnsupportedAppUsage public static boolean isAvrcpTarget(ParcelUuid uuid) { return uuid.equals(AvrcpTarget); } @@ -162,6 +175,7 @@ public final class BluetoothUuid { * @param uuidArray - Array of ParcelUuids * @param uuid */ + @UnsupportedAppUsage public static boolean isUuidPresent(ParcelUuid[] uuidArray, ParcelUuid uuid) { if ((uuidArray == null || uuidArray.length == 0) && uuid == null) { return true; @@ -183,6 +197,7 @@ public final class BluetoothUuid { * @param uuidA - List of ParcelUuids * @param uuidB - List of ParcelUuids */ + @UnsupportedAppUsage public static boolean containsAnyUuid(ParcelUuid[] uuidA, ParcelUuid[] uuidB) { if (uuidA == null && uuidB == null) return true; @@ -330,6 +345,7 @@ public final class BluetoothUuid { * @param parcelUuid * @return true if the parcelUuid can be converted to 16 bit uuid, false otherwise. */ + @UnsupportedAppUsage public static boolean is16BitUuid(ParcelUuid parcelUuid) { UUID uuid = parcelUuid.getUuid(); if (uuid.getLeastSignificantBits() != BASE_UUID.getUuid().getLeastSignificantBits()) { @@ -345,6 +361,7 @@ public final class BluetoothUuid { * @param parcelUuid * @return true if the parcelUuid can be converted to 32 bit uuid, false otherwise. */ + @UnsupportedAppUsage public static boolean is32BitUuid(ParcelUuid parcelUuid) { UUID uuid = parcelUuid.getUuid(); if (uuid.getLeastSignificantBits() != BASE_UUID.getUuid().getLeastSignificantBits()) { diff --git a/core/java/android/bluetooth/le/ScanRecord.java b/core/java/android/bluetooth/le/ScanRecord.java index 04dd060cae5d..07ed18d90ee6 100644 --- a/core/java/android/bluetooth/le/ScanRecord.java +++ b/core/java/android/bluetooth/le/ScanRecord.java @@ -17,6 +17,7 @@ package android.bluetooth.le; import android.annotation.Nullable; +import android.annotation.UnsupportedAppUsage; import android.bluetooth.BluetoothUuid; import android.os.ParcelUuid; import android.util.ArrayMap; @@ -174,6 +175,7 @@ public final class ScanRecord { * @param scanRecord The scan record of Bluetooth LE advertisement and/or scan response. * @hide */ + @UnsupportedAppUsage public static ScanRecord parseFromBytes(byte[] scanRecord) { if (scanRecord == null) { return null; diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java index df6447a7e51e..359726015c12 100644 --- a/core/java/android/database/sqlite/SQLiteConnection.java +++ b/core/java/android/database/sqlite/SQLiteConnection.java @@ -19,6 +19,7 @@ package android.database.sqlite; import android.database.Cursor; import android.database.CursorWindow; import android.database.DatabaseUtils; +import android.database.sqlite.SQLiteDebug.Consts; import android.database.sqlite.SQLiteDebug.DbStats; import android.os.CancellationSignal; import android.os.OperationCanceledException; @@ -34,7 +35,6 @@ import dalvik.system.CloseGuard; import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Arrays; import java.util.Date; import java.util.Map; @@ -90,8 +90,6 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen private static final String TAG = "SQLiteConnection"; private static final boolean DEBUG = false; - public static volatile boolean sLocalDebug = false; - private static final String[] EMPTY_STRING_ARRAY = new String[0]; private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; @@ -212,7 +210,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen private void open() { mConnectionPtr = nativeOpen(mConfiguration.path, mConfiguration.openFlags, mConfiguration.label, - SQLiteDebug.DEBUG_SQL_STATEMENTS, SQLiteDebug.DEBUG_SQL_TIME, + SQLiteDebug.Consts.DEBUG_SQL_STATEMENTS, SQLiteDebug.Consts.DEBUG_SQL_TIME, mConfiguration.lookasideSlotSize, mConfiguration.lookasideSlotCount); setPageSize(); setForeignKeyModeFromConfiguration(); @@ -993,10 +991,6 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen } private void bindArguments(PreparedStatement statement, Object[] bindArgs) { - if (sLocalDebug) { - Log.v(TAG, statement.mSql + " with args " + Arrays.toString(bindArgs)); - } - final int count = bindArgs != null ? bindArgs.length : 0; if (count != statement.mNumParameters) { throw new SQLiteBindOrColumnIndexOutOfRangeException( @@ -1097,7 +1091,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen printer.println(" isPrimaryConnection: " + mIsPrimaryConnection); printer.println(" onlyAllowReadOnlyOperations: " + mOnlyAllowReadOnlyOperations); - mRecentOperations.dump(printer, verbose); + mRecentOperations.dump(printer); if (verbose) { mPreparedStatementCache.dump(printer); @@ -1407,7 +1401,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen operation.mFinished = true; final long execTime = operation.mEndTime - operation.mStartTime; mPool.onStatementExecuted(execTime); - return SQLiteDebug.DEBUG_LOG_SLOW_QUERIES && SQLiteDebug.shouldLogSlowQuery( + return SQLiteDebug.Consts.DEBUG_LOG_SLOW_QUERIES && SQLiteDebug.shouldLogSlowQuery( execTime); } return false; @@ -1416,7 +1410,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen private void logOperationLocked(int cookie, String detail) { final Operation operation = getOperationLocked(cookie); StringBuilder msg = new StringBuilder(); - operation.describe(msg, false); + operation.describe(msg, true); if (detail != null) { msg.append(", ").append(detail); } @@ -1446,7 +1440,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen } } - public void dump(Printer printer, boolean verbose) { + public void dump(Printer printer) { synchronized (mOperations) { printer.println(" Most recently executed operations:"); int index = mIndex; @@ -1463,7 +1457,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen String formattedStartTime = opDF.format(new Date(operation.mStartWallTime)); msg.append(formattedStartTime); msg.append("] "); - operation.describe(msg, verbose); + operation.describe(msg, false); // Never dump bingargs in a bugreport printer.println(msg.toString()); if (index > 0) { @@ -1498,7 +1492,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen public Exception mException; public int mCookie; - public void describe(StringBuilder msg, boolean verbose) { + public void describe(StringBuilder msg, boolean allowBindArgsLog) { msg.append(mKind); if (mFinished) { msg.append(" took ").append(mEndTime - mStartTime).append("ms"); @@ -1510,7 +1504,8 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen if (mSql != null) { msg.append(", sql=\"").append(trimSqlForDisplay(mSql)).append("\""); } - if (verbose && mBindArgs != null && mBindArgs.size() != 0) { + if (allowBindArgsLog && Consts.DEBUG_LOG_BIND_ARGS + && mBindArgs != null && mBindArgs.size() != 0) { msg.append(", bindArgs=["); final int count = mBindArgs.size(); for (int i = 0; i < count; i++) { diff --git a/core/java/android/database/sqlite/SQLiteDebug.java b/core/java/android/database/sqlite/SQLiteDebug.java index d39252130d4b..790af6a370ed 100644 --- a/core/java/android/database/sqlite/SQLiteDebug.java +++ b/core/java/android/database/sqlite/SQLiteDebug.java @@ -18,6 +18,7 @@ package android.database.sqlite; import android.annotation.TestApi; import android.os.Build; +import android.os.Process; import android.os.SystemProperties; import android.util.Log; import android.util.Printer; @@ -34,35 +35,53 @@ public final class SQLiteDebug { private static native void nativeGetPagerStats(PagerStats stats); /** - * Controls the printing of informational SQL log messages. + * Inner class to avoid getting the value frozen in zygote. * - * Enable using "adb shell setprop log.tag.SQLiteLog VERBOSE". + * {@hide} */ - public static final boolean DEBUG_SQL_LOG = - Log.isLoggable("SQLiteLog", Log.VERBOSE); + public static final class Consts { + /** + * Controls the printing of informational SQL log messages. + * + * Enable using "adb shell setprop log.tag.SQLiteLog VERBOSE". + */ + public static final boolean DEBUG_SQL_LOG = + Log.isLoggable("SQLiteLog", Log.VERBOSE); - /** - * Controls the printing of SQL statements as they are executed. - * - * Enable using "adb shell setprop log.tag.SQLiteStatements VERBOSE". - */ - public static final boolean DEBUG_SQL_STATEMENTS = - Log.isLoggable("SQLiteStatements", Log.VERBOSE); + /** + * Controls the printing of SQL statements as they are executed. + * + * Enable using "adb shell setprop log.tag.SQLiteStatements VERBOSE". + */ + public static final boolean DEBUG_SQL_STATEMENTS = + Log.isLoggable("SQLiteStatements", Log.VERBOSE); - /** - * Controls the printing of wall-clock time taken to execute SQL statements - * as they are executed. - * - * Enable using "adb shell setprop log.tag.SQLiteTime VERBOSE". - */ - public static final boolean DEBUG_SQL_TIME = - Log.isLoggable("SQLiteTime", Log.VERBOSE); + /** + * Controls the printing of wall-clock time taken to execute SQL statements + * as they are executed. + * + * Enable using "adb shell setprop log.tag.SQLiteTime VERBOSE". + */ + public static final boolean DEBUG_SQL_TIME = + Log.isLoggable("SQLiteTime", Log.VERBOSE); - /** - * True to enable database performance testing instrumentation. - * @hide - */ - public static final boolean DEBUG_LOG_SLOW_QUERIES = Build.IS_DEBUGGABLE; + + /** + * True to enable database performance testing instrumentation. + */ + public static final boolean DEBUG_LOG_SLOW_QUERIES = Build.IS_DEBUGGABLE; + + private static final String SLOW_QUERY_THRESHOLD_PROP = "db.log.slow_query_threshold"; + + private static final String SLOW_QUERY_THRESHOLD_UID_PROP = + SLOW_QUERY_THRESHOLD_PROP + "." + Process.myUid(); + + /** + * Whether to log bind args in slow query log or not. + */ + public static final boolean DEBUG_LOG_BIND_ARGS = Build.IS_DEBUGGABLE + && SystemProperties.getBoolean("db.log.bindargs", false); + } private SQLiteDebug() { } @@ -75,14 +94,19 @@ public final class SQLiteDebug { * be considered slow. If the value does not exist or is negative, then no queries will * be considered slow. * + * To enable it for a specific UID, "db.log.slow_query_threshold.UID" could also be used. + * * This value can be changed dynamically while the system is running. * For example, "adb shell setprop db.log.slow_query_threshold 200" will * log all queries that take 200ms or longer to run. * @hide */ - public static final boolean shouldLogSlowQuery(long elapsedTimeMillis) { - int slowQueryMillis = SystemProperties.getInt("db.log.slow_query_threshold", -1); - return slowQueryMillis >= 0 && elapsedTimeMillis >= slowQueryMillis; + public static boolean shouldLogSlowQuery(long elapsedTimeMillis) { + final int slowQueryMillis = Math.min( + SystemProperties.getInt(Consts.SLOW_QUERY_THRESHOLD_PROP, Integer.MAX_VALUE), + SystemProperties.getInt(Consts.SLOW_QUERY_THRESHOLD_UID_PROP, + Integer.MAX_VALUE)); + return elapsedTimeMillis >= slowQueryMillis; } /** diff --git a/core/java/android/hardware/location/ContextHubClient.java b/core/java/android/hardware/location/ContextHubClient.java index 0a21083a0262..2335203eb100 100644 --- a/core/java/android/hardware/location/ContextHubClient.java +++ b/core/java/android/hardware/location/ContextHubClient.java @@ -102,7 +102,7 @@ public class ContextHubClient implements Closeable { /** * Sends a message to a nanoapp through the Context Hub Service. * - * This function returns TRANSACTION_SUCCESS if the message has reached the HAL, but + * This function returns RESULT_SUCCESS if the message has reached the HAL, but * does not guarantee delivery of the message to the target nanoapp. * * @param message the message object to send diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java index 6c84b63bb241..e3e2069422fc 100644 --- a/core/java/android/view/ViewPropertyAnimator.java +++ b/core/java/android/view/ViewPropertyAnimator.java @@ -109,11 +109,6 @@ public class ViewPropertyAnimator { private ValueAnimator mTempValueAnimator; /** - * A RenderThread-driven backend that may intercept startAnimation - */ - private ViewPropertyAnimatorRT mRTBackend; - - /** * This listener is the mechanism by which the underlying Animator causes changes to the * properties currently being animated, as well as the cleanup after an animation is * complete. @@ -434,9 +429,6 @@ public class ViewPropertyAnimator { mPendingOnStartAction = null; mPendingOnEndAction = null; mView.removeCallbacks(mAnimationStarter); - if (mRTBackend != null) { - mRTBackend.cancelAll(); - } } /** @@ -859,9 +851,6 @@ public class ViewPropertyAnimator { * value accordingly. */ private void startAnimation() { - if (mRTBackend != null && mRTBackend.startAnimation(this)) { - return; - } mView.setHasTransientState(true); ValueAnimator animator = ValueAnimator.ofFloat(1.0f); ArrayList<NameValuesHolder> nameValueList = diff --git a/core/java/android/view/ViewPropertyAnimatorRT.java b/core/java/android/view/ViewPropertyAnimatorRT.java deleted file mode 100644 index de96887db9fb..000000000000 --- a/core/java/android/view/ViewPropertyAnimatorRT.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2014 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.view; - -import android.animation.TimeInterpolator; -import android.view.ViewPropertyAnimator.NameValuesHolder; -import android.view.animation.Interpolator; -import android.view.animation.LinearInterpolator; - -import com.android.internal.view.animation.FallbackLUTInterpolator; - -import java.util.ArrayList; - - -/** - * This is a RenderThread driven backend for ViewPropertyAnimator. - */ -class ViewPropertyAnimatorRT { - - private static final Interpolator sLinearInterpolator = new LinearInterpolator(); - - private final View mView; - - private RenderNodeAnimator mAnimators[] = new RenderNodeAnimator[RenderNodeAnimator.LAST_VALUE + 1]; - - ViewPropertyAnimatorRT(View view) { - mView = view; - } - - /** - * @return true if ViewPropertyAnimatorRT handled the animation, - * false if ViewPropertyAnimator needs to handle it - */ - public boolean startAnimation(ViewPropertyAnimator parent) { - cancelAnimators(parent.mPendingAnimations); - if (!canHandleAnimator(parent)) { - return false; - } - doStartAnimation(parent); - return true; - } - - public void cancelAll() { - for (int i = 0; i < mAnimators.length; i++) { - if (mAnimators[i] != null) { - mAnimators[i].cancel(); - mAnimators[i] = null; - } - } - } - - private void doStartAnimation(ViewPropertyAnimator parent) { - int size = parent.mPendingAnimations.size(); - - long startDelay = parent.getStartDelay(); - long duration = parent.getDuration(); - TimeInterpolator interpolator = parent.getInterpolator(); - if (interpolator == null) { - // Documented to be LinearInterpolator in ValueAnimator.setInterpolator - interpolator = sLinearInterpolator; - } - if (!RenderNodeAnimator.isNativeInterpolator(interpolator)) { - interpolator = new FallbackLUTInterpolator(interpolator, duration); - } - for (int i = 0; i < size; i++) { - NameValuesHolder holder = parent.mPendingAnimations.get(i); - int property = RenderNodeAnimator.mapViewPropertyToRenderProperty(holder.mNameConstant); - - final float finalValue = holder.mFromValue + holder.mDeltaValue; - RenderNodeAnimator animator = new RenderNodeAnimator(property, finalValue); - animator.setStartDelay(startDelay); - animator.setDuration(duration); - animator.setInterpolator(interpolator); - animator.setTarget(mView); - animator.start(); - - mAnimators[property] = animator; - } - - parent.mPendingAnimations.clear(); - } - - private boolean canHandleAnimator(ViewPropertyAnimator parent) { - // TODO: Can we eliminate this entirely? - // If RenderNode.animatorProperties() can be toggled to point at staging - // instead then RNA can be used as the animators for software as well - // as the updateListener fallback paths. If this can be toggled - // at the top level somehow, combined with requiresUiRedraw, we could - // ensure that RT does not self-animate, allowing for safe driving of - // the animators from the UI thread using the same mechanisms - // ViewPropertyAnimator does, just with everything sitting on a single - // animator subsystem instead of multiple. - - if (parent.getUpdateListener() != null) { - return false; - } - if (parent.getListener() != null) { - // TODO support - return false; - } - if (!mView.isHardwareAccelerated()) { - // TODO handle this maybe? - return false; - } - if (parent.hasActions()) { - return false; - } - // Here goes nothing... - return true; - } - - private void cancelAnimators(ArrayList<NameValuesHolder> mPendingAnimations) { - int size = mPendingAnimations.size(); - for (int i = 0; i < size; i++) { - NameValuesHolder holder = mPendingAnimations.get(i); - int property = RenderNodeAnimator.mapViewPropertyToRenderProperty(holder.mNameConstant); - if (mAnimators[property] != null) { - mAnimators[property].cancel(); - mAnimators[property] = null; - } - } - } - -} diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 6856e2942f21..6316da57ec21 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -155,6 +155,7 @@ cc_library_shared { "android/graphics/Utils.cpp", "android/graphics/YuvToJpegEncoder.cpp", "android/graphics/fonts/Font.cpp", + "android/graphics/fonts/FontFamily.cpp", "android/graphics/pdf/PdfDocument.cpp", "android/graphics/pdf/PdfEditor.cpp", "android/graphics/pdf/PdfRenderer.cpp", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 1820888f9c0a..7fe095bbc215 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -141,6 +141,7 @@ extern int register_android_graphics_SurfaceTexture(JNIEnv* env); extern int register_android_graphics_drawable_AnimatedVectorDrawable(JNIEnv* env); extern int register_android_graphics_drawable_VectorDrawable(JNIEnv* env); extern int register_android_graphics_fonts_Font(JNIEnv* env); +extern int register_android_graphics_fonts_FontFamily(JNIEnv* env); extern int register_android_graphics_pdf_PdfDocument(JNIEnv* env); extern int register_android_graphics_pdf_PdfEditor(JNIEnv* env); extern int register_android_graphics_pdf_PdfRenderer(JNIEnv* env); @@ -1408,6 +1409,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable), REG_JNI(register_android_graphics_drawable_VectorDrawable), REG_JNI(register_android_graphics_fonts_Font), + REG_JNI(register_android_graphics_fonts_FontFamily), REG_JNI(register_android_graphics_pdf_PdfDocument), REG_JNI(register_android_graphics_pdf_PdfEditor), REG_JNI(register_android_graphics_pdf_PdfRenderer), diff --git a/core/jni/android/graphics/Camera.cpp b/core/jni/android/graphics/Camera.cpp index 76d685149ec4..da954972ab57 100644 --- a/core/jni/android/graphics/Camera.cpp +++ b/core/jni/android/graphics/Camera.cpp @@ -96,10 +96,12 @@ static void Camera_getMatrix(JNIEnv* env, jobject obj, jlong matrixHandle) { } static void Camera_applyToCanvas(JNIEnv* env, jobject obj, jlong canvasHandle) { - SkCanvas* canvas = reinterpret_cast<android::Canvas*>(canvasHandle)->asSkCanvas(); + android::Canvas* canvas = reinterpret_cast<android::Canvas*>(canvasHandle); jlong viewHandle = env->GetLongField(obj, gNativeInstanceFieldID); Sk3DView* v = reinterpret_cast<Sk3DView*>(viewHandle); - v->applyToCanvas(canvas); + SkMatrix matrix; + v->getMatrix(&matrix); + canvas->concat(matrix); } static jfloat Camera_dotWithNormal(JNIEnv* env, jobject obj, diff --git a/core/jni/android/graphics/fonts/FontFamily.cpp b/core/jni/android/graphics/fonts/FontFamily.cpp new file mode 100644 index 000000000000..4597386084a2 --- /dev/null +++ b/core/jni/android/graphics/fonts/FontFamily.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2018 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. + */ + +#define LOG_TAG "Minikin" + +#include <nativehelper/JNIHelp.h> +#include <core_jni_helpers.h> + +#include "FontUtils.h" + +#include <minikin/FontFamily.h> + +#include <memory> + +namespace android { + +struct NativeFamilyBuilder { + std::vector<minikin::Font> fonts; +}; + +static inline NativeFamilyBuilder* toBuilder(jlong ptr) { + return reinterpret_cast<NativeFamilyBuilder*>(ptr); +} + +static inline FontWrapper* toFontWrapper(jlong ptr) { + return reinterpret_cast<FontWrapper*>(ptr); +} + +static void releaseFontFamily(jlong family) { + delete reinterpret_cast<FontFamilyWrapper*>(family); +} + +// Regular JNI +static jlong FontFamily_Builder_initBuilder(JNIEnv*, jobject) { + return reinterpret_cast<jlong>(new NativeFamilyBuilder()); +} + +// Critical Native +static void FontFamily_Builder_addFont(jlong builderPtr, jlong fontPtr) { + toBuilder(builderPtr)->fonts.push_back(toFontWrapper(fontPtr)->font); +} + +// Regular JNI +static jlong FontFamily_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr) { + std::unique_ptr<NativeFamilyBuilder> builder(toBuilder(builderPtr)); + std::shared_ptr<minikin::FontFamily> family = + std::make_shared<minikin::FontFamily>(std::move(builder->fonts)); + if (family->getCoverage().length() == 0) { + // No coverage means minikin rejected given font for some reasons. + jniThrowException(env, "java/lang/IllegalArgumentException", + "Failed to create internal object. maybe invalid font data"); + return 0; + } + return reinterpret_cast<jlong>(new FontFamilyWrapper(std::move(family))); +} + +// CriticalNative +static jlong FontFamily_Builder_GetReleaseFunc() { + return reinterpret_cast<jlong>(releaseFontFamily); +} + +/////////////////////////////////////////////////////////////////////////////// + +static const JNINativeMethod gFontFamilyBuilderMethods[] = { + { "nInitBuilder", "()J", (void*) FontFamily_Builder_initBuilder }, + { "nAddFont", "(JJ)V", (void*) FontFamily_Builder_addFont }, + { "nBuild", "(J)J", (void*) FontFamily_Builder_build }, + + { "nGetReleaseNativeFamily", "()J", (void*) FontFamily_Builder_GetReleaseFunc }, +}; + +int register_android_graphics_fonts_FontFamily(JNIEnv* env) { + return RegisterMethodsOrDie(env, "android/graphics/fonts/FontFamily$Builder", + gFontFamilyBuilderMethods, NELEM(gFontFamilyBuilderMethods)); +} + +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 6a3487c16572..f9d81bad87df 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3256,6 +3256,13 @@ <permission android:name="android.permission.MODIFY_AUDIO_ROUTING" android:protectionLevel="signature|privileged" /> + <!-- Allows an application to modify what effects are applied to all audio + (matching certain criteria) from any application. + <p>Not for use by third-party applications.</p> + @hide --> + <permission android:name="android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS" + android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows an application to capture video output. <p>Not for use by third-party applications.</p> --> <permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT" diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml index 141a7435ef9f..6f7990907cf7 100644 --- a/core/res/res/values-ca/strings.xml +++ b/core/res/res/values-ca/strings.xml @@ -1289,9 +1289,9 @@ <string name="usb_power_notification_message" msgid="4647527153291917218">"S\'està carregant el dispositiu connectat. Toca per veure més opcions."</string> <string name="usb_unsupported_audio_accessory_title" msgid="3529881374464628084">"S\'ha detectat un accessori d\'àudio analògic"</string> <string name="usb_unsupported_audio_accessory_message" msgid="6309553946441565215">"El dispositiu connectat no és compatible amb aquest telèfon. Toca per obtenir més informació."</string> - <string name="adb_active_notification_title" msgid="6729044778949189918">"Depuració USB activada"</string> - <string name="adb_active_notification_message" msgid="7463062450474107752">"Toca per desactivar la depuració USB"</string> - <string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Selecciona per desactivar la depuració USB"</string> + <string name="adb_active_notification_title" msgid="6729044778949189918">"Depuració per USB activada"</string> + <string name="adb_active_notification_message" msgid="7463062450474107752">"Toca per desactivar la depuració per USB"</string> + <string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Selecciona per desactivar la depuració per USB"</string> <string name="taking_remote_bugreport_notification_title" msgid="6742483073875060934">"S\'està creant l\'informe d\'errors…"</string> <string name="share_remote_bugreport_notification_title" msgid="4987095013583691873">"Vols compartir l\'informe d\'errors?"</string> <string name="sharing_remote_bugreport_notification_title" msgid="7572089031496651372">"S\'està compartint l\'informe d\'errors…"</string> diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index 46abc80ca875..289e49e16c99 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -1335,7 +1335,7 @@ <string name="usb_unsupported_audio_accessory_message" msgid="6309553946441565215">"Připojené zařízení není s tímto telefonem kompatibilní. Klepnutím zobrazíte další informace."</string> <string name="adb_active_notification_title" msgid="6729044778949189918">"Ladění přes USB připojeno"</string> <string name="adb_active_notification_message" msgid="7463062450474107752">"Klepnutím vypnete ladění přes USB"</string> - <string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Vyberte, chcete-li zakázat ladění USB."</string> + <string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Vyberte, chcete-li zakázat ladění přes USB."</string> <string name="taking_remote_bugreport_notification_title" msgid="6742483073875060934">"Vytváření zprávy o chybě…"</string> <string name="share_remote_bugreport_notification_title" msgid="4987095013583691873">"Sdílet zprávu o chybě?"</string> <string name="sharing_remote_bugreport_notification_title" msgid="7572089031496651372">"Sdílení zprávy o chybě…"</string> @@ -1839,7 +1839,7 @@ <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Sbalit"</string> <string name="zen_mode_feature_name" msgid="5254089399895895004">"Nerušit"</string> - <string name="zen_mode_downtime_feature_name" msgid="2626974636779860146">"Období klidu"</string> + <string name="zen_mode_downtime_feature_name" msgid="2626974636779860146">"Doba klidu"</string> <string name="zen_mode_default_weeknights_name" msgid="3081318299464998143">"Večer v pracovním týdnu"</string> <string name="zen_mode_default_weekends_name" msgid="2786495801019345244">"Víkend"</string> <string name="zen_mode_default_events_name" msgid="8158334939013085363">"Událost"</string> @@ -1920,7 +1920,7 @@ <string name="app_category_maps" msgid="5878491404538024367">"Mapy a navigace"</string> <string name="app_category_productivity" msgid="3742083261781538852">"Produktivita"</string> <string name="device_storage_monitor_notification_channel" msgid="3295871267414816228">"Úložiště zařízení"</string> - <string name="adb_debugging_notification_channel_tv" msgid="5537766997350092316">"Ladění USB"</string> + <string name="adb_debugging_notification_channel_tv" msgid="5537766997350092316">"Ladění přes USB"</string> <string name="time_picker_hour_label" msgid="2979075098868106450">"hodina"</string> <string name="time_picker_minute_label" msgid="5168864173796598399">"minuta"</string> <string name="time_picker_header_text" msgid="143536825321922567">"Nastavení času"</string> diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml index 5e107ee886b3..d8067f0a3f2a 100644 --- a/core/res/res/values-fa/strings.xml +++ b/core/res/res/values-fa/strings.xml @@ -221,7 +221,7 @@ <string name="global_action_logout" msgid="935179188218826050">"پایان جلسه"</string> <string name="global_action_screenshot" msgid="8329831278085426283">"عکس صفحهنمایش"</string> <string name="bugreport_title" msgid="2667494803742548533">"گرفتن گزارش اشکال"</string> - <string name="bugreport_message" msgid="398447048750350456">"این گزارش اطلاعات مربوط به وضعیت دستگاه کنونی شما را جمعآوری میکند تا به صورت یک پیام رایانامه ارسال شود. از زمان شروع گزارش اشکال تا آماده شدن برای ارسال اندکی زمان میبرد؛ لطفاً شکیبا باشید."</string> + <string name="bugreport_message" msgid="398447048750350456">"این گزارش اطلاعات مربوط به وضعیت دستگاه کنونی شما را جمعآوری میکند تا به صورت یک پیام ایمیل ارسال شود. از زمان شروع گزارش اشکال تا آماده شدن برای ارسال اندکی زمان میبرد؛ لطفاً شکیبا باشید."</string> <string name="bugreport_option_interactive_title" msgid="8635056131768862479">"گزارش تعاملی"</string> <string name="bugreport_option_interactive_summary" msgid="229299488536107968">"در بیشتر شرایط از این گزینه استفاده کنید. به شما امکان ردیابی پیشرفت گزارش و وارد کردن جزئیات بیشتری درباره مشکل را میدهد. ممکن است برخی از بخشهایی را که کمتر استفاده شده و باعث افزایش طول زمان گزارش میشود حذف کند."</string> <string name="bugreport_option_full_title" msgid="6354382025840076439">"گزارش کامل"</string> @@ -396,7 +396,7 @@ <string name="permdesc_readCalendar" product="tablet" msgid="4993979255403945892">"این برنامه میتواند همه رویدادهای تقویم ذخیرهشده در رایانه لوحی شما را بخواند و دادههای تقویم شما را به اشتراک بگذارد یا ذخیره کند."</string> <string name="permdesc_readCalendar" product="tv" msgid="8837931557573064315">"این برنامه میتواند همه رویدادهای تقویم ذخیرهشده در تلویزیون شما را بخواند و دادههای تقویم شما را به اشتراک بگذارد یا ذخیره کند."</string> <string name="permdesc_readCalendar" product="default" msgid="4373978642145196715">"این برنامه میتواند همه رویدادهای تقویم ذخیرهشده در تلفن شما را بخواند و دادههای تقویم شما را به اشتراک بگذارد یا ذخیره کند."</string> - <string name="permlab_writeCalendar" msgid="8438874755193825647">"افزودن یا تغییر رویدادهای تقویم و ارسال رایانامه به مهمانان بدون دخالت مالک"</string> + <string name="permlab_writeCalendar" msgid="8438874755193825647">"افزودن یا تغییر رویدادهای تقویم و ارسال ایمیل به مهمانان بدون دخالت مالک"</string> <string name="permdesc_writeCalendar" product="tablet" msgid="1675270619903625982">"این برنامه میتواند در رایانه لوحی شما رویدادهای تقویم اضافه کند، آنها را حذف کند یا تغییر دهد. این برنامه میتواند پیامهایی ارسال کند که گویی از طرف مالکان تقویم هستند یا رویدادها را بدون اطلاع مالکان تغییر دهد."</string> <string name="permdesc_writeCalendar" product="tv" msgid="9017809326268135866">"این برنامه میتواند در تلویزیون شما رویدادهای تقویم اضافه کند، آنها را حذف کند یا تغییر دهد. این برنامه میتواند پیامهایی ارسال کند که گویی از طرف مالکان تقویم هستند یا رویدادها را بدون اطلاع مالکان تغییر دهد."</string> <string name="permdesc_writeCalendar" product="default" msgid="7592791790516943173">"این برنامه میتواند در تلفن شما رویدادهای تقویم اضافه کند، آنها را حذف کند یا تغییر دهد. این برنامه میتواند پیامهایی ارسال کند که گویی از طرف مالکان تقویم هستند یا رویدادها را بدون اطلاع مالکان تغییر دهد."</string> @@ -813,7 +813,7 @@ <string name="lockscreen_glogin_forgot_pattern" msgid="2588521501166032747">"بازگشایی قفل حساب"</string> <string name="lockscreen_glogin_too_many_attempts" msgid="2751368605287288808">"تلاشهای زیادی برای کشیدن الگو صورت گرفته است"</string> <string name="lockscreen_glogin_instructions" msgid="3931816256100707784">"برای بازگشایی قفل، با حساب Google خود وارد سیستم شوید."</string> - <string name="lockscreen_glogin_username_hint" msgid="8846881424106484447">"نام کاربری (رایانامه)"</string> + <string name="lockscreen_glogin_username_hint" msgid="8846881424106484447">"نام کاربری (ایمیل)"</string> <string name="lockscreen_glogin_password_hint" msgid="5958028383954738528">"گذرواژه"</string> <string name="lockscreen_glogin_submit_button" msgid="7130893694795786300">"ورود به سیستم"</string> <string name="lockscreen_glogin_invalid_input" msgid="1364051473347485908">"نام کاربر یا گذرواژه نامعتبر است."</string> @@ -1038,15 +1038,15 @@ <string name="copyUrl" msgid="2538211579596067402">"کپی URL"</string> <string name="selectTextMode" msgid="1018691815143165326">"انتخاب متن"</string> <string name="undo" msgid="7905788502491742328">"لغو"</string> - <string name="redo" msgid="7759464876566803888">"انجام مجدد"</string> + <string name="redo" msgid="7759464876566803888">"بازانجام"</string> <string name="autofill" msgid="3035779615680565188">"تکمیل خودکار"</string> <string name="textSelectionCABTitle" msgid="5236850394370820357">"انتخاب متن"</string> <string name="addToDictionary" msgid="4352161534510057874">"افزودن به واژهنامه"</string> <string name="deleteText" msgid="6979668428458199034">"حذف"</string> <string name="inputMethod" msgid="1653630062304567879">"روش ورودی"</string> <string name="editTextMenuTitle" msgid="4909135564941815494">"کنشهای متنی"</string> - <string name="email" msgid="4560673117055050403">"رایانامه"</string> - <string name="email_desc" msgid="3638665569546416795">"ارسال رایانامه به نشانی انتخابی"</string> + <string name="email" msgid="4560673117055050403">"ایمیل"</string> + <string name="email_desc" msgid="3638665569546416795">"ارسال ایمیل به نشانی انتخابی"</string> <string name="dial" msgid="1253998302767701559">"تماس"</string> <string name="dial_desc" msgid="6573723404985517250">"تماس با شماره تلفن انتخابی"</string> <string name="map" msgid="5441053548030107189">"نقشه"</string> @@ -1566,7 +1566,7 @@ <string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"پین کدها منطبق نیستند"</string> <string name="kg_login_too_many_attempts" msgid="6486842094005698475">"تلاشهای زیادی برای کشیدن الگو صورت گرفته است"</string> <string name="kg_login_instructions" msgid="1100551261265506448">"برای بازگشایی قفل، با حساب Google خود وارد سیستم شوید."</string> - <string name="kg_login_username_hint" msgid="5718534272070920364">"نام کاربری (رایانامه)"</string> + <string name="kg_login_username_hint" msgid="5718534272070920364">"نام کاربری (ایمیل)"</string> <string name="kg_login_password_hint" msgid="9057289103827298549">"گذرواژه"</string> <string name="kg_login_submit_button" msgid="5355904582674054702">"ورود به سیستم"</string> <string name="kg_login_invalid_input" msgid="5754664119319872197">"نام کاربری یا گذرواژه نامعتبر."</string> @@ -1581,9 +1581,9 @@ <string name="kg_failed_attempts_now_wiping" product="tablet" msgid="2072996269148483637">"شما به اشتباه <xliff:g id="NUMBER">%d</xliff:g> بار اقدام به باز کردن قفل رایانه لوحی کردهاید. رایانه لوحی اکنون به پیشفرض کارخانه بازنشانی میشود."</string> <string name="kg_failed_attempts_now_wiping" product="tv" msgid="4987878286750741463">"<xliff:g id="NUMBER">%d</xliff:g> دفعه به صورت نادرست سعی کردهاید قفل تلویزیون را باز کنید. اکنون تلویزیون به تنظیمات پیشفرض کارخانه بازنشانی خواهد شد."</string> <string name="kg_failed_attempts_now_wiping" product="default" msgid="4817627474419471518">"شما به اشتباه <xliff:g id="NUMBER">%d</xliff:g> بار اقدام به باز کردن قفل تلفن کردهاید. این تلفن اکنون به پیشفرض کارخانه بازنشانی میشود."</string> - <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="3253575572118914370">"شما الگوی بازگشایی قفل خود را <xliff:g id="NUMBER_0">%1$d</xliff:g> بار اشتباه کشیدهاید. بعد از <xliff:g id="NUMBER_1">%2$d</xliff:g> تلاش ناموفق، از شما خواسته میشود که با استفاده از یک حساب رایانامه قفل رایانه لوحی خود را باز کنید.\n\n لطفاً پس از <xliff:g id="NUMBER_2">%3$d</xliff:g> ثانیه دوباره امتحان کنید."</string> - <string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4224651132862313471">"الگوی بازگشاییتان را <xliff:g id="NUMBER_0">%1$d</xliff:g> دفعه به صورت نادرست رسم کردهاید. <xliff:g id="NUMBER_1">%2$d</xliff:g> پس از \n تلاش ناموفق دیگر، از شما خواسته میشود تا با استفاده از یک حساب رایانامه، قفل تلویزیونتان را باز کنید.\n پس از <xliff:g id="NUMBER_2">%3$d</xliff:g> ثانیه دوباره امتحان کنید."</string> - <string name="kg_failed_attempts_almost_at_login" product="default" msgid="1437638152015574839">"شما الگوی بازگشایی قفل خود را <xliff:g id="NUMBER_0">%1$d</xliff:g> بار اشتباه کشیدهاید. پس از <xliff:g id="NUMBER_1">%2$d</xliff:g> تلاش ناموفق، از شما خواسته میشود که با استفاده از یک حساب رایانامه قفل تلفن خود را باز کنید.\n\n لطفاً پس از <xliff:g id="NUMBER_2">%3$d</xliff:g> ثانیه دوباره امتحان کنید."</string> + <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="3253575572118914370">"شما الگوی بازگشایی قفل خود را <xliff:g id="NUMBER_0">%1$d</xliff:g> بار اشتباه کشیدهاید. بعد از <xliff:g id="NUMBER_1">%2$d</xliff:g> تلاش ناموفق، از شما خواسته میشود که با استفاده از یک حساب ایمیل قفل رایانه لوحی خود را باز کنید.\n\n لطفاً پس از <xliff:g id="NUMBER_2">%3$d</xliff:g> ثانیه دوباره امتحان کنید."</string> + <string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4224651132862313471">"الگوی بازگشاییتان را <xliff:g id="NUMBER_0">%1$d</xliff:g> دفعه به صورت نادرست رسم کردهاید. <xliff:g id="NUMBER_1">%2$d</xliff:g> پس از \n تلاش ناموفق دیگر، از شما خواسته میشود تا با استفاده از یک حساب ایمیل، قفل تلویزیونتان را باز کنید.\n پس از <xliff:g id="NUMBER_2">%3$d</xliff:g> ثانیه دوباره امتحان کنید."</string> + <string name="kg_failed_attempts_almost_at_login" product="default" msgid="1437638152015574839">"شما الگوی بازگشایی قفل خود را <xliff:g id="NUMBER_0">%1$d</xliff:g> بار اشتباه کشیدهاید. پس از <xliff:g id="NUMBER_1">%2$d</xliff:g> تلاش ناموفق، از شما خواسته میشود که با استفاده از یک حساب ایمیل قفل تلفن خود را باز کنید.\n\n لطفاً پس از <xliff:g id="NUMBER_2">%3$d</xliff:g> ثانیه دوباره امتحان کنید."</string> <string name="kg_text_message_separator" product="default" msgid="4160700433287233771">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="7899202978204438708">"حذف"</string> <string name="safe_media_volume_warning" product="default" msgid="2276318909314492312">"میزان صدا را به بالاتر از حد توصیه شده افزایش میدهید؟\n\nگوش دادن به صداهای بلند برای مدت طولانی میتواند به شنواییتان آسیب وارد کند."</string> @@ -1878,7 +1878,7 @@ <string name="autofill_save_type_address" msgid="4936707762193009542">"نشانی"</string> <string name="autofill_save_type_credit_card" msgid="7127694776265563071">"کارت اعتباری"</string> <string name="autofill_save_type_username" msgid="239040540379769562">"نام کاربری"</string> - <string name="autofill_save_type_email_address" msgid="5752949432129262174">"نشانی رایانامه"</string> + <string name="autofill_save_type_email_address" msgid="5752949432129262174">"نشانی ایمیل"</string> <string name="etws_primary_default_message_earthquake" msgid="5541962250262769193">"آرام باشید و پناهگاهی در این اطراف پیدا کنید."</string> <string name="etws_primary_default_message_tsunami" msgid="1887685943498368548">"فوراً مناطق ساحلی و محدوده رودخانه را ترک کنید و به جایی امن، مثل ارتفاعات بروید."</string> <string name="etws_primary_default_message_earthquake_and_tsunami" msgid="998797956848445862">"آرام باشید و پناهگاهی در این اطراف پیدا کنید."</string> diff --git a/graphics/java/android/graphics/Camera.java b/graphics/java/android/graphics/Camera.java index 33889410a54b..cbd4eadca30a 100644 --- a/graphics/java/android/graphics/Camera.java +++ b/graphics/java/android/graphics/Camera.java @@ -24,8 +24,6 @@ import android.annotation.UnsupportedAppUsage; * {@link Canvas}. */ public class Camera { - private Matrix mMatrix; - /** * Creates a new camera, with empty transformations. */ @@ -151,13 +149,7 @@ public class Camera { * @param canvas The Canvas to set the transform matrix onto */ public void applyToCanvas(Canvas canvas) { - if (canvas.isHardwareAccelerated()) { - if (mMatrix == null) mMatrix = new Matrix(); - getMatrix(mMatrix); - canvas.concat(mMatrix); - } else { - nativeApplyToCanvas(canvas.getNativeCanvasWrapper()); - } + nativeApplyToCanvas(canvas.getNativeCanvasWrapper()); } public native float dotWithNormal(float dx, float dy, float dz); diff --git a/graphics/java/android/graphics/fonts/Font.java b/graphics/java/android/graphics/fonts/Font.java index 9da61db94780..9d94a641f795 100644 --- a/graphics/java/android/graphics/fonts/Font.java +++ b/graphics/java/android/graphics/fonts/Font.java @@ -469,4 +469,9 @@ public class Font { public long getNativePtr() { return mNativePtr; } + + @Override + public String toString() { + return "Font {weight=" + mWeight + ", italic=" + mItalic + "}"; + } } diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java new file mode 100644 index 000000000000..74b58ea76b2f --- /dev/null +++ b/graphics/java/android/graphics/fonts/FontFamily.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2018 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.graphics.fonts; + +import android.annotation.IntRange; +import android.annotation.NonNull; + +import com.android.internal.util.Preconditions; + +import dalvik.annotation.optimization.CriticalNative; + +import libcore.util.NativeAllocationRegistry; + +import java.util.ArrayList; +import java.util.HashSet; + +/** + * A font family class can be used for creating Typeface. + * + * <p> + * A font family is a bundle of fonts for drawing text in various styles. + * For example, you can bundle regular style font and bold style font into a single font family, + * then system will select the correct style font from family for drawing. + * + * <pre> + * FontFamily family = new FontFamily.Builder(new Font.Builder("regular.ttf").build()) + * .addFont(new Font.Builder("bold.ttf").build()).build(); + * Typeface typeface = new Typeface.Builder2(family).build(); + * + * SpannableStringBuilder ssb = new SpannableStringBuilder("Hello, World."); + * ssb.setSpan(new StyleSpan(Typeface.Bold), 6, 12, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + * + * textView.setTypeface(typeface); + * textView.setText(ssb); + * </pre> + * + * In this example, "Hello, " is drawn with "regular.ttf", and "World." is drawn with "bold.ttf". + * + * If there is no font exactly matches with the text style, the system will select the closest font. + * </p> + * + */ +public class FontFamily { + private static final String TAG = "FontFamily"; + + /** + * A builder class for creating new FontFamily. + */ + public static class Builder { + private static final NativeAllocationRegistry sFamilyRegistory = + new NativeAllocationRegistry(FontFamily.class.getClassLoader(), + nGetReleaseNativeFamily(), 64); + + private final ArrayList<Font> mFonts = new ArrayList<>(); + private final HashSet<Integer> mStyleHashSet = new HashSet<>(); + + /** + * Constructs a builder. + * + * @param font a font + */ + public Builder(@NonNull Font font) { + Preconditions.checkNotNull(font, "font can not be null"); + mStyleHashSet.add(makeStyleIdentifier(font)); + mFonts.add(font); + } + + /** + * Adds different style font to the builder. + * + * System will select the font if the text style is closest to the font. + * If the same style font is already added to the builder, this method will fail with + * {@link IllegalArgumentException}. + * + * Note that system assumes all fonts bundled in FontFamily have the same coverage for the + * code points. For example, regular style font and bold style font must have the same code + * point coverage, otherwise some character may be shown as tofu. + * + * @param font a font + * @return this builder + */ + public @NonNull Builder addFont(@NonNull Font font) { + Preconditions.checkNotNull(font, "font can not be null"); + if (!mStyleHashSet.add(makeStyleIdentifier(font))) { + throw new IllegalArgumentException(font + " has already been added"); + } + mFonts.add(font); + return this; + } + + /** + * Build the font family + * @return a font family + */ + public @NonNull FontFamily build() { + final long builderPtr = nInitBuilder(); + for (int i = 0; i < mFonts.size(); ++i) { + nAddFont(builderPtr, mFonts.get(i).getNativePtr()); + } + final long ptr = nBuild(builderPtr); + final FontFamily family = new FontFamily(mFonts, ptr); + sFamilyRegistory.registerNativeAllocation(family, ptr); + return family; + } + + private static int makeStyleIdentifier(@NonNull Font font) { + return font.getWeight() | (font.isItalic() ? (1 << 16) : 0); + } + + private static native long nInitBuilder(); + @CriticalNative + private static native void nAddFont(long builderPtr, long fontPtr); + private static native long nBuild(long builderPtr); + @CriticalNative + private static native long nGetReleaseNativeFamily(); + } + + private final ArrayList<Font> mFonts; + private final long mNativePtr; + + // Use Builder instead. + private FontFamily(@NonNull ArrayList<Font> fonts, long ptr) { + mFonts = fonts; + mNativePtr = ptr; + } + + /** + * Returns a font + * + * @param index an index of the font + * @return a registered font + */ + public Font getFont(@IntRange(from = 0) int index) { + return mFonts.get(index); + } + + /** + * Returns the number of fonts in this FontFamily. + * + * @return the number of fonts registered in this family. + */ + public int getFontCount() { + return mFonts.size(); + } + + /** @hide */ + public long getNativePtr() { + return mNativePtr; + } +} diff --git a/media/java/android/media/audiofx/DefaultEffect.java b/media/java/android/media/audiofx/DefaultEffect.java new file mode 100644 index 000000000000..a919868218f8 --- /dev/null +++ b/media/java/android/media/audiofx/DefaultEffect.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2018 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.media.audiofx; + +/** + * DefaultEffect is the base class for controlling default audio effects linked into the + * Android audio framework. + * <p>DefaultEffects are effects that get attached automatically to all AudioTracks, + * AudioRecords, and MediaPlayer instances meeting some criteria. + * <p>Applications should not use the DefaultEffect class directly but one of its derived classes + * to control specific types of defaults: + * <ul> + * <li> {@link android.media.audiofx.StreamDefaultEffect}</li> + * </ul> + * <p>Creating a DefaultEffect object will register the corresponding effect engine as a default + * for the specified criteria. Whenever an audio session meets the criteria, an AudioEffect will + * be created and attached to it using the specified priority. + * @hide + */ + +public abstract class DefaultEffect { + /** + * System wide unique default effect ID. + */ + int mId; +} diff --git a/media/java/android/media/audiofx/StreamDefaultEffect.java b/media/java/android/media/audiofx/StreamDefaultEffect.java new file mode 100644 index 000000000000..9b1a21a21b0b --- /dev/null +++ b/media/java/android/media/audiofx/StreamDefaultEffect.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2018 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.media.audiofx; + +import android.annotation.RequiresPermission; +import android.app.ActivityThread; +import android.util.Log; +import java.util.UUID; + +/** + * StreamDefaultEffect is a default effect that attaches automatically to all AudioTracks and + * MediaPlayer instances of a given stream type. + * <p>see {@link android.media.audiofx.DefaultEffect} class for more details on default effects. + * @hide + */ + +public class StreamDefaultEffect extends DefaultEffect { + static { + System.loadLibrary("audioeffect_jni"); + } + + private final static String TAG = "StreamDefaultEffect-JAVA"; + + /** + * Class constructor. + * + * @param type type of effect engine to be default. This parameter is ignored if uuid is set, + * and can be set to {@link android.media.audiofx.AudioEffect#EFFECT_TYPE_NULL} + * in that case. + * @param uuid unique identifier of a particular effect implementation to be default. This + * parameter can be set to + * {@link android.media.audiofx.AudioEffect#EFFECT_TYPE_NULL}, in which case only + * the type will be used to select the effect. + * @param priority the priority level requested by the application for controlling the effect + * engine. As the same engine can be shared by several applications, this parameter + * indicates how much the requesting application needs control of effect parameters. + * The normal priority is 0, above normal is a positive number, below normal a + * negative number. + * @param streamUsage a USAGE_* constant from {@link android.media.AudioAttributes} indicating + * what streams the given effect should attach to by default. Note that similar + * usages may share defaults. + * + * @throws java.lang.IllegalArgumentException + * @throws java.lang.UnsupportedOperationException + * @throws java.lang.RuntimeException + */ + @RequiresPermission(value = android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS, + conditional = true) // Android Things uses an alternate permission. + public StreamDefaultEffect(UUID type, UUID uuid, int priority, int streamUsage) { + int[] id = new int[1]; + int initResult = native_setup(type.toString(), + uuid.toString(), + priority, + streamUsage, + ActivityThread.currentOpPackageName(), + id); + if (initResult != AudioEffect.SUCCESS) { + Log.e(TAG, "Error code " + initResult + " when initializing StreamDefaultEffect"); + switch (initResult) { + case AudioEffect.ERROR_BAD_VALUE: + throw (new IllegalArgumentException( + "Stream usage, type uuid, or implementation uuid not supported.")); + case AudioEffect.ERROR_INVALID_OPERATION: + throw (new UnsupportedOperationException( + "Effect library not loaded")); + default: + throw (new RuntimeException( + "Cannot initialize effect engine for type: " + type + + " Error: " + initResult)); + } + } + + mId = id[0]; + } + + + /** + * Releases the native StreamDefaultEffect resources. It is a good practice to + * release the default effect when done with use as control can be returned to + * other applications or the native resources released. + */ + public void release() { + native_release(mId); + } + + @Override + protected void finalize() { + release(); + } + + // --------------------------------------------------------- + // Native methods called from the Java side + // -------------------- + + private native final int native_setup(String type, + String uuid, + int priority, + int streamUsage, + String opPackageName, + int[] id); + + private native final void native_release(int id); +} diff --git a/media/jni/audioeffect/Android.bp b/media/jni/audioeffect/Android.bp index 2aca0c1f9a40..0063c11a7c64 100644 --- a/media/jni/audioeffect/Android.bp +++ b/media/jni/audioeffect/Android.bp @@ -3,6 +3,7 @@ cc_library_shared { srcs: [ "android_media_AudioEffect.cpp", + "android_media_StreamDefaultEffect.cpp", "android_media_Visualizer.cpp", ], diff --git a/media/jni/audioeffect/android_media_AudioEffect.cpp b/media/jni/audioeffect/android_media_AudioEffect.cpp index 51b080a334d6..d3ba9f2076dd 100644 --- a/media/jni/audioeffect/android_media_AudioEffect.cpp +++ b/media/jni/audioeffect/android_media_AudioEffect.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#include "android_media_AudioEffect.h" + #include <stdio.h> //#define LOG_NDEBUG 0 @@ -71,7 +73,7 @@ class AudioEffectJniStorage { }; -static jint translateError(int code) { +jint AudioEffectJni::translateNativeErrorToJava(int code) { switch(code) { case NO_ERROR: return AUDIOEFFECT_SUCCESS; @@ -81,6 +83,10 @@ static jint translateError(int code) { return AUDIOEFFECT_ERROR_NO_INIT; case BAD_VALUE: return AUDIOEFFECT_ERROR_BAD_VALUE; + case NAME_NOT_FOUND: + // Name not found means the client tried to create an effect not found on the system, + // which is a form of bad value. + return AUDIOEFFECT_ERROR_BAD_VALUE; case INVALID_OPERATION: return AUDIOEFFECT_ERROR_INVALID_OPERATION; case NO_MEMORY: @@ -359,7 +365,7 @@ android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_t goto setup_failure; } - lStatus = translateError(lpAudioEffect->initCheck()); + lStatus = AudioEffectJni::translateNativeErrorToJava(lpAudioEffect->initCheck()); if (lStatus != AUDIOEFFECT_SUCCESS && lStatus != AUDIOEFFECT_ERROR_ALREADY_EXISTS) { ALOGE("AudioEffect initCheck failed %d", lStatus); goto setup_failure; @@ -495,7 +501,7 @@ android_media_AudioEffect_native_setEnabled(JNIEnv *env, jobject thiz, jboolean return AUDIOEFFECT_ERROR_NO_INIT; } - return (jint) translateError(lpAudioEffect->setEnabled(enabled)); + return AudioEffectJni::translateNativeErrorToJava(lpAudioEffect->setEnabled(enabled)); } static jboolean @@ -590,7 +596,7 @@ setParameter_Exit: if (lpValue != NULL) { env->ReleasePrimitiveArrayCritical(pJavaValue, lpValue, 0); } - return (jint) translateError(lStatus); + return AudioEffectJni::translateNativeErrorToJava(lStatus); } static jint @@ -658,7 +664,7 @@ getParameter_Exit: if (lStatus == NO_ERROR) { return vsize; } - return (jint) translateError(lStatus); + return AudioEffectJni::translateNativeErrorToJava(lStatus); } static jint android_media_AudioEffect_native_command(JNIEnv *env, jobject thiz, @@ -697,11 +703,12 @@ static jint android_media_AudioEffect_native_command(JNIEnv *env, jobject thiz, } } - lStatus = translateError(lpAudioEffect->command((uint32_t)cmdCode, - (uint32_t)cmdSize, - pCmdData, - (uint32_t *)&replySize, - pReplyData)); + lStatus = AudioEffectJni::translateNativeErrorToJava( + lpAudioEffect->command((uint32_t)cmdCode, + (uint32_t)cmdSize, + pCmdData, + (uint32_t *)&replySize, + pReplyData)); command_Exit: @@ -900,6 +907,7 @@ static const JNINativeMethod gMethods[] = { // ---------------------------------------------------------------------------- +extern int register_android_media_StreamDefaultEffect(JNIEnv *env); extern int register_android_media_visualizer(JNIEnv *env); int register_android_media_AudioEffect(JNIEnv *env) @@ -924,6 +932,11 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved __unused) goto bail; } + if (register_android_media_StreamDefaultEffect(env) < 0) { + ALOGE("ERROR: StreamDefaultEffect native registration failed\n"); + goto bail; + } + if (register_android_media_visualizer(env) < 0) { ALOGE("ERROR: Visualizer native registration failed\n"); goto bail; @@ -935,4 +948,3 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved __unused) bail: return result; } - diff --git a/media/jni/audioeffect/android_media_AudioEffect.h b/media/jni/audioeffect/android_media_AudioEffect.h new file mode 100644 index 000000000000..1d7d75d0b1b8 --- /dev/null +++ b/media/jni/audioeffect/android_media_AudioEffect.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2018 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. + */ + +#ifndef _ANDROID_MEDIA_AUDIO_EFFECT_H_ +#define _ANDROID_MEDIA_AUDIO_EFFECT_H_ + +#include <jni.h> + +namespace android { + +class AudioEffectJni { +public: + // Convert from native error code to AudioEffect.java error code. + static jint translateNativeErrorToJava(int code); +private: + // Static methods only, so private constructor. + AudioEffectJni() = default; +}; + +} // namespace android + +#endif // _ANDROID_MEDIA_AUDIO_EFFECT_H_ diff --git a/media/jni/audioeffect/android_media_StreamDefaultEffect.cpp b/media/jni/audioeffect/android_media_StreamDefaultEffect.cpp new file mode 100644 index 000000000000..accdddbc97eb --- /dev/null +++ b/media/jni/audioeffect/android_media_StreamDefaultEffect.cpp @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2018 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "StreamDefaultEffect-JNI" + +#include <utils/Errors.h> +#include <utils/Log.h> +#include <jni.h> +#include <nativehelper/JNIHelp.h> +#include <android_runtime/AndroidRuntime.h> +#include "media/AudioEffect.h" + +#include <nativehelper/ScopedUtfChars.h> + +#include "android_media_AudioEffect.h" + +using namespace android; + +static const char* const kClassPathName = "android/media/audiofx/StreamDefaultEffect"; + +static jint android_media_StreamDefaultEffect_native_setup(JNIEnv *env, + jobject /*thiz*/, + jstring type, + jstring uuid, + jint priority, + jint streamUsage, + jstring opPackageName, + jintArray jId) +{ + ALOGV("android_media_StreamDefaultEffect_native_setup"); + status_t lStatus = NO_ERROR; + jint* nId = NULL; + const char *typeStr = NULL; + const char *uuidStr = NULL; + + ScopedUtfChars opPackageNameStr(env, opPackageName); + + if (type != NULL) { + typeStr = env->GetStringUTFChars(type, NULL); + if (typeStr == NULL) { // Out of memory + lStatus = NO_MEMORY; + jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); + goto setup_exit; + } + } + + if (uuid != NULL) { + uuidStr = env->GetStringUTFChars(uuid, NULL); + if (uuidStr == NULL) { // Out of memory + lStatus = NO_MEMORY; + jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); + goto setup_exit; + } + } + + if (typeStr == NULL && uuidStr == NULL) { + lStatus = BAD_VALUE; + goto setup_exit; + } + + nId = reinterpret_cast<jint *>(env->GetPrimitiveArrayCritical(jId, NULL)); + if (nId == NULL) { + ALOGE("setup: Error retrieving id pointer"); + lStatus = BAD_VALUE; + goto setup_exit; + } + + // create the native StreamDefaultEffect. + audio_unique_id_t id; + lStatus = AudioEffect::addStreamDefaultEffect(typeStr, + String16(opPackageNameStr.c_str()), + uuidStr, + priority, + static_cast<audio_usage_t>(streamUsage), + &id); + if (lStatus != NO_ERROR) { + ALOGE("setup: Error adding StreamDefaultEffect"); + goto setup_exit; + } + + nId[0] = static_cast<jint>(id); + +setup_exit: + // Final cleanup and return. + + if (nId != NULL) { + env->ReleasePrimitiveArrayCritical(jId, nId, 0); + nId = NULL; + } + + if (uuidStr != NULL) { + env->ReleaseStringUTFChars(uuid, uuidStr); + uuidStr = NULL; + } + + if (typeStr != NULL) { + env->ReleaseStringUTFChars(type, typeStr); + typeStr = NULL; + } + + return AudioEffectJni::translateNativeErrorToJava(lStatus); +} + +static void android_media_StreamDefaultEffect_native_release(JNIEnv */*env*/, + jobject /*thiz*/, + jint id) { + status_t lStatus = AudioEffect::removeStreamDefaultEffect(id); + if (lStatus != NO_ERROR) { + ALOGW("Error releasing StreamDefaultEffect: %d", lStatus); + } +} + +// ---------------------------------------------------------------------------- + +// Dalvik VM type signatures +static const JNINativeMethod gMethods[] = { + {"native_setup", "(Ljava/lang/String;Ljava/lang/String;IILjava/lang/String;[I)I", + (void *)android_media_StreamDefaultEffect_native_setup}, + {"native_release", "(I)V", (void *)android_media_StreamDefaultEffect_native_release}, +}; + + +// ---------------------------------------------------------------------------- + +int register_android_media_StreamDefaultEffect(JNIEnv *env) +{ + return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods)); +} diff --git a/media/mca/filterfw/java/android/filterfw/GraphEnvironment.java b/media/mca/filterfw/java/android/filterfw/GraphEnvironment.java index 5f6d45c58554..7c90b2731ff8 100644 --- a/media/mca/filterfw/java/android/filterfw/GraphEnvironment.java +++ b/media/mca/filterfw/java/android/filterfw/GraphEnvironment.java @@ -17,6 +17,7 @@ package android.filterfw; +import android.annotation.UnsupportedAppUsage; import android.content.Context; import android.filterfw.core.AsyncRunner; import android.filterfw.core.FilterGraph; @@ -83,6 +84,7 @@ public class GraphEnvironment extends MffEnvironment { /** * Create a new GraphEnvironment with default components. */ + @UnsupportedAppUsage public GraphEnvironment() { super(null); } @@ -128,6 +130,7 @@ public class GraphEnvironment extends MffEnvironment { * @param resourceId The ID of the graph resource to load. * @return A unique ID for the graph. */ + @UnsupportedAppUsage public int loadGraph(Context context, int resourceId) { // Read the file into a graph FilterGraph graph = null; @@ -180,6 +183,7 @@ public class GraphEnvironment extends MffEnvironment { MODE_SYNCHRONOUS or MODE_ASYNCHRONOUS. * @return A GraphRunner instance for this graph. */ + @UnsupportedAppUsage public GraphRunner getRunner(int graphId, int executionMode) { switch (executionMode) { case MODE_ASYNCHRONOUS: diff --git a/media/mca/filterfw/java/android/filterfw/core/Filter.java b/media/mca/filterfw/java/android/filterfw/core/Filter.java index 062b6bacc617..4f56b923f6ed 100644 --- a/media/mca/filterfw/java/android/filterfw/core/Filter.java +++ b/media/mca/filterfw/java/android/filterfw/core/Filter.java @@ -17,6 +17,7 @@ package android.filterfw.core; +import android.annotation.UnsupportedAppUsage; import android.filterfw.core.FilterContext; import android.filterfw.core.FilterPort; import android.filterfw.core.KeyValueMap; @@ -69,6 +70,7 @@ public abstract class Filter { private boolean mLogVerbose; private static final String TAG = "Filter"; + @UnsupportedAppUsage public Filter(String name) { mName = name; mFramesToRelease = new HashSet<Frame>(); @@ -81,6 +83,7 @@ public abstract class Filter { /** Tests to see if a given filter is installed on the system. Requires * full filter package name, including filterpack. */ + @UnsupportedAppUsage public static final boolean isAvailable(String filterName) { ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); Class filterClass; @@ -150,6 +153,7 @@ public abstract class Filter { port.setFrame(frame); } + @UnsupportedAppUsage public final void setInputValue(String inputName, Object value) { setInputFrame(inputName, wrapInputValue(inputName, value)); } diff --git a/media/mca/filterfw/java/android/filterfw/core/FilterContext.java b/media/mca/filterfw/java/android/filterfw/core/FilterContext.java index 3c79d1b67011..a19220ef85f8 100644 --- a/media/mca/filterfw/java/android/filterfw/core/FilterContext.java +++ b/media/mca/filterfw/java/android/filterfw/core/FilterContext.java @@ -17,6 +17,7 @@ package android.filterfw.core; +import android.annotation.UnsupportedAppUsage; import android.filterfw.core.Filter; import android.filterfw.core.Frame; import android.filterfw.core.FrameManager; @@ -36,6 +37,7 @@ public class FilterContext { private HashMap<String, Frame> mStoredFrames = new HashMap<String, Frame>(); private Set<FilterGraph> mGraphs = new HashSet<FilterGraph>(); + @UnsupportedAppUsage public FrameManager getFrameManager() { return mFrameManager; } @@ -52,6 +54,7 @@ public class FilterContext { } } + @UnsupportedAppUsage public GLEnvironment getGLEnvironment() { return mGLEnvironment; } diff --git a/media/mca/filterfw/java/android/filterfw/core/FilterGraph.java b/media/mca/filterfw/java/android/filterfw/core/FilterGraph.java index 12f789264920..e6ca11ffca3c 100644 --- a/media/mca/filterfw/java/android/filterfw/core/FilterGraph.java +++ b/media/mca/filterfw/java/android/filterfw/core/FilterGraph.java @@ -30,6 +30,7 @@ import android.filterfw.core.KeyValueMap; import android.filterpacks.base.FrameBranch; import android.filterpacks.base.NullFilter; +import android.annotation.UnsupportedAppUsage; import android.util.Log; /** @@ -75,6 +76,7 @@ public class FilterGraph { return mFilters.contains(filter); } + @UnsupportedAppUsage public Filter getFilter(String name) { return mNameMap.get(name); } @@ -160,6 +162,7 @@ public class FilterGraph { mTypeCheckMode = typeCheckMode; } + @UnsupportedAppUsage public void tearDown(FilterContext context) { if (!mFilters.isEmpty()) { flushFrames(); diff --git a/media/mca/filterfw/java/android/filterfw/core/Frame.java b/media/mca/filterfw/java/android/filterfw/core/Frame.java index 7dd078327a09..e880783247a3 100644 --- a/media/mca/filterfw/java/android/filterfw/core/Frame.java +++ b/media/mca/filterfw/java/android/filterfw/core/Frame.java @@ -17,6 +17,7 @@ package android.filterfw.core; +import android.annotation.UnsupportedAppUsage; import android.filterfw.core.FrameFormat; import android.filterfw.core.FrameManager; import android.graphics.Bitmap; @@ -54,6 +55,7 @@ public abstract class Frame { mBindingId = bindingId; } + @UnsupportedAppUsage public FrameFormat getFormat() { return mFormat; } @@ -94,6 +96,7 @@ public abstract class Frame { public abstract Object getObjectValue(); + @UnsupportedAppUsage public abstract void setInts(int[] ints); public abstract int[] getInts(); @@ -116,12 +119,15 @@ public abstract class Frame { public abstract void setBitmap(Bitmap bitmap); + @UnsupportedAppUsage public abstract Bitmap getBitmap(); + @UnsupportedAppUsage public void setTimestamp(long timestamp) { mTimestamp = timestamp; } + @UnsupportedAppUsage public long getTimestamp() { return mTimestamp; } @@ -138,6 +144,7 @@ public abstract class Frame { return mRefCount; } + @UnsupportedAppUsage public Frame release() { if (mFrameManager != null) { return mFrameManager.releaseFrame(this); diff --git a/media/mca/filterfw/java/android/filterfw/core/FrameFormat.java b/media/mca/filterfw/java/android/filterfw/core/FrameFormat.java index 8f619be30e74..eb0ff0a32c3f 100644 --- a/media/mca/filterfw/java/android/filterfw/core/FrameFormat.java +++ b/media/mca/filterfw/java/android/filterfw/core/FrameFormat.java @@ -17,6 +17,7 @@ package android.filterfw.core; +import android.annotation.UnsupportedAppUsage; import android.filterfw.core.KeyValueMap; import android.filterfw.core.MutableFrameFormat; @@ -90,6 +91,7 @@ public class FrameFormat { return mBytesPerSample / bytesPerSampleOf(mBaseType); } + @UnsupportedAppUsage public int getTarget() { return mTarget; } @@ -135,10 +137,12 @@ public class FrameFormat { return (mDimensions != null && mDimensions.length >= 1) ? mDimensions[0] : -1; } + @UnsupportedAppUsage public int getWidth() { return getLength(); } + @UnsupportedAppUsage public int getHeight() { return (mDimensions != null && mDimensions.length >= 2) ? mDimensions[1] : -1; } @@ -156,6 +160,7 @@ public class FrameFormat { return mObjectClass; } + @UnsupportedAppUsage public MutableFrameFormat mutableCopy() { MutableFrameFormat result = new MutableFrameFormat(); result.setBaseType(getBaseType()); diff --git a/media/mca/filterfw/java/android/filterfw/core/FrameManager.java b/media/mca/filterfw/java/android/filterfw/core/FrameManager.java index 8d6c4832da27..85c8fcd9787d 100644 --- a/media/mca/filterfw/java/android/filterfw/core/FrameManager.java +++ b/media/mca/filterfw/java/android/filterfw/core/FrameManager.java @@ -17,6 +17,7 @@ package android.filterfw.core; +import android.annotation.UnsupportedAppUsage; import android.filterfw.core.Frame; import android.filterfw.core.FrameFormat; import android.filterfw.core.MutableFrameFormat; @@ -28,10 +29,13 @@ public abstract class FrameManager { private FilterContext mContext; + @UnsupportedAppUsage public abstract Frame newFrame(FrameFormat format); + @UnsupportedAppUsage public abstract Frame newBoundFrame(FrameFormat format, int bindingType, long bindingId); + @UnsupportedAppUsage public Frame duplicateFrame(Frame frame) { Frame result = newFrame(frame.getFormat()); result.setDataFromFrame(frame); diff --git a/media/mca/filterfw/java/android/filterfw/core/GLEnvironment.java b/media/mca/filterfw/java/android/filterfw/core/GLEnvironment.java index 19d564c9a4d0..e25d6a7d70ab 100644 --- a/media/mca/filterfw/java/android/filterfw/core/GLEnvironment.java +++ b/media/mca/filterfw/java/android/filterfw/core/GLEnvironment.java @@ -17,6 +17,7 @@ package android.filterfw.core; +import android.annotation.UnsupportedAppUsage; import android.filterfw.core.NativeAllocatorTag; import android.graphics.SurfaceTexture; import android.os.Looper; @@ -66,6 +67,7 @@ public class GLEnvironment { } } + @UnsupportedAppUsage public boolean isActive() { return nativeIsActive(); } @@ -78,6 +80,7 @@ public class GLEnvironment { return nativeIsAnyContextActive(); } + @UnsupportedAppUsage public void activate() { if (Looper.myLooper() != null && Looper.myLooper().equals(Looper.getMainLooper())) { Log.e("FilterFramework", "Activating GL context in UI thread!"); @@ -87,12 +90,14 @@ public class GLEnvironment { } } + @UnsupportedAppUsage public void deactivate() { if (mManageContext && !nativeDeactivate()) { throw new RuntimeException("Could not deactivate GLEnvironment!"); } } + @UnsupportedAppUsage public void swapBuffers() { if (!nativeSwapBuffers()) { throw new RuntimeException("Error swapping EGL buffers!"); @@ -117,6 +122,7 @@ public class GLEnvironment { return result; } + @UnsupportedAppUsage public int registerSurfaceFromMediaRecorder(MediaRecorder mediaRecorder) { int result = nativeAddSurfaceFromMediaRecorder(mediaRecorder); if (result < 0) { @@ -126,18 +132,21 @@ public class GLEnvironment { return result; } + @UnsupportedAppUsage public void activateSurfaceWithId(int surfaceId) { if (!nativeActivateSurfaceId(surfaceId)) { throw new RuntimeException("Could not activate surface " + surfaceId + "!"); } } + @UnsupportedAppUsage public void unregisterSurfaceId(int surfaceId) { if (!nativeRemoveSurfaceId(surfaceId)) { throw new RuntimeException("Could not unregister surface " + surfaceId + "!"); } } + @UnsupportedAppUsage public void setSurfaceTimestamp(long timestamp) { if (!nativeSetSurfaceTimestamp(timestamp)) { throw new RuntimeException("Could not set timestamp for current surface!"); diff --git a/media/mca/filterfw/java/android/filterfw/core/GLFrame.java b/media/mca/filterfw/java/android/filterfw/core/GLFrame.java index 1558cc676d8b..9e3025fafb6e 100644 --- a/media/mca/filterfw/java/android/filterfw/core/GLFrame.java +++ b/media/mca/filterfw/java/android/filterfw/core/GLFrame.java @@ -17,6 +17,7 @@ package android.filterfw.core; +import android.annotation.UnsupportedAppUsage; import android.filterfw.core.Frame; import android.filterfw.core.FrameFormat; import android.filterfw.core.FrameManager; @@ -223,6 +224,7 @@ public class GLFrame extends Frame { } @Override + @UnsupportedAppUsage public void setBitmap(Bitmap bitmap) { assertFrameMutable(); assertGLEnvValid(); @@ -283,6 +285,7 @@ public class GLFrame extends Frame { setNativeViewport(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top); } + @UnsupportedAppUsage public void generateMipMap() { assertFrameMutable(); assertGLEnvValid(); @@ -291,6 +294,7 @@ public class GLFrame extends Frame { } } + @UnsupportedAppUsage public void setTextureParameter(int param, int value) { assertFrameMutable(); assertGLEnvValid(); @@ -300,6 +304,7 @@ public class GLFrame extends Frame { } } + @UnsupportedAppUsage public int getTextureId() { return getNativeTextureId(); } diff --git a/media/mca/filterfw/java/android/filterfw/core/GraphRunner.java b/media/mca/filterfw/java/android/filterfw/core/GraphRunner.java index b496c5451e4b..250cfaaba9d4 100644 --- a/media/mca/filterfw/java/android/filterfw/core/GraphRunner.java +++ b/media/mca/filterfw/java/android/filterfw/core/GraphRunner.java @@ -17,6 +17,8 @@ package android.filterfw.core; +import android.annotation.UnsupportedAppUsage; + /** * @hide */ @@ -50,6 +52,7 @@ public abstract class GraphRunner { mFilterContext = context; } + @UnsupportedAppUsage public abstract FilterGraph getGraph(); public FilterContext getContext() { @@ -81,12 +84,15 @@ public abstract class GraphRunner { } /** Starts running the graph. Will open the filters in the graph if they are not already open. */ + @UnsupportedAppUsage public abstract void run(); + @UnsupportedAppUsage public abstract void setDoneCallback(OnRunnerDoneListener listener); public abstract boolean isRunning(); /** Stops graph execution. As part of stopping, also closes the graph nodes. */ + @UnsupportedAppUsage public abstract void stop(); /** Closes the filters in a graph. Can only be called if the graph is not running. */ @@ -96,5 +102,6 @@ public abstract class GraphRunner { * Returns the last exception that happened during an asynchronous run. Returns null if * there is nothing to report. */ + @UnsupportedAppUsage public abstract Exception getError(); } diff --git a/media/mca/filterfw/java/android/filterfw/core/MutableFrameFormat.java b/media/mca/filterfw/java/android/filterfw/core/MutableFrameFormat.java index 8c789754a3f6..ae2ad99899f0 100644 --- a/media/mca/filterfw/java/android/filterfw/core/MutableFrameFormat.java +++ b/media/mca/filterfw/java/android/filterfw/core/MutableFrameFormat.java @@ -17,6 +17,7 @@ package android.filterfw.core; +import android.annotation.UnsupportedAppUsage; import android.filterfw.core.FrameFormat; import android.filterfw.core.KeyValueMap; @@ -31,6 +32,7 @@ public class MutableFrameFormat extends FrameFormat { super(); } + @UnsupportedAppUsage public MutableFrameFormat(int baseType, int target) { super(baseType, target); } @@ -44,6 +46,7 @@ public class MutableFrameFormat extends FrameFormat { mTarget = target; } + @UnsupportedAppUsage public void setBytesPerSample(int bytesPerSample) { mBytesPerSample = bytesPerSample; mSize = SIZE_UNKNOWN; @@ -61,6 +64,7 @@ public class MutableFrameFormat extends FrameFormat { mSize = SIZE_UNKNOWN; } + @UnsupportedAppUsage public void setDimensions(int width, int height) { int[] dimensions = new int[2]; dimensions[0] = width; diff --git a/media/mca/filterfw/java/android/filterfw/core/Program.java b/media/mca/filterfw/java/android/filterfw/core/Program.java index 19306480a8de..376c08554eb2 100644 --- a/media/mca/filterfw/java/android/filterfw/core/Program.java +++ b/media/mca/filterfw/java/android/filterfw/core/Program.java @@ -17,6 +17,7 @@ package android.filterfw.core; +import android.annotation.UnsupportedAppUsage; import android.filterfw.core.Frame; /** @@ -24,14 +25,17 @@ import android.filterfw.core.Frame; */ public abstract class Program { + @UnsupportedAppUsage public abstract void process(Frame[] inputs, Frame output); + @UnsupportedAppUsage public void process(Frame input, Frame output) { Frame[] inputs = new Frame[1]; inputs[0] = input; process(inputs, output); } + @UnsupportedAppUsage public abstract void setHostValue(String variableName, Object value); public abstract Object getHostValue(String variableName); diff --git a/media/mca/filterfw/java/android/filterfw/core/ShaderProgram.java b/media/mca/filterfw/java/android/filterfw/core/ShaderProgram.java index a971cb6b7a9c..f41636e7cf76 100644 --- a/media/mca/filterfw/java/android/filterfw/core/ShaderProgram.java +++ b/media/mca/filterfw/java/android/filterfw/core/ShaderProgram.java @@ -17,6 +17,7 @@ package android.filterfw.core; +import android.annotation.UnsupportedAppUsage; import android.filterfw.core.Frame; import android.filterfw.core.NativeAllocatorTag; import android.filterfw.core.Program; @@ -51,6 +52,7 @@ public class ShaderProgram extends Program { private ShaderProgram(NativeAllocatorTag tag) { } + @UnsupportedAppUsage public ShaderProgram(FilterContext context, String fragmentShader) { mGLEnvironment = getGLEnvironment(context); allocate(mGLEnvironment, null, fragmentShader); @@ -69,6 +71,7 @@ public class ShaderProgram extends Program { this.setTimer(); } + @UnsupportedAppUsage public static ShaderProgram createIdentity(FilterContext context) { ShaderProgram program = nativeCreateIdentity(getGLEnvironment(context)); program.setTimer(); @@ -85,6 +88,7 @@ public class ShaderProgram extends Program { } @Override + @UnsupportedAppUsage public void process(Frame[] inputs, Frame output) { if (mTimer.LOG_MFF_RUNNING_TIMES) { mTimer.start("glFinish"); @@ -129,6 +133,7 @@ public class ShaderProgram extends Program { } @Override + @UnsupportedAppUsage public void setHostValue(String variableName, Object value) { if (!setUniformValue(variableName, value)) { throw new RuntimeException("Error setting uniform value for variable '" + @@ -167,6 +172,7 @@ public class ShaderProgram extends Program { } } + @UnsupportedAppUsage public void setSourceRegion(Quad region) { setSourceRegion(region.p0.x, region.p0.y, region.p1.x, region.p1.y, @@ -181,6 +187,7 @@ public class ShaderProgram extends Program { region.p3.x, region.p3.y); } + @UnsupportedAppUsage public void setSourceRect(float x, float y, float width, float height) { setSourceRegion(x, y, x + width, y, x, y + height, x + width, y + height); } @@ -225,6 +232,7 @@ public class ShaderProgram extends Program { } } + @UnsupportedAppUsage public void setMaximumTileSize(int size) { mMaxTileSize = size; } diff --git a/media/mca/filterfw/java/android/filterfw/format/ImageFormat.java b/media/mca/filterfw/java/android/filterfw/format/ImageFormat.java index d57f47c7a853..ac087305287f 100644 --- a/media/mca/filterfw/java/android/filterfw/format/ImageFormat.java +++ b/media/mca/filterfw/java/android/filterfw/format/ImageFormat.java @@ -17,6 +17,7 @@ package android.filterfw.format; +import android.annotation.UnsupportedAppUsage; import android.filterfw.core.FrameFormat; import android.filterfw.core.MutableFrameFormat; import android.graphics.Bitmap; @@ -48,6 +49,7 @@ public class ImageFormat { return result; } + @UnsupportedAppUsage public static MutableFrameFormat create(int width, int height, int colorspace, @@ -59,6 +61,7 @@ public class ImageFormat { target); } + @UnsupportedAppUsage public static MutableFrameFormat create(int colorspace, int target) { return create(FrameFormat.SIZE_UNSPECIFIED, FrameFormat.SIZE_UNSPECIFIED, @@ -67,6 +70,7 @@ public class ImageFormat { target); } + @UnsupportedAppUsage public static MutableFrameFormat create(int colorspace) { return create(FrameFormat.SIZE_UNSPECIFIED, FrameFormat.SIZE_UNSPECIFIED, diff --git a/media/mca/filterfw/java/android/filterfw/geometry/Point.java b/media/mca/filterfw/java/android/filterfw/geometry/Point.java index 4682a0db85fd..d7acf12dd1de 100644 --- a/media/mca/filterfw/java/android/filterfw/geometry/Point.java +++ b/media/mca/filterfw/java/android/filterfw/geometry/Point.java @@ -17,6 +17,7 @@ package android.filterfw.geometry; +import android.annotation.UnsupportedAppUsage; import java.lang.Math; /** @@ -24,12 +25,16 @@ import java.lang.Math; */ public class Point { + @UnsupportedAppUsage public float x; + @UnsupportedAppUsage public float y; + @UnsupportedAppUsage public Point() { } + @UnsupportedAppUsage public Point(float x, float y) { this.x = x; this.y = y; diff --git a/media/mca/filterfw/java/android/filterfw/geometry/Quad.java b/media/mca/filterfw/java/android/filterfw/geometry/Quad.java index ee092fd1f650..610e5b80399d 100644 --- a/media/mca/filterfw/java/android/filterfw/geometry/Quad.java +++ b/media/mca/filterfw/java/android/filterfw/geometry/Quad.java @@ -17,6 +17,7 @@ package android.filterfw.geometry; +import android.annotation.UnsupportedAppUsage; import android.filterfw.geometry.Point; import java.lang.Float; @@ -29,14 +30,20 @@ import java.util.List; */ public class Quad { + @UnsupportedAppUsage public Point p0; + @UnsupportedAppUsage public Point p1; + @UnsupportedAppUsage public Point p2; + @UnsupportedAppUsage public Point p3; + @UnsupportedAppUsage public Quad() { } + @UnsupportedAppUsage public Quad(Point p0, Point p1, Point p2, Point p3) { this.p0 = p0; this.p1 = p1; diff --git a/packages/PackageInstaller/.gitignore b/packages/PackageInstaller/.gitignore deleted file mode 100644 index 7866e9eb7797..000000000000 --- a/packages/PackageInstaller/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -bin/ -gen/ -*.iml -.project -.classpath -project.properties diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp new file mode 100644 index 000000000000..bc06cab7aef5 --- /dev/null +++ b/packages/PackageInstaller/Android.bp @@ -0,0 +1,14 @@ +android_app { + name: "PackageInstaller", + + srcs: ["src/**/*.java"], + + static_libs: [ + "androidx.leanback_leanback", + "xz-java", + ], + + certificate: "platform", + privileged: true, + platform_apis: true, +}
\ No newline at end of file diff --git a/packages/PackageInstaller/Android.mk b/packages/PackageInstaller/Android.mk deleted file mode 100644 index f556b48f27b1..000000000000 --- a/packages/PackageInstaller/Android.mk +++ /dev/null @@ -1,24 +0,0 @@ -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_USE_AAPT2 := true - -LOCAL_MODULE_TAGS := optional - -LOCAL_SRC_FILES := \ - $(call all-java-files-under, src) - -LOCAL_STATIC_ANDROID_LIBRARIES += \ - androidx.leanback_leanback - -LOCAL_STATIC_JAVA_LIBRARIES := \ - xz-java \ - androidx.annotation_annotation - -LOCAL_PACKAGE_NAME := PackageInstaller - -LOCAL_CERTIFICATE := platform -LOCAL_PRIVILEGED_MODULE := true -LOCAL_PRIVATE_PLATFORM_APIS := true - -include $(BUILD_PACKAGE) diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml index f4b9cefc99ce..513c8624ca71 100644 --- a/packages/PackageInstaller/AndroidManifest.xml +++ b/packages/PackageInstaller/AndroidManifest.xml @@ -17,6 +17,7 @@ <application android:name=".PackageInstallerApplication" android:label="@string/app_name" + android:icon="@drawable/ic_app_icon" android:allowBackup="false" android:theme="@style/DialogWhenLarge" android:supportsRtl="true" diff --git a/packages/PackageInstaller/res/drawable/app_icon_foreground.xml b/packages/PackageInstaller/res/drawable/app_icon_foreground.xml new file mode 100644 index 000000000000..b1f40c12b9dd --- /dev/null +++ b/packages/PackageInstaller/res/drawable/app_icon_foreground.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2018 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. + --> +<inset + xmlns:android="http://schemas.android.com/apk/res/android" + android:insetTop="12dp" + android:insetRight="12dp" + android:insetBottom="12dp" + android:insetLeft="12dp"> + + <vector + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + + <path + android:fillColor="#FFFFFF" + android:pathData="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z" /> + <path + android:pathData="M0 0h24v24H0z" /> + </vector> +</inset> diff --git a/packages/PackageInstaller/res/drawable/ic_app_icon.xml b/packages/PackageInstaller/res/drawable/ic_app_icon.xml new file mode 100644 index 000000000000..82c18e07f5c4 --- /dev/null +++ b/packages/PackageInstaller/res/drawable/ic_app_icon.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2018 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. + --> + +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@*android:color/accent_device_default_light"/> + <foreground android:drawable="@drawable/app_icon_foreground"/> +</adaptive-icon>
\ No newline at end of file diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/EventResultPersister.java b/packages/PackageInstaller/src/com/android/packageinstaller/EventResultPersister.java index 0e62889c52aa..3a94fdcd252d 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/EventResultPersister.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/EventResultPersister.java @@ -16,12 +16,12 @@ package com.android.packageinstaller; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInstaller; import android.os.AsyncTask; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import android.util.AtomicFile; import android.util.Log; import android.util.SparseArray; diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallEventReceiver.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallEventReceiver.java index b7d32e9dcf93..c70d7dbcf518 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallEventReceiver.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallEventReceiver.java @@ -16,10 +16,10 @@ package com.android.packageinstaller; +import android.annotation.NonNull; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import androidx.annotation.NonNull; /** * Receives install events and perists them using a {@link EventResultPersister}. diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java index 56dc71d5fa65..5ba2d327d7d6 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java @@ -16,6 +16,7 @@ package com.android.packageinstaller; +import android.annotation.Nullable; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; @@ -28,7 +29,6 @@ import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; -import androidx.annotation.Nullable; import android.util.Log; import android.widget.TextView; diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java index 22b1336c2998..c2dd740f91e5 100755 --- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java @@ -18,6 +18,7 @@ package com.android.packageinstaller; import static android.content.pm.PackageInstaller.SessionParams.UID_UNKNOWN; +import android.annotation.Nullable; import android.app.Activity; import android.app.PendingIntent; import android.content.Intent; @@ -32,8 +33,6 @@ import android.util.Log; import android.widget.Button; import android.widget.ProgressBar; -import androidx.annotation.Nullable; - import com.android.internal.content.PackageHelper; import java.io.File; diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java index bf72b65d8fb9..1bc9dbd39b0a 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java @@ -16,6 +16,7 @@ package com.android.packageinstaller; +import android.annotation.Nullable; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; @@ -27,7 +28,6 @@ import android.content.pm.PackageManager; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; -import androidx.annotation.Nullable; import android.util.Log; import java.io.File; diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java index 96c6c2f62187..b3f11054c960 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java @@ -19,6 +19,7 @@ package com.android.packageinstaller; import static com.android.packageinstaller.PackageUtil.getMaxTargetSdkVersionForUid; import android.Manifest; +import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityManager; import android.app.AppGlobals; @@ -35,8 +36,6 @@ import android.os.Bundle; import android.os.RemoteException; import android.util.Log; -import androidx.annotation.Nullable; - /** * Select which activity is the first visible activity of the installation and forward the intent to * it. diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java index ae902d3afc78..94f6b31383bd 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java @@ -16,6 +16,7 @@ package com.android.packageinstaller; +import android.annotation.Nullable; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Intent; @@ -24,7 +25,6 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.net.Uri; import android.os.Bundle; -import androidx.annotation.Nullable; import android.util.Log; import android.widget.Button; diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/permission/ui/OverlayTouchActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/OverlayTouchActivity.java index 8aa158b28227..1fdbd97089a3 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/permission/ui/OverlayTouchActivity.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/OverlayTouchActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2018 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. @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.packageinstaller.permission.ui; +package com.android.packageinstaller; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; import android.app.Activity; import android.os.Bundle; -public class OverlayTouchActivity extends Activity { +class OverlayTouchActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { getWindow().addPrivateFlags(PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java index 83c91eaadd3c..97bafe75be90 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java @@ -17,6 +17,8 @@ package com.android.packageinstaller; import android.Manifest; +import android.annotation.NonNull; +import android.annotation.StringRes; import android.app.AlertDialog; import android.app.AppGlobals; import android.app.AppOpsManager; @@ -47,11 +49,6 @@ import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; -import androidx.annotation.NonNull; -import androidx.annotation.StringRes; - -import com.android.packageinstaller.permission.ui.OverlayTouchActivity; - import java.io.File; /** diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java index 68daed7602ac..ba4bf8a6b838 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java @@ -17,6 +17,8 @@ package com.android.packageinstaller; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.Activity; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -27,8 +29,6 @@ import android.content.res.AssetManager; import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.os.UserHandle; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import android.util.Log; import android.view.View; import android.widget.ImageView; diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/TemporaryFileManager.java b/packages/PackageInstaller/src/com/android/packageinstaller/TemporaryFileManager.java index 66e93b3100ec..f77318cf6e23 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/TemporaryFileManager.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/TemporaryFileManager.java @@ -16,11 +16,11 @@ package com.android.packageinstaller; +import android.annotation.NonNull; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.os.SystemClock; -import androidx.annotation.NonNull; import android.util.Log; import java.io.File; diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallEventReceiver.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallEventReceiver.java index ad3cd4c65aac..c3e9c23cda26 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallEventReceiver.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallEventReceiver.java @@ -16,10 +16,10 @@ package com.android.packageinstaller; +import android.annotation.NonNull; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import androidx.annotation.NonNull; /** * Receives uninstall events and persists them using a {@link EventResultPersister}. diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallFinish.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallFinish.java index 8282c8382292..5a51ac22b88f 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallFinish.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallFinish.java @@ -16,6 +16,7 @@ package com.android.packageinstaller; +import android.annotation.NonNull; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; @@ -35,7 +36,6 @@ import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; -import androidx.annotation.NonNull; import android.util.Log; import android.widget.Toast; diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java index e27186ba5e61..1c0aec18dd30 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java @@ -16,6 +16,7 @@ package com.android.packageinstaller; +import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityThread; import android.app.AlertDialog; @@ -34,7 +35,6 @@ import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.os.UserHandle; -import androidx.annotation.Nullable; import android.util.Log; import android.widget.Toast; diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java index d5df9f52d5fb..1a01dc019bf7 100755 --- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java @@ -21,6 +21,8 @@ import static android.app.AppOpsManager.MODE_ALLOWED; import static com.android.packageinstaller.PackageUtil.getMaxTargetSdkVersionForUid; import android.Manifest; +import android.annotation.NonNull; +import android.annotation.StringRes; import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityThread; @@ -52,8 +54,6 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; -import androidx.annotation.NonNull; -import androidx.annotation.StringRes; import android.util.Log; import com.android.packageinstaller.handheld.ErrorDialogFragment; diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAppProgressFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAppProgressFragment.java index 7402a7dae7f3..af6d9c58c733 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAppProgressFragment.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAppProgressFragment.java @@ -16,11 +16,11 @@ package com.android.packageinstaller.television; +import android.annotation.Nullable; import android.app.Fragment; import android.content.Intent; import android.os.Bundle; import android.provider.Settings; -import androidx.annotation.Nullable; import android.util.Log; import android.view.LayoutInflater; import android.view.View; diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java index f8674a6e136a..5e7f6d42574c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java @@ -35,7 +35,10 @@ import java.util.Set; * <p>Connection and bonding state changes affecting specific devices * are handled by {@link CachedBluetoothDeviceManager}, * {@link BluetoothEventManager}, and {@link LocalBluetoothProfileManager}. + * + * @deprecated use {@link BluetoothAdapter} instead. */ +@Deprecated public class LocalBluetoothAdapter { private static final String TAG = "LocalBluetoothAdapter"; diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java b/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java index 1cf83ac51306..2e0a94cedd4f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java @@ -24,27 +24,16 @@ import android.text.TextUtils; import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.List; public class DashboardCategory implements Parcelable { /** - * Title of the category that is shown to the user. - */ - public CharSequence title; - - /** * Key used for placing external tiles. */ public String key; /** - * Used to control display order. - */ - public int priority; - - /** * List of the category's children */ private List<Tile> mTiles = new ArrayList<>(); @@ -75,14 +64,6 @@ public class DashboardCategory implements Parcelable { mTiles.add(tile); } - public synchronized void addTile(int n, Tile tile) { - mTiles.add(n, tile); - } - - public synchronized void removeTile(Tile tile) { - mTiles.remove(tile); - } - public synchronized void removeTile(int n) { mTiles.remove(n); } @@ -99,7 +80,7 @@ public class DashboardCategory implements Parcelable { * Sort priority value for tiles in this category. */ public void sortTiles() { - Collections.sort(mTiles, TILE_COMPARATOR); + Collections.sort(mTiles, Tile.TILE_COMPARATOR); } /** @@ -136,9 +117,7 @@ public class DashboardCategory implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { - TextUtils.writeToParcel(title, dest, flags); dest.writeString(key); - dest.writeInt(priority); final int count = mTiles.size(); dest.writeInt(count); @@ -150,9 +129,7 @@ public class DashboardCategory implements Parcelable { } public void readFromParcel(Parcel in) { - title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); key = in.readString(); - priority = in.readInt(); final int count = in.readInt(); @@ -173,12 +150,4 @@ public class DashboardCategory implements Parcelable { } }; - public static final Comparator<Tile> TILE_COMPARATOR = - new Comparator<Tile>() { - @Override - public int compare(Tile lhs, Tile rhs) { - return rhs.priority - lhs.priority; - } - }; - } diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/ProfileSelectDialog.java b/packages/SettingsLib/src/com/android/settingslib/drawer/ProfileSelectDialog.java deleted file mode 100644 index c79b1466d606..000000000000 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/ProfileSelectDialog.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2015 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.settingslib.drawer; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.DialogFragment; -import android.app.FragmentManager; -import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; -import android.content.Intent; -import android.os.Bundle; -import android.os.UserHandle; -import android.os.UserManager; -import android.util.Log; - -import java.util.List; - -public class ProfileSelectDialog extends DialogFragment implements OnClickListener { - - private static final String TAG = "ProfileSelectDialog"; - private static final String ARG_SELECTED_TILE = "selectedTile"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - - private Tile mSelectedTile; - - public static void show(FragmentManager manager, Tile tile) { - ProfileSelectDialog dialog = new ProfileSelectDialog(); - Bundle args = new Bundle(); - args.putParcelable(ARG_SELECTED_TILE, tile); - dialog.setArguments(args); - dialog.show(manager, "select_profile"); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mSelectedTile = getArguments().getParcelable(ARG_SELECTED_TILE); - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - Context context = getActivity(); - AlertDialog.Builder builder = new AlertDialog.Builder(context); - UserAdapter adapter = UserAdapter.createUserAdapter(UserManager.get(context), context, - mSelectedTile.userHandle); - builder.setTitle(com.android.settingslib.R.string.choose_profile) - .setAdapter(adapter, this); - - return builder.create(); - } - - @Override - public void onClick(DialogInterface dialog, int which) { - UserHandle user = mSelectedTile.userHandle.get(which); - // Show menu on top level items. - mSelectedTile.intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); - getActivity().startActivityAsUser(mSelectedTile.intent, user); - } - - public static void updateUserHandlesIfNeeded(Context context, Tile tile) { - List<UserHandle> userHandles = tile.userHandle; - if (tile.userHandle == null || tile.userHandle.size() <= 1) { - return; - } - final UserManager userManager = UserManager.get(context); - for (int i = userHandles.size() - 1; i >= 0; i--) { - if (userManager.getUserInfo(userHandles.get(i).getIdentifier()) == null) { - if (DEBUG) { - Log.d(TAG, "Delete the user: " + userHandles.get(i).getIdentifier()); - } - userHandles.remove(i); - } - } - } -} diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/Tile.java b/packages/SettingsLib/src/com/android/settingslib/drawer/Tile.java index fe8303ed6c5e..18f94b7246a0 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/Tile.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawer/Tile.java @@ -35,6 +35,7 @@ import android.os.UserHandle; import android.text.TextUtils; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; /** @@ -75,11 +76,6 @@ public class Tile implements Parcelable { public ArrayList<UserHandle> userHandle = new ArrayList<>(); /** - * Optional additional data for use by subclasses of the activity - */ - public Bundle extras; - - /** * Category in which the tile should be placed. */ public String category; @@ -132,7 +128,6 @@ public class Tile implements Parcelable { for (int i = 0; i < N; i++) { userHandle.get(i).writeToParcel(dest, flags); } - dest.writeBundle(extras); dest.writeString(category); dest.writeInt(priority); dest.writeBundle(metaData); @@ -179,7 +174,6 @@ public class Tile implements Parcelable { for (int i = 0; i < N; i++) { userHandle.add(UserHandle.CREATOR.createFromParcel(in)); } - extras = in.readBundle(); category = in.readString(); priority = in.readInt(); metaData = in.readBundle(); @@ -216,4 +210,7 @@ public class Tile implements Parcelable { profile = (profile != null ? profile : PROFILE_ALL); return TextUtils.equals(profile, PROFILE_PRIMARY); } + + public static final Comparator<Tile> TILE_COMPARATOR = + (lhs, rhs) -> rhs.priority - lhs.priority; } diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java index 11976d7a4710..6b9a08e3a54e 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java @@ -37,8 +37,6 @@ import android.util.Pair; import androidx.annotation.VisibleForTesting; import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -76,7 +74,6 @@ public class TileUtils { private static final String IA_SETTINGS_ACTION = "com.android.settings.action.IA_SETTINGS"; - /** * Same as #EXTRA_SETTINGS_ACTION but used for the platform Settings activities. */ @@ -106,7 +103,7 @@ public class TileUtils { * The key used to get the package name of the icon resource for the preference. */ private static final String EXTRA_PREFERENCE_ICON_PACKAGE = - "com.android.settings.icon_package"; + "com.android.settings.icon_package"; /** * Name of the meta-data item that should be set in the AndroidManifest.xml @@ -199,8 +196,9 @@ public class TileUtils { /** * Build a list of DashboardCategory. + * * @param extraAction additional intent filter action to be usetileutild to build the dashboard - * categories + * categories */ public static List<DashboardCategory> getCategories(Context context, Map<Pair<String, String>, Tile> cache, String extraAction) { @@ -247,9 +245,11 @@ public class TileUtils { for (DashboardCategory category : categories) { category.sortTiles(); } - Collections.sort(categories, CATEGORY_COMPARATOR); - if (DEBUG_TIMING) Log.d(LOG_TAG, "getCategories took " - + (System.currentTimeMillis() - startTime) + " ms"); + + if (DEBUG_TIMING) { + Log.d(LOG_TAG, "getCategories took " + + (System.currentTimeMillis() - startTime) + " ms"); + } return categories; } @@ -269,13 +269,13 @@ public class TileUtils { intent.setPackage(SETTING_PKG); } getTilesForIntent(context, user, intent, addedCache, defaultCategory, outTiles, - usePriority, true, true); + usePriority, true); } public static void getTilesForIntent( Context context, UserHandle user, Intent intent, Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles, - boolean usePriority, boolean checkCategory, boolean forceTintExternalIcon) { + boolean usePriority, boolean checkCategory) { PackageManager pm = context.getPackageManager(); List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent, PackageManager.GET_META_DATA, user.getIdentifier()); @@ -308,8 +308,7 @@ public class TileUtils { tile.category = categoryKey; tile.priority = usePriority ? resolved.priority : 0; tile.metaData = activityInfo.metaData; - updateTileData(context, tile, activityInfo, activityInfo.applicationInfo, - pm, forceTintExternalIcon); + updateTileData(context, tile, activityInfo, activityInfo.applicationInfo, pm); if (DEBUG) Log.d(LOG_TAG, "Adding tile " + tile.title); addedCache.put(key, tile); } @@ -324,8 +323,7 @@ public class TileUtils { } private static boolean updateTileData(Context context, Tile tile, - ActivityInfo activityInfo, ApplicationInfo applicationInfo, PackageManager pm, - boolean forceTintExternalIcon) { + ActivityInfo activityInfo, ApplicationInfo applicationInfo, PackageManager pm) { if (applicationInfo.isSystemApp()) { boolean forceTintIcon = false; CharSequence title = null; @@ -338,8 +336,7 @@ public class TileUtils { Resources res = pm.getResourcesForApplication(applicationInfo.packageName); Bundle metaData = activityInfo.metaData; - if (forceTintExternalIcon - && !context.getPackageName().equals(applicationInfo.packageName)) { + if (!context.getPackageName().equals(applicationInfo.packageName)) { isIconTintable = true; forceTintIcon = true; } @@ -403,9 +400,10 @@ public class TileUtils { /** * Gets the icon package name and resource id from content provider. - * @param context context + * + * @param context context * @param packageName package name of the target activity - * @param uriString URI for the content provider + * @param uriString URI for the content provider * @param providerMap Maps URI authorities to providers * @return package name and resource id of the icon specified */ @@ -433,10 +431,11 @@ public class TileUtils { /** * Gets text associated with the input key from the content provider. - * @param context context - * @param uriString URI for the content provider + * + * @param context context + * @param uriString URI for the content provider * @param providerMap Maps URI authorities to providers - * @param key Key mapping to the text in bundle returned by the content provider + * @param key Key mapping to the text in bundle returned by the content provider * @return Text associated with the key, if returned by the content provider */ public static String getTextFromUri(Context context, String uriString, @@ -466,10 +465,6 @@ public class TileUtils { } } - private static String getString(Bundle bundle, String key) { - return bundle == null ? null : bundle.getString(key); - } - private static IContentProvider getProviderFromUri(Context context, Uri uri, Map<String, IContentProvider> providerMap) { if (uri == null) { @@ -496,12 +491,4 @@ public class TileUtils { } return pathSegments.get(0); } - - private static final Comparator<DashboardCategory> CATEGORY_COMPARATOR = - new Comparator<DashboardCategory>() { - @Override - public int compare(DashboardCategory lhs, DashboardCategory rhs) { - return rhs.priority - lhs.priority; - } - }; } diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/UserAdapter.java b/packages/SettingsLib/src/com/android/settingslib/drawer/UserAdapter.java deleted file mode 100644 index 8a09df2849c9..000000000000 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/UserAdapter.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (C) 2014 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.settingslib.drawer; - -import android.app.ActivityManager; - -import android.content.Context; -import android.content.pm.UserInfo; -import android.database.DataSetObserver; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.os.UserHandle; -import android.os.UserManager; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.ListAdapter; -import android.widget.SpinnerAdapter; -import android.widget.TextView; -import com.android.internal.util.UserIcons; -import com.android.settingslib.drawable.UserIconDrawable; - -import com.android.settingslib.R; - -import java.util.ArrayList; -import java.util.List; - -/** - * Adapter for a spinner that shows a list of users. - */ -public class UserAdapter implements SpinnerAdapter, ListAdapter { - /** Holder for user details */ - public static class UserDetails { - private final UserHandle mUserHandle; - private final String mName; - private final Drawable mIcon; - - public UserDetails(UserHandle userHandle, UserManager um, Context context) { - mUserHandle = userHandle; - UserInfo userInfo = um.getUserInfo(mUserHandle.getIdentifier()); - Drawable icon; - if (userInfo.isManagedProfile()) { - mName = context.getString(R.string.managed_user_title); - icon = context.getDrawable( - com.android.internal.R.drawable.ic_corp_badge); - } else { - mName = userInfo.name; - final int userId = userInfo.id; - if (um.getUserIcon(userId) != null) { - icon = new BitmapDrawable(context.getResources(), um.getUserIcon(userId)); - } else { - icon = UserIcons.getDefaultUserIcon( - context.getResources(), userId, /* light= */ false); - } - } - this.mIcon = encircle(context, icon); - } - - private static Drawable encircle(Context context, Drawable icon) { - return new UserIconDrawable(UserIconDrawable.getSizeForList(context)) - .setIconDrawable(icon).bake(); - } - } - private ArrayList<UserDetails> data; - private final LayoutInflater mInflater; - - public UserAdapter(Context context, ArrayList<UserDetails> users) { - if (users == null) { - throw new IllegalArgumentException("A list of user details must be provided"); - } - this.data = users; - mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - } - - public UserHandle getUserHandle(int position) { - if (position < 0 || position >= data.size()) { - return null; - } - return data.get(position).mUserHandle; - } - - @Override - public View getDropDownView(int position, View convertView, ViewGroup parent) { - final View row = convertView != null ? convertView : createUser(parent); - - UserDetails user = data.get(position); - ((ImageView) row.findViewById(android.R.id.icon)).setImageDrawable(user.mIcon); - ((TextView) row.findViewById(android.R.id.title)).setText(getTitle(user)); - return row; - } - - private int getTitle(UserDetails user) { - int userHandle = user.mUserHandle.getIdentifier(); - if (userHandle == UserHandle.USER_CURRENT - || userHandle == ActivityManager.getCurrentUser()) { - return R.string.category_personal; - } else { - return R.string.category_work; - } - } - - private View createUser(ViewGroup parent) { - return mInflater.inflate(R.layout.user_preference, parent, false); - } - - @Override - public void registerDataSetObserver(DataSetObserver observer) { - // We don't support observers - } - - @Override - public void unregisterDataSetObserver(DataSetObserver observer) { - // We don't support observers - } - - @Override - public int getCount() { - return data.size(); - } - - @Override - public UserDetails getItem(int position) { - return data.get(position); - } - - @Override - public long getItemId(int position) { - return data.get(position).mUserHandle.getIdentifier(); - } - - @Override - public boolean hasStableIds() { - return false; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - return getDropDownView(position, convertView, parent); - } - - @Override - public int getItemViewType(int position) { - return 0; - } - - @Override - public int getViewTypeCount() { - return 1; - } - - @Override - public boolean isEmpty() { - return data.isEmpty(); - } - - @Override - public boolean areAllItemsEnabled() { - return true; - } - - @Override - public boolean isEnabled(int position) { - return true; - } - - /** - * Creates a {@link UserAdapter} if there is more than one profile on the device. - * - * <p> The adapter can be used to populate a spinner that switches between the Settings - * app on the different profiles. - * - * @return a {@link UserAdapter} or null if there is only one profile. - */ - public static UserAdapter createUserSpinnerAdapter(UserManager userManager, - Context context) { - List<UserHandle> userProfiles = userManager.getUserProfiles(); - if (userProfiles.size() < 2) { - return null; - } - - UserHandle myUserHandle = new UserHandle(UserHandle.myUserId()); - // The first option should be the current profile - userProfiles.remove(myUserHandle); - userProfiles.add(0, myUserHandle); - - return createUserAdapter(userManager, context, userProfiles); - } - - public static UserAdapter createUserAdapter(UserManager userManager, - Context context, List<UserHandle> userProfiles) { - ArrayList<UserDetails> userDetails = new ArrayList<UserDetails>(userProfiles.size()); - final int count = userProfiles.size(); - for (int i = 0; i < count; i++) { - userDetails.add(new UserDetails(userProfiles.get(i), userManager, context)); - } - return new UserAdapter(context, userDetails); - } -} diff --git a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoader.java b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoader.java index 7ed357c42390..393006940740 100644 --- a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoader.java +++ b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoader.java @@ -16,14 +16,17 @@ package com.android.settingslib.license; +import static com.android.settingslib.license.LicenseHtmlLoaderCompat.generateHtmlFile; +import static com.android.settingslib.license.LicenseHtmlLoaderCompat.getCachedHtmlFile; +import static com.android.settingslib.license.LicenseHtmlLoaderCompat.getVaildXmlFiles; +import static com.android.settingslib.license.LicenseHtmlLoaderCompat.isCachedHtmlFileOutdated; + import android.content.Context; -import androidx.annotation.VisibleForTesting; import android.util.Log; import com.android.settingslib.utils.AsyncLoader; import java.io.File; -import java.util.ArrayList; import java.util.List; /** @@ -32,14 +35,6 @@ import java.util.List; public class LicenseHtmlLoader extends AsyncLoader<File> { private static final String TAG = "LicenseHtmlLoader"; - private static final String[] DEFAULT_LICENSE_XML_PATHS = { - "/system/etc/NOTICE.xml.gz", - "/vendor/etc/NOTICE.xml.gz", - "/odm/etc/NOTICE.xml.gz", - "/oem/etc/NOTICE.xml.gz", - "/product/etc/NOTICE.xml.gz"}; - private static final String NOTICE_HTML_FILE_NAME = "NOTICE.html"; - private Context mContext; public LicenseHtmlLoader(Context context) { @@ -63,7 +58,7 @@ public class LicenseHtmlLoader extends AsyncLoader<File> { return null; } - File cachedHtmlFile = getCachedHtmlFile(); + File cachedHtmlFile = getCachedHtmlFile(mContext); if (!isCachedHtmlFileOutdated(xmlFiles, cachedHtmlFile) || generateHtmlFile(xmlFiles, cachedHtmlFile)) { return cachedHtmlFile; @@ -72,40 +67,4 @@ public class LicenseHtmlLoader extends AsyncLoader<File> { return null; } - @VisibleForTesting - List<File> getVaildXmlFiles() { - final List<File> xmlFiles = new ArrayList(); - for (final String xmlPath : DEFAULT_LICENSE_XML_PATHS) { - File file = new File(xmlPath); - if (file.exists() && file.length() != 0) { - xmlFiles.add(file); - } - } - return xmlFiles; - } - - @VisibleForTesting - File getCachedHtmlFile() { - return new File(mContext.getCacheDir(), NOTICE_HTML_FILE_NAME); - } - - @VisibleForTesting - boolean isCachedHtmlFileOutdated(List<File> xmlFiles, File cachedHtmlFile) { - boolean outdated = true; - if (cachedHtmlFile.exists() && cachedHtmlFile.length() != 0) { - outdated = false; - for (File file : xmlFiles) { - if (cachedHtmlFile.lastModified() < file.lastModified()) { - outdated = true; - break; - } - } - } - return outdated; - } - - @VisibleForTesting - boolean generateHtmlFile(List<File> xmlFiles, File htmlFile) { - return LicenseHtmlGeneratorFromXml.generateHtml(xmlFiles, htmlFile); - } } diff --git a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java index d7c14ad66baa..d3b19031d0dc 100644 --- a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java +++ b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java @@ -25,22 +25,21 @@ import java.io.File; import java.util.ArrayList; import java.util.List; -import androidx.annotation.VisibleForTesting; - /** * LicenseHtmlLoader is a loader which loads a license html file from default license xml files. */ public class LicenseHtmlLoaderCompat extends AsyncLoaderCompat<File> { private static final String TAG = "LicenseHtmlLoaderCompat"; - private static final String[] DEFAULT_LICENSE_XML_PATHS = { + static final String[] DEFAULT_LICENSE_XML_PATHS = { "/system/etc/NOTICE.xml.gz", "/vendor/etc/NOTICE.xml.gz", "/odm/etc/NOTICE.xml.gz", - "/oem/etc/NOTICE.xml.gz"}; - private static final String NOTICE_HTML_FILE_NAME = "NOTICE.html"; + "/oem/etc/NOTICE.xml.gz", + "/product/etc/NOTICE.xml.gz"}; + static final String NOTICE_HTML_FILE_NAME = "NOTICE.html"; - private Context mContext; + private final Context mContext; public LicenseHtmlLoaderCompat(Context context) { super(context); @@ -63,7 +62,7 @@ public class LicenseHtmlLoaderCompat extends AsyncLoaderCompat<File> { return null; } - File cachedHtmlFile = getCachedHtmlFile(); + File cachedHtmlFile = getCachedHtmlFile(mContext); if (!isCachedHtmlFileOutdated(xmlFiles, cachedHtmlFile) || generateHtmlFile(xmlFiles, cachedHtmlFile)) { return cachedHtmlFile; @@ -72,8 +71,7 @@ public class LicenseHtmlLoaderCompat extends AsyncLoaderCompat<File> { return null; } - @VisibleForTesting - List<File> getVaildXmlFiles() { + static List<File> getVaildXmlFiles() { final List<File> xmlFiles = new ArrayList(); for (final String xmlPath : DEFAULT_LICENSE_XML_PATHS) { File file = new File(xmlPath); @@ -84,13 +82,11 @@ public class LicenseHtmlLoaderCompat extends AsyncLoaderCompat<File> { return xmlFiles; } - @VisibleForTesting - File getCachedHtmlFile() { - return new File(mContext.getCacheDir(), NOTICE_HTML_FILE_NAME); + static File getCachedHtmlFile(Context context) { + return new File(context.getCacheDir(), NOTICE_HTML_FILE_NAME); } - @VisibleForTesting - boolean isCachedHtmlFileOutdated(List<File> xmlFiles, File cachedHtmlFile) { + static boolean isCachedHtmlFileOutdated(List<File> xmlFiles, File cachedHtmlFile) { boolean outdated = true; if (cachedHtmlFile.exists() && cachedHtmlFile.length() != 0) { outdated = false; @@ -104,8 +100,7 @@ public class LicenseHtmlLoaderCompat extends AsyncLoaderCompat<File> { return outdated; } - @VisibleForTesting - boolean generateHtmlFile(List<File> xmlFiles, File htmlFile) { + static boolean generateHtmlFile(List<File> xmlFiles, File htmlFile) { return LicenseHtmlGeneratorFromXml.generateHtml(xmlFiles, htmlFile); } } diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/drawer/ProfileSelectDialogTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/drawer/ProfileSelectDialogTest.java deleted file mode 100644 index 63f462c25242..000000000000 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/drawer/ProfileSelectDialogTest.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2016 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.settingslib.drawer; - -import static junit.framework.Assert.assertEquals; - -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.Context; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.UserInfo; -import android.os.UserHandle; -import android.os.UserManager; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class ProfileSelectDialogTest { - - @Mock - private Context mContext; - @Mock - private UserManager mUserManager; - private static final UserHandle NORMAL_USER = UserHandle.of(1111); - private static final UserHandle REMOVED_USER = UserHandle.of(2222); - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); - final UserInfo userInfo = new UserInfo( - NORMAL_USER.getIdentifier(), "test_user", UserInfo.FLAG_RESTRICTED); - when(mUserManager.getUserInfo(NORMAL_USER.getIdentifier())).thenReturn(userInfo); - } - - @Test - public void testUpdateUserHandlesIfNeeded_Normal() { - final Tile tile = new Tile(new ActivityInfo()); - tile.intent = new Intent(); - tile.userHandle.add(NORMAL_USER); - - ProfileSelectDialog.updateUserHandlesIfNeeded(mContext, tile); - - assertEquals(tile.userHandle.size(), 1); - assertEquals(tile.userHandle.get(0).getIdentifier(), NORMAL_USER.getIdentifier()); - verify(mUserManager, never()).getUserInfo(NORMAL_USER.getIdentifier()); - } - - @Test - public void testUpdateUserHandlesIfNeeded_Remove() { - final Tile tile = new Tile(new ActivityInfo()); - tile.intent = new Intent(); - tile.userHandle.add(REMOVED_USER); - tile.userHandle.add(NORMAL_USER); - tile.userHandle.add(REMOVED_USER); - - ProfileSelectDialog.updateUserHandlesIfNeeded(mContext, tile); - - assertEquals(tile.userHandle.size(), 1); - assertEquals(tile.userHandle.get(0).getIdentifier(), NORMAL_USER.getIdentifier()); - verify(mUserManager, times(1)).getUserInfo(NORMAL_USER.getIdentifier()); - verify(mUserManager, times(2)).getUserInfo(REMOVED_USER.getIdentifier()); - } -} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java index 5f4be21771ca..0b6acde157be 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java @@ -50,7 +50,6 @@ import android.provider.Settings.Global; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Pair; -import android.widget.RemoteViews; import com.android.settingslib.SettingsLibRobolectricTestRunner; @@ -60,16 +59,12 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.robolectric.annotation.Config; -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; import java.util.ArrayList; import java.util.List; import java.util.Map; @RunWith(SettingsLibRobolectricTestRunner.class) -@Config(shadows = TileUtilsTest.TileUtilsShadowRemoteViews.class) public class TileUtilsTest { private Context mContext; @@ -114,7 +109,7 @@ public class TileUtilsTest { TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache, null /* defaultCategory */, outTiles, false /* usePriority */, - false /* checkCategory */, true /* forceTintExternalIcon */); + false /* checkCategory */); assertThat(outTiles.size()).isEqualTo(1); assertThat(outTiles.get(0).category).isEqualTo(testCategory); @@ -135,7 +130,7 @@ public class TileUtilsTest { TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache, null /* defaultCategory */, outTiles, false /* usePriority */, - false /* checkCategory */, true /* forceTintExternalIcon */); + false /* checkCategory */); assertThat(outTiles.size()).isEqualTo(1); assertThat(outTiles.get(0).key).isEqualTo(keyHint); @@ -155,7 +150,7 @@ public class TileUtilsTest { TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache, null /* defaultCategory */, outTiles, false /* usePriority */, - false /* checkCategory */, true /* forceTintExternalIcon */); + false /* checkCategory */); assertThat(outTiles.isEmpty()).isTrue(); } @@ -215,7 +210,7 @@ public class TileUtilsTest { TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache, null /* defaultCategory */, outTiles, false /* usePriority */, - false /* checkCategory */, true /* forceTintExternalIcon */); + false /* checkCategory */); assertThat(outTiles.size()).isEqualTo(1); assertThat(outTiles.get(0).title).isEqualTo("my title"); @@ -239,7 +234,7 @@ public class TileUtilsTest { TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache, null /* defaultCategory */, outTiles, false /* usePriority */, - false /* checkCategory */, true /* forceTintExternalIcon */); + false /* checkCategory */); assertThat(outTiles.size()).isEqualTo(1); assertThat(outTiles.get(0).title).isEqualTo("my localized title"); @@ -266,7 +261,7 @@ public class TileUtilsTest { TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache, null /* defaultCategory */, outTiles, false /* usePriority */, - false /* checkCategory */, true /* forceTintExternalIcon */); + false /* checkCategory */); assertThat(outTiles.size()).isEqualTo(1); assertThat(outTiles.get(0).isIconTintable).isFalse(); @@ -289,7 +284,7 @@ public class TileUtilsTest { TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache, null /* defaultCategory */, outTiles, false /* usePriority */, - false /* checkCategory */, false /* forceTintExternalIcon */); + false /* checkCategory */); assertThat(outTiles.size()).isEqualTo(1); assertThat(outTiles.get(0).isIconTintable).isTrue(); @@ -311,7 +306,7 @@ public class TileUtilsTest { // Case 1: No provider associated with the uri specified. TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache, null /* defaultCategory */, outTiles, false /* usePriority */, - false /* checkCategory */, true /* forceTintExternalIcon */); + false /* checkCategory */); assertThat(outTiles.size()).isEqualTo(1); assertThat(outTiles.get(0).getIcon(mContext).getResId()).isEqualTo(314159); @@ -329,7 +324,7 @@ public class TileUtilsTest { TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache, null /* defaultCategory */, outTiles, false /* usePriority */, - false /* checkCategory */, true /* forceTintExternalIcon */); + false /* checkCategory */); assertThat(outTiles.size()).isEqualTo(1); assertThat(outTiles.get(0).getIcon(mContext).getResId()).isEqualTo(314159); @@ -351,7 +346,7 @@ public class TileUtilsTest { TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache, null /* defaultCategory */, outTiles, false /* usePriority */, - false /* checkCategory */, true /* forceTintExternalIcon */); + false /* checkCategory */); assertThat(outTiles.size()).isEqualTo(1); } @@ -413,17 +408,4 @@ public class TileUtilsTest { info.activityInfo.metaData.putString(key, value); } } - - @Implements(RemoteViews.class) - public static class TileUtilsShadowRemoteViews { - - private Integer overrideViewId; - private CharSequence overrideText; - - @Implementation - public void setTextViewText(int viewId, CharSequence text) { - overrideViewId = viewId; - overrideText = text; - } - } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlLoaderCompatTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlLoaderCompatTest.java index f981f365ec2b..12a4e699fb76 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlLoaderCompatTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlLoaderCompatTest.java @@ -17,44 +17,43 @@ package com.android.settingslib.license; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; import android.content.Context; import com.android.settingslib.SettingsLibRobolectricTestRunner; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.annotation.Resetter; import java.io.File; import java.util.ArrayList; +import java.util.List; @RunWith(SettingsLibRobolectricTestRunner.class) +@Config(shadows = LicenseHtmlLoaderCompatTest.ShadowLicenseHtmlLoaderCompat.class) public class LicenseHtmlLoaderCompatTest { + @Mock private Context mContext; - - LicenseHtmlLoaderCompat newLicenseHtmlLoader(ArrayList<File> xmlFiles, - File cachedHtmlFile, boolean isCachedHtmlFileOutdated, - boolean generateHtmlFileSucceeded) { - LicenseHtmlLoaderCompat loader = spy(new LicenseHtmlLoaderCompat(mContext)); - doReturn(xmlFiles).when(loader).getVaildXmlFiles(); - doReturn(cachedHtmlFile).when(loader).getCachedHtmlFile(); - doReturn(isCachedHtmlFileOutdated).when(loader).isCachedHtmlFileOutdated(any(), any()); - doReturn(generateHtmlFileSucceeded).when(loader).generateHtmlFile(any(), any()); - return loader; - } + private LicenseHtmlLoaderCompat mLoader; @Before public void setUp() { MockitoAnnotations.initMocks(this); + mLoader = new LicenseHtmlLoaderCompat(mContext); + } + + @After + public void tearDown() { + ShadowLicenseHtmlLoaderCompat.reset(); } @Test @@ -63,10 +62,9 @@ public class LicenseHtmlLoaderCompatTest { xmlFiles.add(new File("test.xml")); File cachedHtmlFile = new File("test.html"); - LicenseHtmlLoaderCompat loader = newLicenseHtmlLoader(xmlFiles, cachedHtmlFile, true, true); + setupFakeData(xmlFiles, cachedHtmlFile, true, true); - assertThat(loader.loadInBackground()).isEqualTo(cachedHtmlFile); - verify(loader).generateHtmlFile(any(), any()); + assertThat(mLoader.loadInBackground()).isEqualTo(cachedHtmlFile); } @Test @@ -74,10 +72,9 @@ public class LicenseHtmlLoaderCompatTest { ArrayList<File> xmlFiles = new ArrayList(); File cachedHtmlFile = new File("test.html"); - LicenseHtmlLoaderCompat loader = newLicenseHtmlLoader(xmlFiles, cachedHtmlFile, true, true); + setupFakeData(xmlFiles, cachedHtmlFile, true, true); - assertThat(loader.loadInBackground()).isNull(); - verify(loader, never()).generateHtmlFile(any(), any()); + assertThat(mLoader.loadInBackground()).isNull(); } @Test @@ -86,11 +83,9 @@ public class LicenseHtmlLoaderCompatTest { xmlFiles.add(new File("test.xml")); File cachedHtmlFile = new File("test.html"); - LicenseHtmlLoaderCompat loader = newLicenseHtmlLoader(xmlFiles, cachedHtmlFile, false, - true); + setupFakeData(xmlFiles, cachedHtmlFile, false, true); - assertThat(loader.loadInBackground()).isEqualTo(cachedHtmlFile); - verify(loader, never()).generateHtmlFile(any(), any()); + assertThat(mLoader.loadInBackground()).isEqualTo(cachedHtmlFile); } @Test @@ -99,10 +94,56 @@ public class LicenseHtmlLoaderCompatTest { xmlFiles.add(new File("test.xml")); File cachedHtmlFile = new File("test.html"); - LicenseHtmlLoaderCompat loader = newLicenseHtmlLoader(xmlFiles, cachedHtmlFile, true, - false); + setupFakeData(xmlFiles, cachedHtmlFile, true, false); + + assertThat(mLoader.loadInBackground()).isNull(); + } + + void setupFakeData(ArrayList<File> xmlFiles, + File cachedHtmlFile, boolean isCachedHtmlFileOutdated, + boolean generateHtmlFileSucceeded) { + + ShadowLicenseHtmlLoaderCompat.sValidXmlFiles = xmlFiles; + ShadowLicenseHtmlLoaderCompat.sCachedHtmlFile = cachedHtmlFile; + ShadowLicenseHtmlLoaderCompat.sIsCachedHtmlFileOutdated = isCachedHtmlFileOutdated; + ShadowLicenseHtmlLoaderCompat.sGenerateHtmlFileSucceeded = generateHtmlFileSucceeded; + } - assertThat(loader.loadInBackground()).isNull(); - verify(loader).generateHtmlFile(any(), any()); + @Implements(LicenseHtmlLoaderCompat.class) + public static class ShadowLicenseHtmlLoaderCompat { + + + public static List<File> sValidXmlFiles; + public static File sCachedHtmlFile; + public static boolean sIsCachedHtmlFileOutdated; + public static boolean sGenerateHtmlFileSucceeded; + + @Resetter + public static void reset() { + sValidXmlFiles = null; + sCachedHtmlFile = null; + sIsCachedHtmlFileOutdated = false; + sGenerateHtmlFileSucceeded = false; + } + + @Implementation + static List<File> getVaildXmlFiles() { + return sValidXmlFiles; + } + + @Implementation + static File getCachedHtmlFile(Context context) { + return sCachedHtmlFile; + } + + @Implementation + static boolean isCachedHtmlFileOutdated(List<File> xmlFiles, File cachedHtmlFile) { + return sIsCachedHtmlFileOutdated; + } + + @Implementation + static boolean generateHtmlFile(List<File> xmlFiles, File htmlFile) { + return sGenerateHtmlFileSucceeded; + } } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlLoaderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlLoaderTest.java deleted file mode 100644 index 5095f508997e..000000000000 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlLoaderTest.java +++ /dev/null @@ -1,106 +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.settingslib.license; - -import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; - -import android.content.Context; - -import com.android.settingslib.SettingsLibRobolectricTestRunner; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.io.File; -import java.util.ArrayList; - -@RunWith(SettingsLibRobolectricTestRunner.class) -public class LicenseHtmlLoaderTest { - @Mock - private Context mContext; - - LicenseHtmlLoader newLicenseHtmlLoader(ArrayList<File> xmlFiles, - File cachedHtmlFile, boolean isCachedHtmlFileOutdated, - boolean generateHtmlFileSucceeded) { - LicenseHtmlLoader loader = spy(new LicenseHtmlLoader(mContext)); - doReturn(xmlFiles).when(loader).getVaildXmlFiles(); - doReturn(cachedHtmlFile).when(loader).getCachedHtmlFile(); - doReturn(isCachedHtmlFileOutdated).when(loader).isCachedHtmlFileOutdated(any(), any()); - doReturn(generateHtmlFileSucceeded).when(loader).generateHtmlFile(any(), any()); - return loader; - } - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - } - - @Test - public void testLoadInBackground() { - ArrayList<File> xmlFiles = new ArrayList(); - xmlFiles.add(new File("test.xml")); - File cachedHtmlFile = new File("test.html"); - - LicenseHtmlLoader loader = newLicenseHtmlLoader(xmlFiles, cachedHtmlFile, true, true); - - assertThat(loader.loadInBackground()).isEqualTo(cachedHtmlFile); - verify(loader).generateHtmlFile(any(), any()); - } - - @Test - public void testLoadInBackgroundWithNoVaildXmlFiles() { - ArrayList<File> xmlFiles = new ArrayList(); - File cachedHtmlFile = new File("test.html"); - - LicenseHtmlLoader loader = newLicenseHtmlLoader(xmlFiles, cachedHtmlFile, true, true); - - assertThat(loader.loadInBackground()).isNull(); - verify(loader, never()).generateHtmlFile(any(), any()); - } - - @Test - public void testLoadInBackgroundWithNonOutdatedCachedHtmlFile() { - ArrayList<File> xmlFiles = new ArrayList(); - xmlFiles.add(new File("test.xml")); - File cachedHtmlFile = new File("test.html"); - - LicenseHtmlLoader loader = newLicenseHtmlLoader(xmlFiles, cachedHtmlFile, false, true); - - assertThat(loader.loadInBackground()).isEqualTo(cachedHtmlFile); - verify(loader, never()).generateHtmlFile(any(), any()); - } - - @Test - public void testLoadInBackgroundWithGenerateHtmlFileFailed() { - ArrayList<File> xmlFiles = new ArrayList(); - xmlFiles.add(new File("test.xml")); - File cachedHtmlFile = new File("test.html"); - - LicenseHtmlLoader loader = newLicenseHtmlLoader(xmlFiles, cachedHtmlFile, true, false); - - assertThat(loader.loadInBackground()).isNull(); - verify(loader).generateHtmlFile(any(), any()); - } -} diff --git a/packages/SystemUI/res-keyguard/values-fa/strings.xml b/packages/SystemUI/res-keyguard/values-fa/strings.xml index 62cd4ef9be45..db7923d67928 100644 --- a/packages/SystemUI/res-keyguard/values-fa/strings.xml +++ b/packages/SystemUI/res-keyguard/values-fa/strings.xml @@ -99,8 +99,8 @@ <string name="kg_failed_attempts_almost_at_erase_profile" product="default" msgid="2162434417489128282">"<xliff:g id="NUMBER_0">%1$d</xliff:g> تلاش ناموفق برای باز کردن قفل تلفن داشتهاید. پس از <xliff:g id="NUMBER_1">%2$d</xliff:g> تلاش ناموفق دیگر، نمایه کاری پاک میشود که با آن همه دادههای نمایه حذف میشود."</string> <string name="kg_failed_attempts_now_erasing_profile" product="tablet" msgid="8966727588974691544">"<xliff:g id="NUMBER">%d</xliff:g> تلاش ناموفق برای باز کردن قفل رایانه لوحی داشتهاید. نمایه کاری پاک میشود که با آن همه دادههای نمایه حذف میشود."</string> <string name="kg_failed_attempts_now_erasing_profile" product="default" msgid="8476407539834855">"<xliff:g id="NUMBER">%d</xliff:g> تلاش ناموفق برای باز کردن قفل تلفن داشتهاید. نمایه کاری پاک میشود که با آن همه دادههای نمایه حذف میشود."</string> - <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="956706236554092172">"شما الگوی باز کردن قفل را <xliff:g id="NUMBER_0">%1$d</xliff:g> بار اشتباه کشیدهاید. بعد از <xliff:g id="NUMBER_1">%2$d</xliff:g> تلاش ناموفق، از شما خواسته میشود که با استفاده از یک حساب رایانامه قفل رایانه لوحی خود را باز کنید.\n\n لطفاً پس از <xliff:g id="NUMBER_2">%3$d</xliff:g> ثانیه دوباره امتحان کنید."</string> - <string name="kg_failed_attempts_almost_at_login" product="default" msgid="8364140853305528449">"شما الگوی باز کردن قفل را <xliff:g id="NUMBER_0">%1$d</xliff:g> بار اشتباه کشیدهاید. پس از <xliff:g id="NUMBER_1">%2$d</xliff:g> تلاش ناموفق، از شما خواسته میشود که با استفاده از یک حساب رایانامه قفل تلفن را باز کنید.\n\n لطفاً پس از <xliff:g id="NUMBER_2">%3$d</xliff:g> ثانیه دوباره امتحان کنید."</string> + <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="956706236554092172">"شما الگوی باز کردن قفل را <xliff:g id="NUMBER_0">%1$d</xliff:g> بار اشتباه کشیدهاید. بعد از <xliff:g id="NUMBER_1">%2$d</xliff:g> تلاش ناموفق، از شما خواسته میشود که با استفاده از یک حساب ایمیل قفل رایانه لوحی خود را باز کنید.\n\n لطفاً پس از <xliff:g id="NUMBER_2">%3$d</xliff:g> ثانیه دوباره امتحان کنید."</string> + <string name="kg_failed_attempts_almost_at_login" product="default" msgid="8364140853305528449">"شما الگوی باز کردن قفل را <xliff:g id="NUMBER_0">%1$d</xliff:g> بار اشتباه کشیدهاید. پس از <xliff:g id="NUMBER_1">%2$d</xliff:g> تلاش ناموفق، از شما خواسته میشود که با استفاده از یک حساب ایمیل قفل تلفن را باز کنید.\n\n لطفاً پس از <xliff:g id="NUMBER_2">%3$d</xliff:g> ثانیه دوباره امتحان کنید."</string> <string name="kg_password_wrong_pin_code_pukked" msgid="3389829202093674267">"کد پین سیمکارت اشتباه است، اکنون برای باز کردن قفل دستگاهتان باید با شرکت مخابراتی تماس بگیرید."</string> <plurals name="kg_password_wrong_pin_code" formatted="false" msgid="4314341367727055967"> <item quantity="one">کد پین سیمکارت اشتباه است، <xliff:g id="NUMBER_1">%d</xliff:g> بار دیگر میتوانید تلاش کنید.</item> diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml index 364156a21be9..9c82b70e0759 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -62,11 +62,11 @@ <string name="label_view" msgid="6304565553218192990">"Mostra"</string> <string name="always_use_device" msgid="4015357883336738417">"Obre sempre <xliff:g id="APPLICATION">%1$s</xliff:g> quan es connecti <xliff:g id="USB_DEVICE">%2$s</xliff:g>"</string> <string name="always_use_accessory" msgid="3257892669444535154">"Obre sempre <xliff:g id="APPLICATION">%1$s</xliff:g> quan es connecti <xliff:g id="USB_ACCESSORY">%2$s</xliff:g>"</string> - <string name="usb_debugging_title" msgid="4513918393387141949">"Vols permetre la depuració USB?"</string> + <string name="usb_debugging_title" msgid="4513918393387141949">"Vols permetre la depuració per USB?"</string> <string name="usb_debugging_message" msgid="2220143855912376496">"L\'empremta digital de la clau de l\'RSA de l\'equip és:\n<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string> <string name="usb_debugging_always" msgid="303335496705863070">"Dona sempre permís des d\'aquest equip"</string> - <string name="usb_debugging_secondary_user_title" msgid="6353808721761220421">"No es permet la depuració USB"</string> - <string name="usb_debugging_secondary_user_message" msgid="6067122453571699801">"L\'usuari que té iniciada la sessió al dispositiu en aquest moment no pot activar la depuració USB. Per utilitzar aquesta funció, cal canviar a l\'usuari principal."</string> + <string name="usb_debugging_secondary_user_title" msgid="6353808721761220421">"No es permet la depuració per USB"</string> + <string name="usb_debugging_secondary_user_message" msgid="6067122453571699801">"L\'usuari que té iniciada la sessió al dispositiu en aquest moment no pot activar la depuració per USB. Per utilitzar aquesta funció, cal canviar a l\'usuari principal."</string> <string name="compat_mode_on" msgid="6623839244840638213">"Zoom per omplir pantalla"</string> <string name="compat_mode_off" msgid="4434467572461327898">"Estira per omplir pant."</string> <string name="global_action_screenshot" msgid="8329831278085426283">"Captura de pantalla"</string> diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml index d72b1d1adaa6..5b99dd438312 100644 --- a/packages/SystemUI/res/values-cs/strings.xml +++ b/packages/SystemUI/res/values-cs/strings.xml @@ -64,11 +64,11 @@ <string name="label_view" msgid="6304565553218192990">"Zobrazit"</string> <string name="always_use_device" msgid="4015357883336738417">"Když bude připojeno zařízení <xliff:g id="USB_DEVICE">%2$s</xliff:g>, vždy otevřít <xliff:g id="APPLICATION">%1$s</xliff:g>"</string> <string name="always_use_accessory" msgid="3257892669444535154">"Když bude připojeno zařízení <xliff:g id="USB_ACCESSORY">%2$s</xliff:g>, vždy otevřít <xliff:g id="APPLICATION">%1$s</xliff:g>"</string> - <string name="usb_debugging_title" msgid="4513918393387141949">"Povolit ladění USB?"</string> + <string name="usb_debugging_title" msgid="4513918393387141949">"Povolit ladění přes USB?"</string> <string name="usb_debugging_message" msgid="2220143855912376496">"Digitální otisk RSA počítače je:\n<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string> <string name="usb_debugging_always" msgid="303335496705863070">"Vždy povolit z tohoto počítače"</string> - <string name="usb_debugging_secondary_user_title" msgid="6353808721761220421">"Ladění USB není povoleno"</string> - <string name="usb_debugging_secondary_user_message" msgid="6067122453571699801">"Uživatel aktuálně přihlášený k tomuto zařízení nemůže zapnout ladění USB. Chcete-li tuto funkci použít, přepněte na primárního uživatele."</string> + <string name="usb_debugging_secondary_user_title" msgid="6353808721761220421">"Ladění přes USB není povoleno"</string> + <string name="usb_debugging_secondary_user_message" msgid="6067122453571699801">"Uživatel aktuálně přihlášený k tomuto zařízení nemůže zapnout ladění přes USB. Chcete-li tuto funkci použít, přepněte na primárního uživatele."</string> <string name="compat_mode_on" msgid="6623839244840638213">"Přiblížit na celou obrazovku"</string> <string name="compat_mode_off" msgid="4434467572461327898">"Na celou obrazovku"</string> <string name="global_action_screenshot" msgid="8329831278085426283">"Snímek obrazovky"</string> diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml index 34ab46f62ccc..db393c0b26c2 100644 --- a/packages/SystemUI/res/values-fa/strings.xml +++ b/packages/SystemUI/res/values-fa/strings.xml @@ -472,28 +472,28 @@ <string name="monitoring_description_managed_profile_ca_certificate" msgid="4683248196789897964">"سازمان شما مرجع گواهینامهای در نمایه کاری شما نصب کرده است. ممکن است ترافیک امن شبکه شما پایش یا تغییر داده شود."</string> <string name="monitoring_description_ca_certificate" msgid="7886985418413598352">"مرجع گواهینامهای در این دستگاه نصب شده است. ممکن است ترافیک امن شبکه شما پایش یا تغییر داده شود."</string> <string name="monitoring_description_management_network_logging" msgid="7184005419733060736">"سرپرست سیستم شما گزارشگیری از شبکه را (که ترافیک دستگاه شما را پایش میکند) روشن کرده است."</string> - <string name="monitoring_description_named_vpn" msgid="7403457334088909254">"به <xliff:g id="VPN_APP">%1$s</xliff:g> متصل شدهاید، که میتواند فعالیت شبکه شما را (ازجمله رایانامهها، برنامهها و وبسایتها) پایش کند."</string> - <string name="monitoring_description_two_named_vpns" msgid="4198511413729213802">"به <xliff:g id="VPN_APP_0">%1$s</xliff:g> و <xliff:g id="VPN_APP_1">%2$s</xliff:g> متصل شدهاید، که میتوانند فعالیت شما را در شبکه (ازجمله رایانامهها، برنامهها و وبسایتها) پایش کنند."</string> - <string name="monitoring_description_managed_profile_named_vpn" msgid="1427905889862420559">"نمایه کاری شما به <xliff:g id="VPN_APP">%1$s</xliff:g> متصل است، که میتواند فعالیت شما در شبکه (ازجمله رایانامهها، برنامهها و وبسایتها) را پایش کند."</string> - <string name="monitoring_description_personal_profile_named_vpn" msgid="3133980926929069283">"نمایه شخصی شما به <xliff:g id="VPN_APP">%1$s</xliff:g> متصل شده است، که میتواند فعالیت شما در شبکه (ازجمله رایانامهها، برنامهها و وبسایتها) را پایش کند."</string> + <string name="monitoring_description_named_vpn" msgid="7403457334088909254">"به <xliff:g id="VPN_APP">%1$s</xliff:g> متصل شدهاید، که میتواند فعالیت شبکه شما را (ازجمله ایمیلها، برنامهها و وبسایتها) پایش کند."</string> + <string name="monitoring_description_two_named_vpns" msgid="4198511413729213802">"به <xliff:g id="VPN_APP_0">%1$s</xliff:g> و <xliff:g id="VPN_APP_1">%2$s</xliff:g> متصل شدهاید، که میتوانند فعالیت شما را در شبکه (ازجمله ایمیلها، برنامهها و وبسایتها) پایش کنند."</string> + <string name="monitoring_description_managed_profile_named_vpn" msgid="1427905889862420559">"نمایه کاری شما به <xliff:g id="VPN_APP">%1$s</xliff:g> متصل است، که میتواند فعالیت شما در شبکه (ازجمله ایمیلها، برنامهها و وبسایتها) را پایش کند."</string> + <string name="monitoring_description_personal_profile_named_vpn" msgid="3133980926929069283">"نمایه شخصی شما به <xliff:g id="VPN_APP">%1$s</xliff:g> متصل شده است، که میتواند فعالیت شما در شبکه (ازجمله ایمیلها، برنامهها و وبسایتها) را پایش کند."</string> <string name="monitoring_description_do_header_generic" msgid="96588491028288691">"<xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g> دستگاه شما را مدیریت میکند."</string> <string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> با استفاده از <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g> دستگاهتان را مدیریت میکند."</string> <string name="monitoring_description_do_body" msgid="3639594537660975895">"سرپرست سیستم شما میتواند تنظیمات، دسترسی شرکتی، برنامهها، دادههای مرتبط با دستگاه و اطلاعات مکان دستگاه شما را مدیریت کند و بر آنها نظارت داشته باشد."</string> <string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string> <string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"بیشتر بدانید"</string> - <string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"به <xliff:g id="VPN_APP">%1$s</xliff:g> وصل شدهاید، که میتواند فعالیت شبکه شما را (ازجمله رایانامهها، برنامهها و وبسایتها) کنترل کند."</string> + <string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"به <xliff:g id="VPN_APP">%1$s</xliff:g> وصل شدهاید، که میتواند فعالیت شبکه شما را (ازجمله ایمیلها، برنامهها و وبسایتها) کنترل کند."</string> <string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string> <string name="monitoring_description_vpn_settings" msgid="6434859242636063861">"باز کردن تنظیمات VPN"</string> <string name="monitoring_description_ca_cert_settings_separator" msgid="4987350385906393626">" "</string> <string name="monitoring_description_ca_cert_settings" msgid="5489969458872997092">"باز کردن اعتبارنامه مورداعتماد"</string> <string name="monitoring_description_network_logging" msgid="7223505523384076027">"سرپرست سیستم شما گزارشگیری شبکه را (که بر ترافیک دستگاهتان نظارت میکند) روشن کرده است.\n\nبرای اطلاعات بیشتر، با سرپرست خود تماس بگیرید."</string> - <string name="monitoring_description_vpn" msgid="4445150119515393526">"شما به برنامهای برای تنظیم اتصال VPN اجازه دادید.\n\n این برنامه میتواند دستگاه و فعالیت شبکهتان را کنترل کند، از جمله رایانامه، برنامه و وبسایتها."</string> - <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"نمایه کاری شما توسط <xliff:g id="ORGANIZATION">%1$s</xliff:g> مدیریت میشود.\n\nسرپرست سیستم شما میتواند بر فعالیت شبکه شما (ازجمله رایانامهها، برنامهها و وبسایتها) نظارت داشته باشد.\n\nبرای اطلاعات بیشتر، با سرپرست خود تماس بگیرید.\n\nهمچنین به VPN متصل هستید که میتواند بر فعالیت شبکه شما نظارت داشته باشد."</string> + <string name="monitoring_description_vpn" msgid="4445150119515393526">"شما به برنامهای برای تنظیم اتصال VPN اجازه دادید.\n\n این برنامه میتواند دستگاه و فعالیت شبکهتان را کنترل کند، از جمله ایمیل، برنامه و وبسایتها."</string> + <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"نمایه کاری شما توسط <xliff:g id="ORGANIZATION">%1$s</xliff:g> مدیریت میشود.\n\nسرپرست سیستم شما میتواند بر فعالیت شبکه شما (ازجمله ایمیلها، برنامهها و وبسایتها) نظارت داشته باشد.\n\nبرای اطلاعات بیشتر، با سرپرست خود تماس بگیرید.\n\nهمچنین به VPN متصل هستید که میتواند بر فعالیت شبکه شما نظارت داشته باشد."</string> <string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string> - <string name="monitoring_description_app" msgid="1828472472674709532">"شما به <xliff:g id="APPLICATION">%1$s</xliff:g> متصل هستید، که میتواند فعالیت شما در شبکه (ازجمله رایانامه، برنامه و وبسایتها) را پایش کند."</string> - <string name="monitoring_description_app_personal" msgid="484599052118316268">"شما به <xliff:g id="APPLICATION">%1$s</xliff:g> وصل شدهاید، که میتواند فعالیت شبکه شخصی شما از جمله رایانامه، برنامه و وبسایتها را کنترل کند."</string> - <string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"به <xliff:g id="APPLICATION">%1$s</xliff:g> وصل شدهاید، که میتواند فعالیت شبکه شخصی شما را (ازجمله رایانامهها، برنامهها و وبسایتها) کنترل کند."</string> - <string name="monitoring_description_app_work" msgid="4612997849787922906">"نمایه کاری شما توسط <xliff:g id="ORGANIZATION">%1$s</xliff:g> مدیریت میشود. این نمایه به <xliff:g id="APPLICATION">%2$s</xliff:g> متصل است که میتواند فعالیت شما در شبکه (ازجمله رایانامهها، برنامهها و وبسایتها) را پایش کند.\n\nبرای اطلاعات بیشتر، با سرپرست سیستم تماس بگیرید."</string> + <string name="monitoring_description_app" msgid="1828472472674709532">"شما به <xliff:g id="APPLICATION">%1$s</xliff:g> متصل هستید، که میتواند فعالیت شما در شبکه (ازجمله ایمیل، برنامه و وبسایتها) را پایش کند."</string> + <string name="monitoring_description_app_personal" msgid="484599052118316268">"شما به <xliff:g id="APPLICATION">%1$s</xliff:g> وصل شدهاید، که میتواند فعالیت شبکه شخصی شما از جمله ایمیل، برنامه و وبسایتها را کنترل کند."</string> + <string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"به <xliff:g id="APPLICATION">%1$s</xliff:g> وصل شدهاید که میتواند فعالیت شبکه شخصی شما را (ازجمله ایمیلها، برنامهها و وبسایتها) کنترل کند."</string> + <string name="monitoring_description_app_work" msgid="4612997849787922906">"نمایه کاری شما توسط <xliff:g id="ORGANIZATION">%1$s</xliff:g> مدیریت میشود. این نمایه به <xliff:g id="APPLICATION">%2$s</xliff:g> متصل است که میتواند فعالیت شما در شبکه (ازجمله ایمیلها، برنامهها و وبسایتها) را پایش کند.\n\nبرای اطلاعات بیشتر، با سرپرست سیستم تماس بگیرید."</string> <string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"نمایه کاریتان توسط <xliff:g id="ORGANIZATION">%1$s</xliff:g> مدیریت میشود. این نمایه به <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> متصل است که میتواند تنظیمات، دسترسی شرکتی، برنامهها، دادههای مرتبط با دستگاه و اطلاعات مکان دستگاه شما را پایش کند.\n\nشما همچنین به <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> متصل هستید که میتواند فعالیت خصوصی شما را در شبکه پایش کند."</string> <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"قفل برای <xliff:g id="USER_NAME">%1$s</xliff:g> باز شد"</string> <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> درحال اجرا است"</string> @@ -681,7 +681,7 @@ <string name="keyboard_shortcut_group_applications_assist" msgid="9095441910537146013">"دستیار"</string> <string name="keyboard_shortcut_group_applications_browser" msgid="6465985474000766533">"مرورگر"</string> <string name="keyboard_shortcut_group_applications_contacts" msgid="2064197111278436375">"مخاطبین"</string> - <string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"رایانامه"</string> + <string name="keyboard_shortcut_group_applications_email" msgid="6257036897441939004">"ایمیل"</string> <string name="keyboard_shortcut_group_applications_sms" msgid="638701213803242744">"پیامک"</string> <string name="keyboard_shortcut_group_applications_music" msgid="4775559515850922780">"موسیقی"</string> <string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java index dce72b440f58..0ded9631cb3d 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java @@ -394,6 +394,22 @@ public class ActivityManagerWrapper { } /** + * Removes all the recent tasks. + */ + public void removeAllRecentTasks() { + mBackgroundExecutor.submit(new Runnable() { + @Override + public void run() { + try { + ActivityTaskManager.getService().removeAllVisibleRecentTasks(); + } catch (RemoteException e) { + Log.w(TAG, "Failed to remove all tasks", e); + } + } + }); + } + + /** * Cancels the current window transtion to/from Recents for the given task id. */ public void cancelWindowTransition(int taskId) { diff --git a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java index 4bb4c24638b9..3c44eb42ffca 100644 --- a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java @@ -340,7 +340,8 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis boolean bound = false; try { bound = mContext.bindServiceAsUser(launcherServiceIntent, - mOverviewServiceConnection, Context.BIND_AUTO_CREATE, + mOverviewServiceConnection, + Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE, UserHandle.of(mDeviceProvisionedController.getCurrentUser())); } catch (SecurityException e) { Log.e(TAG_OPS, "Unable to bind because of security error", e); diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java index 6237212685a4..e0eb269ee728 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java @@ -111,6 +111,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect UserState userState = mUserStateWeakReference.get(); if (userState == null) return; userState.removeServiceLocked(this); + mSystemSupport.getMagnificationController().resetIfNeeded(mId); resetLocked(); } @@ -257,9 +258,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect if (userState != null) { userState.serviceDisconnectedLocked(this); } - if (mId == mSystemSupport.getMagnificationController().getIdOfLastServiceToMagnify()) { - mSystemSupport.getMagnificationController().resetIfNeeded(true); - } + mSystemSupport.getMagnificationController().resetIfNeeded(mId); mSystemSupport.onClientChange(false); } } diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java index b697434f28bf..b938f3be4926 100644 --- a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java @@ -722,6 +722,19 @@ public class MagnificationController implements Handler.Callback { } } + /** + * Resets magnification if last magnifying service is disabled. + * + * @param connectionId the connection ID be disabled. + * @return {@code true} on success, {@code false} on failure + */ + boolean resetIfNeeded(int connectionId) { + if (mIdOfLastServiceToMagnify == connectionId) { + return resetIfNeeded(true /*animate*/); + } + return false; + } + void setForceShowMagnifiableBounds(boolean show) { if (mRegistered) { mWindowManager.setForceShowMagnifiableBounds(show); diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java index 6c6dd5b8bd67..8d691ffd194d 100644 --- a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java @@ -247,6 +247,9 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { if (mScreenStateReceiver != null) { mScreenStateReceiver.unregister(); } + // Check if need to reset when MagnificationGestureHandler is the last magnifying service. + mMagnificationController.resetIfNeeded( + AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); clearAndTransitionToStateDetecting(); } diff --git a/services/art-profile b/services/art-profile index a33527e3396e..24964f32f63e 100644 --- a/services/art-profile +++ b/services/art-profile @@ -9258,20 +9258,20 @@ PLcom/android/server/backup/internal/BackupRequest;->toString()Ljava/lang/String PLcom/android/server/backup/internal/BackupState;-><init>(Ljava/lang/String;I)V PLcom/android/server/backup/internal/BackupState;->values()[Lcom/android/server/backup/internal/BackupState; PLcom/android/server/backup/internal/Operation;-><init>(ILcom/android/server/backup/BackupRestoreTask;I)V -PLcom/android/server/backup/internal/PerformBackupTask;-><init>(Lcom/android/server/backup/BackupManagerService;Lcom/android/server/backup/transport/TransportClient;Ljava/lang/String;Ljava/util/ArrayList;Lcom/android/server/backup/DataChangedJournal;Landroid/app/backup/IBackupObserver;Landroid/app/backup/IBackupManagerMonitor;Lcom/android/server/backup/internal/OnTaskFinishedListener;Ljava/util/List;ZZ)V -PLcom/android/server/backup/internal/PerformBackupTask;->backupPm()V -PLcom/android/server/backup/internal/PerformBackupTask;->beginBackup()V -PLcom/android/server/backup/internal/PerformBackupTask;->clearAgentState()V -PLcom/android/server/backup/internal/PerformBackupTask;->execute()V -PLcom/android/server/backup/internal/PerformBackupTask;->executeNextState(Lcom/android/server/backup/internal/BackupState;)V -PLcom/android/server/backup/internal/PerformBackupTask;->finalizeBackup()V -PLcom/android/server/backup/internal/PerformBackupTask;->invokeAgentForBackup(Ljava/lang/String;Landroid/app/IBackupAgent;)I -PLcom/android/server/backup/internal/PerformBackupTask;->invokeNextAgent()V -PLcom/android/server/backup/internal/PerformBackupTask;->operationComplete(J)V -PLcom/android/server/backup/internal/PerformBackupTask;->registerTask()V -PLcom/android/server/backup/internal/PerformBackupTask;->revertAndEndBackup()V -PLcom/android/server/backup/internal/PerformBackupTask;->unregisterTask()V -PLcom/android/server/backup/internal/PerformBackupTask;->writeWidgetPayloadIfAppropriate(Ljava/io/FileDescriptor;Ljava/lang/String;)V +PLcom/android/server/backup/internal/KeyValueBackupTask;-><init>(Lcom/android/server/backup/BackupManagerService;Lcom/android/server/backup/transport/TransportClient;Ljava/lang/String;Ljava/util/List;Lcom/android/server/backup/DataChangedJournal;Landroid/app/backup/IBackupObserver;Landroid/app/backup/IBackupManagerMonitor;Lcom/android/server/backup/internal/OnTaskFinishedListener;Ljava/util/List;ZZ)V +PLcom/android/server/backup/internal/KeyValueBackupTask;->backupPm()V +PLcom/android/server/backup/internal/KeyValueBackupTask;->beginBackup()V +PLcom/android/server/backup/internal/KeyValueBackupTask;->clearAgentState()V +PLcom/android/server/backup/internal/KeyValueBackupTask;->execute()V +PLcom/android/server/backup/internal/KeyValueBackupTask;->executeNextState(Lcom/android/server/backup/internal/BackupState;)V +PLcom/android/server/backup/internal/KeyValueBackupTask;->finalizeBackup()V +PLcom/android/server/backup/internal/KeyValueBackupTask;->invokeAgentForBackup(Ljava/lang/String;Landroid/app/IBackupAgent;)I +PLcom/android/server/backup/internal/KeyValueBackupTask;->invokeNextAgent()V +PLcom/android/server/backup/internal/KeyValueBackupTask;->operationComplete(J)V +PLcom/android/server/backup/internal/KeyValueBackupTask;->registerTask()V +PLcom/android/server/backup/internal/KeyValueBackupTask;->revertAndEndBackup()V +PLcom/android/server/backup/internal/KeyValueBackupTask;->unregisterTask()V +PLcom/android/server/backup/internal/KeyValueBackupTask;->writeWidgetPayloadIfAppropriate(Ljava/io/FileDescriptor;Ljava/lang/String;)V PLcom/android/server/backup/internal/ProvisionedObserver;-><init>(Lcom/android/server/backup/BackupManagerService;Landroid/os/Handler;)V PLcom/android/server/backup/internal/RunBackupReceiver;-><init>(Lcom/android/server/backup/BackupManagerService;)V PLcom/android/server/backup/internal/RunBackupReceiver;->onReceive(Landroid/content/Context;Landroid/content/Intent;)V diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java index 66b6b37b8b4f..6e96fe0ba4ba 100644 --- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java +++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java @@ -154,7 +154,7 @@ public class BackupHandler extends Handler { caller -> transportManager .disposeOfTransportClient(transportClient, caller); - PerformBackupTask.start( + KeyValueBackupTask.start( backupManagerService, transportClient, transport.transportDirName(), @@ -410,7 +410,7 @@ public class BackupHandler extends Handler { backupManagerService.setBackupRunning(true); backupManagerService.getWakelock().acquire(); - PerformBackupTask.start( + KeyValueBackupTask.start( backupManagerService, params.transportClient, params.dirName, diff --git a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java b/services/backup/java/com/android/server/backup/internal/KeyValueBackupTask.java index c5df82ed2e79..a4d7bd1efbeb 100644 --- a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java +++ b/services/backup/java/com/android/server/backup/internal/KeyValueBackupTask.java @@ -168,8 +168,8 @@ import java.util.concurrent.atomic.AtomicInteger; // TODO: Stop poking into BMS state and doing things for it (e.g. synchronizing on public locks) // TODO: Consider having the caller responsible for some clean-up (like resetting state) // TODO: Distinguish between cancel and time-out where possible for logging/monitoring/observing -public class PerformBackupTask implements BackupRestoreTask, Runnable { - private static final String TAG = "PerformBackupTask"; +public class KeyValueBackupTask implements BackupRestoreTask, Runnable { + private static final String TAG = "KeyValueBackupTask"; private static final boolean DEBUG = BackupManagerService.DEBUG || true; private static final boolean MORE_DEBUG = BackupManagerService.MORE_DEBUG || false; private static final int THREAD_PRIORITY = Process.THREAD_PRIORITY_BACKGROUND; @@ -181,7 +181,7 @@ public class PerformBackupTask implements BackupRestoreTask, Runnable { public static final String NEW_STATE_FILE_SUFFIX = ".new"; /** - * Creates a new {@link PerformBackupTask} for key-value backup operation, spins up a new + * Creates a new {@link KeyValueBackupTask} for key-value backup operation, spins up a new * dedicated thread and kicks off the operation in it. * * @param backupManagerService The {@link BackupManagerService} system service. @@ -201,13 +201,13 @@ public class PerformBackupTask implements BackupRestoreTask, Runnable { * @param userInitiated Whether this was user-initiated or not. * @param nonIncremental If {@code true}, this will be a complete backup for each package, * otherwise it will be just an incremental one over the current dataset. - * @return The {@link PerformBackupTask} that was started. + * @return The {@link KeyValueBackupTask} that was started. */ - public static PerformBackupTask start( + public static KeyValueBackupTask start( BackupManagerService backupManagerService, TransportClient transportClient, String transportDirName, - ArrayList<BackupRequest> queue, + List<BackupRequest> queue, @Nullable DataChangedJournal dataChangedJournal, IBackupObserver observer, IBackupManagerMonitor monitor, @@ -215,8 +215,8 @@ public class PerformBackupTask implements BackupRestoreTask, Runnable { List<String> pendingFullBackups, boolean userInitiated, boolean nonIncremental) { - PerformBackupTask task = - new PerformBackupTask( + KeyValueBackupTask task = + new KeyValueBackupTask( backupManagerService, transportClient, transportDirName, @@ -245,8 +245,8 @@ public class PerformBackupTask implements BackupRestoreTask, Runnable { private final boolean mNonIncremental; private final int mCurrentOpToken; private final File mStateDir; - private final ArrayList<BackupRequest> mOriginalQueue; - private final ArrayList<BackupRequest> mQueue; + private final List<BackupRequest> mOriginalQueue; + private final List<BackupRequest> mQueue; private final List<String> mPendingFullBackups; @Nullable private final DataChangedJournal mJournal; private IBackupManagerMonitor mMonitor; @@ -285,11 +285,11 @@ public class PerformBackupTask implements BackupRestoreTask, Runnable { @Nullable private volatile RemoteCall mPendingCall; @VisibleForTesting - public PerformBackupTask( + public KeyValueBackupTask( BackupManagerService backupManagerService, TransportClient transportClient, String transportDirName, - ArrayList<BackupRequest> queue, + List<BackupRequest> queue, @Nullable DataChangedJournal journal, IBackupObserver observer, IBackupManagerMonitor monitor, @@ -453,7 +453,7 @@ public class PerformBackupTask implements BackupRestoreTask, Runnable { } File pmState = new File(mStateDir, PACKAGE_MANAGER_SENTINEL); try { - IBackupTransport transport = mTransportClient.connectOrThrow("PBT.beginBackup()"); + IBackupTransport transport = mTransportClient.connectOrThrow("KVBT.beginBackup()"); String transportName = transport.name(); EventLog.writeEvent(EventLogTags.BACKUP_START, transportName); @@ -696,7 +696,7 @@ public class PerformBackupTask implements BackupRestoreTask, Runnable { // If everything actually went through and this is the first time we've // done a backup, we can now record what the current backup dataset token // is. - String callerLogString = "PBT.finalizeBackup()"; + String callerLogString = "KVBT.finalizeBackup()"; if ((mBackupManagerService.getCurrentToken() == 0) && (mStatus == BackupTransport.TRANSPORT_OK)) { mBackupManagerService.addBackupTrace("success; recording token"); @@ -843,7 +843,7 @@ public class PerformBackupTask implements BackupRestoreTask, Runnable { ParcelFileDescriptor.MODE_TRUNCATE); IBackupTransport transport = - mTransportClient.connectOrThrow("PBT.invokeAgentForBackup()"); + mTransportClient.connectOrThrow("KVBT.invokeAgentForBackup()"); final long quota = transport.getBackupQuota(packageName, false /* isFullBackup */); callingAgent = true; @@ -1043,7 +1043,7 @@ public class PerformBackupTask implements BackupRestoreTask, Runnable { mStatus = BackupTransport.TRANSPORT_OK; long size = 0; try { - IBackupTransport transport = mTransportClient.connectOrThrow("PBT.handleAgentResult()"); + IBackupTransport transport = mTransportClient.connectOrThrow("KVBT.handleAgentResult()"); size = mBackupDataFile.length(); if (size > 0) { if (MORE_DEBUG) { @@ -1191,7 +1191,7 @@ public class PerformBackupTask implements BackupRestoreTask, Runnable { if (mAgentBinder != null) { try { IBackupTransport transport = - mTransportClient.connectOrThrow("PBT.handleAgentResult()"); + mTransportClient.connectOrThrow("KVBT.handleAgentResult()"); long quota = transport.getBackupQuota(mCurrentPackage.packageName, false); mAgentBinder.doQuotaExceeded(size, quota); } catch (Exception e) { @@ -1287,7 +1287,7 @@ public class PerformBackupTask implements BackupRestoreTask, Runnable { long delay; try { IBackupTransport transport = - mTransportClient.connectOrThrow("PBT.revertAndEndBackup()"); + mTransportClient.connectOrThrow("KVBT.revertAndEndBackup()"); delay = transport.requestBackupTime(); } catch (Exception e) { Slog.w(TAG, "Unable to contact transport for recommended backoff: " + e.getMessage()); diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index d1585cf8fb48..609ad752a3dc 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -3230,12 +3230,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } @Override - public void onRecentTaskRemoved(TaskRecord task, boolean wasTrimmed) { + public void onRecentTaskRemoved(TaskRecord task, boolean wasTrimmed, boolean killProcess) { if (wasTrimmed) { // Task was trimmed from the recent tasks list -- remove the active task record as well // since the user won't really be able to go back to it - removeTaskByIdLocked(task.taskId, false /* killProcess */, - false /* removeFromRecents */, !PAUSE_IMMEDIATELY, "recent-task-trimmed"); + removeTaskByIdLocked(task.taskId, killProcess, false /* removeFromRecents */, + !PAUSE_IMMEDIATELY, "recent-task-trimmed"); } task.removedFromRecents(); } diff --git a/services/core/java/com/android/server/am/ActivityTaskManagerService.java b/services/core/java/com/android/server/am/ActivityTaskManagerService.java index 748b2d2d612a..54c2ee51c7a2 100644 --- a/services/core/java/com/android/server/am/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/am/ActivityTaskManagerService.java @@ -27,6 +27,7 @@ import static android.Manifest.permission.REMOVE_TASKS; import static android.Manifest.permission.START_TASKS_FROM_RECENTS; import static android.Manifest.permission.STOP_APP_SWITCHES; import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; +import static android.content.pm.PackageManager.FEATURE_PC; import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; import static android.provider.Settings.Global.HIDE_ERROR_DIALOGS; import static android.provider.Settings.System.FONT_SCALE; @@ -541,6 +542,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { final boolean forceRtl = Settings.Global.getInt(resolver, DEVELOPMENT_FORCE_RTL, 0) != 0; final boolean forceResizable = Settings.Global.getInt( resolver, DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, 0) != 0; + final boolean isPc = mContext.getPackageManager().hasSystemFeature(FEATURE_PC); // Transfer any global setting for forcing RTL layout, into a System Property SystemProperties.set(DEVELOPMENT_FORCE_RTL, forceRtl ? "1":"0"); @@ -573,6 +575,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } mWindowManager.setForceResizableTasks(mForceResizableActivities); mWindowManager.setSupportsPictureInPicture(mSupportsPictureInPicture); + mWindowManager.setSupportsFreeformWindowManagement(mSupportsFreeformWindowManagement); + mWindowManager.setIsPc(isPc); // This happens before any activities are started, so we can change global configuration // in-place. updateConfigurationLocked(configuration, null, true); @@ -1668,6 +1672,19 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override + public void removeAllVisibleRecentTasks() { + enforceCallerIsRecentsOrHasPermission(REMOVE_TASKS, "removeAllVisibleRecentTasks()"); + synchronized (mGlobalLock) { + final long ident = Binder.clearCallingIdentity(); + try { + getRecentTasks().removeAllVisibleTasks(); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + @Override public boolean shouldUpRecreateTask(IBinder token, String destAffinity) { synchronized (mGlobalLock) { final ActivityRecord srec = ActivityRecord.forTokenLocked(token); diff --git a/services/core/java/com/android/server/am/RecentTasks.java b/services/core/java/com/android/server/am/RecentTasks.java index fb6b5c1f05ec..e11e00368a78 100644 --- a/services/core/java/com/android/server/am/RecentTasks.java +++ b/services/core/java/com/android/server/am/RecentTasks.java @@ -106,7 +106,6 @@ class RecentTasks { private static final String TAG = TAG_WITH_CLASS_NAME ? "RecentTasks" : TAG_AM; private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS; private static final String TAG_TASKS = TAG + POSTFIX_TASKS; - private static final boolean TRIMMED = true; private static final int DEFAULT_INITIAL_CAPACITY = 5; @@ -134,7 +133,7 @@ class RecentTasks { /** * Called when a task is removed from the recent tasks list. */ - void onRecentTaskRemoved(TaskRecord task, boolean wasTrimmed); + void onRecentTaskRemoved(TaskRecord task, boolean wasTrimmed, boolean killProcess); } /** @@ -322,9 +321,9 @@ class RecentTasks { } } - private void notifyTaskRemoved(TaskRecord task, boolean wasTrimmed) { + private void notifyTaskRemoved(TaskRecord task, boolean wasTrimmed, boolean killProcess) { for (int i = 0; i < mCallbacks.size(); i++) { - mCallbacks.get(i).onRecentTaskRemoved(task, wasTrimmed); + mCallbacks.get(i).onRecentTaskRemoved(task, wasTrimmed, killProcess); } } @@ -547,6 +546,16 @@ class RecentTasks { } } + void removeAllVisibleTasks() { + for (int i = mTasks.size() - 1; i >= 0; --i) { + final TaskRecord tr = mTasks.get(i); + if (isVisibleRecentTask(tr)) { + mTasks.remove(i); + notifyTaskRemoved(tr, true /* wasTrimmed */, true /* killProcess */); + } + } + } + void cleanupDisabledPackageTasksLocked(String packageName, Set<String> filterByClasses, int userId) { for (int i = mTasks.size() - 1; i >= 0; --i) { @@ -1048,7 +1057,7 @@ class RecentTasks { */ void remove(TaskRecord task) { mTasks.remove(task); - notifyTaskRemoved(task, !TRIMMED); + notifyTaskRemoved(task, false /* wasTrimmed */, false /* killProcess */); } /** @@ -1060,7 +1069,7 @@ class RecentTasks { // Remove from the end of the list until we reach the max number of recents while (recentsCount > mGlobalMaxNumTasks) { final TaskRecord tr = mTasks.remove(recentsCount - 1); - notifyTaskRemoved(tr, TRIMMED); + notifyTaskRemoved(tr, true /* wasTrimmed */, false /* killProcess */); recentsCount--; if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "Trimming over max-recents task=" + tr + " max=" + mGlobalMaxNumTasks); @@ -1114,7 +1123,7 @@ class RecentTasks { // Task is no longer active, trim it from the list mTasks.remove(task); - notifyTaskRemoved(task, TRIMMED); + notifyTaskRemoved(task, true /* wasTrimmed */, false /* killProcess */); notifyTaskPersisterLocked(task, false /* flush */); } } @@ -1268,7 +1277,7 @@ class RecentTasks { // callbacks here. final TaskRecord removedTask = mTasks.remove(removeIndex); if (removedTask != task) { - notifyTaskRemoved(removedTask, !TRIMMED); + notifyTaskRemoved(removedTask, false /* wasTrimmed */, false /* killProcess */); if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "Trimming task=" + removedTask + " for addition of task=" + task); } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index b124ac78a8ef..e2c8ef982eb3 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -1659,8 +1659,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call pw.println(" mReportedToPolicy=" + reportedToPolicyToString(mReportedScreenStateToPolicy)); - pw.println(" mScreenBrightnessRampAnimator.isAnimating()=" + - mScreenBrightnessRampAnimator.isAnimating()); + if (mScreenBrightnessRampAnimator != null) { + pw.println(" mScreenBrightnessRampAnimator.isAnimating()=" + + mScreenBrightnessRampAnimator.isAnimating()); + } if (mColorFadeOnAnimator != null) { pw.println(" mColorFadeOnAnimator.isStarted()=" + diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index d7d5cf84ede0..4ab06a28712d 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1389,9 +1389,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo final int dw = displayInfo.logicalWidth; final int dh = displayInfo.logicalHeight; config.orientation = (dw <= dh) ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; - // TODO: Probably best to set this based on some setting in the display content object, - // so the display can be configured for things like fullscreen. - config.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + config.windowConfiguration.setWindowingMode(getWindowingMode()); final float density = mDisplayMetrics.density; config.screenWidthDp = diff --git a/services/core/java/com/android/server/wm/DisplaySettings.java b/services/core/java/com/android/server/wm/DisplaySettings.java index 97b64dc2b6b1..bbb690f6a34a 100644 --- a/services/core/java/com/android/server/wm/DisplaySettings.java +++ b/services/core/java/com/android/server/wm/DisplaySettings.java @@ -19,12 +19,19 @@ package com.android.server.wm; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; +import android.app.WindowConfiguration; +import android.content.Context; +import android.content.pm.PackageManager; import android.graphics.Rect; import android.os.Environment; +import android.provider.Settings; import android.util.AtomicFile; import android.util.Slog; import android.util.Xml; +import android.view.Display; +import android.view.DisplayInfo; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.XmlUtils; @@ -43,37 +50,48 @@ import org.xmlpull.v1.XmlSerializer; /** * Current persistent settings about a display */ -public class DisplaySettings { +class DisplaySettings { private static final String TAG = TAG_WITH_CLASS_NAME ? "DisplaySettings" : TAG_WM; + private final WindowManagerService mService; private final AtomicFile mFile; private final HashMap<String, Entry> mEntries = new HashMap<String, Entry>(); - public static class Entry { - public final String name; - public int overscanLeft; - public int overscanTop; - public int overscanRight; - public int overscanBottom; + private static class Entry { + private final String name; + private int overscanLeft; + private int overscanTop; + private int overscanRight; + private int overscanBottom; + private int windowingMode = WindowConfiguration.WINDOWING_MODE_UNDEFINED; - public Entry(String _name) { + private Entry(String _name) { name = _name; } } - public DisplaySettings() { - File dataDir = Environment.getDataDirectory(); - File systemDir = new File(dataDir, "system"); - mFile = new AtomicFile(new File(systemDir, "display_settings.xml"), "wm-displays"); + DisplaySettings(WindowManagerService service) { + this(service, new File(Environment.getDataDirectory(), "system")); } - public void getOverscanLocked(String name, String uniqueId, Rect outRect) { + @VisibleForTesting + DisplaySettings(WindowManagerService service, File folder) { + mService = service; + mFile = new AtomicFile(new File(folder, "display_settings.xml"), "wm-displays"); + } + + private Entry getEntry(String name, String uniqueId) { // Try to get the entry with the unique if possible. // Else, fall back on the display name. Entry entry; if (uniqueId == null || (entry = mEntries.get(uniqueId)) == null) { entry = mEntries.get(name); } + return entry; + } + + private void getOverscanLocked(String name, String uniqueId, Rect outRect) { + final Entry entry = getEntry(name, uniqueId); if (entry != null) { outRect.left = entry.overscanLeft; outRect.top = entry.overscanTop; @@ -84,7 +102,7 @@ public class DisplaySettings { } } - public void setOverscanLocked(String uniqueId, String name, int left, int top, int right, + void setOverscanLocked(String uniqueId, String name, int left, int top, int right, int bottom) { if (left == 0 && top == 0 && right == 0 && bottom == 0) { // Right now all we are storing is overscan; if there is no overscan, @@ -105,7 +123,47 @@ public class DisplaySettings { entry.overscanBottom = bottom; } - public void readSettingsLocked() { + private int getWindowingModeLocked(String name, String uniqueId, int displayId) { + final Entry entry = getEntry(name, uniqueId); + int windowingMode = entry != null ? entry.windowingMode + : WindowConfiguration.WINDOWING_MODE_UNDEFINED; + // This display used to be in freeform, but we don't support freeform anymore, so fall + // back to fullscreen. + if (windowingMode == WindowConfiguration.WINDOWING_MODE_FREEFORM + && !mService.mSupportsFreeformWindowManagement) { + return WindowConfiguration.WINDOWING_MODE_FULLSCREEN; + } + // No record is present so use default windowing mode policy. + if (windowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED) { + if (displayId == Display.DEFAULT_DISPLAY) { + windowingMode = (mService.mIsPc && mService.mSupportsFreeformWindowManagement) + ? WindowConfiguration.WINDOWING_MODE_FREEFORM + : WindowConfiguration.WINDOWING_MODE_FULLSCREEN; + } else { + windowingMode = mService.mSupportsFreeformWindowManagement + ? WindowConfiguration.WINDOWING_MODE_FREEFORM + : WindowConfiguration.WINDOWING_MODE_FULLSCREEN; + } + } + return windowingMode; + } + + void applySettingsToDisplayLocked(DisplayContent dc) { + final DisplayInfo displayInfo = dc.getDisplayInfo(); + + // Setting windowing mode first, because it may override overscan values later. + dc.setWindowingMode(getWindowingModeLocked(displayInfo.name, displayInfo.uniqueId, + dc.getDisplayId())); + + final Rect rect = new Rect(); + getOverscanLocked(displayInfo.name, displayInfo.uniqueId, rect); + displayInfo.overscanLeft = rect.left; + displayInfo.overscanTop = rect.top; + displayInfo.overscanRight = rect.right; + displayInfo.overscanBottom = rect.bottom; + } + + void readSettingsLocked() { FileInputStream stream; try { stream = mFile.openRead(); @@ -169,11 +227,15 @@ public class DisplaySettings { } private int getIntAttribute(XmlPullParser parser, String name) { + return getIntAttribute(parser, name, 0 /* defaultValue */); + } + + private int getIntAttribute(XmlPullParser parser, String name, int defaultValue) { try { String str = parser.getAttributeValue(null, name); - return str != null ? Integer.parseInt(str) : 0; + return str != null ? Integer.parseInt(str) : defaultValue; } catch (NumberFormatException e) { - return 0; + return defaultValue; } } @@ -186,12 +248,14 @@ public class DisplaySettings { entry.overscanTop = getIntAttribute(parser, "overscanTop"); entry.overscanRight = getIntAttribute(parser, "overscanRight"); entry.overscanBottom = getIntAttribute(parser, "overscanBottom"); + entry.windowingMode = getIntAttribute(parser, "windowingMode", + WindowConfiguration.WINDOWING_MODE_UNDEFINED); mEntries.put(name, entry); } XmlUtils.skipCurrentTag(parser); } - public void writeSettingsLocked() { + void writeSettingsLocked() { FileOutputStream stream; try { stream = mFile.startWrite(); @@ -221,6 +285,9 @@ public class DisplaySettings { if (entry.overscanBottom != 0) { out.attribute(null, "overscanBottom", Integer.toString(entry.overscanBottom)); } + if (entry.windowingMode != WindowConfiguration.WINDOWING_MODE_UNDEFINED) { + out.attribute(null, "windowingMode", Integer.toString(entry.windowingMode)); + } out.endTag(null, "display"); } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index ca4fa648e543..a6bda37d6e3d 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -224,16 +224,11 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Adding display=" + display); - final DisplayInfo displayInfo = dc.getDisplayInfo(); - final Rect rect = new Rect(); - mService.mDisplaySettings.getOverscanLocked(displayInfo.name, displayInfo.uniqueId, rect); - displayInfo.overscanLeft = rect.left; - displayInfo.overscanTop = rect.top; - displayInfo.overscanRight = rect.right; - displayInfo.overscanBottom = rect.bottom; + mService.mDisplaySettings.applySettingsToDisplayLocked(dc); + if (mService.mDisplayManagerInternal != null) { mService.mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager( - displayId, displayInfo); + displayId, dc.getDisplayInfo()); dc.configureDisplayPolicy(); // Tap Listeners are supported for: diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index 733a2486215d..6c8572a864c6 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotController.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java @@ -38,7 +38,6 @@ import android.view.DisplayListCanvas; import android.view.RenderNode; import android.view.SurfaceControl; import android.view.ThreadedRenderer; -import android.view.View; import android.view.WindowManager.LayoutParams; import com.android.internal.annotations.VisibleForTesting; @@ -219,15 +218,32 @@ class TaskSnapshotController { return TaskSnapshotSurface.create(mService, token, snapshot); } - private TaskSnapshot snapshotTask(Task task) { - final AppWindowToken top = task.getTopChild(); - if (top == null) { - return null; - } - final WindowState mainWindow = top.findMainWindow(); - if (mainWindow == null) { - return null; + /** + * Find the window for a given task to take a snapshot. Top child of the task is usually the one + * we're looking for, but during app transitions, trampoline activities can appear in the + * children, which should be ignored. + */ + @Nullable private AppWindowToken findAppTokenForSnapshot(Task task) { + for (int i = task.getChildCount() - 1; i >= 0; --i) { + final AppWindowToken appWindowToken = task.getChildAt(i); + if (appWindowToken == null || !appWindowToken.isSurfaceShowing() + || appWindowToken.findMainWindow() == null) { + continue; + } + final boolean hasVisibleChild = appWindowToken.forAllWindows( + // Ensure at least one window for the top app is visible before attempting to + // take a screenshot. Visible here means that the WSA surface is shown and has + // an alpha greater than 0. + ws -> ws.mWinAnimator != null && ws.mWinAnimator.getShown() + && ws.mWinAnimator.mLastAlpha > 0f, true /* traverseTopToBottom */); + if (hasVisibleChild) { + return appWindowToken; + } } + return null; + } + + @Nullable private TaskSnapshot snapshotTask(Task task) { if (!mService.mPolicy.isScreenOn()) { if (DEBUG_SCREENSHOT) { Slog.i(TAG_WM, "Attempted to take screenshot while display was off."); @@ -235,27 +251,22 @@ class TaskSnapshotController { return null; } if (task.getSurfaceControl() == null) { + if (DEBUG_SCREENSHOT) { + Slog.w(TAG_WM, "Failed to take screenshot. No surface control for " + task); + } return null; } - if (top.hasCommittedReparentToAnimationLeash()) { + final AppWindowToken appWindowToken = findAppTokenForSnapshot(task); + if (appWindowToken == null) { if (DEBUG_SCREENSHOT) { - Slog.w(TAG_WM, "Failed to take screenshot. App is animating " + top); + Slog.w(TAG_WM, "Failed to take screenshot. No visible windows for " + task); } return null; } - - final boolean hasVisibleChild = top.forAllWindows( - // Ensure at least one window for the top app is visible before attempting to take - // a screenshot. Visible here means that the WSA surface is shown and has an alpha - // greater than 0. - ws -> (ws.mAppToken == null || ws.mAppToken.isSurfaceShowing()) - && ws.mWinAnimator != null && ws.mWinAnimator.getShown() - && ws.mWinAnimator.mLastAlpha > 0f, true); - - if (!hasVisibleChild) { + if (appWindowToken.hasCommittedReparentToAnimationLeash()) { if (DEBUG_SCREENSHOT) { - Slog.w(TAG_WM, "Failed to take screenshot. No visible windows for " + task); + Slog.w(TAG_WM, "Failed to take screenshot. App is animating " + appWindowToken); } return null; } @@ -265,19 +276,24 @@ class TaskSnapshotController { task.getBounds(mTmpRect); mTmpRect.offsetTo(0, 0); + final WindowState mainWindow = appWindowToken.findMainWindow(); + if (mainWindow == null) { + Slog.w(TAG_WM, "Failed to take screenshot. No main window for " + task); + return null; + } final GraphicBuffer buffer = SurfaceControl.captureLayers( task.getSurfaceControl().getHandle(), mTmpRect, scaleFraction); - final boolean isWindowTranslucent = mainWindow.getAttrs().format != PixelFormat.OPAQUE; if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) { if (DEBUG_SCREENSHOT) { Slog.w(TAG_WM, "Failed to take screenshot for " + task); } return null; } - return new TaskSnapshot(buffer, top.getConfiguration().orientation, + final boolean isWindowTranslucent = mainWindow.getAttrs().format != PixelFormat.OPAQUE; + return new TaskSnapshot(buffer, appWindowToken.getConfiguration().orientation, getInsets(mainWindow), isLowRamDevice /* reduced */, scaleFraction /* scale */, true /* isRealSnapshot */, task.getWindowingMode(), getSystemUiVisibility(task), - !top.fillsParent() || isWindowTranslucent); + !appWindowToken.fillsParent() || isWindowTranslucent); } private boolean shouldDisableSnapshots() { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index ea68e17732af..ab735af9a075 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -565,6 +565,8 @@ public class WindowManagerService extends IWindowManager.Stub boolean mForceResizableTasks = false; boolean mSupportsPictureInPicture = false; + boolean mSupportsFreeformWindowManagement = false; + boolean mIsPc = false; boolean mDisableTransitionAnimation = false; @@ -953,7 +955,7 @@ public class WindowManagerService extends IWindowManager.Stub com.android.internal.R.bool.config_disableTransitionAnimation); mInputManager = inputManager; // Must be before createDisplayContentLocked. mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); - mDisplaySettings = new DisplaySettings(); + mDisplaySettings = new DisplaySettings(this); mDisplaySettings.readSettingsLocked(); mPolicy = policy; @@ -6910,6 +6912,18 @@ public class WindowManagerService extends IWindowManager.Stub } } + public void setSupportsFreeformWindowManagement(boolean supportsFreeformWindowManagement) { + synchronized (mWindowMap) { + mSupportsFreeformWindowManagement = supportsFreeformWindowManagement; + } + } + + public void setIsPc(boolean isPc) { + synchronized (mWindowMap) { + mIsPc = isPc; + } + } + static int dipToPixel(int dip, DisplayMetrics displayMetrics) { return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, displayMetrics); } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 6385229b28c6..9f394ac466d6 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -3870,7 +3870,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final boolean isAccessibilityOverlay = windowInfo.type == WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY; if (TextUtils.isEmpty(windowInfo.title) && (isPanelWindow || isAccessibilityOverlay)) { - windowInfo.title = mAttrs.getTitle(); + final CharSequence title = mAttrs.getTitle(); + windowInfo.title = TextUtils.isEmpty(title) ? null : title; } windowInfo.accessibilityIdOfAnchor = mAttrs.accessibilityIdOfAnchor; windowInfo.focused = isFocused(); diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java index 08decdfc13e5..d6f9ac3a3ab9 100644 --- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java +++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.TRANSIT_ACTIVITY_CLOSE; import static android.view.WindowManager.TRANSIT_ACTIVITY_OPEN; import static android.view.WindowManager.TRANSIT_CRASHING_ACTIVITY_CLOSE; @@ -636,10 +637,15 @@ class WindowSurfacePlacer { return transit; } - // if wallpaper is animating in or out set oldWallpaper to null else to wallpaper final WindowState wallpaperTarget = mWallpaperControllerLocked.getWallpaperTarget(); - final WindowState oldWallpaper = mWallpaperControllerLocked.isWallpaperTargetAnimating() - ? null : wallpaperTarget; + final boolean showWallpaper = wallpaperTarget != null + && (wallpaperTarget.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0; + // If wallpaper is animating or wallpaperTarget doesn't have SHOW_WALLPAPER flag set, + // don't consider upgrading to wallpaper transition. + final WindowState oldWallpaper = + (mWallpaperControllerLocked.isWallpaperTargetAnimating() || !showWallpaper) + ? null + : wallpaperTarget; final ArraySet<AppWindowToken> openingApps = mService.mOpeningApps; final ArraySet<AppWindowToken> closingApps = mService.mClosingApps; final AppWindowToken topOpeningApp = getTopApp(mService.mOpeningApps, diff --git a/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java index abaed7c2b6d4..ea9967b6ea43 100644 --- a/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java +++ b/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java @@ -56,7 +56,7 @@ import com.android.server.testing.shadows.ShadowAppBackupUtils; import com.android.server.testing.shadows.ShadowBackupPolicyEnforcer; import com.android.server.testing.shadows.ShadowBinder; import com.android.server.testing.shadows.ShadowKeyValueBackupJob; -import com.android.server.testing.shadows.ShadowPerformBackupTask; +import com.android.server.testing.shadows.ShadowKeyValueBackupTask; import java.io.File; import java.util.List; import org.junit.After; @@ -72,7 +72,6 @@ import org.robolectric.shadows.ShadowContextWrapper; import org.robolectric.shadows.ShadowLooper; import org.robolectric.shadows.ShadowPackageManager; import org.robolectric.shadows.ShadowSettings; -import org.robolectric.shadows.ShadowSystemClock; @RunWith(FrameworkRobolectricTestRunner.class) @Config( @@ -668,7 +667,7 @@ public class BackupManagerServiceTest { } private void tearDownForRequestBackup() { - ShadowPerformBackupTask.reset(); + ShadowKeyValueBackupTask.reset(); } @Test @@ -755,12 +754,12 @@ public class BackupManagerServiceTest { assertThat(result).isEqualTo(BackupManager.SUCCESS); verify(mObserver).onResult(PACKAGE_1, BackupManager.ERROR_BACKUP_NOT_ALLOWED); - // TODO: We probably don't need to kick-off PerformBackupTask when list is empty + // TODO: We probably don't need to kick-off KeyValueBackupTask when list is empty tearDownForRequestBackup(); } @Test - @Config(shadows = ShadowPerformBackupTask.class) + @Config(shadows = ShadowKeyValueBackupTask.class) public void testRequestBackup_whenPackageIsKeyValue() throws Exception { setUpForRequestBackup(PACKAGE_1); BackupManagerService backupManagerService = createBackupManagerServiceForRequestBackup(); @@ -769,15 +768,15 @@ public class BackupManagerServiceTest { mShadowBackupLooper.runToEndOfTasks(); assertThat(result).isEqualTo(BackupManager.SUCCESS); - ShadowPerformBackupTask shadowTask = ShadowPerformBackupTask.getLastCreated(); + ShadowKeyValueBackupTask shadowTask = ShadowKeyValueBackupTask.getLastCreated(); assertThat(shadowTask.getQueue()).containsExactly(new BackupRequest(PACKAGE_1)); assertThat(shadowTask.getPendingFullBackups()).isEmpty(); - // TODO: Assert more about PerformBackupTask + // TODO: Assert more about KeyValueBackupTask tearDownForRequestBackup(); } @Test - @Config(shadows = ShadowPerformBackupTask.class) + @Config(shadows = ShadowKeyValueBackupTask.class) public void testRequestBackup_whenPackageIsFullBackup() throws Exception { setUpForRequestBackup(PACKAGE_1); ShadowAppBackupUtils.setAppGetsFullBackup(PACKAGE_1); @@ -787,10 +786,10 @@ public class BackupManagerServiceTest { mShadowBackupLooper.runToEndOfTasks(); assertThat(result).isEqualTo(BackupManager.SUCCESS); - ShadowPerformBackupTask shadowTask = ShadowPerformBackupTask.getLastCreated(); + ShadowKeyValueBackupTask shadowTask = ShadowKeyValueBackupTask.getLastCreated(); assertThat(shadowTask.getQueue()).isEmpty(); assertThat(shadowTask.getPendingFullBackups()).containsExactly(PACKAGE_1); - // TODO: Assert more about PerformBackupTask + // TODO: Assert more about KeyValueBackupTask tearDownForRequestBackup(); } diff --git a/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java b/services/robotests/src/com/android/server/backup/KeyValueBackupTaskTest.java index 1f6ac8d00ba1..56f5f15371ca 100644 --- a/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java +++ b/services/robotests/src/com/android/server/backup/KeyValueBackupTaskTest.java @@ -93,7 +93,7 @@ import com.android.server.EventLogTags; import com.android.server.backup.internal.BackupHandler; import com.android.server.backup.internal.BackupRequest; import com.android.server.backup.internal.OnTaskFinishedListener; -import com.android.server.backup.internal.PerformBackupTask; +import com.android.server.backup.internal.KeyValueBackupTask; import com.android.server.backup.testing.PackageData; import com.android.server.backup.testing.TransportData; import com.android.server.backup.testing.TransportTestUtils; @@ -155,7 +155,7 @@ import java.util.stream.Stream; @SystemLoaderPackages({"com.android.server.backup", "android.app.backup"}) @SystemLoaderClasses({IBackupTransport.class, IBackupAgent.class, PackageInfo.class}) @Presubmit -public class PerformBackupTaskTest { +public class KeyValueBackupTaskTest { private static final PackageData PACKAGE_1 = keyValuePackage(1); private static final PackageData PACKAGE_2 = keyValuePackage(2); @@ -233,8 +233,8 @@ public class PerformBackupTaskTest { public void testRunTask_whenQueueEmpty_updatesBookkeeping() throws Exception { TransportMock transportMock = setUpInitializedTransport(mTransport); when(mBackupManagerService.getCurrentToken()).thenReturn(0L); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, true); runTask(task); @@ -249,8 +249,8 @@ public class PerformBackupTaskTest { public void testRunTask_whenQueueEmpty_releasesWakeLock() throws Exception { TransportMock transportMock = setUpInitializedTransport(mTransport); when(mBackupManagerService.getCurrentToken()).thenReturn(0L); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, true); runTask(task); @@ -262,8 +262,8 @@ public class PerformBackupTaskTest { public void testRunTask_whenQueueEmpty_doesNotProduceData() throws Exception { TransportMock transportMock = setUpTransport(mTransport); when(mBackupManagerService.getCurrentToken()).thenReturn(0L); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, true); runTask(task); @@ -276,8 +276,8 @@ public class PerformBackupTaskTest { public void testRunTask_whenQueueEmpty_doesNotCallTransport() throws Exception { TransportMock transportMock = setUpInitializedTransport(mTransport); when(mBackupManagerService.getCurrentToken()).thenReturn(0L); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, true); runTask(task); @@ -291,8 +291,8 @@ public class PerformBackupTaskTest { public void testRunTask_whenQueueEmpty_notifiesCorrectly() throws Exception { TransportMock transportMock = setUpInitializedTransport(mTransport); when(mBackupManagerService.getCurrentToken()).thenReturn(0L); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, true); runTask(task); @@ -305,8 +305,8 @@ public class PerformBackupTaskTest { @Test public void testRunTask_whenQueueEmpty_doesNotChangeStateFiles() throws Exception { TransportMock transportMock = setUpInitializedTransport(mTransport); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, true); Files.write(getStateFile(mTransport, PM_PACKAGE), "pmState".getBytes()); Files.write(getStateFile(mTransport, PACKAGE_1), "packageState".getBytes()); @@ -323,8 +323,8 @@ public class PerformBackupTaskTest { public void testRunTask_whenOnePackageAndTransportUnavailable() throws Exception { TransportMock transportMock = setUpInitializedTransport(mTransport.unavailable()); setUpAgentWithData(PACKAGE_1); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -338,8 +338,8 @@ public class PerformBackupTaskTest { public void testRunTask_whenOnePackage_logsBackupStartEvent() throws Exception { TransportMock transportMock = setUpInitializedTransport(mTransport); setUpAgentWithData(PACKAGE_1); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -351,8 +351,8 @@ public class PerformBackupTaskTest { public void testRunTask_whenOnePackage_releasesWakeLock() throws Exception { TransportMock transportMock = setUpInitializedTransport(mTransport); setUpAgentWithData(PACKAGE_1); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -367,8 +367,8 @@ public class PerformBackupTaskTest { mBackupManagerService.setCurrentToken(0L); when(transportMock.transport.getCurrentRestoreSet()).thenReturn(1234L); setUpAgentWithData(PACKAGE_1); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -386,8 +386,8 @@ public class PerformBackupTaskTest { throws Exception { TransportMock transportMock = setUpInitializedTransport(mTransport); AgentMock agentMock = setUpAgentWithData(PACKAGE_1); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, false, @@ -404,8 +404,8 @@ public class PerformBackupTaskTest { throws Exception { TransportMock transportMock = setUpInitializedTransport(mTransport); AgentMock agentMock = setUpAgentWithData(PACKAGE_1); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, true, @@ -423,8 +423,8 @@ public class PerformBackupTaskTest { setUpAgentWithData(PACKAGE_1); PackageManagerBackupAgent pmAgent = spy(createPmAgent()); when(mBackupManagerService.makeMetadataAgent()).thenReturn(forward(pmAgent)); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, true, @@ -441,8 +441,8 @@ public class PerformBackupTaskTest { setUpAgentWithData(PACKAGE_1); PackageManagerBackupAgent pmAgent = spy(createPmAgent()); when(mBackupManagerService.makeMetadataAgent()).thenReturn(forward(pmAgent)); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, true, @@ -460,8 +460,8 @@ public class PerformBackupTaskTest { setUpAgentWithData(PACKAGE_1); PackageManagerBackupAgent pmAgent = spy(createPmAgent()); when(mBackupManagerService.makeMetadataAgent()).thenReturn(forward(pmAgent)); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, false, @@ -478,8 +478,8 @@ public class PerformBackupTaskTest { TransportMock transportMock = setUpTransport(mTransport); // Need 2 packages to be able to verify state of package not involved in the task setUpAgentsWithData(PACKAGE_1, PACKAGE_2); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); deletePmStateFile(); Files.write(getStateFile(mTransport, PACKAGE_2), "package2State".getBytes()); @@ -499,8 +499,8 @@ public class PerformBackupTaskTest { throws Exception { TransportMock transportMock = setUpTransport(mTransport); setUpAgentsWithData(PACKAGE_1, PACKAGE_2); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); createPmStateFile(); Files.write(getStateFile(mTransport, PACKAGE_2), "package2State".getBytes()); @@ -518,8 +518,8 @@ public class PerformBackupTaskTest { when(transportMock.transport.initializeDevice()) .thenReturn(BackupTransport.TRANSPORT_ERROR); AgentMock agentMock = setUpAgentWithData(PACKAGE_1); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); deletePmStateFile(); @@ -539,8 +539,8 @@ public class PerformBackupTaskTest { TransportMock transportMock = setUpTransport(mTransport); when(transportMock.transport.initializeDevice()).thenThrow(RemoteException.class); AgentMock agentMock = setUpAgentWithData(PACKAGE_1); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); deletePmStateFile(); @@ -558,8 +558,8 @@ public class PerformBackupTaskTest { public void testRunTask_whenPackageNotEligibleForBackup() throws Exception { TransportMock transportMock = setUpInitializedTransport(mTransport); AgentMock agentMock = setUpAgentWithData(PACKAGE_1.backupNotAllowed()); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -577,8 +577,8 @@ public class PerformBackupTaskTest { TransportMock transportMock = setUpInitializedTransport(mTransport); PackageData packageData = fullBackupPackage(1); AgentMock agentMock = setUpAgentWithData(packageData); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, packageData); runTask(task); @@ -594,8 +594,8 @@ public class PerformBackupTaskTest { public void testRunTask_whenPackageIsStopped() throws Exception { TransportMock transportMock = setUpInitializedTransport(mTransport); AgentMock agentMock = setUpAgentWithData(PACKAGE_1.stopped()); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -610,8 +610,8 @@ public class PerformBackupTaskTest { public void testRunTask_whenPackageUnknown() throws Exception { TransportMock transportMock = setUpInitializedTransport(mTransport); // Not calling setUpAgent() - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -638,8 +638,8 @@ public class PerformBackupTaskTest { argThat(workSource -> workSource.get(0) == PACKAGE_1.uid)); verify(mBackupManagerService, never()).setWorkSource(null); }); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -658,8 +658,8 @@ public class PerformBackupTaskTest { public void testRunTask_whenAgentUnavailable() throws Exception { TransportMock transportMock = setUpInitializedTransport(mTransport); setUpAgent(PACKAGE_1.unavailable()); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -676,8 +676,8 @@ public class PerformBackupTaskTest { doThrow(SecurityException.class) .when(mBackupManagerService) .bindToAgentSynchronous(argThat(applicationInfo(PACKAGE_1)), anyInt()); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -693,8 +693,8 @@ public class PerformBackupTaskTest { when(transportMock.transport.getBackupQuota(PACKAGE_1.packageName, false)) .thenThrow(DeadObjectException.class); setUpAgent(PACKAGE_1); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -710,8 +710,8 @@ public class PerformBackupTaskTest { when(transportMock.transport.getBackupQuota(PACKAGE_1.packageName, false)) .thenThrow(DeadObjectException.class); setUpAgent(PACKAGE_1); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -726,8 +726,8 @@ public class PerformBackupTaskTest { when(transportMock.transport.getBackupQuota(PACKAGE_1.packageName, false)) .thenThrow(DeadObjectException.class); setUpAgent(PACKAGE_1); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); Files.write(getStateFile(mTransport, PACKAGE_1), "packageState".getBytes()); @@ -745,8 +745,8 @@ public class PerformBackupTaskTest { when(transportMock.transport.getBackupQuota(PACKAGE_1.packageName, false)) .thenThrow(DeadObjectException.class); setUpAgent(PACKAGE_1); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -758,7 +758,7 @@ public class PerformBackupTaskTest { /** * For local agents the exception is thrown in our stack, so it hits the catch clause around - * invocation earlier than the {@link PerformBackupTask#operationComplete(long)} code-path, + * invocation earlier than the {@link KeyValueBackupTask#operationComplete(long)} code-path, * invalidating the latter. Note that this happens because {@link * BackupManagerService#opComplete(int, long)} schedules the actual execution to the backup * handler. @@ -772,8 +772,8 @@ public class PerformBackupTaskTest { (oldState, dataOutput, newState) -> { throw new RuntimeException(); }); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -794,8 +794,8 @@ public class PerformBackupTaskTest { int flags = BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED; when(transportMock.transport.getTransportFlags()).thenReturn(flags); AgentMock agentMock = setUpAgent(PACKAGE_1); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -808,8 +808,8 @@ public class PerformBackupTaskTest { public void testRunTask_whenTransportDoesNotProvidesFlags() throws Exception { TransportMock transportMock = setUpInitializedTransport(mTransport); AgentMock agentMock = setUpAgent(PACKAGE_1); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -826,8 +826,8 @@ public class PerformBackupTaskTest { List<AgentMock> agentMocks = setUpAgents(PACKAGE_1, PACKAGE_2); BackupAgent agent1 = agentMocks.get(0).agent; BackupAgent agent2 = agentMocks.get(1).agent; - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1, @@ -843,8 +843,8 @@ public class PerformBackupTaskTest { public void testRunTask_whenTransportChangeFlagsAfterTaskCreation() throws Exception { TransportMock transportMock = setUpInitializedTransport(mTransport); AgentMock agentMock = setUpAgent(PACKAGE_1); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); int flags = BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED; when(transportMock.transport.getTransportFlags()).thenReturn(flags); @@ -866,8 +866,8 @@ public class PerformBackupTaskTest { writeData(dataOutput, prohibitedChar + "key", "data".getBytes()); writeState(newState, "newState".getBytes()); }); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -887,8 +887,8 @@ public class PerformBackupTaskTest { writeData(dataOutput, prohibitedChar + "key", "data".getBytes()); writeState(newState, "newState".getBytes()); }); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); Files.write(getStateFile(mTransport, PACKAGE_1), "oldState".getBytes()); @@ -911,8 +911,8 @@ public class PerformBackupTaskTest { writeData(dataOutput, prohibitedChar + "key", "data".getBytes()); writeState(newState, "newState".getBytes()); }); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); Files.write(getStateFile(mTransport, PACKAGE_1), "oldState".getBytes()); @@ -933,8 +933,8 @@ public class PerformBackupTaskTest { writeData(dataOutput, prohibitedChar + "key", "data".getBytes()); writeState(newState, "newState".getBytes()); }); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); Files.write(getStateFile(mTransport, PACKAGE_1), "oldState".getBytes()); @@ -956,8 +956,8 @@ public class PerformBackupTaskTest { writeData(dataOutput, prohibitedChar + "key", "data".getBytes()); writeState(newState, "newState".getBytes()); }); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); Files.write(getStateFile(mTransport, PACKAGE_1), "oldState".getBytes()); @@ -986,8 +986,8 @@ public class PerformBackupTaskTest { writeData(dataOutput, "key", "data".getBytes()); writeState(newState, "newState".getBytes()); }); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1, @@ -1011,8 +1011,8 @@ public class PerformBackupTaskTest { (oldState, dataOutput, newState) -> { // No-op }); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -1030,8 +1030,8 @@ public class PerformBackupTaskTest { (oldState, dataOutput, newState) -> { // No-op }); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -1049,8 +1049,8 @@ public class PerformBackupTaskTest { (oldState, dataOutput, newState) -> { // No-op }); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -1068,8 +1068,8 @@ public class PerformBackupTaskTest { (oldState, dataOutput, newState) -> { // No-op }); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -1086,8 +1086,8 @@ public class PerformBackupTaskTest { (oldState, dataOutput, newState) -> { // No-op }); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -1113,8 +1113,8 @@ public class PerformBackupTaskTest { writeData(dataOutput, "key2", "data2".getBytes()); writeState(newState, "newState".getBytes()); }); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -1138,8 +1138,8 @@ public class PerformBackupTaskTest { argThat(packageInfo(PACKAGE_1)), any(), anyInt())) .thenReturn(BackupTransport.TRANSPORT_OK); setUpAgentWithData(PACKAGE_1); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -1159,8 +1159,8 @@ public class PerformBackupTaskTest { writeData(dataOutput, "key", "data".getBytes()); writeState(newState, "newState".getBytes()); }); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -1179,8 +1179,8 @@ public class PerformBackupTaskTest { when(transportMock.transport.performBackup( argThat(packageInfo(PACKAGE_1)), any(), anyInt())) .then(copyBackupDataTo(backupData)); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -1193,8 +1193,8 @@ public class PerformBackupTaskTest { public void testRunTask_whenFinishBackupSucceeds_notifiesCorrectly() throws Exception { TransportMock transportMock = setUpInitializedTransport(mTransport); setUpAgentWithData(PACKAGE_1); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -1209,8 +1209,8 @@ public class PerformBackupTaskTest { public void testRunTask_whenFinishBackupSucceeds_updatesBookkeeping() throws Exception { TransportMock transportMock = setUpInitializedTransport(mTransport); setUpAgentWithData(PACKAGE_1); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -1225,8 +1225,8 @@ public class PerformBackupTaskTest { argThat(packageInfo(PACKAGE_1)), any(), anyInt())) .thenReturn(BackupTransport.TRANSPORT_PACKAGE_REJECTED); setUpAgentWithData(PACKAGE_1); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -1242,8 +1242,8 @@ public class PerformBackupTaskTest { argThat(packageInfo(PACKAGE_1)), any(), anyInt())) .thenReturn(BackupTransport.TRANSPORT_PACKAGE_REJECTED); setUpAgentWithData(PACKAGE_1); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); Files.write(getStateFile(mTransport, PACKAGE_1), "oldState".getBytes()); @@ -1262,8 +1262,8 @@ public class PerformBackupTaskTest { argThat(packageInfo(PACKAGE_1)), any(), anyInt())) .thenReturn(BackupTransport.TRANSPORT_PACKAGE_REJECTED); setUpAgentWithData(PACKAGE_1); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -1279,8 +1279,8 @@ public class PerformBackupTaskTest { argThat(packageInfo(PACKAGE_1)), any(), anyInt())) .thenReturn(BackupTransport.TRANSPORT_PACKAGE_REJECTED); setUpAgentWithData(PACKAGE_1); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -1297,8 +1297,8 @@ public class PerformBackupTaskTest { argThat(packageInfo(PACKAGE_1)), any(), anyInt())) .thenReturn(BackupTransport.TRANSPORT_PACKAGE_REJECTED); setUpAgentWithData(PACKAGE_1); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -1316,8 +1316,8 @@ public class PerformBackupTaskTest { argThat(packageInfo(PACKAGE_2)), any(), anyInt())) .thenReturn(BackupTransport.TRANSPORT_OK); setUpAgentsWithData(PACKAGE_1, PACKAGE_2); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1, @@ -1340,8 +1340,8 @@ public class PerformBackupTaskTest { argThat(packageInfo(PACKAGE_2)), any(), anyInt())) .thenReturn(BackupTransport.TRANSPORT_PACKAGE_REJECTED); setUpAgentsWithData(PACKAGE_1, PACKAGE_2); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1, @@ -1363,8 +1363,8 @@ public class PerformBackupTaskTest { argThat(packageInfo(PACKAGE_1)), any(), anyInt())) .thenReturn(BackupTransport.TRANSPORT_QUOTA_EXCEEDED); AgentMock agentMock = setUpAgentWithData(PACKAGE_1); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -1385,8 +1385,8 @@ public class PerformBackupTaskTest { argThat(packageInfo(PACKAGE_1)), any(), anyInt())) .thenReturn(BackupTransport.TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED); setUpAgentWithData(PACKAGE_1); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, true, @@ -1433,8 +1433,8 @@ public class PerformBackupTaskTest { writeState(newState, "stateForNonIncremental".getBytes()); } }); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, false, @@ -1472,8 +1472,8 @@ public class PerformBackupTaskTest { argThat(packageInfo(PACKAGE_1)), any(), anyInt())) .thenReturn(BackupTransport.TRANSPORT_ERROR); setUpAgentWithData(PACKAGE_1); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -1490,8 +1490,8 @@ public class PerformBackupTaskTest { argThat(packageInfo(PACKAGE_1)), any(), anyInt())) .thenReturn(BackupTransport.TRANSPORT_ERROR); setUpAgentWithData(PACKAGE_1); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -1506,8 +1506,8 @@ public class PerformBackupTaskTest { argThat(packageInfo(PACKAGE_1)), any(), anyInt())) .thenReturn(BackupTransport.TRANSPORT_ERROR); setUpAgentWithData(PACKAGE_1); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -1524,8 +1524,8 @@ public class PerformBackupTaskTest { argThat(packageInfo(PACKAGE_1)), any(), anyInt())) .thenReturn(BackupTransport.TRANSPORT_ERROR); setUpAgentWithData(PACKAGE_1); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); Files.write(getStateFile(mTransport, PACKAGE_1), "oldState".getBytes()); @@ -1544,8 +1544,8 @@ public class PerformBackupTaskTest { when(transportMock.transport.getBackupQuota(PM_PACKAGE.packageName, false)) .thenThrow(DeadObjectException.class); setUpAgentWithData(PACKAGE_1); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -1563,8 +1563,8 @@ public class PerformBackupTaskTest { TransportMock transportMock = setUpInitializedTransport(mTransport); PackageManagerBackupAgent pmAgent = createThrowingPmAgent(new RuntimeException()); when(mBackupManagerService.makeMetadataAgent()).thenReturn(pmAgent); - PerformBackupTask task = - createPerformBackupTask( + KeyValueBackupTask task = + createKeyValueBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); runTask(task); @@ -1577,7 +1577,7 @@ public class PerformBackupTaskTest { new RuntimeException().toString()); } - private void runTask(PerformBackupTask task) { + private void runTask(KeyValueBackupTask task) { // Pretend we are not on the main-thread to prevent RemoteCall from complaining mShadowMainLooper.setCurrentThread(false); task.run(); @@ -1609,7 +1609,7 @@ public class PerformBackupTaskTest { private Path getTemporaryStateFile(TransportData transport, PackageData packageData) { return getStateDirectory(transport) - .resolve(packageData.packageName + PerformBackupTask.NEW_STATE_FILE_SUFFIX); + .resolve(packageData.packageName + KeyValueBackupTask.NEW_STATE_FILE_SUFFIX); } private Path getStagingDirectory() { @@ -1618,7 +1618,7 @@ public class PerformBackupTaskTest { private Path getStagingFile(PackageData packageData) { return getStagingDirectory() - .resolve(packageData.packageName + PerformBackupTask.STAGING_FILE_SUFFIX); + .resolve(packageData.packageName + KeyValueBackupTask.STAGING_FILE_SUFFIX); } private List<AgentMock> setUpAgents(PackageData... packageNames) { @@ -1695,12 +1695,12 @@ public class PerformBackupTaskTest { return agentMock; } - private PerformBackupTask createPerformBackupTask( + private KeyValueBackupTask createKeyValueBackupTask( TransportClient transportClient, String transportDirName, PackageData... packages) { - return createPerformBackupTask(transportClient, transportDirName, false, packages); + return createKeyValueBackupTask(transportClient, transportDirName, false, packages); } - private PerformBackupTask createPerformBackupTask( + private KeyValueBackupTask createKeyValueBackupTask( TransportClient transportClient, String transportDirName, boolean nonIncremental, @@ -1714,8 +1714,8 @@ public class PerformBackupTaskTest { // mOldJournal is a mock, but it would be the value returned by BMS.getJournal() now mBackupManagerService.setJournal(null); mWakeLock.acquire(); - PerformBackupTask task = - new PerformBackupTask( + KeyValueBackupTask task = + new KeyValueBackupTask( mBackupManagerService, transportClient, transportDirName, @@ -1884,14 +1884,14 @@ public class PerformBackupTaskTest { private void assertBackupPendingFor(PackageData packageData) throws IOException { String packageName = packageData.packageName; - // We verify the current journal, NOT the old one passed to PerformBackupTask constructor + // We verify the current journal, NOT the old one passed to KeyValueBackupTask constructor assertThat(mBackupManagerService.getJournal().getPackages()).contains(packageName); assertThat(mBackupManagerService.getPendingBackups()).containsKey(packageName); } private void assertBackupNotPendingFor(PackageData packageData) throws IOException { String packageName = packageData.packageName; - // We verify the current journal, NOT the old one passed to PerformBackupTask constructor + // We verify the current journal, NOT the old one passed to KeyValueBackupTask constructor assertJournalDoesNotContain(mBackupManagerService.getJournal(), packageName); assertThat(mBackupManagerService.getPendingBackups()).doesNotContainKey(packageName); } diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowPerformBackupTask.java b/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupTask.java index 7c10377d0ec7..f22cdb8f5860 100644 --- a/services/robotests/src/com/android/server/testing/shadows/ShadowPerformBackupTask.java +++ b/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupTask.java @@ -24,7 +24,7 @@ import com.android.server.backup.BackupManagerService; import com.android.server.backup.DataChangedJournal; import com.android.server.backup.internal.BackupRequest; import com.android.server.backup.internal.OnTaskFinishedListener; -import com.android.server.backup.internal.PerformBackupTask; +import com.android.server.backup.internal.KeyValueBackupTask; import com.android.server.backup.transport.TransportClient; import org.robolectric.annotation.Implementation; @@ -33,17 +33,17 @@ import org.robolectric.annotation.Implements; import java.util.ArrayList; import java.util.List; -@Implements(PerformBackupTask.class) -public class ShadowPerformBackupTask { - @Nullable private static ShadowPerformBackupTask sLastShadow; +@Implements(KeyValueBackupTask.class) +public class ShadowKeyValueBackupTask { + @Nullable private static ShadowKeyValueBackupTask sLastShadow; /** - * Retrieves the shadow for the last {@link PerformBackupTask} object created. + * Retrieves the shadow for the last {@link KeyValueBackupTask} object created. * * @return The shadow or {@code null} if no object created since last {@link #reset()}. */ @Nullable - public static ShadowPerformBackupTask getLastCreated() { + public static ShadowKeyValueBackupTask getLastCreated() { return sLastShadow; } @@ -52,7 +52,7 @@ public class ShadowPerformBackupTask { } private OnTaskFinishedListener mListener; - private ArrayList<BackupRequest> mQueue; + private List<BackupRequest> mQueue; private List<String> mPendingFullBackups; @Implementation @@ -60,7 +60,7 @@ public class ShadowPerformBackupTask { BackupManagerService backupManagerService, TransportClient transportClient, String dirName, - ArrayList<BackupRequest> queue, + List<BackupRequest> queue, @Nullable DataChangedJournal journal, IBackupObserver observer, IBackupManagerMonitor monitor, @@ -76,7 +76,7 @@ public class ShadowPerformBackupTask { @Implementation public void execute() { - mListener.onFinished("ShadowPerformBackupTask.execute()"); + mListener.onFinished("ShadowKeyValueBackupTask.execute()"); } public List<BackupRequest> getQueue() { diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java index d3a3090956a7..5253cb4da32e 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java @@ -453,6 +453,20 @@ public class MagnificationControllerTest { } @Test + public void testResetIfNeeded_resetsOnlyIfLastMagnifyingServiceIsDisabled() { + mMagnificationController.register(); + PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER; + mMagnificationController + .setScale(2.0f, startCenter.x, startCenter.y, false, SERVICE_ID_1); + mMagnificationController + .setScale(1.5f, startCenter.x, startCenter.y, false, SERVICE_ID_2); + assertFalse(mMagnificationController.resetIfNeeded(SERVICE_ID_1)); + assertTrue(mMagnificationController.isMagnifying()); + assertTrue(mMagnificationController.resetIfNeeded(SERVICE_ID_2)); + assertFalse(mMagnificationController.isMagnifying()); + } + + @Test public void testSetUserId_resetsOnlyIfIdChanges() { final int userId1 = 1; final int userId2 = 2; diff --git a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java index 227a70f7a5a3..ee484d6a5a50 100644 --- a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java +++ b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java @@ -17,6 +17,7 @@ package com.android.server.am; import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; @@ -43,6 +44,7 @@ import static java.lang.Integer.MAX_VALUE; import android.app.ActivityManager.RecentTaskInfo; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityTaskManager; +import android.app.WindowConfiguration; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; @@ -51,7 +53,6 @@ import android.content.pm.UserInfo; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Bundle; -import android.os.Debug; import android.os.Looper; import android.os.RemoteException; import android.os.SystemClock; @@ -276,13 +277,11 @@ public class RecentTasksTest extends ActivityTestsBase { public void testAddTaskCompatibleActivityType_expectRemove() throws Exception { // Test with undefined activity type since the type is not persisted by the task persister // and we want to ensure that a new task will match a restored task - Configuration config1 = new Configuration(); - config1.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED); TaskRecord task1 = createTaskBuilder(".Task1") .setFlags(FLAG_ACTIVITY_NEW_TASK) .setStack(mStack) .build(); - task1.onConfigurationChanged(config1); + setTaskActivityType(task1, ACTIVITY_TYPE_UNDEFINED); assertTrue(task1.getActivityType() == ACTIVITY_TYPE_UNDEFINED); mRecentTasks.add(task1); mCallbacksRecorder.clear(); @@ -302,14 +301,12 @@ public class RecentTasksTest extends ActivityTestsBase { @Test public void testAddTaskCompatibleActivityTypeDifferentUser_expectNoRemove() throws Exception { - Configuration config1 = new Configuration(); - config1.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED); TaskRecord task1 = createTaskBuilder(".Task1") .setFlags(FLAG_ACTIVITY_NEW_TASK) .setStack(mStack) .setUserId(TEST_USER_0_ID) .build(); - task1.onConfigurationChanged(config1); + setTaskActivityType(task1, ACTIVITY_TYPE_UNDEFINED); assertTrue(task1.getActivityType() == ACTIVITY_TYPE_UNDEFINED); mRecentTasks.add(task1); mCallbacksRecorder.clear(); @@ -329,24 +326,20 @@ public class RecentTasksTest extends ActivityTestsBase { @Test public void testAddTaskCompatibleWindowingMode_expectRemove() throws Exception { - Configuration config1 = new Configuration(); - config1.windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED); TaskRecord task1 = createTaskBuilder(".Task1") .setFlags(FLAG_ACTIVITY_NEW_TASK) .setStack(mStack) .build(); - task1.onConfigurationChanged(config1); + setTaskWindowingMode(task1, WINDOWING_MODE_UNDEFINED); assertTrue(task1.getWindowingMode() == WINDOWING_MODE_UNDEFINED); mRecentTasks.add(task1); mCallbacksRecorder.clear(); - Configuration config2 = new Configuration(); - config2.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); TaskRecord task2 = createTaskBuilder(".Task1") .setFlags(FLAG_ACTIVITY_NEW_TASK) .setStack(mStack) .build(); - task2.onConfigurationChanged(config2); + setTaskWindowingMode(task2, WINDOWING_MODE_FULLSCREEN); assertTrue(task2.getWindowingMode() == WINDOWING_MODE_FULLSCREEN); mRecentTasks.add(task2); @@ -359,23 +352,19 @@ public class RecentTasksTest extends ActivityTestsBase { @Test public void testAddTaskIncompatibleWindowingMode_expectNoRemove() throws Exception { - Configuration config1 = new Configuration(); - config1.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); TaskRecord task1 = createTaskBuilder(".Task1") .setFlags(FLAG_ACTIVITY_NEW_TASK) .setStack(mStack) .build(); - task1.onConfigurationChanged(config1); + setTaskWindowingMode(task1, WINDOWING_MODE_FULLSCREEN); assertTrue(task1.getWindowingMode() == WINDOWING_MODE_FULLSCREEN); mRecentTasks.add(task1); - Configuration config2 = new Configuration(); - config2.windowConfiguration.setWindowingMode(WINDOWING_MODE_PINNED); TaskRecord task2 = createTaskBuilder(".Task1") .setFlags(FLAG_ACTIVITY_NEW_TASK) .setStack(mStack) .build(); - task2.onConfigurationChanged(config2); + setTaskWindowingMode(task2, WINDOWING_MODE_PINNED); assertTrue(task2.getWindowingMode() == WINDOWING_MODE_PINNED); mRecentTasks.add(task2); @@ -644,6 +633,43 @@ public class RecentTasksTest extends ActivityTestsBase { } @Test + public void testRemoveAllVisibleTasks() throws Exception { + mRecentTasks.setParameters(-1 /* min */, 3 /* max */, 100 /* ms */); + + // Create some set of tasks, some of which are visible and some are not + TaskRecord t1 = createTaskBuilder("com.android.pkg1", ".Task1").build(); + mRecentTasks.add(t1); + mRecentTasks.add(setTaskActivityType( + createTaskBuilder("com.android.pkg1", ".HomeTask").build(), + ACTIVITY_TYPE_HOME)); + TaskRecord t2 = createTaskBuilder("com.android.pkg2", ".Task2").build(); + mRecentTasks.add(t2); + mRecentTasks.add(setTaskWindowingMode( + createTaskBuilder("com.android.pkg1", ".PipTask").build(), + WINDOWING_MODE_PINNED)); + TaskRecord t3 = createTaskBuilder("com.android.pkg3", ".Task3").build(); + mRecentTasks.add(t3); + + // Create some more tasks that are out of visible range, but are still visible + TaskRecord t4 = createTaskBuilder("com.android.pkg3", ".Task4").build(); + mRecentTasks.add(t4); + TaskRecord t5 = createTaskBuilder("com.android.pkg3", ".Task5").build(); + mRecentTasks.add(t5); + + // Create some more tasks that are out of the active session range, but are still visible + TaskRecord t6 = createTaskBuilder("com.android.pkg3", ".Task6").build(); + t6.lastActiveTime = SystemClock.elapsedRealtime() - 200; + mRecentTasks.add(t6); + TaskRecord t7 = createTaskBuilder("com.android.pkg3", ".Task7").build(); + t7.lastActiveTime = SystemClock.elapsedRealtime() - 200; + mRecentTasks.add(t7); + + // Remove all the visible tasks and ensure that they are removed + mRecentTasks.removeAllVisibleTasks(); + assertTrimmed(t1, t2, t3, t4, t5, t6, t7); + } + + @Test public void testNotRecentsComponent_denyApiAccess() throws Exception { doReturn(PackageManager.PERMISSION_DENIED).when(mService) .checkGetTasksPermission(anyString(), anyInt(), anyInt()); @@ -754,6 +780,22 @@ public class RecentTasksTest extends ActivityTestsBase { return task; } + private TaskRecord setTaskActivityType(TaskRecord task, + @WindowConfiguration.ActivityType int activityType) { + Configuration config1 = new Configuration(); + config1.windowConfiguration.setActivityType(activityType); + task.onConfigurationChanged(config1); + return task; + } + + private TaskRecord setTaskWindowingMode(TaskRecord task, + @WindowConfiguration.WindowingMode int windowingMode) { + Configuration config1 = new Configuration(); + config1.windowConfiguration.setWindowingMode(windowingMode); + task.onConfigurationChanged(config1); + return task; + } + private boolean arrayContainsUser(int[] userIds, int targetUserId) { Arrays.sort(userIds); return Arrays.binarySearch(userIds, targetUserId) >= 0; @@ -880,7 +922,7 @@ public class RecentTasksTest extends ActivityTestsBase { } @Override - public void onRecentTaskRemoved(TaskRecord task, boolean wasTrimmed) { + public void onRecentTaskRemoved(TaskRecord task, boolean wasTrimmed, boolean killProcess) { if (wasTrimmed) { trimmed.add(task); } diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplaySettingsTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplaySettingsTests.java new file mode 100644 index 000000000000..0ea56ed146fc --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/wm/DisplaySettingsTests.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2018 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.wm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import android.app.WindowConfiguration; +import android.platform.test.annotations.Presubmit; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.Display; +import android.view.DisplayInfo; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; + +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class DisplaySettingsTests extends WindowTestsBase { + + private File mTestFolder; + private DisplaySettings mTarget; + + private DisplayContent mPrimaryDisplay; + private DisplayContent mSecondaryDisplay; + + @Before + public void setUp() throws Exception { + super.setUp(); + + mTestFolder = InstrumentationRegistry.getContext().getCacheDir(); + deleteRecursively(mTestFolder); + + sWm.setSupportsFreeformWindowManagement(false); + sWm.setIsPc(false); + + mTarget = new DisplaySettings(sWm, mTestFolder); + mTarget.readSettingsLocked(); + + mPrimaryDisplay = sWm.getDefaultDisplayContentLocked(); + mSecondaryDisplay = createNewDisplay(); + assertNotEquals(Display.DEFAULT_DISPLAY, mSecondaryDisplay.getDisplayId()); + } + + @Test + public void testPrimaryDisplayDefaultToFullscreenWithoutFreeformSupport() { + mTarget.applySettingsToDisplayLocked(mPrimaryDisplay); + + assertEquals(WindowConfiguration.WINDOWING_MODE_FULLSCREEN, + mPrimaryDisplay.getWindowingMode()); + } + + @Test + public void testPrimaryDisplayDefaultToFullscreenWithFreeformSupportNonPc() { + sWm.setSupportsFreeformWindowManagement(true); + + mTarget.applySettingsToDisplayLocked(mPrimaryDisplay); + + assertEquals(WindowConfiguration.WINDOWING_MODE_FULLSCREEN, + mPrimaryDisplay.getWindowingMode()); + } + + @Test + public void testPrimaryDisplayDefaultToFreeformWithFreeformIsPc() { + sWm.setSupportsFreeformWindowManagement(true); + sWm.setIsPc(true); + + mTarget.applySettingsToDisplayLocked(mPrimaryDisplay); + + assertEquals(WindowConfiguration.WINDOWING_MODE_FREEFORM, + mPrimaryDisplay.getWindowingMode()); + } + + @Test + public void testSecondaryDisplayDefaultToFullscreenWithoutFreeformSupport() { + mTarget.applySettingsToDisplayLocked(mSecondaryDisplay); + + assertEquals(WindowConfiguration.WINDOWING_MODE_FULLSCREEN, + mSecondaryDisplay.getWindowingMode()); + } + + @Test + public void testSecondaryDisplayDefaultToFreeformWithFreeformSupportNonPc() { + sWm.setSupportsFreeformWindowManagement(true); + + mTarget.applySettingsToDisplayLocked(mSecondaryDisplay); + + assertEquals(WindowConfiguration.WINDOWING_MODE_FREEFORM, + mSecondaryDisplay.getWindowingMode()); + } + + @Test + public void testSecondaryDisplayDefaultToFreeformWithFreeformSupportIsPc() { + sWm.setSupportsFreeformWindowManagement(true); + sWm.setIsPc(true); + + mTarget.applySettingsToDisplayLocked(mSecondaryDisplay); + + assertEquals(WindowConfiguration.WINDOWING_MODE_FREEFORM, + mSecondaryDisplay.getWindowingMode()); + } + + @Test + public void testDefaultToZeroOverscan() { + mTarget.applySettingsToDisplayLocked(mPrimaryDisplay); + + assertOverscan(mPrimaryDisplay, 0 /* left */, 0 /* top */, 0 /* right */, 0 /* bottom */); + } + + @Test + public void testPersistOverscanInSameInstance() { + final DisplayInfo info = mPrimaryDisplay.getDisplayInfo(); + mTarget.setOverscanLocked(info.uniqueId, info.name, 1 /* left */, 2 /* top */, + 3 /* right */, 4 /* bottom */); + + mTarget.applySettingsToDisplayLocked(mPrimaryDisplay); + + assertOverscan(mPrimaryDisplay, 1 /* left */, 2 /* top */, 3 /* right */, 4 /* bottom */); + } + + @Test + public void testPersistOverscanAcrossInstances() { + final DisplayInfo info = mPrimaryDisplay.getDisplayInfo(); + mTarget.setOverscanLocked(info.uniqueId, info.name, 1 /* left */, 2 /* top */, + 3 /* right */, 4 /* bottom */); + mTarget.writeSettingsLocked(); + + DisplaySettings target = new DisplaySettings(sWm, mTestFolder); + target.readSettingsLocked(); + + target.applySettingsToDisplayLocked(mPrimaryDisplay); + + assertOverscan(mPrimaryDisplay, 1 /* left */, 2 /* top */, 3 /* right */, 4 /* bottom */); + } + + private static void assertOverscan(DisplayContent display, int left, int top, int right, + int bottom) { + final DisplayInfo info = display.getDisplayInfo(); + + assertEquals(left, info.overscanLeft); + assertEquals(top, info.overscanTop); + assertEquals(right, info.overscanRight); + assertEquals(bottom, info.overscanBottom); + } + + private static boolean deleteRecursively(File file) { + if (file.isFile()) { + return file.delete(); + } + + boolean fullyDeleted = true; + final File[] files = file.listFiles(); + for (File child : files) { + fullyDeleted &= deleteRecursively(child); + } + fullyDeleted &= file.delete(); + return fullyDeleted; + } +} diff --git a/tests/FlickerTests/Android.mk b/tests/FlickerTests/Android.mk new file mode 100644 index 000000000000..3c70f8bc2d72 --- /dev/null +++ b/tests/FlickerTests/Android.mk @@ -0,0 +1,35 @@ +# +# Copyright (C) 2018 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. +# + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_PACKAGE_NAME := FlickerTests +LOCAL_MODULE_TAGS := tests optional +LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_PRIVATE_PLATFORM_APIS := true +LOCAL_CERTIFICATE := platform +LOCAL_COMPATIBILITY_SUITE := device-tests + +LOCAL_JAVA_LIBRARIES := android.test.runner +LOCAL_STATIC_JAVA_LIBRARIES := \ + flickertestapplib \ + flickerlib \ + truth-prebuilt \ + app-helpers-core + +include $(BUILD_PACKAGE) +include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file diff --git a/tests/FlickerTests/AndroidManifest.xml b/tests/FlickerTests/AndroidManifest.xml new file mode 100644 index 000000000000..ba6394008642 --- /dev/null +++ b/tests/FlickerTests/AndroidManifest.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.wm.flicker"> + + <uses-sdk android:minSdkVersion="27" android:targetSdkVersion="27"/> + <!-- Read and write traces from external storage --> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> + <!-- Capture screen contents --> + <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" /> + <!-- Run layers trace --> + <uses-permission android:name="android.permission.HARDWARE_TEST"/> + <application> + <uses-library android:name="android.test.runner"/> + </application> + + <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.server.wm.flicker" + android:label="WindowManager Flicker Tests"> + </instrumentation> +</manifest>
\ No newline at end of file diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTest.xml new file mode 100644 index 000000000000..b31235be8373 --- /dev/null +++ b/tests/FlickerTests/AndroidTest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright 2018 Google Inc. All Rights Reserved. + --> +<configuration description="Runs WindowManager Flicker Tests"> + <option name="test-tag" value="FlickerTests" /> + <target_preparer class="com.google.android.tradefed.targetprep.GoogleDeviceSetup"> + <!-- keeps the screen on during tests --> + <option name="screen-always-on" value="on" /> + <!-- prevents the phone from restarting --> + <option name="force-skip-system-props" value="true" /> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true"/> + <option name="test-file-name" value="FlickerTests.apk"/> + <option name="test-file-name" value="FlickerTestApp.apk" /> + </target_preparer> + <test class="com.android.tradefed.testtype.AndroidJUnitTest"> + <option name="package" value="com.android.server.wm.flicker"/> + <option name="shell-timeout" value="6600s" /> + <option name="test-timeout" value="6000s" /> + <option name="hidden-api-checks" value="false" /> + </test> + <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> + <option name="directory-keys" value="/sdcard/flicker" /> + <option name="collect-on-run-ended-only" value="true" /> + </metrics_collector> +</configuration> diff --git a/tests/FlickerTests/README.md b/tests/FlickerTests/README.md new file mode 100644 index 000000000000..a7c9e20e0a07 --- /dev/null +++ b/tests/FlickerTests/README.md @@ -0,0 +1,146 @@ +# Flicker Test Library + +## Motivation +Detect *flicker* — any discontinuous, or unpredictable behavior seen during UI transitions that is not due to performance. This is often the result of a logic error in the code and difficult to identify because the issue is transient and at times difficult to reproduce. This library helps create integration tests between `SurfaceFlinger`, `WindowManager` and `SystemUI` to identify flicker. + +## Adding a Test +The library builds and runs UI transitions, captures Winscope traces and exposes common assertions that can be tested against each trace. + +### Building Transitions +Start by defining common or error prone transitions using `TransitionRunner`. +```java +// Example: Build a transition that cold launches an app from launcher +TransitionRunner transition = TransitionRunner.newBuilder() + // Specify a tag to identify the transition (optional) + .withTag("OpenAppCold_" + testApp.getLauncherName()) + + // Specify preconditions to setup the device + // Wake up device and go to home screen + .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen) + + // Setup transition under test + // Press the home button and close the app to test a cold start + .runBefore(device::pressHome) + .runBefore(testApp::exit) + + // Run the transition under test + // Open the app and wait for UI to be idle + // This is the part of the transition that will be tested. + .run(testApp::open) + .run(device::waitForIdle) + + // Perform any tear downs + // Close the app + .runAfterAll(testApp::exit) + + // Number of times to repeat the transition to catch any flaky issues + .repeat(5); +``` + + +Run the transition to get a list of `TransitionResult` for each time the transition is repeated. +```java + List<TransitionResult> results = transition.run(); +``` +`TransitionResult` contains paths to test artifacts such as Winscope traces and screen recordings. + + +### Checking Assertions +Each `TransitionResult` can be tested using an extension of the Google Truth library, `LayersTraceSubject` and `WmTraceSubject`. They try to balance test principles set out by Google Truth (not supporting nested assertions, keeping assertions simple) with providing support for common assertion use cases. + +Each trace can be represented as a ordered collection of trace entries, with an associated timestamp. Each trace entry has common assertion checks. The trace subjects expose methods to filter the range of entries and test for changing assertions. + +```java + TransitionResult result = results.get(0); + Rect displayBounds = getDisplayBounds(); + + // check all trace entries + assertThat(result).coversRegion(displayBounds).forAllEntries(); + + // check a range of entries + assertThat(result).coversRegion(displayBounds).forRange(startTime, endTime); + + // check first entry + assertThat(result).coversRegion(displayBounds).inTheBeginning(); + + // check last entry + assertThat(result).coversRegion(displayBounds).atTheEnd(); + + // check a change in assertions, e.g. wallpaper window is visible, + // then wallpaper window becomes and stays invisible + assertThat(result) + .showsBelowAppWindow("wallpaper") + .then() + .hidesBelowAppWindow("wallpaper") + .forAllEntries(); +``` + +All assertions return `Result` which contains a `success` flag, `assertionName` string identifier, and `reason` string to provide actionable details to the user. The `reason` string is build along the way with all the details as to why the assertions failed and any hints which might help the user determine the root cause. Failed assertion message will also contain a path to the trace that was tested. Example of a failed test: + +``` + java.lang.AssertionError: Not true that <com.android.server.wm.flicker.LayersTrace@65da4cc> + Layers Trace can be found in: /layers_trace_emptyregion.pb + Timestamp: 2308008331271 + Assertion: coversRegion + Reason: Region to test: Rect(0, 0 - 1440, 2880) + first empty point: 0, 99 + visible regions: + StatusBar#0Rect(0, 0 - 1440, 98) + NavigationBar#0Rect(0, 2712 - 1440, 2880) + ScreenDecorOverlay#0Rect(0, 0 - 1440, 91) + ... + at com.google.common.truth.FailureStrategy.fail(FailureStrategy.java:24) + ... +``` + +--- + +## Running Tests + +The tests can be run as any other Android JUnit tests. `platform_testing/tests/flicker` uses the library to test common UI transitions. Run `atest FlickerTest` to execute these tests. + +--- + +## Other Topics +### Monitors +Monitors capture test artifacts for each transition run. They are started before each iteration of the test transition (after the `runBefore` calls) and stopped after the transition is completed. Each iteration will produce a new test artifact. The following monitors are available: + +#### LayersTraceMonitor +Captures Layers trace. This monitor is started by default. Build a transition with `skipLayersTrace()` to disable this monitor. +#### WindowManagerTraceMonitor +Captures Window Manager trace. This monitor is started by default. Build a transition with `skipWindowManagerTrace()` to disable this monitor. +#### WindowAnimationFrameStatsMonitor +Captures WindowAnimationFrameStats for the transition. This monitor is started by default and is used to eliminate *janky* runs. If an iteration has skipped frames, as determined by WindowAnimationFrameStats, the results for the iteration is skipped. If the list of results is empty after all iterations are completed, then the test should fail. Build a transition with `includeJankyRuns()` to disable this monitor. +#### ScreenRecorder +Captures screen to a video file. This monitor is disabled by default. Build a transition with `recordEachRun()` to capture each transition or build with `recordAllRuns()` to capture every transition including setup and teardown. + +--- + +### Extending Assertions + +To add a new assertion, add a function to one of the trace entry classes, `LayersTrace.Entry` or `WindowManagerTrace.Entry`. + +```java + // Example adds an assertion to the check if layer is hidden by parent. + Result isHiddenByParent(String layerName) { + // Result should contain a details if assertion fails for any reason + // such as if layer is not found or layer is not hidden by parent + // or layer has no parent. + // ... + } +``` +Then add a function to the trace subject `LayersTraceSubject` or `WmTraceSubject` which will add the assertion for testing. When the assertion is evaluated, the trace will first be filtered then the assertion will be applied to the remaining entries. + +```java + public LayersTraceSubject isHiddenByParent(String layerName) { + mChecker.add(entry -> entry.isHiddenByParent(layerName), + "isHiddenByParent(" + layerName + ")"); + return this; + } +``` + +To use the new assertion: +```java + // Check if "Chrome" layer is hidden by parent in the first trace entry. + assertThat(result).isHiddenByParent("Chrome").inTheBeginning(); +```
\ No newline at end of file diff --git a/tests/FlickerTests/TEST_MAPPING b/tests/FlickerTests/TEST_MAPPING new file mode 100644 index 000000000000..55a61471dfb8 --- /dev/null +++ b/tests/FlickerTests/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "postsubmit": [ + { + "name": "FlickerTests" + } + ] +}
\ No newline at end of file diff --git a/tests/FlickerTests/lib/Android.mk b/tests/FlickerTests/lib/Android.mk new file mode 100644 index 000000000000..6a8dfe8b5d0a --- /dev/null +++ b/tests/FlickerTests/lib/Android.mk @@ -0,0 +1,48 @@ +# +# Copyright (C) 2018 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. +# + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE := flickerlib +LOCAL_MODULE_TAGS := tests optional +# sign this with platform cert, so this test is allowed to call private platform apis +LOCAL_CERTIFICATE := platform +LOCAL_PRIVATE_PLATFORM_APIS := true +LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_STATIC_JAVA_LIBRARIES := \ + ub-janktesthelper \ + cts-amwm-util \ + platformprotosnano \ + layersprotosnano \ + truth-prebuilt \ + sysui-helper \ + launcher-helper-lib \ + +include $(BUILD_STATIC_JAVA_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := flickerautomationhelperlib +LOCAL_SDK_VERSION := current +LOCAL_SRC_FILES := src/com/android/server/wm/flicker/AutomationUtils.java \ + src/com/android/server/wm/flicker/WindowUtils.java +LOCAL_STATIC_JAVA_LIBRARIES := sysui-helper \ + launcher-helper-lib \ + compatibility-device-util + +include $(BUILD_STATIC_JAVA_LIBRARY) + +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/Assertions.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/Assertions.java new file mode 100644 index 000000000000..84f9f871324c --- /dev/null +++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/Assertions.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2018 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.wm.flicker; + +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +/** + * Collection of functional interfaces and classes representing assertions and their associated + * results. Assertions are functions that are applied over a single trace entry and returns a + * result which includes a detailed reason if the assertion fails. + */ +class Assertions { + /** + * Checks assertion on a single trace entry. + * + * @param <T> trace entry type to perform the assertion on. + */ + @FunctionalInterface + interface TraceAssertion<T> extends Function<T, Result> { + /** + * Returns an assertion that represents the logical negation of this assertion. + * + * @return a assertion that represents the logical negation of this assertion + */ + default TraceAssertion<T> negate() { + return (T t) -> apply(t).negate(); + } + } + + /** + * Checks assertion on a single layers trace entry. + */ + @FunctionalInterface + interface LayersTraceAssertion extends TraceAssertion<LayersTrace.Entry> { + + } + + /** + * Utility class to store assertions with an identifier to help generate more useful debug + * data when dealing with multiple assertions. + */ + static class NamedAssertion<T> { + final TraceAssertion<T> assertion; + final String name; + + NamedAssertion(TraceAssertion<T> assertion, String name) { + this.assertion = assertion; + this.name = name; + } + } + + /** + * Contains the result of an assertion including the reason for failed assertions. + */ + static class Result { + static final String NEGATION_PREFIX = "!"; + final boolean success; + final long timestamp; + final String assertionName; + final String reason; + + Result(boolean success, long timestamp, String assertionName, String reason) { + this.success = success; + this.timestamp = timestamp; + this.assertionName = assertionName; + this.reason = reason; + } + + Result(boolean success, String reason) { + this.success = success; + this.reason = reason; + this.assertionName = ""; + this.timestamp = 0; + } + + /** + * Returns the negated {@code Result} and adds a negation prefix to the assertion name. + */ + Result negate() { + String negatedAssertionName; + if (this.assertionName.startsWith(NEGATION_PREFIX)) { + negatedAssertionName = this.assertionName.substring(NEGATION_PREFIX.length() + 1); + } else { + negatedAssertionName = NEGATION_PREFIX + this.assertionName; + } + return new Result(!this.success, this.timestamp, negatedAssertionName, this.reason); + } + + boolean passed() { + return this.success; + } + + boolean failed() { + return !this.success; + } + + @Override + public String toString() { + return "Timestamp: " + prettyTimestamp(timestamp) + + "\nAssertion: " + assertionName + + "\nReason: " + reason; + } + + private String prettyTimestamp(long timestamp_ns) { + StringBuilder prettyTimestamp = new StringBuilder(); + TimeUnit[] timeUnits = {TimeUnit.HOURS, TimeUnit.MINUTES, TimeUnit.SECONDS, TimeUnit + .MILLISECONDS}; + String[] unitSuffixes = {"h", "m", "s", "ms"}; + + for (int i = 0; i < timeUnits.length; i++) { + long convertedTime = timeUnits[i].convert(timestamp_ns, TimeUnit.NANOSECONDS); + timestamp_ns -= TimeUnit.NANOSECONDS.convert(convertedTime, timeUnits[i]); + prettyTimestamp.append(convertedTime).append(unitSuffixes[i]); + } + + return prettyTimestamp.toString(); + } + } +} diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/AssertionsChecker.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/AssertionsChecker.java new file mode 100644 index 000000000000..3c65d3c341b3 --- /dev/null +++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/AssertionsChecker.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2018 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.wm.flicker; + +import com.android.server.wm.flicker.Assertions.NamedAssertion; +import com.android.server.wm.flicker.Assertions.Result; +import com.android.server.wm.flicker.Assertions.TraceAssertion; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Captures some of the common logic in {@link LayersTraceSubject} and {@link WmTraceSubject} + * used to filter trace entries and combine multiple assertions. + * + * @param <T> trace entry type + */ +public class AssertionsChecker<T extends ITraceEntry> { + private boolean mFilterEntriesByRange = false; + private long mFilterStartTime = 0; + private long mFilterEndTime = 0; + private AssertionOption mOption = AssertionOption.NONE; + private List<NamedAssertion<T>> mAssertions = new LinkedList<>(); + + void add(Assertions.TraceAssertion<T> assertion, String name) { + mAssertions.add(new NamedAssertion<>(assertion, name)); + } + + void filterByRange(long startTime, long endTime) { + mFilterEntriesByRange = true; + mFilterStartTime = startTime; + mFilterEndTime = endTime; + } + + private void setOption(AssertionOption option) { + if (mOption != AssertionOption.NONE && option != mOption) { + throw new IllegalArgumentException("Cannot use " + mOption + " option with " + + option + " option."); + } + mOption = option; + } + + public void checkFirstEntry() { + setOption(AssertionOption.CHECK_FIRST_ENTRY); + } + + public void checkLastEntry() { + setOption(AssertionOption.CHECK_LAST_ENTRY); + } + + public void checkChangingAssertions() { + setOption(AssertionOption.CHECK_CHANGING_ASSERTIONS); + } + + + /** + * Filters trace entries then runs assertions returning a list of failures. + * + * @param entries list of entries to perform assertions on + * @return list of failed assertion results + */ + List<Result> test(List<T> entries) { + List<T> filteredEntries; + List<Result> failures; + + if (mFilterEntriesByRange) { + filteredEntries = entries.stream() + .filter(e -> ((e.getTimestamp() >= mFilterStartTime) + && (e.getTimestamp() <= mFilterEndTime))) + .collect(Collectors.toList()); + } else { + filteredEntries = entries; + } + + switch (mOption) { + case CHECK_CHANGING_ASSERTIONS: + return assertChanges(filteredEntries); + case CHECK_FIRST_ENTRY: + return assertEntry(filteredEntries.get(0)); + case CHECK_LAST_ENTRY: + return assertEntry(filteredEntries.get(filteredEntries.size() - 1)); + } + return assertAll(filteredEntries); + } + + /** + * Steps through each trace entry checking if provided assertions are true in the order they + * are added. Each assertion must be true for at least a single trace entry. + * + * This can be used to check for asserting a change in property over a trace. Such as visibility + * for a window changes from true to false or top-most window changes from A to Bb and back to A + * again. + */ + private List<Result> assertChanges(List<T> entries) { + List<Result> failures = new ArrayList<>(); + int entryIndex = 0; + int assertionIndex = 0; + int lastPassedAssertionIndex = -1; + + if (mAssertions.size() == 0) { + return failures; + } + + while (assertionIndex < mAssertions.size() && entryIndex < entries.size()) { + TraceAssertion<T> currentAssertion = mAssertions.get(assertionIndex).assertion; + Result result = currentAssertion.apply(entries.get(entryIndex)); + if (result.passed()) { + lastPassedAssertionIndex = assertionIndex; + entryIndex++; + continue; + } + + if (lastPassedAssertionIndex != assertionIndex) { + failures.add(result); + break; + } + assertionIndex++; + + if (assertionIndex == mAssertions.size()) { + failures.add(result); + break; + } + } + + if (failures.isEmpty()) { + if (assertionIndex != mAssertions.size() - 1) { + String reason = "\nAssertion " + mAssertions.get(assertionIndex).name + + " never became false"; + reason += "\nPassed assertions: " + mAssertions.stream().limit(assertionIndex) + .map(assertion -> assertion.name).collect(Collectors.joining(",")); + reason += "\nUntested assertions: " + mAssertions.stream().skip(assertionIndex + 1) + .map(assertion -> assertion.name).collect(Collectors.joining(",")); + + Result result = new Result(false /* success */, 0 /* timestamp */, + "assertChanges", "Not all assertions passed." + reason); + failures.add(result); + } + } + return failures; + } + + private List<Result> assertEntry(T entry) { + List<Result> failures = new ArrayList<>(); + for (NamedAssertion<T> assertion : mAssertions) { + Result result = assertion.assertion.apply(entry); + if (result.failed()) { + failures.add(result); + } + } + return failures; + } + + private List<Result> assertAll(List<T> entries) { + return mAssertions.stream().flatMap( + assertion -> entries.stream() + .map(assertion.assertion) + .filter(Result::failed)) + .collect(Collectors.toList()); + } + + private enum AssertionOption { + NONE, + CHECK_CHANGING_ASSERTIONS, + CHECK_FIRST_ENTRY, + CHECK_LAST_ENTRY, + } +} diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/AutomationUtils.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/AutomationUtils.java new file mode 100644 index 000000000000..6bac67549ddc --- /dev/null +++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/AutomationUtils.java @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2018 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.wm.flicker; + +import static android.os.SystemClock.sleep; +import static android.system.helpers.OverviewHelper.isRecentsInLauncher; +import static android.view.Surface.ROTATION_0; + +import static com.android.compatibility.common.util.SystemUtil.runShellCommand; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.RemoteException; +import android.support.test.InstrumentationRegistry; +import android.support.test.launcherhelper.LauncherStrategyFactory; +import android.support.test.uiautomator.By; +import android.support.test.uiautomator.BySelector; +import android.support.test.uiautomator.Configurator; +import android.support.test.uiautomator.UiDevice; +import android.support.test.uiautomator.UiObject2; +import android.support.test.uiautomator.Until; +import android.util.Log; +import android.util.Rational; +import android.view.View; +import android.view.ViewConfiguration; + +/** + * Collection of UI Automation helper functions. + */ +public class AutomationUtils { + private static final String SYSTEMUI_PACKAGE = "com.android.systemui"; + private static final long FIND_TIMEOUT = 10000; + private static final long LONG_PRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout() * 2L; + private static final String TAG = "FLICKER"; + + public static void wakeUpAndGoToHomeScreen() { + UiDevice device = UiDevice.getInstance(InstrumentationRegistry + .getInstrumentation()); + try { + device.wakeUp(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + device.pressHome(); + } + + /** + * Sets {@link android.app.UiAutomation#waitForIdle(long, long)} global timeout to 0 causing + * the {@link android.app.UiAutomation#waitForIdle(long, long)} function to timeout instantly. + * This removes some delays when using the UIAutomator library required to create fast UI + * transitions. + */ + static void setFastWait() { + Configurator.getInstance().setWaitForIdleTimeout(0); + } + + /** + * Reverts {@link android.app.UiAutomation#waitForIdle(long, long)} to default behavior. + */ + static void setDefaultWait() { + Configurator.getInstance().setWaitForIdleTimeout(10000); + } + + public static boolean isQuickstepEnabled(UiDevice device) { + return device.findObject(By.res(SYSTEMUI_PACKAGE, "recent_apps")) == null; + } + + public static void openQuickstep(UiDevice device) { + if (isQuickstepEnabled(device)) { + int height = device.getDisplayHeight(); + UiObject2 navBar = device.findObject(By.res(SYSTEMUI_PACKAGE, "navigation_bar_frame")); + + Rect navBarVisibleBounds; + + // TODO(vishnun) investigate why this object cannot be found. + if (navBar != null) { + navBarVisibleBounds = navBar.getVisibleBounds(); + } else { + Log.e(TAG, "Could not find nav bar, infer location"); + navBarVisibleBounds = WindowUtils.getNavigationBarPosition(ROTATION_0); + } + + // Swipe from nav bar to 2/3rd down the screen. + device.swipe( + navBarVisibleBounds.centerX(), navBarVisibleBounds.centerY(), + navBarVisibleBounds.centerX(), height * 2 / 3, + (navBarVisibleBounds.centerY() - height * 2 / 3) / 100); // 100 px/step + } else { + try { + device.pressRecentApps(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + BySelector RECENTS = By.res(SYSTEMUI_PACKAGE, "recents_view"); + + // use a long timeout to wait until recents populated + if (device.wait( + Until.findObject(isRecentsInLauncher() + ? getLauncherOverviewSelector(device) : RECENTS), + 10000) == null) { + fail("Recents didn't appear"); + } + device.waitForIdle(); + } + + static void clearRecents(UiDevice device) { + if (isQuickstepEnabled(device)) { + openQuickstep(device); + + for (int i = 0; i < 5; i++) { + device.swipe(device.getDisplayWidth() / 2, + device.getDisplayHeight() / 2, device.getDisplayWidth(), + device.getDisplayHeight() / 2, + 5); + + BySelector clearAllSelector = By.res("com.google.android.apps.nexuslauncher", + "clear_all_button"); + UiObject2 clearAllButton = device.wait(Until.findObject(clearAllSelector), 100); + if (clearAllButton != null) { + clearAllButton.click(); + return; + } + } + } + } + + private static BySelector getLauncherOverviewSelector(UiDevice device) { + return By.res(device.getLauncherPackageName(), "overview_panel"); + } + + private static void longPressRecents(UiDevice device) { + BySelector recentsSelector = By.res(SYSTEMUI_PACKAGE, "recent_apps"); + UiObject2 recentsButton = device.wait(Until.findObject(recentsSelector), FIND_TIMEOUT); + assertNotNull("Unable to find recents button", recentsButton); + recentsButton.click(LONG_PRESS_TIMEOUT); + } + + public static void launchSplitScreen(UiDevice device) { + String mLauncherPackage = LauncherStrategyFactory.getInstance(device) + .getLauncherStrategy().getSupportedLauncherPackage(); + + if (isQuickstepEnabled(device)) { + // Quickstep enabled + openQuickstep(device); + + BySelector overviewIconSelector = By.res(mLauncherPackage, "icon") + .clazz(View.class); + UiObject2 overviewIcon = device.wait(Until.findObject(overviewIconSelector), + FIND_TIMEOUT); + assertNotNull("Unable to find app icon in Overview", overviewIcon); + overviewIcon.click(); + + BySelector splitscreenButtonSelector = By.text("Split screen"); + UiObject2 splitscreenButton = device.wait(Until.findObject(splitscreenButtonSelector), + FIND_TIMEOUT); + assertNotNull("Unable to find Split screen button in Overview", overviewIcon); + splitscreenButton.click(); + } else { + // Classic long press recents + longPressRecents(device); + } + // Wait for animation to complete. + sleep(2000); + } + + public static void exitSplitScreen(UiDevice device) { + if (isQuickstepEnabled(device)) { + // Quickstep enabled + BySelector dividerSelector = By.res(SYSTEMUI_PACKAGE, "docked_divider_handle"); + UiObject2 divider = device.wait(Until.findObject(dividerSelector), FIND_TIMEOUT); + assertNotNull("Unable to find Split screen divider", divider); + + // Drag the split screen divider to the top of the screen + divider.drag(new Point(device.getDisplayWidth() / 2, 0), 400); + } else { + // Classic long press recents + longPressRecents(device); + } + // Wait for animation to complete. + sleep(2000); + } + + static void resizeSplitScreen(UiDevice device, Rational windowHeightRatio) { + BySelector dividerSelector = By.res(SYSTEMUI_PACKAGE, "docked_divider_handle"); + UiObject2 divider = device.wait(Until.findObject(dividerSelector), FIND_TIMEOUT); + assertNotNull("Unable to find Split screen divider", divider); + int destHeight = + (int) (WindowUtils.getDisplayBounds().height() * windowHeightRatio.floatValue()); + // Drag the split screen divider to so that the ratio of top window height and bottom + // window height is windowHeightRatio + device.drag(divider.getVisibleBounds().centerX(), divider.getVisibleBounds().centerY(), + device.getDisplayWidth() / 2, destHeight, 10); + //divider.drag(new Point(device.getDisplayWidth() / 2, destHeight), 400) + divider = device.wait(Until.findObject(dividerSelector), FIND_TIMEOUT); + + // Wait for animation to complete. + sleep(2000); + } + + static void closePipWindow(UiDevice device) { + UiObject2 pipWindow = device.findObject( + By.res(SYSTEMUI_PACKAGE, "background")); + pipWindow.click(); + UiObject2 exitPipObject = device.findObject( + By.res(SYSTEMUI_PACKAGE, "dismiss")); + exitPipObject.click(); + // Wait for animation to complete. + sleep(2000); + } + + static void expandPipWindow(UiDevice device) { + UiObject2 pipWindow = device.findObject( + By.res(SYSTEMUI_PACKAGE, "background")); + pipWindow.click(); + pipWindow.click(); + } + + public static void stopPackage(Context context, String packageName) { + runShellCommand("am force-stop " + packageName); + int packageUid; + try { + packageUid = context.getPackageManager().getPackageUid(packageName, /* flags= */0); + } catch (PackageManager.NameNotFoundException e) { + return; + } + while (targetPackageIsRunning(packageUid)) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + //ignore + } + } + } + + private static boolean targetPackageIsRunning(int uid) { + final String result = runShellCommand( + String.format("cmd activity get-uid-state %d", uid)); + return !result.contains("(NONEXISTENT)"); + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/ITraceEntry.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/ITraceEntry.java new file mode 100644 index 000000000000..9525f41b46b2 --- /dev/null +++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/ITraceEntry.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2018 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.wm.flicker; + +/** + * Common interface for Layer and WindowManager trace entries. + */ +interface ITraceEntry { + /** + * @return timestamp of current entry + */ + long getTimestamp(); +} diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/LayersTrace.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/LayersTrace.java new file mode 100644 index 000000000000..660ec0fe4833 --- /dev/null +++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/LayersTrace.java @@ -0,0 +1,412 @@ +/* + * Copyright (C) 2018 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.wm.flicker; + +import android.annotation.Nullable; +import android.graphics.Rect; +import android.surfaceflinger.nano.Layers.LayerProto; +import android.surfaceflinger.nano.Layers.RectProto; +import android.surfaceflinger.nano.Layers.RegionProto; +import android.surfaceflinger.nano.Layerstrace.LayersTraceFileProto; +import android.surfaceflinger.nano.Layerstrace.LayersTraceProto; +import android.util.SparseArray; + +import com.android.server.wm.flicker.Assertions.Result; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * Contains a collection of parsed Layers trace entries and assertions to apply over + * a single entry. + * + * Each entry is parsed into a list of {@link LayersTrace.Entry} objects. + */ +public class LayersTrace { + final private List<Entry> mEntries; + @Nullable + final private Path mSource; + + private LayersTrace(List<Entry> entries, Path source) { + this.mEntries = entries; + this.mSource = source; + } + + /** + * Parses {@code LayersTraceFileProto} from {@code data} and uses the proto to generates a list + * of trace entries, storing the flattened layers into its hierarchical structure. + * + * @param data binary proto data + * @param source Path to source of data for additional debug information + */ + static LayersTrace parseFrom(byte[] data, Path source) { + List<Entry> entries = new ArrayList<>(); + LayersTraceFileProto fileProto; + try { + fileProto = LayersTraceFileProto.parseFrom(data); + } catch (Exception e) { + throw new RuntimeException(e); + } + for (LayersTraceProto traceProto : fileProto.entry) { + Entry entry = Entry.fromFlattenedLayers(traceProto.elapsedRealtimeNanos, + traceProto.layers.layers); + entries.add(entry); + } + return new LayersTrace(entries, source); + } + + /** + * Parses {@code LayersTraceFileProto} from {@code data} and uses the proto to generates a list + * of trace entries, storing the flattened layers into its hierarchical structure. + * + * @param data binary proto data + */ + static LayersTrace parseFrom(byte[] data) { + return parseFrom(data, null); + } + + List<Entry> getEntries() { + return mEntries; + } + + Entry getEntry(long timestamp) { + Optional<Entry> entry = mEntries.stream() + .filter(e -> e.getTimestamp() == timestamp) + .findFirst(); + if (!entry.isPresent()) { + throw new RuntimeException("Entry does not exist for timestamp " + timestamp); + } + return entry.get(); + } + + Optional<Path> getSource() { + return Optional.ofNullable(mSource); + } + + /** + * Represents a single Layer trace entry. + */ + static class Entry implements ITraceEntry { + private long mTimestamp; + private List<Layer> mRootLayers; // hierarchical representation of layers + private List<Layer> mFlattenedLayers = null; + + private Entry(long timestamp, List<Layer> rootLayers) { + this.mTimestamp = timestamp; + this.mRootLayers = rootLayers; + } + + /** + * Constructs the layer hierarchy from a flattened list of layers. + */ + static Entry fromFlattenedLayers(long timestamp, LayerProto[] protos) { + SparseArray<Layer> layerMap = new SparseArray<>(); + ArrayList<Layer> orphans = new ArrayList<>(); + for (LayerProto proto : protos) { + int id = proto.id; + int parentId = proto.parent; + + Layer newLayer = layerMap.get(id); + if (newLayer == null) { + newLayer = new Layer(proto); + layerMap.append(id, newLayer); + } else if (newLayer.mProto != null) { + throw new RuntimeException("Duplicate layer id found:" + id); + } else { + newLayer.mProto = proto; + orphans.remove(newLayer); + } + + // add parent placeholder + if (layerMap.get(parentId) == null) { + Layer orphanLayer = new Layer(null); + layerMap.append(parentId, orphanLayer); + orphans.add(orphanLayer); + } + layerMap.get(parentId).addChild(newLayer); + newLayer.addParent(layerMap.get(parentId)); + } + + // Fail if we find orphan layers. + orphans.remove(layerMap.get(-1)); + orphans.forEach(orphan -> { + String childNodes = orphan.mChildren.stream().map(node -> + Integer.toString(node.getId())).collect(Collectors.joining(", ")); + int orphanId = orphan.mChildren.get(0).mProto.parent; + throw new RuntimeException( + "Failed to parse layers trace. Found orphan layers with parent " + + "layer id:" + orphanId + " : " + childNodes); + }); + + return new Entry(timestamp, layerMap.get(-1).mChildren); + } + + /** + * Extracts {@link Rect} from {@link RectProto}. + */ + private static Rect extract(RectProto proto) { + return new Rect(proto.left, proto.top, proto.right, proto.bottom); + } + + /** + * Extracts {@link Rect} from {@link RegionProto} by returning a rect that encompasses all + * the rects making up the region. + */ + private static Rect extract(RegionProto regionProto) { + Rect region = new Rect(); + for (RectProto proto : regionProto.rect) { + region.union(proto.left, proto.top, proto.right, proto.bottom); + } + return region; + } + + /** + * Checks if a region specified by {@code testRect} is covered by all visible layers. + */ + Result coversRegion(Rect testRect) { + String assertionName = "coversRegion"; + Collection<Layer> layers = asFlattenedLayers(); + + for (int x = testRect.left; x < testRect.right; x++) { + for (int y = testRect.top; y < testRect.bottom; y++) { + boolean emptyRegionFound = true; + for (Layer layer : layers) { + if (layer.isInvisible() || layer.isHiddenByParent()) { + continue; + } + for (RectProto rectProto : layer.mProto.visibleRegion.rect) { + Rect r = extract(rectProto); + if (r.contains(x, y)) { + y = r.bottom; + emptyRegionFound = false; + } + } + } + if (emptyRegionFound) { + String reason = "Region to test: " + testRect + + "\nfirst empty point: " + x + ", " + y; + reason += "\nvisible regions:"; + for (Layer layer : layers) { + if (layer.isInvisible() || layer.isHiddenByParent()) { + continue; + } + Rect r = extract(layer.mProto.visibleRegion); + reason += "\n" + layer.mProto.name + r.toString(); + } + return new Result(false /* success */, this.mTimestamp, assertionName, + reason); + } + } + } + String info = "Region covered: " + testRect; + return new Result(true /* success */, this.mTimestamp, assertionName, info); + } + + /** + * Checks if a layer with name {@code layerName} has a visible region + * {@code expectedVisibleRegion}. + */ + Result hasVisibleRegion(String layerName, Rect expectedVisibleRegion) { + String assertionName = "hasVisibleRegion"; + String reason = "Could not find " + layerName; + for (Layer layer : asFlattenedLayers()) { + if (layer.mProto.name.contains(layerName)) { + if (layer.isHiddenByParent()) { + reason = layer.getHiddenByParentReason(); + continue; + } + if (layer.isInvisible()) { + reason = layer.getVisibilityReason(); + continue; + } + Rect visibleRegion = extract(layer.mProto.visibleRegion); + if (visibleRegion.equals(expectedVisibleRegion)) { + return new Result(true /* success */, this.mTimestamp, assertionName, + layer.mProto.name + "has visible region " + expectedVisibleRegion); + } + reason = layer.mProto.name + " has visible region:" + visibleRegion + " " + + "expected:" + expectedVisibleRegion; + } + } + return new Result(false /* success */, this.mTimestamp, assertionName, reason); + } + + /** + * Checks if a layer with name {@code layerName} is visible. + */ + Result isVisible(String layerName) { + String assertionName = "isVisible"; + String reason = "Could not find " + layerName; + for (Layer layer : asFlattenedLayers()) { + if (layer.mProto.name.contains(layerName)) { + if (layer.isHiddenByParent()) { + reason = layer.getHiddenByParentReason(); + continue; + } + if (layer.isInvisible()) { + reason = layer.getVisibilityReason(); + continue; + } + return new Result(true /* success */, this.mTimestamp, assertionName, + layer.mProto.name + " is visible"); + } + } + return new Result(false /* success */, this.mTimestamp, assertionName, reason); + } + + @Override + public long getTimestamp() { + return mTimestamp; + } + + List<Layer> getRootLayers() { + return mRootLayers; + } + + List<Layer> asFlattenedLayers() { + if (mFlattenedLayers == null) { + mFlattenedLayers = new ArrayList<>(); + ArrayList<Layer> pendingLayers = new ArrayList<>(this.mRootLayers); + while (!pendingLayers.isEmpty()) { + Layer layer = pendingLayers.remove(0); + mFlattenedLayers.add(layer); + pendingLayers.addAll(layer.mChildren); + } + } + return mFlattenedLayers; + } + + Rect getVisibleBounds(String layerName) { + List<Layer> layers = asFlattenedLayers(); + for (Layer layer : layers) { + if (layer.mProto.name.contains(layerName) && layer.isVisible()) { + return extract(layer.mProto.visibleRegion); + } + } + return new Rect(0, 0, 0, 0); + } + } + + /** + * Represents a single layer with links to its parent and child layers. + */ + static class Layer { + @Nullable + LayerProto mProto; + List<Layer> mChildren; + @Nullable + Layer mParent = null; + + private Layer(LayerProto proto) { + this.mProto = proto; + this.mChildren = new ArrayList<>(); + } + + private void addChild(Layer childLayer) { + this.mChildren.add(childLayer); + } + + private void addParent(Layer parentLayer) { + this.mParent = parentLayer; + } + + int getId() { + return mProto.id; + } + + boolean isActiveBufferEmpty() { + return this.mProto.activeBuffer == null || this.mProto.activeBuffer.height == 0 + || this.mProto.activeBuffer.width == 0; + } + + boolean isVisibleRegionEmpty() { + if (this.mProto.visibleRegion == null) { + return true; + } + Rect visibleRect = Entry.extract(this.mProto.visibleRegion); + return visibleRect.height() == 0 || visibleRect.width() == 0; + } + + boolean isHidden() { + return (this.mProto.flags & /* FLAG_HIDDEN */ 0x1) != 0x0; + } + + boolean isVisible() { + return (!isActiveBufferEmpty() || isColorLayer()) && + !isHidden() && this.mProto.color.a > 0 && !isVisibleRegionEmpty(); + } + + boolean isColorLayer() { + return this.mProto.type.equals("ColorLayer"); + } + + boolean isRootLayer() { + return mParent == null || mParent.mProto == null; + } + + boolean isInvisible() { + return !isVisible(); + } + + boolean isHiddenByParent() { + return !isRootLayer() && (mParent.isHidden() || mParent.isHiddenByParent()); + } + + String getHiddenByParentReason() { + String reason = "Layer " + mProto.name; + if (isHiddenByParent()) { + reason += " is hidden by parent: " + mParent.mProto.name; + } else { + reason += " is not hidden by parent: " + mParent.mProto.name; + } + return reason; + } + + String getVisibilityReason() { + String reason = "Layer " + mProto.name; + if (isVisible()) { + reason += " is visible:"; + } else { + reason += " is invisible:"; + if (this.mProto.activeBuffer == null) { + reason += " activeBuffer=null"; + } else if (this.mProto.activeBuffer.height == 0) { + reason += " activeBuffer.height=0"; + } else if (this.mProto.activeBuffer.width == 0) { + reason += " activeBuffer.width=0"; + } + if (!isColorLayer()) { + reason += " type != ColorLayer"; + } + if (isHidden()) { + reason += " flags=" + this.mProto.flags + " (FLAG_HIDDEN set)"; + } + if (this.mProto.color.a == 0) { + reason += " color.a=0"; + } + if (isVisibleRegionEmpty()) { + reason += " visible region is empty"; + } + } + return reason; + } + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/LayersTraceSubject.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/LayersTraceSubject.java new file mode 100644 index 000000000000..b4c97e4fc4f2 --- /dev/null +++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/LayersTraceSubject.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2018 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.wm.flicker; + +import static com.google.common.truth.Truth.assertAbout; +import static com.google.common.truth.Truth.assertWithMessage; + +import android.annotation.Nullable; +import android.graphics.Rect; + +import com.android.server.wm.flicker.Assertions.Result; +import com.android.server.wm.flicker.LayersTrace.Entry; +import com.android.server.wm.flicker.TransitionRunner.TransitionResult; + +import com.google.common.truth.FailureStrategy; +import com.google.common.truth.Subject; +import com.google.common.truth.SubjectFactory; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * Truth subject for {@link LayersTrace} objects. + */ +public class LayersTraceSubject extends Subject<LayersTraceSubject, LayersTrace> { + // Boiler-plate Subject.Factory for LayersTraceSubject + private static final SubjectFactory<LayersTraceSubject, LayersTrace> FACTORY = + new SubjectFactory<LayersTraceSubject, LayersTrace>() { + @Override + public LayersTraceSubject getSubject( + FailureStrategy fs, @Nullable LayersTrace target) { + return new LayersTraceSubject(fs, target); + } + }; + + private AssertionsChecker<Entry> mChecker = new AssertionsChecker<>(); + + private LayersTraceSubject(FailureStrategy fs, @Nullable LayersTrace subject) { + super(fs, subject); + } + + // User-defined entry point + public static LayersTraceSubject assertThat(@Nullable LayersTrace entry) { + return assertAbout(FACTORY).that(entry); + } + + // User-defined entry point + public static LayersTraceSubject assertThat(@Nullable TransitionResult result) { + LayersTrace entries = LayersTrace.parseFrom(result.getLayersTrace(), + result.getLayersTracePath()); + return assertWithMessage(result.toString()).about(FACTORY).that(entries); + } + + // Static method for getting the subject factory (for use with assertAbout()) + public static SubjectFactory<LayersTraceSubject, LayersTrace> entries() { + return FACTORY; + } + + public void forAllEntries() { + test(); + } + + public void forRange(long startTime, long endTime) { + mChecker.filterByRange(startTime, endTime); + test(); + } + + public LayersTraceSubject then() { + mChecker.checkChangingAssertions(); + return this; + } + + public void inTheBeginning() { + if (getSubject().getEntries().isEmpty()) { + fail("No entries found."); + } + mChecker.checkFirstEntry(); + test(); + } + + public void atTheEnd() { + if (getSubject().getEntries().isEmpty()) { + fail("No entries found."); + } + mChecker.checkLastEntry(); + test(); + } + + private void test() { + List<Result> failures = mChecker.test(getSubject().getEntries()); + if (!failures.isEmpty()) { + String failureLogs = failures.stream().map(Result::toString) + .collect(Collectors.joining("\n")); + String tracePath = ""; + if (getSubject().getSource().isPresent()) { + tracePath = "\nLayers Trace can be found in: " + + getSubject().getSource().get().toAbsolutePath() + "\n"; + } + fail(tracePath + failureLogs); + } + } + + public LayersTraceSubject coversRegion(Rect rect) { + mChecker.add(entry -> entry.coversRegion(rect), + "coversRegion(" + rect + ")"); + return this; + } + + public LayersTraceSubject hasVisibleRegion(String layerName, Rect size) { + mChecker.add(entry -> entry.hasVisibleRegion(layerName, size), + "hasVisibleRegion(" + layerName + size + ")"); + return this; + } + + public LayersTraceSubject showsLayer(String layerName) { + mChecker.add(entry -> entry.isVisible(layerName), + "showsLayer(" + layerName + ")"); + return this; + } + + public LayersTraceSubject hidesLayer(String layerName) { + mChecker.add(entry -> entry.isVisible(layerName).negate(), + "hidesLayer(" + layerName + ")"); + return this; + } +} diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/TransitionRunner.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/TransitionRunner.java new file mode 100644 index 000000000000..f6e8192ee4c0 --- /dev/null +++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/TransitionRunner.java @@ -0,0 +1,423 @@ +/* + * Copyright (C) 2018 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.wm.flicker; + +import android.annotation.Nullable; +import android.support.annotation.VisibleForTesting; +import android.support.test.InstrumentationRegistry; +import android.util.Log; + +import com.android.server.wm.flicker.monitor.ITransitionMonitor; +import com.android.server.wm.flicker.monitor.LayersTraceMonitor; +import com.android.server.wm.flicker.monitor.ScreenRecorder; +import com.android.server.wm.flicker.monitor.WindowAnimationFrameStatsMonitor; +import com.android.server.wm.flicker.monitor.WindowManagerTraceMonitor; + +import com.google.common.io.Files; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +/** + * Builds and runs UI transitions capturing test artifacts. + * + * User can compose a transition from simpler steps, specifying setup and teardown steps. During + * a transition, Layers trace, WindowManager trace, screen recordings and window animation frame + * stats can be captured. + * + * <pre> + * Transition builder options: + * {@link TransitionBuilder#run(Runnable)} run transition under test. Monitors will be started + * before the transition and stopped after the transition is completed. + * {@link TransitionBuilder#repeat(int)} repeat transitions under test multiple times recording + * result for each run. + * {@link TransitionBuilder#withTag(String)} specify a string identifier used to prefix logs and + * artifacts generated. + * {@link TransitionBuilder#runBeforeAll(Runnable)} run setup transitions once before all other + * transition are run to set up an initial state on device. + * {@link TransitionBuilder#runBefore(Runnable)} run setup transitions before each test transition + * run. + * {@link TransitionBuilder#runAfter(Runnable)} run teardown transitions after each test + * transition. + * {@link TransitionBuilder#runAfter(Runnable)} run teardown transitions once after all + * other transition are run. + * {@link TransitionBuilder#includeJankyRuns()} disables {@link WindowAnimationFrameStatsMonitor} + * to monitor janky frames. If janky frames are detected, then the test run is skipped. This + * monitor is enabled by default. + * {@link TransitionBuilder#skipLayersTrace()} disables {@link LayersTraceMonitor} used to + * capture Layers trace during a transition. This monitor is enabled by default. + * {@link TransitionBuilder#skipWindowManagerTrace()} disables {@link WindowManagerTraceMonitor} + * used to capture WindowManager trace during a transition. This monitor is enabled by + * default. + * {@link TransitionBuilder#recordAllRuns()} records the screen contents and saves it to a file. + * All the runs including setup and teardown transitions are included in the recording. This + * monitor is used for debugging purposes. + * {@link TransitionBuilder#recordEachRun()} records the screen contents during test transitions + * and saves it to a file for each run. This monitor is used for debugging purposes. + * + * Example transition to capture WindowManager and Layers trace when opening a test app: + * {@code + * TransitionRunner.newBuilder() + * .withTag("OpenTestAppFast") + * .runBeforeAll(UiAutomationLib::wakeUp) + * .runBeforeAll(UiAutomationLib::UnlockDevice) + * .runBeforeAll(UiAutomationLib::openTestApp) + * .runBefore(UiAutomationLib::closeTestApp) + * .run(UiAutomationLib::openTestApp) + * .runAfterAll(UiAutomationLib::closeTestApp) + * .repeat(5) + * .build() + * .run(); + * } + * </pre> + */ +class TransitionRunner { + private static final String TAG = "FLICKER"; + private final ScreenRecorder mScreenRecorder; + private final WindowManagerTraceMonitor mWmTraceMonitor; + private final LayersTraceMonitor mLayersTraceMonitor; + private final WindowAnimationFrameStatsMonitor mFrameStatsMonitor; + + private final List<ITransitionMonitor> mAllRunsMonitors; + private final List<ITransitionMonitor> mPerRunMonitors; + private final List<Runnable> mBeforeAlls; + private final List<Runnable> mBefores; + private final List<Runnable> mTransitions; + private final List<Runnable> mAfters; + private final List<Runnable> mAfterAlls; + + private final int mIterations; + private final String mTestTag; + + @Nullable + private List<TransitionResult> mResults = null; + + private TransitionRunner(TransitionBuilder builder) { + mScreenRecorder = builder.mScreenRecorder; + mWmTraceMonitor = builder.mWmTraceMonitor; + mLayersTraceMonitor = builder.mLayersTraceMonitor; + mFrameStatsMonitor = builder.mFrameStatsMonitor; + + mAllRunsMonitors = builder.mAllRunsMonitors; + mPerRunMonitors = builder.mPerRunMonitors; + mBeforeAlls = builder.mBeforeAlls; + mBefores = builder.mBefores; + mTransitions = builder.mTransitions; + mAfters = builder.mAfters; + mAfterAlls = builder.mAfterAlls; + + mIterations = builder.mIterations; + mTestTag = builder.mTestTag; + } + + static TransitionBuilder newBuilder() { + return new TransitionBuilder(); + } + + /** + * Runs the composed transition and calls monitors at the appropriate stages. If jank monitor + * is enabled, transitions with jank are skipped. + * + * @return itself + */ + TransitionRunner run() { + mResults = new ArrayList<>(); + mAllRunsMonitors.forEach(ITransitionMonitor::start); + mBeforeAlls.forEach(Runnable::run); + for (int iteration = 0; iteration < mIterations; iteration++) { + mBefores.forEach(Runnable::run); + mPerRunMonitors.forEach(ITransitionMonitor::start); + mTransitions.forEach(Runnable::run); + mPerRunMonitors.forEach(ITransitionMonitor::stop); + mAfters.forEach(Runnable::run); + if (runJankFree() && mFrameStatsMonitor.jankyFramesDetected()) { + String msg = String.format("Skipping iteration %d/%d for test %s due to jank. %s", + iteration, mIterations - 1, mTestTag, mFrameStatsMonitor.toString()); + Log.e(TAG, msg); + continue; + } + mResults.add(saveResult(iteration)); + } + mAfterAlls.forEach(Runnable::run); + mAllRunsMonitors.forEach(monitor -> { + monitor.stop(); + Path path = monitor.save(mTestTag); + Log.e(TAG, "Video saved to " + path.toString()); + }); + return this; + } + + /** + * Returns a list of transition results. + * + * @return list of transition results. + */ + List<TransitionResult> getResults() { + if (mResults == null) { + throw new IllegalStateException("Results do not exist!"); + } + return mResults; + } + + /** + * Deletes all transition results that are not marked for saving. + * + * @return list of transition results. + */ + void deleteResults() { + if (mResults == null) { + return; + } + mResults.stream() + .filter(TransitionResult::canDelete) + .forEach(TransitionResult::delete); + mResults = null; + } + + /** + * Saves monitor results to file. + * + * @return object containing paths to test artifacts + */ + private TransitionResult saveResult(int iteration) { + Path windowTrace = null; + Path layerTrace = null; + Path screenCaptureVideo = null; + + if (mPerRunMonitors.contains(mWmTraceMonitor)) { + windowTrace = mWmTraceMonitor.save(mTestTag, iteration); + } + if (mPerRunMonitors.contains(mLayersTraceMonitor)) { + layerTrace = mLayersTraceMonitor.save(mTestTag, iteration); + } + if (mPerRunMonitors.contains(mScreenRecorder)) { + screenCaptureVideo = mScreenRecorder.save(mTestTag, iteration); + } + return new TransitionResult(layerTrace, windowTrace, screenCaptureVideo); + } + + private boolean runJankFree() { + return mPerRunMonitors.contains(mFrameStatsMonitor); + } + + public String getTestTag() { + return mTestTag; + } + + /** + * Stores paths to all test artifacts. + */ + @VisibleForTesting + public static class TransitionResult { + @Nullable + final Path layersTrace; + @Nullable + final Path windowManagerTrace; + @Nullable + final Path screenCaptureVideo; + private boolean flaggedForSaving; + + TransitionResult(@Nullable Path layersTrace, @Nullable Path windowManagerTrace, + @Nullable Path screenCaptureVideo) { + this.layersTrace = layersTrace; + this.windowManagerTrace = windowManagerTrace; + this.screenCaptureVideo = screenCaptureVideo; + } + + void flagForSaving() { + flaggedForSaving = true; + } + + boolean canDelete() { + return !flaggedForSaving; + } + + boolean layersTraceExists() { + return layersTrace != null && layersTrace.toFile().exists(); + } + + byte[] getLayersTrace() { + try { + return Files.toByteArray(this.layersTrace.toFile()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + Path getLayersTracePath() { + return layersTrace; + } + + boolean windowManagerTraceExists() { + return windowManagerTrace != null && windowManagerTrace.toFile().exists(); + } + + public byte[] getWindowManagerTrace() { + try { + return Files.toByteArray(this.windowManagerTrace.toFile()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + Path getWindowManagerTracePath() { + return windowManagerTrace; + } + + boolean screenCaptureVideoExists() { + return screenCaptureVideo != null && screenCaptureVideo.toFile().exists(); + } + + Path screenCaptureVideoPath() { + return screenCaptureVideo; + } + + void delete() { + if (layersTraceExists()) layersTrace.toFile().delete(); + if (windowManagerTraceExists()) windowManagerTrace.toFile().delete(); + if (screenCaptureVideoExists()) screenCaptureVideo.toFile().delete(); + } + } + + /** + * Builds a {@link TransitionRunner} instance. + */ + static class TransitionBuilder { + private ScreenRecorder mScreenRecorder; + private WindowManagerTraceMonitor mWmTraceMonitor; + private LayersTraceMonitor mLayersTraceMonitor; + private WindowAnimationFrameStatsMonitor mFrameStatsMonitor; + + private List<ITransitionMonitor> mAllRunsMonitors = new LinkedList<>(); + private List<ITransitionMonitor> mPerRunMonitors = new LinkedList<>(); + private List<Runnable> mBeforeAlls = new LinkedList<>(); + private List<Runnable> mBefores = new LinkedList<>(); + private List<Runnable> mTransitions = new LinkedList<>(); + private List<Runnable> mAfters = new LinkedList<>(); + private List<Runnable> mAfterAlls = new LinkedList<>(); + + private boolean mRunJankFree = true; + private boolean mCaptureWindowManagerTrace = true; + private boolean mCaptureLayersTrace = true; + private boolean mRecordEachRun = false; + private int mIterations = 1; + private String mTestTag = ""; + + private boolean mRecordAllRuns = false; + + TransitionBuilder() { + mScreenRecorder = new ScreenRecorder(); + mWmTraceMonitor = new WindowManagerTraceMonitor(); + mLayersTraceMonitor = new LayersTraceMonitor(); + mFrameStatsMonitor = new + WindowAnimationFrameStatsMonitor(InstrumentationRegistry.getInstrumentation()); + } + + TransitionRunner build() { + if (mCaptureWindowManagerTrace) { + mPerRunMonitors.add(mWmTraceMonitor); + } + + if (mCaptureLayersTrace) { + mPerRunMonitors.add(mLayersTraceMonitor); + } + + if (mRunJankFree) { + mPerRunMonitors.add(mFrameStatsMonitor); + } + + if (mRecordAllRuns) { + mAllRunsMonitors.add(mScreenRecorder); + } + + if (mRecordEachRun) { + mPerRunMonitors.add(mScreenRecorder); + } + + return new TransitionRunner(this); + } + + TransitionBuilder runBeforeAll(Runnable runnable) { + mBeforeAlls.add(runnable); + return this; + } + + TransitionBuilder runBefore(Runnable runnable) { + mBefores.add(runnable); + return this; + } + + TransitionBuilder run(Runnable runnable) { + mTransitions.add(runnable); + return this; + } + + TransitionBuilder runAfter(Runnable runnable) { + mAfters.add(runnable); + return this; + } + + TransitionBuilder runAfterAll(Runnable runnable) { + mAfterAlls.add(runnable); + return this; + } + + TransitionBuilder repeat(int iterations) { + mIterations = iterations; + return this; + } + + TransitionBuilder skipWindowManagerTrace() { + mCaptureWindowManagerTrace = false; + return this; + } + + TransitionBuilder skipLayersTrace() { + mCaptureLayersTrace = false; + return this; + } + + TransitionBuilder includeJankyRuns() { + mRunJankFree = false; + return this; + } + + TransitionBuilder recordEachRun() { + if (mRecordAllRuns) { + throw new IllegalArgumentException("Invalid option with recordAllRuns"); + } + mRecordEachRun = true; + return this; + } + + TransitionBuilder recordAllRuns() { + if (mRecordEachRun) { + throw new IllegalArgumentException("Invalid option with recordEachRun"); + } + mRecordAllRuns = true; + return this; + } + + TransitionBuilder withTag(String testTag) { + mTestTag = testTag; + return this; + } + } +} diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WindowManagerTrace.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WindowManagerTrace.java new file mode 100644 index 000000000000..e3592eb8cd01 --- /dev/null +++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WindowManagerTrace.java @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2018 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.wm.flicker; + +import android.annotation.Nullable; + +import com.android.server.wm.flicker.Assertions.Result; +import com.android.server.wm.nano.AppWindowTokenProto; +import com.android.server.wm.nano.StackProto; +import com.android.server.wm.nano.TaskProto; +import com.android.server.wm.nano.WindowManagerTraceFileProto; +import com.android.server.wm.nano.WindowManagerTraceProto; +import com.android.server.wm.nano.WindowStateProto; +import com.android.server.wm.nano.WindowTokenProto; + +import com.google.protobuf.nano.InvalidProtocolBufferNanoException; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * Contains a collection of parsed WindowManager trace entries and assertions to apply over + * a single entry. + * + * Each entry is parsed into a list of {@link WindowManagerTrace.Entry} objects. + */ +public class WindowManagerTrace { + private static final int DEFAULT_DISPLAY = 0; + private final List<Entry> mEntries; + @Nullable + final private Path mSource; + + private WindowManagerTrace(List<Entry> entries, Path source) { + this.mEntries = entries; + this.mSource = source; + } + + /** + * Parses {@code WindowManagerTraceFileProto} from {@code data} and uses the proto to + * generates a list of trace entries. + * + * @param data binary proto data + * @param source Path to source of data for additional debug information + */ + static WindowManagerTrace parseFrom(byte[] data, Path source) { + List<Entry> entries = new ArrayList<>(); + + WindowManagerTraceFileProto fileProto; + try { + fileProto = WindowManagerTraceFileProto.parseFrom(data); + } catch (InvalidProtocolBufferNanoException e) { + throw new RuntimeException(e); + } + for (WindowManagerTraceProto entryProto : fileProto.entry) { + entries.add(new Entry(entryProto)); + } + return new WindowManagerTrace(entries, source); + } + + static WindowManagerTrace parseFrom(byte[] data) { + return parseFrom(data, null); + } + + public List<Entry> getEntries() { + return mEntries; + } + + Entry getEntry(long timestamp) { + Optional<Entry> entry = mEntries.stream() + .filter(e -> e.getTimestamp() == timestamp) + .findFirst(); + if (!entry.isPresent()) { + throw new RuntimeException("Entry does not exist for timestamp " + timestamp); + } + return entry.get(); + } + + Optional<Path> getSource() { + return Optional.ofNullable(mSource); + } + + /** + * Represents a single WindowManager trace entry. + */ + static class Entry implements ITraceEntry { + private final WindowManagerTraceProto mProto; + + Entry(WindowManagerTraceProto proto) { + mProto = proto; + } + + private static Result isWindowVisible(String windowTitle, + WindowTokenProto[] windowTokenProtos) { + boolean titleFound = false; + for (WindowTokenProto windowToken : windowTokenProtos) { + for (WindowStateProto windowState : windowToken.windows) { + if (windowState.identifier.title.contains(windowTitle)) { + titleFound = true; + if (isVisible(windowState)) { + return new Result(true /* success */, + windowState.identifier.title + " is visible"); + } + } + } + } + + String reason; + if (!titleFound) { + reason = windowTitle + " cannot be found"; + } else { + reason = windowTitle + " is invisible"; + } + return new Result(false /* success */, reason); + } + + private static boolean isVisible(WindowStateProto windowState) { + return windowState.windowContainer.visible; + } + + @Override + public long getTimestamp() { + return mProto.elapsedRealtimeNanos; + } + + /** + * Returns window title of the top most visible app window. + */ + private String getTopVisibleAppWindow() { + StackProto[] stacks = mProto.windowManagerService.rootWindowContainer + .displays[DEFAULT_DISPLAY].stacks; + for (StackProto stack : stacks) { + for (TaskProto task : stack.tasks) { + for (AppWindowTokenProto token : task.appWindowTokens) { + for (WindowStateProto windowState : token.windowToken.windows) { + if (windowState.windowContainer.visible) { + return task.appWindowTokens[0].name; + } + } + } + } + } + + return ""; + } + + /** + * Checks if aboveAppWindow with {@code windowTitle} is visible. + */ + Result isAboveAppWindowVisible(String windowTitle) { + WindowTokenProto[] windowTokenProtos = mProto.windowManagerService + .rootWindowContainer + .displays[DEFAULT_DISPLAY].aboveAppWindows; + Result result = isWindowVisible(windowTitle, windowTokenProtos); + return new Result(result.success, getTimestamp(), "showsAboveAppWindow", result.reason); + } + + /** + * Checks if belowAppWindow with {@code windowTitle} is visible. + */ + Result isBelowAppWindowVisible(String windowTitle) { + WindowTokenProto[] windowTokenProtos = mProto.windowManagerService + .rootWindowContainer + .displays[DEFAULT_DISPLAY].belowAppWindows; + Result result = isWindowVisible(windowTitle, windowTokenProtos); + return new Result(result.success, getTimestamp(), "isBelowAppWindowVisible", + result.reason); + } + + /** + * Checks if imeWindow with {@code windowTitle} is visible. + */ + Result isImeWindowVisible(String windowTitle) { + WindowTokenProto[] windowTokenProtos = mProto.windowManagerService + .rootWindowContainer + .displays[DEFAULT_DISPLAY].imeWindows; + Result result = isWindowVisible(windowTitle, windowTokenProtos); + return new Result(result.success, getTimestamp(), "isImeWindowVisible", + result.reason); + } + + /** + * Checks if app window with {@code windowTitle} is on top. + */ + Result isVisibleAppWindowOnTop(String windowTitle) { + String topAppWindow = getTopVisibleAppWindow(); + boolean success = topAppWindow.contains(windowTitle); + String reason = "wanted=" + windowTitle + " found=" + topAppWindow; + return new Result(success, getTimestamp(), "isAppWindowOnTop", reason); + } + + /** + * Checks if app window with {@code windowTitle} is visible. + */ + Result isAppWindowVisible(String windowTitle) { + final String assertionName = "isAppWindowVisible"; + boolean titleFound = false; + StackProto[] stacks = mProto.windowManagerService.rootWindowContainer + .displays[DEFAULT_DISPLAY].stacks; + for (StackProto stack : stacks) { + for (TaskProto task : stack.tasks) { + for (AppWindowTokenProto token : task.appWindowTokens) { + if (token.name.contains(windowTitle)) { + titleFound = true; + for (WindowStateProto windowState : token.windowToken.windows) { + if (windowState.windowContainer.visible) { + return new Result(true /* success */, getTimestamp(), + assertionName, "Window " + token.name + + "is visible"); + } + } + } + } + } + } + String reason; + if (!titleFound) { + reason = "Window " + windowTitle + " cannot be found"; + } else { + reason = "Window " + windowTitle + " is invisible"; + } + return new Result(false /* success */, getTimestamp(), assertionName, reason); + } + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WindowUtils.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WindowUtils.java new file mode 100644 index 000000000000..0da876173995 --- /dev/null +++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WindowUtils.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2018 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.wm.flicker; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Point; +import android.graphics.Rect; +import android.support.test.InstrumentationRegistry; +import android.view.Surface; +import android.view.WindowManager; + +/** + * Helper functions to retrieve system window sizes and positions. + */ +class WindowUtils { + + static Rect getDisplayBounds() { + Point display = new Point(); + WindowManager wm = + (WindowManager) InstrumentationRegistry.getContext().getSystemService( + Context.WINDOW_SERVICE); + wm.getDefaultDisplay().getRealSize(display); + return new Rect(0, 0, display.x, display.y); + } + + private static int getCurrentRotation() { + WindowManager wm = + (WindowManager) InstrumentationRegistry.getContext().getSystemService( + Context.WINDOW_SERVICE); + return wm.getDefaultDisplay().getRotation(); + } + + static Rect getDisplayBounds(int requestedRotation) { + Rect displayBounds = getDisplayBounds(); + int currentDisplayRotation = getCurrentRotation(); + + boolean displayIsRotated = (currentDisplayRotation == Surface.ROTATION_90 || + currentDisplayRotation == Surface.ROTATION_270); + + boolean requestedDisplayIsRotated = requestedRotation == Surface.ROTATION_90 || + requestedRotation == Surface.ROTATION_270; + + // if the current orientation changes with the requested rotation, + // flip height and width of display bounds. + if (displayIsRotated != requestedDisplayIsRotated) { + return new Rect(0, 0, displayBounds.height(), displayBounds.width()); + } + + return new Rect(0, 0, displayBounds.width(), displayBounds.height()); + } + + + static Rect getAppPosition(int requestedRotation) { + Rect displayBounds = getDisplayBounds(); + int currentDisplayRotation = getCurrentRotation(); + + boolean displayIsRotated = currentDisplayRotation == Surface.ROTATION_90 || + currentDisplayRotation == Surface.ROTATION_270; + + boolean requestedAppIsRotated = requestedRotation == Surface.ROTATION_90 || + requestedRotation == Surface.ROTATION_270; + + // display size will change if the display is reflected. Flip height and width of app if the + // requested rotation is different from the current rotation. + if (displayIsRotated != requestedAppIsRotated) { + return new Rect(0, 0, displayBounds.height(), displayBounds.width()); + } + + return new Rect(0, 0, displayBounds.width(), displayBounds.height()); + } + + static Rect getStatusBarPosition(int requestedRotation) { + Resources resources = InstrumentationRegistry.getContext().getResources(); + String resourceName; + Rect displayBounds = getDisplayBounds(); + int width; + if (requestedRotation == Surface.ROTATION_0 || requestedRotation == Surface.ROTATION_180) { + resourceName = "status_bar_height_portrait"; + width = Math.min(displayBounds.width(), displayBounds.height()); + } else { + resourceName = "status_bar_height_landscape"; + width = Math.max(displayBounds.width(), displayBounds.height()); + } + + int resourceId = resources.getIdentifier(resourceName, "dimen", "android"); + int height = resources.getDimensionPixelSize(resourceId); + + return new Rect(0, 0, width, height); + } + + static Rect getNavigationBarPosition(int requestedRotation) { + Resources resources = InstrumentationRegistry.getContext().getResources(); + Rect displayBounds = getDisplayBounds(); + int displayWidth = Math.min(displayBounds.width(), displayBounds.height()); + int displayHeight = Math.max(displayBounds.width(), displayBounds.height()); + int resourceId; + if (requestedRotation == Surface.ROTATION_0 || requestedRotation == Surface.ROTATION_180) { + resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android"); + int height = resources.getDimensionPixelSize(resourceId); + return new Rect(0, displayHeight - height, displayWidth, displayHeight); + } else { + resourceId = resources.getIdentifier("navigation_bar_width", "dimen", "android"); + int width = resources.getDimensionPixelSize(resourceId); + // swap display dimensions in landscape or seascape mode + int temp = displayHeight; + displayHeight = displayWidth; + displayWidth = temp; + if (requestedRotation == Surface.ROTATION_90) { + return new Rect(0, 0, width, displayHeight); + } else { + return new Rect(displayWidth - width, 0, displayWidth, displayHeight); + } + } + } + + static int getNavigationBarHeight() { + Resources resources = InstrumentationRegistry.getContext().getResources(); + int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android"); + return resources.getDimensionPixelSize(resourceId); + } + + static int getDockedStackDividerInset() { + Resources resources = InstrumentationRegistry.getContext().getResources(); + int resourceId = resources.getIdentifier("docked_stack_divider_insets", "dimen", + "android"); + return resources.getDimensionPixelSize(resourceId); + } +} diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WmTraceSubject.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WmTraceSubject.java new file mode 100644 index 000000000000..1fc7d591d2bb --- /dev/null +++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WmTraceSubject.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2018 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.wm.flicker; + +import static com.google.common.truth.Truth.assertAbout; +import static com.google.common.truth.Truth.assertWithMessage; + +import android.annotation.Nullable; + +import com.android.server.wm.flicker.Assertions.Result; +import com.android.server.wm.flicker.TransitionRunner.TransitionResult; + +import com.google.common.truth.FailureStrategy; +import com.google.common.truth.Subject; +import com.google.common.truth.SubjectFactory; + +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * Truth subject for {@link WindowManagerTrace} objects. + */ +public class WmTraceSubject extends Subject<WmTraceSubject, WindowManagerTrace> { + // Boiler-plate Subject.Factory for WmTraceSubject + private static final SubjectFactory<WmTraceSubject, WindowManagerTrace> FACTORY = + new SubjectFactory<WmTraceSubject, WindowManagerTrace>() { + @Override + public WmTraceSubject getSubject( + FailureStrategy fs, @Nullable WindowManagerTrace target) { + return new WmTraceSubject(fs, target); + } + }; + + private AssertionsChecker<WindowManagerTrace.Entry> mChecker = new AssertionsChecker<>(); + + private WmTraceSubject(FailureStrategy fs, @Nullable WindowManagerTrace subject) { + super(fs, subject); + } + + // User-defined entry point + public static WmTraceSubject assertThat(@Nullable WindowManagerTrace entry) { + return assertAbout(FACTORY).that(entry); + } + + // User-defined entry point + public static WmTraceSubject assertThat(@Nullable TransitionResult result) { + WindowManagerTrace entries = WindowManagerTrace.parseFrom(result.getWindowManagerTrace(), + result.getWindowManagerTracePath()); + return assertWithMessage(result.toString()).about(FACTORY).that(entries); + } + + // Static method for getting the subject factory (for use with assertAbout()) + public static SubjectFactory<WmTraceSubject, WindowManagerTrace> entries() { + return FACTORY; + } + + public void forAllEntries() { + test(); + } + + public void forRange(long startTime, long endTime) { + mChecker.filterByRange(startTime, endTime); + test(); + } + + public WmTraceSubject then() { + mChecker.checkChangingAssertions(); + return this; + } + + public void inTheBeginning() { + if (getSubject().getEntries().isEmpty()) { + fail("No entries found."); + } + mChecker.checkFirstEntry(); + test(); + } + + public void atTheEnd() { + if (getSubject().getEntries().isEmpty()) { + fail("No entries found."); + } + mChecker.checkLastEntry(); + test(); + } + + private void test() { + List<Result> failures = mChecker.test(getSubject().getEntries()); + if (!failures.isEmpty()) { + Optional<Path> failureTracePath = getSubject().getSource(); + String failureLogs = failures.stream().map(Result::toString) + .collect(Collectors.joining("\n")); + String tracePath = ""; + if (failureTracePath.isPresent()) { + tracePath = "\nWindowManager Trace can be found in: " + + failureTracePath.get().toAbsolutePath() + "\n"; + } + fail(tracePath + failureLogs); + } + } + + public WmTraceSubject showsAboveAppWindow(String partialWindowTitle) { + mChecker.add(entry -> entry.isAboveAppWindowVisible(partialWindowTitle), + "showsAboveAppWindow(" + partialWindowTitle + ")"); + return this; + } + + public WmTraceSubject hidesAboveAppWindow(String partialWindowTitle) { + mChecker.add(entry -> entry.isAboveAppWindowVisible(partialWindowTitle).negate(), + "hidesAboveAppWindow" + "(" + partialWindowTitle + ")"); + return this; + } + + public WmTraceSubject showsBelowAppWindow(String partialWindowTitle) { + mChecker.add(entry -> entry.isBelowAppWindowVisible(partialWindowTitle), + "showsBelowAppWindow(" + partialWindowTitle + ")"); + return this; + } + + public WmTraceSubject hidesBelowAppWindow(String partialWindowTitle) { + mChecker.add(entry -> entry.isBelowAppWindowVisible(partialWindowTitle).negate(), + "hidesBelowAppWindow" + "(" + partialWindowTitle + ")"); + return this; + } + + public WmTraceSubject showsImeWindow(String partialWindowTitle) { + mChecker.add(entry -> entry.isImeWindowVisible(partialWindowTitle), + "showsBelowAppWindow(" + partialWindowTitle + ")"); + return this; + } + + public WmTraceSubject hidesImeWindow(String partialWindowTitle) { + mChecker.add(entry -> entry.isImeWindowVisible(partialWindowTitle).negate(), + "hidesImeWindow" + "(" + partialWindowTitle + ")"); + return this; + } + + public WmTraceSubject showsAppWindowOnTop(String partialWindowTitle) { + mChecker.add( + entry -> { + Result result = entry.isAppWindowVisible(partialWindowTitle); + if (result.passed()) { + result = entry.isVisibleAppWindowOnTop(partialWindowTitle); + } + return result; + }, + "showsAppWindowOnTop(" + partialWindowTitle + ")" + ); + return this; + } + + public WmTraceSubject hidesAppWindowOnTop(String partialWindowTitle) { + mChecker.add( + entry -> { + Result result = entry.isAppWindowVisible(partialWindowTitle).negate(); + if (result.failed()) { + result = entry.isVisibleAppWindowOnTop(partialWindowTitle).negate(); + } + return result; + }, + "hidesAppWindowOnTop(" + partialWindowTitle + ")" + ); + return this; + } + + public WmTraceSubject showsAppWindow(String partialWindowTitle) { + mChecker.add(entry -> entry.isAppWindowVisible(partialWindowTitle), + "showsAppWindow(" + partialWindowTitle + ")"); + return this; + } + + public WmTraceSubject hidesAppWindow(String partialWindowTitle) { + mChecker.add(entry -> entry.isAppWindowVisible(partialWindowTitle).negate(), + "hidesAppWindow(" + partialWindowTitle + ")"); + return this; + } +} diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/ITransitionMonitor.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/ITransitionMonitor.java new file mode 100644 index 000000000000..67e0ecc1cde7 --- /dev/null +++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/ITransitionMonitor.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2018 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.wm.flicker.monitor; + +import android.os.Environment; + +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * Collects test artifacts during a UI transition. + */ +public interface ITransitionMonitor { + Path OUTPUT_DIR = Paths.get(Environment.getExternalStorageDirectory().toString(), "flicker"); + + /** + * Starts monitor. + */ + void start(); + + /** + * Stops monitor. + */ + void stop(); + + /** + * Saves any monitor artifacts to file adding {@code testTag} and {@code iteration} + * to the file name. + * + * @param testTag suffix added to artifact name + * @param iteration suffix added to artifact name + * + * @return Path to saved artifact + */ + default Path save(String testTag, int iteration) { + return save(testTag + "_" + iteration); + } + + /** + * Saves any monitor artifacts to file adding {@code testTag} to the file name. + * + * @param testTag suffix added to artifact name + * + * @return Path to saved artifact + */ + default Path save(String testTag) { + throw new UnsupportedOperationException("Save not implemented for this monitor"); + } +} diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/LayersTraceMonitor.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/LayersTraceMonitor.java new file mode 100644 index 000000000000..c55d068b41b8 --- /dev/null +++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/LayersTraceMonitor.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2018 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.wm.flicker.monitor; + +import android.os.IBinder; +import android.os.Parcel; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; + +/** + * Captures Layers trace from SurfaceFlinger. + */ +public class LayersTraceMonitor extends TraceMonitor { + private static final String TAG = "LayersTraceMonitor"; + private IBinder mSurfaceFlinger = ServiceManager.getService("SurfaceFlinger"); + + public LayersTraceMonitor() { + traceFileName = "layers_trace.pb"; + } + + @Override + public void start() { + setEnabled(true); + } + + @Override + public void stop() { + setEnabled(false); + } + + @Override + public boolean isEnabled() throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken("android.ui.ISurfaceComposer"); + mSurfaceFlinger.transact(/* LAYER_TRACE_STATUS_CODE */ 1026, + data, reply, 0 /* flags */); + return reply.readBoolean(); + } + + private void setEnabled(boolean isEnabled) { + Parcel data = null; + try { + if (mSurfaceFlinger != null) { + data = Parcel.obtain(); + data.writeInterfaceToken("android.ui.ISurfaceComposer"); + data.writeInt(isEnabled ? 1 : 0); + mSurfaceFlinger.transact( /* LAYER_TRACE_CONTROL_CODE */ 1025, + data, null, 0 /* flags */); + } + } catch (RemoteException e) { + Log.e(TAG, "Could not set layer tracing." + e.toString()); + } finally { + if (data != null) { + data.recycle(); + } + } + } +} diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/ScreenRecorder.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/ScreenRecorder.java new file mode 100644 index 000000000000..4787586777ae --- /dev/null +++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/ScreenRecorder.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2018 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.wm.flicker.monitor; + +import static com.android.compatibility.common.util.SystemUtil.runShellCommand; + +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; + +import android.support.annotation.VisibleForTesting; +import android.util.Log; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * Captures screen contents and saves it as a mp4 video file. + */ +public class ScreenRecorder implements ITransitionMonitor { + @VisibleForTesting + static final Path DEFAULT_OUTPUT_PATH = OUTPUT_DIR.resolve("transition.mp4"); + private static final String TAG = "FLICKER"; + private Thread recorderThread; + + @VisibleForTesting + static Path getPath(String testTag) { + return OUTPUT_DIR.resolve(testTag + ".mp4"); + } + + @Override + public void start() { + OUTPUT_DIR.toFile().mkdirs(); + String command = "screenrecord " + DEFAULT_OUTPUT_PATH; + recorderThread = new Thread(() -> { + try { + Runtime.getRuntime().exec(command); + } catch (IOException e) { + Log.e(TAG, "Error executing " + command, e); + } + }); + recorderThread.start(); + } + + @Override + public void stop() { + runShellCommand("killall -s 2 screenrecord"); + try { + recorderThread.join(); + } catch (InterruptedException e) { + // ignore + } + } + + @Override + public Path save(String testTag) { + try { + return Files.move(DEFAULT_OUTPUT_PATH, getPath(testTag), + REPLACE_EXISTING); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/TraceMonitor.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/TraceMonitor.java new file mode 100644 index 000000000000..0e154ecd5d4d --- /dev/null +++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/TraceMonitor.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2018 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.wm.flicker.monitor; + +import static com.android.compatibility.common.util.SystemUtil.runShellCommand; + +import android.os.RemoteException; + +import com.android.internal.annotations.VisibleForTesting; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Locale; + +/** + * Base class for monitors containing common logic to read the trace + * as a byte array and save the trace to another location. + */ +public abstract class TraceMonitor implements ITransitionMonitor { + public static final String TAG = "FLICKER"; + private static final String TRACE_DIR = "/data/misc/wmtrace/"; + + String traceFileName; + + abstract boolean isEnabled() throws RemoteException; + + /** + * Saves trace file to the external storage directory suffixing the name with the testtag + * and iteration. + * + * Moves the trace file from the default location via a shell command since the test app + * does not have security privileges to access /data/misc/wmtrace. + * + * @param testTag suffix added to trace name used to identify trace + * + * @return Path to saved trace file + */ + @Override + public Path save(String testTag) { + OUTPUT_DIR.toFile().mkdirs(); + Path traceFileCopy = getOutputTraceFilePath(testTag); + String copyCommand = String.format(Locale.getDefault(), "mv %s%s %s", TRACE_DIR, + traceFileName, traceFileCopy.toString()); + runShellCommand(copyCommand); + return traceFileCopy; + } + + @VisibleForTesting + Path getOutputTraceFilePath(String testTag) { + return OUTPUT_DIR.resolve(traceFileName + "_" + testTag); + } +} diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/WindowAnimationFrameStatsMonitor.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/WindowAnimationFrameStatsMonitor.java new file mode 100644 index 000000000000..717d187e1d4a --- /dev/null +++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/WindowAnimationFrameStatsMonitor.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2018 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.wm.flicker.monitor; + +import static android.view.FrameStats.UNDEFINED_TIME_NANO; + +import android.app.Instrumentation; +import android.util.Log; +import android.view.FrameStats; + +/** + * Monitors {@link android.view.WindowAnimationFrameStats} to detect janky frames. + * + * Adapted from {@link android.support.test.jank.internal.WindowAnimationFrameStatsMonitorImpl} + * using the same threshold to determine jank. + */ +public class WindowAnimationFrameStatsMonitor implements ITransitionMonitor { + + private static final String TAG = "FLICKER"; + // Maximum normalized error in frame duration before the frame is considered janky + private static final double MAX_ERROR = 0.5f; + // Maximum normalized frame duration before the frame is considered a pause + private static final double PAUSE_THRESHOLD = 15.0f; + private Instrumentation mInstrumentation; + private FrameStats stats; + private int numJankyFrames; + private long mLongestFrameNano = 0L; + + + /** + * Constructs a WindowAnimationFrameStatsMonitor instance. + */ + public WindowAnimationFrameStatsMonitor(Instrumentation instrumentation) { + mInstrumentation = instrumentation; + } + + private void analyze() { + int frameCount = stats.getFrameCount(); + long refreshPeriodNano = stats.getRefreshPeriodNano(); + + // Skip first frame + for (int i = 2; i < frameCount; i++) { + // Handle frames that have not been presented. + if (stats.getFramePresentedTimeNano(i) == UNDEFINED_TIME_NANO) { + // The animation must not have completed. Warn and break out of the loop. + Log.w(TAG, "Skipping fenced frame."); + break; + } + long frameDurationNano = stats.getFramePresentedTimeNano(i) - + stats.getFramePresentedTimeNano(i - 1); + double normalized = (double) frameDurationNano / refreshPeriodNano; + if (normalized < PAUSE_THRESHOLD) { + if (normalized > 1.0f + MAX_ERROR) { + numJankyFrames++; + } + mLongestFrameNano = Math.max(mLongestFrameNano, frameDurationNano); + } + } + } + + @Override + public void start() { + // Clear out any previous data + numJankyFrames = 0; + mLongestFrameNano = 0; + mInstrumentation.getUiAutomation().clearWindowAnimationFrameStats(); + } + + @Override + public void stop() { + stats = mInstrumentation.getUiAutomation().getWindowAnimationFrameStats(); + analyze(); + } + + public boolean jankyFramesDetected() { + return stats.getFrameCount() > 0 && numJankyFrames > 0; + } + + @Override + public String toString() { + return stats.toString() + + " RefreshPeriodNano:" + stats.getRefreshPeriodNano() + + " NumJankyFrames:" + numJankyFrames + + " LongestFrameNano:" + mLongestFrameNano; + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitor.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitor.java new file mode 100644 index 000000000000..ae160b68c976 --- /dev/null +++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitor.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2018 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.wm.flicker.monitor; + +import android.os.RemoteException; +import android.view.IWindowManager; +import android.view.WindowManagerGlobal; + +/** + * Captures WindowManager trace from WindowManager. + */ +public class WindowManagerTraceMonitor extends TraceMonitor { + private IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); + + public WindowManagerTraceMonitor() { + traceFileName = "wm_trace.pb"; + } + + @Override + public void start() { + try { + wm.startWindowTrace(); + } catch (RemoteException e) { + throw new RuntimeException("Could not start trace", e); + } + } + + @Override + public void stop() { + try { + wm.stopWindowTrace(); + } catch (RemoteException e) { + throw new RuntimeException("Could not stop trace", e); + } + } + + @Override + public boolean isEnabled() throws RemoteException{ + return wm.isWindowTraceEnabled(); + } +} diff --git a/tests/FlickerTests/lib/test/Android.mk b/tests/FlickerTests/lib/test/Android.mk new file mode 100644 index 000000000000..0e3f58d8a8c9 --- /dev/null +++ b/tests/FlickerTests/lib/test/Android.mk @@ -0,0 +1,36 @@ +# +# Copyright (C) 2018 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. +# + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_PACKAGE_NAME := FlickerLibTest +LOCAL_MODULE_TAGS := tests optional +# sign this with platform cert, so this test is allowed to call private platform apis +LOCAL_CERTIFICATE := platform +LOCAL_PRIVATE_PLATFORM_APIS := true +LOCAL_COMPATIBILITY_SUITE := tests +LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_JAVA_LIBRARIES := android.test.runner +LOCAL_STATIC_JAVA_LIBRARIES := \ + android-support-test \ + platform-test-annotations \ + truth-prebuilt \ + platformprotosnano \ + layersprotosnano \ + flickerlib + +include $(BUILD_PACKAGE) +include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file diff --git a/tests/FlickerTests/lib/test/AndroidManifest.xml b/tests/FlickerTests/lib/test/AndroidManifest.xml new file mode 100644 index 000000000000..d30172d56c2c --- /dev/null +++ b/tests/FlickerTests/lib/test/AndroidManifest.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright 2018 Google Inc. All Rights Reserved. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.wm.flicker"> + + <uses-sdk android:minSdkVersion="27" android:targetSdkVersion="27"/> + <!-- Read and write traces from external storage --> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> + <!-- Capture screen contents --> + <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" /> + <!-- Run layers trace --> + <uses-permission android:name="android.permission.HARDWARE_TEST"/> + <application android:label="FlickerLibTest"> + <uses-library android:name="android.test.runner"/> + </application> + + <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.server.wm.flicker" + android:label="WindowManager Flicker Lib Test"> + </instrumentation> + +</manifest>
\ No newline at end of file diff --git a/tests/FlickerTests/lib/test/AndroidTest.xml b/tests/FlickerTests/lib/test/AndroidTest.xml new file mode 100644 index 000000000000..e4cc298a2aa8 --- /dev/null +++ b/tests/FlickerTests/lib/test/AndroidTest.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright 2018 Google Inc. All Rights Reserved. + --> +<configuration description="Config for WindowManager Flicker Tests"> + <target_preparer class="com.google.android.tradefed.targetprep.GoogleDeviceSetup"> + <!-- keeps the screen on during tests --> + <option name="screen-always-on" value="on" /> + <!-- prevents the phone from restarting --> + <option name="force-skip-system-props" value="true" /> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true"/> + <option name="test-file-name" value="FlickerLibTest.apk"/> + </target_preparer> + <test class="com.android.tradefed.testtype.AndroidJUnitTest"> + <option name="package" value="com.android.server.wm.flicker"/> + <option name="hidden-api-checks" value="false" /> + </test> +</configuration> diff --git a/tests/FlickerTests/lib/test/assets/testdata/layers_trace_emptyregion.pb b/tests/FlickerTests/lib/test/assets/testdata/layers_trace_emptyregion.pb Binary files differnew file mode 100644 index 000000000000..98ee6f3ed269 --- /dev/null +++ b/tests/FlickerTests/lib/test/assets/testdata/layers_trace_emptyregion.pb diff --git a/tests/FlickerTests/lib/test/assets/testdata/layers_trace_invalid_layer_visibility.pb b/tests/FlickerTests/lib/test/assets/testdata/layers_trace_invalid_layer_visibility.pb Binary files differnew file mode 100644 index 000000000000..20572d79d826 --- /dev/null +++ b/tests/FlickerTests/lib/test/assets/testdata/layers_trace_invalid_layer_visibility.pb diff --git a/tests/FlickerTests/lib/test/assets/testdata/layers_trace_orphanlayers.pb b/tests/FlickerTests/lib/test/assets/testdata/layers_trace_orphanlayers.pb Binary files differnew file mode 100644 index 000000000000..af4079707c69 --- /dev/null +++ b/tests/FlickerTests/lib/test/assets/testdata/layers_trace_orphanlayers.pb diff --git a/tests/FlickerTests/lib/test/assets/testdata/wm_trace_openchrome.pb b/tests/FlickerTests/lib/test/assets/testdata/wm_trace_openchrome.pb Binary files differnew file mode 100644 index 000000000000..b3f31706f55c --- /dev/null +++ b/tests/FlickerTests/lib/test/assets/testdata/wm_trace_openchrome.pb diff --git a/tests/FlickerTests/lib/test/assets/testdata/wm_trace_openchrome2.pb b/tests/FlickerTests/lib/test/assets/testdata/wm_trace_openchrome2.pb Binary files differnew file mode 100644 index 000000000000..b3b73ce0518a --- /dev/null +++ b/tests/FlickerTests/lib/test/assets/testdata/wm_trace_openchrome2.pb diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/AssertionsCheckerTest.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/AssertionsCheckerTest.java new file mode 100644 index 000000000000..8e7fe1b4f942 --- /dev/null +++ b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/AssertionsCheckerTest.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2018 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.wm.flicker; + +import static com.google.common.truth.Truth.assertThat; + +import com.android.server.wm.flicker.Assertions.Result; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +/** + * Contains {@link AssertionsChecker} tests. + * To run this test: {@code atest FlickerLibTest:AssertionsCheckerTest} + */ +public class AssertionsCheckerTest { + + /** + * Returns a list of SimpleEntry objects with {@code data} and incremental timestamps starting + * at 0. + */ + private static List<SimpleEntry> getTestEntries(int... data) { + List<SimpleEntry> entries = new ArrayList<>(); + for (int i = 0; i < data.length; i++) { + entries.add(new SimpleEntry(i, data[i])); + } + return entries; + } + + @Test + public void canCheckAllEntries() { + AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>(); + checker.add(SimpleEntry::isData42, "isData42"); + + List<Result> failures = checker.test(getTestEntries(1, 1, 1, 1, 1)); + + assertThat(failures).hasSize(5); + } + + @Test + public void canCheckFirstEntry() { + AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>(); + checker.checkFirstEntry(); + checker.add(SimpleEntry::isData42, "isData42"); + + List<Result> failures = checker.test(getTestEntries(1, 1, 1, 1, 1)); + + assertThat(failures).hasSize(1); + assertThat(failures.get(0).timestamp).isEqualTo(0); + } + + @Test + public void canCheckLastEntry() { + AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>(); + checker.checkLastEntry(); + checker.add(SimpleEntry::isData42, "isData42"); + + List<Result> failures = checker.test(getTestEntries(1, 1, 1, 1, 1)); + + assertThat(failures).hasSize(1); + assertThat(failures.get(0).timestamp).isEqualTo(4); + } + + @Test + public void canCheckRangeOfEntries() { + AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>(); + checker.filterByRange(1, 2); + checker.add(SimpleEntry::isData42, "isData42"); + + List<Result> failures = checker.test(getTestEntries(1, 42, 42, 1, 1)); + + assertThat(failures).hasSize(0); + } + + @Test + public void emptyRangePasses() { + AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>(); + checker.filterByRange(9, 10); + checker.add(SimpleEntry::isData42, "isData42"); + + List<Result> failures = checker.test(getTestEntries(1, 1, 1, 1, 1)); + + assertThat(failures).isEmpty(); + } + + @Test + public void canCheckChangingAssertions() { + AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>(); + checker.add(SimpleEntry::isData42, "isData42"); + checker.add(SimpleEntry::isData0, "isData0"); + checker.checkChangingAssertions(); + + List<Result> failures = checker.test(getTestEntries(42, 0, 0, 0, 0)); + + assertThat(failures).isEmpty(); + } + + @Test + public void canCheckChangingAssertions_withNoAssertions() { + AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>(); + checker.checkChangingAssertions(); + + List<Result> failures = checker.test(getTestEntries(42, 0, 0, 0, 0)); + + assertThat(failures).isEmpty(); + } + + @Test + public void canCheckChangingAssertions_withSingleAssertion() { + AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>(); + checker.add(SimpleEntry::isData42, "isData42"); + checker.checkChangingAssertions(); + + List<Result> failures = checker.test(getTestEntries(42, 42, 42, 42, 42)); + + assertThat(failures).isEmpty(); + } + + @Test + public void canFailCheckChangingAssertions_ifStartingAssertionFails() { + AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>(); + checker.add(SimpleEntry::isData42, "isData42"); + checker.add(SimpleEntry::isData0, "isData0"); + checker.checkChangingAssertions(); + + List<Result> failures = checker.test(getTestEntries(0, 0, 0, 0, 0)); + + assertThat(failures).hasSize(1); + } + + @Test + public void canFailCheckChangingAssertions_ifStartingAssertionAlwaysPasses() { + AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>(); + checker.add(SimpleEntry::isData42, "isData42"); + checker.add(SimpleEntry::isData0, "isData0"); + checker.checkChangingAssertions(); + + List<Result> failures = checker.test(getTestEntries(0, 0, 0, 0, 0)); + + assertThat(failures).hasSize(1); + } + + static class SimpleEntry implements ITraceEntry { + long timestamp; + int data; + + SimpleEntry(long timestamp, int data) { + this.timestamp = timestamp; + this.data = data; + } + + @Override + public long getTimestamp() { + return timestamp; + } + + Result isData42() { + return new Result(this.data == 42, this.timestamp, "is42", ""); + } + + Result isData0() { + return new Result(this.data == 0, this.timestamp, "is42", ""); + } + } +} diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/AssertionsTest.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/AssertionsTest.java new file mode 100644 index 000000000000..7fd178ca6e51 --- /dev/null +++ b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/AssertionsTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2018 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.wm.flicker; + +import static com.google.common.truth.Truth.assertThat; + +import com.android.server.wm.flicker.Assertions.Result; + +import org.junit.Test; + +/** + * Contains {@link Assertions} tests. + * To run this test: {@code atest FlickerLibTest:AssertionsTest} + */ +public class AssertionsTest { + @Test + public void traceEntryAssertionCanNegateResult() { + Assertions.TraceAssertion<Integer> assertNumEquals42 = + getIntegerTraceEntryAssertion(); + + assertThat(assertNumEquals42.apply(1).success).isFalse(); + assertThat(assertNumEquals42.negate().apply(1).success).isTrue(); + + assertThat(assertNumEquals42.apply(42).success).isTrue(); + assertThat(assertNumEquals42.negate().apply(42).success).isFalse(); + } + + @Test + public void resultCanBeNegated() { + String reason = "Everything is fine!"; + Result result = new Result(true, 0, "TestAssert", reason); + Result negatedResult = result.negate(); + assertThat(negatedResult.success).isFalse(); + assertThat(negatedResult.reason).isEqualTo(reason); + assertThat(negatedResult.assertionName).isEqualTo("!TestAssert"); + } + + private Assertions.TraceAssertion<Integer> getIntegerTraceEntryAssertion() { + return (num) -> { + if (num == 42) { + return new Result(true, "Num equals 42"); + } + return new Result(false, "Num doesn't equal 42, actual:" + num); + }; + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/LayersTraceSubjectTest.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/LayersTraceSubjectTest.java new file mode 100644 index 000000000000..d06c5d76552b --- /dev/null +++ b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/LayersTraceSubjectTest.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2018 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.wm.flicker; + +import static com.android.server.wm.flicker.LayersTraceSubject.assertThat; +import static com.android.server.wm.flicker.TestFileUtils.readTestFile; + +import static com.google.common.truth.Truth.assertWithMessage; + +import static org.junit.Assert.fail; + +import android.graphics.Rect; + +import org.junit.Test; + +import java.nio.file.Paths; + +/** + * Contains {@link LayersTraceSubject} tests. + * To run this test: {@code atest FlickerLibTest:LayersTraceSubjectTest} + */ +public class LayersTraceSubjectTest { + private static final Rect displayRect = new Rect(0, 0, 1440, 2880); + + private static LayersTrace readLayerTraceFromFile(String relativePath) { + try { + return LayersTrace.parseFrom(readTestFile(relativePath), Paths.get(relativePath)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Test + public void testCanDetectEmptyRegionFromLayerTrace() { + LayersTrace layersTraceEntries = readLayerTraceFromFile("layers_trace_emptyregion.pb"); + try { + assertThat(layersTraceEntries).coversRegion(displayRect).forAllEntries(); + fail("Assertion passed"); + } catch (AssertionError e) { + assertWithMessage("Contains path to trace") + .that(e.getMessage()).contains("layers_trace_emptyregion.pb"); + assertWithMessage("Contains timestamp") + .that(e.getMessage()).contains("0h38m28s8ms"); + assertWithMessage("Contains assertion function") + .that(e.getMessage()).contains("coversRegion"); + assertWithMessage("Contains debug info") + .that(e.getMessage()).contains("Region to test: " + displayRect); + assertWithMessage("Contains debug info") + .that(e.getMessage()).contains("first empty point: 0, 99"); + } + } + + @Test + public void testCanDetectIncorrectVisibilityFromLayerTrace() { + LayersTrace layersTraceEntries = readLayerTraceFromFile( + "layers_trace_invalid_layer_visibility.pb"); + try { + assertThat(layersTraceEntries).showsLayer("com.android.server.wm.flicker.testapp") + .then().hidesLayer("com.android.server.wm.flicker.testapp").forAllEntries(); + fail("Assertion passed"); + } catch (AssertionError e) { + assertWithMessage("Contains path to trace") + .that(e.getMessage()).contains("layers_trace_invalid_layer_visibility.pb"); + assertWithMessage("Contains timestamp") + .that(e.getMessage()).contains("70h13m14s303ms"); + assertWithMessage("Contains assertion function") + .that(e.getMessage()).contains("!isVisible"); + assertWithMessage("Contains debug info") + .that(e.getMessage()).contains( + "com.android.server.wm.flicker.testapp/com.android.server.wm.flicker.testapp" + + ".SimpleActivity#0 is visible"); + } + } +} diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/LayersTraceTest.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/LayersTraceTest.java new file mode 100644 index 000000000000..42b2acace8c9 --- /dev/null +++ b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/LayersTraceTest.java @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2018 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.wm.flicker; + +import static com.android.server.wm.flicker.TestFileUtils.readTestFile; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import static org.junit.Assert.fail; + +import android.content.Context; +import android.graphics.Point; +import android.graphics.Rect; +import android.support.test.InstrumentationRegistry; +import android.view.WindowManager; + +import org.junit.Test; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * Contains {@link LayersTrace} tests. + * To run this test: {@code atest FlickerLibTest:LayersTraceTest} + */ +public class LayersTraceTest { + private static LayersTrace readLayerTraceFromFile(String relativePath) { + try { + return LayersTrace.parseFrom(readTestFile(relativePath)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static Rect getDisplayBounds() { + Point display = new Point(); + WindowManager wm = + (WindowManager) InstrumentationRegistry.getContext().getSystemService( + Context.WINDOW_SERVICE); + wm.getDefaultDisplay().getRealSize(display); + return new Rect(0, 0, display.x, display.y); + } + + @Test + public void canParseAllLayers() { + LayersTrace trace = readLayerTraceFromFile( + "layers_trace_emptyregion.pb"); + assertThat(trace.getEntries()).isNotEmpty(); + assertThat(trace.getEntries().get(0).getTimestamp()).isEqualTo(2307984557311L); + assertThat(trace.getEntries().get(trace.getEntries().size() - 1).getTimestamp()) + .isEqualTo(2308521813510L); + List<LayersTrace.Layer> flattenedLayers = trace.getEntries().get(0).asFlattenedLayers(); + String msg = "Layers:\n" + flattenedLayers.stream().map(layer -> layer.mProto.name) + .collect(Collectors.joining("\n\t")); + assertWithMessage(msg).that(flattenedLayers).hasSize(47); + } + + @Test + public void canParseVisibleLayers() { + LayersTrace trace = readLayerTraceFromFile( + "layers_trace_emptyregion.pb"); + assertThat(trace.getEntries()).isNotEmpty(); + assertThat(trace.getEntries().get(0).getTimestamp()).isEqualTo(2307984557311L); + assertThat(trace.getEntries().get(trace.getEntries().size() - 1).getTimestamp()) + .isEqualTo(2308521813510L); + List<LayersTrace.Layer> flattenedLayers = trace.getEntries().get(0).asFlattenedLayers(); + List<LayersTrace.Layer> visibleLayers = flattenedLayers.stream() + .filter(layer -> layer.isVisible() && !layer.isHiddenByParent()) + .collect(Collectors.toList()); + + String msg = "Visible Layers:\n" + visibleLayers.stream() + .map(layer -> layer.mProto.name) + .collect(Collectors.joining("\n\t")); + + assertWithMessage(msg).that(visibleLayers).hasSize(9); + } + + @Test + public void canParseLayerHierarchy() { + LayersTrace trace = readLayerTraceFromFile( + "layers_trace_emptyregion.pb"); + assertThat(trace.getEntries()).isNotEmpty(); + assertThat(trace.getEntries().get(0).getTimestamp()).isEqualTo(2307984557311L); + assertThat(trace.getEntries().get(trace.getEntries().size() - 1).getTimestamp()) + .isEqualTo(2308521813510L); + List<LayersTrace.Layer> layers = trace.getEntries().get(0).getRootLayers(); + assertThat(layers).hasSize(2); + assertThat(layers.get(0).mChildren).hasSize(layers.get(0).mProto.children.length); + assertThat(layers.get(1).mChildren).hasSize(layers.get(1).mProto.children.length); + } + + // b/76099859 + @Test + public void canDetectOrphanLayers() { + try { + readLayerTraceFromFile( + "layers_trace_orphanlayers.pb"); + fail("Failed to detect orphaned layers."); + } catch (RuntimeException exception) { + assertThat(exception.getMessage()).contains( + "Failed to parse layers trace. Found orphan layers " + + "with parent layer id:1006 : 49"); + } + } + + // b/75276931 + @Test + public void canDetectUncoveredRegion() { + LayersTrace trace = readLayerTraceFromFile( + "layers_trace_emptyregion.pb"); + LayersTrace.Entry entry = trace.getEntry(2308008331271L); + + Assertions.Result result = entry.coversRegion(getDisplayBounds()); + + assertThat(result.failed()).isTrue(); + assertThat(result.reason).contains("Region to test: Rect(0, 0 - 1440, 2880)"); + assertThat(result.reason).contains("first empty point: 0, 99"); + assertThat(result.reason).contains("visible regions:"); + assertWithMessage("Reason contains list of visible regions") + .that(result.reason).contains("StatusBar#0Rect(0, 0 - 1440, 98"); + } + + // Visible region tests + @Test + public void canTestLayerVisibleRegion_layerDoesNotExist() { + LayersTrace trace = readLayerTraceFromFile( + "layers_trace_emptyregion.pb"); + LayersTrace.Entry entry = trace.getEntry(2308008331271L); + + final Rect expectedVisibleRegion = new Rect(0, 0, 1, 1); + Assertions.Result result = entry.hasVisibleRegion("ImaginaryLayer", + expectedVisibleRegion); + + assertThat(result.failed()).isTrue(); + assertThat(result.reason).contains("Could not find ImaginaryLayer"); + } + + @Test + public void canTestLayerVisibleRegion_layerDoesNotHaveExpectedVisibleRegion() { + LayersTrace trace = readLayerTraceFromFile( + "layers_trace_emptyregion.pb"); + LayersTrace.Entry entry = trace.getEntry(2307993020072L); + + final Rect expectedVisibleRegion = new Rect(0, 0, 1, 1); + Assertions.Result result = entry.hasVisibleRegion("NexusLauncherActivity#2", + expectedVisibleRegion); + + assertThat(result.failed()).isTrue(); + assertThat(result.reason).contains( + "Layer com.google.android.apps.nexuslauncher/com.google.android.apps" + + ".nexuslauncher.NexusLauncherActivity#2 is invisible: activeBuffer=null" + + " type != ColorLayer flags=1 (FLAG_HIDDEN set) visible region is empty"); + } + + @Test + public void canTestLayerVisibleRegion_layerIsHiddenByParent() { + LayersTrace trace = readLayerTraceFromFile( + "layers_trace_emptyregion.pb"); + LayersTrace.Entry entry = trace.getEntry(2308455948035L); + + final Rect expectedVisibleRegion = new Rect(0, 0, 1, 1); + Assertions.Result result = entry.hasVisibleRegion( + "SurfaceView - com.android.chrome/com.google.android.apps.chrome.Main", + expectedVisibleRegion); + + assertThat(result.failed()).isTrue(); + assertThat(result.reason).contains( + "Layer SurfaceView - com.android.chrome/com.google.android.apps.chrome.Main#0 is " + + "hidden by parent: com.android.chrome/com.google.android.apps.chrome" + + ".Main#0"); + } + + @Test + public void canTestLayerVisibleRegion_incorrectRegionSize() { + LayersTrace trace = readLayerTraceFromFile( + "layers_trace_emptyregion.pb"); + LayersTrace.Entry entry = trace.getEntry(2308008331271L); + + final Rect expectedVisibleRegion = new Rect(0, 0, 1440, 99); + Assertions.Result result = entry.hasVisibleRegion( + "StatusBar", + expectedVisibleRegion); + + assertThat(result.failed()).isTrue(); + assertThat(result.reason).contains("StatusBar#0 has visible " + + "region:Rect(0, 0 - 1440, 98) expected:Rect(0, 0 - 1440, 99)"); + } + + @Test + public void canTestLayerVisibleRegion() { + LayersTrace trace = readLayerTraceFromFile( + "layers_trace_emptyregion.pb"); + LayersTrace.Entry entry = trace.getEntry(2308008331271L); + + final Rect expectedVisibleRegion = new Rect(0, 0, 1440, 98); + Assertions.Result result = entry.hasVisibleRegion("StatusBar", expectedVisibleRegion); + + assertThat(result.passed()).isTrue(); + } + + @Test + public void canTestLayerVisibleRegion_layerIsNotVisible() { + LayersTrace trace = readLayerTraceFromFile( + "layers_trace_invalid_layer_visibility.pb"); + LayersTrace.Entry entry = trace.getEntry(252794268378458L); + + Assertions.Result result = entry.isVisible("com.android.server.wm.flicker.testapp"); + assertThat(result.failed()).isTrue(); + assertThat(result.reason).contains( + "Layer com.android.server.wm.flicker.testapp/com.android.server.wm.flicker" + + ".testapp.SimpleActivity#0 is invisible: type != ColorLayer visible " + + "region is empty"); + } +} diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/TestFileUtils.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/TestFileUtils.java new file mode 100644 index 000000000000..5a24e6d91595 --- /dev/null +++ b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/TestFileUtils.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2018 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.wm.flicker; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; + +import com.google.common.io.ByteStreams; + +import java.io.InputStream; + +/** + * Helper functions for test file resources. + */ +class TestFileUtils { + static byte[] readTestFile(String relativePath) throws Exception { + Context context = InstrumentationRegistry.getContext(); + InputStream in = context.getResources().getAssets().open("testdata/" + relativePath); + return ByteStreams.toByteArray(in); + } +} diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/TransitionRunnerTest.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/TransitionRunnerTest.java new file mode 100644 index 000000000000..9c5e2059a0e6 --- /dev/null +++ b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/TransitionRunnerTest.java @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2018 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.wm.flicker; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import android.os.Environment; + +import com.android.server.wm.flicker.TransitionRunner.TransitionBuilder; +import com.android.server.wm.flicker.TransitionRunner.TransitionResult; +import com.android.server.wm.flicker.monitor.LayersTraceMonitor; +import com.android.server.wm.flicker.monitor.ScreenRecorder; +import com.android.server.wm.flicker.monitor.WindowAnimationFrameStatsMonitor; +import com.android.server.wm.flicker.monitor.WindowManagerTraceMonitor; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.IOException; +import java.nio.file.Paths; +import java.util.List; + +/** + * Contains {@link TransitionRunner} tests. + * {@code atest FlickerLibTest:TransitionRunnerTest} + */ +public class TransitionRunnerTest { + @Mock + private SimpleUiTransitions mTransitionsMock; + @Mock + private ScreenRecorder mScreenRecorderMock; + @Mock + private WindowManagerTraceMonitor mWindowManagerTraceMonitorMock; + @Mock + private LayersTraceMonitor mLayersTraceMonitorMock; + @Mock + private WindowAnimationFrameStatsMonitor mWindowAnimationFrameStatsMonitor; + @InjectMocks + private TransitionBuilder mTransitionBuilder; + + @Before + public void init() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void transitionsRunInOrder() { + TransitionRunner.newBuilder() + .runBeforeAll(mTransitionsMock::turnOnDevice) + .runBefore(mTransitionsMock::openApp) + .run(mTransitionsMock::performMagic) + .runAfter(mTransitionsMock::closeApp) + .runAfterAll(mTransitionsMock::cleanUpTracks) + .skipLayersTrace() + .skipWindowManagerTrace() + .build() + .run(); + + InOrder orderVerifier = inOrder(mTransitionsMock); + orderVerifier.verify(mTransitionsMock).turnOnDevice(); + orderVerifier.verify(mTransitionsMock).openApp(); + orderVerifier.verify(mTransitionsMock).performMagic(); + orderVerifier.verify(mTransitionsMock).closeApp(); + orderVerifier.verify(mTransitionsMock).cleanUpTracks(); + } + + @Test + public void canCombineTransitions() { + TransitionRunner.newBuilder() + .runBeforeAll(mTransitionsMock::turnOnDevice) + .runBeforeAll(mTransitionsMock::turnOnDevice) + .runBefore(mTransitionsMock::openApp) + .runBefore(mTransitionsMock::openApp) + .run(mTransitionsMock::performMagic) + .run(mTransitionsMock::performMagic) + .runAfter(mTransitionsMock::closeApp) + .runAfter(mTransitionsMock::closeApp) + .runAfterAll(mTransitionsMock::cleanUpTracks) + .runAfterAll(mTransitionsMock::cleanUpTracks) + .skipLayersTrace() + .skipWindowManagerTrace() + .build() + .run(); + + final int wantedNumberOfInvocations = 2; + verify(mTransitionsMock, times(wantedNumberOfInvocations)).turnOnDevice(); + verify(mTransitionsMock, times(wantedNumberOfInvocations)).openApp(); + verify(mTransitionsMock, times(wantedNumberOfInvocations)).performMagic(); + verify(mTransitionsMock, times(wantedNumberOfInvocations)).closeApp(); + verify(mTransitionsMock, times(wantedNumberOfInvocations)).cleanUpTracks(); + } + + @Test + public void emptyTransitionPasses() { + List<TransitionResult> results = TransitionRunner.newBuilder() + .skipLayersTrace() + .skipWindowManagerTrace() + .build() + .run() + .getResults(); + assertThat(results).hasSize(1); + assertThat(results.get(0).layersTraceExists()).isFalse(); + assertThat(results.get(0).windowManagerTraceExists()).isFalse(); + assertThat(results.get(0).screenCaptureVideoExists()).isFalse(); + } + + @Test + public void canRepeatTransitions() { + final int wantedNumberOfInvocations = 10; + TransitionRunner.newBuilder() + .runBeforeAll(mTransitionsMock::turnOnDevice) + .runBefore(mTransitionsMock::openApp) + .run(mTransitionsMock::performMagic) + .runAfter(mTransitionsMock::closeApp) + .runAfterAll(mTransitionsMock::cleanUpTracks) + .repeat(wantedNumberOfInvocations) + .skipLayersTrace() + .skipWindowManagerTrace() + .build() + .run(); + verify(mTransitionsMock).turnOnDevice(); + verify(mTransitionsMock, times(wantedNumberOfInvocations)).openApp(); + verify(mTransitionsMock, times(wantedNumberOfInvocations)).performMagic(); + verify(mTransitionsMock, times(wantedNumberOfInvocations)).closeApp(); + verify(mTransitionsMock).cleanUpTracks(); + } + + private void emptyTask() { + + } + + @Test + public void canCaptureWindowManagerTrace() { + mTransitionBuilder + .run(this::emptyTask) + .includeJankyRuns() + .skipLayersTrace() + .withTag("mCaptureWmTraceTransitionRunner") + .build().run(); + InOrder orderVerifier = inOrder(mWindowManagerTraceMonitorMock); + orderVerifier.verify(mWindowManagerTraceMonitorMock).start(); + orderVerifier.verify(mWindowManagerTraceMonitorMock).stop(); + orderVerifier.verify(mWindowManagerTraceMonitorMock) + .save("mCaptureWmTraceTransitionRunner", 0); + verifyNoMoreInteractions(mWindowManagerTraceMonitorMock); + } + + @Test + public void canCaptureLayersTrace() { + mTransitionBuilder + .run(this::emptyTask) + .includeJankyRuns() + .skipWindowManagerTrace() + .withTag("mCaptureLayersTraceTransitionRunner") + .build().run(); + InOrder orderVerifier = inOrder(mLayersTraceMonitorMock); + orderVerifier.verify(mLayersTraceMonitorMock).start(); + orderVerifier.verify(mLayersTraceMonitorMock).stop(); + orderVerifier.verify(mLayersTraceMonitorMock) + .save("mCaptureLayersTraceTransitionRunner", 0); + verifyNoMoreInteractions(mLayersTraceMonitorMock); + } + + @Test + public void canRecordEachRun() throws IOException { + mTransitionBuilder + .run(this::emptyTask) + .withTag("mRecordEachRun") + .recordEachRun() + .includeJankyRuns() + .skipLayersTrace() + .skipWindowManagerTrace() + .repeat(2) + .build().run(); + InOrder orderVerifier = inOrder(mScreenRecorderMock); + orderVerifier.verify(mScreenRecorderMock).start(); + orderVerifier.verify(mScreenRecorderMock).stop(); + orderVerifier.verify(mScreenRecorderMock).save("mRecordEachRun", 0); + orderVerifier.verify(mScreenRecorderMock).start(); + orderVerifier.verify(mScreenRecorderMock).stop(); + orderVerifier.verify(mScreenRecorderMock).save("mRecordEachRun", 1); + verifyNoMoreInteractions(mScreenRecorderMock); + } + + @Test + public void canRecordAllRuns() throws IOException { + doReturn(Paths.get(Environment.getExternalStorageDirectory().getAbsolutePath(), + "mRecordAllRuns.mp4")).when(mScreenRecorderMock).save("mRecordAllRuns"); + mTransitionBuilder + .run(this::emptyTask) + .recordAllRuns() + .includeJankyRuns() + .skipLayersTrace() + .skipWindowManagerTrace() + .withTag("mRecordAllRuns") + .repeat(2) + .build().run(); + InOrder orderVerifier = inOrder(mScreenRecorderMock); + orderVerifier.verify(mScreenRecorderMock).start(); + orderVerifier.verify(mScreenRecorderMock).stop(); + orderVerifier.verify(mScreenRecorderMock).save("mRecordAllRuns"); + verifyNoMoreInteractions(mScreenRecorderMock); + } + + @Test + public void canSkipJankyRuns() { + doReturn(false).doReturn(true).doReturn(false) + .when(mWindowAnimationFrameStatsMonitor).jankyFramesDetected(); + List<TransitionResult> results = mTransitionBuilder + .run(this::emptyTask) + .skipLayersTrace() + .skipWindowManagerTrace() + .repeat(3) + .build().run().getResults(); + assertThat(results).hasSize(2); + } + + public static class SimpleUiTransitions { + public void turnOnDevice() { + } + + public void openApp() { + } + + public void performMagic() { + } + + public void closeApp() { + } + + public void cleanUpTracks() { + } + } +} diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/WindowManagerTraceTest.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/WindowManagerTraceTest.java new file mode 100644 index 000000000000..49278718932c --- /dev/null +++ b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/WindowManagerTraceTest.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2018 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.wm.flicker; + +import static com.android.server.wm.flicker.TestFileUtils.readTestFile; + +import static com.google.common.truth.Truth.assertThat; + +import com.android.server.wm.flicker.Assertions.Result; + +import org.junit.Before; +import org.junit.Test; + +/** + * Contains {@link WindowManagerTrace} tests. + * To run this test: {@code atest FlickerLibTest:WindowManagerTraceTest} + */ +public class WindowManagerTraceTest { + private WindowManagerTrace mTrace; + + private static WindowManagerTrace readWindowManagerTraceFromFile(String relativePath) { + try { + return WindowManagerTrace.parseFrom(readTestFile(relativePath)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Before + public void setup() { + mTrace = readWindowManagerTraceFromFile("wm_trace_openchrome.pb"); + } + + @Test + public void canParseAllEntries() { + assertThat(mTrace.getEntries().get(0).getTimestamp()).isEqualTo(241777211939236L); + assertThat(mTrace.getEntries().get(mTrace.getEntries().size() - 1).getTimestamp()).isEqualTo + (241779809471942L); + } + + @Test + public void canDetectAboveAppWindowVisibility() { + WindowManagerTrace.Entry entry = mTrace.getEntry(241777211939236L); + Result result = entry.isAboveAppWindowVisible("NavigationBar"); + assertThat(result.passed()).isTrue(); + } + + @Test + public void canDetectBelowAppWindowVisibility() { + WindowManagerTrace.Entry entry = mTrace.getEntry(241777211939236L); + Result result = entry.isBelowAppWindowVisible("wallpaper"); + assertThat(result.passed()).isTrue(); + } + + @Test + public void canDetectAppWindowVisibility() { + WindowManagerTrace.Entry entry = mTrace.getEntry(241777211939236L); + Result result = entry.isAppWindowVisible("com.google.android.apps.nexuslauncher"); + assertThat(result.passed()).isTrue(); + } + + @Test + public void canFailWithReasonForVisibilityChecks_windowNotFound() { + WindowManagerTrace.Entry entry = mTrace.getEntry(241777211939236L); + Result result = entry.isAboveAppWindowVisible("ImaginaryWindow"); + assertThat(result.failed()).isTrue(); + assertThat(result.reason).contains("ImaginaryWindow cannot be found"); + } + + @Test + public void canFailWithReasonForVisibilityChecks_windowNotVisible() { + WindowManagerTrace.Entry entry = mTrace.getEntry(241777211939236L); + Result result = entry.isAboveAppWindowVisible("AssistPreviewPanel"); + assertThat(result.failed()).isTrue(); + assertThat(result.reason).contains("AssistPreviewPanel is invisible"); + } + + @Test + public void canDetectAppZOrder() { + WindowManagerTrace.Entry entry = mTrace.getEntry(241778130296410L); + Result result = entry.isVisibleAppWindowOnTop("com.google.android.apps.chrome"); + assertThat(result.passed()).isTrue(); + } + + @Test + public void canFailWithReasonForZOrderChecks_windowNotOnTop() { + WindowManagerTrace.Entry entry = mTrace.getEntry(241778130296410L); + Result result = entry.isVisibleAppWindowOnTop("com.google.android.apps.nexuslauncher"); + assertThat(result.failed()).isTrue(); + assertThat(result.reason).contains("wanted=com.google.android.apps.nexuslauncher"); + assertThat(result.reason).contains("found=com.android.chrome/" + + "com.google.android.apps.chrome.Main"); + } +} diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/WmTraceSubjectTest.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/WmTraceSubjectTest.java new file mode 100644 index 000000000000..d547a188a663 --- /dev/null +++ b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/WmTraceSubjectTest.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2018 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.wm.flicker; + +import static com.android.server.wm.flicker.TestFileUtils.readTestFile; +import static com.android.server.wm.flicker.WmTraceSubject.assertThat; + +import org.junit.Test; + +/** + * Contains {@link WmTraceSubject} tests. + * To run this test: {@code atest FlickerLibTest:WmTraceSubjectTest} + */ +public class WmTraceSubjectTest { + private static WindowManagerTrace readWmTraceFromFile(String relativePath) { + try { + return WindowManagerTrace.parseFrom(readTestFile(relativePath)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Test + public void testCanTransitionInAppWindow() { + WindowManagerTrace trace = readWmTraceFromFile("wm_trace_openchrome2.pb"); + + assertThat(trace).showsAppWindowOnTop("com.google.android.apps.nexuslauncher/" + + ".NexusLauncherActivity").forRange(174684850717208L, 174685957511016L); + assertThat(trace).showsAppWindowOnTop( + "com.google.android.apps.nexuslauncher/.NexusLauncherActivity") + .then() + .showsAppWindowOnTop("com.android.chrome") + .forAllEntries(); + } +} diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/LayersTraceMonitorTest.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/LayersTraceMonitorTest.java new file mode 100644 index 000000000000..dbd6761a05b0 --- /dev/null +++ b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/LayersTraceMonitorTest.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2018 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.wm.flicker.monitor; + +import static android.surfaceflinger.nano.Layerstrace.LayersTraceFileProto.MAGIC_NUMBER_H; +import static android.surfaceflinger.nano.Layerstrace.LayersTraceFileProto.MAGIC_NUMBER_L; + +import static com.google.common.truth.Truth.assertThat; + +import android.surfaceflinger.nano.Layerstrace.LayersTraceFileProto; + +import com.google.common.io.Files; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; + +/** + * Contains {@link LayersTraceMonitor} tests. + * To run this test: {@code atest FlickerLibTest:LayersTraceMonitorTest} + */ +public class LayersTraceMonitorTest { + private LayersTraceMonitor mLayersTraceMonitor; + + @Before + public void setup() { + mLayersTraceMonitor = new LayersTraceMonitor(); + } + + @After + public void teardown() { + mLayersTraceMonitor.stop(); + mLayersTraceMonitor.getOutputTraceFilePath("captureLayersTrace").toFile().delete(); + } + + @Test + public void canStartLayersTrace() throws Exception { + mLayersTraceMonitor.start(); + assertThat(mLayersTraceMonitor.isEnabled()).isTrue(); + } + + @Test + public void canStopLayersTrace() throws Exception { + mLayersTraceMonitor.start(); + assertThat(mLayersTraceMonitor.isEnabled()).isTrue(); + mLayersTraceMonitor.stop(); + assertThat(mLayersTraceMonitor.isEnabled()).isFalse(); + } + + @Test + public void captureLayersTrace() throws Exception { + mLayersTraceMonitor.start(); + mLayersTraceMonitor.stop(); + File testFile = mLayersTraceMonitor.save("captureLayersTrace").toFile(); + assertThat(testFile.exists()).isTrue(); + byte[] trace = Files.toByteArray(testFile); + assertThat(trace.length).isGreaterThan(0); + LayersTraceFileProto mLayerTraceFileProto = LayersTraceFileProto.parseFrom(trace); + assertThat(mLayerTraceFileProto.magicNumber).isEqualTo( + (long) MAGIC_NUMBER_H << 32 | MAGIC_NUMBER_L); + } +} diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/ScreenRecorderTest.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/ScreenRecorderTest.java new file mode 100644 index 000000000000..e73eecc348f0 --- /dev/null +++ b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/ScreenRecorderTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2018 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.wm.flicker.monitor; + +import static android.os.SystemClock.sleep; + +import static com.android.server.wm.flicker.monitor.ScreenRecorder.DEFAULT_OUTPUT_PATH; +import static com.android.server.wm.flicker.monitor.ScreenRecorder.getPath; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +/** + * Contains {@link ScreenRecorder} tests. + * To run this test: {@code atest FlickerLibTest:ScreenRecorderTest} + */ +public class ScreenRecorderTest { + private static final String TEST_VIDEO_FILENAME = "test.mp4"; + private ScreenRecorder mScreenRecorder; + + @Before + public void setup() { + mScreenRecorder = new ScreenRecorder(); + } + + @After + public void teardown() { + DEFAULT_OUTPUT_PATH.toFile().delete(); + getPath(TEST_VIDEO_FILENAME).toFile().delete(); + } + + @Test + public void videoIsRecorded() { + mScreenRecorder.start(); + sleep(100); + mScreenRecorder.stop(); + File file = DEFAULT_OUTPUT_PATH.toFile(); + assertThat(file.exists()).isTrue(); + } + + @Test + public void videoCanBeSaved() { + mScreenRecorder.start(); + sleep(100); + mScreenRecorder.stop(); + mScreenRecorder.save(TEST_VIDEO_FILENAME); + File file = getPath(TEST_VIDEO_FILENAME).toFile(); + assertThat(file.exists()).isTrue(); + } +} diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/WindowAnimationFrameStatsMonitorTest.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/WindowAnimationFrameStatsMonitorTest.java new file mode 100644 index 000000000000..f7fa0d572de6 --- /dev/null +++ b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/WindowAnimationFrameStatsMonitorTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2018 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.wm.flicker.monitor; + +import static com.android.server.wm.flicker.AutomationUtils.wakeUpAndGoToHomeScreen; + +import static com.google.common.truth.Truth.assertThat; + +import android.support.test.InstrumentationRegistry; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +/** + * Contains {@link WindowAnimationFrameStatsMonitor} tests. + * To run this test: {@code atest FlickerLibTest:WindowAnimationFrameStatsMonitorTest} + */ +public class WindowAnimationFrameStatsMonitorTest { + private WindowAnimationFrameStatsMonitor mWindowAnimationFrameStatsMonitor; + + @Before + public void setup() { + mWindowAnimationFrameStatsMonitor = new WindowAnimationFrameStatsMonitor( + InstrumentationRegistry.getInstrumentation()); + wakeUpAndGoToHomeScreen(); + } + + // TODO(vishnun) + @Ignore("Disabled until app-helper libraries are available.") + @Test + public void captureWindowAnimationFrameStats() throws Exception { + mWindowAnimationFrameStatsMonitor.start(); + //AppHelperWrapper.getInstance().getHelper(CHROME).open(); + //AppHelperWrapper.getInstance().getHelper(CHROME).exit(); + mWindowAnimationFrameStatsMonitor.stop(); + } +} diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitorTest.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitorTest.java new file mode 100644 index 000000000000..56284d7d516a --- /dev/null +++ b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitorTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2018 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.wm.flicker.monitor; + +import static com.android.server.wm.nano.WindowManagerTraceFileProto.MAGIC_NUMBER_H; +import static com.android.server.wm.nano.WindowManagerTraceFileProto.MAGIC_NUMBER_L; + +import static com.google.common.truth.Truth.assertThat; + +import com.android.server.wm.nano.WindowManagerTraceFileProto; + +import com.google.common.io.Files; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; + +/** + * Contains {@link WindowManagerTraceMonitor} tests. + * To run this test: {@code atest FlickerLibTest:WindowManagerTraceMonitorTest} + */ +public class WindowManagerTraceMonitorTest { + private WindowManagerTraceMonitor mWindowManagerTraceMonitor; + + @Before + public void setup() { + mWindowManagerTraceMonitor = new WindowManagerTraceMonitor(); + } + + @After + public void teardown() { + mWindowManagerTraceMonitor.stop(); + mWindowManagerTraceMonitor.getOutputTraceFilePath("captureWindowTrace").toFile().delete(); + } + + @Test + public void canStartWindowTrace() throws Exception { + mWindowManagerTraceMonitor.start(); + assertThat(mWindowManagerTraceMonitor.isEnabled()).isTrue(); + } + + @Test + public void canStopWindowTrace() throws Exception { + mWindowManagerTraceMonitor.start(); + assertThat(mWindowManagerTraceMonitor.isEnabled()).isTrue(); + mWindowManagerTraceMonitor.stop(); + assertThat(mWindowManagerTraceMonitor.isEnabled()).isFalse(); + } + + @Test + public void captureWindowTrace() throws Exception { + mWindowManagerTraceMonitor.start(); + mWindowManagerTraceMonitor.stop(); + File testFile = mWindowManagerTraceMonitor.save("captureWindowTrace").toFile(); + assertThat(testFile.exists()).isTrue(); + byte[] trace = Files.toByteArray(testFile); + assertThat(trace.length).isGreaterThan(0); + WindowManagerTraceFileProto mWindowTraceFileProto = WindowManagerTraceFileProto.parseFrom( + trace); + assertThat(mWindowTraceFileProto.magicNumber).isEqualTo( + (long) MAGIC_NUMBER_H << 32 | MAGIC_NUMBER_L); + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ChangeAppRotationTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/ChangeAppRotationTest.java new file mode 100644 index 000000000000..34f4ebb89543 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ChangeAppRotationTest.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2018 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.wm.flicker; + +import static android.view.Surface.rotationToString; + +import static com.android.server.wm.flicker.CommonTransitions.changeAppRotation; +import static com.android.server.wm.flicker.WindowUtils.getAppPosition; +import static com.android.server.wm.flicker.WindowUtils.getNavigationBarPosition; +import static com.android.server.wm.flicker.WindowUtils.getStatusBarPosition; +import static com.android.server.wm.flicker.WmTraceSubject.assertThat; + +import android.graphics.Rect; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.LargeTest; +import android.util.Log; +import android.view.Surface; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Cycle through supported app rotations. + * To run this test: {@code atest FlickerTest:ChangeAppRotationTest} + */ +@RunWith(Parameterized.class) +@LargeTest +public class ChangeAppRotationTest extends FlickerTestBase { + private int beginRotation; + private int endRotation; + + public ChangeAppRotationTest(String beginRotationName, String endRotationName, + int beginRotation, int endRotation) { + this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(), + "com.android.server.wm.flicker.testapp", "SimpleApp"); + this.beginRotation = beginRotation; + this.endRotation = endRotation; + } + + @Parameters(name = "{0}-{1}") + public static Collection<Object[]> getParams() { + int[] supportedRotations = + {Surface.ROTATION_0, Surface.ROTATION_90, Surface.ROTATION_270}; + Collection<Object[]> params = new ArrayList<>(); + for (int begin : supportedRotations) { + for (int end : supportedRotations) { + if (begin != end) { + params.add(new Object[]{rotationToString(begin), rotationToString(end), begin, + end}); + } + } + } + return params; + } + + @Before + public void runTransition() { + super.runTransition( + changeAppRotation(testApp, uiDevice, beginRotation, endRotation).build()); + } + + @Test + public void checkVisibility_navBarWindowIsAlwaysVisible() { + checkResults(result -> assertThat(result) + .showsAboveAppWindow(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries()); + } + + @Test + public void checkVisibility_statusBarWindowIsAlwaysVisible() { + checkResults(result -> assertThat(result) + .showsAboveAppWindow(STATUS_BAR_WINDOW_TITLE).forAllEntries()); + } + + @Test + public void checkPosition_navBarLayerRotatesAndScales() { + Rect startingPos = getNavigationBarPosition(beginRotation); + Rect endingPos = getNavigationBarPosition(endRotation); + checkResults(result -> { + LayersTraceSubject.assertThat(result) + .hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, startingPos) + .inTheBeginning(); + LayersTraceSubject.assertThat(result) + .hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, endingPos).atTheEnd(); + } + ); + } + + @Test + public void checkPosition_appLayerRotates() { + Rect startingPos = getAppPosition(beginRotation); + Rect endingPos = getAppPosition(endRotation); + Log.e(TAG, "startingPos=" + startingPos + " endingPos=" + endingPos); + checkResults(result -> { + LayersTraceSubject.assertThat(result) + .hasVisibleRegion(testApp.getPackage(), startingPos).inTheBeginning(); + LayersTraceSubject.assertThat(result) + .hasVisibleRegion(testApp.getPackage(), endingPos).atTheEnd(); + } + ); + } + + @Test + public void checkPosition_statusBarLayerScales() { + Rect startingPos = getStatusBarPosition(beginRotation); + Rect endingPos = getStatusBarPosition(endRotation); + checkResults(result -> { + LayersTraceSubject.assertThat(result) + .hasVisibleRegion(STATUS_BAR_WINDOW_TITLE, startingPos) + .inTheBeginning(); + LayersTraceSubject.assertThat(result) + .hasVisibleRegion(STATUS_BAR_WINDOW_TITLE, endingPos).atTheEnd(); + } + ); + } + + @Test + public void checkVisibility_navBarLayerIsAlwaysVisible() { + checkResults(result -> LayersTraceSubject.assertThat(result) + .showsLayer(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries()); + } + + @Test + public void checkVisibility_statusBarLayerIsAlwaysVisible() { + checkResults(result -> LayersTraceSubject.assertThat(result) + .showsLayer(STATUS_BAR_WINDOW_TITLE).forAllEntries()); + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToAppTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToAppTest.java new file mode 100644 index 000000000000..2b62fcf196fb --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToAppTest.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2018 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.wm.flicker; + +import static com.android.server.wm.flicker.CommonTransitions.editTextLoseFocusToApp; +import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds; + +import android.platform.helpers.IAppHelper; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.LargeTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test IME window closing back to app window transitions. + * To run this test: {@code atest FlickerTests:CloseImeWindowToAppTest} + */ +@LargeTest +@RunWith(AndroidJUnit4.class) +public class CloseImeWindowToAppTest extends FlickerTestBase { + + private static final String IME_WINDOW_TITLE = "InputMethod"; + private IAppHelper mImeTestApp = new StandardAppHelper( + InstrumentationRegistry.getInstrumentation(), + "com.android.server.wm.flicker.testapp", "ImeApp"); + + @Before + public void runTransition() { + super.runTransition(editTextLoseFocusToApp(uiDevice) + .includeJankyRuns().build()); + } + + @Test + public void checkVisibility_imeLayerBecomesInvisible() { + checkResults(result -> LayersTraceSubject.assertThat(result) + .showsLayer(IME_WINDOW_TITLE) + .then() + .hidesLayer(IME_WINDOW_TITLE) + .forAllEntries()); + } + + @Test + public void checkVisibility_imeAppLayerIsAlwaysVisible() { + checkResults(result -> LayersTraceSubject.assertThat(result) + .showsLayer(mImeTestApp.getPackage()) + .forAllEntries()); + } + + @Test + public void checkVisibility_imeAppWindowIsAlwaysVisible() { + checkResults(result -> WmTraceSubject.assertThat(result) + .showsAppWindowOnTop(mImeTestApp.getPackage()) + .forAllEntries()); + } + + @Test + public void checkCoveredRegion_noUncoveredRegions() { + checkResults(result -> LayersTraceSubject.assertThat(result).coversRegion( + getDisplayBounds()).forAllEntries()); + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToHomeTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToHomeTest.java new file mode 100644 index 000000000000..42b161f8a604 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToHomeTest.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2018 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.wm.flicker; + +import static com.android.server.wm.flicker.CommonTransitions.editTextLoseFocusToHome; +import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds; + +import android.platform.helpers.IAppHelper; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.LargeTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test IME window closing to home transitions. + * To run this test: {@code atest FlickerTests:CloseImeWindowToHomeTest} + */ +@LargeTest +@RunWith(AndroidJUnit4.class) +public class CloseImeWindowToHomeTest extends FlickerTestBase { + + private static final String IME_WINDOW_TITLE = "InputMethod"; + private IAppHelper mImeTestApp = new StandardAppHelper( + InstrumentationRegistry.getInstrumentation(), + "com.android.server.wm.flicker.testapp", "ImeApp"); + + @Before + public void runTransition() { + super.runTransition(editTextLoseFocusToHome(uiDevice) + .includeJankyRuns().build()); + } + + @Test + public void checkVisibility_imeWindowBecomesInvisible() { + checkResults(result -> WmTraceSubject.assertThat(result) + .showsImeWindow(IME_WINDOW_TITLE) + .then() + .hidesImeWindow(IME_WINDOW_TITLE) + .forAllEntries()); + } + + @Test + public void checkVisibility_imeLayerBecomesInvisible() { + checkResults(result -> LayersTraceSubject.assertThat(result) + .showsLayer(IME_WINDOW_TITLE) + .then() + .hidesLayer(IME_WINDOW_TITLE) + .forAllEntries()); + } + + @Test + public void checkVisibility_imeAppLayerBecomesInvisible() { + checkResults(result -> LayersTraceSubject.assertThat(result) + .showsLayer(mImeTestApp.getPackage()) + .then() + .hidesLayer(mImeTestApp.getPackage()) + .forAllEntries()); + } + + @Test + public void checkVisibility_imeAppWindowBecomesInvisible() { + checkResults(result -> WmTraceSubject.assertThat(result) + .showsAppWindowOnTop(mImeTestApp.getPackage()) + .then() + .hidesAppWindowOnTop(mImeTestApp.getPackage()) + .forAllEntries()); + } + + @Test + public void checkCoveredRegion_noUncoveredRegions() { + checkResults(result -> LayersTraceSubject.assertThat(result).coversRegion( + getDisplayBounds()).forAllEntries()); + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonTransitions.java b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonTransitions.java new file mode 100644 index 000000000000..92bb1ea0b58f --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonTransitions.java @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2018 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.wm.flicker; + +import static android.os.SystemClock.sleep; +import static android.view.Surface.rotationToString; + +import static com.android.server.wm.flicker.AutomationUtils.clearRecents; +import static com.android.server.wm.flicker.AutomationUtils.closePipWindow; +import static com.android.server.wm.flicker.AutomationUtils.exitSplitScreen; +import static com.android.server.wm.flicker.AutomationUtils.expandPipWindow; +import static com.android.server.wm.flicker.AutomationUtils.launchSplitScreen; +import static com.android.server.wm.flicker.AutomationUtils.stopPackage; + +import android.content.Context; +import android.content.Intent; +import android.os.RemoteException; +import android.platform.helpers.IAppHelper; +import android.support.test.InstrumentationRegistry; +import android.support.test.uiautomator.By; +import android.support.test.uiautomator.UiDevice; +import android.support.test.uiautomator.UiObject2; +import android.support.test.uiautomator.Until; +import android.util.Rational; +import android.view.Surface; + +import com.android.server.wm.flicker.TransitionRunner.TransitionBuilder; + +/** + * Collection of common transitions which can be used to test different apps or scenarios. + */ +class CommonTransitions { + + public static final int ITERATIONS = 1; + private static final String TAG = "FLICKER"; + private static final long APP_LAUNCH_TIMEOUT = 10000; + + private static void setRotation(UiDevice device, int rotation) { + try { + switch (rotation) { + case Surface.ROTATION_270: + device.setOrientationLeft(); + break; + + case Surface.ROTATION_90: + device.setOrientationRight(); + break; + + case Surface.ROTATION_0: + default: + device.setOrientationNatural(); + } + // Wait for animation to complete + sleep(3000); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + private static void clickEditTextWidget(UiDevice device, IAppHelper testApp) { + UiObject2 editText = device.findObject(By.res(testApp.getPackage(), "plain_text_input")); + editText.click(); + sleep(500); + } + + private static void clickEnterPipButton(UiDevice device, IAppHelper testApp) { + UiObject2 enterPipButton = device.findObject(By.res(testApp.getPackage(), "enter_pip")); + enterPipButton.click(); + sleep(500); + } + + static TransitionBuilder openAppWarm(IAppHelper testApp, UiDevice + device) { + return TransitionRunner.newBuilder() + .withTag("OpenAppWarm_" + testApp.getLauncherName()) + .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen) + .runBeforeAll(testApp::open) + .runBefore(device::pressHome) + .runBefore(device::waitForIdle) + .run(testApp::open) + .runAfterAll(testApp::exit) + .runAfterAll(AutomationUtils::setDefaultWait) + .repeat(ITERATIONS); + } + + static TransitionBuilder closeAppWithBackKey(IAppHelper testApp, UiDevice + device) { + return TransitionRunner.newBuilder() + .withTag("closeAppWithBackKey_" + testApp.getLauncherName()) + .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen) + .runBefore(testApp::open) + .runBefore(device::waitForIdle) + .run(device::pressBack) + .run(device::waitForIdle) + .runAfterAll(testApp::exit) + .runAfterAll(AutomationUtils::setDefaultWait) + .repeat(ITERATIONS); + } + + static TransitionBuilder closeAppWithHomeKey(IAppHelper testApp, UiDevice + device) { + return TransitionRunner.newBuilder() + .withTag("closeAppWithHomeKey_" + testApp.getLauncherName()) + .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen) + .runBefore(testApp::open) + .runBefore(device::waitForIdle) + .run(device::pressHome) + .run(device::waitForIdle) + .runAfterAll(testApp::exit) + .runAfterAll(AutomationUtils::setDefaultWait) + .repeat(ITERATIONS); + } + + static TransitionBuilder getOpenAppCold(IAppHelper testApp, + UiDevice device) { + return TransitionRunner.newBuilder() + .withTag("OpenAppCold_" + testApp.getLauncherName()) + .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen) + .runBefore(device::pressHome) + .runBefore(testApp::exit) + .runBefore(device::waitForIdle) + .run(testApp::open) + .runAfterAll(testApp::exit) + .repeat(ITERATIONS); + } + + static TransitionBuilder changeAppRotation(IAppHelper testApp, UiDevice + device, int beginRotation, int endRotation) { + return TransitionRunner.newBuilder() + .withTag("changeAppRotation_" + testApp.getLauncherName() + + rotationToString(beginRotation) + "_" + + rotationToString(endRotation)) + .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen) + .runBeforeAll(testApp::open) + .runBefore(() -> setRotation(device, beginRotation)) + .run(() -> setRotation(device, endRotation)) + .runAfterAll(testApp::exit) + .runAfterAll(() -> setRotation(device, Surface.ROTATION_0)) + .repeat(ITERATIONS); + } + + static TransitionBuilder changeAppRotation(Intent intent, String intentId, Context context, + UiDevice + device, int beginRotation, int endRotation) { + final String testTag = "changeAppRotation_" + intentId + "_" + + rotationToString(beginRotation) + "_" + rotationToString(endRotation); + return TransitionRunner.newBuilder() + .withTag(testTag) + .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen) + .runBeforeAll(() -> { + context.startActivity(intent); + device.wait(Until.hasObject(By.pkg(intent.getComponent() + .getPackageName()).depth(0)), APP_LAUNCH_TIMEOUT); + } + ) + .runBefore(() -> setRotation(device, beginRotation)) + .run(() -> setRotation(device, endRotation)) + .runAfterAll(() -> stopPackage(context, intent.getComponent().getPackageName())) + .runAfterAll(() -> setRotation(device, Surface.ROTATION_0)) + .repeat(ITERATIONS); + } + + static TransitionBuilder appToSplitScreen(IAppHelper testApp, UiDevice device) { + return TransitionRunner.newBuilder() + .withTag("appToSplitScreen_" + testApp.getLauncherName()) + .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen) + .runBefore(testApp::open) + .runBefore(device::waitForIdle) + .runBefore(() -> sleep(500)) + .run(() -> launchSplitScreen(device)) + .runAfter(() -> exitSplitScreen(device)) + .runAfterAll(testApp::exit) + .repeat(ITERATIONS); + } + + static TransitionBuilder splitScreenToLauncher(IAppHelper testApp, UiDevice device) { + return TransitionRunner.newBuilder() + .withTag("splitScreenToLauncher_" + testApp.getLauncherName()) + .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen) + .runBefore(testApp::open) + .runBefore(device::waitForIdle) + .runBefore(() -> launchSplitScreen(device)) + .run(() -> exitSplitScreen(device)) + .runAfterAll(testApp::exit) + .repeat(ITERATIONS); + } + + static TransitionBuilder editTextSetFocus(UiDevice device) { + IAppHelper testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(), + "com.android.server.wm.flicker.testapp", "ImeApp"); + return TransitionRunner.newBuilder() + .withTag("editTextSetFocus_" + testApp.getLauncherName()) + .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen) + .runBefore(device::pressHome) + .runBefore(testApp::open) + .run(() -> clickEditTextWidget(device, testApp)) + .runAfterAll(testApp::exit) + .repeat(ITERATIONS); + } + + static TransitionBuilder resizeSplitScreen(IAppHelper testAppTop, IAppHelper testAppBottom, + UiDevice device, Rational startRatio, Rational stopRatio) { + String testTag = "resizeSplitScreen_" + testAppTop.getLauncherName() + "_" + + testAppBottom.getLauncherName() + "_" + + startRatio.toString().replace("/", ":") + "_to_" + + stopRatio.toString().replace("/", ":"); + return TransitionRunner.newBuilder() + .withTag(testTag) + .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen) + .runBeforeAll(() -> clearRecents(device)) + .runBefore(testAppBottom::open) + .runBefore(device::pressHome) + .runBefore(testAppTop::open) + .runBefore(device::waitForIdle) + .runBefore(() -> launchSplitScreen(device)) + .runBefore(() -> { + UiObject2 snapshot = device.findObject( + By.res("com.google.android.apps.nexuslauncher", "snapshot")); + snapshot.click(); + }) + .runBefore(() -> AutomationUtils.resizeSplitScreen(device, startRatio)) + .run(() -> AutomationUtils.resizeSplitScreen(device, stopRatio)) + .runAfter(() -> exitSplitScreen(device)) + .runAfter(device::pressHome) + .runAfterAll(testAppTop::exit) + .runAfterAll(testAppBottom::exit) + .repeat(ITERATIONS); + } + + static TransitionBuilder editTextLoseFocusToHome(UiDevice device) { + IAppHelper testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(), + "com.android.server.wm.flicker.testapp", "ImeApp"); + return TransitionRunner.newBuilder() + .withTag("editTextLoseFocusToHome_" + testApp.getLauncherName()) + .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen) + .runBefore(device::pressHome) + .runBefore(testApp::open) + .runBefore(() -> clickEditTextWidget(device, testApp)) + .run(device::pressHome) + .run(device::waitForIdle) + .runAfterAll(testApp::exit) + .repeat(ITERATIONS); + } + + static TransitionBuilder editTextLoseFocusToApp(UiDevice device) { + IAppHelper testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(), + "com.android.server.wm.flicker.testapp", "ImeApp"); + return TransitionRunner.newBuilder() + .withTag("editTextLoseFocusToApp_" + testApp.getLauncherName()) + .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen) + .runBefore(device::pressHome) + .runBefore(testApp::open) + .runBefore(() -> clickEditTextWidget(device, testApp)) + .run(device::pressBack) + .run(device::waitForIdle) + .runAfterAll(testApp::exit) + .repeat(ITERATIONS); + } + + static TransitionBuilder enterPipMode(UiDevice device) { + IAppHelper testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(), + "com.android.server.wm.flicker.testapp", "PipApp"); + return TransitionRunner.newBuilder() + .withTag("enterPipMode_" + testApp.getLauncherName()) + .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen) + .runBefore(device::pressHome) + .runBefore(testApp::open) + .run(() -> clickEnterPipButton(device, testApp)) + .runAfter(() -> closePipWindow(device)) + .runAfterAll(testApp::exit) + .repeat(ITERATIONS); + } + + static TransitionBuilder exitPipModeToHome(UiDevice device) { + IAppHelper testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(), + "com.android.server.wm.flicker.testapp", "PipApp"); + return TransitionRunner.newBuilder() + .withTag("exitPipModeToHome_" + testApp.getLauncherName()) + .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen) + .runBefore(device::pressHome) + .runBefore(testApp::open) + .runBefore(() -> clickEnterPipButton(device, testApp)) + .run(() -> closePipWindow(device)) + .run(device::waitForIdle) + .runAfterAll(testApp::exit) + .repeat(ITERATIONS); + } + + static TransitionBuilder exitPipModeToApp(UiDevice device) { + IAppHelper testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(), + "com.android.server.wm.flicker.testapp", "PipApp"); + return TransitionRunner.newBuilder() + .withTag("exitPipModeToApp_" + testApp.getLauncherName()) + .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen) + .runBefore(device::pressHome) + .runBefore(testApp::open) + .runBefore(() -> clickEnterPipButton(device, testApp)) + .run(() -> expandPipWindow(device)) + .run(device::waitForIdle) + .runAfterAll(testApp::exit) + .repeat(ITERATIONS); + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/DebugTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/DebugTest.java new file mode 100644 index 000000000000..fec248c13818 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/DebugTest.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2018 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.wm.flicker; + +import android.platform.helpers.IAppHelper; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; +import android.support.test.uiautomator.UiDevice; +import android.util.Rational; +import android.view.Surface; + +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests to help debug individual transitions, capture video recordings and create test cases. + */ +@Ignore("Used for debugging transitions used in FlickerTests.") +@RunWith(AndroidJUnit4.class) +public class DebugTest { + private IAppHelper testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(), + "com.android.server.wm.flicker.testapp", "SimpleApp"); + private UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); + + /** + * atest FlickerTest:DebugTests#openAppCold + */ + @Test + public void openAppCold() { + CommonTransitions.getOpenAppCold(testApp, uiDevice).recordAllRuns().build().run(); + } + + /** + * atest FlickerTest:DebugTests#openAppWarm + */ + @Test + public void openAppWarm() { + CommonTransitions.openAppWarm(testApp, uiDevice).recordAllRuns().build().run(); + } + + /** + * atest FlickerTest:DebugTests#changeOrientationFromNaturalToLeft + */ + @Test + public void changeOrientationFromNaturalToLeft() { + CommonTransitions.changeAppRotation(testApp, uiDevice, Surface.ROTATION_0, + Surface.ROTATION_270).recordAllRuns().build().run(); + } + + /** + * atest FlickerTest:DebugTests#closeAppWithBackKey + */ + @Test + public void closeAppWithBackKey() { + CommonTransitions.closeAppWithBackKey(testApp, uiDevice).recordAllRuns().build().run(); + } + + /** + * atest FlickerTest:DebugTests#closeAppWithHomeKey + */ + @Test + public void closeAppWithHomeKey() { + CommonTransitions.closeAppWithHomeKey(testApp, uiDevice).recordAllRuns().build().run(); + } + + /** + * atest FlickerTest:DebugTests#openAppToSplitScreen + */ + @Test + public void openAppToSplitScreen() { + CommonTransitions.appToSplitScreen(testApp, uiDevice).includeJankyRuns().recordAllRuns() + .build().run(); + } + + /** + * atest FlickerTest:DebugTests#splitScreenToLauncher + */ + @Test + public void splitScreenToLauncher() { + CommonTransitions.splitScreenToLauncher(testApp, + uiDevice).includeJankyRuns().recordAllRuns() + .build().run(); + } + + /** + * atest FlickerTest:DebugTests#resizeSplitScreen + */ + @Test + public void resizeSplitScreen() { + IAppHelper bottomApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(), + "com.android.server.wm.flicker.testapp", "ImeApp"); + CommonTransitions.resizeSplitScreen(testApp, bottomApp, uiDevice, new Rational(1, 3), + new Rational(2, 3)).includeJankyRuns().recordEachRun().build().run(); + } + + // IME tests + + /** + * atest FlickerTest:DebugTests#editTextSetFocus + */ + @Test + public void editTextSetFocus() { + CommonTransitions.editTextSetFocus(uiDevice).includeJankyRuns().recordEachRun() + .build().run(); + } + + /** + * atest FlickerTest:DebugTests#editTextLoseFocusToHome + */ + @Test + public void editTextLoseFocusToHome() { + CommonTransitions.editTextLoseFocusToHome(uiDevice).includeJankyRuns().recordEachRun() + .build().run(); + } + + /** + * atest FlickerTest:DebugTests#editTextLoseFocusToApp + */ + @Test + public void editTextLoseFocusToApp() { + CommonTransitions.editTextLoseFocusToHome(uiDevice).includeJankyRuns().recordEachRun() + .build().run(); + } + + // PIP tests + + /** + * atest FlickerTest:DebugTests#enterPipMode + */ + @Test + public void enterPipMode() { + CommonTransitions.enterPipMode(uiDevice).includeJankyRuns().recordEachRun().build().run(); + } + + /** + * atest FlickerTest:DebugTests#exitPipModeToHome + */ + @Test + public void exitPipModeToHome() { + CommonTransitions.exitPipModeToHome(uiDevice).includeJankyRuns().recordEachRun() + .build().run(); + } + + /** + * atest FlickerTest:DebugTests#exitPipModeToApp + */ + @Test + public void exitPipModeToApp() { + CommonTransitions.exitPipModeToApp(uiDevice).includeJankyRuns().recordEachRun() + .build().run(); + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/FlickerTestBase.java b/tests/FlickerTests/src/com/android/server/wm/flicker/FlickerTestBase.java new file mode 100644 index 000000000000..7061b23c069d --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/FlickerTestBase.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2018 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.wm.flicker; + +import static com.android.server.wm.flicker.AutomationUtils.setDefaultWait; + +import static com.google.common.truth.Truth.assertWithMessage; + +import android.platform.helpers.IAppHelper; +import android.support.test.InstrumentationRegistry; +import android.support.test.uiautomator.UiDevice; +import android.util.Log; + +import com.android.server.wm.flicker.TransitionRunner.TransitionResult; + +import org.junit.After; +import org.junit.AfterClass; + +import java.util.HashMap; +import java.util.List; +import java.util.function.Consumer; + +/** + * Base class of all Flicker test that performs common functions for all flicker tests: + * <p> + * - Caches transitions so that a transition is run once and the transition results are used by + * tests multiple times. This is needed for parameterized tests which call the BeforeClass methods + * multiple times. + * - Keeps track of all test artifacts and deletes ones which do not need to be reviewed. + * - Fails tests if results are not available for any test due to jank. + */ +public class FlickerTestBase { + public static final String TAG = "FLICKER"; + static final String NAVIGATION_BAR_WINDOW_TITLE = "NavigationBar"; + static final String STATUS_BAR_WINDOW_TITLE = "StatusBar"; + static final String DOCKED_STACK_DIVIDER = "DockedStackDivider"; + private static HashMap<String, List<TransitionResult>> transitionResults = + new HashMap<>(); + IAppHelper testApp; + UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); + private List<TransitionResult> results; + private TransitionResult lastResult = null; + + /** + * Teardown any system settings and clean up test artifacts from the file system. + * + * Note: test artifacts for failed tests will remain on the device. + */ + @AfterClass + public static void teardown() { + setDefaultWait(); + transitionResults.values().stream() + .flatMap(List::stream) + .forEach(result -> { + if (result.canDelete()) { + result.delete(); + } else { + if (result.layersTraceExists()) { + Log.e(TAG, "Layers trace saved to " + result.getLayersTracePath()); + } + if (result.windowManagerTraceExists()) { + Log.e(TAG, "WindowManager trace saved to " + result + .getWindowManagerTracePath + ()); + } + if (result.screenCaptureVideoExists()) { + Log.e(TAG, "Screen capture video saved to " + result + .screenCaptureVideo.toString()); + } + } + }); + } + + /** + * Runs a transition, returns a cached result if the transition has run before. + */ + void runTransition(TransitionRunner transition) { + if (transitionResults.containsKey(transition.getTestTag())) { + results = transitionResults.get(transition.getTestTag()); + return; + } + results = transition.run().getResults(); + /* Fail if we don't have any results due to jank */ + assertWithMessage("No results to test because all transition runs were invalid because " + + "of Jank").that(results).isNotEmpty(); + transitionResults.put(transition.getTestTag(), results); + } + + /** + * Goes through a list of transition results and checks assertions on each result. + */ + void checkResults(Consumer<TransitionResult> assertion) { + + for (TransitionResult result : results) { + lastResult = result; + assertion.accept(result); + } + lastResult = null; + } + + /** + * Kludge to mark a file for saving. If {@code checkResults} fails, the last result is not + * cleared. This indicates the assertion failed for the result, so mark it for saving. + */ + @After + public void markArtifactsForSaving() { + if (lastResult != null) { + lastResult.flagForSaving(); + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppColdTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppColdTest.java new file mode 100644 index 000000000000..7e713699c72e --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppColdTest.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2018 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.wm.flicker; + +import static com.android.server.wm.flicker.CommonTransitions.getOpenAppCold; +import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds; +import static com.android.server.wm.flicker.WmTraceSubject.assertThat; + +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.LargeTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test cold launch app from launcher. + * To run this test: {@code atest FlickerTests:OpenAppColdTest} + */ +@LargeTest +@RunWith(AndroidJUnit4.class) +public class OpenAppColdTest extends FlickerTestBase { + + public OpenAppColdTest() { + this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(), + "com.android.server.wm.flicker.testapp", "SimpleApp"); + } + + @Before + public void runTransition() { + super.runTransition(getOpenAppCold(testApp, uiDevice).build()); + } + + @Test + public void checkVisibility_navBarWindowIsAlwaysVisible() { + checkResults(result -> assertThat(result) + .showsAboveAppWindow(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries()); + } + + @Test + public void checkVisibility_statusBarWindowIsAlwaysVisible() { + checkResults(result -> assertThat(result) + .showsAboveAppWindow(STATUS_BAR_WINDOW_TITLE).forAllEntries()); + } + + @Test + public void checkVisibility_wallpaperWindowBecomesInvisible() { + checkResults(result -> assertThat(result) + .showsBelowAppWindow("wallpaper") + .then() + .hidesBelowAppWindow("wallpaper") + .forAllEntries()); + } + + @Test + public void checkZOrder_appWindowReplacesLauncherAsTopWindow() { + checkResults(result -> assertThat(result) + .showsAppWindowOnTop( + "com.google.android.apps.nexuslauncher/.NexusLauncherActivity") + .then() + .showsAppWindowOnTop(testApp.getPackage()) + .forAllEntries()); + } + + @Test + public void checkCoveredRegion_noUncoveredRegions() { + checkResults(result -> LayersTraceSubject.assertThat(result).coversRegion( + getDisplayBounds()).forAllEntries()); + } + + @Test + public void checkVisibility_navBarLayerIsAlwaysVisible() { + checkResults(result -> LayersTraceSubject.assertThat(result) + .showsLayer(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries()); + } + + @Test + public void checkVisibility_statusBarLayerIsAlwaysVisible() { + checkResults(result -> LayersTraceSubject.assertThat(result) + .showsLayer(STATUS_BAR_WINDOW_TITLE).forAllEntries()); + } + + @Test + public void checkVisibility_wallpaperLayerBecomesInvisible() { + checkResults(result -> LayersTraceSubject.assertThat(result) + .showsLayer("wallpaper") + .then() + .hidesLayer("wallpaper") + .forAllEntries()); + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppToSplitScreenTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppToSplitScreenTest.java new file mode 100644 index 000000000000..745569aac7ab --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppToSplitScreenTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2018 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.wm.flicker; + +import static com.android.server.wm.flicker.CommonTransitions.appToSplitScreen; +import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds; + +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.LargeTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test open app to split screen. + * To run this test: {@code atest FlickerTests:OpenAppToSplitScreenTest} + */ +@LargeTest +@RunWith(AndroidJUnit4.class) +public class OpenAppToSplitScreenTest extends FlickerTestBase { + + public OpenAppToSplitScreenTest() { + this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(), + "com.android.server.wm.flicker.testapp", "SimpleApp"); + } + + @Before + public void runTransition() { + super.runTransition(appToSplitScreen(testApp, uiDevice).includeJankyRuns().build()); + } + + @Test + public void checkVisibility_navBarWindowIsAlwaysVisible() { + checkResults(result -> WmTraceSubject.assertThat(result) + .showsAboveAppWindow(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries()); + } + + @Test + public void checkVisibility_statusBarWindowIsAlwaysVisible() { + checkResults(result -> WmTraceSubject.assertThat(result) + .showsAboveAppWindow(STATUS_BAR_WINDOW_TITLE).forAllEntries()); + } + + @Test + public void checkVisibility_dividerWindowBecomesVisible() { + checkResults(result -> WmTraceSubject.assertThat(result) + .hidesAboveAppWindow(DOCKED_STACK_DIVIDER) + .then() + .showsAboveAppWindow(DOCKED_STACK_DIVIDER) + .forAllEntries()); + } + + @Test + public void checkCoveredRegion_noUncoveredRegions() { + checkResults(result -> + LayersTraceSubject.assertThat(result) + .coversRegion(getDisplayBounds()).forAllEntries()); + } + + @Test + public void checkVisibility_navBarLayerIsAlwaysVisible() { + checkResults(result -> LayersTraceSubject.assertThat(result) + .showsLayer(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries()); + } + + @Test + public void checkVisibility_statusBarLayerIsAlwaysVisible() { + checkResults(result -> LayersTraceSubject.assertThat(result) + .showsLayer(STATUS_BAR_WINDOW_TITLE).forAllEntries()); + } + + @Test + public void checkVisibility_dividerLayerBecomesVisible() { + checkResults(result -> LayersTraceSubject.assertThat(result) + .hidesLayer(DOCKED_STACK_DIVIDER) + .then() + .showsLayer(DOCKED_STACK_DIVIDER) + .forAllEntries()); + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppWarmTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppWarmTest.java new file mode 100644 index 000000000000..de7639d43d3c --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppWarmTest.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2018 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.wm.flicker; + +import static com.android.server.wm.flicker.CommonTransitions.openAppWarm; +import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds; +import static com.android.server.wm.flicker.WmTraceSubject.assertThat; + +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.LargeTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test warm launch app. + * To run this test: {@code atest FlickerTests:OpenAppWarmTest} + */ +@LargeTest +@RunWith(AndroidJUnit4.class) +public class OpenAppWarmTest extends FlickerTestBase { + + public OpenAppWarmTest() { + this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(), + "com.android.server.wm.flicker.testapp", "SimpleApp"); + } + + @Before + public void runTransition() { + super.runTransition(openAppWarm(testApp, uiDevice).build()); + } + + @Test + public void checkVisibility_navBarIsAlwaysVisible() { + checkResults(result -> assertThat(result) + .showsAboveAppWindow(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries()); + } + + @Test + public void checkVisibility_statusBarIsAlwaysVisible() { + checkResults(result -> assertThat(result) + .showsAboveAppWindow(STATUS_BAR_WINDOW_TITLE).forAllEntries()); + } + + @Test + public void checkVisibility_wallpaperBecomesInvisible() { + checkResults(result -> assertThat(result) + .showsBelowAppWindow("wallpaper") + .then() + .hidesBelowAppWindow("wallpaper") + .forAllEntries()); + } + + @Test + public void checkZOrder_appWindowReplacesLauncherAsTopWindow() { + checkResults(result -> assertThat(result) + .showsAppWindowOnTop( + "com.google.android.apps.nexuslauncher/.NexusLauncherActivity") + .then() + .showsAppWindowOnTop(testApp.getPackage()) + .forAllEntries()); + } + + @Test + public void checkCoveredRegion_noUncoveredRegions() { + checkResults(result -> LayersTraceSubject.assertThat(result).coversRegion( + getDisplayBounds()).forAllEntries()); + } + + @Test + public void checkVisibility_navBarLayerIsAlwaysVisible() { + checkResults(result -> LayersTraceSubject.assertThat(result) + .showsLayer(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries()); + } + + @Test + public void checkVisibility_statusBarLayerIsAlwaysVisible() { + checkResults(result -> LayersTraceSubject.assertThat(result) + .showsLayer(STATUS_BAR_WINDOW_TITLE).forAllEntries()); + } + + @Test + public void checkVisibility_wallpaperLayerBecomesInvisible() { + checkResults(result -> LayersTraceSubject.assertThat(result) + .showsLayer("wallpaper") + .then() + .hidesLayer("wallpaper") + .forAllEntries()); + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenImeWindowTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenImeWindowTest.java new file mode 100644 index 000000000000..1bd519c8334e --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenImeWindowTest.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2018 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.wm.flicker; + +import static com.android.server.wm.flicker.CommonTransitions.editTextSetFocus; +import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds; + +import android.support.test.filters.LargeTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test IME window opening transitions. + * To run this test: {@code atest FlickerTests:OpenImeWindowTest} + */ +@LargeTest +@RunWith(AndroidJUnit4.class) +public class OpenImeWindowTest extends FlickerTestBase { + + private static final String IME_WINDOW_TITLE = "InputMethod"; + + @Before + public void runTransition() { + super.runTransition(editTextSetFocus(uiDevice) + .includeJankyRuns().build()); + } + + @Test + public void checkVisibility_imeWindowBecomesVisible() { + checkResults(result -> WmTraceSubject.assertThat(result) + .hidesImeWindow(IME_WINDOW_TITLE) + .then() + .showsImeWindow(IME_WINDOW_TITLE) + .forAllEntries()); + } + + @Test + public void checkVisibility_imeLayerBecomesVisible() { + checkResults(result -> LayersTraceSubject.assertThat(result) + .hidesLayer(IME_WINDOW_TITLE) + .then() + .showsLayer(IME_WINDOW_TITLE) + .forAllEntries()); + } + + @Test + public void checkCoveredRegion_noUncoveredRegions() { + checkResults(result -> LayersTraceSubject.assertThat(result).coversRegion( + getDisplayBounds()).forAllEntries()); + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ResizeSplitScreenTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/ResizeSplitScreenTest.java new file mode 100644 index 000000000000..8a15cbdb7709 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ResizeSplitScreenTest.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2018 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.wm.flicker; + +import static com.android.server.wm.flicker.CommonTransitions.resizeSplitScreen; +import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds; +import static com.android.server.wm.flicker.WindowUtils.getDockedStackDividerInset; +import static com.android.server.wm.flicker.WindowUtils.getNavigationBarHeight; + +import static com.google.common.truth.Truth.assertThat; + +import android.graphics.Rect; +import android.platform.helpers.IAppHelper; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.LargeTest; +import android.support.test.runner.AndroidJUnit4; +import android.util.Rational; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test split screen resizing window transitions. + * To run this test: {@code atest FlickerTests:ResizeSplitScreenTest} + */ +@LargeTest +@RunWith(AndroidJUnit4.class) +public class ResizeSplitScreenTest extends FlickerTestBase { + + public ResizeSplitScreenTest() { + this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(), + "com.android.server.wm.flicker.testapp", "SimpleApp"); + } + + @Before + public void runTransition() { + IAppHelper bottomApp = new StandardAppHelper(InstrumentationRegistry + .getInstrumentation(), + "com.android.server.wm.flicker.testapp", "ImeApp"); + super.runTransition(resizeSplitScreen(testApp, bottomApp, uiDevice, new Rational(1, 3), + new Rational(2, 3)).includeJankyRuns().build()); + } + + @Test + public void checkVisibility_navBarLayerIsAlwaysVisible() { + checkResults(result -> LayersTraceSubject.assertThat(result) + .showsLayer(NAVIGATION_BAR_WINDOW_TITLE) + .forAllEntries()); + } + + @Test + public void checkVisibility_statusBarLayerIsAlwaysVisible() { + checkResults(result -> LayersTraceSubject.assertThat(result) + .showsLayer(STATUS_BAR_WINDOW_TITLE) + .forAllEntries()); + } + + @Test + public void checkVisibility_topAppLayerIsAlwaysVisible() { + checkResults(result -> LayersTraceSubject.assertThat(result) + .showsLayer("SimpleActivity") + .forAllEntries()); + } + + @Test + public void checkVisibility_bottomAppLayerIsAlwaysVisible() { + checkResults(result -> LayersTraceSubject.assertThat(result) + .showsLayer("ImeActivity") + .forAllEntries()); + } + + @Test + public void checkVisibility_dividerLayerIsAlwaysVisible() { + checkResults(result -> LayersTraceSubject.assertThat(result) + .showsLayer(DOCKED_STACK_DIVIDER) + .forAllEntries()); + } + + @Test + public void checkPosition_appsStartingBounds() { + Rect displayBounds = getDisplayBounds(); + checkResults(result -> { + LayersTrace entries = LayersTrace.parseFrom(result.getLayersTrace(), + result.getLayersTracePath()); + + assertThat(entries.getEntries()).isNotEmpty(); + Rect startingDividerBounds = entries.getEntries().get(0).getVisibleBounds + (DOCKED_STACK_DIVIDER); + + Rect startingTopAppBounds = new Rect(0, 0, startingDividerBounds.right, + startingDividerBounds.top + getDockedStackDividerInset()); + + Rect startingBottomAppBounds = new Rect(0, + startingDividerBounds.bottom - getDockedStackDividerInset(), + displayBounds.right, + displayBounds.bottom - getNavigationBarHeight()); + + LayersTraceSubject.assertThat(result) + .hasVisibleRegion("SimpleActivity", startingTopAppBounds) + .inTheBeginning(); + + LayersTraceSubject.assertThat(result) + .hasVisibleRegion("ImeActivity", startingBottomAppBounds) + .inTheBeginning(); + }); + } + + @Test + public void checkPosition_appsEndingBounds() { + Rect displayBounds = getDisplayBounds(); + checkResults(result -> { + LayersTrace entries = LayersTrace.parseFrom(result.getLayersTrace(), + result.getLayersTracePath()); + + assertThat(entries.getEntries()).isNotEmpty(); + Rect endingDividerBounds = entries.getEntries().get( + entries.getEntries().size() - 1).getVisibleBounds( + DOCKED_STACK_DIVIDER); + + Rect startingTopAppBounds = new Rect(0, 0, endingDividerBounds.right, + endingDividerBounds.top + getDockedStackDividerInset()); + + Rect startingBottomAppBounds = new Rect(0, + endingDividerBounds.bottom - getDockedStackDividerInset(), + displayBounds.right, + displayBounds.bottom - getNavigationBarHeight()); + + LayersTraceSubject.assertThat(result) + .hasVisibleRegion("SimpleActivity", startingTopAppBounds) + .atTheEnd(); + + LayersTraceSubject.assertThat(result) + .hasVisibleRegion("ImeActivity", startingBottomAppBounds) + .atTheEnd(); + }); + } + + @Test + public void checkVisibility_navBarWindowIsAlwaysVisible() { + checkResults(result -> WmTraceSubject.assertThat(result) + .showsAboveAppWindow(NAVIGATION_BAR_WINDOW_TITLE) + .forAllEntries()); + } + + @Test + public void checkVisibility_statusBarWindowIsAlwaysVisible() { + checkResults(result -> WmTraceSubject.assertThat(result) + .showsAboveAppWindow(STATUS_BAR_WINDOW_TITLE) + .forAllEntries()); + } + + @Test + public void checkVisibility_topAppWindowIsAlwaysVisible() { + checkResults(result -> WmTraceSubject.assertThat(result) + .showsAppWindow("SimpleActivity") + .forAllEntries()); + } + + @Test + public void checkVisibility_bottomAppWindowIsAlwaysVisible() { + checkResults(result -> WmTraceSubject.assertThat(result) + .showsAppWindow("ImeActivity") + .forAllEntries()); + } + + @Test + public void checkVisibility_dividerWindowIsAlwaysVisible() { + checkResults(result -> WmTraceSubject.assertThat(result) + .showsAboveAppWindow(DOCKED_STACK_DIVIDER) + .forAllEntries()); + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/SeamlessAppRotationTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/SeamlessAppRotationTest.java new file mode 100644 index 000000000000..3eab68d1272f --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/SeamlessAppRotationTest.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2018 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.wm.flicker; + +import static android.view.Surface.rotationToString; + +import static com.android.server.wm.flicker.CommonTransitions.changeAppRotation; +import static com.android.server.wm.flicker.WindowUtils.getAppPosition; +import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds; +import static com.android.server.wm.flicker.WindowUtils.getNavigationBarPosition; +import static com.android.server.wm.flicker.testapp.ActivityOptions.EXTRA_STARVE_UI_THREAD; +import static com.android.server.wm.flicker.testapp.ActivityOptions.SEAMLESS_ACTIVITY_COMPONENT_NAME; + +import android.content.Intent; +import android.graphics.Rect; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.LargeTest; +import android.view.Surface; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Cycle through supported app rotations using seamless rotations. + * To run this test: {@code atest FlickerTests:SeamlessAppRotationTest} + */ +@LargeTest +@RunWith(Parameterized.class) +public class SeamlessAppRotationTest extends FlickerTestBase { + private int mBeginRotation; + private int mEndRotation; + private Intent mIntent; + + public SeamlessAppRotationTest(String testId, Intent intent, int beginRotation, + int endRotation) { + this.mIntent = intent; + this.mBeginRotation = beginRotation; + this.mEndRotation = endRotation; + } + + @Parameters(name = "{0}") + public static Collection<Object[]> getParams() { + int[] supportedRotations = + {Surface.ROTATION_0, Surface.ROTATION_90, Surface.ROTATION_270}; + Collection<Object[]> params = new ArrayList<>(); + + ArrayList<Intent> testIntents = new ArrayList<>(); + + // launch test activity that supports seamless rotation + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.setComponent(SEAMLESS_ACTIVITY_COMPONENT_NAME); + testIntents.add(intent); + + // launch test activity that supports seamless rotation with a busy UI thread to miss frames + // when the app is asked to redraw + intent = new Intent(intent); + intent.putExtra(EXTRA_STARVE_UI_THREAD, true); + testIntents.add(intent); + + for (Intent testIntent : testIntents) { + for (int begin : supportedRotations) { + for (int end : supportedRotations) { + if (begin != end) { + String testId = rotationToString(begin) + "_" + rotationToString(end); + if (testIntent.getExtras() != null && + testIntent.getExtras().getBoolean(EXTRA_STARVE_UI_THREAD)) { + testId += "_" + "BUSY_UI_THREAD"; + } + params.add(new Object[]{testId, testIntent, begin, end}); + } + } + } + } + return params; + } + + @Before + public void runTransition() { + String intentId = ""; + if (mIntent.getExtras() != null && + mIntent.getExtras().getBoolean(EXTRA_STARVE_UI_THREAD)) { + intentId = "BUSY_UI_THREAD"; + } + + super.runTransition( + changeAppRotation(mIntent, intentId, InstrumentationRegistry.getContext(), + uiDevice, mBeginRotation, mEndRotation).repeat(5).build()); + } + + @Test + public void checkVisibility_navBarWindowIsAlwaysVisible() { + checkResults(result -> WmTraceSubject.assertThat(result) + .showsAboveAppWindow(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries()); + } + + @Test + public void checkPosition_navBarLayerRotatesAndScales() { + Rect startingPos = getNavigationBarPosition(mBeginRotation); + Rect endingPos = getNavigationBarPosition(mEndRotation); + if (startingPos.equals(endingPos)) { + checkResults(result -> LayersTraceSubject.assertThat(result) + .hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, startingPos) + .forAllEntries()); + } else { + checkResults(result -> LayersTraceSubject.assertThat(result) + .hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, startingPos) + .inTheBeginning()); + checkResults(result -> LayersTraceSubject.assertThat(result) + .hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, endingPos) + .atTheEnd()); + } + } + + @Test + public void checkPosition_appLayerRotates() { + Rect startingPos = getAppPosition(mBeginRotation); + Rect endingPos = getAppPosition(mEndRotation); + if (startingPos.equals(endingPos)) { + checkResults(result -> LayersTraceSubject.assertThat(result) + .hasVisibleRegion(mIntent.getComponent().getPackageName(), startingPos) + .forAllEntries()); + } else { + checkResults(result -> LayersTraceSubject.assertThat(result) + .hasVisibleRegion(mIntent.getComponent().getPackageName(), startingPos) + .then() + .hasVisibleRegion(mIntent.getComponent().getPackageName(), endingPos) + .forAllEntries()); + } + } + + @Test + public void checkCoveredRegion_noUncoveredRegions() { + Rect startingBounds = getDisplayBounds(mBeginRotation); + Rect endingBounds = getDisplayBounds(mEndRotation); + if (startingBounds.equals(endingBounds)) { + checkResults(result -> + LayersTraceSubject.assertThat(result) + .coversRegion(startingBounds) + .forAllEntries()); + } else { + checkResults(result -> + LayersTraceSubject.assertThat(result) + .coversRegion(startingBounds) + .then() + .coversRegion(endingBounds) + .forAllEntries()); + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/SplitScreenToLauncherTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/SplitScreenToLauncherTest.java new file mode 100644 index 000000000000..40bd4e962153 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/SplitScreenToLauncherTest.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2018 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.wm.flicker; + +import static com.android.server.wm.flicker.CommonTransitions.splitScreenToLauncher; +import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds; + +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.FlakyTest; +import android.support.test.filters.LargeTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test open app to split screen. + * To run this test: {@code atest FlickerTests:SplitScreenToLauncherTest} + */ +@LargeTest +@RunWith(AndroidJUnit4.class) +public class SplitScreenToLauncherTest extends FlickerTestBase { + + public SplitScreenToLauncherTest() { + this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(), + "com.android.server.wm.flicker.testapp", "SimpleApp"); + } + + @Before + public void runTransition() { + super.runTransition(splitScreenToLauncher(testApp, uiDevice).includeJankyRuns().build()); + } + + @Test + public void checkCoveredRegion_noUncoveredRegions() { + checkResults(result -> + LayersTraceSubject.assertThat(result) + .coversRegion(getDisplayBounds()).forAllEntries()); + } + + @Test + public void checkVisibility_dividerLayerBecomesInVisible() { + checkResults(result -> LayersTraceSubject.assertThat(result) + .showsLayer(DOCKED_STACK_DIVIDER) + .then() + .hidesLayer(DOCKED_STACK_DIVIDER) + .forAllEntries()); + } + + @FlakyTest(bugId = 79686616) + @Test + public void checkVisibility_appLayerBecomesInVisible() { + checkResults(result -> LayersTraceSubject.assertThat(result) + .showsLayer(testApp.getPackage()) + .then() + .hidesLayer(testApp.getPackage()) + .forAllEntries()); + } + + @Test + public void checkVisibility_navBarWindowIsAlwaysVisible() { + checkResults(result -> WmTraceSubject.assertThat(result) + .showsAboveAppWindow(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries()); + } + + @Test + public void checkVisibility_statusBarWindowIsAlwaysVisible() { + checkResults(result -> WmTraceSubject.assertThat(result) + .showsAboveAppWindow(STATUS_BAR_WINDOW_TITLE).forAllEntries()); + } + + @Test + public void checkVisibility_dividerWindowBecomesInVisible() { + checkResults(result -> WmTraceSubject.assertThat(result) + .showsAboveAppWindow(DOCKED_STACK_DIVIDER) + .then() + .hidesAboveAppWindow(DOCKED_STACK_DIVIDER) + .forAllEntries()); + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/StandardAppHelper.java b/tests/FlickerTests/src/com/android/server/wm/flicker/StandardAppHelper.java new file mode 100644 index 000000000000..79a0220e0e87 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/StandardAppHelper.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2018 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.wm.flicker; + +import android.app.Instrumentation; +import android.platform.helpers.AbstractStandardAppHelper; + +/** + * Class to take advantage of {@code IAppHelper} interface so the same test can be run against + * first party and third party apps. + */ +public class StandardAppHelper extends AbstractStandardAppHelper { + private final String mPackageName; + private final String mLauncherName; + + public StandardAppHelper(Instrumentation instr, String packageName, String launcherName) { + super(instr); + mPackageName = packageName; + mLauncherName = launcherName; + } + + /** + * {@inheritDoc} + */ + @Override + public String getPackage() { + return mPackageName; + } + + /** + * {@inheritDoc} + */ + @Override + public String getLauncherName() { + return mLauncherName; + } + + /** + * {@inheritDoc} + */ + @Override + public void dismissInitialDialogs() { + + } +} diff --git a/tests/FlickerTests/test-apps/Android.mk b/tests/FlickerTests/test-apps/Android.mk new file mode 100644 index 000000000000..9af9f444ca59 --- /dev/null +++ b/tests/FlickerTests/test-apps/Android.mk @@ -0,0 +1,15 @@ +# Copyright (C) 2018 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. + +include $(call all-subdir-makefiles) diff --git a/tests/FlickerTests/test-apps/flickerapp/Android.mk b/tests/FlickerTests/test-apps/flickerapp/Android.mk new file mode 100644 index 000000000000..b916900a7504 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/Android.mk @@ -0,0 +1,31 @@ +# Copyright (C) 2018 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. + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) +LOCAL_PACKAGE_NAME := FlickerTestApp +LOCAL_MODULE_TAGS := tests optional +LOCAL_SRC_FILES := $(call all-subdir-java-files) +LOCAL_SDK_VERSION := current +LOCAL_COMPATIBILITY_SUITE := device-tests +include $(BUILD_PACKAGE) + +include $(CLEAR_VARS) +LOCAL_MODULE := flickertestapplib +LOCAL_MODULE_TAGS := tests optional +LOCAL_SDK_VERSION := current +LOCAL_SRC_FILES := src/com/android/server/wm/flicker/testapp/ActivityOptions.java +include $(BUILD_STATIC_JAVA_LIBRARY) + +include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml new file mode 100644 index 000000000000..b694172d60ca --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 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. +--> + +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.wm.flicker.testapp"> + + <uses-sdk android:minSdkVersion="17" android:targetSdkVersion="27"/> + <application + android:allowBackup="false" + android:supportsRtl="true"> + <activity android:name=".SimpleActivity" + android:taskAffinity="com.android.server.wm.flicker.testapp.SimpleActivity" + android:label="SimpleApp"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + <activity android:name=".ImeActivity" + android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivity" + android:label="ImeApp"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + <activity android:name=".PipActivity" + android:resizeableActivity="true" + android:supportsPictureInPicture="true" + android:configChanges= + "screenSize|smallestScreenSize|screenLayout|orientation" + android:taskAffinity="com.android.server.wm.flicker.testapp.PipActivity" + android:label="PipApp"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + <activity android:name=".SeamlessRotationActivity" + android:taskAffinity= + "com.android.server.wm.flicker.testapp.SeamlessRotationActivity" + android:configChanges="orientation|screenSize" + android:label="SeamlessApp"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + </application> +</manifest>
\ No newline at end of file diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml new file mode 100644 index 000000000000..d5eb02330441 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2018 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. +--> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@android:color/holo_green_light"> + <EditText android:id="@+id/plain_text_input" + android:layout_height="wrap_content" + android:layout_width="match_parent" + android:inputType="text"/> +</LinearLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml new file mode 100644 index 000000000000..2c58d91e34fe --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2018 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. +--> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@android:color/holo_blue_bright"> + <Button android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/enter_pip" + android:text="Enter PIP"/> +</LinearLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_simple.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_simple.xml new file mode 100644 index 000000000000..5d94e5177dcc --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_simple.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2018 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. +--> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@android:color/holo_orange_light"> + +</LinearLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java new file mode 100644 index 000000000000..18994111324e --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2018 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.wm.flicker.testapp; + +import android.content.ComponentName; + +public class ActivityOptions { + public static final String EXTRA_STARVE_UI_THREAD = "StarveUiThread"; + public static final ComponentName SEAMLESS_ACTIVITY_COMPONENT_NAME = + new ComponentName("com.android.server.wm.flicker.testapp", + "com.android.server.wm.flicker.testapp.SeamlessRotationActivity"); +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivity.java new file mode 100644 index 000000000000..df60460e7ae3 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivity.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2018 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.wm.flicker.testapp; + +import android.app.Activity; +import android.os.Bundle; +import android.view.WindowManager; + +public class ImeActivity extends Activity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + WindowManager.LayoutParams p = getWindow().getAttributes(); + p.layoutInDisplayCutoutMode = WindowManager.LayoutParams + .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + getWindow().setAttributes(p); + setContentView(R.layout.activity_ime); + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java new file mode 100644 index 000000000000..9a8f39907877 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2018 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.wm.flicker.testapp; + +import android.app.Activity; +import android.app.PictureInPictureParams; +import android.graphics.Rect; +import android.os.Bundle; +import android.util.Rational; +import android.view.WindowManager; +import android.widget.Button; + +public class PipActivity extends Activity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + WindowManager.LayoutParams p = getWindow().getAttributes(); + p.layoutInDisplayCutoutMode = WindowManager.LayoutParams + .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + getWindow().setAttributes(p); + setContentView(R.layout.activity_pip); + Button enterPip = (Button) findViewById(R.id.enter_pip); + + PictureInPictureParams params = new PictureInPictureParams.Builder() + .setAspectRatio(new Rational(1, 1)) + .setSourceRectHint(new Rect(0, 0, 100, 100)) + .build(); + + enterPip.setOnClickListener((v) -> enterPictureInPictureMode(params)); + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java new file mode 100644 index 000000000000..3a0c1c9382fe --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2018 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.wm.flicker.testapp; + +import static android.os.SystemClock.sleep; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + +import static com.android.server.wm.flicker.testapp.ActivityOptions.EXTRA_STARVE_UI_THREAD; + +import android.app.Activity; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.view.Window; +import android.view.WindowManager; + +import java.util.Timer; +import java.util.TimerTask; + +public class SeamlessRotationActivity extends Activity { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + enableSeamlessRotation(); + setContentView(R.layout.activity_simple); + boolean starveUiThread = getIntent().getExtras() != null && + getIntent().getExtras().getBoolean(EXTRA_STARVE_UI_THREAD); + if (starveUiThread) { + starveUiThread(); + } + } + + private void starveUiThread() { + Handler handler = new Handler(Looper.getMainLooper(), (Message unused) -> { + sleep(20); + return true; + }); + new Timer().schedule(new TimerTask() { + @Override + public void run() { + handler.sendEmptyMessage(0); + } + }, 0, 21); + } + + private void enableSeamlessRotation() { + WindowManager.LayoutParams p = getWindow().getAttributes(); + p.rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS; + p.layoutInDisplayCutoutMode = WindowManager.LayoutParams + .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + requestWindowFeature(Window.FEATURE_NO_TITLE); + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + getWindow().setAttributes(p); + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SimpleActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SimpleActivity.java new file mode 100644 index 000000000000..699abf87d341 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SimpleActivity.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2018 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.wm.flicker.testapp; + +import android.app.Activity; +import android.os.Bundle; +import android.view.WindowManager; + +public class SimpleActivity extends Activity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + WindowManager.LayoutParams p = getWindow().getAttributes(); + p.layoutInDisplayCutoutMode = WindowManager.LayoutParams + .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + getWindow().setAttributes(p); + setContentView(R.layout.activity_simple); + } +} |