mirror of
https://github.com/oxen-io/session-android.git
synced 2025-06-09 03:38:33 +00:00
Added support for blurring faces.
Co-authored-by: Alan Evans <alan@signal.org>
This commit is contained in:
parent
514048171b
commit
c8dd4e5254
@ -281,9 +281,10 @@ dependencies {
|
|||||||
implementation "androidx.autofill:autofill:1.0.0"
|
implementation "androidx.autofill:autofill:1.0.0"
|
||||||
implementation "androidx.paging:paging-common:2.1.2"
|
implementation "androidx.paging:paging-common:2.1.2"
|
||||||
implementation "androidx.paging:paging-runtime:2.1.2"
|
implementation "androidx.paging:paging-runtime:2.1.2"
|
||||||
|
implementation 'com.google.firebase:firebase-ml-vision:24.0.3'
|
||||||
|
implementation 'com.google.firebase:firebase-ml-vision-face-model:20.0.1'
|
||||||
|
|
||||||
|
implementation ('com.google.firebase:firebase-messaging:20.2.0') {
|
||||||
implementation('com.google.firebase:firebase-messaging:17.3.4') {
|
|
||||||
exclude group: 'com.google.firebase', module: 'firebase-core'
|
exclude group: 'com.google.firebase', module: 'firebase-core'
|
||||||
exclude group: 'com.google.firebase', module: 'firebase-analytics'
|
exclude group: 'com.google.firebase', module: 'firebase-analytics'
|
||||||
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
|
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
|
||||||
|
@ -29,7 +29,7 @@ public class FcmReceiveService extends FirebaseMessagingService {
|
|||||||
if (challenge != null) {
|
if (challenge != null) {
|
||||||
handlePushChallenge(challenge);
|
handlePushChallenge(challenge);
|
||||||
} else {
|
} else {
|
||||||
handleReceivedNotification(getApplicationContext());
|
handleReceivedNotification(ApplicationDependencies.getApplication());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ public class FcmReceiveService extends FirebaseMessagingService {
|
|||||||
public void onNewToken(String token) {
|
public void onNewToken(String token) {
|
||||||
Log.i(TAG, "onNewToken()");
|
Log.i(TAG, "onNewToken()");
|
||||||
|
|
||||||
if (!TextSecurePreferences.isPushRegistered(getApplicationContext())) {
|
if (!TextSecurePreferences.isPushRegistered(ApplicationDependencies.getApplication())) {
|
||||||
Log.i(TAG, "Got a new FCM token, but the user isn't registered.");
|
Log.i(TAG, "Got a new FCM token, but the user isn't registered.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -311,7 +311,7 @@ public final class ImageEditorView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private @Nullable EditSession startEdit(@NonNull Matrix inverse, @NonNull PointF point, @Nullable EditorElement selected) {
|
private @Nullable EditSession startEdit(@NonNull Matrix inverse, @NonNull PointF point, @Nullable EditorElement selected) {
|
||||||
if (mode == Mode.Draw) {
|
if (mode == Mode.Draw || mode == Mode.Blur) {
|
||||||
return startADrawingSession(point);
|
return startADrawingSession(point);
|
||||||
} else {
|
} else {
|
||||||
return startAMoveAndResizeSession(inverse, point, selected);
|
return startAMoveAndResizeSession(inverse, point, selected);
|
||||||
@ -320,7 +320,7 @@ public final class ImageEditorView extends FrameLayout {
|
|||||||
|
|
||||||
private EditSession startADrawingSession(@NonNull PointF point) {
|
private EditSession startADrawingSession(@NonNull PointF point) {
|
||||||
BezierDrawingRenderer renderer = new BezierDrawingRenderer(color, thickness * Bounds.FULL_BOUNDS.width(), cap, model.findCropRelativeToRoot());
|
BezierDrawingRenderer renderer = new BezierDrawingRenderer(color, thickness * Bounds.FULL_BOUNDS.width(), cap, model.findCropRelativeToRoot());
|
||||||
EditorElement element = new EditorElement(renderer, EditorModel.Z_DRAWING);
|
EditorElement element = new EditorElement(renderer, mode == Mode.Blur ? EditorModel.Z_MASK : EditorModel.Z_DRAWING);
|
||||||
model.addElementCentered(element, 1);
|
model.addElementCentered(element, 1);
|
||||||
|
|
||||||
Matrix elementInverseMatrix = model.findElementInverseMatrix(element, viewMatrix);
|
Matrix elementInverseMatrix = model.findElementInverseMatrix(element, viewMatrix);
|
||||||
@ -354,10 +354,10 @@ public final class ImageEditorView extends FrameLayout {
|
|||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startDrawing(float thickness, @NonNull Paint.Cap cap) {
|
public void startDrawing(float thickness, @NonNull Paint.Cap cap, boolean blur) {
|
||||||
this.thickness = thickness;
|
this.thickness = thickness;
|
||||||
this.cap = cap;
|
this.cap = cap;
|
||||||
setMode(Mode.Draw);
|
setMode(blur ? Mode.Blur : Mode.Draw);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDrawingBrushColor(int color) {
|
public void setDrawingBrushColor(int color) {
|
||||||
@ -448,12 +448,13 @@ public final class ImageEditorView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean allowTaps() {
|
private boolean allowTaps() {
|
||||||
return !model.isCropping() && mode != Mode.Draw;
|
return !model.isCropping() && mode != Mode.Draw && mode != Mode.Blur;
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Mode {
|
public enum Mode {
|
||||||
MoveAndResize,
|
MoveAndResize,
|
||||||
Draw
|
Draw,
|
||||||
|
Blur
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface DrawingChangedListener {
|
public interface DrawingChangedListener {
|
||||||
|
@ -20,6 +20,7 @@ import org.thoughtcrime.securesms.imageeditor.Renderer;
|
|||||||
import org.thoughtcrime.securesms.imageeditor.RendererContext;
|
import org.thoughtcrime.securesms.imageeditor.RendererContext;
|
||||||
import org.thoughtcrime.securesms.imageeditor.UndoRedoStackListener;
|
import org.thoughtcrime.securesms.imageeditor.UndoRedoStackListener;
|
||||||
import org.thoughtcrime.securesms.imageeditor.renderers.MultiLineTextRenderer;
|
import org.thoughtcrime.securesms.imageeditor.renderers.MultiLineTextRenderer;
|
||||||
|
import org.thoughtcrime.securesms.imageeditor.renderers.FaceBlurRenderer;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
@ -662,7 +663,10 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
|
|||||||
*/
|
*/
|
||||||
public void addElement(@NonNull EditorElement element) {
|
public void addElement(@NonNull EditorElement element) {
|
||||||
pushUndoPoint();
|
pushUndoPoint();
|
||||||
|
addElementWithoutPushUndo(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addElementWithoutPushUndo(@NonNull EditorElement element) {
|
||||||
EditorElement mainImage = editorElementHierarchy.getMainImage();
|
EditorElement mainImage = editorElementHierarchy.getMainImage();
|
||||||
EditorElement parent = mainImage != null ? mainImage : editorElementHierarchy.getImageRoot();
|
EditorElement parent = mainImage != null ? mainImage : editorElementHierarchy.getImageRoot();
|
||||||
|
|
||||||
@ -675,6 +679,36 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
|
|||||||
updateUndoRedoAvailableState(undoRedoStacks);
|
updateUndoRedoAvailableState(undoRedoStacks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void clearFaceRenderers() {
|
||||||
|
EditorElement mainImage = editorElementHierarchy.getMainImage();
|
||||||
|
if (mainImage != null) {
|
||||||
|
boolean hasPushedUndo = false;
|
||||||
|
for (int i = mainImage.getChildCount() - 1; i >= 0; i--) {
|
||||||
|
if (mainImage.getChild(i).getRenderer() instanceof FaceBlurRenderer) {
|
||||||
|
if (!hasPushedUndo) {
|
||||||
|
pushUndoPoint();
|
||||||
|
hasPushedUndo = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
mainImage.deleteChild(mainImage.getChild(i), invalidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasFaceRenderer() {
|
||||||
|
EditorElement mainImage = editorElementHierarchy.getMainImage();
|
||||||
|
if (mainImage != null) {
|
||||||
|
for (int i = mainImage.getChildCount() - 1; i >= 0; i--) {
|
||||||
|
if (mainImage.getChild(i).getRenderer() instanceof FaceBlurRenderer) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isChanged() {
|
public boolean isChanged() {
|
||||||
return undoRedoStacks.isChanged(editorElementHierarchy.getRoot());
|
return undoRedoStacks.isChanged(editorElementHierarchy.getRoot());
|
||||||
}
|
}
|
||||||
@ -741,6 +775,10 @@ public final class EditorModel implements Parcelable, RendererContext.Ready {
|
|||||||
return editorElementHierarchy.getRoot();
|
return editorElementHierarchy.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public EditorElement getMainImage() {
|
||||||
|
return editorElementHierarchy.getMainImage();
|
||||||
|
}
|
||||||
|
|
||||||
public void delete(@NonNull EditorElement editorElement) {
|
public void delete(@NonNull EditorElement editorElement) {
|
||||||
editorElementHierarchy.getImageRoot().forAllInTree(element -> element.deleteChild(editorElement, invalidate));
|
editorElementHierarchy.getImageRoot().forAllInTree(element -> element.deleteChild(editorElement, invalidate));
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,86 @@
|
|||||||
|
package org.thoughtcrime.securesms.imageeditor.renderers;
|
||||||
|
|
||||||
|
import android.graphics.Matrix;
|
||||||
|
import android.graphics.Point;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.os.Parcel;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.imageeditor.Bounds;
|
||||||
|
import org.thoughtcrime.securesms.imageeditor.Renderer;
|
||||||
|
import org.thoughtcrime.securesms.imageeditor.RendererContext;
|
||||||
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A rectangle that will be rendered on the blur mask layer. Intended for blurring faces.
|
||||||
|
*/
|
||||||
|
public class FaceBlurRenderer implements Renderer {
|
||||||
|
|
||||||
|
private static final int CORNER_RADIUS = 0;
|
||||||
|
|
||||||
|
private final RectF faceRect;
|
||||||
|
private final Point imageDimensions;
|
||||||
|
private final Matrix scaleMatrix;
|
||||||
|
|
||||||
|
public FaceBlurRenderer(@NonNull RectF faceRect, @NonNull Matrix matrix) {
|
||||||
|
this.faceRect = faceRect;
|
||||||
|
this.imageDimensions = new Point(0, 0);
|
||||||
|
this.scaleMatrix = matrix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FaceBlurRenderer(@NonNull RectF faceRect, @NonNull Point imageDimensions) {
|
||||||
|
this.faceRect = faceRect;
|
||||||
|
this.imageDimensions = imageDimensions;
|
||||||
|
this.scaleMatrix = new Matrix();
|
||||||
|
|
||||||
|
scaleMatrix.setRectToRect(new RectF(0, 0, this.imageDimensions.x, this.imageDimensions.y), Bounds.FULL_BOUNDS, Matrix.ScaleToFit.CENTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(@NonNull RendererContext rendererContext) {
|
||||||
|
rendererContext.canvas.save();
|
||||||
|
rendererContext.canvas.concat(scaleMatrix);
|
||||||
|
rendererContext.canvas.drawRoundRect(faceRect, CORNER_RADIUS, CORNER_RADIUS, rendererContext.getMaskPaint());
|
||||||
|
rendererContext.canvas.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hitTest(float x, float y) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeFloat(faceRect.left);
|
||||||
|
dest.writeFloat(faceRect.top);
|
||||||
|
dest.writeFloat(faceRect.right);
|
||||||
|
dest.writeFloat(faceRect.bottom);
|
||||||
|
dest.writeInt(imageDimensions.x);
|
||||||
|
dest.writeInt(imageDimensions.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<FaceBlurRenderer> CREATOR = new Creator<FaceBlurRenderer>() {
|
||||||
|
@Override
|
||||||
|
public FaceBlurRenderer createFromParcel(Parcel in) {
|
||||||
|
float left = in.readFloat();
|
||||||
|
float top = in.readFloat();
|
||||||
|
float right = in.readFloat();
|
||||||
|
float bottom = in.readFloat();
|
||||||
|
int x = in.readInt();
|
||||||
|
int y = in.readInt();
|
||||||
|
|
||||||
|
return new FaceBlurRenderer(new RectF(left, top, right, bottom), new Point(x, y));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FaceBlurRenderer[] newArray(int size) {
|
||||||
|
return new FaceBlurRenderer[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package org.thoughtcrime.securesms.keyvalue;
|
package org.thoughtcrime.securesms.keyvalue;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.lifecycle.OnLifecycleEvent;
|
||||||
import androidx.preference.PreferenceDataStore;
|
import androidx.preference.PreferenceDataStore;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
@ -19,6 +20,7 @@ public final class SignalStore {
|
|||||||
public static void onFirstEverAppLaunch() {
|
public static void onFirstEverAppLaunch() {
|
||||||
registrationValues().onFirstEverAppLaunch();
|
registrationValues().onFirstEverAppLaunch();
|
||||||
uiHints().onFirstEverAppLaunch();
|
uiHints().onFirstEverAppLaunch();
|
||||||
|
tooltips().onFirstEverAppLaunch();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static @NonNull KbsValues kbsValues() {
|
public static @NonNull KbsValues kbsValues() {
|
||||||
@ -41,6 +43,14 @@ public final class SignalStore {
|
|||||||
return new StorageServiceValues(getStore());
|
return new StorageServiceValues(getStore());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static @NonNull UiHints uiHints() {
|
||||||
|
return new UiHints(getStore());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @NonNull TooltipValues tooltips() {
|
||||||
|
return new TooltipValues(getStore());
|
||||||
|
}
|
||||||
|
|
||||||
public static @NonNull GroupsV2AuthorizationSignalStoreCache groupsV2AuthorizationCache() {
|
public static @NonNull GroupsV2AuthorizationSignalStoreCache groupsV2AuthorizationCache() {
|
||||||
return new GroupsV2AuthorizationSignalStoreCache(getStore());
|
return new GroupsV2AuthorizationSignalStoreCache(getStore());
|
||||||
}
|
}
|
||||||
@ -61,10 +71,6 @@ public final class SignalStore {
|
|||||||
putLong(MESSAGE_REQUEST_ENABLE_TIME, time);
|
putLong(MESSAGE_REQUEST_ENABLE_TIME, time);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static UiHints uiHints() {
|
|
||||||
return new UiHints(getStore());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NonNull PreferenceDataStore getPreferenceDataStore() {
|
public static @NonNull PreferenceDataStore getPreferenceDataStore() {
|
||||||
return new SignalPreferenceDataStore(getStore());
|
return new SignalPreferenceDataStore(getStore());
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
package org.thoughtcrime.securesms.keyvalue;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.whispersystems.signalservice.api.storage.StorageKey;
|
||||||
|
|
||||||
|
public class TooltipValues {
|
||||||
|
|
||||||
|
private static final String BLUR_HUD_ICON = "tooltip.blur_hud_icon";
|
||||||
|
private static final String AUTO_BLUR_FACES = "tooltip.auto_blur_faces";
|
||||||
|
|
||||||
|
private final KeyValueStore store;
|
||||||
|
|
||||||
|
TooltipValues(@NonNull KeyValueStore store) {
|
||||||
|
this.store = store;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onFirstEverAppLaunch() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasSeenBlurHudIconTooltip() {
|
||||||
|
return store.getBoolean(BLUR_HUD_ICON, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markBlurHudIconTooltipSeen() {
|
||||||
|
store.beginWrite().putBoolean(BLUR_HUD_ICON, true).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasSeenAutoBlurFacesTooltip() {
|
||||||
|
return store.getBoolean(AUTO_BLUR_FACES, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markAutoBlurFacesTooltipSeen() {
|
||||||
|
store.beginWrite().putBoolean(AUTO_BLUR_FACES, true).apply();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package org.thoughtcrime.securesms.scribbles;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
interface FaceDetector {
|
||||||
|
List<RectF> detect(Bitmap bitmap);
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
package org.thoughtcrime.securesms.scribbles;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import com.annimon.stream.Stream;
|
||||||
|
import com.google.firebase.ml.vision.FirebaseVision;
|
||||||
|
import com.google.firebase.ml.vision.common.FirebaseVisionImage;
|
||||||
|
import com.google.firebase.ml.vision.face.FirebaseVisionFace;
|
||||||
|
import com.google.firebase.ml.vision.face.FirebaseVisionFaceDetector;
|
||||||
|
import com.google.firebase.ml.vision.face.FirebaseVisionFaceDetectorOptions;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
class FirebaseFaceDetector implements FaceDetector {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(FirebaseFaceDetector.class);
|
||||||
|
|
||||||
|
private static final long MAX_SIZE = 1000 * 1000;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<RectF> detect(Bitmap source) {
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
int performanceMode = getPerformanceMode(source);
|
||||||
|
Log.d(TAG, "Using performance mode " + performanceMode + " (API " + Build.VERSION.SDK_INT + ", " + source.getWidth() + "x" + source.getHeight() + ")");
|
||||||
|
|
||||||
|
FirebaseVisionFaceDetectorOptions options = new FirebaseVisionFaceDetectorOptions.Builder()
|
||||||
|
.setPerformanceMode(performanceMode)
|
||||||
|
.setMinFaceSize(0.05f)
|
||||||
|
.setContourMode(FirebaseVisionFaceDetectorOptions.NO_CONTOURS)
|
||||||
|
.setLandmarkMode(FirebaseVisionFaceDetectorOptions.NO_LANDMARKS)
|
||||||
|
.setClassificationMode(FirebaseVisionFaceDetectorOptions.NO_CLASSIFICATIONS)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(source);
|
||||||
|
List<RectF> output = new ArrayList<>();
|
||||||
|
|
||||||
|
try (FirebaseVisionFaceDetector detector = FirebaseVision.getInstance().getVisionFaceDetector(options)) {
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
|
||||||
|
detector.detectInImage(image)
|
||||||
|
.addOnSuccessListener(firebaseVisionFaces -> {
|
||||||
|
output.addAll(Stream.of(firebaseVisionFaces)
|
||||||
|
.map(FirebaseVisionFace::getBoundingBox)
|
||||||
|
.map(r -> new RectF(r.left, r.top, r.right, r.bottom))
|
||||||
|
.toList());
|
||||||
|
latch.countDown();
|
||||||
|
})
|
||||||
|
.addOnFailureListener(e -> latch.countDown());
|
||||||
|
|
||||||
|
latch.await(15, TimeUnit.SECONDS);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(TAG, "Failed to close!", e);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "Finished in " + (System.currentTimeMillis() - startTime) + " ms");
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getPerformanceMode(Bitmap source) {
|
||||||
|
if (Build.VERSION.SDK_INT < 28) {
|
||||||
|
return FirebaseVisionFaceDetectorOptions.FAST;
|
||||||
|
}
|
||||||
|
|
||||||
|
return source.getWidth() * source.getHeight() < MAX_SIZE ? FirebaseVisionFaceDetectorOptions.ACCURATE
|
||||||
|
: FirebaseVisionFaceDetectorOptions.FAST;
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,8 @@ import android.Manifest;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Point;
|
||||||
|
import android.graphics.RectF;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@ -13,15 +15,19 @@ import android.widget.Toast;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
|
import org.thoughtcrime.securesms.components.TooltipPopup;
|
||||||
import org.thoughtcrime.securesms.imageeditor.ColorableRenderer;
|
import org.thoughtcrime.securesms.imageeditor.ColorableRenderer;
|
||||||
import org.thoughtcrime.securesms.imageeditor.ImageEditorView;
|
import org.thoughtcrime.securesms.imageeditor.ImageEditorView;
|
||||||
import org.thoughtcrime.securesms.imageeditor.Renderer;
|
import org.thoughtcrime.securesms.imageeditor.Renderer;
|
||||||
import org.thoughtcrime.securesms.imageeditor.model.EditorElement;
|
import org.thoughtcrime.securesms.imageeditor.model.EditorElement;
|
||||||
import org.thoughtcrime.securesms.imageeditor.model.EditorModel;
|
import org.thoughtcrime.securesms.imageeditor.model.EditorModel;
|
||||||
import org.thoughtcrime.securesms.imageeditor.renderers.MultiLineTextRenderer;
|
import org.thoughtcrime.securesms.imageeditor.renderers.MultiLineTextRenderer;
|
||||||
|
import org.thoughtcrime.securesms.imageeditor.renderers.FaceBlurRenderer;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.mediasend.MediaSendPageFragment;
|
import org.thoughtcrime.securesms.mediasend.MediaSendPageFragment;
|
||||||
import org.thoughtcrime.securesms.mms.MediaConstraints;
|
import org.thoughtcrime.securesms.mms.MediaConstraints;
|
||||||
@ -29,15 +35,18 @@ import org.thoughtcrime.securesms.mms.PushMediaConstraints;
|
|||||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||||
import org.thoughtcrime.securesms.scribbles.widget.VerticalSlideColorPicker;
|
import org.thoughtcrime.securesms.scribbles.widget.VerticalSlideColorPicker;
|
||||||
import org.thoughtcrime.securesms.stickers.StickerSearchRepository;
|
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
import org.thoughtcrime.securesms.util.ParcelUtil;
|
import org.thoughtcrime.securesms.util.ParcelUtil;
|
||||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
|
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||||
|
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
|
||||||
|
import org.whispersystems.libsignal.util.Pair;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static android.app.Activity.RESULT_OK;
|
import static android.app.Activity.RESULT_OK;
|
||||||
|
|
||||||
@ -54,6 +63,8 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
|
|||||||
|
|
||||||
private EditorModel restoredModel;
|
private EditorModel restoredModel;
|
||||||
|
|
||||||
|
private Pair<Uri, FaceDetectionResult> cachedFaceDetection;
|
||||||
|
|
||||||
@Nullable private EditorElement currentSelection;
|
@Nullable private EditorElement currentSelection;
|
||||||
private int imageMaxHeight;
|
private int imageMaxHeight;
|
||||||
private int imageMaxWidth;
|
private int imageMaxWidth;
|
||||||
@ -84,10 +95,10 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Uri imageUri;
|
private Uri imageUri;
|
||||||
private Controller controller;
|
private Controller controller;
|
||||||
private ImageEditorHud imageEditorHud;
|
private ImageEditorHud imageEditorHud;
|
||||||
private ImageEditorView imageEditorView;
|
private ImageEditorView imageEditorView;
|
||||||
|
|
||||||
public static ImageEditorFragment newInstanceForAvatar(@NonNull Uri imageUri) {
|
public static ImageEditorFragment newInstanceForAvatar(@NonNull Uri imageUri) {
|
||||||
ImageEditorFragment fragment = newInstance(imageUri);
|
ImageEditorFragment fragment = newInstance(imageUri);
|
||||||
@ -169,6 +180,11 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
|
|||||||
|
|
||||||
imageEditorView.setModel(editorModel);
|
imageEditorView.setModel(editorModel);
|
||||||
|
|
||||||
|
if (!SignalStore.tooltips().hasSeenBlurHudIconTooltip()) {
|
||||||
|
imageEditorHud.showBlurHudTooltip();
|
||||||
|
SignalStore.tooltips().markBlurHudIconTooltipSeen();
|
||||||
|
}
|
||||||
|
|
||||||
refreshUniqueColors();
|
refreshUniqueColors();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,12 +295,22 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
|
|||||||
}
|
}
|
||||||
|
|
||||||
case DRAW: {
|
case DRAW: {
|
||||||
imageEditorView.startDrawing(0.01f, Paint.Cap.ROUND);
|
imageEditorView.startDrawing(0.01f, Paint.Cap.ROUND, false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case HIGHLIGHT: {
|
case HIGHLIGHT: {
|
||||||
imageEditorView.startDrawing(0.03f, Paint.Cap.SQUARE);
|
imageEditorView.startDrawing(0.03f, Paint.Cap.SQUARE, false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case BLUR: {
|
||||||
|
imageEditorView.startDrawing(0.055f, Paint.Cap.ROUND, true);
|
||||||
|
imageEditorHud.setBlurFacesToggleEnabled(imageEditorView.getModel().hasFaceRenderer());
|
||||||
|
if (!SignalStore.tooltips().hasSeenAutoBlurFacesTooltip()) {
|
||||||
|
imageEditorHud.showAutoBlurFacesTooltip();
|
||||||
|
SignalStore.tooltips().markAutoBlurFacesTooltipSeen();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,10 +342,42 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
|
|||||||
changeEntityColor(color);
|
changeEntityColor(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBlurFacesToggled(boolean enabled) {
|
||||||
|
if (!enabled) {
|
||||||
|
imageEditorView.getModel().clearFaceRenderers();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cachedFaceDetection != null && cachedFaceDetection.first().equals(getUri())) {
|
||||||
|
renderFaceBlurs(cachedFaceDetection.second());
|
||||||
|
return;
|
||||||
|
} else if (cachedFaceDetection != null && !cachedFaceDetection.first().equals(getUri())) {
|
||||||
|
cachedFaceDetection = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
AlertDialog progress = SimpleProgressDialog.show(requireContext());
|
||||||
|
|
||||||
|
SimpleTask.run(() -> {
|
||||||
|
Bitmap bitmap = ((UriGlideRenderer) imageEditorView.getModel().getMainImage().getRenderer()).getBitmap();
|
||||||
|
|
||||||
|
if (bitmap != null) {
|
||||||
|
FaceDetector detector = new FirebaseFaceDetector();
|
||||||
|
return new FaceDetectionResult(detector.detect(bitmap), new Point(bitmap.getWidth(), bitmap.getHeight()));
|
||||||
|
} else {
|
||||||
|
return new FaceDetectionResult(Collections.emptyList(), new Point(0, 0));
|
||||||
|
}
|
||||||
|
}, result -> {
|
||||||
|
renderFaceBlurs(result);
|
||||||
|
progress.dismiss();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUndo() {
|
public void onUndo() {
|
||||||
imageEditorView.getModel().undo();
|
imageEditorView.getModel().undo();
|
||||||
refreshUniqueColors();
|
refreshUniqueColors();
|
||||||
|
imageEditorHud.setBlurFacesToggleEnabled(imageEditorView.getModel().hasFaceRenderer());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -396,7 +454,30 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
|
|||||||
imageEditorHud.setUndoAvailability(undoAvailable);
|
imageEditorHud.setUndoAvailability(undoAvailable);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final ImageEditorView.TapListener selectionListener = new ImageEditorView.TapListener() {
|
private void renderFaceBlurs(@NonNull FaceDetectionResult result) {
|
||||||
|
List<RectF> faces = result.rects;
|
||||||
|
Point size = result.imageSize;
|
||||||
|
|
||||||
|
if (faces.isEmpty()) {
|
||||||
|
Toast.makeText(requireContext(), R.string.ImageEditorFragment_no_faces_detected, Toast.LENGTH_SHORT).show();
|
||||||
|
imageEditorHud.setBlurFacesToggleEnabled(false);
|
||||||
|
cachedFaceDetection = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
imageEditorView.getModel().pushUndoPoint();
|
||||||
|
|
||||||
|
for (RectF face : faces) {
|
||||||
|
FaceBlurRenderer faceBlurRenderer = new FaceBlurRenderer(face, size);
|
||||||
|
imageEditorView.getModel().addElementWithoutPushUndo(new EditorElement(faceBlurRenderer, EditorModel.Z_MASK));
|
||||||
|
}
|
||||||
|
|
||||||
|
imageEditorView.invalidate();
|
||||||
|
|
||||||
|
cachedFaceDetection = new Pair<>(getUri(), result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ImageEditorView.TapListener selectionListener = new ImageEditorView.TapListener() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEntityDown(@Nullable EditorElement editorElement) {
|
public void onEntityDown(@Nullable EditorElement editorElement) {
|
||||||
@ -449,4 +530,14 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
|
|||||||
|
|
||||||
void onDoneEditing();
|
void onDoneEditing();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class FaceDetectionResult {
|
||||||
|
private final List<RectF> rects;
|
||||||
|
private final Point imageSize;
|
||||||
|
|
||||||
|
private FaceDetectionResult(@NonNull List<RectF> rects, @NonNull Point imageSize) {
|
||||||
|
this.rects = rects;
|
||||||
|
this.imageSize = imageSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,15 +4,19 @@ import android.content.Context;
|
|||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.widget.CompoundButton;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.Switch;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
|
import org.thoughtcrime.securesms.components.TooltipPopup;
|
||||||
import org.thoughtcrime.securesms.scribbles.widget.ColorPaletteAdapter;
|
import org.thoughtcrime.securesms.scribbles.widget.ColorPaletteAdapter;
|
||||||
import org.thoughtcrime.securesms.scribbles.widget.VerticalSlideColorPicker;
|
import org.thoughtcrime.securesms.scribbles.widget.VerticalSlideColorPicker;
|
||||||
|
|
||||||
@ -34,6 +38,7 @@ public final class ImageEditorHud extends LinearLayout {
|
|||||||
private ImageView cropAspectLock;
|
private ImageView cropAspectLock;
|
||||||
private View drawButton;
|
private View drawButton;
|
||||||
private View highlightButton;
|
private View highlightButton;
|
||||||
|
private View blurButton;
|
||||||
private View textButton;
|
private View textButton;
|
||||||
private View stickerButton;
|
private View stickerButton;
|
||||||
private View undoButton;
|
private View undoButton;
|
||||||
@ -41,6 +46,8 @@ public final class ImageEditorHud extends LinearLayout {
|
|||||||
private View deleteButton;
|
private View deleteButton;
|
||||||
private View confirmButton;
|
private View confirmButton;
|
||||||
private View doneButton;
|
private View doneButton;
|
||||||
|
private View blurToggleContainer;
|
||||||
|
private Switch blurToggle;
|
||||||
private VerticalSlideColorPicker colorPicker;
|
private VerticalSlideColorPicker colorPicker;
|
||||||
private RecyclerView colorPalette;
|
private RecyclerView colorPalette;
|
||||||
|
|
||||||
@ -74,21 +81,24 @@ public final class ImageEditorHud extends LinearLayout {
|
|||||||
inflate(getContext(), R.layout.image_editor_hud, this);
|
inflate(getContext(), R.layout.image_editor_hud, this);
|
||||||
setOrientation(VERTICAL);
|
setOrientation(VERTICAL);
|
||||||
|
|
||||||
cropButton = findViewById(R.id.scribble_crop_button);
|
cropButton = findViewById(R.id.scribble_crop_button);
|
||||||
cropFlipButton = findViewById(R.id.scribble_crop_flip);
|
cropFlipButton = findViewById(R.id.scribble_crop_flip);
|
||||||
cropRotateButton = findViewById(R.id.scribble_crop_rotate);
|
cropRotateButton = findViewById(R.id.scribble_crop_rotate);
|
||||||
cropAspectLock = findViewById(R.id.scribble_crop_aspect_lock);
|
cropAspectLock = findViewById(R.id.scribble_crop_aspect_lock);
|
||||||
colorPalette = findViewById(R.id.scribble_color_palette);
|
colorPalette = findViewById(R.id.scribble_color_palette);
|
||||||
drawButton = findViewById(R.id.scribble_draw_button);
|
drawButton = findViewById(R.id.scribble_draw_button);
|
||||||
highlightButton = findViewById(R.id.scribble_highlight_button);
|
highlightButton = findViewById(R.id.scribble_highlight_button);
|
||||||
textButton = findViewById(R.id.scribble_text_button);
|
blurButton = findViewById(R.id.scribble_blur_button);
|
||||||
stickerButton = findViewById(R.id.scribble_sticker_button);
|
textButton = findViewById(R.id.scribble_text_button);
|
||||||
undoButton = findViewById(R.id.scribble_undo_button);
|
stickerButton = findViewById(R.id.scribble_sticker_button);
|
||||||
saveButton = findViewById(R.id.scribble_save_button);
|
undoButton = findViewById(R.id.scribble_undo_button);
|
||||||
deleteButton = findViewById(R.id.scribble_delete_button);
|
saveButton = findViewById(R.id.scribble_save_button);
|
||||||
confirmButton = findViewById(R.id.scribble_confirm_button);
|
deleteButton = findViewById(R.id.scribble_delete_button);
|
||||||
colorPicker = findViewById(R.id.scribble_color_picker);
|
confirmButton = findViewById(R.id.scribble_confirm_button);
|
||||||
doneButton = findViewById(R.id.scribble_done_button);
|
colorPicker = findViewById(R.id.scribble_color_picker);
|
||||||
|
doneButton = findViewById(R.id.scribble_done_button);
|
||||||
|
blurToggleContainer = findViewById(R.id.scribble_blur_toggle_container);
|
||||||
|
blurToggle = findViewById(R.id.scribble_blur_toggle);
|
||||||
|
|
||||||
cropAspectLock.setOnClickListener(v -> {
|
cropAspectLock.setOnClickListener(v -> {
|
||||||
eventListener.onCropAspectLock(!eventListener.isCropAspectLocked());
|
eventListener.onCropAspectLock(!eventListener.isCropAspectLocked());
|
||||||
@ -105,12 +115,14 @@ public final class ImageEditorHud extends LinearLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initializeVisibilityMap() {
|
private void initializeVisibilityMap() {
|
||||||
setVisibleViewsWhenInMode(Mode.NONE, drawButton, highlightButton, textButton, stickerButton, cropButton, undoButton, saveButton);
|
setVisibleViewsWhenInMode(Mode.NONE, drawButton, blurButton, textButton, stickerButton, cropButton, undoButton, saveButton);
|
||||||
|
|
||||||
setVisibleViewsWhenInMode(Mode.DRAW, confirmButton, undoButton, colorPicker, colorPalette);
|
setVisibleViewsWhenInMode(Mode.DRAW, confirmButton, undoButton, colorPicker, colorPalette);
|
||||||
|
|
||||||
setVisibleViewsWhenInMode(Mode.HIGHLIGHT, confirmButton, undoButton, colorPicker, colorPalette);
|
setVisibleViewsWhenInMode(Mode.HIGHLIGHT, confirmButton, undoButton, colorPicker, colorPalette);
|
||||||
|
|
||||||
|
setVisibleViewsWhenInMode(Mode.BLUR, confirmButton, undoButton, blurToggleContainer);
|
||||||
|
|
||||||
setVisibleViewsWhenInMode(Mode.TEXT, confirmButton, deleteButton, colorPicker, colorPalette);
|
setVisibleViewsWhenInMode(Mode.TEXT, confirmButton, deleteButton, colorPicker, colorPalette);
|
||||||
|
|
||||||
setVisibleViewsWhenInMode(Mode.MOVE_DELETE, confirmButton, deleteButton);
|
setVisibleViewsWhenInMode(Mode.MOVE_DELETE, confirmButton, deleteButton);
|
||||||
@ -152,11 +164,13 @@ public final class ImageEditorHud extends LinearLayout {
|
|||||||
colorPalette.setAdapter(colorPaletteAdapter);
|
colorPalette.setAdapter(colorPaletteAdapter);
|
||||||
|
|
||||||
drawButton.setOnClickListener(v -> setMode(Mode.DRAW));
|
drawButton.setOnClickListener(v -> setMode(Mode.DRAW));
|
||||||
|
blurButton.setOnClickListener(v -> setMode(Mode.BLUR));
|
||||||
highlightButton.setOnClickListener(v -> setMode(Mode.HIGHLIGHT));
|
highlightButton.setOnClickListener(v -> setMode(Mode.HIGHLIGHT));
|
||||||
textButton.setOnClickListener(v -> setMode(Mode.TEXT));
|
textButton.setOnClickListener(v -> setMode(Mode.TEXT));
|
||||||
stickerButton.setOnClickListener(v -> setMode(Mode.INSERT_STICKER));
|
stickerButton.setOnClickListener(v -> setMode(Mode.INSERT_STICKER));
|
||||||
saveButton.setOnClickListener(v -> eventListener.onSave());
|
saveButton.setOnClickListener(v -> eventListener.onSave());
|
||||||
doneButton.setOnClickListener(v -> eventListener.onDone());
|
doneButton.setOnClickListener(v -> eventListener.onDone());
|
||||||
|
blurToggle.setOnCheckedChangeListener((button, enabled) -> eventListener.onBlurFacesToggled(enabled));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUpForAvatarEditing() {
|
public void setUpForAvatarEditing() {
|
||||||
@ -186,6 +200,26 @@ public final class ImageEditorHud extends LinearLayout {
|
|||||||
colorPicker.setActiveColor(color);
|
colorPicker.setActiveColor(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setBlurFacesToggleEnabled(boolean enabled) {
|
||||||
|
blurToggle.setChecked(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showBlurHudTooltip() {
|
||||||
|
TooltipPopup.forTarget(blurButton)
|
||||||
|
.setText(R.string.ImageEditorHud_new_auto_blur_faces_and_blur_brush)
|
||||||
|
.setBackgroundTint(ContextCompat.getColor(getContext(), R.color.core_ultramarine))
|
||||||
|
.setTextColor(ContextCompat.getColor(getContext(), R.color.core_white))
|
||||||
|
.show(TooltipPopup.POSITION_BELOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showAutoBlurFacesTooltip() {
|
||||||
|
TooltipPopup.forTarget(blurToggleContainer)
|
||||||
|
.setText(R.string.ImageEditorHud_draw_to_blur_or_try_auto_blur)
|
||||||
|
.setBackgroundTint(ContextCompat.getColor(getContext(), R.color.core_ultramarine))
|
||||||
|
.setTextColor(ContextCompat.getColor(getContext(), R.color.core_white))
|
||||||
|
.show(TooltipPopup.POSITION_ABOVE);
|
||||||
|
}
|
||||||
|
|
||||||
public void setEventListener(@Nullable EventListener eventListener) {
|
public void setEventListener(@Nullable EventListener eventListener) {
|
||||||
this.eventListener = eventListener != null ? eventListener : NULL_EVENT_LISTENER;
|
this.eventListener = eventListener != null ? eventListener : NULL_EVENT_LISTENER;
|
||||||
}
|
}
|
||||||
@ -267,6 +301,7 @@ public final class ImageEditorHud extends LinearLayout {
|
|||||||
TEXT,
|
TEXT,
|
||||||
DRAW,
|
DRAW,
|
||||||
HIGHLIGHT,
|
HIGHLIGHT,
|
||||||
|
BLUR,
|
||||||
MOVE_DELETE,
|
MOVE_DELETE,
|
||||||
INSERT_STICKER,
|
INSERT_STICKER,
|
||||||
}
|
}
|
||||||
@ -274,6 +309,7 @@ public final class ImageEditorHud extends LinearLayout {
|
|||||||
public interface EventListener {
|
public interface EventListener {
|
||||||
void onModeStarted(@NonNull Mode mode);
|
void onModeStarted(@NonNull Mode mode);
|
||||||
void onColorChange(int color);
|
void onColorChange(int color);
|
||||||
|
void onBlurFacesToggled(boolean enabled);
|
||||||
void onUndo();
|
void onUndo();
|
||||||
void onDelete();
|
void onDelete();
|
||||||
void onSave();
|
void onSave();
|
||||||
@ -295,6 +331,10 @@ public final class ImageEditorHud extends LinearLayout {
|
|||||||
public void onColorChange(int color) {
|
public void onColorChange(int color) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBlurFacesToggled(boolean enabled) {
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUndo() {
|
public void onUndo() {
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.scribbles;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BlurMaskFilter;
|
|
||||||
import android.graphics.Matrix;
|
import android.graphics.Matrix;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.Point;
|
import android.graphics.Point;
|
||||||
@ -29,9 +28,11 @@ import org.thoughtcrime.securesms.imageeditor.Renderer;
|
|||||||
import org.thoughtcrime.securesms.imageeditor.RendererContext;
|
import org.thoughtcrime.securesms.imageeditor.RendererContext;
|
||||||
import org.thoughtcrime.securesms.imageeditor.model.EditorElement;
|
import org.thoughtcrime.securesms.imageeditor.model.EditorElement;
|
||||||
import org.thoughtcrime.securesms.imageeditor.model.EditorModel;
|
import org.thoughtcrime.securesms.imageeditor.model.EditorModel;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
|
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequest;
|
import org.thoughtcrime.securesms.mms.GlideRequest;
|
||||||
|
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||||
|
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
@ -42,12 +43,16 @@ import java.util.concurrent.ExecutionException;
|
|||||||
*/
|
*/
|
||||||
final class UriGlideRenderer implements Renderer {
|
final class UriGlideRenderer implements Renderer {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(UriGlideRenderer.class);
|
||||||
|
|
||||||
private static final int PREVIEW_DIMENSION_LIMIT = 2048;
|
private static final int PREVIEW_DIMENSION_LIMIT = 2048;
|
||||||
|
private static final int MAX_BLUR_DIMENSION = 300;
|
||||||
|
|
||||||
private final Uri imageUri;
|
private final Uri imageUri;
|
||||||
private final Paint paint = new Paint();
|
private final Paint paint = new Paint();
|
||||||
private final Matrix imageProjectionMatrix = new Matrix();
|
private final Matrix imageProjectionMatrix = new Matrix();
|
||||||
private final Matrix temp = new Matrix();
|
private final Matrix temp = new Matrix();
|
||||||
|
private final Matrix blurScaleMatrix = new Matrix();
|
||||||
private final boolean decryptable;
|
private final boolean decryptable;
|
||||||
private final int maxWidth;
|
private final int maxWidth;
|
||||||
private final int maxHeight;
|
private final int maxHeight;
|
||||||
@ -55,7 +60,6 @@ final class UriGlideRenderer implements Renderer {
|
|||||||
@Nullable private Bitmap bitmap;
|
@Nullable private Bitmap bitmap;
|
||||||
@Nullable private Bitmap blurredBitmap;
|
@Nullable private Bitmap blurredBitmap;
|
||||||
@Nullable private Paint blurPaint;
|
@Nullable private Paint blurPaint;
|
||||||
@Nullable private BlurMaskFilter blurMaskFilter;
|
|
||||||
|
|
||||||
UriGlideRenderer(@NonNull Uri imageUri, boolean decryptable, int maxWidth, int maxHeight) {
|
UriGlideRenderer(@NonNull Uri imageUri, boolean decryptable, int maxWidth, int maxHeight) {
|
||||||
this.imageUri = imageUri;
|
this.imageUri = imageUri;
|
||||||
@ -124,12 +128,8 @@ final class UriGlideRenderer implements Renderer {
|
|||||||
for (EditorElement child : rendererContext.getChildren()) {
|
for (EditorElement child : rendererContext.getChildren()) {
|
||||||
if (child.getZOrder() == EditorModel.Z_MASK) {
|
if (child.getZOrder() == EditorModel.Z_MASK) {
|
||||||
renderMask = true;
|
renderMask = true;
|
||||||
if (blurMaskFilter == null) {
|
|
||||||
blurMaskFilter = new BlurMaskFilter(4, BlurMaskFilter.Blur.NORMAL); // This blurs edges of the mask shapes
|
|
||||||
}
|
|
||||||
if (blurPaint == null) {
|
if (blurPaint == null) {
|
||||||
blurPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
blurPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
blurPaint.setMaskFilter(blurMaskFilter);
|
|
||||||
}
|
}
|
||||||
blurPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
|
blurPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
|
||||||
rendererContext.setMaskPaint(blurPaint);
|
rendererContext.setMaskPaint(blurPaint);
|
||||||
@ -143,7 +143,16 @@ final class UriGlideRenderer implements Renderer {
|
|||||||
|
|
||||||
blurPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));
|
blurPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));
|
||||||
blurPaint.setMaskFilter(null);
|
blurPaint.setMaskFilter(null);
|
||||||
if (blurredBitmap == null) blurredBitmap = blur(bitmap, rendererContext.context);
|
|
||||||
|
if (blurredBitmap == null) {
|
||||||
|
blurredBitmap = blur(bitmap, rendererContext.context);
|
||||||
|
|
||||||
|
blurScaleMatrix.setRectToRect(new RectF(0, 0, blurredBitmap.getWidth(), blurredBitmap.getHeight()),
|
||||||
|
new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight()),
|
||||||
|
Matrix.ScaleToFit.FILL);
|
||||||
|
}
|
||||||
|
|
||||||
|
rendererContext.canvas.concat(blurScaleMatrix);
|
||||||
rendererContext.canvas.drawBitmap(blurredBitmap, 0, 0, blurPaint);
|
rendererContext.canvas.drawBitmap(blurredBitmap, 0, 0, blurPaint);
|
||||||
blurPaint.setXfermode(null);
|
blurPaint.setXfermode(null);
|
||||||
|
|
||||||
@ -197,7 +206,7 @@ final class UriGlideRenderer implements Renderer {
|
|||||||
* Always use this getter, as Bitmap is kept in Glide's LRUCache, so it could have been recycled
|
* Always use this getter, as Bitmap is kept in Glide's LRUCache, so it could have been recycled
|
||||||
* by Glide. If it has, or was never set, this method returns null.
|
* by Glide. If it has, or was never set, this method returns null.
|
||||||
*/
|
*/
|
||||||
private @Nullable Bitmap getBitmap() {
|
public @Nullable Bitmap getBitmap() {
|
||||||
if (bitmap != null && bitmap.isRecycled()) {
|
if (bitmap != null && bitmap.isRecycled()) {
|
||||||
bitmap = null;
|
bitmap = null;
|
||||||
}
|
}
|
||||||
@ -223,21 +232,48 @@ final class UriGlideRenderer implements Renderer {
|
|||||||
return matrix;
|
return matrix;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static @NonNull Bitmap blur(@NonNull Bitmap bitmap, @NonNull Context context) {
|
private static @NonNull Bitmap blur(Bitmap bitmap, Context context) {
|
||||||
|
Point previewSize = scaleKeepingAspectRatio(new Point(bitmap.getWidth(), bitmap.getHeight()), PREVIEW_DIMENSION_LIMIT);
|
||||||
|
Point blurSize = scaleKeepingAspectRatio(new Point(previewSize.x / 2, previewSize.y / 2 ), MAX_BLUR_DIMENSION);
|
||||||
|
Bitmap small = BitmapUtil.createScaledBitmap(bitmap, blurSize.x, blurSize.y);
|
||||||
|
|
||||||
|
Log.d(TAG, "Bitmap: " + bitmap.getWidth() + "x" + bitmap.getHeight() + ", Blur: " + blurSize.x + "x" + blurSize.y);
|
||||||
|
|
||||||
RenderScript rs = RenderScript.create(context);
|
RenderScript rs = RenderScript.create(context);
|
||||||
Allocation input = Allocation.createFromBitmap(rs, bitmap);
|
Allocation input = Allocation.createFromBitmap(rs, small);
|
||||||
Allocation output = Allocation.createTyped (rs, input.getType());
|
Allocation output = Allocation.createTyped(rs, input.getType());
|
||||||
ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
|
ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
|
||||||
|
|
||||||
script.setRadius(25f);
|
script.setRadius(25f);
|
||||||
script.setInput(input);
|
script.setInput(input);
|
||||||
script.forEach(output);
|
script.forEach(output);
|
||||||
|
|
||||||
Bitmap blurred = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
|
Bitmap blurred = Bitmap.createBitmap(small.getWidth(), small.getHeight(), small.getConfig());
|
||||||
output.copyTo(blurred);
|
output.copyTo(blurred);
|
||||||
return blurred;
|
return blurred;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static @NonNull Point scaleKeepingAspectRatio(@NonNull Point dimens, int maxDimen) {
|
||||||
|
int outX = dimens.x;
|
||||||
|
int outY = dimens.y;
|
||||||
|
|
||||||
|
if (dimens.x > maxDimen || dimens.y > maxDimen) {
|
||||||
|
outX = maxDimen;
|
||||||
|
outY = maxDimen;
|
||||||
|
|
||||||
|
float widthRatio = dimens.x / (float) maxDimen;
|
||||||
|
float heightRatio = dimens.y / (float) maxDimen;
|
||||||
|
|
||||||
|
if (widthRatio > heightRatio) {
|
||||||
|
outY = (int) (dimens.y / widthRatio);
|
||||||
|
} else {
|
||||||
|
outX = (int) (dimens.x / heightRatio);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Point(outX, outY);
|
||||||
|
}
|
||||||
|
|
||||||
public static final Creator<UriGlideRenderer> CREATOR = new Creator<UriGlideRenderer>() {
|
public static final Creator<UriGlideRenderer> CREATOR = new Creator<UriGlideRenderer>() {
|
||||||
@Override
|
@Override
|
||||||
public UriGlideRenderer createFromParcel(Parcel in) {
|
public UriGlideRenderer createFromParcel(Parcel in) {
|
||||||
|
BIN
app/src/main/res/drawable-hdpi/ic_image_editor_blur.png
Normal file
BIN
app/src/main/res/drawable-hdpi/ic_image_editor_blur.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_image_editor_blur.png
Normal file
BIN
app/src/main/res/drawable-mdpi/ic_image_editor_blur.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
BIN
app/src/main/res/drawable-xhdpi/ic_image_editor_blur.png
Normal file
BIN
app/src/main/res/drawable-xhdpi/ic_image_editor_blur.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
BIN
app/src/main/res/drawable-xxhdpi/ic_image_editor_blur.png
Normal file
BIN
app/src/main/res/drawable-xxhdpi/ic_image_editor_blur.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.3 KiB |
BIN
app/src/main/res/drawable-xxxhdpi/ic_image_editor_blur.png
Normal file
BIN
app/src/main/res/drawable-xxxhdpi/ic_image_editor_blur.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.6 KiB |
Binary file not shown.
Before Width: | Height: | Size: 3.9 KiB |
@ -71,7 +71,16 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
android:padding="8dp"
|
android:padding="8dp"
|
||||||
android:src="@drawable/ic_brush_highlight_32" />
|
android:src="@drawable/ic_brush_highlight_32"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/scribble_blur_button"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:src="@drawable/ic_image_editor_blur" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/scribble_sticker_button"
|
android:id="@+id/scribble_sticker_button"
|
||||||
@ -164,6 +173,35 @@
|
|||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent" />
|
app:layout_constraintEnd_toEndOf="parent" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/scribble_blur_toggle_container"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="42dp"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
android:paddingBottom="8dp"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:background="@drawable/transparent_black_pill"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="6dp"
|
||||||
|
android:text="@string/ImageEditorHud_auto_blur_faces"
|
||||||
|
android:textColor="@color/core_white"/>
|
||||||
|
|
||||||
|
<Switch
|
||||||
|
android:id="@+id/scribble_blur_toggle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:checked="false" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
</merge>
|
</merge>
|
@ -558,6 +558,14 @@
|
|||||||
<string name="CropImageActivity_group_avatar">Group avatar</string>
|
<string name="CropImageActivity_group_avatar">Group avatar</string>
|
||||||
<string name="CropImageActivity_profile_avatar">Avatar</string>
|
<string name="CropImageActivity_profile_avatar">Avatar</string>
|
||||||
|
|
||||||
|
<!-- ImageEditorFragment -->
|
||||||
|
<string name="ImageEditorFragment_no_faces_detected">No faces detected</string>
|
||||||
|
|
||||||
|
<!-- ImageEditorHud -->
|
||||||
|
<string name="ImageEditorHud_auto_blur_faces">Auto-blur faces</string>
|
||||||
|
<string name="ImageEditorHud_new_auto_blur_faces_and_blur_brush">New: Auto-blur faces and blur brush</string>
|
||||||
|
<string name="ImageEditorHud_draw_to_blur_or_try_auto_blur">Draw to blur, or try auto-blur</string>
|
||||||
|
|
||||||
<!-- InputPanel -->
|
<!-- InputPanel -->
|
||||||
<string name="InputPanel_tap_and_hold_to_record_a_voice_message_release_to_send">Tap and hold to record a voice message, release to send</string>
|
<string name="InputPanel_tap_and_hold_to_record_a_voice_message_release_to_send">Tap and hold to record a voice message, release to send</string>
|
||||||
|
|
||||||
|
@ -240,6 +240,15 @@ dependencyVerification {
|
|||||||
['com.github.dmytrodanylyk.circular-progress-button:library:1.1.3-S2',
|
['com.github.dmytrodanylyk.circular-progress-button:library:1.1.3-S2',
|
||||||
'8dc6a29a5a8db7b2ad5a9a7fda1dc9ae0893f4c8f0545732b2c63854ea693e8e'],
|
'8dc6a29a5a8db7b2ad5a9a7fda1dc9ae0893f4c8f0545732b2c63854ea693e8e'],
|
||||||
|
|
||||||
|
['com.google.android.datatransport:transport-api:2.2.0',
|
||||||
|
'576514f8b75d8ae32897f1b9b031f88b00465bf6e0996e227d09af688195f71e'],
|
||||||
|
|
||||||
|
['com.google.android.datatransport:transport-backend-cct:2.2.0',
|
||||||
|
'33abba2b7749479ae397176ae482b1807010b2bb331d61264bbdcc799eb398cd'],
|
||||||
|
|
||||||
|
['com.google.android.datatransport:transport-runtime:2.2.0',
|
||||||
|
'e72912014b67151b689a7e820d3f1edf12fe2af5fbc308ab196ac392436ab771'],
|
||||||
|
|
||||||
['com.google.android.exoplayer:exoplayer-core:2.9.1',
|
['com.google.android.exoplayer:exoplayer-core:2.9.1',
|
||||||
'b6ab34abac36bc2bc6934b7a50008162feca2c0fde91aaf1e8c1c22f2c16e2c0'],
|
'b6ab34abac36bc2bc6934b7a50008162feca2c0fde91aaf1e8c1c22f2c16e2c0'],
|
||||||
|
|
||||||
@ -249,26 +258,47 @@ dependencyVerification {
|
|||||||
['com.google.android.gms:play-services-auth-api-phone:16.0.0',
|
['com.google.android.gms:play-services-auth-api-phone:16.0.0',
|
||||||
'19365818b9ceb048ef48db12b5ffadd5eb86dbeb2c7c7b823bfdd89c665f42e5'],
|
'19365818b9ceb048ef48db12b5ffadd5eb86dbeb2c7c7b823bfdd89c665f42e5'],
|
||||||
|
|
||||||
['com.google.android.gms:play-services-auth-base:16.0.0',
|
['com.google.android.gms:play-services-auth-base:17.0.0',
|
||||||
'51dc02ad2f8d1d9dff7b5b52c4df2c6c12ef7df55d752e919d5cb4dd6002ecd0'],
|
'c494d23d5cdc7e4c33721877592868d3dc16085cab535c3f589c03052524f737'],
|
||||||
|
|
||||||
['com.google.android.gms:play-services-auth:16.0.1',
|
['com.google.android.gms:play-services-auth:16.0.1',
|
||||||
'aec9e1c584d442cb9f59481a50b2c66dc191872607c04d97ecb82dd0eb5149ec'],
|
'aec9e1c584d442cb9f59481a50b2c66dc191872607c04d97ecb82dd0eb5149ec'],
|
||||||
|
|
||||||
['com.google.android.gms:play-services-base:16.0.1',
|
['com.google.android.gms:play-services-base:17.0.0',
|
||||||
'aca10c780c3219bc50f3db06734f4ab88badd3113c564c0a3156ff8ff674655b'],
|
'dd0980edf729e0d346e2b58e70801dc237c1aed0c7ab274fa3f1c8c8efc64cc7'],
|
||||||
|
|
||||||
['com.google.android.gms:play-services-basement:16.0.1',
|
['com.google.android.gms:play-services-basement:17.0.0',
|
||||||
'e08bfd1e87c4e50ef76161d7ac76b873aeb975367eeb3afa4abe62ea1887c7c6'],
|
'd324a1785bbc48bfe3639fc847cfd3cf43d49e967b5caf2794240a854557a39c'],
|
||||||
|
|
||||||
|
['com.google.android.gms:play-services-clearcut:17.0.0',
|
||||||
|
'cce72073c269c2b4cff301304751f2faa2cd1b0344fef581a59da63665f9a4b4'],
|
||||||
|
|
||||||
|
['com.google.android.gms:play-services-flags:17.0.0',
|
||||||
|
'746e66b850c5d2b3a0c73871d3fe71ad1b98b62abc0625bbd5badabb73c82cf2'],
|
||||||
|
|
||||||
['com.google.android.gms:play-services-maps:16.1.0',
|
['com.google.android.gms:play-services-maps:16.1.0',
|
||||||
'ff50cae9e4059416202375597d99cdc8ddefd9cea3f1dc2ff53779a3a12eb480'],
|
'ff50cae9e4059416202375597d99cdc8ddefd9cea3f1dc2ff53779a3a12eb480'],
|
||||||
|
|
||||||
['com.google.android.gms:play-services-stats:16.0.1',
|
['com.google.android.gms:play-services-phenotype:17.0.0',
|
||||||
'5b2d8281adbfd6e74d2295c94bab9ea80fc9a84dfbb397995673f5af4d4c6368'],
|
'53d40a205e48ad4e35923a01f04d9850acbd7403b3d30fb388e586fad1540ece'],
|
||||||
|
|
||||||
['com.google.android.gms:play-services-tasks:16.0.1',
|
['com.google.android.gms:play-services-stats:17.0.0',
|
||||||
'b31c18d8d1cc8d9814f295ee7435471333f370ba5bd904ca14f8f2bec4f35c35'],
|
'e8ae5b40512b71e2258bfacd8cd3da398733aa4cde3b32d056093f832b83a6fe'],
|
||||||
|
|
||||||
|
['com.google.android.gms:play-services-tasks:17.0.0',
|
||||||
|
'2e6d1738b73647f3fe7a038b9780b97717b3746eae258009197e36e7bf3112a5'],
|
||||||
|
|
||||||
|
['com.google.android.gms:play-services-vision-common:19.0.2',
|
||||||
|
'b1d93b40a8b49d63d86dfd88ddc4030ab7231d839c5ff3adeb876de94d44b970'],
|
||||||
|
|
||||||
|
['com.google.android.gms:play-services-vision-face-contour-internal:16.0.0',
|
||||||
|
'79e5be6ea321a7c10822f190c45612f1999d37c7bc846d8b01a35478eeb0f985'],
|
||||||
|
|
||||||
|
['com.google.android.gms:play-services-vision-image-label:18.0.3',
|
||||||
|
'aea181d214e170a07f13f537c165750cf81fe4522c4e3df6a845b9aa1dcaa06d'],
|
||||||
|
|
||||||
|
['com.google.android.gms:play-services-vision:20.0.0',
|
||||||
|
'0386c1c32b06c3c771dd518220d47bb5828fa3d415863ecd6859909b52cc4f6f'],
|
||||||
|
|
||||||
['com.google.android.material:material:1.1.0',
|
['com.google.android.material:material:1.1.0',
|
||||||
'58f4fb6e5986ec8e01a733ea85e9df83cf79060e0329fe18abc192d9eda97b26'],
|
'58f4fb6e5986ec8e01a733ea85e9df83cf79060e0329fe18abc192d9eda97b26'],
|
||||||
@ -276,20 +306,47 @@ dependencyVerification {
|
|||||||
['com.google.android:flexbox:0.3.0',
|
['com.google.android:flexbox:0.3.0',
|
||||||
'a9989fd13ae2ee42765dfc515fe362edf4f326e74925d02a10369df8092a4935'],
|
'a9989fd13ae2ee42765dfc515fe362edf4f326e74925d02a10369df8092a4935'],
|
||||||
|
|
||||||
['com.google.auto.value:auto-value-annotations:1.6.3',
|
['com.google.auto.value:auto-value-annotations:1.6.5',
|
||||||
'0e951fee8c31f60270bc46553a8586001b7b93dbb12aec06373aa99a150392c0'],
|
'3677f725f5b1b6cd6a4cc8aa8cf8f5fd2b76d170205cbdc3e9bfd9b58f934b3b'],
|
||||||
|
|
||||||
['com.google.firebase:firebase-common:16.0.3',
|
['com.google.dagger:dagger:2.24',
|
||||||
'3db6bfd4c6f758551e5f9acdeada2050577277e6da1aefb2412de23829759bcf'],
|
'550a6e46a6dfcdf1d764887b6090cea94f783327e50e5c73754f18facfc70b64'],
|
||||||
|
|
||||||
['com.google.firebase:firebase-iid-interop:16.0.1',
|
['com.google.firebase:firebase-common:19.3.0',
|
||||||
'2a86322b9346fd4836219206d249e85803311655e96036a8e4b714ce7e79693b'],
|
'7bd7971470ff943e3c3abb1d7809ef5cb4b81f1996be0867714372b3efa7405a'],
|
||||||
|
|
||||||
['com.google.firebase:firebase-iid:17.0.4',
|
['com.google.firebase:firebase-components:16.0.0',
|
||||||
'bb42774e309d5eac1aa493d19711032bee4f677a409639b6a5cfa93089af93eb'],
|
'8ef43b412de4ec3e36a87c66d8a0a14a3de0a2e8566946da6a0e799b7fdd8ec9'],
|
||||||
|
|
||||||
['com.google.firebase:firebase-messaging:17.3.4',
|
['com.google.firebase:firebase-datatransport:17.0.3',
|
||||||
'e42288e7950d7d3b033d3395a5ac9365d230da3e439a2794ec13e2ef0fbaf078'],
|
'10c9f65c4f897ea33d028e46226daaabdfee43ac712559e5570d21b6b58a067e'],
|
||||||
|
|
||||||
|
['com.google.firebase:firebase-encoders-json:16.0.0',
|
||||||
|
'd1769fcec2a424ee7f92b9996c4b5c1dff0dfa27ceed28981b857b144fb5ec49'],
|
||||||
|
|
||||||
|
['com.google.firebase:firebase-iid-interop:17.0.0',
|
||||||
|
'b6f4ad581eb489370be3bf38a4bdabfc6ea3d4e716234c625a0f42516c53523c'],
|
||||||
|
|
||||||
|
['com.google.firebase:firebase-iid:20.2.0',
|
||||||
|
'1b6977f8ce19becd20b5a1055347e085490d556b4ef98f6666cb25af1d74ff9b'],
|
||||||
|
|
||||||
|
['com.google.firebase:firebase-installations-interop:16.0.0',
|
||||||
|
'd498fe20e7d2c65fc8f7124f1c1791d2828bebdf6bf06ab4cdee13e7fe9ccaa2'],
|
||||||
|
|
||||||
|
['com.google.firebase:firebase-installations:16.3.1',
|
||||||
|
'20427c6899bcbc0390988c958ab7da0352ba84a869817cb6ae9da3b19892af9f'],
|
||||||
|
|
||||||
|
['com.google.firebase:firebase-messaging:20.2.0',
|
||||||
|
'f49cfba49ab33c6fb7436fe9b790b16d3f1265a29955b48fccc1fb1f231da2d8'],
|
||||||
|
|
||||||
|
['com.google.firebase:firebase-ml-common:22.1.1',
|
||||||
|
'74ac365da2578a07b7dd5cd6ca4ae6d7279c7010153025d081afa5db0dce6d57'],
|
||||||
|
|
||||||
|
['com.google.firebase:firebase-ml-vision-face-model:20.0.1',
|
||||||
|
'e81fc985d9e680be0b18891fa8d108f546173c5da2fd923d787fd13759db3b8a'],
|
||||||
|
|
||||||
|
['com.google.firebase:firebase-ml-vision:24.0.3',
|
||||||
|
'afe0d27eebcb8c52a1e40f1e147b750456e7e02747b7e8f3b9d7f3aa58922c78'],
|
||||||
|
|
||||||
['com.google.guava:listenablefuture:1.0',
|
['com.google.guava:listenablefuture:1.0',
|
||||||
'e4ad7607e5c0477c6f890ef26a49cb8d1bb4dffb650bab4502afee64644e3069'],
|
'e4ad7607e5c0477c6f890ef26a49cb8d1bb4dffb650bab4502afee64644e3069'],
|
||||||
@ -345,6 +402,9 @@ dependencyVerification {
|
|||||||
['dnsjava:dnsjava:2.1.9',
|
['dnsjava:dnsjava:2.1.9',
|
||||||
'072bba34267ffad8907c30a99a6b68f900782f3191454d278e395e289d478446'],
|
'072bba34267ffad8907c30a99a6b68f900782f3191454d278e395e289d478446'],
|
||||||
|
|
||||||
|
['javax.inject:javax.inject:1',
|
||||||
|
'91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff'],
|
||||||
|
|
||||||
['me.leolin:ShortcutBadger:1.1.16',
|
['me.leolin:ShortcutBadger:1.1.16',
|
||||||
'e3cb3e7625892129b0c92dd5e4bc649faffdd526d5af26d9c45ee31ff8851774'],
|
'e3cb3e7625892129b0c92dd5e4bc649faffdd526d5af26d9c45ee31ff8851774'],
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user