Merge "Complication Entity & Presentation Refactor."
This commit is contained in:
@@ -18,7 +18,6 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/dream_overlay_complications_layer"
|
||||
android:padding="20dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<TextClock
|
||||
|
||||
@@ -28,8 +28,6 @@
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
<include layout="@layout/dream_overlay_complications_layer" />
|
||||
|
||||
<com.android.systemui.dreams.DreamOverlayStatusBarView
|
||||
android:id="@+id/dream_overlay_status_bar"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.dreams;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* A collection of interfaces related to hosting a complication.
|
||||
*/
|
||||
public abstract class ComplicationHost {
|
||||
/**
|
||||
* An interface for the callback from the complication provider to indicate when the
|
||||
* complication is ready.
|
||||
*/
|
||||
public interface CreationCallback {
|
||||
/**
|
||||
* Called to inform the complication view is ready to be placed within the visual space.
|
||||
* @param view The view representing the complication.
|
||||
* @param layoutParams The parameters to create the view with.
|
||||
*/
|
||||
void onCreated(View view, ComplicationHostView.LayoutParams layoutParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface for the callback from the complication provider to signal interactions in the
|
||||
* complication.
|
||||
*/
|
||||
public interface InteractionCallback {
|
||||
/**
|
||||
* Called to signal the calling complication would like to exit the dream.
|
||||
*/
|
||||
void onExit();
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.dreams;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
|
||||
/**
|
||||
* {@link ComplicationHostView} is the container view for housing complications above of a dream.
|
||||
*/
|
||||
public class ComplicationHostView extends ConstraintLayout {
|
||||
public ComplicationHostView(Context context) {
|
||||
super(context, null);
|
||||
}
|
||||
|
||||
public ComplicationHostView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs, 0);
|
||||
}
|
||||
|
||||
public ComplicationHostView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr, 0);
|
||||
}
|
||||
|
||||
public ComplicationHostView(Context context, AttributeSet attrs, int defStyleAttr,
|
||||
int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.dreams;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.content.Context;
|
||||
|
||||
import com.android.settingslib.dream.DreamBackend;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* {@link ComplicationProvider} is an interface for defining entities that can supply complications
|
||||
* to show over a dream. Presentation components such as the {@link DreamOverlayService} supply
|
||||
* implementations with the necessary context for constructing such overlays.
|
||||
*/
|
||||
public interface ComplicationProvider {
|
||||
/**
|
||||
* The type of dream complications which can be provided by a {@link ComplicationProvider}.
|
||||
*/
|
||||
@IntDef(prefix = {"COMPLICATION_TYPE_"}, flag = true, value = {
|
||||
COMPLICATION_TYPE_NONE,
|
||||
COMPLICATION_TYPE_TIME,
|
||||
COMPLICATION_TYPE_DATE,
|
||||
COMPLICATION_TYPE_WEATHER,
|
||||
COMPLICATION_TYPE_AIR_QUALITY,
|
||||
COMPLICATION_TYPE_CAST_INFO
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@interface ComplicationType {}
|
||||
|
||||
int COMPLICATION_TYPE_NONE = 0;
|
||||
int COMPLICATION_TYPE_TIME = 1;
|
||||
int COMPLICATION_TYPE_DATE = 1 << 1;
|
||||
int COMPLICATION_TYPE_WEATHER = 1 << 2;
|
||||
int COMPLICATION_TYPE_AIR_QUALITY = 1 << 3;
|
||||
int COMPLICATION_TYPE_CAST_INFO = 1 << 4;
|
||||
|
||||
/**
|
||||
* Called when the {@link ComplicationHost} requests the associated complication be produced.
|
||||
*
|
||||
* @param context The {@link Context} used to construct the view.
|
||||
* @param creationCallback The callback to inform the complication has been created.
|
||||
* @param interactionCallback The callback to inform the complication has been interacted with.
|
||||
*/
|
||||
void onCreateComplication(Context context, ComplicationHost.CreationCallback creationCallback,
|
||||
ComplicationHost.InteractionCallback interactionCallback);
|
||||
|
||||
/**
|
||||
* Converts a {@link com.android.settingslib.dream.DreamBackend.ComplicationType} to
|
||||
* {@link ComplicationType}.
|
||||
*/
|
||||
@ComplicationType
|
||||
default int convertComplicationType(@DreamBackend.ComplicationType int type) {
|
||||
switch (type) {
|
||||
case DreamBackend.COMPLICATION_TYPE_TIME:
|
||||
return COMPLICATION_TYPE_TIME;
|
||||
case DreamBackend.COMPLICATION_TYPE_DATE:
|
||||
return COMPLICATION_TYPE_DATE;
|
||||
case DreamBackend.COMPLICATION_TYPE_WEATHER:
|
||||
return COMPLICATION_TYPE_WEATHER;
|
||||
case DreamBackend.COMPLICATION_TYPE_AIR_QUALITY:
|
||||
return COMPLICATION_TYPE_AIR_QUALITY;
|
||||
case DreamBackend.COMPLICATION_TYPE_CAST_INFO:
|
||||
return COMPLICATION_TYPE_CAST_INFO;
|
||||
default:
|
||||
return COMPLICATION_TYPE_NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,11 +25,11 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.dagger.qualifiers.Main;
|
||||
import com.android.systemui.dreams.complication.ComplicationHostViewController;
|
||||
import com.android.systemui.dreams.complication.dagger.ComplicationHostViewComponent;
|
||||
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
|
||||
import com.android.systemui.dreams.dagger.DreamOverlayModule;
|
||||
import com.android.systemui.util.ViewController;
|
||||
@@ -47,6 +47,8 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
|
||||
private final int mDreamOverlayNotificationsDragAreaHeight;
|
||||
private final DreamOverlayStatusBarViewController mStatusBarViewController;
|
||||
|
||||
private final ComplicationHostViewController mComplicationHostViewController;
|
||||
|
||||
// The dream overlay's content view, which is located below the status bar (in z-order) and is
|
||||
// the space into which widgets are placed.
|
||||
private final ViewGroup mDreamOverlayContentView;
|
||||
@@ -74,6 +76,13 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
|
||||
final int childCount = mDreamOverlayContentView.getChildCount();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
View child = mDreamOverlayContentView.getChildAt(i);
|
||||
|
||||
if (mComplicationHostViewController.getView() == child) {
|
||||
region.op(mComplicationHostViewController.getTouchRegions(),
|
||||
Region.Op.UNION);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (child.getGlobalVisibleRect(rect)) {
|
||||
region.op(rect, Region.Op.UNION);
|
||||
}
|
||||
@@ -93,6 +102,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
|
||||
@Inject
|
||||
public DreamOverlayContainerViewController(
|
||||
DreamOverlayContainerView containerView,
|
||||
ComplicationHostViewComponent.Factory complicationHostViewFactory,
|
||||
@Named(DreamOverlayModule.DREAM_OVERLAY_CONTENT_VIEW) ViewGroup contentView,
|
||||
DreamOverlayStatusBarViewController statusBarViewController,
|
||||
@Main Handler handler,
|
||||
@@ -106,6 +116,13 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
|
||||
mView.getResources().getDimensionPixelSize(
|
||||
R.dimen.dream_overlay_notifications_drag_area_height);
|
||||
|
||||
mComplicationHostViewController = complicationHostViewFactory.create().getController();
|
||||
final View view = mComplicationHostViewController.getView();
|
||||
|
||||
mDreamOverlayContentView.addView(view,
|
||||
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
|
||||
mHandler = handler;
|
||||
mMaxBurnInOffset = maxBurnInOffset;
|
||||
mBurnInProtectionUpdateInterval = burnInProtectionUpdateInterval;
|
||||
@@ -114,6 +131,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
|
||||
@Override
|
||||
protected void onInit() {
|
||||
mStatusBarViewController.init();
|
||||
mComplicationHostViewController.init();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -130,18 +148,10 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
|
||||
.removeOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
|
||||
}
|
||||
|
||||
void addOverlay(View overlayView, ConstraintLayout.LayoutParams layoutParams) {
|
||||
mDreamOverlayContentView.addView(overlayView, layoutParams);
|
||||
}
|
||||
|
||||
View getContainerView() {
|
||||
return mView;
|
||||
}
|
||||
|
||||
void removeAllOverlays() {
|
||||
mDreamOverlayContentView.removeAllViews();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
int getDreamOverlayNotificationsDragAreaHeight() {
|
||||
return mDreamOverlayNotificationsDragAreaHeight;
|
||||
|
||||
@@ -24,10 +24,13 @@ import android.view.WindowInsets;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import androidx.lifecycle.LifecycleRegistry;
|
||||
import androidx.lifecycle.ViewModelStore;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.policy.PhoneWindow;
|
||||
import com.android.systemui.dagger.qualifiers.Main;
|
||||
import com.android.systemui.dreams.complication.Complication;
|
||||
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -47,8 +50,6 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
|
||||
private final Context mContext;
|
||||
// The Executor ensures actions and ui updates happen on the same thread.
|
||||
private final Executor mExecutor;
|
||||
// The state controller informs the service of updates to the complications present.
|
||||
private final DreamOverlayStateController mStateController;
|
||||
// A controller for the dream overlay container view (which contains both the status bar and the
|
||||
// content area).
|
||||
private final DreamOverlayContainerViewController mDreamOverlayContainerViewController;
|
||||
@@ -56,47 +57,51 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
|
||||
// A reference to the {@link Window} used to hold the dream overlay.
|
||||
private Window mWindow;
|
||||
|
||||
private final DreamOverlayStateController.Callback mOverlayStateCallback =
|
||||
new DreamOverlayStateController.Callback() {
|
||||
@Override
|
||||
public void onComplicationsChanged() {
|
||||
mExecutor.execute(() -> reloadComplicationsLocked());
|
||||
}
|
||||
};
|
||||
private final Complication.Host mHost = new Complication.Host() {
|
||||
@Override
|
||||
public void requestExitDream() {
|
||||
mExecutor.execute(DreamOverlayService.this::requestExit);
|
||||
}
|
||||
};
|
||||
|
||||
private final LifecycleRegistry mLifecycleRegistry;
|
||||
|
||||
private ViewModelStore mViewModelStore = new ViewModelStore();
|
||||
|
||||
@Inject
|
||||
public DreamOverlayService(
|
||||
Context context,
|
||||
@Main Executor executor,
|
||||
DreamOverlayStateController overlayStateController,
|
||||
DreamOverlayComponent.Factory dreamOverlayComponentFactory) {
|
||||
mContext = context;
|
||||
mExecutor = executor;
|
||||
mStateController = overlayStateController;
|
||||
mDreamOverlayContainerViewController =
|
||||
dreamOverlayComponentFactory.create().getDreamOverlayContainerViewController();
|
||||
|
||||
mStateController.addCallback(mOverlayStateCallback);
|
||||
final DreamOverlayComponent component =
|
||||
dreamOverlayComponentFactory.create(mViewModelStore, mHost);
|
||||
mDreamOverlayContainerViewController = component.getDreamOverlayContainerViewController();
|
||||
setCurrentState(Lifecycle.State.CREATED);
|
||||
mLifecycleRegistry = component.getLifecycleRegistry();
|
||||
}
|
||||
|
||||
private void setCurrentState(Lifecycle.State state) {
|
||||
mExecutor.execute(() -> mLifecycleRegistry.setCurrentState(state));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
setCurrentState(Lifecycle.State.DESTROYED);
|
||||
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
|
||||
windowManager.removeView(mWindow.getDecorView());
|
||||
mStateController.removeCallback(mOverlayStateCallback);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
|
||||
mExecutor.execute(() -> addOverlayWindowLocked(layoutParams));
|
||||
}
|
||||
|
||||
private void reloadComplicationsLocked() {
|
||||
mDreamOverlayContainerViewController.removeAllOverlays();
|
||||
for (ComplicationProvider overlayProvider : mStateController.getComplications()) {
|
||||
addComplication(overlayProvider);
|
||||
}
|
||||
setCurrentState(Lifecycle.State.STARTED);
|
||||
mExecutor.execute(() -> {
|
||||
addOverlayWindowLocked(layoutParams);
|
||||
setCurrentState(Lifecycle.State.RESUMED);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,20 +134,5 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
|
||||
|
||||
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
|
||||
windowManager.addView(mWindow.getDecorView(), mWindow.getAttributes());
|
||||
mExecutor.execute(this::reloadComplicationsLocked);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected void addComplication(ComplicationProvider provider) {
|
||||
provider.onCreateComplication(mContext,
|
||||
(view, layoutParams) -> {
|
||||
// Always move UI related work to the main thread.
|
||||
mExecutor.execute(() -> mDreamOverlayContainerViewController
|
||||
.addOverlay(view, layoutParams));
|
||||
},
|
||||
() -> {
|
||||
// The Callback is set on the main thread.
|
||||
mExecutor.execute(this::requestExit);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,18 +17,17 @@
|
||||
package com.android.systemui.dreams;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.concurrent.futures.CallbackToFutureAdapter;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.systemui.dagger.SysUISingleton;
|
||||
import com.android.systemui.dagger.qualifiers.Main;
|
||||
import com.android.systemui.dreams.complication.Complication;
|
||||
import com.android.systemui.statusbar.policy.CallbackController;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
@@ -42,35 +41,6 @@ import javax.inject.Inject;
|
||||
@SysUISingleton
|
||||
public class DreamOverlayStateController implements
|
||||
CallbackController<DreamOverlayStateController.Callback> {
|
||||
// A counter for guaranteeing unique complications tokens within the scope of this state
|
||||
// controller.
|
||||
private int mNextComplicationTokenId = 0;
|
||||
|
||||
/**
|
||||
* {@link ComplicationToken} provides a unique key for identifying {@link ComplicationProvider}
|
||||
* instances registered with {@link DreamOverlayStateController}.
|
||||
*/
|
||||
public static class ComplicationToken {
|
||||
private final int mId;
|
||||
|
||||
private ComplicationToken(int id) {
|
||||
mId = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof ComplicationToken)) return false;
|
||||
ComplicationToken that = (ComplicationToken) o;
|
||||
return mId == that.mId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for dream overlay events.
|
||||
*/
|
||||
@@ -84,7 +54,8 @@ public class DreamOverlayStateController implements
|
||||
|
||||
private final Executor mExecutor;
|
||||
private final ArrayList<Callback> mCallbacks = new ArrayList<>();
|
||||
private final HashMap<ComplicationToken, ComplicationProvider> mComplications = new HashMap<>();
|
||||
|
||||
private final Collection<Complication> mComplications = new HashSet();
|
||||
|
||||
@VisibleForTesting
|
||||
@Inject
|
||||
@@ -93,47 +64,32 @@ public class DreamOverlayStateController implements
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a complication to be presented on top of dreams.
|
||||
* @param provider The {@link ComplicationProvider} providing the dream.
|
||||
* @return The {@link ComplicationToken} tied to the supplied {@link ComplicationProvider}.
|
||||
* Adds a complication to be included on the dream overlay.
|
||||
*/
|
||||
public ListenableFuture<ComplicationToken> addComplication(ComplicationProvider provider) {
|
||||
return CallbackToFutureAdapter.getFuture(completer -> {
|
||||
mExecutor.execute(() -> {
|
||||
final ComplicationToken token = new ComplicationToken(mNextComplicationTokenId++);
|
||||
mComplications.put(token, provider);
|
||||
notifyCallbacks();
|
||||
completer.set(token);
|
||||
});
|
||||
return "DreamOverlayStateController::addComplication";
|
||||
public void addComplication(Complication complication) {
|
||||
mExecutor.execute(() -> {
|
||||
if (mComplications.add(complication)) {
|
||||
mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a complication from being shown on dreams.
|
||||
* @param token The {@link ComplicationToken} associated with the {@link ComplicationProvider}
|
||||
* to be removed.
|
||||
* @return The removed {@link ComplicationProvider}, {@code null} if not found.
|
||||
* Removes a complication from inclusion on the dream overlay.
|
||||
*/
|
||||
public ListenableFuture<ComplicationProvider> removeComplication(ComplicationToken token) {
|
||||
return CallbackToFutureAdapter.getFuture(completer -> {
|
||||
mExecutor.execute(() -> {
|
||||
final ComplicationProvider removedComplication = mComplications.remove(token);
|
||||
|
||||
if (removedComplication != null) {
|
||||
notifyCallbacks();
|
||||
}
|
||||
completer.set(removedComplication);
|
||||
});
|
||||
|
||||
return "DreamOverlayStateController::removeComplication";
|
||||
public void removeComplication(Complication complication) {
|
||||
mExecutor.execute(() -> {
|
||||
if (mComplications.remove(complication)) {
|
||||
mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void notifyCallbacks() {
|
||||
for (Callback callback : mCallbacks) {
|
||||
callback.onComplicationsChanged();
|
||||
}
|
||||
/**
|
||||
* Returns collection of present {@link Complication}.
|
||||
*/
|
||||
public Collection<Complication> getComplications() {
|
||||
return Collections.unmodifiableCollection(mComplications);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -161,12 +117,4 @@ public class DreamOverlayStateController implements
|
||||
mCallbacks.remove(callback);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all registered {@link ComplicationProvider} instances.
|
||||
* @return A collection of {@link ComplicationProvider}.
|
||||
*/
|
||||
public Collection<ComplicationProvider> getComplications() {
|
||||
return mComplications.values();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.dreams.complication;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.view.View;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* {@link Complication} is an interface for defining a complication, a visual component rendered
|
||||
* above a dream. {@link Complication} instances encapsulate the logic for generating the view to be
|
||||
* shown, along with the supporting control/logic. The decision for including the
|
||||
* {@link Complication} is not the responsibility of the {@link Complication}. This is instead
|
||||
* handled by domain logic and invocations to add and remove the {@link Complication} through
|
||||
* {@link com.android.systemui.dreams.DreamOverlayStateController#addComplication(Complication)} and
|
||||
* {@link com.android.systemui.dreams.DreamOverlayStateController#removeComplication(Complication)}.
|
||||
* A {@link Complication} also does not represent a specific instance of the view. Instead, it
|
||||
* should be viewed as a provider, where view instances are requested from it. The associated
|
||||
* {@link ViewHolder} interface is requested for each view request. This object is retained for the
|
||||
* view's lifetime, providing a container for any associated logic. The complication rendering
|
||||
* system will consult this {@link ViewHolder} for {@link View} to show and
|
||||
* {@link ComplicationLayoutParams} to position the view. {@link ComplicationLayoutParams} allow for
|
||||
* specifying the sizing and position of the {@link Complication}.
|
||||
*
|
||||
* The following code sample exhibits the entities and lifecycle involved with a
|
||||
* {@link Complication}.
|
||||
*
|
||||
* <pre>{@code
|
||||
* // This component allows for the complication to generate a new ViewHolder for every request.
|
||||
* @Subcomponent
|
||||
* interface ExampleViewHolderComponent {
|
||||
* @Subcomponent.Factory
|
||||
* interface Factory {
|
||||
* ExampleViewHolderComponent create();
|
||||
* }
|
||||
*
|
||||
* ExampleViewHolder getViewHolder();
|
||||
* }
|
||||
*
|
||||
* // An example entity that controls whether or not a complication should be included on dreams.
|
||||
* // Note how the complication is tracked by reference for removal.
|
||||
* public class ExampleComplicationProvider {
|
||||
* private final DreamOverlayStateController mDreamOverlayStateController;
|
||||
* private final ExampleComplication mComplication;
|
||||
* @Inject
|
||||
* public ExampleComplicationProvider(
|
||||
* ExampleComplication complication,
|
||||
* DreamOverlayStateController stateController) {
|
||||
* mDreamOverlayStateController = stateController;
|
||||
* mComplication = complication;
|
||||
* }
|
||||
*
|
||||
* public void onShowConditionsMet(boolean met) {
|
||||
* if (met) {
|
||||
* mDreamOverlayStateController.addComplication(mComplication);
|
||||
* } else {
|
||||
* mDreamOverlayStateController.removeComplication(mComplication);
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* // An example complication. Note how a factory is created to supply a unique ViewHolder for each
|
||||
* // request. Also, there is no particular view instance members defined in the complication.
|
||||
* class ExampleComplication implements Complication {
|
||||
* private final ExampleViewHolderComponent.Factory mFactory;
|
||||
* @Inject
|
||||
* public ExampleComplication(ExampleViewHolderComponent.Factory viewHolderComponentFactory) {
|
||||
* mFactory = viewHolderComponentFactory;
|
||||
* }
|
||||
*
|
||||
* @Override
|
||||
* public ViewHolder createView(ComplicationViewModel model) {
|
||||
* return mFactory.create().getViewHolder();
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* // Not every ViewHolder needs to include a view controller. It is included here as an example of
|
||||
* // how such logic can be contained and associated with the ViewHolder lifecycle.
|
||||
* class ExampleViewController extends ViewController<FrameLayout> {
|
||||
* protected ExampleViewController(FrameLayout view) {
|
||||
* super(view);
|
||||
* }
|
||||
*
|
||||
* @Override
|
||||
* protected void onViewAttached() { }
|
||||
*
|
||||
* @Override
|
||||
* protected void onViewDetached() { }
|
||||
* }
|
||||
*
|
||||
* // An example ViewHolder. This is the correct place to contain any value/logic associated with a
|
||||
* // particular instance of the ComplicationView.
|
||||
* class ExampleViewHolder implements Complication.ViewHolder {
|
||||
* final FrameLayout mView;
|
||||
* final ExampleViewController mController;
|
||||
*
|
||||
* @Inject
|
||||
* public ExampleViewHolder(Context context) {
|
||||
* mView = new FrameLayout(context);
|
||||
* mController = new ExampleViewController(mView);
|
||||
* }
|
||||
* @Override
|
||||
* public View getView() {
|
||||
* return mView;
|
||||
* }
|
||||
*
|
||||
* @Override
|
||||
* public ComplicationLayoutParams getLayoutParams() {
|
||||
* return new ComplicationLayoutParams(
|
||||
* 200,
|
||||
* 100,
|
||||
* ComplicationLayoutParams.POSITION_TOP | ComplicationLayoutParams.DIRECTION_END,
|
||||
* ComplicationLayoutParams.DIRECTION_DOWN,
|
||||
* 4);
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
public interface Complication {
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef(prefix = { "CATEGORY_" }, value = {
|
||||
CATEGORY_STANDARD,
|
||||
CATEGORY_SYSTEM,
|
||||
})
|
||||
|
||||
@interface Category {}
|
||||
/**
|
||||
* {@code CATEGORY_STANDARD} indicates the complication is a normal component. Rules and
|
||||
* settings, such as hiding all complications, will apply to this complication.
|
||||
*/
|
||||
int CATEGORY_STANDARD = 1 << 0;
|
||||
/**
|
||||
* {@code CATEGORY_SYSTEM} indicates complications driven by SystemUI. Usually, these are
|
||||
* core components that are not user controlled. These can potentially deviate from given
|
||||
* rule sets that would normally apply to {@code CATEGORY_STANDARD}.
|
||||
*/
|
||||
int CATEGORY_SYSTEM = 1 << 1;
|
||||
|
||||
/**
|
||||
* The {@link Host} interface specifies a way a {@link Complication} to communicate with its
|
||||
* parent entity for information and actions.
|
||||
*/
|
||||
interface Host {
|
||||
/**
|
||||
* Called to signal a {@link Complication} has requested to exit the dream.
|
||||
*/
|
||||
void requestExitDream();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returned through {@link Complication#createView(ComplicationViewModel)}, {@link ViewHolder}
|
||||
* is a container for a single {@link Complication} instance. The {@link Host} guarantees that
|
||||
* the {@link ViewHolder} will be retained for the lifetime of the {@link Complication}
|
||||
* instance's user. The view is responsible for providing the view that represents the
|
||||
* {@link Complication}. This object is the proper place to store any related entities, such as
|
||||
* a {@link com.android.systemui.util.ViewController} for the view.
|
||||
*/
|
||||
interface ViewHolder {
|
||||
/**
|
||||
* Returns the {@link View} associated with the {@link ViewHolder}. This {@link View} should
|
||||
* be stable and generated once.
|
||||
* @return
|
||||
*/
|
||||
View getView();
|
||||
|
||||
/**
|
||||
* Returns the {@link Category} associated with the {@link Complication}. {@link Category}
|
||||
* is a grouping which helps define the relationship of the {@link Complication} to
|
||||
* System UI and the rest of the system. It is used for presentation and other decisions.
|
||||
*/
|
||||
@Complication.Category
|
||||
default int getCategory() {
|
||||
return Complication.CATEGORY_STANDARD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link ComplicationLayoutParams} associated with this complication. The
|
||||
* values expressed here are treated as preference rather than requirement. The hosting
|
||||
* entity is free to modify/interpret the parameters as deemed fit.
|
||||
*/
|
||||
ComplicationLayoutParams getLayoutParams();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a {@link ViewHolder} for the {@link Complication}. This captures both the view and
|
||||
* control logic for a single instance of the complication. The {@link Complication} may be
|
||||
* asked at any time to generate another view.
|
||||
* @param model The {@link ComplicationViewModel} associated with this particular
|
||||
* {@link Complication} instance.
|
||||
* @return a {@link ViewHolder} for this {@link Complication} instance.
|
||||
*/
|
||||
ViewHolder createView(ComplicationViewModel model);
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.dreams.complication;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
|
||||
import com.android.systemui.dreams.DreamOverlayStateController;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* {@link ComplicationCollectionLiveData} wraps
|
||||
* {@link DreamOverlayStateController#getComplications()} to provide an observable
|
||||
* {@link Complication} data set tied to a lifecycle. This should not be directly accessed. Instead,
|
||||
* clients should access the data from {@link ComplicationCollectionViewModel}.
|
||||
*/
|
||||
public class ComplicationCollectionLiveData extends LiveData<Collection<Complication>> {
|
||||
final DreamOverlayStateController mDreamOverlayStateController;
|
||||
|
||||
final DreamOverlayStateController.Callback mStateControllerCallback;
|
||||
|
||||
{
|
||||
mStateControllerCallback = new DreamOverlayStateController.Callback() {
|
||||
@Override
|
||||
public void onComplicationsChanged() {
|
||||
setValue(mDreamOverlayStateController.getComplications());
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@Inject
|
||||
public ComplicationCollectionLiveData(DreamOverlayStateController stateController) {
|
||||
super();
|
||||
mDreamOverlayStateController = stateController;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActive() {
|
||||
super.onActive();
|
||||
mDreamOverlayStateController.addCallback(mStateControllerCallback);
|
||||
setValue(mDreamOverlayStateController.getComplications());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInactive() {
|
||||
mDreamOverlayStateController.removeCallback(mStateControllerCallback);
|
||||
super.onInactive();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.dreams.complication;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.Transformations;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* {@link ComplicationCollectionViewModel} is an abstraction for observing and accessing
|
||||
* {@link ComplicationViewModel} for registered {@link Complication}.
|
||||
*/
|
||||
public class ComplicationCollectionViewModel extends ViewModel {
|
||||
private final LiveData<Collection<ComplicationViewModel>> mComplications;
|
||||
private final ComplicationViewModelTransformer mTransformer;
|
||||
|
||||
/**
|
||||
* Injectable constructor for {@link ComplicationCollectionViewModel}. Note that this cannot
|
||||
* be implicitly injected. Clients must bind scoped instance values through the corresponding
|
||||
* dagger subcomponent.
|
||||
*/
|
||||
@Inject
|
||||
public ComplicationCollectionViewModel(
|
||||
ComplicationCollectionLiveData complications,
|
||||
ComplicationViewModelTransformer transformer) {
|
||||
mComplications = Transformations.map(complications, collection -> convert(collection));
|
||||
mTransformer = transformer;
|
||||
}
|
||||
|
||||
private Collection<ComplicationViewModel> convert(Collection<Complication> complications) {
|
||||
return complications
|
||||
.stream()
|
||||
.map(complication -> mTransformer.getViewModel(complication))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link LiveData} for the collection of {@link Complication} represented as
|
||||
* {@link ComplicationViewModel}.
|
||||
*/
|
||||
public LiveData<Collection<ComplicationViewModel>> getComplications() {
|
||||
return mComplications;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.dreams.complication;
|
||||
|
||||
import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewComponent.SCOPED_COMPLICATIONS_LAYOUT;
|
||||
import static com.android.systemui.dreams.complication.dagger.ComplicationModule.SCOPED_COMPLICATIONS_MODEL;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Region;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
|
||||
import com.android.systemui.util.ViewController;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
/**
|
||||
* The {@link ComplicationHostViewController} is responsible for displaying complications within
|
||||
* a given container. It monitors the available {@link Complication} instances from
|
||||
* {@link com.android.systemui.dreams.DreamOverlayStateController} and inserts/removes them through
|
||||
* a {@link ComplicationLayoutEngine}.
|
||||
*/
|
||||
public class ComplicationHostViewController extends ViewController<ConstraintLayout> {
|
||||
private final ComplicationLayoutEngine mLayoutEngine;
|
||||
private final LifecycleOwner mLifecycleOwner;
|
||||
private final ComplicationCollectionViewModel mComplicationCollectionViewModel;
|
||||
private final HashMap<ComplicationId, Complication.ViewHolder> mComplications = new HashMap<>();
|
||||
|
||||
@Inject
|
||||
protected ComplicationHostViewController(
|
||||
@Named(SCOPED_COMPLICATIONS_LAYOUT) ConstraintLayout view,
|
||||
ComplicationLayoutEngine layoutEngine,
|
||||
LifecycleOwner lifecycleOwner,
|
||||
@Named(SCOPED_COMPLICATIONS_MODEL) ComplicationCollectionViewModel viewModel) {
|
||||
super(view);
|
||||
mLayoutEngine = layoutEngine;
|
||||
mLifecycleOwner = lifecycleOwner;
|
||||
mComplicationCollectionViewModel = viewModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInit() {
|
||||
super.onInit();
|
||||
mComplicationCollectionViewModel.getComplications().observe(mLifecycleOwner,
|
||||
complicationViewModels -> updateComplications(complicationViewModels));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the region in display space occupied by complications. Touches in this region
|
||||
* (composed of a collection of individual rectangular regions) should be directed to the
|
||||
* complications rather than the region underneath.
|
||||
*/
|
||||
public Region getTouchRegions() {
|
||||
final Region region = new Region();
|
||||
final Rect rect = new Rect();
|
||||
final int childCount = mView.getChildCount();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
View child = mView.getChildAt(i);
|
||||
if (child.getGlobalVisibleRect(rect)) {
|
||||
region.op(rect, Region.Op.UNION);
|
||||
}
|
||||
}
|
||||
|
||||
return region;
|
||||
}
|
||||
|
||||
private void updateComplications(Collection<ComplicationViewModel> complications) {
|
||||
final Collection<ComplicationId> ids = complications.stream()
|
||||
.map(complicationViewModel -> complicationViewModel.getId())
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
final Collection<ComplicationId> removedComplicationIds =
|
||||
mComplications.keySet().stream()
|
||||
.filter(complicationId -> !ids.contains(complicationId))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// Trim removed complications
|
||||
removedComplicationIds.forEach(complicationId -> {
|
||||
mLayoutEngine.removeComplication(complicationId);
|
||||
mComplications.remove(complicationId);
|
||||
});
|
||||
|
||||
// Add new complications
|
||||
final Collection<ComplicationViewModel> newComplications = complications
|
||||
.stream()
|
||||
.filter(complication -> !mComplications.containsKey(complication.getId()))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
newComplications
|
||||
.forEach(complication -> {
|
||||
final ComplicationId id = complication.getId();
|
||||
final Complication.ViewHolder viewHolder = complication.getComplication()
|
||||
.createView(complication);
|
||||
mComplications.put(id, viewHolder);
|
||||
mLayoutEngine.addComplication(id, viewHolder.getView(),
|
||||
viewHolder.getLayoutParams(), viewHolder.getCategory());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onViewAttached() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onViewDetached() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes the associated {@link View}. Since this {@link View} is instantiated through dagger
|
||||
* in the {@link ComplicationHostViewController} constructor, the
|
||||
* {@link ComplicationHostViewController} is responsible for surfacing it so that it can be
|
||||
* included in the parent view hierarchy.
|
||||
*/
|
||||
public View getView() {
|
||||
return mView;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.systemui.dreams.complication;
|
||||
|
||||
/**
|
||||
* A {@link ComplicationId} is a value to uniquely identify a complication during the current
|
||||
* runtime and within a particular scope. Any guarantees beyond this will need to be enforced
|
||||
* externally.
|
||||
*/
|
||||
public class ComplicationId {
|
||||
/**
|
||||
* An associated factory for minting ids that are unique in for the factory's scope.
|
||||
*/
|
||||
public static class Factory {
|
||||
private int mNextId;
|
||||
|
||||
ComplicationId getNextId() {
|
||||
return new ComplicationId(mNextId++);
|
||||
}
|
||||
}
|
||||
|
||||
private int mId;
|
||||
|
||||
private ComplicationId(int id) {
|
||||
mId = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ComplicationId{" + "mId=" + mId + "}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,429 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.dreams.complication;
|
||||
|
||||
import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewComponent.SCOPED_COMPLICATIONS_LAYOUT;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.constraintlayout.widget.Constraints;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
/**
|
||||
* {@link ComplicationLayoutEngine} arranges a collection of {@link ComplicationViewModel} based on
|
||||
* their layout parameters and attributes. The management of this set is done by
|
||||
* {@link ComplicationHostViewController}.
|
||||
*/
|
||||
public class ComplicationLayoutEngine {
|
||||
public static final String TAG = "ComplicationLayoutEngine";
|
||||
|
||||
/**
|
||||
* {@link ViewEntry} is an internal container, capturing information necessary for working with
|
||||
* a particular {@link Complication} view.
|
||||
*/
|
||||
private static class ViewEntry implements Comparable<ViewEntry> {
|
||||
private final View mView;
|
||||
private final ComplicationLayoutParams mLayoutParams;
|
||||
private final Parent mParent;
|
||||
@Complication.Category
|
||||
private final int mCategory;
|
||||
|
||||
/**
|
||||
* Default constructor. {@link Parent} allows for the {@link ViewEntry}'s surrounding
|
||||
* view hierarchy to be accessed without traversing the entire view tree.
|
||||
*/
|
||||
ViewEntry(View view, ComplicationLayoutParams layoutParams, int category, Parent parent) {
|
||||
mView = view;
|
||||
// Views that are generated programmatically do not have a unique id assigned to them
|
||||
// at construction. A new id is assigned here to enable ConstraintLayout relative
|
||||
// specifications. Existing ids for inflated views are not preserved.
|
||||
// {@link Complication.ViewHolder} should not reference the root container by id.
|
||||
mView.setId(View.generateViewId());
|
||||
mLayoutParams = layoutParams;
|
||||
mCategory = category;
|
||||
mParent = parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link View} associated with the {@link Complication}. This is the instance
|
||||
* passed in at construction. The reference to this {@link View} is captured when the
|
||||
* {@link Complication} is added to the {@link ComplicationLayoutEngine}. The
|
||||
* {@link Complication} cannot modify the {@link View} reference beyond this point.
|
||||
*/
|
||||
private View getView() {
|
||||
return mView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns The {@link ComplicationLayoutParams} associated with the view.
|
||||
*/
|
||||
public ComplicationLayoutParams getLayoutParams() {
|
||||
return mLayoutParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interprets the {@link #getLayoutParams()} into {@link ConstraintLayout.LayoutParams} and
|
||||
* applies them to the view. The method accounts for the relationship of the {@link View} to
|
||||
* the other {@link Complication} views around it. The organization of the {@link View}
|
||||
* instances in {@link ComplicationLayoutEngine} can be seen as lists. A {@link View} is
|
||||
* either the head of its list or a following node. This head is passed into this method,
|
||||
* which can be a reference to the {@link View} to indicate it is the head.
|
||||
*/
|
||||
public void applyLayoutParams(View head) {
|
||||
// Only the basic dimension parameters from the base ViewGroup.LayoutParams are carried
|
||||
// over verbatim from the complication specified LayoutParam. Other fields are
|
||||
// interpreted.
|
||||
final ConstraintLayout.LayoutParams params =
|
||||
new Constraints.LayoutParams(mLayoutParams.width, mLayoutParams.height);
|
||||
|
||||
final int direction = getLayoutParams().getDirection();
|
||||
|
||||
// If no parent, view is the anchor. In this case, it is given the highest priority for
|
||||
// alignment. All alignment preferences are done in relation to the parent container.
|
||||
final boolean isRoot = head == mView;
|
||||
|
||||
// Each view can be seen as a vector, having a point (described here as position) and
|
||||
// direction. When a view is the head of a position, then it is the first in a sequence
|
||||
// of complications to appear from that position. For example, being the head for
|
||||
// position POSITION_TOP | POSITION_END will cause the view to be shown as the first
|
||||
// view in that corner. In this case, the positions specify which sides to align with
|
||||
// the parent. If the view is not the head, the positions perpendicular to the direction
|
||||
// of the view specify which side to align with the opposing side of the head view.
|
||||
// Otherwise, the position aligns with the containing view. This means a
|
||||
// POSITION_BOTTOM | POSITION_START with DIRECTION_UP non-head view's bottom to be
|
||||
// aligned with the preceding view node's top and start to be aligned with the
|
||||
// parent's start.
|
||||
mLayoutParams.iteratePositions(position -> {
|
||||
switch(position) {
|
||||
case ComplicationLayoutParams.POSITION_START:
|
||||
if (isRoot || direction != ComplicationLayoutParams.DIRECTION_END) {
|
||||
params.startToStart = ConstraintLayout.LayoutParams.PARENT_ID;
|
||||
} else {
|
||||
params.startToEnd = head.getId();
|
||||
}
|
||||
break;
|
||||
case ComplicationLayoutParams.POSITION_TOP:
|
||||
if (isRoot || direction != ComplicationLayoutParams.DIRECTION_DOWN) {
|
||||
params.topToTop = ConstraintLayout.LayoutParams.PARENT_ID;
|
||||
} else {
|
||||
params.topToBottom = head.getId();
|
||||
}
|
||||
break;
|
||||
case ComplicationLayoutParams.POSITION_BOTTOM:
|
||||
if (isRoot || direction != ComplicationLayoutParams.DIRECTION_UP) {
|
||||
params.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID;
|
||||
} else {
|
||||
params.bottomToTop = head.getId();
|
||||
}
|
||||
break;
|
||||
case ComplicationLayoutParams.POSITION_END:
|
||||
if (isRoot || direction != ComplicationLayoutParams.DIRECTION_START) {
|
||||
params.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID;
|
||||
} else {
|
||||
params.endToStart = head.getId();
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
mView.setLayoutParams(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Informs the {@link ViewEntry}'s parent entity to remove the {@link ViewEntry} from
|
||||
* being shown further.
|
||||
*/
|
||||
public void remove() {
|
||||
mParent.removeEntry(this);
|
||||
|
||||
((ViewGroup) mView.getParent()).removeView(mView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ViewEntry viewEntry) {
|
||||
// If the two entries have different categories, system complications take precedence.
|
||||
if (viewEntry.mCategory != mCategory) {
|
||||
// Note that this logic will need to be adjusted if more categories are introduced.
|
||||
return mCategory == Complication.CATEGORY_SYSTEM ? 1 : -1;
|
||||
}
|
||||
|
||||
// A higher weight indicates greater precedence if all else being equal.
|
||||
if (viewEntry.mLayoutParams.getWeight() != mLayoutParams.getWeight()) {
|
||||
return mLayoutParams.getWeight() > viewEntry.mLayoutParams.getWeight() ? 1 : -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Builder} allows for a multiple entities to contribute to the {@link ViewEntry}
|
||||
* construction. This is necessary for setting an immutable parent, which might not be
|
||||
* known until the view hierarchy is traversed.
|
||||
*/
|
||||
private static class Builder {
|
||||
private final View mView;
|
||||
private final ComplicationLayoutParams mLayoutParams;
|
||||
private final int mCategory;
|
||||
private Parent mParent;
|
||||
|
||||
Builder(View view, ComplicationLayoutParams lp, @Complication.Category int category) {
|
||||
mView = view;
|
||||
mLayoutParams = lp;
|
||||
mCategory = category;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set {@link ComplicationLayoutParams}
|
||||
*/
|
||||
public ComplicationLayoutParams getLayoutParams() {
|
||||
return mLayoutParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set {@link Complication.Category}.
|
||||
*/
|
||||
@Complication.Category
|
||||
public int getCategory() {
|
||||
return mCategory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the parent. Note that this references to the entity for handling events, such as
|
||||
* requesting the removal of the {@link View}. It is not the
|
||||
* {@link android.view.ViewGroup} which contains the {@link View}.
|
||||
*/
|
||||
Builder setParent(Parent parent) {
|
||||
mParent = parent;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and returns the resulting {@link ViewEntry}.
|
||||
*/
|
||||
ViewEntry build() {
|
||||
return new ViewEntry(mView, mLayoutParams, mCategory, mParent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface allowing an {@link ViewEntry} to signal events.
|
||||
*/
|
||||
interface Parent {
|
||||
/**
|
||||
* Indicates the {@link ViewEntry} requests removal.
|
||||
*/
|
||||
void removeEntry(ViewEntry entry);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link PositionGroup} represents a collection of {@link Complication} at a given location.
|
||||
* It further organizes the {@link Complication} by the direction in which they emanate from
|
||||
* this position.
|
||||
*/
|
||||
private static class PositionGroup implements DirectionGroup.Parent {
|
||||
private final HashMap<Integer, DirectionGroup> mDirectionGroups = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Invoked by the {@link PositionGroup} holder to introduce a {@link Complication} view to
|
||||
* this group. It is assumed that the caller has correctly identified this
|
||||
* {@link PositionGroup} as the proper home for the {@link Complication} based on its
|
||||
* declared position.
|
||||
*/
|
||||
public ViewEntry add(ViewEntry.Builder entryBuilder) {
|
||||
final int direction = entryBuilder.getLayoutParams().getDirection();
|
||||
if (!mDirectionGroups.containsKey(direction)) {
|
||||
mDirectionGroups.put(direction, new DirectionGroup(this));
|
||||
}
|
||||
|
||||
return mDirectionGroups.get(direction).add(entryBuilder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEntriesChanged() {
|
||||
// Whenever an entry is added/removed from a child {@link DirectionGroup}, it is vital
|
||||
// that all {@link DirectionGroup} children are visited. It is possible the overall
|
||||
// head has changed, requiring constraints to be adjusted.
|
||||
updateViews();
|
||||
}
|
||||
|
||||
private void updateViews() {
|
||||
ViewEntry head = null;
|
||||
|
||||
// Identify which {@link Complication} head from the set of {@link DirectionGroup}
|
||||
// should be treated as the {@link PositionGroup} head.
|
||||
for (DirectionGroup directionGroup : mDirectionGroups.values()) {
|
||||
final ViewEntry groupHead = directionGroup.getHead();
|
||||
if (head == null || (groupHead != null && groupHead.compareTo(head) > 0)) {
|
||||
head = groupHead;
|
||||
}
|
||||
}
|
||||
|
||||
// A headless position group indicates no complications.
|
||||
if (head == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (DirectionGroup directionGroup : mDirectionGroups.values()) {
|
||||
// Tell each {@link DirectionGroup} to update its containing {@link ViewEntry} based
|
||||
// on the identified head. This iteration will also capture any newly added views.
|
||||
directionGroup.updateViews(head.getView());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link DirectionGroup} organizes the {@link ViewEntry} of a parent group that point are
|
||||
* laid out in the same direction.
|
||||
*/
|
||||
private static class DirectionGroup implements ViewEntry.Parent {
|
||||
/**
|
||||
* An interface implemented by the {@link DirectionGroup} parent to receive updates.
|
||||
*/
|
||||
interface Parent {
|
||||
/**
|
||||
* Invoked to indicate a change to the {@link ViewEntry} composition for this
|
||||
* {@link DirectionGroup}.
|
||||
*/
|
||||
void onEntriesChanged();
|
||||
}
|
||||
private final ArrayList<ViewEntry> mViews = new ArrayList<>();
|
||||
private final Parent mParent;
|
||||
|
||||
/**
|
||||
* Creates a new {@link DirectionGroup} with the specified parent. Note that the
|
||||
* {@link DirectionGroup} does not store its own direction. It is the responsibility of the
|
||||
* {@link DirectionGroup.Parent} to maintain this association.
|
||||
*/
|
||||
DirectionGroup(Parent parent) {
|
||||
mParent = parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the head of the group. It is assumed that the order of the {@link ViewEntry} is
|
||||
* proactively maintained.
|
||||
*/
|
||||
public ViewEntry getHead() {
|
||||
return mViews.isEmpty() ? null : mViews.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a {@link ViewEntry} via {@link ViewEntry.Builder} to this group.
|
||||
*/
|
||||
public ViewEntry add(ViewEntry.Builder entryBuilder) {
|
||||
final ViewEntry entry = entryBuilder.setParent(this).build();
|
||||
mViews.add(entry);
|
||||
|
||||
// After adding view, reverse sort collection.
|
||||
Collections.sort(mViews);
|
||||
Collections.reverse(mViews);
|
||||
|
||||
mParent.onEntriesChanged();
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEntry(ViewEntry entry) {
|
||||
// Sort is handled when the view is added, so should still be correct after removal.
|
||||
// However, the head may have been removed, which may affect the layout of views in
|
||||
// other DirectionGroups of the same PositionGroup.
|
||||
mViews.remove(entry);
|
||||
mParent.onEntriesChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked by {@link Parent} to update the layout of all children {@link ViewEntry} with
|
||||
* the specified head. Note that the head might not be in this group and instead part of a
|
||||
* neighboring group.
|
||||
*/
|
||||
public void updateViews(View groupHead) {
|
||||
Iterator<ViewEntry> it = mViews.iterator();
|
||||
|
||||
while (it.hasNext()) {
|
||||
final ViewEntry viewEntry = it.next();
|
||||
viewEntry.applyLayoutParams(groupHead);
|
||||
groupHead = viewEntry.getView();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final ConstraintLayout mLayout;
|
||||
private final HashMap<ComplicationId, ViewEntry> mEntries = new HashMap<>();
|
||||
private final HashMap<Integer, PositionGroup> mPositions = new HashMap<>();
|
||||
|
||||
/** */
|
||||
@Inject
|
||||
public ComplicationLayoutEngine(@Named(SCOPED_COMPLICATIONS_LAYOUT) ConstraintLayout layout) {
|
||||
mLayout = layout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a complication to this {@link ComplicationLayoutEngine}.
|
||||
* @param id A {@link ComplicationId} unique to this complication. If this matches a
|
||||
* complication within this {@link ComplicationViewModel}, the existing complication
|
||||
* will be removed.
|
||||
* @param view The {@link View} to be shown.
|
||||
* @param lp The {@link ComplicationLayoutParams} as expressed by the {@link Complication}.
|
||||
* These will be interpreted into the final applied parameters.
|
||||
* @param category The {@link Complication.Category} for the {@link Complication}.
|
||||
*/
|
||||
public void addComplication(ComplicationId id, View view,
|
||||
ComplicationLayoutParams lp, @Complication.Category int category) {
|
||||
// If the complication is present, remove.
|
||||
if (mEntries.containsKey(id)) {
|
||||
removeComplication(id);
|
||||
}
|
||||
|
||||
final ViewEntry.Builder entryBuilder = new ViewEntry.Builder(view, lp, category);
|
||||
|
||||
// Add position group if doesn't already exist
|
||||
final int position = lp.getPosition();
|
||||
if (!mPositions.containsKey(position)) {
|
||||
mPositions.put(position, new PositionGroup());
|
||||
}
|
||||
|
||||
// Insert entry into group
|
||||
final ViewEntry entry = mPositions.get(position).add(entryBuilder);
|
||||
mEntries.put(id, entry);
|
||||
|
||||
mLayout.addView(entry.getView());
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a complication by {@link ComplicationId}.
|
||||
*/
|
||||
public void removeComplication(ComplicationId id) {
|
||||
if (!mEntries.containsKey(id)) {
|
||||
Log.e(TAG, "could not find id:" + id);
|
||||
return;
|
||||
}
|
||||
|
||||
final ViewEntry entry = mEntries.get(id);
|
||||
entry.remove();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.systemui.dreams.complication;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* {@link ComplicationLayoutParams} allows a {@link Complication} to express its preferred location
|
||||
* and dimensions. Note that these parameters are not directly applied by any {@link ViewGroup}.
|
||||
* They are instead consulted for the final parameters which best seem fit for usage.
|
||||
*/
|
||||
public class ComplicationLayoutParams extends ViewGroup.LayoutParams {
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef(flag = true, prefix = { "POSITION_" }, value = {
|
||||
POSITION_TOP,
|
||||
POSITION_END,
|
||||
POSITION_BOTTOM,
|
||||
POSITION_START,
|
||||
})
|
||||
|
||||
@interface Position {}
|
||||
/** Align view with the top of parent or bottom of preceding {@link Complication}. */
|
||||
static final int POSITION_TOP = 1 << 0;
|
||||
/** Align view with the bottom of parent or top of preceding {@link Complication}. */
|
||||
static final int POSITION_BOTTOM = 1 << 1;
|
||||
/** Align view with the start of parent or end of preceding {@link Complication}. */
|
||||
static final int POSITION_START = 1 << 2;
|
||||
/** Align view with the end of parent or start of preceding {@link Complication}. */
|
||||
static final int POSITION_END = 1 << 3;
|
||||
|
||||
private static final int FIRST_POSITION = POSITION_TOP;
|
||||
private static final int LAST_POSITION = POSITION_END;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef(flag = true, prefix = { "DIRECTION_" }, value = {
|
||||
DIRECTION_UP,
|
||||
DIRECTION_DOWN,
|
||||
DIRECTION_START,
|
||||
DIRECTION_END,
|
||||
})
|
||||
|
||||
@interface Direction {}
|
||||
/** Position view upward from position. */
|
||||
static final int DIRECTION_UP = 1 << 0;
|
||||
/** Position view downward from position. */
|
||||
static final int DIRECTION_DOWN = 1 << 1;
|
||||
/** Position view towards the start of the parent. */
|
||||
static final int DIRECTION_START = 1 << 2;
|
||||
/** Position view towards the end of parent. */
|
||||
static final int DIRECTION_END = 1 << 3;
|
||||
|
||||
@Position
|
||||
private final int mPosition;
|
||||
|
||||
@Direction
|
||||
private final int mDirection;
|
||||
|
||||
private final int mWeight;
|
||||
|
||||
// Do not allow specifying opposite positions
|
||||
private static final int[] INVALID_POSITIONS =
|
||||
{ POSITION_BOTTOM | POSITION_TOP, POSITION_END | POSITION_START };
|
||||
|
||||
// Do not allow for specifying a direction towards the outside of the container.
|
||||
private static final Map<Integer, Integer> INVALID_DIRECTIONS;
|
||||
static {
|
||||
INVALID_DIRECTIONS = new HashMap<>();
|
||||
INVALID_DIRECTIONS.put(POSITION_BOTTOM, DIRECTION_DOWN);
|
||||
INVALID_DIRECTIONS.put(POSITION_TOP, DIRECTION_UP);
|
||||
INVALID_DIRECTIONS.put(POSITION_START, DIRECTION_START);
|
||||
INVALID_DIRECTIONS.put(POSITION_END, DIRECTION_END);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@link ComplicationLayoutParams}.
|
||||
* @param width The width {@link android.view.View.MeasureSpec} for the view.
|
||||
* @param height The height {@link android.view.View.MeasureSpec} for the view.
|
||||
* @param position The place within the parent container where the view should be positioned.
|
||||
* @param direction The direction the view should be laid out from either the parent container
|
||||
* or preceding view.
|
||||
* @param weight The weight that should be considered for this view when compared to other
|
||||
* views. This has an impact on the placement of the view but not the rendering of
|
||||
* the view.
|
||||
*/
|
||||
public ComplicationLayoutParams(int width, int height, @Position int position,
|
||||
@Direction int direction, int weight) {
|
||||
super(width, height);
|
||||
|
||||
if (!validatePosition(position)) {
|
||||
throw new IllegalArgumentException("invalid position:" + position);
|
||||
}
|
||||
mPosition = position;
|
||||
|
||||
if (!validateDirection(position, direction)) {
|
||||
throw new IllegalArgumentException("invalid direction:" + direction);
|
||||
}
|
||||
|
||||
mDirection = direction;
|
||||
|
||||
mWeight = weight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs {@link ComplicationLayoutParams} from an existing instance.
|
||||
*/
|
||||
public ComplicationLayoutParams(ComplicationLayoutParams source) {
|
||||
super(source);
|
||||
mPosition = source.mPosition;
|
||||
mDirection = source.mDirection;
|
||||
mWeight = source.mWeight;
|
||||
}
|
||||
|
||||
private static boolean validateDirection(@Position int position, @Direction int direction) {
|
||||
for (int currentPosition = FIRST_POSITION; currentPosition <= LAST_POSITION;
|
||||
currentPosition <<= 1) {
|
||||
if ((position & currentPosition) == currentPosition
|
||||
&& INVALID_DIRECTIONS.containsKey(currentPosition)
|
||||
&& (direction & INVALID_DIRECTIONS.get(currentPosition)) != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates over the defined positions and invokes the specified {@link Consumer} for each
|
||||
* position specified for this {@link ComplicationLayoutParams}.
|
||||
*/
|
||||
public void iteratePositions(Consumer<Integer> consumer) {
|
||||
for (int currentPosition = FIRST_POSITION; currentPosition <= LAST_POSITION;
|
||||
currentPosition <<= 1) {
|
||||
if ((mPosition & currentPosition) == currentPosition) {
|
||||
consumer.accept(currentPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean validatePosition(@Position int position) {
|
||||
if (position == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int combination : INVALID_POSITIONS) {
|
||||
if ((position & combination) == combination) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Direction
|
||||
public int getDirection() {
|
||||
return mDirection;
|
||||
}
|
||||
|
||||
@Position
|
||||
public int getPosition() {
|
||||
return mPosition;
|
||||
}
|
||||
|
||||
public int getWeight() {
|
||||
return mWeight;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.systemui.dreams.complication;
|
||||
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* {@link ComplicationViewModel} is an abstraction over {@link Complication}, providing the model
|
||||
* from which any view-related interpretation of the {@link Complication} should be derived from.
|
||||
*/
|
||||
public class ComplicationViewModel extends ViewModel {
|
||||
private final Complication mComplication;
|
||||
private final ComplicationId mId;
|
||||
private final Complication.Host mHost;
|
||||
|
||||
/**
|
||||
* Default constructor for generating a {@link ComplicationViewModel}.
|
||||
* @param complication The {@link Complication} represented by the view model.
|
||||
* @param id The {@link ComplicationId} tied to this {@link Complication}.
|
||||
* @param host The environment {@link Complication.Host}.
|
||||
*/
|
||||
@Inject
|
||||
public ComplicationViewModel(Complication complication, ComplicationId id,
|
||||
Complication.Host host) {
|
||||
mComplication = complication;
|
||||
mId = id;
|
||||
mHost = host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link ComplicationId} for this {@link Complication} for stable id association.
|
||||
*/
|
||||
public ComplicationId getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying {@link Complication}. Should only as a redirection - for example,
|
||||
* using the {@link Complication} to generate view. Any property should be surfaced through
|
||||
* this ViewModel.
|
||||
*/
|
||||
public Complication getComplication() {
|
||||
return mComplication;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests the dream exit on behalf of the {@link Complication}.
|
||||
*/
|
||||
public void exitDream() {
|
||||
mHost.requestExitDream();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.systemui.dreams.complication;
|
||||
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.lifecycle.ViewModelStore;
|
||||
|
||||
import com.android.systemui.dreams.complication.dagger.DaggerViewModelProviderFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* An intermediary to generate {@link ComplicationViewModel} tracked with a {@link ViewModelStore}.
|
||||
*/
|
||||
public class ComplicationViewModelProvider extends ViewModelProvider {
|
||||
@Inject
|
||||
public ComplicationViewModelProvider(ViewModelStore store, ComplicationViewModel viewModel) {
|
||||
super(store, new DaggerViewModelProviderFactory(() -> viewModel));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.dreams.complication;
|
||||
|
||||
import com.android.systemui.dreams.complication.dagger.ComplicationViewModelComponent;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* The {@link ComplicationViewModelTransformer} responsibility is provide a mapping from
|
||||
* {@link Complication} to {@link ComplicationViewModel}.
|
||||
*/
|
||||
public class ComplicationViewModelTransformer {
|
||||
private final ComplicationId.Factory mComplicationIdFactory = new ComplicationId.Factory();
|
||||
private final HashMap<Complication, ComplicationId> mComplicationIdMapping = new HashMap<>();
|
||||
private final ComplicationViewModelComponent.Factory mViewModelComponentFactory;
|
||||
|
||||
@Inject
|
||||
public ComplicationViewModelTransformer(
|
||||
ComplicationViewModelComponent.Factory viewModelComponentFactory) {
|
||||
mViewModelComponentFactory = viewModelComponentFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates {@link ComplicationViewModel} from a {@link Complication}.
|
||||
*/
|
||||
public ComplicationViewModel getViewModel(Complication complication) {
|
||||
final ComplicationId id = getComplicationId(complication);
|
||||
return mViewModelComponentFactory.create(complication, id)
|
||||
.getViewModelProvider().get(id.toString(), ComplicationViewModel.class);
|
||||
}
|
||||
|
||||
private ComplicationId getComplicationId(Complication complication) {
|
||||
if (!mComplicationIdMapping.containsKey(complication)) {
|
||||
mComplicationIdMapping.put(complication, mComplicationIdFactory.getNextId());
|
||||
}
|
||||
|
||||
return mComplicationIdMapping.get(complication);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.dreams.complication.dagger;
|
||||
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.dreams.complication.ComplicationHostViewController;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Scope;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.Subcomponent;
|
||||
|
||||
/**
|
||||
* {@link ComplicationHostViewComponent} encapsulates the shared logic around the host view layer
|
||||
* for complications. Anything that references the layout should be provided through this component
|
||||
* and its child module. The factory should be used in order to best tie the lifetime of the view
|
||||
* to components.
|
||||
*/
|
||||
@Subcomponent(modules = {
|
||||
ComplicationHostViewComponent.ComplicationHostViewModule.class,
|
||||
})
|
||||
@ComplicationHostViewComponent.ComplicationHostViewScope
|
||||
public interface ComplicationHostViewComponent {
|
||||
String SCOPED_COMPLICATIONS_LAYOUT = "scoped_complications_layout";
|
||||
|
||||
/** Scope annotation for singleton items within {@link ComplicationHostViewComponent}. */
|
||||
@Documented
|
||||
@Retention(RUNTIME)
|
||||
@Scope
|
||||
@interface ComplicationHostViewScope {}
|
||||
|
||||
/**
|
||||
* Factory for generating a new scoped component.
|
||||
*/
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
ComplicationHostViewComponent create();
|
||||
}
|
||||
|
||||
/** */
|
||||
ComplicationHostViewController getController();
|
||||
|
||||
/**
|
||||
* Module for providing a scoped host view.
|
||||
*/
|
||||
@Module
|
||||
abstract class ComplicationHostViewModule {
|
||||
/**
|
||||
* Generates a {@link ConstraintLayout}, which can host
|
||||
* {@link com.android.systemui.dreams.complication.Complication} instances.
|
||||
*/
|
||||
@Provides
|
||||
@Named(SCOPED_COMPLICATIONS_LAYOUT)
|
||||
@ComplicationHostViewScope
|
||||
static ConstraintLayout providesComplicationHostView(
|
||||
LayoutInflater layoutInflater) {
|
||||
return Preconditions.checkNotNull((ConstraintLayout)
|
||||
layoutInflater.inflate(R.layout.dream_overlay_complications_layer,
|
||||
null),
|
||||
"R.layout.dream_overlay_complications_layer did not properly inflated");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.dreams.complication.dagger;
|
||||
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.lifecycle.ViewModelStore;
|
||||
|
||||
import com.android.systemui.dreams.complication.ComplicationCollectionViewModel;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Scope;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
/**
|
||||
* Module for housing components related to rendering complications.
|
||||
*/
|
||||
@Module(subcomponents = {
|
||||
ComplicationViewModelComponent.class,
|
||||
ComplicationHostViewComponent.class,
|
||||
})
|
||||
public interface ComplicationModule {
|
||||
String SCOPED_COMPLICATIONS_MODEL = "scoped_complications_model";
|
||||
|
||||
/** Scope annotation for singleton items within the {@link ComplicationModule}. */
|
||||
@Documented
|
||||
@Retention(RUNTIME)
|
||||
@Scope
|
||||
@interface ComplicationScope {}
|
||||
|
||||
/**
|
||||
* The complication collection is provided through this way to ensure that the instances are
|
||||
* tied to the {@link ViewModelStore}.
|
||||
*/
|
||||
@Provides
|
||||
@Named(SCOPED_COMPLICATIONS_MODEL)
|
||||
static ComplicationCollectionViewModel providesComplicationCollectionViewModel(
|
||||
ViewModelStore store, ComplicationCollectionViewModel viewModel) {
|
||||
final ViewModelProvider provider = new ViewModelProvider(store,
|
||||
new DaggerViewModelProviderFactory(() -> viewModel));
|
||||
|
||||
return provider.get(ComplicationCollectionViewModel.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.systemui.dreams.complication.dagger;
|
||||
|
||||
import com.android.systemui.dreams.complication.Complication;
|
||||
import com.android.systemui.dreams.complication.ComplicationId;
|
||||
import com.android.systemui.dreams.complication.ComplicationViewModelProvider;
|
||||
|
||||
import dagger.BindsInstance;
|
||||
import dagger.Subcomponent;
|
||||
|
||||
/**
|
||||
* The {@link ComplicationViewModelComponent} allows for a
|
||||
* {@link com.android.systemui.dreams.complication.ComplicationViewModel} for a particular
|
||||
* {@link Complication}. This component binds these instance specific values to allow injection with
|
||||
* values provided at the wider scope.
|
||||
*/
|
||||
@Subcomponent
|
||||
public interface ComplicationViewModelComponent {
|
||||
/**
|
||||
* Factory for generating {@link ComplicationViewModelComponent}.
|
||||
*/
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
ComplicationViewModelComponent create(@BindsInstance Complication complication,
|
||||
@BindsInstance ComplicationId id);
|
||||
}
|
||||
|
||||
/** */
|
||||
ComplicationViewModelProvider getViewModelProvider();
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.dreams.complication.dagger;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
/**
|
||||
* {@link DaggerViewModelProviderFactory} is a wrapper around a lambda allowing for uses of Dagger
|
||||
* component.
|
||||
*/
|
||||
public class DaggerViewModelProviderFactory implements ViewModelProvider.Factory {
|
||||
/**
|
||||
* An interface for providing a {@link ViewModel} through
|
||||
* {@link DaggerViewModelProviderFactory}.
|
||||
*/
|
||||
public interface ViewModelCreator {
|
||||
/**
|
||||
* Creates a {@link ViewModel} to be returned.
|
||||
*/
|
||||
ViewModel create();
|
||||
}
|
||||
|
||||
private final ViewModelCreator mCreator;
|
||||
|
||||
public DaggerViewModelProviderFactory(ViewModelCreator creator) {
|
||||
mCreator = creator;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public <T extends ViewModel> T create(@NonNull Class<T> aClass) {
|
||||
return (T) mCreator.create();
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import dagger.Module;
|
||||
* Dagger Module providing Communal-related functionality.
|
||||
*/
|
||||
@Module(subcomponents = {
|
||||
DreamOverlayComponent.class})
|
||||
DreamOverlayComponent.class,
|
||||
})
|
||||
public interface DreamModule {
|
||||
}
|
||||
@@ -18,25 +18,36 @@ package com.android.systemui.dreams.dagger;
|
||||
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.LifecycleRegistry;
|
||||
import androidx.lifecycle.ViewModelStore;
|
||||
|
||||
import com.android.systemui.dreams.DreamOverlayContainerViewController;
|
||||
import com.android.systemui.dreams.complication.Complication;
|
||||
import com.android.systemui.dreams.complication.dagger.ComplicationModule;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
|
||||
import javax.inject.Scope;
|
||||
|
||||
import dagger.BindsInstance;
|
||||
import dagger.Subcomponent;
|
||||
|
||||
/**
|
||||
* Dagger subcomponent for {@link DreamOverlayModule}.
|
||||
*/
|
||||
@Subcomponent(modules = {DreamOverlayModule.class})
|
||||
@Subcomponent(modules = {
|
||||
DreamOverlayModule.class,
|
||||
ComplicationModule.class,
|
||||
})
|
||||
@DreamOverlayComponent.DreamOverlayScope
|
||||
public interface DreamOverlayComponent {
|
||||
/** Simple factory for {@link DreamOverlayComponent}. */
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
DreamOverlayComponent create();
|
||||
DreamOverlayComponent create(@BindsInstance ViewModelStore store,
|
||||
@BindsInstance Complication.Host host);
|
||||
}
|
||||
|
||||
/** Scope annotation for singleton items within the {@link DreamOverlayComponent}. */
|
||||
@@ -46,6 +57,11 @@ public interface DreamOverlayComponent {
|
||||
@interface DreamOverlayScope {}
|
||||
|
||||
/** Builds a {@link DreamOverlayContainerViewController}. */
|
||||
@DreamOverlayScope
|
||||
DreamOverlayContainerViewController getDreamOverlayContainerViewController();
|
||||
|
||||
/** Builds a {@link androidx.lifecycle.LifecycleRegistry} */
|
||||
LifecycleRegistry getLifecycleRegistry();
|
||||
|
||||
/** Builds a {@link androidx.lifecycle.LifecycleOwner} */
|
||||
LifecycleOwner getLifecycleOwner();
|
||||
}
|
||||
|
||||
@@ -22,6 +22,9 @@ import android.os.Handler;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.LifecycleRegistry;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.battery.BatteryMeterView;
|
||||
@@ -36,6 +39,7 @@ import com.android.systemui.tuner.TunerService;
|
||||
|
||||
import javax.inject.Named;
|
||||
|
||||
import dagger.Lazy;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
@@ -124,4 +128,16 @@ public abstract class DreamOverlayModule {
|
||||
return resources.getInteger(
|
||||
R.integer.config_dreamOverlayBurnInProtectionUpdateIntervalMillis);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@DreamOverlayComponent.DreamOverlayScope
|
||||
static LifecycleOwner providesLifecycleOwner(Lazy<LifecycleRegistry> lifecycleRegistryLazy) {
|
||||
return () -> lifecycleRegistryLazy.get();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@DreamOverlayComponent.DreamOverlayScope
|
||||
static LifecycleRegistry providesLifecycleRegistry(LifecycleOwner lifecycleOwner) {
|
||||
return new LifecycleRegistry(lifecycleOwner);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.dreams;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import android.content.Context;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.settingslib.dream.DreamBackend;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
public class ComplicationProviderTest {
|
||||
private TestComplicationProvider mComplicationProvider;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
mComplicationProvider = new TestComplicationProvider();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertComplicationType() {
|
||||
assertEquals(ComplicationProvider.COMPLICATION_TYPE_TIME,
|
||||
mComplicationProvider.convertComplicationType(DreamBackend.COMPLICATION_TYPE_TIME));
|
||||
assertEquals(ComplicationProvider.COMPLICATION_TYPE_DATE,
|
||||
mComplicationProvider.convertComplicationType(DreamBackend.COMPLICATION_TYPE_DATE));
|
||||
assertEquals(ComplicationProvider.COMPLICATION_TYPE_WEATHER,
|
||||
mComplicationProvider.convertComplicationType(
|
||||
DreamBackend.COMPLICATION_TYPE_WEATHER));
|
||||
assertEquals(ComplicationProvider.COMPLICATION_TYPE_AIR_QUALITY,
|
||||
mComplicationProvider.convertComplicationType(
|
||||
DreamBackend.COMPLICATION_TYPE_AIR_QUALITY));
|
||||
assertEquals(ComplicationProvider.COMPLICATION_TYPE_CAST_INFO,
|
||||
mComplicationProvider.convertComplicationType(
|
||||
DreamBackend.COMPLICATION_TYPE_CAST_INFO));
|
||||
}
|
||||
|
||||
private static class TestComplicationProvider implements ComplicationProvider {
|
||||
@Override
|
||||
public void onCreateComplication(Context context,
|
||||
ComplicationHost.CreationCallback creationCallback,
|
||||
ComplicationHost.InteractionCallback interactionCallback) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,15 +27,15 @@ import static org.mockito.Mockito.when;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Handler;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.dreams.complication.ComplicationHostViewController;
|
||||
import com.android.systemui.dreams.complication.dagger.ComplicationHostViewComponent;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -63,6 +63,15 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
|
||||
@Mock
|
||||
DreamOverlayContainerView mDreamOverlayContainerView;
|
||||
|
||||
@Mock
|
||||
ComplicationHostViewController mComplicationHostViewController;
|
||||
|
||||
@Mock
|
||||
ComplicationHostViewComponent.Factory mComplicationHostViewComponentFactory;
|
||||
|
||||
@Mock
|
||||
ComplicationHostViewComponent mComplicationHostViewComponent;
|
||||
|
||||
@Mock
|
||||
ViewGroup mDreamOverlayContentView;
|
||||
|
||||
@@ -80,9 +89,14 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
|
||||
DREAM_OVERLAY_NOTIFICATIONS_DRAG_AREA_HEIGHT);
|
||||
when(mDreamOverlayContainerView.getResources()).thenReturn(mResources);
|
||||
when(mDreamOverlayContainerView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
|
||||
when(mComplicationHostViewComponentFactory.create())
|
||||
.thenReturn(mComplicationHostViewComponent);
|
||||
when(mComplicationHostViewComponent.getController())
|
||||
.thenReturn(mComplicationHostViewController);
|
||||
|
||||
mController = new DreamOverlayContainerViewController(
|
||||
mDreamOverlayContainerView,
|
||||
mComplicationHostViewComponentFactory,
|
||||
mDreamOverlayContentView,
|
||||
mDreamOverlayStatusBarViewController,
|
||||
mHandler,
|
||||
@@ -103,20 +117,6 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
|
||||
DREAM_OVERLAY_NOTIFICATIONS_DRAG_AREA_HEIGHT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddOverlayAddsOverlayToContentView() {
|
||||
View overlay = new View(getContext());
|
||||
ConstraintLayout.LayoutParams layoutParams = new ConstraintLayout.LayoutParams(100, 100);
|
||||
mController.addOverlay(overlay, layoutParams);
|
||||
verify(mDreamOverlayContentView).addView(overlay, layoutParams);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveAllOverlaysRemovesOverlaysFromContentView() {
|
||||
mController.removeAllOverlays();
|
||||
verify(mDreamOverlayContentView).removeAllViews();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnViewAttachedRegistersComputeInsetsListener() {
|
||||
mController.onViewAttached();
|
||||
|
||||
@@ -28,12 +28,11 @@ import android.service.dreams.DreamService;
|
||||
import android.service.dreams.IDreamOverlay;
|
||||
import android.service.dreams.IDreamOverlayCallback;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.view.WindowManagerImpl;
|
||||
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.LifecycleRegistry;
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
@@ -48,18 +47,21 @@ import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
public class DreamOverlayServiceTest extends SysuiTestCase {
|
||||
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
|
||||
private final FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
|
||||
|
||||
@Mock
|
||||
LifecycleOwner mLifecycleOwner;
|
||||
|
||||
@Mock
|
||||
LifecycleRegistry mLifecycleRegistry;
|
||||
|
||||
@Rule
|
||||
public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck();
|
||||
|
||||
@@ -75,12 +77,6 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
|
||||
@Mock
|
||||
WindowManagerImpl mWindowManager;
|
||||
|
||||
@Mock
|
||||
ComplicationProvider mProvider;
|
||||
|
||||
@Mock
|
||||
DreamOverlayStateController mDreamOverlayStateController;
|
||||
|
||||
@Mock
|
||||
DreamOverlayComponent.Factory mDreamOverlayComponentFactory;
|
||||
|
||||
@@ -102,13 +98,18 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
|
||||
|
||||
when(mDreamOverlayComponent.getDreamOverlayContainerViewController())
|
||||
.thenReturn(mDreamOverlayContainerViewController);
|
||||
when(mDreamOverlayComponentFactory.create())
|
||||
when(mDreamOverlayComponent.getLifecycleOwner())
|
||||
.thenReturn(mLifecycleOwner);
|
||||
when(mDreamOverlayComponent.getLifecycleRegistry())
|
||||
.thenReturn(mLifecycleRegistry);
|
||||
when(mDreamOverlayComponentFactory
|
||||
.create(any(), any()))
|
||||
.thenReturn(mDreamOverlayComponent);
|
||||
when(mDreamOverlayContainerViewController.getContainerView())
|
||||
.thenReturn(mDreamOverlayContainerView);
|
||||
|
||||
mService = new DreamOverlayService(mContext, mMainExecutor,
|
||||
mDreamOverlayStateController, mDreamOverlayComponentFactory);
|
||||
mDreamOverlayComponentFactory);
|
||||
final IBinder proxy = mService.onBind(new Intent());
|
||||
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
|
||||
|
||||
@@ -127,78 +128,6 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
|
||||
verify(mDreamOverlayContainerViewController).init();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddingOverlayToDream() throws Exception {
|
||||
// Add overlay.
|
||||
mService.addComplication(mProvider);
|
||||
mMainExecutor.runAllReady();
|
||||
|
||||
final ArgumentCaptor<ComplicationHost.CreationCallback> creationCallbackCapture =
|
||||
ArgumentCaptor.forClass(ComplicationHost.CreationCallback.class);
|
||||
final ArgumentCaptor<ComplicationHost.InteractionCallback> interactionCallbackCapture =
|
||||
ArgumentCaptor.forClass(ComplicationHost.InteractionCallback.class);
|
||||
|
||||
// Ensure overlay provider is asked to create view.
|
||||
verify(mProvider).onCreateComplication(any(), creationCallbackCapture.capture(),
|
||||
interactionCallbackCapture.capture());
|
||||
mMainExecutor.runAllReady();
|
||||
|
||||
// Inform service of overlay view creation.
|
||||
final View view = new View(mContext);
|
||||
final ConstraintLayout.LayoutParams lp = new ConstraintLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
|
||||
);
|
||||
creationCallbackCapture.getValue().onCreated(view, lp);
|
||||
mMainExecutor.runAllReady();
|
||||
|
||||
// Verify that DreamOverlayContainerViewController is asked to add an overlay for the view.
|
||||
verify(mDreamOverlayContainerViewController).addOverlay(view, lp);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDreamOverlayExit() throws Exception {
|
||||
// Add overlay.
|
||||
mService.addComplication(mProvider);
|
||||
mMainExecutor.runAllReady();
|
||||
|
||||
// Capture interaction callback from overlay creation.
|
||||
final ArgumentCaptor<ComplicationHost.InteractionCallback> interactionCallbackCapture =
|
||||
ArgumentCaptor.forClass(ComplicationHost.InteractionCallback.class);
|
||||
verify(mProvider).onCreateComplication(any(), any(), interactionCallbackCapture.capture());
|
||||
|
||||
// Ask service to exit.
|
||||
interactionCallbackCapture.getValue().onExit();
|
||||
mMainExecutor.runAllReady();
|
||||
|
||||
// Ensure service informs dream host of exit.
|
||||
verify(mDreamOverlayCallback).onExitRequested();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListenerRegisteredWithDreamOverlayStateController() {
|
||||
// Verify overlay service registered as listener with DreamOverlayStateController
|
||||
// and inform callback of addition.
|
||||
final ArgumentCaptor<DreamOverlayStateController.Callback> callbackCapture =
|
||||
ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
|
||||
|
||||
verify(mDreamOverlayStateController).addCallback(callbackCapture.capture());
|
||||
when(mDreamOverlayStateController.getComplications()).thenReturn(Arrays.asList(mProvider));
|
||||
callbackCapture.getValue().onComplicationsChanged();
|
||||
mMainExecutor.runAllReady();
|
||||
|
||||
// Verify provider is asked to create overlay.
|
||||
verify(mProvider).onCreateComplication(any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnDestroyRemovesOverlayStateCallback() {
|
||||
final ArgumentCaptor<DreamOverlayStateController.Callback> callbackCapture =
|
||||
ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
|
||||
verify(mDreamOverlayStateController).addCallback(callbackCapture.capture());
|
||||
mService.onDestroy();
|
||||
verify(mDreamOverlayStateController).removeCallback(callbackCapture.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldShowComplicationsTrueByDefault() {
|
||||
assertThat(mService.shouldShowComplications()).isTrue();
|
||||
|
||||
@@ -27,11 +27,10 @@ import android.testing.AndroidTestingRunner;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.dreams.complication.Complication;
|
||||
import com.android.systemui.util.concurrency.FakeExecutor;
|
||||
import com.android.systemui.util.time.FakeSystemClock;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -47,7 +46,7 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase {
|
||||
DreamOverlayStateController.Callback mCallback;
|
||||
|
||||
@Mock
|
||||
ComplicationProvider mProvider;
|
||||
Complication mComplication;
|
||||
|
||||
final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
|
||||
|
||||
@@ -63,24 +62,23 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase {
|
||||
stateController.addCallback(mCallback);
|
||||
|
||||
// Add complication and verify callback is notified.
|
||||
final ListenableFuture<DreamOverlayStateController.ComplicationToken> tokenFuture =
|
||||
stateController.addComplication(mProvider);
|
||||
stateController.addComplication(mComplication);
|
||||
|
||||
mExecutor.runAllReady();
|
||||
|
||||
verify(mCallback, times(1)).onComplicationsChanged();
|
||||
|
||||
final Collection<ComplicationProvider> providers = stateController.getComplications();
|
||||
assertEquals(providers.size(), 1);
|
||||
assertTrue(providers.contains(mProvider));
|
||||
final Collection<Complication> complications = stateController.getComplications();
|
||||
assertEquals(complications.size(), 1);
|
||||
assertTrue(complications.contains(mComplication));
|
||||
|
||||
clearInvocations(mCallback);
|
||||
|
||||
// Remove complication and verify callback is notified.
|
||||
stateController.removeComplication(tokenFuture.get());
|
||||
stateController.removeComplication(mComplication);
|
||||
mExecutor.runAllReady();
|
||||
verify(mCallback, times(1)).onComplicationsChanged();
|
||||
assertTrue(providers.isEmpty());
|
||||
assertTrue(stateController.getComplications().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -88,7 +86,7 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase {
|
||||
final DreamOverlayStateController stateController =
|
||||
new DreamOverlayStateController(mExecutor);
|
||||
|
||||
stateController.addComplication(mProvider);
|
||||
stateController.addComplication(mComplication);
|
||||
mExecutor.runAllReady();
|
||||
|
||||
// Verify callback occurs on add when an overlay is already present.
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.dreams.complication;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.dreams.DreamOverlayStateController;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@TestableLooper.RunWithLooper
|
||||
public class ComplicationCollectionLiveDataTest extends SysuiTestCase {
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
allowTestableLooperAsMainThread();
|
||||
}
|
||||
|
||||
@Test
|
||||
/**
|
||||
* Ensures registration and callback lifecycles are respected.
|
||||
*/
|
||||
public void testLifecycle() {
|
||||
getContext().getMainExecutor().execute(() -> {
|
||||
final DreamOverlayStateController stateController =
|
||||
Mockito.mock(DreamOverlayStateController.class);
|
||||
final ComplicationCollectionLiveData liveData =
|
||||
new ComplicationCollectionLiveData(stateController);
|
||||
final HashSet<Complication> complications = new HashSet<>();
|
||||
final Observer<Collection<Complication>> observer = Mockito.mock(Observer.class);
|
||||
complications.add(Mockito.mock(Complication.class));
|
||||
|
||||
when(stateController.getComplications()).thenReturn(complications);
|
||||
|
||||
liveData.observeForever(observer);
|
||||
ArgumentCaptor<DreamOverlayStateController.Callback> callbackCaptor =
|
||||
ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
|
||||
|
||||
verify(stateController).addCallback(callbackCaptor.capture());
|
||||
verifyUpdate(observer, complications);
|
||||
|
||||
complications.add(Mockito.mock(Complication.class));
|
||||
callbackCaptor.getValue().onComplicationsChanged();
|
||||
|
||||
verifyUpdate(observer, complications);
|
||||
});
|
||||
}
|
||||
|
||||
void verifyUpdate(Observer<Collection<Complication>> observer,
|
||||
Collection<Complication> targetCollection) {
|
||||
ArgumentCaptor<Collection<Complication>> collectionCaptor =
|
||||
ArgumentCaptor.forClass(Collection.class);
|
||||
|
||||
verify(observer).onChanged(collectionCaptor.capture());
|
||||
|
||||
assertThat(collectionCaptor.getValue().equals(targetCollection)).isTrue();
|
||||
Mockito.clearInvocations(observer);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.systemui.dreams.complication;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
public class ComplicationHostViewControllerTest extends SysuiTestCase {
|
||||
@Mock
|
||||
ConstraintLayout mComplicationHostView;
|
||||
|
||||
@Mock
|
||||
LifecycleOwner mLifecycleOwner;
|
||||
|
||||
@Mock
|
||||
LiveData<Collection<ComplicationViewModel>> mComplicationViewModelLiveData;
|
||||
|
||||
@Mock
|
||||
ComplicationCollectionViewModel mViewModel;
|
||||
|
||||
@Mock
|
||||
ComplicationViewModel mComplicationViewModel;
|
||||
|
||||
@Mock
|
||||
ComplicationLayoutEngine mLayoutEngine;
|
||||
|
||||
@Mock
|
||||
ComplicationId mComplicationId;
|
||||
|
||||
@Mock
|
||||
Complication mComplication;
|
||||
|
||||
@Mock
|
||||
Complication.ViewHolder mViewHolder;
|
||||
|
||||
@Mock
|
||||
View mComplicationView;
|
||||
|
||||
@Mock
|
||||
ComplicationLayoutParams mComplicationLayoutParams;
|
||||
|
||||
@Complication.Category
|
||||
static final int COMPLICATION_CATEGORY = Complication.CATEGORY_SYSTEM;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
when(mViewModel.getComplications()).thenReturn(mComplicationViewModelLiveData);
|
||||
when(mComplicationViewModel.getId()).thenReturn(mComplicationId);
|
||||
when(mComplicationViewModel.getComplication()).thenReturn(mComplication);
|
||||
when(mComplication.createView(eq(mComplicationViewModel))).thenReturn(mViewHolder);
|
||||
when(mViewHolder.getView()).thenReturn(mComplicationView);
|
||||
when(mViewHolder.getCategory()).thenReturn(COMPLICATION_CATEGORY);
|
||||
when(mViewHolder.getLayoutParams()).thenReturn(mComplicationLayoutParams);
|
||||
when(mComplicationView.getParent()).thenReturn(mComplicationHostView);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the lifecycle of complications is properly handled.
|
||||
*/
|
||||
@Test
|
||||
public void testViewModelObservation() {
|
||||
final ArgumentCaptor<Observer<Collection<ComplicationViewModel>>> observerArgumentCaptor =
|
||||
ArgumentCaptor.forClass(Observer.class);
|
||||
final ComplicationHostViewController controller = new ComplicationHostViewController(
|
||||
mComplicationHostView,
|
||||
mLayoutEngine,
|
||||
mLifecycleOwner,
|
||||
mViewModel);
|
||||
|
||||
controller.init();
|
||||
|
||||
verify(mComplicationViewModelLiveData).observe(eq(mLifecycleOwner),
|
||||
observerArgumentCaptor.capture());
|
||||
|
||||
final Observer<Collection<ComplicationViewModel>> observer =
|
||||
observerArgumentCaptor.getValue();
|
||||
|
||||
// Add complication and ensure it is added to the view.
|
||||
final HashSet<ComplicationViewModel> complications = new HashSet<>(
|
||||
Arrays.asList(mComplicationViewModel));
|
||||
observer.onChanged(complications);
|
||||
|
||||
verify(mLayoutEngine).addComplication(eq(mComplicationId), eq(mComplicationView),
|
||||
eq(mComplicationLayoutParams), eq(COMPLICATION_CATEGORY));
|
||||
|
||||
// Remove complication and ensure it is removed from the view by id.
|
||||
observer.onChanged(new HashSet<>());
|
||||
|
||||
verify(mLayoutEngine).removeComplication(eq(mComplicationId));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.systemui.dreams.complication;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
public class ComplicationLayoutEngineTest extends SysuiTestCase {
|
||||
@Mock
|
||||
ConstraintLayout mLayout;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
}
|
||||
|
||||
private static class ViewInfo {
|
||||
private static int sNextId = 1;
|
||||
public final ComplicationId id;
|
||||
public final View view;
|
||||
public final ComplicationLayoutParams lp;
|
||||
|
||||
@Complication.Category
|
||||
public final int category;
|
||||
|
||||
private static ComplicationId.Factory sFactory = new ComplicationId.Factory();
|
||||
|
||||
ViewInfo(ComplicationLayoutParams params, @Complication.Category int category,
|
||||
ConstraintLayout layout) {
|
||||
this.lp = params;
|
||||
this.category = category;
|
||||
this.view = Mockito.mock(View.class);
|
||||
this.id = sFactory.getNextId();
|
||||
when(view.getId()).thenReturn(sNextId++);
|
||||
when(view.getParent()).thenReturn(layout);
|
||||
}
|
||||
|
||||
void clearInvocations() {
|
||||
Mockito.clearInvocations(view);
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyChange(ViewInfo viewInfo,
|
||||
boolean verifyAdd,
|
||||
Consumer<ConstraintLayout.LayoutParams> paramConsumer) {
|
||||
ArgumentCaptor<ConstraintLayout.LayoutParams> lpCaptor =
|
||||
ArgumentCaptor.forClass(ConstraintLayout.LayoutParams.class);
|
||||
verify(viewInfo.view).setLayoutParams(lpCaptor.capture());
|
||||
|
||||
if (verifyAdd) {
|
||||
verify(mLayout).addView(eq(viewInfo.view));
|
||||
}
|
||||
|
||||
ConstraintLayout.LayoutParams capturedParams = lpCaptor.getValue();
|
||||
paramConsumer.accept(capturedParams);
|
||||
}
|
||||
|
||||
private void addComplication(ComplicationLayoutEngine engine, ViewInfo info) {
|
||||
engine.addComplication(info.id, info.view, info.lp, info.category);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure the engine properly places a view within the {@link ConstraintLayout}.
|
||||
*/
|
||||
@Test
|
||||
public void testSingleLayout() {
|
||||
final ViewInfo firstViewInfo = new ViewInfo(
|
||||
new ComplicationLayoutParams(
|
||||
100,
|
||||
100,
|
||||
ComplicationLayoutParams.POSITION_TOP
|
||||
| ComplicationLayoutParams.POSITION_END,
|
||||
ComplicationLayoutParams.DIRECTION_DOWN,
|
||||
0),
|
||||
Complication.CATEGORY_STANDARD,
|
||||
mLayout);
|
||||
|
||||
final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout);
|
||||
addComplication(engine, firstViewInfo);
|
||||
|
||||
// Ensure the view is added to the top end corner
|
||||
verifyChange(firstViewInfo, true, lp -> {
|
||||
assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
|
||||
assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures layout in a particular direction updates.
|
||||
*/
|
||||
@Test
|
||||
public void testDirectionLayout() {
|
||||
final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout);
|
||||
|
||||
final ViewInfo firstViewInfo = new ViewInfo(
|
||||
new ComplicationLayoutParams(
|
||||
100,
|
||||
100,
|
||||
ComplicationLayoutParams.POSITION_TOP
|
||||
| ComplicationLayoutParams.POSITION_END,
|
||||
ComplicationLayoutParams.DIRECTION_DOWN,
|
||||
0),
|
||||
Complication.CATEGORY_STANDARD,
|
||||
mLayout);
|
||||
|
||||
addComplication(engine, firstViewInfo);
|
||||
|
||||
firstViewInfo.clearInvocations();
|
||||
|
||||
final ViewInfo secondViewInfo = new ViewInfo(
|
||||
new ComplicationLayoutParams(
|
||||
100,
|
||||
100,
|
||||
ComplicationLayoutParams.POSITION_TOP
|
||||
| ComplicationLayoutParams.POSITION_END,
|
||||
ComplicationLayoutParams.DIRECTION_DOWN,
|
||||
0),
|
||||
Complication.CATEGORY_SYSTEM,
|
||||
mLayout);
|
||||
|
||||
addComplication(engine, secondViewInfo);
|
||||
|
||||
// The first added view should now be underneath the second view.
|
||||
verifyChange(firstViewInfo, false, lp -> {
|
||||
assertThat(lp.topToBottom == secondViewInfo.view.getId()).isTrue();
|
||||
assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
|
||||
});
|
||||
|
||||
// The second view should be in the top position.
|
||||
verifyChange(secondViewInfo, true, lp -> {
|
||||
assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
|
||||
assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures layout in a particular position updates.
|
||||
*/
|
||||
@Test
|
||||
public void testPositionLayout() {
|
||||
final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout);
|
||||
|
||||
final ViewInfo firstViewInfo = new ViewInfo(
|
||||
new ComplicationLayoutParams(
|
||||
100,
|
||||
100,
|
||||
ComplicationLayoutParams.POSITION_TOP
|
||||
| ComplicationLayoutParams.POSITION_END,
|
||||
ComplicationLayoutParams.DIRECTION_DOWN,
|
||||
0),
|
||||
Complication.CATEGORY_STANDARD,
|
||||
mLayout);
|
||||
|
||||
addComplication(engine, firstViewInfo);
|
||||
|
||||
final ViewInfo secondViewInfo = new ViewInfo(
|
||||
new ComplicationLayoutParams(
|
||||
100,
|
||||
100,
|
||||
ComplicationLayoutParams.POSITION_TOP
|
||||
| ComplicationLayoutParams.POSITION_END,
|
||||
ComplicationLayoutParams.DIRECTION_DOWN,
|
||||
0),
|
||||
Complication.CATEGORY_SYSTEM,
|
||||
mLayout);
|
||||
|
||||
addComplication(engine, secondViewInfo);
|
||||
|
||||
firstViewInfo.clearInvocations();
|
||||
secondViewInfo.clearInvocations();
|
||||
|
||||
final ViewInfo thirdViewInfo = new ViewInfo(
|
||||
new ComplicationLayoutParams(
|
||||
100,
|
||||
100,
|
||||
ComplicationLayoutParams.POSITION_TOP
|
||||
| ComplicationLayoutParams.POSITION_END,
|
||||
ComplicationLayoutParams.DIRECTION_START,
|
||||
1),
|
||||
Complication.CATEGORY_SYSTEM,
|
||||
mLayout);
|
||||
|
||||
addComplication(engine, thirdViewInfo);
|
||||
|
||||
// The first added view should now be underneath the second view.
|
||||
verifyChange(firstViewInfo, false, lp -> {
|
||||
assertThat(lp.topToBottom == secondViewInfo.view.getId()).isTrue();
|
||||
assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
|
||||
});
|
||||
|
||||
// The second view should be in underneath the third view.
|
||||
verifyChange(secondViewInfo, false, lp -> {
|
||||
assertThat(lp.topToBottom == thirdViewInfo.view.getId()).isTrue();
|
||||
assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
|
||||
});
|
||||
|
||||
// The third view should be in at the top.
|
||||
verifyChange(thirdViewInfo, true, lp -> {
|
||||
assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
|
||||
assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
|
||||
});
|
||||
|
||||
final ViewInfo fourthViewInfo = new ViewInfo(
|
||||
new ComplicationLayoutParams(
|
||||
100,
|
||||
100,
|
||||
ComplicationLayoutParams.POSITION_TOP
|
||||
| ComplicationLayoutParams.POSITION_END,
|
||||
ComplicationLayoutParams.DIRECTION_START,
|
||||
1),
|
||||
Complication.CATEGORY_STANDARD,
|
||||
mLayout);
|
||||
|
||||
addComplication(engine, fourthViewInfo);
|
||||
|
||||
verifyChange(fourthViewInfo, true, lp -> {
|
||||
assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
|
||||
assertThat(lp.endToStart == thirdViewInfo.view.getId()).isTrue();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures layout in a particular position updates.
|
||||
*/
|
||||
@Test
|
||||
public void testRemoval() {
|
||||
final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout);
|
||||
|
||||
final ViewInfo firstViewInfo = new ViewInfo(
|
||||
new ComplicationLayoutParams(
|
||||
100,
|
||||
100,
|
||||
ComplicationLayoutParams.POSITION_TOP
|
||||
| ComplicationLayoutParams.POSITION_END,
|
||||
ComplicationLayoutParams.DIRECTION_DOWN,
|
||||
0),
|
||||
Complication.CATEGORY_STANDARD,
|
||||
mLayout);
|
||||
|
||||
engine.addComplication(firstViewInfo.id, firstViewInfo.view, firstViewInfo.lp,
|
||||
firstViewInfo.category);
|
||||
|
||||
final ViewInfo secondViewInfo = new ViewInfo(
|
||||
new ComplicationLayoutParams(
|
||||
100,
|
||||
100,
|
||||
ComplicationLayoutParams.POSITION_TOP
|
||||
| ComplicationLayoutParams.POSITION_END,
|
||||
ComplicationLayoutParams.DIRECTION_DOWN,
|
||||
0),
|
||||
Complication.CATEGORY_SYSTEM,
|
||||
mLayout);
|
||||
|
||||
engine.addComplication(secondViewInfo.id, secondViewInfo.view, secondViewInfo.lp,
|
||||
secondViewInfo.category);
|
||||
|
||||
firstViewInfo.clearInvocations();
|
||||
|
||||
engine.removeComplication(secondViewInfo.id);
|
||||
verify(mLayout).removeView(eq(secondViewInfo.view));
|
||||
|
||||
verifyChange(firstViewInfo, true, lp -> {
|
||||
assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
|
||||
assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.systemui.dreams.complication;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.testing.AndroidTestingRunner;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
public class ComplicationLayoutParamsTest extends SysuiTestCase {
|
||||
/**
|
||||
* Ensures ComplicationLayoutParams cannot be constructed with improper position or direction.
|
||||
*/
|
||||
@Test
|
||||
public void testPositionValidation() {
|
||||
final HashSet<Integer> invalidCombinations = new HashSet(Arrays.asList(
|
||||
ComplicationLayoutParams.POSITION_BOTTOM | ComplicationLayoutParams.POSITION_TOP,
|
||||
ComplicationLayoutParams.POSITION_END | ComplicationLayoutParams.POSITION_START
|
||||
));
|
||||
|
||||
final int allPositions = ComplicationLayoutParams.POSITION_TOP
|
||||
| ComplicationLayoutParams.POSITION_START
|
||||
| ComplicationLayoutParams.POSITION_END
|
||||
| ComplicationLayoutParams.POSITION_BOTTOM;
|
||||
|
||||
final HashSet<Integer> allDirections = new HashSet(Arrays.asList(
|
||||
ComplicationLayoutParams.DIRECTION_DOWN,
|
||||
ComplicationLayoutParams.DIRECTION_UP,
|
||||
ComplicationLayoutParams.DIRECTION_START,
|
||||
ComplicationLayoutParams.DIRECTION_END
|
||||
));
|
||||
|
||||
final HashMap<Integer, Integer> invalidDirections = new HashMap<>();
|
||||
invalidDirections.put(ComplicationLayoutParams.DIRECTION_DOWN,
|
||||
ComplicationLayoutParams.POSITION_BOTTOM);
|
||||
invalidDirections.put(ComplicationLayoutParams.DIRECTION_UP,
|
||||
ComplicationLayoutParams.POSITION_TOP);
|
||||
invalidDirections.put(ComplicationLayoutParams.DIRECTION_START,
|
||||
ComplicationLayoutParams.POSITION_START);
|
||||
invalidDirections.put(ComplicationLayoutParams.DIRECTION_END,
|
||||
ComplicationLayoutParams.POSITION_END);
|
||||
|
||||
|
||||
for (int position = 0; position <= allPositions; ++position) {
|
||||
boolean properPosition = position != 0;
|
||||
if (properPosition) {
|
||||
for (Integer combination : invalidCombinations) {
|
||||
if ((combination & position) == combination) {
|
||||
properPosition = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
boolean exceptionEncountered = false;
|
||||
for (Integer direction : allDirections) {
|
||||
final int invalidPosition = invalidDirections.get(direction);
|
||||
final boolean properDirection = (invalidPosition & position) != invalidPosition;
|
||||
|
||||
try {
|
||||
final ComplicationLayoutParams params = new ComplicationLayoutParams(
|
||||
100,
|
||||
100,
|
||||
position,
|
||||
direction,
|
||||
0);
|
||||
} catch (Exception e) {
|
||||
exceptionEncountered = true;
|
||||
}
|
||||
|
||||
assertThat((properPosition && properDirection) || exceptionEncountered).isTrue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures ComplicationLayoutParams is properly duplicated on copy construction.
|
||||
*/
|
||||
@Test
|
||||
public void testCopyConstruction() {
|
||||
final ComplicationLayoutParams params = new ComplicationLayoutParams(
|
||||
100,
|
||||
100,
|
||||
ComplicationLayoutParams.POSITION_TOP,
|
||||
ComplicationLayoutParams.DIRECTION_DOWN,
|
||||
3);
|
||||
final ComplicationLayoutParams copy = new ComplicationLayoutParams(params);
|
||||
|
||||
assertThat(copy.getDirection() == params.getDirection()).isTrue();
|
||||
assertThat(copy.getPosition() == params.getPosition()).isTrue();
|
||||
assertThat(copy.getWeight() == params.getWeight()).isTrue();
|
||||
assertThat(copy.height == params.height).isTrue();
|
||||
assertThat(copy.width == params.width).isTrue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.dreams.complication;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.testing.AndroidTestingRunner;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.dreams.complication.dagger.ComplicationViewModelComponent;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
public class ComplicationViewModelTransformerTest extends SysuiTestCase {
|
||||
@Mock
|
||||
ComplicationViewModelComponent.Factory mFactory;
|
||||
|
||||
@Mock
|
||||
ComplicationViewModelComponent mComponent;
|
||||
|
||||
@Mock
|
||||
ComplicationViewModelProvider mViewModelProvider;
|
||||
|
||||
@Mock
|
||||
ComplicationViewModel mViewModel;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
when(mFactory.create(Mockito.any(), Mockito.any())).thenReturn(mComponent);
|
||||
when(mComponent.getViewModelProvider()).thenReturn(mViewModelProvider);
|
||||
when(mViewModelProvider.get(Mockito.any(), Mockito.any())).thenReturn(mViewModel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the same id is returned for the same complication across invocations.
|
||||
*/
|
||||
@Test
|
||||
public void testStableIds() {
|
||||
final ComplicationViewModelTransformer transformer =
|
||||
new ComplicationViewModelTransformer(mFactory);
|
||||
|
||||
final Complication complication = Mockito.mock(Complication.class);
|
||||
|
||||
ArgumentCaptor<ComplicationId> idCaptor = ArgumentCaptor.forClass(ComplicationId.class);
|
||||
|
||||
transformer.getViewModel(complication);
|
||||
verify(mFactory).create(Mockito.any(), idCaptor.capture());
|
||||
final ComplicationId firstId = idCaptor.getValue();
|
||||
|
||||
Mockito.clearInvocations(mFactory);
|
||||
|
||||
transformer.getViewModel(complication);
|
||||
verify(mFactory).create(Mockito.any(), idCaptor.capture());
|
||||
final ComplicationId secondId = idCaptor.getValue();
|
||||
|
||||
assertEquals(secondId, firstId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure unique ids are assigned to different complications.
|
||||
*/
|
||||
@Test
|
||||
public void testUniqueIds() {
|
||||
final ComplicationViewModelTransformer transformer =
|
||||
new ComplicationViewModelTransformer(mFactory);
|
||||
|
||||
final Complication firstComplication = Mockito.mock(Complication.class);
|
||||
final Complication secondComplication = Mockito.mock(Complication.class);
|
||||
|
||||
ArgumentCaptor<ComplicationId> idCaptor = ArgumentCaptor.forClass(ComplicationId.class);
|
||||
|
||||
transformer.getViewModel(firstComplication);
|
||||
verify(mFactory).create(Mockito.any(), idCaptor.capture());
|
||||
final ComplicationId firstId = idCaptor.getValue();
|
||||
|
||||
Mockito.clearInvocations(mFactory);
|
||||
|
||||
transformer.getViewModel(secondComplication);
|
||||
verify(mFactory).create(Mockito.any(), idCaptor.capture());
|
||||
final ComplicationId secondId = idCaptor.getValue();
|
||||
|
||||
assertNotEquals(secondId, firstId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user