66陪玩-origin

This commit is contained in:
oujunhui
2020-04-02 10:43:40 +08:00
commit 2f9d26fd7e
6758 changed files with 480551 additions and 0 deletions

9
.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
*.iml
.gradle
/local.properties
*.DS_Store
/build
/captures
.externalNativeBuild
.idea
.settings

17
.project Normal file
View 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
View File

@@ -0,0 +1,3 @@
/build
*.iml
*.DS_Store

Binary file not shown.

View 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
View File

@@ -0,0 +1,3 @@
/build
*.iml
*.DS_Store

View 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'
}

View File

@@ -0,0 +1 @@
<manifest package="com.soundcloud.android.crop" />

View File

@@ -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();
}
}

View File

@@ -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));
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}
}

View File

@@ -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;
}
}

View File

@@ -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());
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 B

View File

@@ -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>

View 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" />

View 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>

View File

@@ -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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View File

@@ -0,0 +1,5 @@
<resources>
<dimen name="crop__bar_height">48dp</dimen>
</resources>

View File

@@ -0,0 +1,5 @@
<resources>
<dimen name="crop__bar_height">64dp</dimen>
</resources>

View 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>

View 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>

View 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>

View 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>

View File

@@ -0,0 +1,5 @@
<resources>
<color name="crop__selector_pressed">#aaaaaa</color>
</resources>

View 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>

View 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>

View 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>

View 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>

View File

@@ -0,0 +1,5 @@
<resources>
<dimen name="crop__bar_height">56dp</dimen>
</resources>

View 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>

View 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
View 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("&", "&amp;")
s = s.replace('"', "&quot;")
s = s.replace("'", "&apos;")
s = s.replace("<", "&lt;")
s = s.replace(">", "&gt;")
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
View File

@@ -0,0 +1 @@
/build

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

78
app/and_res_guard.gradle Normal file
View 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
View 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'
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

306
app/proguard-rules.pro vendored Normal file
View 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
View File

@@ -0,0 +1,2 @@
res path mapping:
res/drawable -> res/drawable

View File

@@ -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());
}
}

View 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>

View 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="@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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View File

@@ -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>

View 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>

View 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>

View 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>

View 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>

View 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">
<corners android:radius="@dimen/dp_22" />
<solid android:color="@color/color_43C5FF" />
</shape>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View 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>

Some files were not shown because too many files have changed in this diff Show More