유료 앱으로 출시하고 싶은 앱이 있습니다. 5 일이라는 시간 제한이있는 "체험판"버전 인 다른 버전을 갖고 싶습니다.
어떻게하면 되나요?
유료 앱으로 출시하고 싶은 앱이 있습니다. 5 일이라는 시간 제한이있는 "체험판"버전 인 다른 버전을 갖고 싶습니다.
어떻게하면 되나요?
답변:
현재 대부분의 개발자는 다음 세 가지 기술 중 하나를 사용하여이를 수행합니다.
첫 번째 접근 방식은 쉽게 우회 할 수 있습니다. 앱을 처음 실행할 때 날짜 / 시간을 파일, 데이터베이스 또는 공유 기본 설정에 저장하고 그 후 앱을 실행할 때마다 평가 기간이 종료되었는지 확인합니다. 제거하고 다시 설치하면 사용자가 다른 평가 기간을 가질 수 있기 때문에 우회하기 쉽습니다.
두 번째 접근 방식은 우회하기가 더 어렵지만 여전히 우회 할 수 있습니다. 하드 코딩 된 시한 폭탄을 사용하십시오. 기본적으로이 접근 방식을 사용하면 평가판의 종료 날짜를 하드 코딩하고 앱을 다운로드하고 사용하는 모든 사용자가 동시에 앱을 사용할 수 없게됩니다. 구현하기 쉽고 대부분의 경우 세 번째 기술의 문제를 겪고 싶지 않았기 때문에이 접근 방식을 사용했습니다. 사용자는 휴대폰에서 수동으로 날짜를 변경하여이를 피할 수 있지만 대부분의 사용자는 이러한 작업을 수행하는 데 어려움을 겪지 않습니다.
세 번째 기술은 당신이하고 싶은 일을 진정으로 성취 할 수있는 유일한 방법입니다. 서버를 설정해야하며 애플리케이션이 시작될 때마다 앱이 전화기 고유 식별자 를 서버로 보냅니다. 서버에 해당 전화 ID에 대한 항목이 없으면 새 항목을 만들고 시간을 기록합니다. 서버에 전화 ID에 대한 항목이있는 경우 평가 기간이 만료되었는지 간단한 확인을 수행합니다. 그런 다음 평가판 만료 확인 결과를 애플리케이션에 다시 전달합니다. 이 접근 방식은 피할 수 없어야하지만 웹 서버 등을 설정해야합니다.
항상 onCreate에서 이러한 검사를 수행하는 것이 좋습니다. 만료가 끝나면 앱의 정식 버전에 대한 시장 링크 가 포함 된 AlertDialog가 팝업 됩니다. "확인"버튼 만 포함하고 사용자가 "확인"을 클릭하면 "finish ()"를 호출하여 활동을 종료합니다.
Android Studio 프로젝트에 간단히 드롭 할 수 있는 Android 평가판 SDK 를 개발했으며 오프라인 유예 기간을 포함하여 모든 서버 측 관리를 처리합니다.
사용하려면 간단히
메인 모듈에 라이브러리 추가 build.gradle
dependencies {
compile 'io.trialy.library:trialy:1.0.2'
}
주요 활동의 onCreate()
방법으로 라이브러리 초기화
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Initialize the library and check the current trial status on every launch
Trialy mTrialy = new Trialy(mContext, "YOUR_TRIALY_APP_KEY");
mTrialy.checkTrial(TRIALY_SKU, mTrialyCallback);
}
콜백 핸들러를 추가합니다.
private TrialyCallback mTrialyCallback = new TrialyCallback() {
@Override
public void onResult(int status, long timeRemaining, String sku) {
switch (status){
case STATUS_TRIAL_JUST_STARTED:
//The trial has just started - enable the premium features for the user
break;
case STATUS_TRIAL_RUNNING:
//The trial is currently running - enable the premium features for the user
break;
case STATUS_TRIAL_JUST_ENDED:
//The trial has just ended - block access to the premium features
break;
case STATUS_TRIAL_NOT_YET_STARTED:
//The user hasn't requested a trial yet - no need to do anything
break;
case STATUS_TRIAL_OVER:
//The trial is over
break;
}
Log.i("TRIALY", "Trialy response: " + Trialy.getStatusMessage(status));
}
};
평가판을 시작하려면 mTrialy.startTrial("YOUR_TRIAL_SKU", mTrialyCallback);
앱 키로 전화하면 평가판 SKU는 Trialy 개발자 대시 보드 에서 찾을 수 있습니다 .
이것은 오래된 질문이지만 어쨌든 누군가에게 도움이 될 것입니다.
가장 단순한 접근 방식 ( 앱을 제거 / 재설치하거나 사용자가 기기의 날짜를 수동으로 변경 하면 실패 함) 을 사용하려는 경우 다음과 같이 할 수 있습니다.
private final SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
private final long ONE_DAY = 24 * 60 * 60 * 1000;
@Override
protected void onCreate(Bundle state){
SharedPreferences preferences = getPreferences(MODE_PRIVATE);
String installDate = preferences.getString("InstallDate", null);
if(installDate == null) {
// First run, so save the current date
SharedPreferences.Editor editor = preferences.edit();
Date now = new Date();
String dateString = formatter.format(now);
editor.putString("InstallDate", dateString);
// Commit the edits!
editor.commit();
}
else {
// This is not the 1st run, check install date
Date before = (Date)formatter.parse(installDate);
Date now = new Date();
long diff = now.getTime() - before.getTime();
long days = diff / ONE_DAY;
if(days > 30) { // More than 30 days?
// Expired !!!
}
}
...
}
getTime
없습니다 getTimeInMillis
.
이 질문과 snctln 의 답변은 저의 학사 논문으로 방법 3을 기반으로 한 솔루션을 작업하도록 영감을주었습니다. 나는 현재 상태가 생산적인 사용을위한 것이 아니라는 것을 알고 있지만 그것에 대해 어떻게 생각하는지 듣고 싶습니다! 그런 시스템을 사용 하시겠습니까? 클라우드 서비스로 보시겠습니까 (서버 구성에 문제가 없음)? 보안 문제 또는 안정성 이유가 걱정 되십니까?
학사 과정을 마치는 즉시 소프트웨어 작업을 계속하고 싶습니다. 이제 여러분의 피드백이 필요합니다!
소스 코드는 GitHub https://github.com/MaChristmann/mobile-trial에서 호스팅됩니다.
시스템에 대한 몇 가지 정보 :-시스템에는 Android 라이브러리, node.js 서버 및 여러 평가판 앱 및 게시자 / 개발자 계정을 관리하기위한 구성 기의 세 부분이 있습니다.
시간 기반 평가판 만 지원하며 전화 ID가 아닌 사용자 (Play 스토어 또는 기타) 계정을 사용합니다.
Android 라이브러리의 경우 Google Play 라이선스 확인 라이브러리를 기반으로합니다. node.js 서버에 연결하도록 수정했으며 추가로 라이브러리는 사용자가 시스템 날짜를 변경했는지 인식하려고 시도합니다. 또한 검색된 평가판 라이센스를 AES 암호화 된 공유 기본 설정에 캐시합니다. 구성자를 사용하여 캐시의 유효 시간을 구성 할 수 있습니다. 사용자가 "데이터를 지우는"경우 라이브러리는 서버 측 검사를 강제합니다.
서버는 https를 사용하고 있으며 라이센스 확인 응답에 디지털 서명도합니다. CRUD 평가판 앱 및 사용자 (게시자 및 개발자)를위한 API도 있습니다. Licensing Verfication Library 개발자와 유사한 개발자는 테스트 결과와 함께 평가판 앱에서 동작 구현을 테스트 할 수 있습니다. 따라서 구성자에서 라이센스 응답을 "라이센스 있음", "라이센스 없음"또는 "서버 오류"로 명시 적으로 설정할 수 있습니다.
새로운 기능으로 앱을 업데이트하는 경우 모든 사람이 다시 시도 할 수 있기를 원할 수 있습니다. 구성 관리자에서이를 트리거해야하는 버전 코드를 설정하여 만료 된 라이선스가있는 사용자의 평가판 라이선스를 갱신 할 수 있습니다. 예를 들어 사용자가 버전 코드 3에서 앱을 실행하고 있고 버전 코드 4의 기능을 사용해 보길 원합니다. 그가 앱을 업데이트하거나 다시 설치하면 서버가 마지막으로 시도한 버전을 알고 있기 때문에 전체 평가 기간을 다시 사용할 수 있습니다. 시각.
모든 것은 Apache 2.0 라이선스하에 있습니다.
이를 수행하는 가장 쉽고 가장 좋은 방법은 BackupSharedPreferences를 구현하는 것입니다.
앱을 제거하고 다시 설치하더라도 기본 설정은 유지됩니다.
설치 날짜를 기본 설정으로 저장하기 만하면됩니다.
이론은 다음과 같습니다. http://developer.android.com/reference/android/app/backup/SharedPreferencesBackupHelper.html
접근 방식 4 : 애플리케이션 설치 시간을 사용합니다.
API 레벨 9 (Android 2.3.2, 2.3.1, Android 2.3, GINGERBREAD)부터 .NET Framework에는 firstInstallTime 및 lastUpdateTime 이 PackageInfo
있습니다.
자세히 알아보기 : Android에서 앱 설치 시간을 얻는 방법
이제 최신 버전의 Android 무료 평가판 구독이 추가되었으므로 무료 평가판 기간 동안 앱 내에서 구독을 구매 한 후에 만 앱의 모든 기능을 잠금 해제 할 수 있습니다. 이렇게하면 사용자가 평가 기간 동안 앱을 사용할 수 있으며, 평가 기간 후에도 앱이 제거되면 구독 금액이 귀하에게 이체됩니다. 나는 시도하지 않았지만 아이디어를 공유했습니다.
제 생각에는이 작업을 수행하는 가장 좋은 방법은 Firebase 실시간 데이터베이스를 사용하는 것입니다.
1) 앱에 Firebase 지원 추가
2) '익명 인증'을 선택하면 사용자가 가입 할 필요가 없으며 자신이 무엇을하는지 알지 못합니다. 이것은 현재 인증 된 사용자 계정에 대한 링크가 보장되므로 여러 장치에서 작동합니다.
3) Realtime Database API를 사용하여 'installed_date'값을 설정합니다. 시작시이 값을 검색하여 사용하면됩니다.
나는 똑같이 해왔고 훌륭하게 작동합니다. 제거 / 재설치를 통해이를 테스트 할 수 있었고 실시간 데이터베이스의 값은 동일하게 유지됩니다. 이렇게하면 평가 기간이 여러 사용자 장치에서 작동합니다. 앱이 새로운 주요 릴리스마다 평가판 날짜를 '재설정'하도록 install_date 버전을 지정할 수도 있습니다.
업데이트 : 조금 더 테스트 한 후 익명의 Firebase는 다른 기기를 가지고 있고 재설치 사이에 보장되지 않는 경우 다른 ID를 할당하는 것 같습니다 : / 유일한 보장 된 방법은 Firebase를 사용하지만 Google에 연결하는 것입니다. 계정. 이것은 작동하지만 사용자가 먼저 로그인 / 가입해야하는 추가 단계가 필요합니다.
지금까지 백업 된 기본 설정과 설치시 기본 설정에 저장된 날짜를 확인하는 약간 덜 우아한 접근 방식으로 끝났습니다. 이는 사용자가 앱을 다시 설치하고 이전에 추가 한 모든 데이터를 다시 입력하는 것이 무의미한 데이터 중심 앱에서 작동하지만 간단한 게임에서는 작동하지 않습니다.
이 스레드와 다른 스레드의 모든 옵션을 살펴본 후 이것이 내 결과입니다.
공유 환경 설정, 데이터베이스 안드로이드 설정에서 지울 수 있으며 앱 재설치 후 손실됩니다. 안드로이드의 백업 메커니즘으로 백업 할 수 있으며 재설치 후 복원됩니다. 백업이 항상 사용 가능한 것은 아니지만 대부분의 장치에 있어야합니다.
외부 저장소 (파일에 쓰기) 설정에서 삭제하거나 애플리케이션의 개인 디렉터리에 쓰지 않는 경우 다시 설치해도 영향을받지 않습니다 . 그러나 : 최신 안드로이드 버전 에서 런타임시 사용자에게 권한을 요청 해야하므로이 권한이 필요한 경우에만 가능합니다. 백업 할 수도 있습니다.
PackageInfo.firstInstallTime 은 재설치 후 재설정되지만 업데이트간에 안정적입니다.
일부 계정에 로그인 Firebase를 통한 Google 계정이든 자체 서버에있는 계정이든 상관 없습니다. 평가판은 계정에 바인딩됩니다. 새 계정을 만들면 평가판이 재설정됩니다.
Firebase 익명 로그인 사용자를 익명으로 로그인하고 Firebase에 데이터를 저장할 수 있습니다. 그러나 분명히 앱을 다시 설치하고 문서화되지 않은 기타 이벤트가 사용자에게 새로운 익명 ID를 제공하여 평가판 시간을 재설정 할 수 있습니다. (Google 자체는 이에 대한 많은 문서를 제공하지 않습니다)
ANDROID_ID 사용할 수 없으며 특정 상황 (예 : 공장 초기화)에서 변경 될 수 있습니다 . 이것을 사용하여 장치를 식별하는 것이 좋은 생각인지에 대한 의견은 다른 것 같습니다.
Play 광고 ID 는 사용자가 재설정 할 수 있습니다. 사용자가 광고 추적을 선택 해제하여 비활성화 할 수 있습니다.
재설치시 InstanceID 재설정 . 보안 이벤트의 경우 재설정합니다. 앱에서 재설정 할 수 있습니다.
어떤 (조합) 방법이 당신에게 효과가 있는지는 당신의 앱과 평균적인 John이 다른 시험 기간을 얻기 위해 얼마나 많은 노력을 기울일 지에 따라 다릅니다. 불안정성 때문에 익명의 Firebase 및 광고 ID 만 사용하지 않는 것이 좋습니다 . 다중 요소 접근 방식은 최상의 결과를 얻을 수있는 것처럼 보입니다. 사용할 수있는 요소는 앱 및 권한에 따라 다릅니다.
내 앱의 경우 공유 환경 설정 + firstInstallTime + 환경 설정 백업이 가장 덜 방해가되지만 충분히 효과적인 방법이라는 것을 알았습니다. 공유 기본 설정에서 평가판 시작 시간을 확인하고 저장 한 후에 만 백업을 요청해야합니다. 공유 Prefs의 값은 firstInstallTime보다 우선해야합니다. 그런 다음 사용자는 앱을 다시 설치하고 한 번 실행 한 다음 앱의 데이터를 삭제하여 평가판을 재설정해야합니다. 이는 상당히 많은 작업입니다. 백업 전송이없는 장치에서는 사용자가 간단히 재설치하여 평가판을 재설정 할 수 있습니다.
이 접근 방식을 확장 가능한 라이브러리 로 만들었습니다 .
정의 에 따라 시장에 나와있는 모든 유료 Android 앱은 구매 후 24 시간 동안 평가할 수 있습니다.
24 시간 후에 '제거'로 변경되는 '제거 및 환불'버튼이 있습니다.
이 버튼이 너무 눈에 잘 띕니다!
동일한 문제를 검색하는 동안이 질문을 보았습니다 .http : //www.timeapi.org/utc/now와 같은 무료 날짜 API 또는 다른 날짜 API를 사용하여 트레일 앱의 만료 여부를 확인할 수 있다고 생각 합니다. 이 방법은 데모를 제공하고 지불에 대해 걱정하고 수정 기간 데모가 필요한 경우 효율적입니다. :)
아래 코드를 찾으십시오.
public class ValidationActivity extends BaseMainActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
protected void onResume() {
processCurrentTime();
super.onResume();
}
private void processCurrentTime() {
if (!isDataConnectionAvailable(ValidationActivity.this)) {
showerrorDialog("No Network coverage!");
} else {
String urlString = "http://api.timezonedb.com/?zone=Europe/London&key=OY8PYBIG2IM9";
new CallAPI().execute(urlString);
}
}
private void showerrorDialog(String data) {
Dialog d = new Dialog(ValidationActivity.this);
d.setTitle("LS14");
TextView tv = new TextView(ValidationActivity.this);
tv.setText(data);
tv.setPadding(20, 30, 20, 50);
d.setContentView(tv);
d.setOnDismissListener(new OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
finish();
}
});
d.show();
}
private void checkExpiry(int isError, long timestampinMillies) {
long base_date = 1392878740000l;// feb_19 13:8 in GMT;
// long expiryInMillies=1000*60*60*24*5;
long expiryInMillies = 1000 * 60 * 10;
if (isError == 1) {
showerrorDialog("Server error, please try again after few seconds");
} else {
System.out.println("fetched time " + timestampinMillies);
System.out.println("system time -" + (base_date + expiryInMillies));
if (timestampinMillies > (base_date + expiryInMillies)) {
showerrorDialog("Demo version expired please contact vendor support");
System.out.println("expired");
}
}
}
private class CallAPI extends AsyncTask<String, String, String> {
@Override
protected void onPreExecute() {
// TODO Auto-generated method stub
super.onPreExecute();
}
@Override
protected String doInBackground(String... params) {
String urlString = params[0]; // URL to call
String resultToDisplay = "";
InputStream in = null;
// HTTP Get
try {
URL url = new URL(urlString);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(urlConnection.getInputStream());
resultToDisplay = convertStreamToString(in);
} catch (Exception e) {
System.out.println(e.getMessage());
return e.getMessage();
}
return resultToDisplay;
}
protected void onPostExecute(String result) {
int isError = 1;
long timestamp = 0;
if (result == null || result.length() == 0 || result.indexOf("<timestamp>") == -1 || result.indexOf("</timestamp>") == -1) {
System.out.println("Error $$$$$$$$$");
} else {
String strTime = result.substring(result.indexOf("<timestamp>") + 11, result.indexOf("</timestamp>"));
System.out.println(strTime);
try {
timestamp = Long.parseLong(strTime) * 1000;
isError = 0;
} catch (NumberFormatException ne) {
}
}
checkExpiry(isError, timestamp);
}
} // end CallAPI
public static boolean isDataConnectionAvailable(Context context) {
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo info = connectivityManager.getActiveNetworkInfo();
if (info == null)
return false;
return connectivityManager.getActiveNetworkInfo().isConnected();
}
public String convertStreamToString(InputStream is) throws IOException {
if (is != null) {
Writer writer = new StringWriter();
char[] buffer = new char[1024];
try {
Reader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
int n;
while ((n = reader.read(buffer)) != -1) {
writer.write(buffer, 0, n);
}
} finally {
is.close();
}
return writer.toString();
} else {
return "";
}
}
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
}
작동하는 솔루션 .....
다음은 제가 어떻게 진행했는지, 하나는 평가판 활동이있는 앱 2 개를 만들고 다른 하나는없는 앱을 만들었습니다.
평가판 활동이없는 것을 유료 앱으로 플레이 스토어에 업로드했는데,
그리고 무료 앱으로 평가판 활동이있는 사람.
최초 실행시 무료 앱에는 평가판 및 스토어 구매 옵션이 있으며, 사용자가 스토어 구매를 선택하면 사용자가 구매할 수 있도록 스토어로 리디렉션되지만 사용자가 평가판을 클릭하면 평가판 활동으로 이동합니다.
NB : @snctln과 같은 옵션 3을 사용했지만 수정했습니다.
첫째 , 나는 장치 시간에 의존하지 않고 db에 대한 평가판 등록을 수행하는 PHP 파일에서 시간을 얻었습니다.
둘째 , 장치 일련 번호를 사용하여 각 장치를 고유하게 식별했습니다.
마지막으로 , 앱은 자체 시간이 아닌 서버 연결에서 반환 된 시간 값에 의존하므로 기기 일련 번호가 변경된 경우에만 시스템을 우회 할 수 있으며 이는 사용자에게 매우 스트레스입니다.
그래서 여기에 내 코드가 있습니다 (평가판 활동).
package com.example.mypackage.my_app.Start_Activity.activity;
import android.Manifest;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.telephony.TelephonyManager;
import android.view.KeyEvent;
import android.widget.TextView;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.Volley;
import com.example.onlinewisdom.cbn_app.R;
import com.example.mypackage.my_app.Start_Activity.app.Config;
import com.example.mypackage.my_app.Start_Activity.data.TrialData;
import com.example.mypackage.my_app.Start_Activity.helper.connection.Connection;
import com.google.gson.Gson;
import org.json.JSONObject;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import cn.pedant.SweetAlert.SweetAlertDialog;
public class Trial extends AppCompatActivity {
Connection check;
SweetAlertDialog pDialog;
TextView tvPleaseWait;
private static final int MY_PERMISSIONS_REQUEST_READ_PHONE_STATE = 0;
String BASE_URL = Config.BASE_URL;
String BASE_URL2 = BASE_URL+ "/register_trial/"; //http://ur link to ur API
//KEY
public static final String KEY_IMEI = "IMEINumber";
private final SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
private final long ONE_DAY = 24 * 60 * 60 * 1000;
SharedPreferences preferences;
String installDate;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_trial);
preferences = getPreferences(MODE_PRIVATE);
installDate = preferences.getString("InstallDate", null);
pDialog = new SweetAlertDialog(this, SweetAlertDialog.PROGRESS_TYPE);
pDialog.getProgressHelper().setBarColor(Color.parseColor("#008753"));
pDialog.setTitleText("Loading...");
pDialog.setCancelable(false);
tvPleaseWait = (TextView) findViewById(R.id.tvPleaseWait);
tvPleaseWait.setText("");
if(installDate == null) {
//register app for trial
animateLoader(true);
CheckConnection();
} else {
//go to main activity and verify there if trial period is over
Intent i = new Intent(Trial.this, MainActivity.class);
startActivity(i);
// close this activity
finish();
}
}
public void CheckConnection() {
check = new Connection(this);
if (check.isConnected()) {
//trigger 'loadIMEI'
loadIMEI();
} else {
errorAlert("Check Connection", "Network is not detected");
tvPleaseWait.setText("Network is not detected");
animateLoader(false);
}
}
public boolean onKeyDown(int keyCode, KeyEvent event) {
//Changes 'back' button action
if (keyCode == KeyEvent.KEYCODE_BACK) {
finish();
}
return true;
}
public void animateLoader(boolean visibility) {
if (visibility)
pDialog.show();
else
pDialog.hide();
}
public void errorAlert(String title, String msg) {
new SweetAlertDialog(this, SweetAlertDialog.ERROR_TYPE)
.setTitleText(title)
.setContentText(msg)
.show();
}
/**
* Called when the 'loadIMEI' function is triggered.
*/
public void loadIMEI() {
// Check if the READ_PHONE_STATE permission is already available.
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
!= PackageManager.PERMISSION_GRANTED) {
// READ_PHONE_STATE permission has not been granted.
requestReadPhoneStatePermission();
} else {
// READ_PHONE_STATE permission is already been granted.
doPermissionGrantedStuffs();
}
}
/**
* Requests the READ_PHONE_STATE permission.
* If the permission has been denied previously, a dialog will prompt the user to grant the
* permission, otherwise it is requested directly.
*/
private void requestReadPhoneStatePermission() {
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.READ_PHONE_STATE)) {
// Provide an additional rationale to the user if the permission was not granted
// and the user would benefit from additional context for the use of the permission.
// For example if the user has previously denied the permission.
new AlertDialog.Builder(Trial.this)
.setTitle("Permission Request")
.setMessage(getString(R.string.permission_read_phone_state_rationale))
.setCancelable(false)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
//re-request
ActivityCompat.requestPermissions(Trial.this,
new String[]{Manifest.permission.READ_PHONE_STATE},
MY_PERMISSIONS_REQUEST_READ_PHONE_STATE);
}
})
.setIcon(R.drawable.warning_sigh)
.show();
} else {
// READ_PHONE_STATE permission has not been granted yet. Request it directly.
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_PHONE_STATE},
MY_PERMISSIONS_REQUEST_READ_PHONE_STATE);
}
}
/**
* Callback received when a permissions request has been completed.
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == MY_PERMISSIONS_REQUEST_READ_PHONE_STATE) {
// Received permission result for READ_PHONE_STATE permission.est.");
// Check if the only required permission has been granted
if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// READ_PHONE_STATE permission has been granted, proceed with displaying IMEI Number
//alertAlert(getString(R.string.permision_available_read_phone_state));
doPermissionGrantedStuffs();
} else {
alertAlert(getString(R.string.permissions_not_granted_read_phone_state));
}
}
}
private void alertAlert(String msg) {
new AlertDialog.Builder(Trial.this)
.setTitle("Permission Request")
.setMessage(msg)
.setCancelable(false)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// do somthing here
}
})
.setIcon(R.drawable.warning_sigh)
.show();
}
private void successAlert(String msg) {
new SweetAlertDialog(this, SweetAlertDialog.SUCCESS_TYPE)
.setTitleText("Success")
.setContentText(msg)
.setConfirmText("Ok")
.setConfirmClickListener(new SweetAlertDialog.OnSweetClickListener() {
@Override
public void onClick(SweetAlertDialog sDialog) {
sDialog.dismissWithAnimation();
// Prepare intent which is to be triggered
//Intent i = new Intent(Trial.this, MainActivity.class);
//startActivity(i);
}
})
.show();
}
public void doPermissionGrantedStuffs() {
//Have an object of TelephonyManager
TelephonyManager tm =(TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
//Get IMEI Number of Phone //////////////// for this example i only need the IMEI
String IMEINumber = tm.getDeviceId();
/************************************************
* **********************************************
* This is just an icing on the cake
* the following are other children of TELEPHONY_SERVICE
*
//Get Subscriber ID
String subscriberID=tm.getDeviceId();
//Get SIM Serial Number
String SIMSerialNumber=tm.getSimSerialNumber();
//Get Network Country ISO Code
String networkCountryISO=tm.getNetworkCountryIso();
//Get SIM Country ISO Code
String SIMCountryISO=tm.getSimCountryIso();
//Get the device software version
String softwareVersion=tm.getDeviceSoftwareVersion()
//Get the Voice mail number
String voiceMailNumber=tm.getVoiceMailNumber();
//Get the Phone Type CDMA/GSM/NONE
int phoneType=tm.getPhoneType();
switch (phoneType)
{
case (TelephonyManager.PHONE_TYPE_CDMA):
// your code
break;
case (TelephonyManager.PHONE_TYPE_GSM)
// your code
break;
case (TelephonyManager.PHONE_TYPE_NONE):
// your code
break;
}
//Find whether the Phone is in Roaming, returns true if in roaming
boolean isRoaming=tm.isNetworkRoaming();
if(isRoaming)
phoneDetails+="\nIs In Roaming : "+"YES";
else
phoneDetails+="\nIs In Roaming : "+"NO";
//Get the SIM state
int SIMState=tm.getSimState();
switch(SIMState)
{
case TelephonyManager.SIM_STATE_ABSENT :
// your code
break;
case TelephonyManager.SIM_STATE_NETWORK_LOCKED :
// your code
break;
case TelephonyManager.SIM_STATE_PIN_REQUIRED :
// your code
break;
case TelephonyManager.SIM_STATE_PUK_REQUIRED :
// your code
break;
case TelephonyManager.SIM_STATE_READY :
// your code
break;
case TelephonyManager.SIM_STATE_UNKNOWN :
// your code
break;
}
*/
// Now read the desired content to a textview.
//tvPleaseWait.setText(IMEINumber);
UserTrialRegistrationTask(IMEINumber);
}
/**
* Represents an asynchronous login task used to authenticate
* the user.
*/
private void UserTrialRegistrationTask(final String IMEINumber) {
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.GET, BASE_URL2+IMEINumber, null,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
Gson gson = new Gson();
TrialData result = gson.fromJson(String.valueOf(response), TrialData.class);
animateLoader(false);
if ("true".equals(result.getError())) {
errorAlert("Error", result.getResult());
tvPleaseWait.setText("Unknown Error");
} else if ("false".equals(result.getError())) {
//already created install/trial_start date using the server
// so just getting the date called back
Date before = null;
try {
before = (Date)formatter.parse(result.getResult());
} catch (ParseException e) {
e.printStackTrace();
}
Date now = new Date();
assert before != null;
long diff = now.getTime() - before.getTime();
long days = diff / ONE_DAY;
// save the date received
SharedPreferences.Editor editor = preferences.edit();
editor.putString("InstallDate", String.valueOf(days));
// Commit the edits!
editor.apply();
//go to main activity and verify there if trial period is over
Intent i = new Intent(Trial.this, MainActivity.class);
startActivity(i);
// close this activity
finish();
//successAlert(String.valueOf(days));
//if(days > 5) { // More than 5 days?
// Expired !!!
//}
}
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
animateLoader(false);
//errorAlert(error.toString());
errorAlert("Check Connection", "Could not establish a network connection.");
tvPleaseWait.setText("Network is not detected");
}
})
{
protected Map<String, String> getParams() {
Map<String, String> params = new HashMap<String, String>();
params.put(KEY_IMEI, IMEINumber);
return params;
}
};
RequestQueue requestQueue = Volley.newRequestQueue(this);
requestQueue.add(jsonObjectRequest);
}
}
내 PHP 파일은 다음과 같습니다 (REST-slim 기술).
/**
* registerTrial
*/
public function registerTrial($IMEINumber) {
//check if $IMEINumber already exist
// Instantiate DBH
$DBH = new PDO_Wrapper();
$DBH->query("SELECT date_reg FROM trials WHERE device_id = :IMEINumber");
$DBH->bind(':IMEINumber', $IMEINumber);
// DETERMINE HOW MANY ROWS OF RESULTS WE GOT
$totalRows_registered = $DBH->rowCount();
// DETERMINE HOW MANY ROWS OF RESULTS WE GOT
$results = $DBH->resultset();
if (!$IMEINumber) {
return 'Device serial number could not be determined.';
} else if ($totalRows_registered > 0) {
$results = $results[0];
$results = $results['date_reg'];
return $results;
} else {
// Instantiate variables
$trial_unique_id = es_generate_guid(60);
$time_reg = date('H:i:s');
$date_reg = date('Y-m-d');
$DBH->beginTransaction();
// opening db connection
//NOW Insert INTO DB
$DBH->query("INSERT INTO trials (time_reg, date_reg, date_time, device_id, trial_unique_id) VALUES (:time_reg, :date_reg, NOW(), :device_id, :trial_unique_id)");
$arrayValue = array(':time_reg' => $time_reg, ':date_reg' => $date_reg, ':device_id' => $IMEINumber, ':trial_unique_id' => $trial_unique_id);
$DBH->bindArray($arrayValue);
$subscribe = $DBH->execute();
$DBH->endTransaction();
return $date_reg;
}
}
그런 다음 주요 활동에서 공유 환경 설정 (평가판 활동에서 생성 된 installDate)을 사용하여 남은 일 수를 모니터링하고 일이 끝나면 구매를 위해 상점으로 이동하는 메시지와 함께 주요 활동 UI를 차단합니다.
내가 여기서 볼 수있는 유일한 단점 은 Rogue 사용자 가 유료 앱을 구입하고 Zender, 파일 공유와 같은 앱과 공유하기로 결정하거나 사람들이 무료로 다운로드 할 수 있도록 서버에서 직접 apk 파일을 호스팅하기로 결정한다는 것입니다. 그러나 곧 해결책이나 해결책에 대한 링크 로이 답변을 편집 할 것이라고 확신합니다.
이것이 영혼을 구하기를 바랍니다 ... 언젠가
해피 코딩 ...
@snctln 옵션 3은 php와 mysql이 많이 설치되어있는 웹 서버에 php 파일을 쉽게 추가 할 수 있습니다.
Android 측에서 식별자 (장치 ID, Google 계정 또는 원하는 모든 것)가 HttpURLConnection을 사용하여 URL의 인수로 전달되고 php는 테이블에있는 경우 첫 번째 설치 날짜를 반환하거나 새 행을 삽입하고 현재 날짜를 반환합니다.
그것은 나를 위해 잘 작동합니다.
시간이 있으면 코드를 게시하겠습니다!
행운을 빕니다 !