Eu tenho um aplicativo que desejo chegar ao mercado como um aplicativo pago. Eu gostaria de ter outra versão que seria uma versão de "teste" com um limite de tempo de, digamos, 5 dias?
Como posso fazer isso?
Eu tenho um aplicativo que desejo chegar ao mercado como um aplicativo pago. Eu gostaria de ter outra versão que seria uma versão de "teste" com um limite de tempo de, digamos, 5 dias?
Como posso fazer isso?
Respostas:
Atualmente, a maioria dos desenvolvedores faz isso usando uma das três técnicas a seguir.
A primeira abordagem é facilmente contornada: na primeira vez que você executar o aplicativo, salve a data / hora em um arquivo, banco de dados ou preferências compartilhadas e sempre que executar o aplicativo depois disso, verifique se o período de teste terminou. Isso é fácil de contornar porque desinstalar e reinstalar permitirá que o usuário tenha outro período de teste.
A segunda abordagem é mais difícil de contornar, mas ainda assim contornável. Use uma bomba-relógio codificada. Basicamente, com essa abordagem, você codificará uma data de término para o teste e todos os usuários que fizerem o download e usarem o aplicativo deixarão de poder usá-lo ao mesmo tempo. Usei essa abordagem porque é fácil de implementar e, na maior parte do tempo, simplesmente não estava com vontade de passar pelos problemas da terceira técnica. Os usuários podem contornar isso alterando manualmente a data em seus telefones, mas a maioria dos usuários não terá o trabalho de fazer isso.
A terceira técnica é a única maneira sobre a qual ouvi falar para realmente conseguir realizar o que você deseja. Você terá que configurar um servidor e, em seguida, sempre que seu aplicativo for iniciado, ele enviará o identificador exclusivo do telefone para o servidor. Se o servidor não tiver uma entrada para esse id de telefone, ele cria um novo e anota a hora. Se o servidor não tiver uma entrada para a identificação do telefone, ele fará uma verificação simples para ver se o período de teste expirou. Em seguida, ele comunica os resultados da verificação de expiração do teste ao seu aplicativo. Esta abordagem não deve ser contornável, mas requer a configuração de um servidor web e tal.
É sempre uma boa prática fazer essas verificações no onCreate. Se a expiração terminou, aparecerá um AlertDialog com um link de mercado para a versão completa do aplicativo. Inclua apenas um botão "OK" e, quando o usuário clicar em "OK", faça uma chamada para "terminar ()" para encerrar a atividade.
Eu desenvolvi um Android Trial SDK que você pode simplesmente colocar em seu projeto Android Studio e ele cuidará de todo o gerenciamento do lado do servidor para você (incluindo períodos de carência offline).
Para usá-lo, basta
Adicione a biblioteca ao seu módulo principal build.gradle
dependencies {
compile 'io.trialy.library:trialy:1.0.2'
}
Inicialize a biblioteca no onCreate()
método da sua atividade principal
@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);
}
Adicione um gerenciador de retorno de chamada:
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));
}
};
Para iniciar um teste, chame mTrialy.startTrial("YOUR_TRIAL_SKU", mTrialyCallback);
sua chave de aplicativo e o SKU de teste pode ser encontrado no painel do desenvolvedor Trialy .
Essa é uma questão antiga, mas de qualquer forma, talvez isso ajude alguém.
No caso de você querer ir com a abordagem mais simplista (que irá falhar se o aplicativo for desinstalado / reinstalado ou o usuário alterar a data do dispositivo manualmente), pode ser assim:
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
não é getTimeInMillis
.
Essa pergunta e a resposta do snctln me inspiraram a trabalhar em uma solução baseada no método 3 como minha tese de bacharelado. Sei que o status atual não é para uso produtivo, mas adoraria saber o que você acha disso! Você usaria tal sistema? Você gostaria de vê-lo como um serviço em nuvem (sem problemas para configurar um servidor)? Preocupado com questões de segurança ou motivos de estabilidade?
Assim que terminar o procedimento de bacharelado, quero continuar trabalhando no software. Agora é a hora de eu precisar de seus comentários!
Sourcecode está hospedado no GitHub https://github.com/MaChristmann/mobile-trial
Algumas informações sobre o sistema: - O sistema tem três partes, uma biblioteca Android, um servidor node.js e um configurador para gerenciar vários aplicativos de teste e contas de editor / desenvolvedor.
Ele só oferece suporte a testes baseados em tempo e usa sua conta (da Play Store ou outra) em vez de um ID de telefone.
Para a biblioteca Android, é baseado na biblioteca de verificação de licenciamento do Google Play. Eu o modifiquei para se conectar ao servidor node.js e, além disso, a biblioteca tenta reconhecer se um usuário alterou a data do sistema. Ele também armazena em cache uma licença de teste recuperada em Preferências compartilhadas criptografadas AES. Você pode configurar o tempo de validade do cache com o configurador. Se um usuário "limpar os dados", a biblioteca forçará uma verificação do lado do servidor.
O servidor está usando https e também assinando digitalmente a resposta de verificação de licença. Ele também tem uma API para aplicativos e usuários de teste CRUD (editor e desenvolvedor). Semelhante ao licenciamento, os desenvolvedores da Verfication Library podem testar a implementação de seu comportamento no aplicativo de teste com o resultado do teste. Assim, no configurador, você pode definir explicitamente a resposta da licença como "licenciada", "não licenciada" ou "erro de servidor".
Se você atualizar seu aplicativo com um novo recurso arrasador, convém que todos possam tentar novamente. No configurador, você pode renovar a licença de teste para usuários com licenças expiradas, definindo um código de versão que deve acionar isso. Por exemplo, o usuário está executando seu aplicativo no código de versão 3 e você deseja que ele experimente os recursos do código de versão 4. Se ele atualizar o aplicativo ou reinstalá-lo, ele poderá usar o período de teste completo novamente porque o servidor sabe em qual versão ele tentou pela última vez Tempo.
Tudo está sob a licença Apache 2.0
A maneira mais fácil e melhor de fazer isso é implementar BackupSharedPreferences.
As preferências são preservadas, mesmo se o aplicativo for desinstalado e reinstalado.
Basta salvar a data de instalação de preferência e pronto.
Esta é a teoria: http://developer.android.com/reference/android/app/backup/SharedPreferencesBackupHelper.html
Este é o exemplo: Android SharedPreferences Backup Not Working
Abordagem 4: use o tempo de instalação do aplicativo.
Desde nível API 9 (Android 2.3.2, 2.3.1, Android 2.3, Gingerbread) existem firstInstallTime e lastUpdateTime no PackageInfo
.
Para ler mais: Como obter o tempo de instalação do aplicativo no Android
Agora que a versão recente da assinatura de teste gratuita do Android foi adicionada, você pode desbloquear todos os recursos do seu aplicativo somente após comprar a assinatura dentro do aplicativo por um período de teste gratuito. Isso permitirá que o usuário use seu aplicativo por um período de avaliação. Se o aplicativo ainda for desinstalado após o período de avaliação, o dinheiro da assinatura será transferido para você. Ainda não tentei, mas apenas compartilhando uma ideia.
Em minha opinião, a melhor maneira de fazer isso é simplesmente usar o Firebase Realtime Database:
1) Adicione suporte do Firebase ao seu aplicativo
2) Selecione 'Autenticação anônima' para que o usuário não precise se inscrever ou mesmo saber o que está fazendo. Isso garante um link para a conta do usuário autenticado no momento e, portanto, funcionará em todos os dispositivos.
3) Use a API Realtime Database para definir um valor para 'installed_date'. No momento da inicialização, basta recuperar esse valor e usá-lo.
Eu fiz o mesmo e funciona muito bem. Consegui testar isso na desinstalação / reinstalação e o valor no banco de dados em tempo real permanece o mesmo. Desta forma, seu período de teste funciona em vários dispositivos de usuário. Você pode até criar uma versão de install_date para que o aplicativo 'redefina' a data de teste para cada novo lançamento principal.
ATUALIZAÇÃO : depois de testar um pouco mais, parece que o Firebase anônimo aloca um ID diferente no caso de você ter dispositivos diferentes e não é garantido entre as reinstalações: / A única maneira garantida é usar o Firebase, mas vinculá-lo ao Google conta. Isso deve funcionar, mas exigiria uma etapa extra em que o usuário primeiro precisa fazer o login / inscrição.
Até agora, acabei com uma abordagem um pouco menos elegante de simplesmente verificar as preferências de backup e uma data armazenada nas preferências durante a instalação. Isso funciona para aplicativos centrados em dados, onde é inútil para uma pessoa reinstalar o aplicativo e inserir novamente todos os dados adicionados anteriormente, mas não funcionaria para um jogo simples.
Depois de examinar todas as opções neste e em outros tópicos, estas são minhas conclusões
Preferências compartilhadas, banco de dados Podem ser apagadas nas configurações do Android, perdidas após a reinstalação de um aplicativo. Pode ser feito backup com o mecanismo de backup do Android e será restaurado após a reinstalação. O backup pode nem sempre estar disponível, embora deva estar na maioria dos dispositivos
Armazenamento externo (gravação em um arquivo) Não afetado por uma limpeza das configurações ou uma reinstalação se não gravarmos no diretório privado do aplicativo . Mas: requer que você peça permissão ao usuário no tempo de execução nas versões mais recentes do Android, então isso provavelmente só é viável se você precisar dessa permissão de qualquer maneira. Também pode ser feito backup.
PackageInfo.firstInstallTime é redefinido após a reinstalação, mas estável entre as atualizações
Faça login em alguma conta Não importa se é sua conta do Google via Firebase ou uma em seu próprio servidor: o teste está vinculado à conta. Criar uma nova conta reiniciará o teste.
Login anônimo do Firebase Você pode fazer login de um usuário anonimamente e armazenar dados para ele no Firebase. Mas, aparentemente, uma reinstalação do aplicativo e talvez outros eventos não documentados podem dar ao usuário uma nova ID anônima , reiniciando seu tempo de teste. (O próprio Google não fornece muita documentação sobre isso)
ANDROID_ID Pode não estar disponível e pode mudar sob certas circunstâncias , por exemplo, redefinição de fábrica. As opiniões sobre se é uma boa ideia usar isso para identificar dispositivos parecem divergentes.
O ID de publicidade do Google Play pode ser redefinido pelo usuário. Pode ser desativado pelo usuário optando por sair do rastreamento de anúncios.
InstanceID Redefinir em uma reinstalação . Reinicialize no caso de um evento de segurança. Pode ser redefinido pelo seu aplicativo.
Quais (combinações de) métodos funcionam para você depende do seu aplicativo e de quanto esforço você acha que o John médio fará para obter outro período de teste. Eu recomendaria evitar o uso de apenas Firebase anônimo e ID de publicidade devido à instabilidade deles. Uma abordagem multifatorial parece produzir os melhores resultados. Os fatores disponíveis para você dependem de seu aplicativo e de suas permissões.
Para meu próprio aplicativo, descobri que as preferências compartilhadas + firstInstallTime + backup das preferências são o método menos intrusivo, mas também eficaz o suficiente. Você deve certificar-se de solicitar um backup apenas após verificar e armazenar a hora de início do teste nas preferências compartilhadas. Os valores nos Prefs compartilhados devem ter precedência sobre o firstInstallTime. Em seguida, o usuário tem que reinstalar o aplicativo, executá-lo uma vez e, em seguida, limpar os dados do aplicativo para redefinir o teste, o que é muito trabalhoso. Em dispositivos sem transporte de backup, o usuário pode reiniciar o teste simplesmente reinstalando.
Disponibilizei essa abordagem como uma biblioteca extensível .
Por definição, todos os aplicativos Android pagos no mercado podem ser avaliados por 24 horas após a compra.
Existe um botão 'Desinstalar e Reembolsar' que muda para 'Desinstalar' após 24 horas.
Eu diria que esse botão é proeminente demais!
Eu me deparei com essa pergunta enquanto procurava o mesmo problema, acho que podemos utilizar a API de data gratuita como http://www.timeapi.org/utc/now ou alguma outra API de data para verificar a validade do aplicativo de trilha. dessa forma é eficiente se você deseja entregar a demonstração e está preocupado com o pagamento e requer uma demonstração de posse fixa. :)
encontre o código abaixo
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
}
}
sua solução de trabalho .....
Aqui está como eu fiz o meu, criei 2 aplicativos, um com atividade de teste e o outro sem,
carreguei aquele sem atividade de teste para a Play Store como um aplicativo pago
e aquele com atividade de teste como aplicativo gratuito.
O aplicativo gratuito no primeiro lançamento tem opções de teste e compra na loja, se o usuário selecionar a compra na loja, ele redireciona para a loja para o usuário comprar, mas se o usuário clicar em teste, ele o levará para a atividade de teste
NB: usei a opção 3 como @snctln, mas com modificações
primeiro , eu não dependia do tempo do dispositivo, obtive meu tempo do arquivo php que faz o registro de teste no banco de dados,
em segundo lugar , usei o número de série do dispositivo para identificar exclusivamente cada dispositivo,
por último , o aplicativo depende do valor de tempo retornado da conexão do servidor e não de seu próprio tempo, então o sistema só pode ser contornado se o número de série do dispositivo for alterado, o que é bastante estressante para o usuário.
então aqui vai meu código (para a atividade de teste):
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);
}
}
Meu arquivo php se parece com isto (é uma tecnologia 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;
}
}
depois, na atividade principal, uso a preferência compartilhada (installDate criado na atividade de teste) para monitorar o número de dias restantes e, se os dias se esgotarem, bloqueio a IU da atividade principal com uma mensagem que os leva à loja para fazer a compra.
A única desvantagem que vejo aqui é que, se um usuário Rogue comprar o aplicativo pago e decidir compartilhar com aplicativos como o Zender, compartilhe arquivos ou mesmo hospede o arquivo apk diretamente em um servidor para as pessoas baixarem gratuitamente. Mas tenho certeza que em breve editarei esta resposta com uma solução para isso ou um link para a solução.
Espero que isso salve uma alma ... algum dia
Boa codificação ...
@snctln opção 3 pode ser facilmente feita adicionando um arquivo php a um servidor web com php e mysql instalados como muitos deles.
Do lado do Android, um identificador (o ID do dispositivo, conta do Google ou o que você quiser) é passado como argumento no URL usando HttpURLConnection e o php retorna a data da primeira instalação se existir na tabela ou insere uma nova linha e ele retorna a data atual.
Isso funciona bem para mim.
Se eu tiver tempo, postarei algum código!
Boa sorte !