66陪玩-origin
9
.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
*.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.idea
|
||||
.settings
|
17
.project
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>Qx_Andriod_Client</name>
|
||||
<comment>Project Qx_Andriod_Client created by Buildship.</comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
3
agora-ktv-kit-release/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
/build
|
||||
*.iml
|
||||
*.DS_Store
|
BIN
agora-ktv-kit-release/agora-ktv-kit-release.aar
Normal file
2
agora-ktv-kit-release/build.gradle
Normal file
@@ -0,0 +1,2 @@
|
||||
configurations.maybeCreate("default")
|
||||
artifacts.add("default", file('agora-ktv-kit-release.aar'))
|
3
android_crop_lib/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
/build
|
||||
*.iml
|
||||
*.DS_Store
|
26
android_crop_lib/build.gradle
Normal file
@@ -0,0 +1,26 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion 26
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 26
|
||||
|
||||
testApplicationId 'com.soundcloud.android.crop.test'
|
||||
testInstrumentationRunner 'android.test.InstrumentationTestRunner'
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api 'com.android.support:support-annotations:23.0.1'
|
||||
api 'com.android.support:support-v4:26.0.1'
|
||||
}
|
||||
|
1
android_crop_lib/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="com.soundcloud.android.crop" />
|
@@ -0,0 +1,266 @@
|
||||
package com.soundcloud.android.crop;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.MediaStore;
|
||||
import android.widget.Toast;
|
||||
|
||||
/**
|
||||
* Builder for crop Intents and utils for handling result
|
||||
*/
|
||||
public class Crop {
|
||||
|
||||
public static final int REQUEST_CROP = 6709;
|
||||
public static final int REQUEST_PICK = 9162;
|
||||
public static final int RESULT_ERROR = 404;
|
||||
|
||||
interface Extra {
|
||||
String ASPECT_X = "aspect_x";
|
||||
String ASPECT_Y = "aspect_y";
|
||||
String MAX_X = "max_x";
|
||||
String MAX_Y = "max_y";
|
||||
String AS_PNG = "as_png";
|
||||
String ERROR = "error";
|
||||
}
|
||||
|
||||
private Intent cropIntent;
|
||||
|
||||
/**
|
||||
* Create a crop Intent builder with source and destination image Uris
|
||||
*
|
||||
* @param source Uri for image to crop
|
||||
* @param destination Uri for saving the cropped image
|
||||
*/
|
||||
public static Crop of(Uri source, Uri destination) {
|
||||
return new Crop(source, destination);
|
||||
}
|
||||
|
||||
private Crop(Uri source, Uri destination) {
|
||||
cropIntent = new Intent();
|
||||
cropIntent.setData(source);
|
||||
cropIntent.putExtra(MediaStore.EXTRA_OUTPUT, destination);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set fixed aspect ratio for crop area
|
||||
*
|
||||
* @param x Aspect X
|
||||
* @param y Aspect Y
|
||||
*/
|
||||
public Crop withAspect(int x, int y) {
|
||||
cropIntent.putExtra(Extra.ASPECT_X, x);
|
||||
cropIntent.putExtra(Extra.ASPECT_Y, y);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Crop area with fixed 1:1 aspect ratio
|
||||
*/
|
||||
public Crop asSquare() {
|
||||
cropIntent.putExtra(Extra.ASPECT_X, 1);
|
||||
cropIntent.putExtra(Extra.ASPECT_Y, 1);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set maximum crop size
|
||||
*
|
||||
* @param width Max width
|
||||
* @param height Max height
|
||||
*/
|
||||
public Crop withMaxSize(int width, int height) {
|
||||
cropIntent.putExtra(Extra.MAX_X, width);
|
||||
cropIntent.putExtra(Extra.MAX_Y, height);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to save the result as a PNG or not. Helpful to preserve alpha.
|
||||
* @param asPng whether to save the result as a PNG or not
|
||||
*/
|
||||
public Crop asPng(boolean asPng) {
|
||||
cropIntent.putExtra(Extra.AS_PNG, asPng);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the crop Intent from an Activity
|
||||
*
|
||||
* @param activity Activity to receive result
|
||||
*/
|
||||
public void start(Activity activity) {
|
||||
start(activity, REQUEST_CROP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the crop Intent from an Activity with a custom request code
|
||||
*
|
||||
* @param activity Activity to receive result
|
||||
* @param requestCode requestCode for result
|
||||
*/
|
||||
public void start(Activity activity, int requestCode) {
|
||||
activity.startActivityForResult(getIntent(activity), requestCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the crop Intent from a Fragment
|
||||
*
|
||||
* @param context Context
|
||||
* @param fragment Fragment to receive result
|
||||
*/
|
||||
public void start(Context context, Fragment fragment) {
|
||||
start(context, fragment, REQUEST_CROP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the crop Intent from a support library Fragment
|
||||
*
|
||||
* @param context Context
|
||||
* @param fragment Fragment to receive result
|
||||
*/
|
||||
public void start(Context context, android.support.v4.app.Fragment fragment) {
|
||||
start(context, fragment, REQUEST_CROP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the crop Intent with a custom request code
|
||||
*
|
||||
* @param context Context
|
||||
* @param fragment Fragment to receive result
|
||||
* @param requestCode requestCode for result
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
public void start(Context context, Fragment fragment, int requestCode) {
|
||||
fragment.startActivityForResult(getIntent(context), requestCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the crop Intent with a custom request code
|
||||
*
|
||||
* @param context Context
|
||||
* @param fragment Fragment to receive result
|
||||
* @param requestCode requestCode for result
|
||||
*/
|
||||
public void start(Context context, android.support.v4.app.Fragment fragment, int requestCode) {
|
||||
fragment.startActivityForResult(getIntent(context), requestCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Intent to start crop Activity
|
||||
*
|
||||
* @param context Context
|
||||
* @return Intent for CropImageActivity
|
||||
*/
|
||||
public Intent getIntent(Context context) {
|
||||
cropIntent.setClass(context, CropImageActivity.class);
|
||||
return cropIntent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve URI for cropped image, as set in the Intent builder
|
||||
*
|
||||
* @param result Output Image URI
|
||||
*/
|
||||
public static Uri getOutput(Intent result) {
|
||||
return result.getParcelableExtra(MediaStore.EXTRA_OUTPUT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve error that caused crop to fail
|
||||
*
|
||||
* @param result Result Intent
|
||||
* @return Throwable handled in CropImageActivity
|
||||
*/
|
||||
public static Throwable getError(Intent result) {
|
||||
return (Throwable) result.getSerializableExtra(Extra.ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick image from an Activity
|
||||
*
|
||||
* @param activity Activity to receive result
|
||||
*/
|
||||
public static void pickImage(Activity activity) {
|
||||
pickImage(activity, REQUEST_PICK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick image from a Fragment
|
||||
*
|
||||
* @param context Context
|
||||
* @param fragment Fragment to receive result
|
||||
*/
|
||||
public static void pickImage(Context context, Fragment fragment) {
|
||||
pickImage(context, fragment, REQUEST_PICK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick image from a support library Fragment
|
||||
*
|
||||
* @param context Context
|
||||
* @param fragment Fragment to receive result
|
||||
*/
|
||||
public static void pickImage(Context context, android.support.v4.app.Fragment fragment) {
|
||||
pickImage(context, fragment, REQUEST_PICK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick image from an Activity with a custom request code
|
||||
*
|
||||
* @param activity Activity to receive result
|
||||
* @param requestCode requestCode for result
|
||||
*/
|
||||
public static void pickImage(Activity activity, int requestCode) {
|
||||
try {
|
||||
activity.startActivityForResult(getImagePicker(), requestCode);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
showImagePickerError(activity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick image from a Fragment with a custom request code
|
||||
*
|
||||
* @param context Context
|
||||
* @param fragment Fragment to receive result
|
||||
* @param requestCode requestCode for result
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
public static void pickImage(Context context, Fragment fragment, int requestCode) {
|
||||
try {
|
||||
fragment.startActivityForResult(getImagePicker(), requestCode);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
showImagePickerError(context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick image from a support library Fragment with a custom request code
|
||||
*
|
||||
* @param context Context
|
||||
* @param fragment Fragment to receive result
|
||||
* @param requestCode requestCode for result
|
||||
*/
|
||||
public static void pickImage(Context context, android.support.v4.app.Fragment fragment, int requestCode) {
|
||||
try {
|
||||
fragment.startActivityForResult(getImagePicker(), requestCode);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
showImagePickerError(context);
|
||||
}
|
||||
}
|
||||
|
||||
private static Intent getImagePicker() {
|
||||
return new Intent(Intent.ACTION_GET_CONTENT).setType("image/*");
|
||||
}
|
||||
|
||||
private static void showImagePickerError(Context context) {
|
||||
Toast.makeText(context.getApplicationContext(), R.string.crop__pick_error, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,441 @@
|
||||
/*
|
||||
* Copyright (C) 2007 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.soundcloud.android.crop;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.BitmapRegionDecoder;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.net.Uri;
|
||||
import android.opengl.GLES10;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.provider.MediaStore;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
/*
|
||||
* Modified from original in AOSP.
|
||||
*/
|
||||
public class CropImageActivity extends MonitoredActivity {
|
||||
|
||||
private static final int SIZE_DEFAULT = 2048;
|
||||
private static final int SIZE_LIMIT = 4096;
|
||||
|
||||
private final Handler handler = new Handler();
|
||||
|
||||
private int aspectX;
|
||||
private int aspectY;
|
||||
|
||||
// Output image
|
||||
private int maxX;
|
||||
private int maxY;
|
||||
private int exifRotation;
|
||||
private boolean saveAsPng;
|
||||
|
||||
private Uri sourceUri;
|
||||
private Uri saveUri;
|
||||
|
||||
private boolean isSaving;
|
||||
|
||||
private int sampleSize;
|
||||
private RotateBitmap rotateBitmap;
|
||||
private CropImageView imageView;
|
||||
private HighlightView cropView;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
setupWindowFlags();
|
||||
setupViews();
|
||||
|
||||
loadInput();
|
||||
if (rotateBitmap == null) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
startCrop();
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||
private void setupWindowFlags() {
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
||||
}
|
||||
}
|
||||
|
||||
private void setupViews() {
|
||||
setContentView(R.layout.crop__activity_crop);
|
||||
|
||||
imageView = (CropImageView) findViewById(R.id.crop_image);
|
||||
imageView.context = this;
|
||||
imageView.setRecycler(new ImageViewTouchBase.Recycler() {
|
||||
@Override
|
||||
public void recycle(Bitmap b) {
|
||||
b.recycle();
|
||||
System.gc();
|
||||
}
|
||||
});
|
||||
|
||||
findViewById(R.id.btn_cancel).setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
findViewById(R.id.btn_done).setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
onSaveClicked();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadInput() {
|
||||
Intent intent = getIntent();
|
||||
Bundle extras = intent.getExtras();
|
||||
|
||||
if (extras != null) {
|
||||
aspectX = extras.getInt(Crop.Extra.ASPECT_X);
|
||||
aspectY = extras.getInt(Crop.Extra.ASPECT_Y);
|
||||
maxX = extras.getInt(Crop.Extra.MAX_X);
|
||||
maxY = extras.getInt(Crop.Extra.MAX_Y);
|
||||
saveAsPng = extras.getBoolean(Crop.Extra.AS_PNG, false);
|
||||
saveUri = extras.getParcelable(MediaStore.EXTRA_OUTPUT);
|
||||
}
|
||||
|
||||
sourceUri = intent.getData();
|
||||
if (sourceUri != null) {
|
||||
exifRotation = CropUtil.getExifRotation(CropUtil.getFromMediaUri(this, getContentResolver(), sourceUri));
|
||||
|
||||
InputStream is = null;
|
||||
try {
|
||||
sampleSize = calculateBitmapSampleSize(sourceUri);
|
||||
is = getContentResolver().openInputStream(sourceUri);
|
||||
BitmapFactory.Options option = new BitmapFactory.Options();
|
||||
option.inSampleSize = sampleSize;
|
||||
rotateBitmap = new RotateBitmap(BitmapFactory.decodeStream(is, null, option), exifRotation);
|
||||
} catch (IOException e) {
|
||||
Log.e("Error reading image: " + e.getMessage(), e);
|
||||
setResultException(e);
|
||||
} catch (OutOfMemoryError e) {
|
||||
Log.e("OOM reading image: " + e.getMessage(), e);
|
||||
setResultException(e);
|
||||
} finally {
|
||||
CropUtil.closeSilently(is);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int calculateBitmapSampleSize(Uri bitmapUri) throws IOException {
|
||||
InputStream is = null;
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
try {
|
||||
is = getContentResolver().openInputStream(bitmapUri);
|
||||
BitmapFactory.decodeStream(is, null, options); // Just get image size
|
||||
} finally {
|
||||
CropUtil.closeSilently(is);
|
||||
}
|
||||
|
||||
int maxSize = getMaxImageSize();
|
||||
int sampleSize = 1;
|
||||
while (options.outHeight / sampleSize > maxSize || options.outWidth / sampleSize > maxSize) {
|
||||
sampleSize = sampleSize << 1;
|
||||
}
|
||||
return sampleSize;
|
||||
}
|
||||
|
||||
private int getMaxImageSize() {
|
||||
int textureLimit = getMaxTextureSize();
|
||||
if (textureLimit == 0) {
|
||||
return SIZE_DEFAULT;
|
||||
} else {
|
||||
return Math.min(textureLimit, SIZE_LIMIT);
|
||||
}
|
||||
}
|
||||
|
||||
private int getMaxTextureSize() {
|
||||
// The OpenGL texture size is the maximum size that can be drawn in an ImageView
|
||||
int[] maxSize = new int[1];
|
||||
GLES10.glGetIntegerv(GLES10.GL_MAX_TEXTURE_SIZE, maxSize, 0);
|
||||
return maxSize[0];
|
||||
}
|
||||
|
||||
private void startCrop() {
|
||||
if (isFinishing()) {
|
||||
return;
|
||||
}
|
||||
imageView.setImageRotateBitmapResetBase(rotateBitmap, true);
|
||||
CropUtil.startBackgroundJob(this, null, getResources().getString(R.string.crop__wait),
|
||||
new Runnable() {
|
||||
public void run() {
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
handler.post(new Runnable() {
|
||||
public void run() {
|
||||
if (imageView.getScale() == 1F) {
|
||||
imageView.center();
|
||||
}
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
try {
|
||||
latch.await();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
new Cropper().crop();
|
||||
}
|
||||
}, handler
|
||||
);
|
||||
}
|
||||
|
||||
private class Cropper {
|
||||
|
||||
private void makeDefault() {
|
||||
if (rotateBitmap == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
HighlightView hv = new HighlightView(imageView);
|
||||
final int width = rotateBitmap.getWidth();
|
||||
final int height = rotateBitmap.getHeight();
|
||||
|
||||
Rect imageRect = new Rect(0, 0, width, height);
|
||||
|
||||
// Make the default size about 4/5 of the width or height
|
||||
int cropWidth = Math.min(width, height) * 4 / 5;
|
||||
@SuppressWarnings("SuspiciousNameCombination")
|
||||
int cropHeight = cropWidth;
|
||||
|
||||
if (aspectX != 0 && aspectY != 0) {
|
||||
if (aspectX > aspectY) {
|
||||
cropHeight = cropWidth * aspectY / aspectX;
|
||||
} else {
|
||||
cropWidth = cropHeight * aspectX / aspectY;
|
||||
}
|
||||
}
|
||||
|
||||
int x = (width - cropWidth) / 2;
|
||||
int y = (height - cropHeight) / 2;
|
||||
|
||||
RectF cropRect = new RectF(x, y, x + cropWidth, y + cropHeight);
|
||||
hv.setup(imageView.getUnrotatedMatrix(), imageRect, cropRect, aspectX != 0 && aspectY != 0);
|
||||
imageView.add(hv);
|
||||
}
|
||||
|
||||
public void crop() {
|
||||
handler.post(new Runnable() {
|
||||
public void run() {
|
||||
makeDefault();
|
||||
imageView.invalidate();
|
||||
if (imageView.highlightViews.size() == 1) {
|
||||
cropView = imageView.highlightViews.get(0);
|
||||
cropView.setFocus(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void onSaveClicked() {
|
||||
if (cropView == null || isSaving) {
|
||||
return;
|
||||
}
|
||||
isSaving = true;
|
||||
|
||||
Bitmap croppedImage;
|
||||
Rect r = cropView.getScaledCropRect(sampleSize);
|
||||
int width = r.width();
|
||||
int height = r.height();
|
||||
|
||||
int outWidth = width;
|
||||
int outHeight = height;
|
||||
if (maxX > 0 && maxY > 0 && (width > maxX || height > maxY)) {
|
||||
float ratio = (float) width / (float) height;
|
||||
if ((float) maxX / (float) maxY > ratio) {
|
||||
outHeight = maxY;
|
||||
outWidth = (int) ((float) maxY * ratio + .5f);
|
||||
} else {
|
||||
outWidth = maxX;
|
||||
outHeight = (int) ((float) maxX / ratio + .5f);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
croppedImage = decodeRegionCrop(r, outWidth, outHeight);
|
||||
} catch (IllegalArgumentException e) {
|
||||
setResultException(e);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (croppedImage != null) {
|
||||
imageView.setImageRotateBitmapResetBase(new RotateBitmap(croppedImage, exifRotation), true);
|
||||
imageView.center();
|
||||
imageView.highlightViews.clear();
|
||||
}
|
||||
saveImage(croppedImage);
|
||||
}
|
||||
|
||||
private void saveImage(Bitmap croppedImage) {
|
||||
if (croppedImage != null) {
|
||||
final Bitmap b = croppedImage;
|
||||
CropUtil.startBackgroundJob(this, null, getResources().getString(R.string.crop__saving),
|
||||
new Runnable() {
|
||||
public void run() {
|
||||
saveOutput(b);
|
||||
}
|
||||
}, handler
|
||||
);
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private Bitmap decodeRegionCrop(Rect rect, int outWidth, int outHeight) {
|
||||
// Release memory now
|
||||
clearImageView();
|
||||
|
||||
InputStream is = null;
|
||||
Bitmap croppedImage = null;
|
||||
try {
|
||||
is = getContentResolver().openInputStream(sourceUri);
|
||||
BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false);
|
||||
final int width = decoder.getWidth();
|
||||
final int height = decoder.getHeight();
|
||||
|
||||
if (exifRotation != 0) {
|
||||
// Adjust crop area to account for image rotation
|
||||
Matrix matrix = new Matrix();
|
||||
matrix.setRotate(-exifRotation);
|
||||
|
||||
RectF adjusted = new RectF();
|
||||
matrix.mapRect(adjusted, new RectF(rect));
|
||||
|
||||
// Adjust to account for origin at 0,0
|
||||
adjusted.offset(adjusted.left < 0 ? width : 0, adjusted.top < 0 ? height : 0);
|
||||
rect = new Rect((int) adjusted.left, (int) adjusted.top, (int) adjusted.right, (int) adjusted.bottom);
|
||||
}
|
||||
|
||||
try {
|
||||
croppedImage = decoder.decodeRegion(rect, new BitmapFactory.Options());
|
||||
if (croppedImage != null && (rect.width() > outWidth || rect.height() > outHeight)) {
|
||||
Matrix matrix = new Matrix();
|
||||
matrix.postScale((float) outWidth / rect.width(), (float) outHeight / rect.height());
|
||||
croppedImage = Bitmap.createBitmap(croppedImage, 0, 0, croppedImage.getWidth(), croppedImage.getHeight(), matrix, true);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Rethrow with some extra information
|
||||
throw new IllegalArgumentException("Rectangle " + rect + " is outside of the image ("
|
||||
+ width + "," + height + "," + exifRotation + ")", e);
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
Log.e("Error cropping image: " + e.getMessage(), e);
|
||||
setResultException(e);
|
||||
} catch (OutOfMemoryError e) {
|
||||
Log.e("OOM cropping image: " + e.getMessage(), e);
|
||||
setResultException(e);
|
||||
} finally {
|
||||
CropUtil.closeSilently(is);
|
||||
}
|
||||
return croppedImage;
|
||||
}
|
||||
|
||||
private void clearImageView() {
|
||||
imageView.clear();
|
||||
if (rotateBitmap != null) {
|
||||
rotateBitmap.recycle();
|
||||
}
|
||||
System.gc();
|
||||
}
|
||||
|
||||
private void saveOutput(Bitmap croppedImage) {
|
||||
if (saveUri != null) {
|
||||
OutputStream outputStream = null;
|
||||
try {
|
||||
outputStream = getContentResolver().openOutputStream(saveUri);
|
||||
if (outputStream != null) {
|
||||
croppedImage.compress(saveAsPng ? Bitmap.CompressFormat.PNG : Bitmap.CompressFormat.JPEG,
|
||||
90, // note: quality is ignored when using PNG
|
||||
outputStream);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
setResultException(e);
|
||||
Log.e("Cannot open file: " + saveUri, e);
|
||||
} finally {
|
||||
CropUtil.closeSilently(outputStream);
|
||||
}
|
||||
|
||||
CropUtil.copyExifRotation(
|
||||
CropUtil.getFromMediaUri(this, getContentResolver(), sourceUri),
|
||||
CropUtil.getFromMediaUri(this, getContentResolver(), saveUri)
|
||||
);
|
||||
|
||||
setResultUri(saveUri);
|
||||
}
|
||||
|
||||
final Bitmap b = croppedImage;
|
||||
handler.post(new Runnable() {
|
||||
public void run() {
|
||||
imageView.clear();
|
||||
b.recycle();
|
||||
}
|
||||
});
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (rotateBitmap != null) {
|
||||
rotateBitmap.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSearchRequested() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isSaving() {
|
||||
return isSaving;
|
||||
}
|
||||
|
||||
private void setResultUri(Uri uri) {
|
||||
setResult(RESULT_OK, new Intent().putExtra(MediaStore.EXTRA_OUTPUT, uri));
|
||||
}
|
||||
|
||||
private void setResultException(Throwable throwable) {
|
||||
setResult(Crop.RESULT_ERROR, new Intent().putExtra(Crop.Extra.ERROR, throwable));
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,195 @@
|
||||
package com.soundcloud.android.crop;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class CropImageView extends ImageViewTouchBase {
|
||||
|
||||
ArrayList<HighlightView> highlightViews = new ArrayList<HighlightView>();
|
||||
HighlightView motionHighlightView;
|
||||
Context context;
|
||||
|
||||
private float lastX;
|
||||
private float lastY;
|
||||
private int motionEdge;
|
||||
private int validPointerId;
|
||||
|
||||
public CropImageView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public CropImageView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public CropImageView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
if (bitmapDisplayed.getBitmap() != null) {
|
||||
for (HighlightView hv : highlightViews) {
|
||||
|
||||
hv.matrix.set(getUnrotatedMatrix());
|
||||
hv.invalidate();
|
||||
if (hv.hasFocus()) {
|
||||
centerBasedOnHighlightView(hv);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void zoomTo(float scale, float centerX, float centerY) {
|
||||
super.zoomTo(scale, centerX, centerY);
|
||||
for (HighlightView hv : highlightViews) {
|
||||
hv.matrix.set(getUnrotatedMatrix());
|
||||
hv.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void zoomIn() {
|
||||
super.zoomIn();
|
||||
for (HighlightView hv : highlightViews) {
|
||||
hv.matrix.set(getUnrotatedMatrix());
|
||||
hv.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void zoomOut() {
|
||||
super.zoomOut();
|
||||
for (HighlightView hv : highlightViews) {
|
||||
hv.matrix.set(getUnrotatedMatrix());
|
||||
hv.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void postTranslate(float deltaX, float deltaY) {
|
||||
super.postTranslate(deltaX, deltaY);
|
||||
for (HighlightView hv : highlightViews) {
|
||||
hv.matrix.postTranslate(deltaX, deltaY);
|
||||
hv.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(@NonNull MotionEvent event) {
|
||||
CropImageActivity cropImageActivity = (CropImageActivity) context;
|
||||
if (cropImageActivity.isSaving()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
for (HighlightView hv : highlightViews) {
|
||||
int edge = hv.getHit(event.getX(), event.getY());
|
||||
if (edge != HighlightView.GROW_NONE) {
|
||||
motionEdge = edge;
|
||||
motionHighlightView = hv;
|
||||
lastX = event.getX();
|
||||
lastY = event.getY();
|
||||
// Prevent multiple touches from interfering with crop area re-sizing
|
||||
validPointerId = event.getPointerId(event.getActionIndex());
|
||||
motionHighlightView.setMode((edge == HighlightView.MOVE)
|
||||
? HighlightView.ModifyMode.Move
|
||||
: HighlightView.ModifyMode.Grow);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
if (motionHighlightView != null) {
|
||||
centerBasedOnHighlightView(motionHighlightView);
|
||||
motionHighlightView.setMode(HighlightView.ModifyMode.None);
|
||||
}
|
||||
motionHighlightView = null;
|
||||
center();
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
if (motionHighlightView != null && event.getPointerId(event.getActionIndex()) == validPointerId) {
|
||||
motionHighlightView.handleMotion(motionEdge, event.getX()
|
||||
- lastX, event.getY() - lastY);
|
||||
lastX = event.getX();
|
||||
lastY = event.getY();
|
||||
}
|
||||
|
||||
// If we're not zoomed then there's no point in even allowing the user to move the image around.
|
||||
// This call to center puts it back to the normalized location.
|
||||
if (getScale() == 1F) {
|
||||
center();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Pan the displayed image to make sure the cropping rectangle is visible.
|
||||
private void ensureVisible(HighlightView hv) {
|
||||
Rect r = hv.drawRect;
|
||||
|
||||
int panDeltaX1 = Math.max(0, getLeft() - r.left);
|
||||
int panDeltaX2 = Math.min(0, getRight() - r.right);
|
||||
|
||||
int panDeltaY1 = Math.max(0, getTop() - r.top);
|
||||
int panDeltaY2 = Math.min(0, getBottom() - r.bottom);
|
||||
|
||||
int panDeltaX = panDeltaX1 != 0 ? panDeltaX1 : panDeltaX2;
|
||||
int panDeltaY = panDeltaY1 != 0 ? panDeltaY1 : panDeltaY2;
|
||||
|
||||
if (panDeltaX != 0 || panDeltaY != 0) {
|
||||
panBy(panDeltaX, panDeltaY);
|
||||
}
|
||||
}
|
||||
|
||||
// If the cropping rectangle's size changed significantly, change the
|
||||
// view's center and scale according to the cropping rectangle.
|
||||
private void centerBasedOnHighlightView(HighlightView hv) {
|
||||
Rect drawRect = hv.drawRect;
|
||||
|
||||
float width = drawRect.width();
|
||||
float height = drawRect.height();
|
||||
|
||||
float thisWidth = getWidth();
|
||||
float thisHeight = getHeight();
|
||||
|
||||
float z1 = thisWidth / width * .6F;
|
||||
float z2 = thisHeight / height * .6F;
|
||||
|
||||
float zoom = Math.min(z1, z2);
|
||||
zoom = zoom * this.getScale();
|
||||
zoom = Math.max(1F, zoom);
|
||||
|
||||
if ((Math.abs(zoom - getScale()) / zoom) > .1) {
|
||||
float[] coordinates = new float[] { hv.cropRect.centerX(), hv.cropRect.centerY() };
|
||||
getUnrotatedMatrix().mapPoints(coordinates);
|
||||
zoomTo(zoom, coordinates[0], coordinates[1], 300F);
|
||||
}
|
||||
|
||||
ensureVisible(hv);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(@NonNull Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
for (HighlightView highlightView : highlightViews) {
|
||||
highlightView.draw(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
public void add(HighlightView hv) {
|
||||
highlightViews.add(hv);
|
||||
invalidate();
|
||||
}
|
||||
}
|
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* Copyright (C) 2009 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.soundcloud.android.crop;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.media.ExifInterface;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.MediaStore;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/*
|
||||
* Modified from original in AOSP.
|
||||
*/
|
||||
class CropUtil {
|
||||
|
||||
private static final String SCHEME_FILE = "file";
|
||||
private static final String SCHEME_CONTENT = "content";
|
||||
|
||||
public static void closeSilently(@Nullable Closeable c) {
|
||||
if (c == null) return;
|
||||
try {
|
||||
c.close();
|
||||
} catch (Throwable t) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
public static int getExifRotation(File imageFile) {
|
||||
if (imageFile == null) return 0;
|
||||
try {
|
||||
ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
|
||||
// We only recognize a subset of orientation tag values
|
||||
switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED)) {
|
||||
case ExifInterface.ORIENTATION_ROTATE_90:
|
||||
return 90;
|
||||
case ExifInterface.ORIENTATION_ROTATE_180:
|
||||
return 180;
|
||||
case ExifInterface.ORIENTATION_ROTATE_270:
|
||||
return 270;
|
||||
default:
|
||||
return ExifInterface.ORIENTATION_UNDEFINED;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e("Error getting Exif data", e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean copyExifRotation(File sourceFile, File destFile) {
|
||||
if (sourceFile == null || destFile == null) return false;
|
||||
try {
|
||||
ExifInterface exifSource = new ExifInterface(sourceFile.getAbsolutePath());
|
||||
ExifInterface exifDest = new ExifInterface(destFile.getAbsolutePath());
|
||||
exifDest.setAttribute(ExifInterface.TAG_ORIENTATION, exifSource.getAttribute(ExifInterface.TAG_ORIENTATION));
|
||||
exifDest.saveAttributes();
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
Log.e("Error copying Exif data", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static File getFromMediaUri(Context context, ContentResolver resolver, Uri uri) {
|
||||
if (uri == null) return null;
|
||||
|
||||
if (SCHEME_FILE.equals(uri.getScheme())) {
|
||||
return new File(uri.getPath());
|
||||
} else if (SCHEME_CONTENT.equals(uri.getScheme())) {
|
||||
final String[] filePathColumn = { MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.DISPLAY_NAME };
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = resolver.query(uri, filePathColumn, null, null, null);
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
final int columnIndex = (uri.toString().startsWith("content://com.google.android.gallery3d")) ?
|
||||
cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME) :
|
||||
cursor.getColumnIndex(MediaStore.MediaColumns.DATA);
|
||||
// Picasa images on API 13+
|
||||
if (columnIndex != -1) {
|
||||
String filePath = cursor.getString(columnIndex);
|
||||
if (!TextUtils.isEmpty(filePath)) {
|
||||
return new File(filePath);
|
||||
}
|
||||
} else if (TextUtils.equals(uri.getAuthority(), UriUtil.getFileProviderName(context))) {
|
||||
//这里修复拍照自定义的uri获取路径失败的问题
|
||||
String path = UriUtil.parseOwnUri(context, uri);
|
||||
if (!TextUtils.isEmpty(path)) {
|
||||
File file = new File(path);
|
||||
if (file.exists()) {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Google Drive images
|
||||
return getFromMediaUriPfd(context, resolver, uri);
|
||||
} catch (SecurityException ignored) {
|
||||
// Nothing we can do
|
||||
} finally {
|
||||
if (cursor != null) cursor.close();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String getTempFilename(Context context) throws IOException {
|
||||
File outputDir = context.getCacheDir();
|
||||
File outputFile = File.createTempFile("image", "tmp", outputDir);
|
||||
return outputFile.getAbsolutePath();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static File getFromMediaUriPfd(Context context, ContentResolver resolver, Uri uri) {
|
||||
if (uri == null) return null;
|
||||
|
||||
FileInputStream input = null;
|
||||
FileOutputStream output = null;
|
||||
try {
|
||||
ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r");
|
||||
FileDescriptor fd = pfd.getFileDescriptor();
|
||||
input = new FileInputStream(fd);
|
||||
|
||||
String tempFilename = getTempFilename(context);
|
||||
output = new FileOutputStream(tempFilename);
|
||||
|
||||
int read;
|
||||
byte[] bytes = new byte[4096];
|
||||
while ((read = input.read(bytes)) != -1) {
|
||||
output.write(bytes, 0, read);
|
||||
}
|
||||
return new File(tempFilename);
|
||||
} catch (IOException ignored) {
|
||||
// Nothing we can do
|
||||
} finally {
|
||||
closeSilently(input);
|
||||
closeSilently(output);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void startBackgroundJob(MonitoredActivity activity,
|
||||
String title, String message, Runnable job, Handler handler) {
|
||||
// Make the progress dialog uncancelable, so that we can guarantee
|
||||
// the thread will be done before the activity getting destroyed
|
||||
ProgressDialog dialog = ProgressDialog.show(
|
||||
activity, title, message, true, false);
|
||||
new Thread(new BackgroundJob(activity, job, dialog, handler)).start();
|
||||
}
|
||||
|
||||
private static class BackgroundJob extends MonitoredActivity.LifeCycleAdapter implements Runnable {
|
||||
|
||||
private final MonitoredActivity activity;
|
||||
private final ProgressDialog dialog;
|
||||
private final Runnable job;
|
||||
private final Handler handler;
|
||||
private final Runnable cleanupRunner = new Runnable() {
|
||||
public void run() {
|
||||
activity.removeLifeCycleListener(BackgroundJob.this);
|
||||
if (dialog.getWindow() != null) dialog.dismiss();
|
||||
}
|
||||
};
|
||||
|
||||
public BackgroundJob(MonitoredActivity activity, Runnable job,
|
||||
ProgressDialog dialog, Handler handler) {
|
||||
this.activity = activity;
|
||||
this.dialog = dialog;
|
||||
this.job = job;
|
||||
this.activity.addLifeCycleListener(this);
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
job.run();
|
||||
} finally {
|
||||
handler.post(cleanupRunner);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityDestroyed(MonitoredActivity activity) {
|
||||
// We get here only when the onDestroyed being called before
|
||||
// the cleanupRunner. So, run it now and remove it from the queue
|
||||
cleanupRunner.run();
|
||||
handler.removeCallbacks(cleanupRunner);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityStopped(MonitoredActivity activity) {
|
||||
dialog.hide();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityStarted(MonitoredActivity activity) {
|
||||
dialog.show();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,395 @@
|
||||
/*
|
||||
* Copyright (C) 2007 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.soundcloud.android.crop;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Region;
|
||||
import android.os.Build;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
|
||||
/*
|
||||
* Modified from version in AOSP.
|
||||
*
|
||||
* This class is used to display a highlighted cropping rectangle
|
||||
* overlayed on the image. There are two coordinate spaces in use. One is
|
||||
* image, another is screen. computeLayout() uses matrix to map from image
|
||||
* space to screen space.
|
||||
*/
|
||||
class HighlightView {
|
||||
|
||||
public static final int GROW_NONE = (1 << 0);
|
||||
public static final int GROW_LEFT_EDGE = (1 << 1);
|
||||
public static final int GROW_RIGHT_EDGE = (1 << 2);
|
||||
public static final int GROW_TOP_EDGE = (1 << 3);
|
||||
public static final int GROW_BOTTOM_EDGE = (1 << 4);
|
||||
public static final int MOVE = (1 << 5);
|
||||
|
||||
private static final int DEFAULT_HIGHLIGHT_COLOR = 0xFF33B5E5;
|
||||
private static final float HANDLE_RADIUS_DP = 12f;
|
||||
private static final float OUTLINE_DP = 2f;
|
||||
|
||||
enum ModifyMode { None, Move, Grow }
|
||||
enum HandleMode { Changing, Always, Never }
|
||||
|
||||
RectF cropRect; // Image space
|
||||
Rect drawRect; // Screen space
|
||||
Matrix matrix;
|
||||
private RectF imageRect; // Image space
|
||||
|
||||
private final Paint outsidePaint = new Paint();
|
||||
private final Paint outlinePaint = new Paint();
|
||||
private final Paint handlePaint = new Paint();
|
||||
|
||||
private View viewContext; // View displaying image
|
||||
private boolean showThirds;
|
||||
private boolean showCircle;
|
||||
private int highlightColor;
|
||||
|
||||
private ModifyMode modifyMode = ModifyMode.None;
|
||||
private HandleMode handleMode = HandleMode.Changing;
|
||||
private boolean maintainAspectRatio;
|
||||
private float initialAspectRatio;
|
||||
private float handleRadius;
|
||||
private float outlineWidth;
|
||||
private boolean isFocused;
|
||||
|
||||
public HighlightView(View context) {
|
||||
viewContext = context;
|
||||
initStyles(context.getContext());
|
||||
}
|
||||
|
||||
private void initStyles(Context context) {
|
||||
TypedValue outValue = new TypedValue();
|
||||
context.getTheme().resolveAttribute(R.attr.cropImageStyle, outValue, true);
|
||||
TypedArray attributes = context.obtainStyledAttributes(outValue.resourceId, R.styleable.CropImageView);
|
||||
try {
|
||||
showThirds = attributes.getBoolean(R.styleable.CropImageView_showThirds, false);
|
||||
showCircle = attributes.getBoolean(R.styleable.CropImageView_showCircle, false);
|
||||
highlightColor = attributes.getColor(R.styleable.CropImageView_highlightColor,
|
||||
DEFAULT_HIGHLIGHT_COLOR);
|
||||
handleMode = HandleMode.values()[attributes.getInt(R.styleable.CropImageView_showHandles, 0)];
|
||||
} finally {
|
||||
attributes.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
public void setup(Matrix m, Rect imageRect, RectF cropRect, boolean maintainAspectRatio) {
|
||||
matrix = new Matrix(m);
|
||||
|
||||
this.cropRect = cropRect;
|
||||
this.imageRect = new RectF(imageRect);
|
||||
this.maintainAspectRatio = maintainAspectRatio;
|
||||
|
||||
initialAspectRatio = this.cropRect.width() / this.cropRect.height();
|
||||
drawRect = computeLayout();
|
||||
|
||||
outsidePaint.setARGB(125, 50, 50, 50);
|
||||
outlinePaint.setStyle(Paint.Style.STROKE);
|
||||
outlinePaint.setAntiAlias(true);
|
||||
outlineWidth = dpToPx(OUTLINE_DP);
|
||||
|
||||
handlePaint.setColor(highlightColor);
|
||||
handlePaint.setStyle(Paint.Style.FILL);
|
||||
handlePaint.setAntiAlias(true);
|
||||
handleRadius = dpToPx(HANDLE_RADIUS_DP);
|
||||
|
||||
modifyMode = ModifyMode.None;
|
||||
}
|
||||
|
||||
private float dpToPx(float dp) {
|
||||
return dp * viewContext.getResources().getDisplayMetrics().density;
|
||||
}
|
||||
|
||||
protected void draw(Canvas canvas) {
|
||||
canvas.save();
|
||||
Path path = new Path();
|
||||
outlinePaint.setStrokeWidth(outlineWidth);
|
||||
if (!hasFocus()) {
|
||||
outlinePaint.setColor(Color.BLACK);
|
||||
canvas.drawRect(drawRect, outlinePaint);
|
||||
} else {
|
||||
Rect viewDrawingRect = new Rect();
|
||||
viewContext.getDrawingRect(viewDrawingRect);
|
||||
|
||||
path.addRect(new RectF(drawRect), Path.Direction.CW);
|
||||
outlinePaint.setColor(highlightColor);
|
||||
|
||||
if (isClipPathSupported(canvas)) {
|
||||
canvas.clipPath(path, Region.Op.DIFFERENCE);
|
||||
canvas.drawRect(viewDrawingRect, outsidePaint);
|
||||
} else {
|
||||
drawOutsideFallback(canvas);
|
||||
}
|
||||
|
||||
canvas.restore();
|
||||
canvas.drawPath(path, outlinePaint);
|
||||
|
||||
if (showThirds) {
|
||||
drawThirds(canvas);
|
||||
}
|
||||
|
||||
if (showCircle) {
|
||||
drawCircle(canvas);
|
||||
}
|
||||
|
||||
if (handleMode == HandleMode.Always ||
|
||||
(handleMode == HandleMode.Changing && modifyMode == ModifyMode.Grow)) {
|
||||
drawHandles(canvas);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Fall back to naive method for darkening outside crop area
|
||||
*/
|
||||
private void drawOutsideFallback(Canvas canvas) {
|
||||
canvas.drawRect(0, 0, canvas.getWidth(), drawRect.top, outsidePaint);
|
||||
canvas.drawRect(0, drawRect.bottom, canvas.getWidth(), canvas.getHeight(), outsidePaint);
|
||||
canvas.drawRect(0, drawRect.top, drawRect.left, drawRect.bottom, outsidePaint);
|
||||
canvas.drawRect(drawRect.right, drawRect.top, canvas.getWidth(), drawRect.bottom, outsidePaint);
|
||||
}
|
||||
|
||||
/*
|
||||
* Clip path is broken, unreliable or not supported on:
|
||||
* - JellyBean MR1
|
||||
* - ICS & ICS MR1 with hardware acceleration turned on
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
private boolean isClipPathSupported(Canvas canvas) {
|
||||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
return false;
|
||||
} else if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH)
|
||||
|| Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
|
||||
return true;
|
||||
} else {
|
||||
return !canvas.isHardwareAccelerated();
|
||||
}
|
||||
}
|
||||
|
||||
private void drawHandles(Canvas canvas) {
|
||||
int xMiddle = drawRect.left + ((drawRect.right - drawRect.left) / 2);
|
||||
int yMiddle = drawRect.top + ((drawRect.bottom - drawRect.top) / 2);
|
||||
|
||||
canvas.drawCircle(drawRect.left, yMiddle, handleRadius, handlePaint);
|
||||
canvas.drawCircle(xMiddle, drawRect.top, handleRadius, handlePaint);
|
||||
canvas.drawCircle(drawRect.right, yMiddle, handleRadius, handlePaint);
|
||||
canvas.drawCircle(xMiddle, drawRect.bottom, handleRadius, handlePaint);
|
||||
}
|
||||
|
||||
private void drawThirds(Canvas canvas) {
|
||||
outlinePaint.setStrokeWidth(1);
|
||||
float xThird = (drawRect.right - drawRect.left) / 3;
|
||||
float yThird = (drawRect.bottom - drawRect.top) / 3;
|
||||
|
||||
canvas.drawLine(drawRect.left + xThird, drawRect.top,
|
||||
drawRect.left + xThird, drawRect.bottom, outlinePaint);
|
||||
canvas.drawLine(drawRect.left + xThird * 2, drawRect.top,
|
||||
drawRect.left + xThird * 2, drawRect.bottom, outlinePaint);
|
||||
canvas.drawLine(drawRect.left, drawRect.top + yThird,
|
||||
drawRect.right, drawRect.top + yThird, outlinePaint);
|
||||
canvas.drawLine(drawRect.left, drawRect.top + yThird * 2,
|
||||
drawRect.right, drawRect.top + yThird * 2, outlinePaint);
|
||||
}
|
||||
|
||||
private void drawCircle(Canvas canvas) {
|
||||
outlinePaint.setStrokeWidth(1);
|
||||
canvas.drawOval(new RectF(drawRect), outlinePaint);
|
||||
}
|
||||
|
||||
public void setMode(ModifyMode mode) {
|
||||
if (mode != modifyMode) {
|
||||
modifyMode = mode;
|
||||
viewContext.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
// Determines which edges are hit by touching at (x, y)
|
||||
public int getHit(float x, float y) {
|
||||
Rect r = computeLayout();
|
||||
final float hysteresis = 20F;
|
||||
int retval = GROW_NONE;
|
||||
|
||||
// verticalCheck makes sure the position is between the top and
|
||||
// the bottom edge (with some tolerance). Similar for horizCheck.
|
||||
boolean verticalCheck = (y >= r.top - hysteresis)
|
||||
&& (y < r.bottom + hysteresis);
|
||||
boolean horizCheck = (x >= r.left - hysteresis)
|
||||
&& (x < r.right + hysteresis);
|
||||
|
||||
// Check whether the position is near some edge(s)
|
||||
if ((Math.abs(r.left - x) < hysteresis) && verticalCheck) {
|
||||
retval |= GROW_LEFT_EDGE;
|
||||
}
|
||||
if ((Math.abs(r.right - x) < hysteresis) && verticalCheck) {
|
||||
retval |= GROW_RIGHT_EDGE;
|
||||
}
|
||||
if ((Math.abs(r.top - y) < hysteresis) && horizCheck) {
|
||||
retval |= GROW_TOP_EDGE;
|
||||
}
|
||||
if ((Math.abs(r.bottom - y) < hysteresis) && horizCheck) {
|
||||
retval |= GROW_BOTTOM_EDGE;
|
||||
}
|
||||
|
||||
// Not near any edge but inside the rectangle: move
|
||||
if (retval == GROW_NONE && r.contains((int) x, (int) y)) {
|
||||
retval = MOVE;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
// Handles motion (dx, dy) in screen space.
|
||||
// The "edge" parameter specifies which edges the user is dragging.
|
||||
void handleMotion(int edge, float dx, float dy) {
|
||||
Rect r = computeLayout();
|
||||
if (edge == MOVE) {
|
||||
// Convert to image space before sending to moveBy()
|
||||
moveBy(dx * (cropRect.width() / r.width()),
|
||||
dy * (cropRect.height() / r.height()));
|
||||
} else {
|
||||
if (((GROW_LEFT_EDGE | GROW_RIGHT_EDGE) & edge) == 0) {
|
||||
dx = 0;
|
||||
}
|
||||
|
||||
if (((GROW_TOP_EDGE | GROW_BOTTOM_EDGE) & edge) == 0) {
|
||||
dy = 0;
|
||||
}
|
||||
|
||||
// Convert to image space before sending to growBy()
|
||||
float xDelta = dx * (cropRect.width() / r.width());
|
||||
float yDelta = dy * (cropRect.height() / r.height());
|
||||
growBy((((edge & GROW_LEFT_EDGE) != 0) ? -1 : 1) * xDelta,
|
||||
(((edge & GROW_TOP_EDGE) != 0) ? -1 : 1) * yDelta);
|
||||
}
|
||||
}
|
||||
|
||||
// Grows the cropping rectangle by (dx, dy) in image space
|
||||
void moveBy(float dx, float dy) {
|
||||
Rect invalRect = new Rect(drawRect);
|
||||
|
||||
cropRect.offset(dx, dy);
|
||||
|
||||
// Put the cropping rectangle inside image rectangle
|
||||
cropRect.offset(
|
||||
Math.max(0, imageRect.left - cropRect.left),
|
||||
Math.max(0, imageRect.top - cropRect.top));
|
||||
|
||||
cropRect.offset(
|
||||
Math.min(0, imageRect.right - cropRect.right),
|
||||
Math.min(0, imageRect.bottom - cropRect.bottom));
|
||||
|
||||
drawRect = computeLayout();
|
||||
invalRect.union(drawRect);
|
||||
invalRect.inset(-(int) handleRadius, -(int) handleRadius);
|
||||
viewContext.invalidate(invalRect);
|
||||
}
|
||||
|
||||
// Grows the cropping rectangle by (dx, dy) in image space.
|
||||
void growBy(float dx, float dy) {
|
||||
if (maintainAspectRatio) {
|
||||
if (dx != 0) {
|
||||
dy = dx / initialAspectRatio;
|
||||
} else if (dy != 0) {
|
||||
dx = dy * initialAspectRatio;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't let the cropping rectangle grow too fast.
|
||||
// Grow at most half of the difference between the image rectangle and
|
||||
// the cropping rectangle.
|
||||
RectF r = new RectF(cropRect);
|
||||
if (dx > 0F && r.width() + 2 * dx > imageRect.width()) {
|
||||
dx = (imageRect.width() - r.width()) / 2F;
|
||||
if (maintainAspectRatio) {
|
||||
dy = dx / initialAspectRatio;
|
||||
}
|
||||
}
|
||||
if (dy > 0F && r.height() + 2 * dy > imageRect.height()) {
|
||||
dy = (imageRect.height() - r.height()) / 2F;
|
||||
if (maintainAspectRatio) {
|
||||
dx = dy * initialAspectRatio;
|
||||
}
|
||||
}
|
||||
|
||||
r.inset(-dx, -dy);
|
||||
|
||||
// Don't let the cropping rectangle shrink too fast
|
||||
final float widthCap = 25F;
|
||||
if (r.width() < widthCap) {
|
||||
r.inset(-(widthCap - r.width()) / 2F, 0F);
|
||||
}
|
||||
float heightCap = maintainAspectRatio
|
||||
? (widthCap / initialAspectRatio)
|
||||
: widthCap;
|
||||
if (r.height() < heightCap) {
|
||||
r.inset(0F, -(heightCap - r.height()) / 2F);
|
||||
}
|
||||
|
||||
// Put the cropping rectangle inside the image rectangle
|
||||
if (r.left < imageRect.left) {
|
||||
r.offset(imageRect.left - r.left, 0F);
|
||||
} else if (r.right > imageRect.right) {
|
||||
r.offset(-(r.right - imageRect.right), 0F);
|
||||
}
|
||||
if (r.top < imageRect.top) {
|
||||
r.offset(0F, imageRect.top - r.top);
|
||||
} else if (r.bottom > imageRect.bottom) {
|
||||
r.offset(0F, -(r.bottom - imageRect.bottom));
|
||||
}
|
||||
|
||||
cropRect.set(r);
|
||||
drawRect = computeLayout();
|
||||
viewContext.invalidate();
|
||||
}
|
||||
|
||||
// Returns the cropping rectangle in image space with specified scale
|
||||
public Rect getScaledCropRect(float scale) {
|
||||
return new Rect((int) (cropRect.left * scale), (int) (cropRect.top * scale),
|
||||
(int) (cropRect.right * scale), (int) (cropRect.bottom * scale));
|
||||
}
|
||||
|
||||
// Maps the cropping rectangle from image space to screen space
|
||||
private Rect computeLayout() {
|
||||
RectF r = new RectF(cropRect.left, cropRect.top,
|
||||
cropRect.right, cropRect.bottom);
|
||||
matrix.mapRect(r);
|
||||
return new Rect(Math.round(r.left), Math.round(r.top),
|
||||
Math.round(r.right), Math.round(r.bottom));
|
||||
}
|
||||
|
||||
public void invalidate() {
|
||||
drawRect = computeLayout();
|
||||
}
|
||||
|
||||
public boolean hasFocus() {
|
||||
return isFocused;
|
||||
}
|
||||
|
||||
public void setFocus(boolean isFocused) {
|
||||
this.isFocused = isFocused;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,400 @@
|
||||
/*
|
||||
* Copyright (C) 2009 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.soundcloud.android.crop;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Handler;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.KeyEvent;
|
||||
import android.widget.ImageView;
|
||||
|
||||
/*
|
||||
* Modified from original in AOSP.
|
||||
*/
|
||||
abstract class ImageViewTouchBase extends ImageView {
|
||||
|
||||
private static final float SCALE_RATE = 1.25F;
|
||||
|
||||
// This is the base transformation which is used to show the image
|
||||
// initially. The current computation for this shows the image in
|
||||
// it's entirety, letterboxing as needed. One could choose to
|
||||
// show the image as cropped instead.
|
||||
//
|
||||
// This matrix is recomputed when we go from the thumbnail image to
|
||||
// the full size image.
|
||||
protected Matrix baseMatrix = new Matrix();
|
||||
|
||||
// This is the supplementary transformation which reflects what
|
||||
// the user has done in terms of zooming and panning.
|
||||
//
|
||||
// This matrix remains the same when we go from the thumbnail image
|
||||
// to the full size image.
|
||||
protected Matrix suppMatrix = new Matrix();
|
||||
|
||||
// This is the final matrix which is computed as the concatentation
|
||||
// of the base matrix and the supplementary matrix.
|
||||
private final Matrix displayMatrix = new Matrix();
|
||||
|
||||
// Temporary buffer used for getting the values out of a matrix.
|
||||
private final float[] matrixValues = new float[9];
|
||||
|
||||
// The current bitmap being displayed.
|
||||
protected final RotateBitmap bitmapDisplayed = new RotateBitmap(null, 0);
|
||||
|
||||
int thisWidth = -1;
|
||||
int thisHeight = -1;
|
||||
|
||||
float maxZoom;
|
||||
|
||||
private Runnable onLayoutRunnable;
|
||||
|
||||
protected Handler handler = new Handler();
|
||||
|
||||
// ImageViewTouchBase will pass a Bitmap to the Recycler if it has finished
|
||||
// its use of that Bitmap
|
||||
public interface Recycler {
|
||||
public void recycle(Bitmap b);
|
||||
}
|
||||
|
||||
private Recycler recycler;
|
||||
|
||||
public ImageViewTouchBase(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public ImageViewTouchBase(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public ImageViewTouchBase(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
init();
|
||||
}
|
||||
|
||||
public void setRecycler(Recycler recycler) {
|
||||
this.recycler = recycler;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
thisWidth = right - left;
|
||||
thisHeight = bottom - top;
|
||||
Runnable r = onLayoutRunnable;
|
||||
if (r != null) {
|
||||
onLayoutRunnable = null;
|
||||
r.run();
|
||||
}
|
||||
if (bitmapDisplayed.getBitmap() != null) {
|
||||
getProperBaseMatrix(bitmapDisplayed, baseMatrix, true);
|
||||
setImageMatrix(getImageViewMatrix());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
|
||||
event.startTracking();
|
||||
return true;
|
||||
}
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking() && !event.isCanceled()) {
|
||||
if (getScale() > 1.0f) {
|
||||
// If we're zoomed in, pressing Back jumps out to show the
|
||||
// entire image, otherwise Back returns the user to the gallery
|
||||
zoomTo(1.0f);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImageBitmap(Bitmap bitmap) {
|
||||
setImageBitmap(bitmap, 0);
|
||||
}
|
||||
|
||||
private void setImageBitmap(Bitmap bitmap, int rotation) {
|
||||
super.setImageBitmap(bitmap);
|
||||
Drawable d = getDrawable();
|
||||
if (d != null) {
|
||||
d.setDither(true);
|
||||
}
|
||||
|
||||
Bitmap old = bitmapDisplayed.getBitmap();
|
||||
bitmapDisplayed.setBitmap(bitmap);
|
||||
bitmapDisplayed.setRotation(rotation);
|
||||
|
||||
if (old != null && old != bitmap && recycler != null) {
|
||||
recycler.recycle(old);
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
setImageBitmapResetBase(null, true);
|
||||
}
|
||||
|
||||
|
||||
// This function changes bitmap, reset base matrix according to the size
|
||||
// of the bitmap, and optionally reset the supplementary matrix
|
||||
public void setImageBitmapResetBase(final Bitmap bitmap, final boolean resetSupp) {
|
||||
setImageRotateBitmapResetBase(new RotateBitmap(bitmap, 0), resetSupp);
|
||||
}
|
||||
|
||||
public void setImageRotateBitmapResetBase(final RotateBitmap bitmap, final boolean resetSupp) {
|
||||
final int viewWidth = getWidth();
|
||||
|
||||
if (viewWidth <= 0) {
|
||||
onLayoutRunnable = new Runnable() {
|
||||
public void run() {
|
||||
setImageRotateBitmapResetBase(bitmap, resetSupp);
|
||||
}
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
if (bitmap.getBitmap() != null) {
|
||||
getProperBaseMatrix(bitmap, baseMatrix, true);
|
||||
setImageBitmap(bitmap.getBitmap(), bitmap.getRotation());
|
||||
} else {
|
||||
baseMatrix.reset();
|
||||
setImageBitmap(null);
|
||||
}
|
||||
|
||||
if (resetSupp) {
|
||||
suppMatrix.reset();
|
||||
}
|
||||
setImageMatrix(getImageViewMatrix());
|
||||
maxZoom = calculateMaxZoom();
|
||||
}
|
||||
|
||||
// Center as much as possible in one or both axis. Centering is defined as follows:
|
||||
// * If the image is scaled down below the view's dimensions then center it.
|
||||
// * If the image is scaled larger than the view and is translated out of view then translate it back into view.
|
||||
protected void center() {
|
||||
final Bitmap bitmap = bitmapDisplayed.getBitmap();
|
||||
if (bitmap == null) {
|
||||
return;
|
||||
}
|
||||
Matrix m = getImageViewMatrix();
|
||||
|
||||
RectF rect = new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight());
|
||||
m.mapRect(rect);
|
||||
|
||||
float height = rect.height();
|
||||
float width = rect.width();
|
||||
|
||||
float deltaX = 0, deltaY = 0;
|
||||
|
||||
deltaY = centerVertical(rect, height, deltaY);
|
||||
deltaX = centerHorizontal(rect, width, deltaX);
|
||||
|
||||
postTranslate(deltaX, deltaY);
|
||||
setImageMatrix(getImageViewMatrix());
|
||||
}
|
||||
|
||||
private float centerVertical(RectF rect, float height, float deltaY) {
|
||||
int viewHeight = getHeight();
|
||||
if (height < viewHeight) {
|
||||
deltaY = (viewHeight - height) / 2 - rect.top;
|
||||
} else if (rect.top > 0) {
|
||||
deltaY = -rect.top;
|
||||
} else if (rect.bottom < viewHeight) {
|
||||
deltaY = getHeight() - rect.bottom;
|
||||
}
|
||||
return deltaY;
|
||||
}
|
||||
|
||||
private float centerHorizontal(RectF rect, float width, float deltaX) {
|
||||
int viewWidth = getWidth();
|
||||
if (width < viewWidth) {
|
||||
deltaX = (viewWidth - width) / 2 - rect.left;
|
||||
} else if (rect.left > 0) {
|
||||
deltaX = -rect.left;
|
||||
} else if (rect.right < viewWidth) {
|
||||
deltaX = viewWidth - rect.right;
|
||||
}
|
||||
return deltaX;
|
||||
}
|
||||
|
||||
private void init() {
|
||||
setScaleType(ImageView.ScaleType.MATRIX);
|
||||
}
|
||||
|
||||
protected float getValue(Matrix matrix, int whichValue) {
|
||||
matrix.getValues(matrixValues);
|
||||
return matrixValues[whichValue];
|
||||
}
|
||||
|
||||
// Get the scale factor out of the matrix.
|
||||
protected float getScale(Matrix matrix) {
|
||||
return getValue(matrix, Matrix.MSCALE_X);
|
||||
}
|
||||
|
||||
protected float getScale() {
|
||||
return getScale(suppMatrix);
|
||||
}
|
||||
|
||||
// Setup the base matrix so that the image is centered and scaled properly.
|
||||
private void getProperBaseMatrix(RotateBitmap bitmap, Matrix matrix, boolean includeRotation) {
|
||||
float viewWidth = getWidth();
|
||||
float viewHeight = getHeight();
|
||||
|
||||
float w = bitmap.getWidth();
|
||||
float h = bitmap.getHeight();
|
||||
matrix.reset();
|
||||
|
||||
// We limit up-scaling to 3x otherwise the result may look bad if it's a small icon
|
||||
float widthScale = Math.min(viewWidth / w, 3.0f);
|
||||
float heightScale = Math.min(viewHeight / h, 3.0f);
|
||||
float scale = Math.min(widthScale, heightScale);
|
||||
|
||||
if (includeRotation) {
|
||||
matrix.postConcat(bitmap.getRotateMatrix());
|
||||
}
|
||||
matrix.postScale(scale, scale);
|
||||
matrix.postTranslate((viewWidth - w * scale) / 2F, (viewHeight - h * scale) / 2F);
|
||||
}
|
||||
|
||||
// Combine the base matrix and the supp matrix to make the final matrix
|
||||
protected Matrix getImageViewMatrix() {
|
||||
// The final matrix is computed as the concatentation of the base matrix
|
||||
// and the supplementary matrix
|
||||
displayMatrix.set(baseMatrix);
|
||||
displayMatrix.postConcat(suppMatrix);
|
||||
return displayMatrix;
|
||||
}
|
||||
|
||||
public Matrix getUnrotatedMatrix(){
|
||||
Matrix unrotated = new Matrix();
|
||||
getProperBaseMatrix(bitmapDisplayed, unrotated, false);
|
||||
unrotated.postConcat(suppMatrix);
|
||||
return unrotated;
|
||||
}
|
||||
|
||||
protected float calculateMaxZoom() {
|
||||
if (bitmapDisplayed.getBitmap() == null) {
|
||||
return 1F;
|
||||
}
|
||||
|
||||
float fw = (float) bitmapDisplayed.getWidth() / (float) thisWidth;
|
||||
float fh = (float) bitmapDisplayed.getHeight() / (float) thisHeight;
|
||||
return Math.max(fw, fh) * 4; // 400%
|
||||
}
|
||||
|
||||
protected void zoomTo(float scale, float centerX, float centerY) {
|
||||
if (scale > maxZoom) {
|
||||
scale = maxZoom;
|
||||
}
|
||||
|
||||
float oldScale = getScale();
|
||||
float deltaScale = scale / oldScale;
|
||||
|
||||
suppMatrix.postScale(deltaScale, deltaScale, centerX, centerY);
|
||||
setImageMatrix(getImageViewMatrix());
|
||||
center();
|
||||
}
|
||||
|
||||
protected void zoomTo(final float scale, final float centerX,
|
||||
final float centerY, final float durationMs) {
|
||||
final float incrementPerMs = (scale - getScale()) / durationMs;
|
||||
final float oldScale = getScale();
|
||||
final long startTime = System.currentTimeMillis();
|
||||
|
||||
handler.post(new Runnable() {
|
||||
public void run() {
|
||||
long now = System.currentTimeMillis();
|
||||
float currentMs = Math.min(durationMs, now - startTime);
|
||||
float target = oldScale + (incrementPerMs * currentMs);
|
||||
zoomTo(target, centerX, centerY);
|
||||
|
||||
if (currentMs < durationMs) {
|
||||
handler.post(this);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void zoomTo(float scale) {
|
||||
float cx = getWidth() / 2F;
|
||||
float cy = getHeight() / 2F;
|
||||
zoomTo(scale, cx, cy);
|
||||
}
|
||||
|
||||
protected void zoomIn() {
|
||||
zoomIn(SCALE_RATE);
|
||||
}
|
||||
|
||||
protected void zoomOut() {
|
||||
zoomOut(SCALE_RATE);
|
||||
}
|
||||
|
||||
protected void zoomIn(float rate) {
|
||||
if (getScale() >= maxZoom) {
|
||||
return; // Don't let the user zoom into the molecular level
|
||||
}
|
||||
if (bitmapDisplayed.getBitmap() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
float cx = getWidth() / 2F;
|
||||
float cy = getHeight() / 2F;
|
||||
|
||||
suppMatrix.postScale(rate, rate, cx, cy);
|
||||
setImageMatrix(getImageViewMatrix());
|
||||
}
|
||||
|
||||
protected void zoomOut(float rate) {
|
||||
if (bitmapDisplayed.getBitmap() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
float cx = getWidth() / 2F;
|
||||
float cy = getHeight() / 2F;
|
||||
|
||||
// Zoom out to at most 1x
|
||||
Matrix tmp = new Matrix(suppMatrix);
|
||||
tmp.postScale(1F / rate, 1F / rate, cx, cy);
|
||||
|
||||
if (getScale(tmp) < 1F) {
|
||||
suppMatrix.setScale(1F, 1F, cx, cy);
|
||||
} else {
|
||||
suppMatrix.postScale(1F / rate, 1F / rate, cx, cy);
|
||||
}
|
||||
setImageMatrix(getImageViewMatrix());
|
||||
center();
|
||||
}
|
||||
|
||||
protected void postTranslate(float dx, float dy) {
|
||||
suppMatrix.postTranslate(dx, dy);
|
||||
}
|
||||
|
||||
protected void panBy(float dx, float dy) {
|
||||
postTranslate(dx, dy);
|
||||
setImageMatrix(getImageViewMatrix());
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
package com.soundcloud.android.crop;
|
||||
|
||||
class Log {
|
||||
|
||||
private static final String TAG = "android-crop";
|
||||
|
||||
public static void e(String msg) {
|
||||
android.util.Log.e(TAG, msg);
|
||||
}
|
||||
|
||||
public static void e(String msg, Throwable e) {
|
||||
android.util.Log.e(TAG, msg, e);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (C) 2009 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.soundcloud.android.crop;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/*
|
||||
* Modified from original in AOSP.
|
||||
*/
|
||||
abstract class MonitoredActivity extends Activity {
|
||||
|
||||
private final ArrayList<LifeCycleListener> listeners = new ArrayList<LifeCycleListener>();
|
||||
|
||||
public static interface LifeCycleListener {
|
||||
public void onActivityCreated(MonitoredActivity activity);
|
||||
public void onActivityDestroyed(MonitoredActivity activity);
|
||||
public void onActivityStarted(MonitoredActivity activity);
|
||||
public void onActivityStopped(MonitoredActivity activity);
|
||||
}
|
||||
|
||||
public static class LifeCycleAdapter implements LifeCycleListener {
|
||||
public void onActivityCreated(MonitoredActivity activity) {}
|
||||
public void onActivityDestroyed(MonitoredActivity activity) {}
|
||||
public void onActivityStarted(MonitoredActivity activity) {}
|
||||
public void onActivityStopped(MonitoredActivity activity) {}
|
||||
}
|
||||
|
||||
public void addLifeCycleListener(LifeCycleListener listener) {
|
||||
if (listeners.contains(listener)) return;
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeLifeCycleListener(LifeCycleListener listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
for (LifeCycleListener listener : listeners) {
|
||||
listener.onActivityCreated(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
for (LifeCycleListener listener : listeners) {
|
||||
listener.onActivityDestroyed(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
for (LifeCycleListener listener : listeners) {
|
||||
listener.onActivityStarted(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
for (LifeCycleListener listener : listeners) {
|
||||
listener.onActivityStopped(this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (C) 2009 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.soundcloud.android.crop;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Matrix;
|
||||
|
||||
/*
|
||||
* Modified from original in AOSP.
|
||||
*/
|
||||
class RotateBitmap {
|
||||
|
||||
private Bitmap bitmap;
|
||||
private int rotation;
|
||||
|
||||
public RotateBitmap(Bitmap bitmap, int rotation) {
|
||||
this.bitmap = bitmap;
|
||||
this.rotation = rotation % 360;
|
||||
}
|
||||
|
||||
public void setRotation(int rotation) {
|
||||
this.rotation = rotation;
|
||||
}
|
||||
|
||||
public int getRotation() {
|
||||
return rotation;
|
||||
}
|
||||
|
||||
public Bitmap getBitmap() {
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
public void setBitmap(Bitmap bitmap) {
|
||||
this.bitmap = bitmap;
|
||||
}
|
||||
|
||||
public Matrix getRotateMatrix() {
|
||||
// By default this is an identity matrix
|
||||
Matrix matrix = new Matrix();
|
||||
if (bitmap != null && rotation != 0) {
|
||||
// We want to do the rotation at origin, but since the bounding
|
||||
// rectangle will be changed after rotation, so the delta values
|
||||
// are based on old & new width/height respectively.
|
||||
int cx = bitmap.getWidth() / 2;
|
||||
int cy = bitmap.getHeight() / 2;
|
||||
matrix.preTranslate(-cx, -cy);
|
||||
matrix.postRotate(rotation);
|
||||
matrix.postTranslate(getWidth() / 2, getHeight() / 2);
|
||||
}
|
||||
return matrix;
|
||||
}
|
||||
|
||||
public boolean isOrientationChanged() {
|
||||
return (rotation / 90) % 2 != 0;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
if (bitmap == null) return 0;
|
||||
if (isOrientationChanged()) {
|
||||
return bitmap.getWidth();
|
||||
} else {
|
||||
return bitmap.getHeight();
|
||||
}
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
if (bitmap == null) return 0;
|
||||
if (isOrientationChanged()) {
|
||||
return bitmap.getHeight();
|
||||
} else {
|
||||
return bitmap.getWidth();
|
||||
}
|
||||
}
|
||||
|
||||
public void recycle() {
|
||||
if (bitmap != null) {
|
||||
bitmap.recycle();
|
||||
bitmap = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,48 @@
|
||||
package com.soundcloud.android.crop;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class UriUtil {
|
||||
|
||||
public final static String getFileProviderName(Context context){
|
||||
return context.getPackageName()+".fileprovider";
|
||||
}
|
||||
|
||||
/**
|
||||
* 将TakePhoto 提供的Uri 解析出文件绝对路径
|
||||
*
|
||||
* @param uri
|
||||
* @return
|
||||
*/
|
||||
public static String parseOwnUri(Context context, Uri uri) {
|
||||
if (uri == null) return null;
|
||||
String path;
|
||||
if (TextUtils.equals(uri.getAuthority(), getFileProviderName(context))) {
|
||||
String target_text_camera_photos = "camera_photos/";
|
||||
if (uri.getPath() != null && uri.getPath().contains(target_text_camera_photos)) {
|
||||
path = new File(uri.getPath().replace(target_text_camera_photos, ""))
|
||||
.getAbsolutePath();
|
||||
} else {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
path = new File(Environment.getExternalStorageDirectory(),
|
||||
uri.getPath())
|
||||
.getAbsolutePath();
|
||||
} else {
|
||||
path = uri.getPath();
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
path = uri.getPath();
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
}
|
BIN
android_crop_lib/src/main/res/drawable-hdpi/crop__divider.9.png
Normal file
After Width: | Height: | Size: 76 B |
BIN
android_crop_lib/src/main/res/drawable-hdpi/crop__ic_cancel.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
android_crop_lib/src/main/res/drawable-hdpi/crop__ic_done.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
android_crop_lib/src/main/res/drawable-mdpi/crop__divider.9.png
Normal file
After Width: | Height: | Size: 76 B |
BIN
android_crop_lib/src/main/res/drawable-mdpi/crop__ic_cancel.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
android_crop_lib/src/main/res/drawable-mdpi/crop__ic_done.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="@color/crop__selector_pressed">
|
||||
<item android:drawable="@color/crop__button_bar" />
|
||||
</ripple>
|
BIN
android_crop_lib/src/main/res/drawable-xhdpi/crop__divider.9.png
Normal file
After Width: | Height: | Size: 83 B |
BIN
android_crop_lib/src/main/res/drawable-xhdpi/crop__ic_cancel.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
android_crop_lib/src/main/res/drawable-xhdpi/crop__ic_done.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
android_crop_lib/src/main/res/drawable-xhdpi/crop__tile.png
Normal file
After Width: | Height: | Size: 142 B |
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:exitFadeDuration="@android:integer/config_mediumAnimTime">
|
||||
|
||||
<item android:state_pressed="true">
|
||||
<shape>
|
||||
<solid android:color="@color/crop__selector_pressed" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item android:state_focused="true">
|
||||
<shape>
|
||||
<solid android:color="@color/crop__selector_focused" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item android:drawable="@android:color/transparent" />
|
||||
|
||||
</selector>
|
5
android_crop_lib/src/main/res/drawable/crop__texture.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:src="@drawable/crop__tile"
|
||||
android:tileMode="repeat" />
|
19
android_crop_lib/src/main/res/layout/crop__activity_crop.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<include
|
||||
android:id="@+id/done_cancel_bar"
|
||||
layout="@layout/crop__layout_done_cancel" />
|
||||
|
||||
<com.soundcloud.android.crop.CropImageView
|
||||
android:id="@+id/crop_image"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/crop__texture"
|
||||
android:layout_below="@id/done_cancel_bar" />
|
||||
|
||||
</RelativeLayout>
|
@@ -0,0 +1,16 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
style="@style/Crop.DoneCancelBar">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/btn_cancel"
|
||||
style="@style/Crop.ActionButton">
|
||||
<TextView style="@style/Crop.ActionButtonText.Cancel" />
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/btn_done"
|
||||
style="@style/Crop.ActionButton">
|
||||
<TextView style="@style/Crop.ActionButtonText.Done" />
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
10
android_crop_lib/src/main/res/values-ar/strings.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<string name="crop__saving">جارى حفظ الصورة …</string>
|
||||
<string name="crop__wait">رجاء الأنتظار …</string>
|
||||
<string name="crop__pick_error">الصورة غير متاحة</string>
|
||||
|
||||
<string name="crop__done">تم</string>
|
||||
<string name="crop__cancel" tools:ignore="ButtonCase">الغاء</string>
|
||||
|
||||
</resources>
|
10
android_crop_lib/src/main/res/values-ca/strings.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<string name="crop__saving">Guardant imatge…</string>
|
||||
<string name="crop__wait">Si us plau esperi…</string>
|
||||
<string name="crop__pick_error">No hi ha imatges disponibles</string>
|
||||
|
||||
<string name="crop__done">ACCEPTAR</string>
|
||||
<string name="crop__cancel" tools:ignore="ButtonCase">CANCEL·LAR</string>
|
||||
|
||||
</resources>
|
10
android_crop_lib/src/main/res/values-de/strings.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<string name="crop__saving">Bild speichern…</string>
|
||||
<string name="crop__wait">Bitte warten…</string>
|
||||
<string name="crop__pick_error">Keine Bildquellen verfügbar</string>
|
||||
|
||||
<string name="crop__done">übernehmen</string>
|
||||
<string name="crop__cancel" tools:ignore="ButtonCase">abbrechen</string>
|
||||
|
||||
</resources>
|
10
android_crop_lib/src/main/res/values-es/strings.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<string name="crop__saving">Guardando imagen…</string>
|
||||
<string name="crop__wait">Por favor espere…</string>
|
||||
<string name="crop__pick_error">No hay imágenes disponibles</string>
|
||||
|
||||
<string name="crop__done">ACEPTAR</string>
|
||||
<string name="crop__cancel" tools:ignore="ButtonCase">CANCELAR</string>
|
||||
|
||||
</resources>
|
10
android_crop_lib/src/main/res/values-fa/strings.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<string name="crop__saving">در حال ذخیره سازی</string>
|
||||
<string name="crop__wait">لطفاً صبر کنید ...</string>
|
||||
<string name="crop__pick_error">تصویری در دسترس نیست</string>
|
||||
|
||||
<string name="crop__done">تأیید</string>
|
||||
<string name="crop__cancel" tools:ignore="ButtonCase">انصراف</string>
|
||||
|
||||
</resources>
|
10
android_crop_lib/src/main/res/values-fr/strings.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<string name="crop__saving">Enregistrement de l\'image…</string>
|
||||
<string name="crop__wait">Veuillez patienter…</string>
|
||||
<string name="crop__pick_error">Aucune image disponible</string>
|
||||
|
||||
<string name="crop__done">ACCEPTER</string>
|
||||
<string name="crop__cancel" tools:ignore="ButtonCase">ANNULER</string>
|
||||
|
||||
</resources>
|
10
android_crop_lib/src/main/res/values-in/strings.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<string name="crop__saving">Menyimpan gambar…</string>
|
||||
<string name="crop__wait">Silakan tunggu…</string>
|
||||
<string name="crop__pick_error">Tidak ada sumber gambar yang tersedia</string>
|
||||
|
||||
<string name="crop__done">SELESAI</string>
|
||||
<string name="crop__cancel" tools:ignore="ButtonCase">BATAL</string>
|
||||
|
||||
</resources>
|
10
android_crop_lib/src/main/res/values-it/strings.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<string name="crop__saving">Salvataggio immagine…</string>
|
||||
<string name="crop__wait">Attendere prego…</string>
|
||||
<string name="crop__pick_error">Nessuna immagine disponibile</string>
|
||||
|
||||
<string name="crop__done">ACCETTA</string>
|
||||
<string name="crop__cancel" tools:ignore="ButtonCase">ANNULLA</string>
|
||||
|
||||
</resources>
|
10
android_crop_lib/src/main/res/values-ja/strings.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<string name="crop__saving">保存中…</string>
|
||||
<string name="crop__wait">お待ちください…</string>
|
||||
<string name="crop__pick_error">画像が見つかりません</string>
|
||||
|
||||
<string name="crop__done">決定</string>
|
||||
<string name="crop__cancel" tools:ignore="ButtonCase">キャンセル</string>
|
||||
|
||||
</resources>
|
10
android_crop_lib/src/main/res/values-ko/strings.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<string name="crop__saving">사진을 저장중입니다…</string>
|
||||
<string name="crop__wait">잠시만 기다려주세요…</string>
|
||||
<string name="crop__pick_error">이미지가 존재하지 않습니다.</string>
|
||||
|
||||
<string name="crop__done">확인</string>
|
||||
<string name="crop__cancel" tools:ignore="ButtonCase">취소</string>
|
||||
|
||||
</resources>
|
5
android_crop_lib/src/main/res/values-land/dimens.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<resources>
|
||||
|
||||
<dimen name="crop__bar_height">48dp</dimen>
|
||||
|
||||
</resources>
|
5
android_crop_lib/src/main/res/values-large/dimens.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<resources>
|
||||
|
||||
<dimen name="crop__bar_height">64dp</dimen>
|
||||
|
||||
</resources>
|
10
android_crop_lib/src/main/res/values-pt/strings.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<string name="crop__saving">Salvando imagem…</string>
|
||||
<string name="crop__wait">Por favor, aguarde…</string>
|
||||
<string name="crop__pick_error">Sem fontes de imagem disponíveis</string>
|
||||
|
||||
<string name="crop__done">FINALIZADO</string>
|
||||
<string name="crop__cancel" tools:ignore="ButtonCase">CANCELAR</string>
|
||||
|
||||
</resources>
|
10
android_crop_lib/src/main/res/values-ru/strings.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<string name="crop__saving">Изображение сохраняется…</string>
|
||||
<string name="crop__wait">Пожалуйста, подождите…</string>
|
||||
<string name="crop__pick_error">Нет доступных изображений</string>
|
||||
|
||||
<string name="crop__done">ГОТОВО</string>
|
||||
<string name="crop__cancel" tools:ignore="ButtonCase">ОТМЕНА</string>
|
||||
|
||||
</resources>
|
10
android_crop_lib/src/main/res/values-sv/strings.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<string name="crop__saving">Sparar bild…</string>
|
||||
<string name="crop__wait">Var god vänta…</string>
|
||||
<string name="crop__pick_error">Inga bildkällor tillgängliga</string>
|
||||
|
||||
<string name="crop__done">KLAR</string>
|
||||
<string name="crop__cancel" tools:ignore="ButtonCase">AVBRYT</string>
|
||||
|
||||
</resources>
|
10
android_crop_lib/src/main/res/values-tr/strings.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<string name="crop__saving">Fotoğraf kaydediliyor…</string>
|
||||
<string name="crop__wait">Lütfen bekleyin…</string>
|
||||
<string name="crop__pick_error">Fotoğraf bulunamadı</string>
|
||||
|
||||
<string name="crop__done">TAMAM</string>
|
||||
<string name="crop__cancel" tools:ignore="ButtonCase">ÇIKIŞ</string>
|
||||
|
||||
</resources>
|
5
android_crop_lib/src/main/res/values-v21/colors.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<resources>
|
||||
|
||||
<color name="crop__selector_pressed">#aaaaaa</color>
|
||||
|
||||
</resources>
|
10
android_crop_lib/src/main/res/values-zh-rCN/strings.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<string name="crop__saving">正在保存照片…</string>
|
||||
<string name="crop__wait">请等待…</string>
|
||||
<string name="crop__pick_error">无效的图片</string>
|
||||
|
||||
<string name="crop__done">完成</string>
|
||||
<string name="crop__cancel" tools:ignore="ButtonCase">取消</string>
|
||||
|
||||
</resources>
|
10
android_crop_lib/src/main/res/values-zh-rTW/strings.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<string name="crop__saving">正在儲存相片…</string>
|
||||
<string name="crop__wait">請稍候…</string>
|
||||
<string name="crop__pick_error">沒有可用的圖片來源</string>
|
||||
|
||||
<string name="crop__done">完成</string>
|
||||
<string name="crop__cancel" tools:ignore="ButtonCase">取消</string>
|
||||
|
||||
</resources>
|
16
android_crop_lib/src/main/res/values/attrs.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<resources>
|
||||
|
||||
<attr name="cropImageStyle" format="reference" />
|
||||
|
||||
<declare-styleable name="CropImageView">
|
||||
<attr name="highlightColor" format="reference|color" />
|
||||
<attr name="showThirds" format="boolean" />
|
||||
<attr name="showCircle" format="boolean" />
|
||||
<attr name="showHandles">
|
||||
<enum name="changing" value="0" />
|
||||
<enum name="always" value="1" />
|
||||
<enum name="never" value="2" />
|
||||
</attr>
|
||||
</declare-styleable>
|
||||
|
||||
</resources>
|
8
android_crop_lib/src/main/res/values/colors.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<resources>
|
||||
|
||||
<color name="crop__button_bar">#f3f3f3</color>
|
||||
<color name="crop__button_text">#666666</color>
|
||||
<color name="crop__selector_pressed">#1a000000</color>
|
||||
<color name="crop__selector_focused">#77000000</color>
|
||||
|
||||
</resources>
|
5
android_crop_lib/src/main/res/values/dimens.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<resources>
|
||||
|
||||
<dimen name="crop__bar_height">56dp</dimen>
|
||||
|
||||
</resources>
|
10
android_crop_lib/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<string name="crop__saving">Saving picture…</string>
|
||||
<string name="crop__wait">Please wait…</string>
|
||||
<string name="crop__pick_error">No image sources available</string>
|
||||
|
||||
<string name="crop__done">DONE</string>
|
||||
<string name="crop__cancel" tools:ignore="ButtonCase">CANCEL</string>
|
||||
|
||||
</resources>
|
44
android_crop_lib/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,44 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<style name="Crop"></style>
|
||||
|
||||
<style name="Crop.DoneCancelBar">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">@dimen/crop__bar_height</item>
|
||||
<item name="android:orientation">horizontal</item>
|
||||
<item name="android:divider">@drawable/crop__divider</item>
|
||||
<item name="android:showDividers" tools:ignore="NewApi">middle</item>
|
||||
<item name="android:dividerPadding" tools:ignore="NewApi">12dp</item>
|
||||
<item name="android:background">@color/crop__button_bar</item>
|
||||
</style>
|
||||
|
||||
<style name="Crop.ActionButton">
|
||||
<item name="android:layout_width">0dp</item>
|
||||
<item name="android:layout_height">match_parent</item>
|
||||
<item name="android:layout_weight">1</item>
|
||||
<item name="android:background">@drawable/crop__selectable_background</item>
|
||||
</style>
|
||||
|
||||
<style name="Crop.ActionButtonText">
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:layout_gravity">center</item>
|
||||
<item name="android:gravity">center_vertical</item>
|
||||
<item name="android:paddingRight">20dp</item> <!-- Offsets left drawable -->
|
||||
<item name="android:drawablePadding">8dp</item>
|
||||
<item name="android:textColor">@color/crop__button_text</item>
|
||||
<item name="android:textStyle">bold</item>
|
||||
<item name="android:textSize">13sp</item>
|
||||
</style>
|
||||
|
||||
<style name="Crop.ActionButtonText.Done">
|
||||
<item name="android:drawableLeft">@drawable/crop__ic_done</item>
|
||||
<item name="android:text">@string/crop__done</item>
|
||||
</style>
|
||||
|
||||
<style name="Crop.ActionButtonText.Cancel">
|
||||
<item name="android:drawableLeft">@drawable/crop__ic_cancel</item>
|
||||
<item name="android:text">@string/crop__cancel</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
998
apkinfo.py
Normal file
@@ -0,0 +1,998 @@
|
||||
# This file is part of Androguard.
|
||||
#
|
||||
# Copyright (C) 2012, Anthony Desnos <desnos at t0t0.fr>
|
||||
# All rights reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import StringIO
|
||||
from struct import pack, unpack
|
||||
from xml.sax.saxutils import escape
|
||||
from zlib import crc32
|
||||
import re
|
||||
import collections
|
||||
import sys
|
||||
import os
|
||||
import logging
|
||||
import types
|
||||
import random
|
||||
import string
|
||||
import imp
|
||||
|
||||
from xml.dom import minidom
|
||||
|
||||
NS_ANDROID_URI = 'http://schemas.android.com/apk/res/android'
|
||||
|
||||
ZIPMODULE = 1
|
||||
|
||||
|
||||
def read(filename, binary=True):
|
||||
with open(filename, 'rb' if binary else 'r') as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
def sign_apk(filename, keystore, storepass):
|
||||
from subprocess import Popen, PIPE, STDOUT
|
||||
compile = Popen([CONF["PATH_JARSIGNER"], "-sigalg", "MD5withRSA",
|
||||
"-digestalg", "SHA1", "-storepass", storepass, "-keystore",
|
||||
keystore, filename, "alias_name"],
|
||||
stdout=PIPE,
|
||||
stderr=STDOUT)
|
||||
stdout, stderr = compile.communicate()
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
"""Base class for exceptions in this module."""
|
||||
pass
|
||||
|
||||
|
||||
class FileNotPresent(Error):
|
||||
pass
|
||||
|
||||
|
||||
######################################################## APK FORMAT ########################################################
|
||||
class APK(object):
|
||||
"""
|
||||
This class can access to all elements in an APK file
|
||||
|
||||
:param filename: specify the path of the file, or raw data
|
||||
:param raw: specify if the filename is a path or raw data (optional)
|
||||
:param mode: specify the mode to open the file (optional)
|
||||
:param magic_file: specify the magic file (optional)
|
||||
:param zipmodule: specify the type of zip module to use (0:chilkat, 1:zipfile, 2:patch zipfile)
|
||||
|
||||
:type filename: string
|
||||
:type raw: boolean
|
||||
:type mode: string
|
||||
:type magic_file: string
|
||||
:type zipmodule: int
|
||||
|
||||
:Example:
|
||||
APK("myfile.apk")
|
||||
APK(read("myfile.apk"), raw=True)
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
filename,
|
||||
raw=False,
|
||||
mode="r",
|
||||
magic_file=None,
|
||||
zipmodule=ZIPMODULE):
|
||||
self.filename = filename
|
||||
|
||||
self.xml = {}
|
||||
self.axml = {}
|
||||
self.arsc = {}
|
||||
|
||||
self.package = ""
|
||||
self.androidversion = {}
|
||||
self.permissions = []
|
||||
self.declared_permissions = {}
|
||||
self.valid_apk = False
|
||||
|
||||
self.files = {}
|
||||
self.files_crc32 = {}
|
||||
|
||||
self.magic_file = magic_file
|
||||
|
||||
if raw is True:
|
||||
self.__raw = filename
|
||||
else:
|
||||
self.__raw = read(filename)
|
||||
|
||||
self.zipmodule = zipmodule
|
||||
|
||||
if zipmodule == 0:
|
||||
self.zip = ChilkatZip(self.__raw)
|
||||
elif zipmodule == 2:
|
||||
from androguard.patch import zipfile
|
||||
self.zip = zipfile.ZipFile(StringIO.StringIO(self.__raw), mode=mode)
|
||||
else:
|
||||
import zipfile
|
||||
self.zip = zipfile.ZipFile(StringIO.StringIO(self.__raw), mode=mode)
|
||||
|
||||
for i in self.zip.namelist():
|
||||
if i == "AndroidManifest.xml":
|
||||
self.axml[i] = AXMLPrinter(self.zip.read(i))
|
||||
try:
|
||||
self.xml[i] = minidom.parseString(self.axml[i].get_buff())
|
||||
except:
|
||||
self.xml[i] = None
|
||||
|
||||
if self.xml[i] != None:
|
||||
self.package = self.xml[i].documentElement.getAttribute(
|
||||
"package")
|
||||
self.androidversion[
|
||||
"Code"
|
||||
] = self.xml[i].documentElement.getAttributeNS(
|
||||
NS_ANDROID_URI, "versionCode")
|
||||
self.androidversion[
|
||||
"Name"
|
||||
] = self.xml[i].documentElement.getAttributeNS(
|
||||
NS_ANDROID_URI, "versionName")
|
||||
|
||||
self.valid_apk = True
|
||||
|
||||
def get_AndroidManifest(self):
|
||||
"""
|
||||
Return the Android Manifest XML file
|
||||
|
||||
:rtype: xml object
|
||||
"""
|
||||
return self.xml["AndroidManifest.xml"]
|
||||
|
||||
def is_valid_APK(self):
|
||||
"""
|
||||
Return true if the APK is valid, false otherwise
|
||||
|
||||
:rtype: boolean
|
||||
"""
|
||||
return self.valid_apk
|
||||
|
||||
def get_filename(self):
|
||||
"""
|
||||
Return the filename of the APK
|
||||
|
||||
:rtype: string
|
||||
"""
|
||||
return self.filename
|
||||
|
||||
def get_package(self):
|
||||
"""
|
||||
Return the name of the package
|
||||
|
||||
:rtype: string
|
||||
"""
|
||||
return self.package
|
||||
|
||||
def get_version_code(self):
|
||||
"""
|
||||
Return the android version code
|
||||
|
||||
:rtype: string
|
||||
"""
|
||||
return self.androidversion["Code"]
|
||||
|
||||
def get_version_name(self):
|
||||
"""
|
||||
Return the android version name
|
||||
|
||||
:rtype: string
|
||||
"""
|
||||
return self.androidversion["Name"]
|
||||
|
||||
def get_raw(self):
|
||||
"""
|
||||
Return raw bytes of the APK
|
||||
|
||||
:rtype: string
|
||||
"""
|
||||
return self.__raw
|
||||
|
||||
def get_elements(self, tag_name, attribute):
|
||||
"""
|
||||
Return elements in xml files which match with the tag name and the specific attribute
|
||||
|
||||
:param tag_name: a string which specify the tag name
|
||||
:param attribute: a string which specify the attribute
|
||||
"""
|
||||
l = []
|
||||
for i in self.xml:
|
||||
for item in self.xml[i].getElementsByTagName(tag_name):
|
||||
value = item.getAttributeNS(NS_ANDROID_URI, attribute)
|
||||
value = self.format_value(value)
|
||||
|
||||
l.append(str(value))
|
||||
return l
|
||||
|
||||
def format_value(self, value):
|
||||
if len(value) > 0:
|
||||
if value[0] == ".":
|
||||
value = self.package + value
|
||||
else:
|
||||
v_dot = value.find(".")
|
||||
if v_dot == 0:
|
||||
value = self.package + "." + value
|
||||
elif v_dot == -1:
|
||||
value = self.package + "." + value
|
||||
return value
|
||||
|
||||
def get_element(self, tag_name, attribute, **attribute_filter):
|
||||
"""
|
||||
Return element in xml files which match with the tag name and the specific attribute
|
||||
|
||||
:param tag_name: specify the tag name
|
||||
:type tag_name: string
|
||||
:param attribute: specify the attribute
|
||||
:type attribute: string
|
||||
|
||||
:rtype: string
|
||||
"""
|
||||
for i in self.xml:
|
||||
for item in self.xml[i].getElementsByTagName(tag_name):
|
||||
skip_this_item = False
|
||||
for attr, val in attribute_filter.items():
|
||||
attr_val = item.getAttributeNS(NS_ANDROID_URI, attr)
|
||||
if attr_val != val:
|
||||
skip_this_item = True
|
||||
break
|
||||
|
||||
if skip_this_item:
|
||||
continue
|
||||
|
||||
value = item.getAttributeNS(NS_ANDROID_URI, attribute)
|
||||
|
||||
if len(value) > 0:
|
||||
return value
|
||||
return None
|
||||
|
||||
def get_max_sdk_version(self):
|
||||
"""
|
||||
Return the android:maxSdkVersion attribute
|
||||
|
||||
:rtype: string
|
||||
"""
|
||||
return self.get_element("uses-sdk", "maxSdkVersion")
|
||||
|
||||
def get_min_sdk_version(self):
|
||||
"""
|
||||
Return the android:minSdkVersion attribute
|
||||
|
||||
:rtype: string
|
||||
"""
|
||||
return self.get_element("uses-sdk", "minSdkVersion")
|
||||
|
||||
def get_target_sdk_version(self):
|
||||
"""
|
||||
Return the android:targetSdkVersion attribute
|
||||
|
||||
:rtype: string
|
||||
"""
|
||||
return self.get_element("uses-sdk", "targetSdkVersion")
|
||||
|
||||
def get_android_manifest_axml(self):
|
||||
"""
|
||||
Return the :class:`AXMLPrinter` object which corresponds to the AndroidManifest.xml file
|
||||
|
||||
:rtype: :class:`AXMLPrinter`
|
||||
"""
|
||||
try:
|
||||
return self.axml["AndroidManifest.xml"]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def get_android_manifest_xml(self):
|
||||
"""
|
||||
Return the xml object which corresponds to the AndroidManifest.xml file
|
||||
|
||||
:rtype: object
|
||||
"""
|
||||
try:
|
||||
return self.xml["AndroidManifest.xml"]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def show(self):
|
||||
|
||||
print "PACKAGE: ", self.get_package()
|
||||
print "VERSION NAME:", self.get_version_name()
|
||||
print "VERSION CODE:", self.get_version_code()
|
||||
|
||||
################################## AXML FORMAT ########################################
|
||||
# Translated from
|
||||
# http://code.google.com/p/android4me/source/browse/src/android/content/res/AXmlResourceParser.java
|
||||
|
||||
UTF8_FLAG = 0x00000100
|
||||
CHUNK_STRINGPOOL_TYPE = 0x001C0001
|
||||
CHUNK_NULL_TYPE = 0x00000000
|
||||
|
||||
|
||||
class StringBlock(object):
|
||||
|
||||
def __init__(self, buff):
|
||||
self.start = buff.get_idx()
|
||||
self._cache = {}
|
||||
self.header_size, self.header = self.skipNullPadding(buff)
|
||||
|
||||
self.chunkSize = unpack('<i', buff.read(4))[0]
|
||||
self.stringCount = unpack('<i', buff.read(4))[0]
|
||||
self.styleOffsetCount = unpack('<i', buff.read(4))[0]
|
||||
|
||||
self.flags = unpack('<i', buff.read(4))[0]
|
||||
self.m_isUTF8 = ((self.flags & UTF8_FLAG) != 0)
|
||||
|
||||
self.stringsOffset = unpack('<i', buff.read(4))[0]
|
||||
self.stylesOffset = unpack('<i', buff.read(4))[0]
|
||||
|
||||
self.m_stringOffsets = []
|
||||
self.m_styleOffsets = []
|
||||
self.m_charbuff = ""
|
||||
self.m_styles = []
|
||||
|
||||
for i in range(0, self.stringCount):
|
||||
self.m_stringOffsets.append(unpack('<i', buff.read(4))[0])
|
||||
|
||||
for i in range(0, self.styleOffsetCount):
|
||||
self.m_styleOffsets.append(unpack('<i', buff.read(4))[0])
|
||||
|
||||
size = self.chunkSize - self.stringsOffset
|
||||
if self.stylesOffset != 0:
|
||||
size = self.stylesOffset - self.stringsOffset
|
||||
|
||||
# FIXME
|
||||
if (size % 4) != 0:
|
||||
warning("ooo")
|
||||
|
||||
self.m_charbuff = buff.read(size)
|
||||
|
||||
if self.stylesOffset != 0:
|
||||
size = self.chunkSize - self.stylesOffset
|
||||
|
||||
# FIXME
|
||||
if (size % 4) != 0:
|
||||
warning("ooo")
|
||||
|
||||
for i in range(0, size / 4):
|
||||
self.m_styles.append(unpack('<i', buff.read(4))[0])
|
||||
|
||||
def skipNullPadding(self, buff):
|
||||
|
||||
def readNext(buff, first_run=True):
|
||||
header = unpack('<i', buff.read(4))[0]
|
||||
|
||||
if header == CHUNK_NULL_TYPE and first_run:
|
||||
info("Skipping null padding in StringBlock header")
|
||||
header = readNext(buff, first_run=False)
|
||||
elif header != CHUNK_STRINGPOOL_TYPE:
|
||||
warning("Invalid StringBlock header")
|
||||
|
||||
return header
|
||||
|
||||
header = readNext(buff)
|
||||
return header >> 8, header & 0xFF
|
||||
|
||||
def getString(self, idx):
|
||||
if idx in self._cache:
|
||||
return self._cache[idx]
|
||||
|
||||
if idx < 0 or not self.m_stringOffsets or idx >= len(
|
||||
self.m_stringOffsets):
|
||||
return ""
|
||||
|
||||
offset = self.m_stringOffsets[idx]
|
||||
|
||||
if self.m_isUTF8:
|
||||
self._cache[idx] = self.decode8(offset)
|
||||
else:
|
||||
self._cache[idx] = self.decode16(offset)
|
||||
|
||||
return self._cache[idx]
|
||||
|
||||
def getStyle(self, idx):
|
||||
# FIXME
|
||||
return self.m_styles[idx]
|
||||
|
||||
def decode8(self, offset):
|
||||
str_len, skip = self.decodeLength(offset, 1)
|
||||
offset += skip
|
||||
|
||||
encoded_bytes, skip = self.decodeLength(offset, 1)
|
||||
offset += skip
|
||||
|
||||
data = self.m_charbuff[offset: offset + encoded_bytes]
|
||||
|
||||
return self.decode_bytes(data, 'utf-8', str_len)
|
||||
|
||||
def decode16(self, offset):
|
||||
str_len, skip = self.decodeLength(offset, 2)
|
||||
offset += skip
|
||||
|
||||
encoded_bytes = str_len * 2
|
||||
|
||||
data = self.m_charbuff[offset: offset + encoded_bytes]
|
||||
|
||||
return self.decode_bytes(data, 'utf-16', str_len)
|
||||
|
||||
def decode_bytes(self, data, encoding, str_len):
|
||||
string = data.decode(encoding, 'replace')
|
||||
if len(string) != str_len:
|
||||
warning("invalid decoded string length")
|
||||
return string
|
||||
|
||||
def decodeLength(self, offset, sizeof_char):
|
||||
length = ord(self.m_charbuff[offset])
|
||||
|
||||
sizeof_2chars = sizeof_char << 1
|
||||
fmt_chr = 'B' if sizeof_char == 1 else 'H'
|
||||
fmt = "<2" + fmt_chr
|
||||
|
||||
length1, length2 = unpack(fmt, self.m_charbuff[offset:(offset + sizeof_2chars)])
|
||||
|
||||
highbit = 0x80 << (8 * (sizeof_char - 1))
|
||||
|
||||
if (length & highbit) != 0:
|
||||
return ((length1 & ~highbit) << (8 * sizeof_char)) | length2, sizeof_2chars
|
||||
else:
|
||||
return length1, sizeof_char
|
||||
|
||||
def show(self):
|
||||
print "StringBlock(%x, %x, %x, %x, %x, %x" % (
|
||||
self.start,
|
||||
self.header,
|
||||
self.header_size,
|
||||
self.chunkSize,
|
||||
self.stringsOffset,
|
||||
self.flags)
|
||||
for i in range(0, len(self.m_stringOffsets)):
|
||||
print i, repr(self.getString(i))
|
||||
|
||||
START_DOCUMENT = 0
|
||||
END_DOCUMENT = 1
|
||||
START_TAG = 2
|
||||
END_TAG = 3
|
||||
TEXT = 4
|
||||
|
||||
ATTRIBUTE_IX_NAMESPACE_URI = 0
|
||||
ATTRIBUTE_IX_NAME = 1
|
||||
ATTRIBUTE_IX_VALUE_STRING = 2
|
||||
ATTRIBUTE_IX_VALUE_TYPE = 3
|
||||
ATTRIBUTE_IX_VALUE_DATA = 4
|
||||
ATTRIBUTE_LENGHT = 5
|
||||
|
||||
CHUNK_AXML_FILE = 0x00080003
|
||||
CHUNK_RESOURCEIDS = 0x00080180
|
||||
CHUNK_XML_FIRST = 0x00100100
|
||||
CHUNK_XML_START_NAMESPACE = 0x00100100
|
||||
CHUNK_XML_END_NAMESPACE = 0x00100101
|
||||
CHUNK_XML_START_TAG = 0x00100102
|
||||
CHUNK_XML_END_TAG = 0x00100103
|
||||
CHUNK_XML_TEXT = 0x00100104
|
||||
CHUNK_XML_LAST = 0x00100104
|
||||
|
||||
|
||||
class AXMLParser(object):
|
||||
|
||||
def __init__(self, raw_buff):
|
||||
self.reset()
|
||||
|
||||
self.valid_axml = True
|
||||
self.buff = BuffHandle(raw_buff)
|
||||
|
||||
axml_file = unpack('<L', self.buff.read(4))[0]
|
||||
|
||||
if axml_file == CHUNK_AXML_FILE:
|
||||
self.buff.read(4)
|
||||
|
||||
self.sb = StringBlock(self.buff)
|
||||
|
||||
self.m_resourceIDs = []
|
||||
self.m_prefixuri = {}
|
||||
self.m_uriprefix = {}
|
||||
self.m_prefixuriL = []
|
||||
|
||||
self.visited_ns = []
|
||||
else:
|
||||
self.valid_axml = False
|
||||
warning("Not a valid xml file")
|
||||
|
||||
def is_valid(self):
|
||||
return self.valid_axml
|
||||
|
||||
def reset(self):
|
||||
self.m_event = -1
|
||||
self.m_lineNumber = -1
|
||||
self.m_name = -1
|
||||
self.m_namespaceUri = -1
|
||||
self.m_attributes = []
|
||||
self.m_idAttribute = -1
|
||||
self.m_classAttribute = -1
|
||||
self.m_styleAttribute = -1
|
||||
|
||||
def next(self):
|
||||
self.doNext()
|
||||
return self.m_event
|
||||
|
||||
def doNext(self):
|
||||
if self.m_event == END_DOCUMENT:
|
||||
return
|
||||
|
||||
event = self.m_event
|
||||
|
||||
self.reset()
|
||||
while True:
|
||||
chunkType = -1
|
||||
|
||||
# Fake END_DOCUMENT event.
|
||||
if event == END_TAG:
|
||||
pass
|
||||
|
||||
# START_DOCUMENT
|
||||
if event == START_DOCUMENT:
|
||||
chunkType = CHUNK_XML_START_TAG
|
||||
else:
|
||||
if self.buff.end():
|
||||
self.m_event = END_DOCUMENT
|
||||
break
|
||||
chunkType = unpack('<L', self.buff.read(4))[0]
|
||||
|
||||
if chunkType == CHUNK_RESOURCEIDS:
|
||||
chunkSize = unpack('<L', self.buff.read(4))[0]
|
||||
# FIXME
|
||||
if chunkSize < 8 or chunkSize % 4 != 0:
|
||||
warning("Invalid chunk size")
|
||||
|
||||
for i in range(0, chunkSize / 4 - 2):
|
||||
self.m_resourceIDs.append(
|
||||
unpack('<L', self.buff.read(4))[0])
|
||||
|
||||
continue
|
||||
|
||||
# FIXME
|
||||
if chunkType < CHUNK_XML_FIRST or chunkType > CHUNK_XML_LAST:
|
||||
warning("invalid chunk type")
|
||||
|
||||
# Fake START_DOCUMENT event.
|
||||
if chunkType == CHUNK_XML_START_TAG and event == -1:
|
||||
self.m_event = START_DOCUMENT
|
||||
break
|
||||
|
||||
self.buff.read(4) # /*chunkSize*/
|
||||
lineNumber = unpack('<L', self.buff.read(4))[0]
|
||||
self.buff.read(4) # 0xFFFFFFFF
|
||||
|
||||
if chunkType == CHUNK_XML_START_NAMESPACE or chunkType == CHUNK_XML_END_NAMESPACE:
|
||||
if chunkType == CHUNK_XML_START_NAMESPACE:
|
||||
prefix = unpack('<L', self.buff.read(4))[0]
|
||||
uri = unpack('<L', self.buff.read(4))[0]
|
||||
|
||||
self.m_prefixuri[prefix] = uri
|
||||
self.m_uriprefix[uri] = prefix
|
||||
self.m_prefixuriL.append((prefix, uri))
|
||||
self.ns = uri
|
||||
else:
|
||||
self.ns = -1
|
||||
self.buff.read(4)
|
||||
self.buff.read(4)
|
||||
(prefix, uri) = self.m_prefixuriL.pop()
|
||||
|
||||
continue
|
||||
|
||||
self.m_lineNumber = lineNumber
|
||||
|
||||
if chunkType == CHUNK_XML_START_TAG:
|
||||
self.m_namespaceUri = unpack('<L', self.buff.read(4))[0]
|
||||
self.m_name = unpack('<L', self.buff.read(4))[0]
|
||||
|
||||
# FIXME
|
||||
self.buff.read(4) # flags
|
||||
|
||||
attributeCount = unpack('<L', self.buff.read(4))[0]
|
||||
self.m_idAttribute = (attributeCount >> 16) - 1
|
||||
attributeCount = attributeCount & 0xFFFF
|
||||
self.m_classAttribute = unpack('<L', self.buff.read(4))[0]
|
||||
self.m_styleAttribute = (self.m_classAttribute >> 16) - 1
|
||||
|
||||
self.m_classAttribute = (self.m_classAttribute & 0xFFFF) - 1
|
||||
|
||||
for i in range(0, attributeCount * ATTRIBUTE_LENGHT):
|
||||
self.m_attributes.append(unpack('<L', self.buff.read(4))[0])
|
||||
|
||||
for i in range(ATTRIBUTE_IX_VALUE_TYPE, len(self.m_attributes),
|
||||
ATTRIBUTE_LENGHT):
|
||||
self.m_attributes[i] = self.m_attributes[i] >> 24
|
||||
|
||||
self.m_event = START_TAG
|
||||
break
|
||||
|
||||
if chunkType == CHUNK_XML_END_TAG:
|
||||
self.m_namespaceUri = unpack('<L', self.buff.read(4))[0]
|
||||
self.m_name = unpack('<L', self.buff.read(4))[0]
|
||||
self.m_event = END_TAG
|
||||
break
|
||||
|
||||
if chunkType == CHUNK_XML_TEXT:
|
||||
self.m_name = unpack('<L', self.buff.read(4))[0]
|
||||
|
||||
# FIXME
|
||||
self.buff.read(4)
|
||||
self.buff.read(4)
|
||||
|
||||
self.m_event = TEXT
|
||||
break
|
||||
|
||||
def getPrefixByUri(self, uri):
|
||||
try:
|
||||
return self.m_uriprefix[uri]
|
||||
except KeyError:
|
||||
return -1
|
||||
|
||||
def getPrefix(self):
|
||||
try:
|
||||
return self.sb.getString(self.m_uriprefix[self.m_namespaceUri])
|
||||
except KeyError:
|
||||
return u''
|
||||
|
||||
def getName(self):
|
||||
if self.m_name == -1 or (self.m_event != START_TAG and
|
||||
self.m_event != END_TAG):
|
||||
return u''
|
||||
|
||||
return self.sb.getString(self.m_name)
|
||||
|
||||
def getText(self):
|
||||
if self.m_name == -1 or self.m_event != TEXT:
|
||||
return u''
|
||||
|
||||
return self.sb.getString(self.m_name)
|
||||
|
||||
def getNamespacePrefix(self, pos):
|
||||
prefix = self.m_prefixuriL[pos][0]
|
||||
return self.sb.getString(prefix)
|
||||
|
||||
def getNamespaceUri(self, pos):
|
||||
uri = self.m_prefixuriL[pos][1]
|
||||
return self.sb.getString(uri)
|
||||
|
||||
def getXMLNS(self):
|
||||
buff = ""
|
||||
for i in self.m_uriprefix:
|
||||
if i not in self.visited_ns:
|
||||
buff += "xmlns:%s=\"%s\"\n" % (
|
||||
self.sb.getString(self.m_uriprefix[i]),
|
||||
self.sb.getString(self.m_prefixuri[self.m_uriprefix[i]]))
|
||||
self.visited_ns.append(i)
|
||||
return buff
|
||||
|
||||
def getNamespaceCount(self, pos):
|
||||
pass
|
||||
|
||||
def getAttributeOffset(self, index):
|
||||
# FIXME
|
||||
if self.m_event != START_TAG:
|
||||
warning("Current event is not START_TAG.")
|
||||
|
||||
offset = index * 5
|
||||
# FIXME
|
||||
if offset >= len(self.m_attributes):
|
||||
warning("Invalid attribute index")
|
||||
|
||||
return offset
|
||||
|
||||
def getAttributeCount(self):
|
||||
if self.m_event != START_TAG:
|
||||
return -1
|
||||
|
||||
return len(self.m_attributes) / ATTRIBUTE_LENGHT
|
||||
|
||||
def getAttributePrefix(self, index):
|
||||
offset = self.getAttributeOffset(index)
|
||||
uri = self.m_attributes[offset + ATTRIBUTE_IX_NAMESPACE_URI]
|
||||
|
||||
prefix = self.getPrefixByUri(uri)
|
||||
|
||||
if prefix == -1:
|
||||
return ""
|
||||
|
||||
return self.sb.getString(prefix)
|
||||
|
||||
def getAttributeName(self, index):
|
||||
offset = self.getAttributeOffset(index)
|
||||
name = self.m_attributes[offset + ATTRIBUTE_IX_NAME]
|
||||
|
||||
if name == -1:
|
||||
return ""
|
||||
|
||||
res = self.sb.getString(name)
|
||||
if not res:
|
||||
attr = self.m_resourceIDs[name]
|
||||
if attr in SYSTEM_RESOURCES['attributes']['inverse']:
|
||||
res = 'android:' + SYSTEM_RESOURCES['attributes']['inverse'][
|
||||
attr
|
||||
]
|
||||
|
||||
return res
|
||||
|
||||
def getAttributeValueType(self, index):
|
||||
offset = self.getAttributeOffset(index)
|
||||
return self.m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE]
|
||||
|
||||
def getAttributeValueData(self, index):
|
||||
offset = self.getAttributeOffset(index)
|
||||
return self.m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA]
|
||||
|
||||
def getAttributeValue(self, index):
|
||||
offset = self.getAttributeOffset(index)
|
||||
valueType = self.m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE]
|
||||
if valueType == TYPE_STRING:
|
||||
valueString = self.m_attributes[offset + ATTRIBUTE_IX_VALUE_STRING]
|
||||
return self.sb.getString(valueString)
|
||||
# WIP
|
||||
return ""
|
||||
|
||||
# resource constants
|
||||
|
||||
TYPE_ATTRIBUTE = 2
|
||||
TYPE_DIMENSION = 5
|
||||
TYPE_FIRST_COLOR_INT = 28
|
||||
TYPE_FIRST_INT = 16
|
||||
TYPE_FLOAT = 4
|
||||
TYPE_FRACTION = 6
|
||||
TYPE_INT_BOOLEAN = 18
|
||||
TYPE_INT_DEC = 16
|
||||
TYPE_INT_HEX = 17
|
||||
TYPE_LAST_COLOR_INT = 31
|
||||
TYPE_LAST_INT = 31
|
||||
TYPE_NULL = 0
|
||||
TYPE_REFERENCE = 1
|
||||
TYPE_STRING = 3
|
||||
|
||||
TYPE_TABLE = {
|
||||
TYPE_ATTRIBUTE: "attribute",
|
||||
TYPE_DIMENSION: "dimension",
|
||||
TYPE_FLOAT: "float",
|
||||
TYPE_FRACTION: "fraction",
|
||||
TYPE_INT_BOOLEAN: "int_boolean",
|
||||
TYPE_INT_DEC: "int_dec",
|
||||
TYPE_INT_HEX: "int_hex",
|
||||
TYPE_NULL: "null",
|
||||
TYPE_REFERENCE: "reference",
|
||||
TYPE_STRING: "string",
|
||||
}
|
||||
|
||||
RADIX_MULTS = [0.00390625, 3.051758E-005, 1.192093E-007, 4.656613E-010]
|
||||
DIMENSION_UNITS = ["px", "dip", "sp", "pt", "in", "mm"]
|
||||
FRACTION_UNITS = ["%", "%p"]
|
||||
|
||||
COMPLEX_UNIT_MASK = 15
|
||||
|
||||
|
||||
def complexToFloat(xcomplex):
|
||||
return (float)(xcomplex & 0xFFFFFF00) * RADIX_MULTS[(xcomplex >> 4) & 3]
|
||||
|
||||
|
||||
def getPackage(id):
|
||||
if id >> 24 == 1:
|
||||
return "android:"
|
||||
return ""
|
||||
|
||||
|
||||
def format_value(_type, _data, lookup_string=lambda ix: "<string>"):
|
||||
if _type == TYPE_STRING:
|
||||
return lookup_string(_data)
|
||||
|
||||
elif _type == TYPE_ATTRIBUTE:
|
||||
return "?%s%08X" % (getPackage(_data), _data)
|
||||
|
||||
elif _type == TYPE_REFERENCE:
|
||||
return "@%s%08X" % (getPackage(_data), _data)
|
||||
|
||||
elif _type == TYPE_FLOAT:
|
||||
return "%f" % unpack("=f", pack("=L", _data))[0]
|
||||
|
||||
elif _type == TYPE_INT_HEX:
|
||||
return "0x%08X" % _data
|
||||
|
||||
elif _type == TYPE_INT_BOOLEAN:
|
||||
if _data == 0:
|
||||
return "false"
|
||||
return "true"
|
||||
|
||||
elif _type == TYPE_DIMENSION:
|
||||
return "%f%s" % (complexToFloat(_data), DIMENSION_UNITS[_data & COMPLEX_UNIT_MASK])
|
||||
|
||||
elif _type == TYPE_FRACTION:
|
||||
return "%f%s" % (complexToFloat(_data) * 100, FRACTION_UNITS[_data & COMPLEX_UNIT_MASK])
|
||||
|
||||
elif _type >= TYPE_FIRST_COLOR_INT and _type <= TYPE_LAST_COLOR_INT:
|
||||
return "#%08X" % _data
|
||||
|
||||
elif _type >= TYPE_FIRST_INT and _type <= TYPE_LAST_INT:
|
||||
return "%d" % long2int(_data)
|
||||
|
||||
return "<0x%X, type 0x%02X>" % (_data, _type)
|
||||
|
||||
|
||||
class AXMLPrinter(object):
|
||||
|
||||
def __init__(self, raw_buff):
|
||||
self.axml = AXMLParser(raw_buff)
|
||||
self.xmlns = False
|
||||
|
||||
self.buff = u''
|
||||
|
||||
while True and self.axml.is_valid():
|
||||
_type = self.axml.next()
|
||||
|
||||
if _type == START_DOCUMENT:
|
||||
self.buff += u'<?xml version="1.0" encoding="utf-8"?>\n'
|
||||
elif _type == START_TAG:
|
||||
self.buff += u'<' + self.getPrefix(self.axml.getPrefix(
|
||||
)) + self.axml.getName() + u'\n'
|
||||
self.buff += self.axml.getXMLNS()
|
||||
|
||||
for i in range(0, self.axml.getAttributeCount()):
|
||||
self.buff += "%s%s=\"%s\"\n" % (
|
||||
self.getPrefix(
|
||||
self.axml.getAttributePrefix(i)),
|
||||
self.axml.getAttributeName(i),
|
||||
self._escape(self.getAttributeValue(i)))
|
||||
|
||||
self.buff += u'>\n'
|
||||
|
||||
elif _type == END_TAG:
|
||||
self.buff += "</%s%s>\n" % (
|
||||
self.getPrefix(self.axml.getPrefix()), self.axml.getName())
|
||||
|
||||
elif _type == TEXT:
|
||||
self.buff += "%s\n" % self.axml.getText()
|
||||
|
||||
elif _type == END_DOCUMENT:
|
||||
break
|
||||
|
||||
# pleed patch
|
||||
def _escape(self, s):
|
||||
s = s.replace("&", "&")
|
||||
s = s.replace('"', """)
|
||||
s = s.replace("'", "'")
|
||||
s = s.replace("<", "<")
|
||||
s = s.replace(">", ">")
|
||||
return escape(s)
|
||||
|
||||
def get_buff(self):
|
||||
return self.buff.encode('utf-8')
|
||||
|
||||
def get_xml(self):
|
||||
return minidom.parseString(self.get_buff()).toprettyxml(
|
||||
encoding="utf-8")
|
||||
|
||||
def get_xml_obj(self):
|
||||
return minidom.parseString(self.get_buff())
|
||||
|
||||
def getPrefix(self, prefix):
|
||||
if prefix is None or len(prefix) == 0:
|
||||
return u''
|
||||
|
||||
return prefix + u':'
|
||||
|
||||
def getAttributeValue(self, index):
|
||||
_type = self.axml.getAttributeValueType(index)
|
||||
_data = self.axml.getAttributeValueData(index)
|
||||
|
||||
return format_value(_type, _data, lambda _: self.axml.getAttributeValue(index))
|
||||
|
||||
|
||||
class SV(object):
|
||||
|
||||
def __init__(self, size, buff):
|
||||
self.__size = size
|
||||
self.__value = unpack(self.__size, buff)[0]
|
||||
|
||||
def _get(self):
|
||||
return pack(self.__size, self.__value)
|
||||
|
||||
def __str__(self):
|
||||
return "0x%x" % self.__value
|
||||
|
||||
def __int__(self):
|
||||
return self.__value
|
||||
|
||||
def get_value_buff(self):
|
||||
return self._get()
|
||||
|
||||
def get_value(self):
|
||||
return self.__value
|
||||
|
||||
def set_value(self, attr):
|
||||
self.__value = attr
|
||||
|
||||
|
||||
class SVs(object):
|
||||
|
||||
def __init__(self, size, ntuple, buff):
|
||||
self.__size = size
|
||||
|
||||
self.__value = ntuple._make(unpack(self.__size, buff))
|
||||
|
||||
def _get(self):
|
||||
l = []
|
||||
for i in self.__value._fields:
|
||||
l.append(getattr(self.__value, i))
|
||||
return pack(self.__size, *l)
|
||||
|
||||
def _export(self):
|
||||
return [x for x in self.__value._fields]
|
||||
|
||||
def get_value_buff(self):
|
||||
return self._get()
|
||||
|
||||
def get_value(self):
|
||||
return self.__value
|
||||
|
||||
def set_value(self, attr):
|
||||
self.__value = self.__value._replace(**attr)
|
||||
|
||||
def __str__(self):
|
||||
return self.__value.__str__()
|
||||
|
||||
|
||||
class BuffHandle(object):
|
||||
|
||||
def __init__(self, buff):
|
||||
self.__buff = buff
|
||||
self.__idx = 0
|
||||
|
||||
def size(self):
|
||||
return len(self.__buff)
|
||||
|
||||
def set_idx(self, idx):
|
||||
self.__idx = idx
|
||||
|
||||
def get_idx(self):
|
||||
return self.__idx
|
||||
|
||||
def readNullString(self, size):
|
||||
data = self.read(size)
|
||||
return data
|
||||
|
||||
def read_b(self, size):
|
||||
return self.__buff[self.__idx:self.__idx + size]
|
||||
|
||||
def read_at(self, offset, size):
|
||||
return self.__buff[offset:offset + size]
|
||||
|
||||
def read(self, size):
|
||||
if isinstance(size, SV):
|
||||
size = size.value
|
||||
|
||||
buff = self.__buff[self.__idx:self.__idx + size]
|
||||
self.__idx += size
|
||||
|
||||
return buff
|
||||
|
||||
def end(self):
|
||||
return self.__idx == len(self.__buff)
|
||||
|
||||
|
||||
class Buff(object):
|
||||
|
||||
def __init__(self, offset, buff):
|
||||
self.offset = offset
|
||||
self.buff = buff
|
||||
|
||||
self.size = len(buff)
|
||||
|
||||
|
||||
def long2int(l):
|
||||
if l > 0x7fffffff:
|
||||
l = (0x7fffffff & l) - 0x80000000
|
||||
return l
|
1
app/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
BIN
app/aliyun-libs/FaceLivenessOpen-2.1.6.12.aar
Normal file
BIN
app/aliyun-libs/NoCaptchaSDK-external-release-5.4.29.aar
Normal file
BIN
app/aliyun-libs/SecurityBodySDK-external-release-5.4.79.aar
Normal file
BIN
app/aliyun-libs/SecurityGuardSDK-external-release-5.4.121.aar
Normal file
BIN
app/aliyun-libs/aliyun-oss-sdk-android-2.3.0.1.jar
Normal file
BIN
app/aliyun-libs/rpsdk-release-3.0.0.1.aar
Normal file
BIN
app/aliyun-libs/windvane-min-8.0.3.2.3.jar
Normal file
78
app/and_res_guard.gradle
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* *************************************
|
||||
* android 资源混淆脚本
|
||||
***************************************
|
||||
*/
|
||||
apply plugin: 'AndResGuard'
|
||||
|
||||
|
||||
andResGuard {
|
||||
mappingFile = file("./resource_mapping.txt")
|
||||
// mappingFile = null
|
||||
// 当你使用v2签名的时候,7zip压缩是无法生效的。
|
||||
use7zip = false
|
||||
useSign = true
|
||||
// 打开这个开关,会keep住所有资源的原始路径,只混淆资源的名字
|
||||
keepRoot = false
|
||||
whiteList = [
|
||||
// for your icon
|
||||
"R.mipmap.app_logo",
|
||||
// for fabric
|
||||
"R.string.com.crashlytics.*",
|
||||
//sharesdk
|
||||
"R.string.ssdk_*",
|
||||
"R.string.smssdk_*",
|
||||
"R.layout.ssdk_*",
|
||||
"R.drawable.ssdk_*",
|
||||
"R.mipmap.ssdk_*",
|
||||
"R.anim.ssdk_*",
|
||||
"R.color.ssdk_*",
|
||||
"R.style.ssdk_*",
|
||||
"R.id.ssdk_*",
|
||||
//ktv
|
||||
"R.id.sb_accompany_voice",
|
||||
"R.id.tv_accompany_voice",
|
||||
"R.id.sb_person_voice",
|
||||
"R.id.tv_person_voice",
|
||||
"R.id.tv_change_audio",
|
||||
"R.id.iv_play_or_pause",
|
||||
"R.id.cv_people_sound",
|
||||
"R.id.cv_accompany_sound",
|
||||
"R.id.layout_song_progress",
|
||||
// for google-services
|
||||
"R.string.google_app_id",
|
||||
"R.string.gcm_defaultSenderId",
|
||||
"R.string.default_web_client_id",
|
||||
"R.string.ga_trackingId",
|
||||
"R.string.firebase_database_url",
|
||||
"R.string.google_api_key",
|
||||
"R.string.google_crash_reporting_api_key",
|
||||
//for aliyun RPSDK
|
||||
"R.drawable.yw_1222_*",
|
||||
//for voice match
|
||||
"R.id.iv_group_like",
|
||||
"R.id.iv_group_dont_like",
|
||||
"R.id.fl_group_content",
|
||||
"R.id.svga_group_voice_like"
|
||||
]
|
||||
compressFilePattern = [
|
||||
"*.png",
|
||||
"*.jpg",
|
||||
"*.jpeg",
|
||||
"*.gif",
|
||||
"resources.arsc"
|
||||
]
|
||||
sevenzip {
|
||||
artifact = 'com.tencent.mm:SevenZip:1.2.15'
|
||||
//path = "/usr/local/bin/7za"
|
||||
}
|
||||
|
||||
// erban-${variant.buildType.name}-${defaultConfig.versionName}_${defaultConfig.versionCode}-${releaseTime()}-${variant.productFlavors[0].name}.apk".toLowerCase())
|
||||
// sourceApk = "${project.rootDir}/耳伴-${buildTypes}-${defaultConfig.versionName}_${defaultConfig.versionCode}-${releaseTime()}.apk".toLowerCase()
|
||||
|
||||
/**
|
||||
* 可选: 指定v1签名时生成jar文件的摘要算法
|
||||
* 默认值为“SHA-1”
|
||||
**/
|
||||
// digestalg = "SHA-256"
|
||||
}
|
280
app/build.gradle
Normal file
@@ -0,0 +1,280 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
// android res guard 资源混淆脚本
|
||||
apply from: 'and_res_guard.gradle'
|
||||
|
||||
android {
|
||||
compileSdkVersion 27
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.wudoo.qingxun"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 26
|
||||
versionCode Integer.valueOf(version_code)
|
||||
versionName version_name
|
||||
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
multiDexEnabled true
|
||||
|
||||
ndk {
|
||||
//设置支持的SO库架构
|
||||
if (ndk_abi_filters == "x86") {
|
||||
abiFilters "x86"
|
||||
} else if (ndk_abi_filters == "arm_x86") {
|
||||
abiFilters "armeabi-v7a", "x86"
|
||||
} else {
|
||||
abiFilters "armeabi-v7a"
|
||||
}
|
||||
}
|
||||
|
||||
flavorDimensions 'default'
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
disable 'MissingTranslation'
|
||||
disable 'ExtraTranslation'
|
||||
}
|
||||
|
||||
dexOptions {
|
||||
javaMaxHeapSize "1g"
|
||||
jumboMode = true
|
||||
}
|
||||
|
||||
dataBinding {
|
||||
enabled = true
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
exclude 'lib/armeabi-v7a/libagora-crypto.so'
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
|
||||
v2 {
|
||||
storeFile file('../qx.jks')
|
||||
storePassword "123456789"
|
||||
keyAlias "key_qingxun"
|
||||
keyPassword "123456789"
|
||||
v2SigningEnabled true
|
||||
v1SigningEnabled true
|
||||
}
|
||||
|
||||
v1 {
|
||||
storeFile file('../qx.jks')
|
||||
storePassword "123456789"
|
||||
keyAlias "key_qingxun"
|
||||
keyPassword "123456789"
|
||||
if (sign_mode == "v1v2") {
|
||||
v2SigningEnabled true
|
||||
} else {
|
||||
v2SigningEnabled false
|
||||
}
|
||||
v1SigningEnabled true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
|
||||
main {
|
||||
java.srcDirs = [
|
||||
'src/main/java',
|
||||
'src/module_public_chat_hall/java',
|
||||
'src/module_upgrade_app/java',
|
||||
'src/module_mentoring_relationship/java',
|
||||
'src/module_labour_union/java',
|
||||
'src/module_room_chat/java',
|
||||
'src/model_customer_server/java',
|
||||
'src/module_music/java',
|
||||
'src/module_mini_world/java',
|
||||
'src/module_lottery_dialog/java',
|
||||
'src/module_bank_card/java',
|
||||
'src/module_super_admin/java',
|
||||
'src/module_treasure_box/java',
|
||||
|
||||
'src/module_community/java',
|
||||
'src/module_album/java'
|
||||
|
||||
]
|
||||
|
||||
res.srcDirs = [
|
||||
'src/main/res',
|
||||
'src/common/res',
|
||||
'src/module_public_chat_hall/res',
|
||||
'src/module_upgrade_app/res',
|
||||
'src/module_mentoring_relationship/res',
|
||||
'src/module_labour_union/res',
|
||||
'src/module_room_chat/res',
|
||||
'src/model_customer_server/res',
|
||||
'src/module_music/res',
|
||||
'src/module_mini_world/res',
|
||||
'src/module_lottery_dialog/res',
|
||||
'src/module_bank_card/res',
|
||||
'src/module_super_admin/res',
|
||||
'src/module_treasure_box/res',
|
||||
|
||||
'src/module_community/res',
|
||||
'src/module_album/res'
|
||||
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// buildConfigField "String", "BASE_URL", "\"https://www.erbanyy.com/\""
|
||||
buildConfigField "String", "BASE_URL", "\"https://api.qxjiaoyou.com/\""
|
||||
buildConfigField "String", "BASE_URL_DEBUG", "BASE_URL"
|
||||
buildConfigField "String", "BASE_URL_STAGING", "BASE_URL"
|
||||
buildConfigField "String", "BASE_URL_RELEASE", "BASE_URL"
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
signingConfig signingConfigs.v2
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
|
||||
staging {
|
||||
buildConfigField "String", "BASE_URL", "\"https://preview.qxjiaoyou.com/\""
|
||||
buildConfigField "String", "BASE_URL_DEBUG", "BASE_URL"
|
||||
buildConfigField "String", "BASE_URL_STAGING", "BASE_URL"
|
||||
buildConfigField "String", "BASE_URL_RELEASE", "BASE_URL"
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
signingConfig signingConfigs.v2
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
matchingFallbacks = ['staging', 'release']
|
||||
}
|
||||
|
||||
debug {
|
||||
// buildConfigField "String", "BASE_URL", "\"https://115.28.86.139/\""
|
||||
buildConfigField "String", "BASE_URL", "\"http://apibeta.qxjiaoyou.com/\""
|
||||
buildConfigField "String", "BASE_URL_DEBUG", "BASE_URL"
|
||||
buildConfigField "String", "BASE_URL_STAGING", "\"https://preview.qxjiaoyou.com/\""
|
||||
buildConfigField "String", "BASE_URL_RELEASE", "\"https://api.qxjiaoyou.com/\""
|
||||
|
||||
minifyEnabled false
|
||||
shrinkResources false
|
||||
signingConfig signingConfigs.v1
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
productFlavors {
|
||||
qingxun {
|
||||
dimension 'default'
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def supportLibraryVersion = "27.1.1"
|
||||
def Lombok = "1.16.20"
|
||||
|
||||
dependencies {
|
||||
api fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation('com.android.support.test.espresso:espresso-core:3.0.1', {
|
||||
exclude group: 'com.android.support', module: 'support-annotations'
|
||||
})
|
||||
|
||||
api "com.android.support:recyclerview-v7:${supportLibraryVersion}"
|
||||
api "com.android.support:cardview-v7:${supportLibraryVersion}"
|
||||
api "com.android.support:gridlayout-v7:${supportLibraryVersion}"
|
||||
api "com.android.support:multidex:1.0.3"
|
||||
|
||||
debugApi "com.squareup.leakcanary:leakcanary-android:1.6.3"
|
||||
releaseApi "com.squareup.leakcanary:leakcanary-android-no-op:1.6.3"
|
||||
stagingApi "com.squareup.leakcanary:leakcanary-android-no-op:1.6.3"
|
||||
|
||||
api "com.orhanobut:dialogplus:1.11@aar"
|
||||
|
||||
api "com.readystatesoftware.systembartint:systembartint:1.0.4"
|
||||
api "com.rengwuxian.materialedittext:library:2.1.4"
|
||||
api "com.github.flavienlaurent.datetimepicker:library:0.0.2"
|
||||
|
||||
api "com.darsh.multipleimageselect:multipleimageselect:1.0.4"
|
||||
api "me.shaohui.advancedluban:library:1.3.2"
|
||||
api "com.tencent.bugly:crashreport:2.8.6"
|
||||
api "pl.droidsonroids.gif:android-gif-drawable:1.2.7"
|
||||
// api "com.jude:rollviewpager:1.4.6"
|
||||
implementation 'com.github.dongxingrong:RollViewPager:V1.0'
|
||||
api "com.makeramen:roundedimageview:2.3.0"
|
||||
api "com.jzxiang.pickerview:TimePickerDialog:1.0.1"
|
||||
api "com.github.zyyoona7:EasyPopup:1.0.2"
|
||||
api "com.github.donkingliang:LabelsView:1.2.0"
|
||||
api "com.github.yyued:SVGAPlayer-Android:2.4.2"
|
||||
api "com.mcxiaoke.packer-ng:helper:2.0.0"
|
||||
implementation "com.orhanobut:logger:2.1.1"
|
||||
api "com.ms-square:expandableTextView:0.1.4"
|
||||
api "com.jakewharton:butterknife:8.8.1"
|
||||
annotationProcessor "com.jakewharton:butterknife-compiler:8.8.1"
|
||||
implementation "com.llew.huawei:verifier:1.0.3"
|
||||
|
||||
api "com.flyco.tablayout:FlycoTabLayout_Lib:2.1.2@aar"
|
||||
api "com.nex3z:flow-layout:1.2.2"
|
||||
|
||||
api project(':core')
|
||||
|
||||
implementation 'com.github.qiujayen:sticky-layoutmanager:1.0.1'
|
||||
|
||||
implementation 'com.github.chenBingX:SuperTextView:v3.0.0'
|
||||
|
||||
//支付密码输入框
|
||||
api 'com.jungly:gridPasswordView:0.3'
|
||||
api 'com.google.android:flexbox:1.0.0'
|
||||
|
||||
compileOnly "org.projectlombok:lombok:${Lombok}"
|
||||
annotationProcessor "org.projectlombok:lombok:${Lombok}"
|
||||
api "android.arch.lifecycle:extensions:1.1.1"
|
||||
implementation 'nl.dionsegijn:konfetti:1.1.2'
|
||||
|
||||
// 华为推送
|
||||
api(name: 'base-2.6.0.301', ext: 'aar')
|
||||
api(name: 'push-2.6.0.301', ext: 'aar')
|
||||
// 魅族推送
|
||||
implementation 'com.meizu.flyme.internet:push-internal:3.6.3@aar'
|
||||
|
||||
// api 'com.aliyun.dpa:oss-android-sdk:2.2.0'
|
||||
api files('aliyun-libs/aliyun-oss-sdk-android-2.3.0.1.jar')
|
||||
api files('aliyun-libs/windvane-min-8.0.3.2.3.jar')
|
||||
// api files('aliyun-libs/okhttp-3.12.0.jar')
|
||||
// api files('aliyun-libs/okio-1.16.0.jar')
|
||||
api(name: 'FaceLivenessOpen-2.1.6.12', ext: 'aar')
|
||||
api(name: 'rpsdk-release-3.0.0.1', ext: 'aar')
|
||||
api(name: 'SecurityGuardSDK-external-release-5.4.121', ext: 'aar')
|
||||
api(name: 'SecurityBodySDK-external-release-5.4.79', ext: 'aar')
|
||||
api(name: 'NoCaptchaSDK-external-release-5.4.29', ext: 'aar')
|
||||
//数字滚动效果
|
||||
implementation 'com.github.YvesCheung:RollingText:1.2.3'
|
||||
// 引入原有第三方裁图源码,方便修改
|
||||
api project(':android_crop_lib')
|
||||
//网易七鱼客服
|
||||
implementation 'com.qiyukf.unicorn:unicorn:4.9.1'
|
||||
|
||||
//rx权限请求框架
|
||||
implementation('com.github.tbruyelle:rxpermissions:0.10.2') {
|
||||
exclude group: 'io.reactivex.rxjava2'
|
||||
}
|
||||
//验证码控件 https://github.com/JingYeoh/VercodeEditText
|
||||
implementation 'com.justkiddingbaby:vercodeedittext:1.1.0'
|
||||
|
||||
//高德地图
|
||||
implementation 'com.amap.api:location:3.3.0'
|
||||
|
||||
implementation 'it.sephiroth.android.library.imagezoom:library:1.0.4'
|
||||
|
||||
}
|
||||
|
||||
repositories {
|
||||
flatDir {
|
||||
dirs 'aliyun-libs', 'hw-push-libs'
|
||||
}
|
||||
}
|
BIN
app/hw-push-libs/base-2.6.0.301-sources.jar
Normal file
BIN
app/hw-push-libs/base-2.6.0.301.aar
Normal file
BIN
app/hw-push-libs/push-2.6.0.301-sources.jar
Normal file
BIN
app/hw-push-libs/push-2.6.0.301.aar
Normal file
BIN
app/libs/MiPush_SDK_Client_3_6_2.jar
Normal file
BIN
app/libs/nanohttpd-2.2.0.jar
Normal file
306
app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,306 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
-optimizationpasses 5
|
||||
-dontusemixedcaseclassnames
|
||||
-dontskipnonpubliclibraryclasses
|
||||
-dontpreverify
|
||||
-verbose
|
||||
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
|
||||
|
||||
# 保留行号
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
#-dontwarn #//dontwarn去掉警告
|
||||
#-dontskipnonpubliclibraryclassmembers
|
||||
#-keep public class * extends android.app.Fragment
|
||||
-keep public class * extends android.app.Activity
|
||||
-keep public class * extends android.app.Application
|
||||
-keep public class * extends android.app.Service
|
||||
-keep public class * extends android.content.BroadcastReceiver
|
||||
-keep public class * extends android.content.ContentProvider
|
||||
#-keep public class * extends android.preference.Preference
|
||||
#-keep public class * extends android.support.v4.**
|
||||
-keep public class com.android.vending.licensing.ILicensingService
|
||||
-keepclasseswithmembernames class * {
|
||||
native <methods>;
|
||||
}
|
||||
#-keepclasseswithmembernames class * {
|
||||
# public <init>(android.content.Context, android.util.AttributeSet);
|
||||
#}
|
||||
#-keepclasseswithmembernames class * {
|
||||
# public <init>(android.content.Context, android.util.AttributeSet, int);
|
||||
#}
|
||||
#----------------enum-----------------
|
||||
-keepclassmembers enum * {
|
||||
public static **[] values();
|
||||
public static ** valueOf(java.lang.String);
|
||||
}
|
||||
#----------------Parcelable-----------------
|
||||
-keepclassmembers class * implements android.os.Parcelable {
|
||||
static ** CREATOR;
|
||||
<fields>;
|
||||
<methods>;
|
||||
}
|
||||
-keepnames class * implements android.os.Parcelable {
|
||||
public static final ** CREATOR;
|
||||
}
|
||||
|
||||
-keep class * implements java.io.Serializable {
|
||||
*;
|
||||
}
|
||||
##---------------Begin: proguard configuration for Gson ----------
|
||||
# Gson specific classes
|
||||
-dontwarn sun.misc.**
|
||||
|
||||
-keep class com.google.gson.**{*;}
|
||||
|
||||
#-keep class com.google.gson.stream.** { *; }
|
||||
|
||||
# Application classes that will be serialized/deserialized over Gson
|
||||
-keep class com.google.gson.examples.android.model.** { *; }
|
||||
|
||||
# Prevent proguard from stripping interface information from TypeAdapterFactory,
|
||||
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
|
||||
-keep class * implements com.google.gson.TypeAdapterFactory
|
||||
-keep class * implements com.google.gson.JsonSerializer
|
||||
-keep class * implements com.google.gson.JsonDeserializer
|
||||
|
||||
-keepattributes Signature
|
||||
-keepattributes *Annotation*
|
||||
|
||||
#----------------android-----------------
|
||||
-dontwarn android.**
|
||||
-keep class android.** { *;}
|
||||
#----------------v4-----------------
|
||||
-dontwarn android.support.v4.**
|
||||
-keep class android.support.v4.** { *; }
|
||||
#----------------v7-----------------
|
||||
-dontwarn android.support.v7.**
|
||||
-keep class android.support.v7.** { *;}
|
||||
|
||||
#----------------EventBus事件巴士-----------------
|
||||
-keepclassmembers class ** {
|
||||
@org.greenrobot.eventbus.Subscribe <methods>;
|
||||
}
|
||||
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
|
||||
|
||||
# Only required if you use AsyncExecutor
|
||||
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
|
||||
<init>(Java.lang.Throwable);
|
||||
}
|
||||
|
||||
|
||||
#-------------云信相关的混淆配置------------
|
||||
-dontwarn com.netease.**
|
||||
-keep class com.netease.** {*;}
|
||||
|
||||
# Presenter 相关
|
||||
-keep class com.yizhuan.erban.base.** { *; }
|
||||
-keep public class * extends com.yizhuan.erban.base.BaseMvpPresenter
|
||||
-keep public class * extends com.yizhuan.xchat_android_library.base.AbstractMvpPresenter
|
||||
|
||||
# 云信自定义 ViewHolder 配置
|
||||
-dontwarn com.yizhuan.erban.ui.im.recent.holder.**
|
||||
-keep class com.yizhuan.erban.ui.im.recent.holder.** {*;}
|
||||
-keep class com.yizhuan.erban.ui.im.chat.** {*;}
|
||||
-keep class com.yizhuan.erban.luckymoney.viewholder.** {*;}
|
||||
-keep class com.yizhuan.erban.share.viewholder.** {*;}
|
||||
-keep class com.yizhuan.erban.public_chat_hall.msg.viewholder.** {*;}
|
||||
-keep class com.yizhuan.erban.module_hall.im.msgholder.** {*;}
|
||||
-keep class com.yizhuan.tutu.mentoring_relationship.viewholder.** {*;}
|
||||
-keep public class * extends com.netease.nim.uikit.common.ui.recyclerview.holder.RecyclerViewHolder {*;}
|
||||
-keep public class * extends com.netease.nim.uikit.business.session.viewholder.MsgViewHolderBase {*;}
|
||||
|
||||
#如果你使用全文检索插件,需要加入
|
||||
-dontwarn org.apache.lucene.**
|
||||
-keep class org.apache.lucene.** {*;}
|
||||
|
||||
# 云信集成小米推送
|
||||
-dontwarn com.xiaomi.push.**
|
||||
-keep class com.xiaomi.** {*;}
|
||||
|
||||
# 云信集成华为推送
|
||||
-ignorewarning
|
||||
-keepattributes *Annotation*
|
||||
-keepattributes Exceptions
|
||||
-keepattributes Signature
|
||||
# hmscore-support: remote transport
|
||||
-keep class * extends com.huawei.hms.core.aidl.IMessageEntity { *; }
|
||||
# hmscore-support: remote transport
|
||||
-keepclasseswithmembers class * implements com.huawei.hms.support.api.transport.DatagramTransport {
|
||||
<init>(...); }
|
||||
# manifest: provider for updates
|
||||
-keep public class com.huawei.hms.update.provider.UpdateProvider { public *; protected *; }
|
||||
|
||||
# 云信集成魅族推送
|
||||
-dontwarn com.meizu.cloud.**
|
||||
-keep class com.meizu.cloud.** {*;}
|
||||
#-------------云信相关的混淆配置------------
|
||||
|
||||
|
||||
#-------------TakePhoto的混淆配置------------
|
||||
-keep class com.jph.takephoto.** { *; }
|
||||
-dontwarn com.jph.takephoto.**
|
||||
|
||||
-keep class com.darsh.multipleimageselect.** { *; }
|
||||
-dontwarn com.darsh.multipleimageselect.**
|
||||
|
||||
-keep class com.soundcloud.android.crop.** { *; }
|
||||
-dontwarn com.soundcloud.android.crop.**
|
||||
|
||||
#-------------TakePhoto的混淆配置------------
|
||||
|
||||
|
||||
|
||||
#腾讯崩溃收集
|
||||
-dontwarn com.tencent.bugly.**
|
||||
-keep public class com.tencent.bugly.**{*;}
|
||||
|
||||
# glide4.0
|
||||
-keep public class * implements com.bumptech.glide.module.GlideModule
|
||||
-keep public class * extends com.bumptech.glide.module.AppGlideModule
|
||||
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
|
||||
**[] $VALUES;
|
||||
public *;
|
||||
}
|
||||
# for DexGuard only
|
||||
#-keepresourcexmlelements manifest/application/meta-data@value=GlideModule
|
||||
|
||||
# BaseAdapter
|
||||
-keep class com.chad.library.adapter.** {
|
||||
*;
|
||||
}
|
||||
-keep public class * extends com.chad.library.adapter.base.BaseQuickAdapter
|
||||
-keep public class * extends com.chad.library.adapter.base.BaseViewHolder
|
||||
-keepclassmembers class **$** extends com.chad.library.adapter.base.BaseViewHolder {
|
||||
<init>(...);
|
||||
}
|
||||
|
||||
-dontwarn com.yizhuan.erban.bindadapter.**
|
||||
-keep class com.yizhuan.erban.bindadapter.** {*;}
|
||||
|
||||
# Ping++ 混淆过滤
|
||||
-dontwarn com.pingplusplus.**
|
||||
-keep class com.pingplusplus.** {*;}
|
||||
# 支付宝混淆过滤
|
||||
-dontwarn com.alipay.**
|
||||
-keep class com.alipay.** {*;}
|
||||
# 微信或QQ钱包混淆过滤
|
||||
-dontwarn com.tencent.**
|
||||
-keep class com.tencent.** {*;}
|
||||
|
||||
# 银联支付混淆过滤
|
||||
#-dontwarn com.unionpay.**
|
||||
#-keep class com.unionpay.** {*;}
|
||||
#
|
||||
## 招行一网通混淆过滤
|
||||
#-keepclasseswithmembers class cmb.pb.util.CMBKeyboardFunc {
|
||||
# public <init>(android.app.Activity);
|
||||
# public boolean HandleUrlCall(android.webkit.WebView,java.lang.String);
|
||||
# public void callKeyBoardActivity();
|
||||
#}
|
||||
|
||||
# 内部WebView混淆过滤
|
||||
-keepclassmembers class * {
|
||||
@android.webkit.JavascriptInterface <methods>;
|
||||
}
|
||||
|
||||
|
||||
# TODO 网络加载 一些业务bean gson 时候混淆问题
|
||||
-keep class org.json.** {*;}
|
||||
-dontwarn com.yizhuan.xchat_android_core.**
|
||||
-keep class com.yizhuan.xchat_android_core.** {*;}
|
||||
|
||||
|
||||
#百度统计
|
||||
-keep class com.baidu.bottom.** { *; }
|
||||
-keep class com.baidu.kirin.** { *; }
|
||||
-keep class com.baidu.mobstat.** { *; }
|
||||
|
||||
-keep class io.agora.** { *; }
|
||||
|
||||
# 七牛
|
||||
-keep class com.qiniu.**{*;}
|
||||
-keep class com.qiniu.**{public <init>();}
|
||||
-ignorewarnings
|
||||
|
||||
# shareSdk
|
||||
-keep class cn.sharesdk.**{*;}
|
||||
-keep class com.sina.**{*;}
|
||||
-keep class **.R$* {*;}
|
||||
-keep class **.R{*;}
|
||||
-keep class com.mob.**{*;}
|
||||
-dontwarn com.mob.**
|
||||
-dontwarn cn.sharesdk.**
|
||||
-dontwarn **.R$*
|
||||
|
||||
# fastjson
|
||||
-dontwarn com.alibaba.fastjson.**
|
||||
-keep class com.alibaba.fastjson.**{*; }
|
||||
|
||||
# retrofit2
|
||||
# Platform calls Class.forName on types which do not checkExist on Android to determine platform.
|
||||
-dontnote retrofit2.Platform
|
||||
# Platform used when running on Java 8 VMs. Will not be used at runtime.
|
||||
-dontwarn retrofit2.Platform$Java8
|
||||
# Retain declared checked exceptions for use by a Proxy instance.
|
||||
-keepattributes Exceptions
|
||||
|
||||
# okhttp3
|
||||
-dontwarn okhttp3.**
|
||||
# okio
|
||||
-dontwarn okio.**
|
||||
# A resource is loaded with a relative path so the package of this class must be preserved.
|
||||
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
|
||||
|
||||
# Aliyun oss
|
||||
-keep class com.taobao.securityjni.**{*;}
|
||||
-keep class com.taobao.wireless.security.**{*;}
|
||||
-keep class com.ut.secbody.**{*;}
|
||||
-keep class com.taobao.dp.**{*;}
|
||||
-keep class com.alibaba.wireless.security.**{*;}
|
||||
-keep class com.alibaba.security.rp.**{*;}
|
||||
-keep class com.alibaba.sdk.android.**{*;}
|
||||
-keep class com.alibaba.security.biometrics.**{*;}
|
||||
-keep class android.taobao.windvane.**{*;}
|
||||
|
||||
|
||||
#网易七鱼客服系统
|
||||
-dontwarn com.qiyukf.**
|
||||
-keep class com.qiyukf.** {*;}
|
||||
|
||||
# 确保openFileChooser方法不被混淆
|
||||
-keepclassmembers class * extends android.webkit.WebChromeClient{
|
||||
public void openFileChooser(...);
|
||||
}
|
||||
|
||||
#高德地图
|
||||
-keep class com.amap.api.location.**{*;}
|
||||
-keep class com.amap.api.fence.**{*;}
|
||||
-keep class com.autonavi.aps.amapapi.model.**{*;}
|
||||
|
||||
#暂时keep这个View排查华为oom问题
|
||||
-keep class com.yizhuan.erban.avroom.widget.MicroView{*;}
|
||||
-keep class com.jude.rollviewpager.RollPagerView{*;}
|
||||
|
||||
#linkedMe
|
||||
-keep class com.microquation.linkedme.android.** { *; }
|
2
app/resource_mapping.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
res path mapping:
|
||||
res/drawable -> res/drawable
|
@@ -0,0 +1,32 @@
|
||||
package com.yizhuan.erban;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExampleInstrumentedTest {
|
||||
@Test
|
||||
public void useAppContext() throws Exception {
|
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getTargetContext();
|
||||
|
||||
assertEquals("com.yizhuan.erban_android_client", appContext.getPackageName());
|
||||
}
|
||||
|
||||
}
|
9
app/src/common/res/drawable/bg_f5f5f5_0_15.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<corners android:radius="15dp"/>
|
||||
|
||||
<solid android:color="@color/color_F5F5F5"/>
|
||||
|
||||
</shape>
|
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/shape_btn_highlight_gradient" android:state_enabled="true" />
|
||||
<item android:drawable="@drawable/shape_round_ffdbdbdb_radius_19" android:state_enabled="false" />
|
||||
<item android:drawable="@drawable/shape_btn_highlight_gradient" />
|
||||
</selector>
|
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@mipmap/common_ic_checked" android:state_selected="true" />
|
||||
<item android:drawable="@mipmap/common_ic_checked" android:state_checked="true" />
|
||||
<item android:drawable="@mipmap/common_ic_unchecked" android:state_selected="false" />
|
||||
<item android:drawable="@mipmap/common_ic_unchecked" android:state_checked="false" />
|
||||
<item android:drawable="@mipmap/common_ic_unchecked" />
|
||||
</selector>
|
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@mipmap/common_ic_checked_white" android:state_selected="true" />
|
||||
<item android:drawable="@mipmap/common_ic_checked_white" android:state_checked="true" />
|
||||
<item android:drawable="@mipmap/common_ic_unchecked_white" android:state_selected="false" />
|
||||
<item android:drawable="@mipmap/common_ic_unchecked_white" android:state_checked="false" />
|
||||
<item android:drawable="@mipmap/common_ic_unchecked_white" />
|
||||
</selector>
|
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@mipmap/common_ic_selected" android:state_selected="true" />
|
||||
<item android:drawable="@mipmap/common_ic_selected" android:state_checked="true" />
|
||||
<item android:drawable="@mipmap/common_ic_unselected" android:state_selected="false" />
|
||||
<item android:drawable="@mipmap/common_ic_unselected" android:state_checked="false" />
|
||||
<item android:drawable="@mipmap/common_ic_unselected" />
|
||||
</selector>
|
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_enabled="false" android:drawable="@drawable/shape_common_unable" />
|
||||
<item android:state_enabled="true" android:drawable="@drawable/shape_common_enable" />
|
||||
</selector>
|
6
app/src/common/res/drawable/selector_radio_btn.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@mipmap/common_ic_checked" android:state_checked="true" />
|
||||
<item android:drawable="@mipmap/common_ic_unchecked" android:state_checked="false" />
|
||||
<item android:drawable="@mipmap/common_ic_unchecked" />
|
||||
</selector>
|
6
app/src/common/res/drawable/selector_select_all_btn.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@mipmap/drawable_checkbox_selected" android:state_checked="true" />
|
||||
<item android:drawable="@mipmap/drawable_checkbox_unselected" android:state_checked="false" />
|
||||
<item android:drawable="@mipmap/drawable_checkbox_unselected" />
|
||||
</selector>
|
6
app/src/common/res/drawable/selector_select_mark.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@mipmap/common_ic_checked" android:state_selected="true" />
|
||||
<item android:drawable="@mipmap/common_ic_unchecked" android:state_selected="false" />
|
||||
<item android:drawable="@mipmap/common_ic_unchecked" />
|
||||
</selector>
|
10
app/src/common/res/drawable/shape_btn_highlight_gradient.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<gradient android:startColor="@color/color_39EBDF"
|
||||
android:endColor="@color/color_39D0EB"/>
|
||||
|
||||
<corners android:radius="19dp"/>
|
||||
|
||||
</shape>
|
5
app/src/common/res/drawable/shape_common_enable.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="@dimen/dp_50" />
|
||||
<solid android:color="@color/appColor" />
|
||||
</shape>
|
5
app/src/common/res/drawable/shape_common_unable.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="@dimen/dp_50" />
|
||||
<solid android:color="@color/color_E0E0E0" />
|
||||
</shape>
|
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/white_transparent_80" />
|
||||
<corners android:radius="@dimen/dp_50" />
|
||||
</shape>
|
6
app/src/common/res/drawable/shape_edit_cursor.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<size android:width="1.5dp" />
|
||||
<solid android:color="@color/appColor" />
|
||||
</shape>
|
5
app/src/common/res/drawable/shape_female_corner.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="@dimen/dp_50" />
|
||||
<solid android:color="@color/color_female" />
|
||||
</shape>
|
9
app/src/common/res/drawable/shape_gray_corner_12.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners
|
||||
android:radius="@dimen/dp_12"
|
||||
/>
|
||||
<solid
|
||||
android:color="@color/color_F5F5F5"
|
||||
/>
|
||||
</shape>
|
5
app/src/common/res/drawable/shape_male_corner.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="@dimen/dp_50" />
|
||||
<solid android:color="@color/color_male" />
|
||||
</shape>
|
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="@dimen/dp_22" />
|
||||
<solid android:color="@color/color_43C5FF" />
|
||||
</shape>
|
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape android:shape="rectangle"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/color_9af5ef" />
|
||||
<corners
|
||||
android:radius="5dp" />
|
||||
</shape>
|
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape android:shape="rectangle"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/appColor" />
|
||||
<corners android:radius="19dp" />
|
||||
</shape>
|
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners
|
||||
android:radius="@dimen/dp_22" />
|
||||
<solid android:color="@color/appColor" />
|
||||
</shape>
|
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape android:shape="rectangle"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/appColor" />
|
||||
<corners
|
||||
android:radius="5dp" />
|
||||
</shape>
|
13
app/src/common/res/drawable/shape_round_app_color_solid.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle"
|
||||
android:color="@color/colorAccent">
|
||||
<corners
|
||||
android:radius="60dip"
|
||||
/>
|
||||
<stroke
|
||||
android:width="2dp"
|
||||
android:color="@color/color_333333" />
|
||||
<solid android:color="@color/appColor" />
|
||||
</shape>
|