참고 : https://velog.io/@plashdof/%EC%95%8C%EB%9E%8C%EA%B6%8C%ED%95%9C-%EB%B6%84%EA%B8%B0%EC%B2%98%EB%A6%AC-Android-13-Android-12

 

targetSdkVersion 33

 

manifest 파일

 

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>

 

<uses-permission android:name="com.google.android.gms.permission.AD_ID"/> // 광고

 

private val requiredPermissionList = if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU){
    arrayOf(  // 안드로이드 13 이상 필요한 권한들
        Manifest.permission.READ_EXTERNAL_STORAGE,
        Manifest.permission.WRITE_EXTERNAL_STORAGE,
        Manifest.permission.READ_MEDIA_IMAGES,
        Manifest.permission.READ_MEDIA_VIDEO,
        Manifest.permission.POST_NOTIFICATIONS
    )
}else if ( Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
    arrayOf(  // 안드로이드 13 미만 마시멜로우 이상 필요한 권한들
        Manifest.permission.READ_EXTERNAL_STORAGE,
        Manifest.permission.WRITE_EXTERNAL_STORAGE
    )
}

 

 

// 권한 체크 이후로직
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grandResults) {
        // READ_PHONE_STATE의 권한 체크 결과를 불러온다
        if (requestCode == 1001) {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
                boolean check_result = true;
                // 모든 퍼미션을 허용했는지 체크
                for (int result : grandResults) {
                    if (result != PackageManager.PERMISSION_GRANTED) {
                        check_result = false;
                        break;
                    }
                }

                // 권한 체크에 동의를 하지 않으면 안드로이드 종료
                if (check_result == true) {
                } else {
                    finish();
                }
            }
        }
    }

Posted by 꼬장e
,

build.gradle 파일에 아래의 옵션을 추가한다.

android {
    lintOptions {
        checkReleaseBuilds false
        // Or, if you prefer, you can continue to check for errors in release builds,
        // but continue the build even when errors are found:
        abortOnError false
    }

Posted by 꼬장e
,

출처 : https://snowdeer.github.io/android/2017/03/26/notification-listener-service-sample ( snowdeer's Code Holic )

 

안드로이드 노티피케이션(Notification)의 정보를 가져오는 코드를 작성해보도록 하겠습니다. 안드로이드에서 NotificationListenerService라는 서비스 형태의 컴포넌트를 제공하고 있습니다. API 버전 18부터 사용가능하며, 원할하게 쓰려면 API 버전 19 이상을 추천합니다.

안드로이드 SDK에 기본으로 탑재되어 있기 때문에 gradle 등에 별도로 추가할 필요는 없고, manifest.xml에 다음과 같이 서비스를 등록해주면 됩니다.

 

AndroidManifest.xml

<application
  android:allowBackup="true"
  android:icon="@mipmap/ic_launcher"
  android:label="@string/app_name"
  android:supportsRtl="true"
  android:theme="@style/AppTheme">
  ...
  <service
    android:name=".SnowNotificationListenerService"
    android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
    <intent-filter>
      <action android:name="android.service.notification.NotificationListenerService"/>
    </intent-filter>
  </service>
</application>

 

그리고, Java 코드는 다음과 같습니다.

SnowNotificationListenerService.java

import android.app.Notification;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.util.Log;

public class SnowNotificationListenerService extends NotificationListenerService {

  @Override
  public void onCreate() {
    super.onCreate();
    Log.i("NotificationListener", "[snowdeer] onCreate()");
  }

  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
    Log.i("NotificationListener", "[snowdeer] onStartCommand()");
    return super.onStartCommand(intent, flags, startId);
  }

  @Override
  public void onDestroy() {
    super.onDestroy();
    Log.i("NotificationListener", "[snowdeer] onDestroy()");
  }

  @Override
  public void onNotificationPosted(StatusBarNotification sbn) {
    Log.i("NotificationListener", "[snowdeer] onNotificationPosted() - " + sbn.toString());
    Log.i("NotificationListener", "[snowdeer] PackageName:" + sbn.getPackageName());
    Log.i("NotificationListener", "[snowdeer] PostTime:" + sbn.getPostTime());

    Notification notificatin = sbn.getNotification();
    Bundle extras = notificatin.extras;
    String title = extras.getString(Notification.EXTRA_TITLE);
    int smallIconRes = extras.getInt(Notification.EXTRA_SMALL_ICON);
    Bitmap largeIcon = ((Bitmap) extras.getParcelable(Notification.EXTRA_LARGE_ICON));
    CharSequence text = extras.getCharSequence(Notification.EXTRA_TEXT);
    CharSequence subText = extras.getCharSequence(Notification.EXTRA_SUB_TEXT);

    Log.i("NotificationListener", "[snowdeer] Title:" + title);
    Log.i("NotificationListener", "[snowdeer] Text:" + text);
    Log.i("NotificationListener", "[snowdeer] Sub Text:" + subText);
  }

  @Override
  public void onNotificationRemoved(StatusBarNotification sbn) {
    Log.i("NotificationListener", "[snowdeer] onNotificationRemoved() - " + sbn.toString());
  }

}

 

참고로 이 서비스는 별도로 startService를 해주지 않아도, 사용자의 권한만 주어지면 자동으로 시작되기 때문에 startService를 구현할 필요는 없습니다.

사용자의 권한을 요청하는 코드는 다음과 같습니다.

Intent intent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
startActivity(intent);

 

하지만, 위 코드를 매번 호출하는 것은 사용자에게 상당히 번거로운 일이기 때문에, 다음과 같은 코드를 이용해서 기존에 사용자가 해당 App에 권한을 부여한 적이 있는지 확인하는 것이 좋습니다.

 

MainActivity.java

import android.content.Intent;
import android.support.v4.app.NotificationManagerCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import java.util.Set;

public class MainActivity extends AppCompatActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    boolean isPermissionAllowed = isNotiPermissionAllowed();

    if(!isPermissionAllowed) {
      Intent intent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
      startActivity(intent);
    }
  }

  private boolean isNotiPermissionAllowed() {
    Set<String> notiListenerSet = NotificationManagerCompat.getEnabledListenerPackages(this);
    String myPackageName = getPackageName();

    for(String packageName : notiListenerSet) {
      if(packageName == null) {
        continue;
      }
      if(packageName.equals(myPackageName)) {
        return true;
      }
    }

    return false;
  }
}
Posted by 꼬장e
,

출처 : http://it-archives.com/222114870620/  (흑곰의 유익한 블로그 2호점)

아이폰(iOS) 하이브리드 앱에서는 <input type=”file”> 을 사용하면 해당 요소를 클릭 시 파일선택 창(FileChooser)이 잘 뜨지만, 안드로이드 웹뷰에서는 <input type=”file”> 요소를 클릭해도 아무 반응이 없다.

안드로이드 하이브리드앱에서 파일을 선택하는 기능은 아래와 같이 구현 가능하다.

1. mFilePathCallback 변수를 전역변수로 선언한다.

webView 를 사용할 Activity 클래스(또는 Activity 를 상속받은 클래스) 에 선언하면 된다.

    // 안드로이드 웹뷰에서 파일 첨부하기
    ValueCallback mFilePathCallback = null;

2. WebChromeClient 클래스의 onShowFileChooser 메서드를 오버라이딩해서 아래와 같이 작성한다.

파일 n개 선택(파일 여러개 선택, 파일 다중선택) 하려면 intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); 코드를 포함시키자.

    webView.setWebChromeClient(new WebChromeClient() {
        // 안드로이드 웹뷰에서 파일 첨부하기
        @Override
        public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, FileChooserParams fileChooserParams) {
            mFilePathCallback = filePathCallback;

            Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
            intent.addCategory(Intent.CATEGORY_OPENABLE);
            intent.setType(“image/*”);

            // 파일 n개 선택 가능하도록 처리
            intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);

            startActivityForResult(intent, 0);
            return true;
        }
    });

만약 WebChromeClient 를 상속받아 별개의 자바파일로 구현했다면(ex : public class OOOWebChromeClient extends WebChromeClient {}), startActivityForResult 메서드를 곧바로 사용할 수 없을 것이다.

이 경우 WebChromeClient 생성자에서 activity 를 받아 thisActivity 라는 멤버변수에 저장해놓으면 thisActivity.startActivityForResult(intent, 0); 식으로 사용할 수 있다.

public class OOOWebChromeClient extends WebChromeClient {
    private final Activity thisActivity;

    public OOOWebChromeClient(Activity thisActivity) {
        super();
        this.thisActivity = thisActivity;
    }

    (중략)

}   

3. mFilePathCallback 변수가 선언된 클래스, 다시 말해서 Activify 클래스(또는 Activity를 상속한 클래스) 하단에 아래와 같이 작성한다.

참고로 파일을 n개 선택(여러개 선택, 다중선택)한 경우 data.getClipData() 값이 not null이며, 파일을 1개 선택한 경우 data.getData() 값이 not null이다.

    // 안드로이드 웹뷰에서 파일 첨부하기
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        Log.e(“resultCode:: “, String.valueOf(resultCode));
        if (requestCode == 0 && resultCode == Activity.RESULT_OK) {

            // 파일 n개 선택한 경우
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && data != null && data.getClipData() != null && data.getClipData().getItemCount() > 0) {
                int count = data.getClipData().getItemCount();

                Uri[] uriArr = new Uri[count];
                for (int i=0; i<count; i++) {
                    uriArr[i] = data.getClipData().getItemAt(i).getUri();
                }

                mFilePathCallback.onReceiveValue(uriArr);

            } else if (data != null && data.getData() != null) {
                // 파일 1개 선택한 경우
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    mFilePathCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data));
                } else {
                    mFilePathCallback.onReceiveValue(new Uri[]{data.getData()});
                }
            }

            mFilePathCallback = null;

        } else {
            mFilePathCallback.onReceiveValue(null);
        }
    }

만약 WebChromeClient 를 상속받아 별개의 자바파일로 구현했다면(ex : public class OOOWebChromeClient extends WebChromeClient {}), mFilePathCallback 변수를 사용할 수 없을 것이다. OOOWebChromeClien 클래스 내에 mFilePathCallback 의 getter, setter 메서드를 만들어서 접근하도록 하자.

Posted by 꼬장e
,

기본

 

app

 

 

Posted by 꼬장e
,